diff --git a/apps/full-app/README.md b/apps/full-app/README.md index 2dc3a45bc..61e677392 100644 --- a/apps/full-app/README.md +++ b/apps/full-app/README.md @@ -61,10 +61,17 @@ pnpm --filter full-app dev Open `http://localhost:5173/dev-login`. The `.env` defaults bind the backend and Vite to loopback, enable only the non-production dev-login helper, and intentionally leave mail unset so local accounts are not blocked on email verification. The helper also rejects non-loopback clients, including clients forwarded through the Vite dev proxy. Uncomment both mail values when testing verification flows. Stop Postgres with `docker compose -f apps/full-app/docker-compose.dev.yml down`; its named `postgres-data` volume persists for the next interactive run. Use `down --volumes` only when intentionally resetting local data. Override only the interactive Postgres host port with `BORING_DEV_POSTGRES_PORT`; the smoke always supplies an allocated value and removes its own uniquely named volume. -### Hosted automation trigger +### Hosted automation scheduler -Set `BORING_AUTOMATION_TRIGGER_TOKEN` to a deployment secret. The platform -scheduler invokes: +The hosted Automation plugin starts an internal Croner wake-up once per minute +by default and evaluates the current minute once when Fastify becomes ready. +Each automation creator is re-authorized before execution, overlapping ticks in +one process are skipped, and database constraints prevent duplicate active or +scheduled-minute runs across processes. + +For a deployment that intentionally uses an external scheduler, set +`BORING_AUTOMATION_INTERNAL_SCHEDULER=false` and set +`BORING_AUTOMATION_TRIGGER_TOKEN` to a deployment secret. Invoke: ```bash curl --fail --silent --show-error -X POST \ @@ -72,8 +79,8 @@ curl --fail --silent --show-error -X POST \ http://localhost:5173/api/v1/boring-automation/due/hosted ``` -The token is service-principal authentication only; each automation creator is -re-authorized before execution. The plugin does not start a timer. +The token authenticates only the external service principal. The endpoint stays +available as an operational fallback when the internal scheduler is enabled. ## Scripts @@ -129,6 +136,7 @@ Common optional: | `PORT` / `HOST` / `LOG_LEVEL` | `3000` / `0.0.0.0` / `info` | HTTP server. The local `.env.example` narrows `HOST` to `127.0.0.1`. | | `CORS_ORIGINS` | `http://localhost:3000,http://localhost:5173` | Allowed origins | | `BORING_PLUGIN_AUTHORING` | `0` | `1` installs the plugin-authoring surface | +| `BORING_AUTOMATION_INTERNAL_SCHEDULER` | `true` | Set to `false` only when an external scheduler owns hosted Automation wake-ups | | `ENABLE_DEV_LOGIN` | `0` | Dev server only. Set `1` to enable `GET /dev-login`, which creates/signs in a local dev user and redirects to `/`. Ignored in `NODE_ENV=production`. | | `DEV_LOGIN_EMAIL`, `DEV_LOGIN_PASSWORD`, `DEV_LOGIN_NAME` | `dev@example.test`, strong local password, `Dev` | Optional credentials for `ENABLE_DEV_LOGIN=1`. | | `RESEND_API_KEY` | — | Resend mail transport | diff --git a/apps/full-app/package.json b/apps/full-app/package.json index 2b5e14327..bd02b114b 100644 --- a/apps/full-app/package.json +++ b/apps/full-app/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build:deps": "pnpm --filter 'full-app^...' --workspace-concurrency=4 run --if-present build", - "dev": "pnpm run build:deps && NODE_ENV=development node --env-file-if-exists=.env --import tsx src/server/dev.ts", + "dev": "pnpm run build:deps && pnpm run migrate && NODE_ENV=development node --env-file-if-exists=.env --import tsx src/server/dev.ts", "build": "pnpm run build:deps && tsx ../../packages/workspace/scripts/build-app.mts", "start": "NODE_ENV=production node dist/server/main.js", "start:worker": "NODE_ENV=production node dist/server/agent-worker.js", diff --git a/apps/full-app/src/front/main.test.ts b/apps/full-app/src/front/main.test.ts index 4851ae273..5b47bb121 100644 --- a/apps/full-app/src/front/main.test.ts +++ b/apps/full-app/src/front/main.test.ts @@ -25,6 +25,7 @@ describe('full-app production chat composition', () => { expect(workspaceFrontPropsSource).toMatch(/\baddressedAgentSelection\b/) expect(workspaceFrontPropsSource).toMatch(/\buseAddressedAgentSelection=\{useAddressedAgentSelection\}/) expect(workspaceFrontPropsSource).toMatch(/\bworkspaceLayout="plugin-tabs"/) + expect(workspaceFrontPropsSource).not.toMatch(/\bagentTypeId\s*=/) expect(chatParamsSource).not.toMatch(/\bagentTypeId\s*:/) expect(source).toMatch(/\bchatParams=\{chatParams\}/) expect(source).toMatch(/\bchatFirstPublicWorkspaceProps=\{\{[\s\S]*\baddressedAgentSelection:\s*false\b/) diff --git a/apps/full-app/src/front/main.tsx b/apps/full-app/src/front/main.tsx index 2c0e3deb5..3f46445ff 100644 --- a/apps/full-app/src/front/main.tsx +++ b/apps/full-app/src/front/main.tsx @@ -18,10 +18,12 @@ import { import '@hachej/boring-core/app/front/styles.css' import { GovernanceUsagePanel, createGovernanceCompanyAdmin } from '@hachej/boring-governance/front' import { BoringMcpSourcesOverlay } from '@hachej/boring-mcp/front' +import boringAutomationPlugin from '@hachej/boring-automation/front' import { PublicHeroDescription } from './PublicHeroDescription' import { fullAppBoringMcpOptions } from './boringMcp' const PRODUCT_NAME = 'Seneca AI' +const fullAppFrontPlugins = [boringAutomationPlugin] // Show the Buy-credits button when the server has Lemon Squeezy checkout wired // (set this alongside the server-side LS env). The checkout itself is created @@ -122,6 +124,7 @@ const chatParams = { createRoot(document.getElementById('root')!).render( <>