From 0900eed92843186d53117d829e06ad319448b322 Mon Sep 17 00:00:00 2001 From: Julien Hurault Date: Fri, 31 Jul 2026 13:18:49 +0200 Subject: [PATCH 1/2] #896 fix(automation): run hosted schedules internally (#906) * #896 fix(automation): run hosted schedules internally * #896 test(automation): prove minute wakeup and session run * #896 fix(automation): adapt scheduler to AgentHost lifecycle * #863 fix(full-app): wire default front plugins * #863 fix(full-app): build automation front package * #896 fix(automation): migrate dev DB before runs * #896 fix(full-app): address hosted agent sessions * #896 feat(automation): prefix run session titles * #896 feat(automation): add pause and last-run status * #896 fix(automation): refresh scheduled runs live * #896 feat(automation): stream run updates * #896 fix(automation): recover abandoned hosted runs * #896 fix(ci): decouple migrations from runtime secrets * #863 refactor(full-app): register automation directly * #863 test(full-app): remove brittle source parity check * #896 docs: plan managed host worker lifecycle * #896 refactor(automation): manage scheduler as a host worker * fix(cli): use Fastify 5 static header API * #896 refactor(automation): rely on durable shutdown recovery * refactor(core): keep database resolution migration-local * refactor(core): reuse file secret resolution for migrations --- apps/full-app/README.md | 18 +- apps/full-app/package.json | 4 +- apps/full-app/src/front/main.tsx | 4 + docs/issues/590/plan.md | 13 +- .../workspaceAgentDispatcher.test.ts | 8 +- .../src/server/workspaceAgentDispatcher.ts | 2 +- .../src/shared/workspaceAgentDispatcher.ts | 2 + .../workspacesModeRuntimePlugins.test.ts | 4 +- packages/cli/src/server/modeApps.ts | 8 +- .../src/server/__tests__/migrations.test.ts | 39 ++++ packages/core/src/server/db/migrate.ts | 2 +- packages/core/src/server/migrations.ts | 16 +- plugins/boring-automation/README.md | 12 +- .../src/front/AutomationCard.tsx | 39 +++- .../src/front/AutomationPanel.tsx | 122 ++++++++--- .../front/__tests__/AutomationPanel.test.tsx | 73 ++++++- .../src/front/__tests__/client.test.ts | 33 +++ plugins/boring-automation/src/front/client.ts | 58 ++++- .../src/server/__tests__/fileStore.test.ts | 26 +++ .../__tests__/hostedDueCoordinator.test.ts | 48 +++++ .../__tests__/hostedDueRunService.test.ts | 204 ++++++++++++++++++ .../server/__tests__/hostedScheduler.test.ts | 125 +++++++++++ .../__tests__/manualRunExecutor.test.ts | 115 +++++++++- .../src/server/__tests__/migrations.test.ts | 78 +++++++ .../__tests__/nestedToolDispatch.test.ts | 6 + .../src/server/__tests__/operations.test.ts | 2 + .../server/__tests__/postgresStore.test.ts | 33 ++- .../src/server/__tests__/routes.test.ts | 85 ++++++++ .../src/server/__tests__/runEventBus.test.ts | 65 ++++++ .../src/server/__tests__/serverPlugin.test.ts | 120 +++++++++++ .../src/server/automationTool.ts | 1 + .../boring-automation/src/server/fileStore.ts | 34 ++- .../src/server/hostedDueCoordinator.ts | 28 +++ .../src/server/hostedDueRunService.ts | 51 ++++- .../src/server/hostedScheduler.ts | 102 +++++++++ plugins/boring-automation/src/server/index.ts | 72 +++++-- .../src/server/manualRunExecutor.ts | 106 +++++++-- .../src/server/postgresStore.ts | 95 +++++++- .../boring-automation/src/server/routes.ts | 65 +++++- .../src/server/runEventBus.ts | 110 ++++++++++ plugins/boring-automation/src/server/store.ts | 8 +- .../src/shared/__tests__/schedule.test.ts | 10 +- .../src/shared/error-codes.ts | 1 + .../boring-automation/src/shared/schedule.ts | 10 +- plugins/boring-automation/src/shared/types.ts | 11 + .../workspace-components/src/fixtures.tsx | 3 + 46 files changed, 1930 insertions(+), 141 deletions(-) create mode 100644 packages/core/src/server/__tests__/migrations.test.ts create mode 100644 plugins/boring-automation/src/server/__tests__/hostedDueCoordinator.test.ts create mode 100644 plugins/boring-automation/src/server/__tests__/hostedDueRunService.test.ts create mode 100644 plugins/boring-automation/src/server/__tests__/hostedScheduler.test.ts create mode 100644 plugins/boring-automation/src/server/__tests__/runEventBus.test.ts create mode 100644 plugins/boring-automation/src/server/hostedDueCoordinator.ts create mode 100644 plugins/boring-automation/src/server/hostedScheduler.ts create mode 100644 plugins/boring-automation/src/server/runEventBus.ts diff --git a/apps/full-app/README.md b/apps/full-app/README.md index 669d7b95e..734bb00dd 100644 --- a/apps/full-app/README.md +++ b/apps/full-app/README.md @@ -52,10 +52,17 @@ pnpm --filter full-app dev Open `http://localhost:5173`. -### 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 \ @@ -63,8 +70,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 @@ -119,6 +126,7 @@ Common optional: | `PORT` / `HOST` / `LOG_LEVEL` | `3000` / `0.0.0.0` / `info` | HTTP server | | `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 23b3209ed..ccc6699f2 100644 --- a/apps/full-app/package.json +++ b/apps/full-app/package.json @@ -4,8 +4,8 @@ "private": true, "type": "module", "scripts": { - "dev": "pnpm --filter @hachej/boring-ui-kit build && pnpm --filter @hachej/boring-sandbox build && pnpm --filter @hachej/boring-bash build && pnpm --filter @hachej/boring-agent build && pnpm --filter @hachej/boring-workspace build && pnpm --filter @hachej/boring-core build && pnpm --filter @hachej/boring-mcp build && pnpm --filter @hachej/boring-governance build && NODE_ENV=development tsx src/server/dev.ts", - "build": "pnpm --filter @hachej/boring-ui-kit build && pnpm --filter @hachej/boring-sandbox build && pnpm --filter @hachej/boring-bash build && pnpm --filter @hachej/boring-agent build && pnpm --filter @hachej/boring-workspace build && pnpm --filter @hachej/boring-core build && pnpm --filter @hachej/boring-mcp build && pnpm --filter @hachej/boring-governance build && tsx ../../packages/workspace/scripts/build-app.mts", + "dev": "pnpm --filter @hachej/boring-ui-kit build && pnpm --filter @hachej/boring-sandbox build && pnpm --filter @hachej/boring-bash build && pnpm --filter @hachej/boring-agent build && pnpm --filter @hachej/boring-workspace build && pnpm --filter @hachej/boring-core build && pnpm --filter @hachej/boring-automation build && pnpm --filter @hachej/boring-mcp build && pnpm --filter @hachej/boring-governance build && NODE_ENV=development tsx src/server/migrate.ts && NODE_ENV=development tsx src/server/dev.ts", + "build": "pnpm --filter @hachej/boring-ui-kit build && pnpm --filter @hachej/boring-sandbox build && pnpm --filter @hachej/boring-bash build && pnpm --filter @hachej/boring-agent build && pnpm --filter @hachej/boring-workspace build && pnpm --filter @hachej/boring-core build && pnpm --filter @hachej/boring-automation build && pnpm --filter @hachej/boring-mcp build && pnpm --filter @hachej/boring-governance build && 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", "migrate": "tsx src/server/migrate.ts", diff --git a/apps/full-app/src/front/main.tsx b/apps/full-app/src/front/main.tsx index 0c2249219..1615d96b6 100644 --- a/apps/full-app/src/front/main.tsx +++ b/apps/full-app/src/front/main.tsx @@ -17,10 +17,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 @@ -121,6 +123,8 @@ const chatParams = { createRoot(document.getElementById('root')!).render( <>