@aitne/daemon 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.d.ts +1 -0
- package/dist/api/env-writer.js +17 -7
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent-schedule.js +5 -1
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/apple-calendar.js +4 -1
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/calendar.js +12 -2
- package/dist/api/routes/context/path-resolve.js +6 -1
- package/dist/api/routes/context/permissions.js +12 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +58 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/dashboard/oauth-google.js +5 -3
- package/dist/api/routes/feedback.d.ts +3 -0
- package/dist/api/routes/feedback.js +349 -0
- package/dist/api/routes/git.js +10 -3
- package/dist/api/routes/github.js +5 -1
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/mcp.js +65 -13
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +12 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +246 -8
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +32 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +38 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +47 -18
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +193 -5
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +11 -1
- package/dist/core/context-paths.js +17 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +50 -1
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +24 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
- package/dist/core/dispatcher-scheduled-tasks.js +480 -8
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +297 -60
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.d.ts +94 -0
- package/dist/core/feedback/consolidation-prep.js +254 -0
- package/dist/core/feedback/eviction-scorer.d.ts +81 -0
- package/dist/core/feedback/eviction-scorer.js +136 -0
- package/dist/core/feedback/lesson-format.d.ts +79 -0
- package/dist/core/feedback/lesson-format.js +199 -0
- package/dist/core/feedback/lesson-injection.d.ts +98 -0
- package/dist/core/feedback/lesson-injection.js +174 -0
- package/dist/core/feedback/lesson-merge.d.ts +51 -0
- package/dist/core/feedback/lesson-merge.js +88 -0
- package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
- package/dist/core/feedback/lesson-store-overview.js +42 -0
- package/dist/core/feedback/promotion-gate.d.ts +69 -0
- package/dist/core/feedback/promotion-gate.js +117 -0
- package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
- package/dist/core/feedback/regeneralization-prep.js +152 -0
- package/dist/core/feedback/scope-parser.d.ts +86 -0
- package/dist/core/feedback/scope-parser.js +141 -0
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +83 -1
- package/dist/core/injection-policy.js +61 -3
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +51 -1
- package/dist/core/signal-detector.js +321 -24
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +60 -14
- package/dist/core/today-direct-writer.js +90 -13
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/wiki/wiki-fts.js +13 -6
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/feedback-signals-store.d.ts +77 -0
- package/dist/db/feedback-signals-store.js +144 -0
- package/dist/db/migrations.js +380 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +260 -22
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/always-disallowed.d.ts +1 -1
- package/dist/safety/always-disallowed.js +39 -0
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +97 -18
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +34 -8
- package/dist/services/browser-history/lifecycle/platform.js +44 -2
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/mcp/probe.js +30 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +45 -12
- package/dist/settings/runtime-settings.js +215 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -285,6 +285,30 @@ const API_RISK = {
|
|
|
285
285
|
"GET /api/browser-task/{*}/screenshots/{*}": RiskTier.ReadSensitive,
|
|
286
286
|
"POST /api/browser-task/{*}/clarify": RiskTier.Autonomous,
|
|
287
287
|
"POST /api/browser-task/{*}/cancel": RiskTier.Autonomous,
|
|
288
|
+
// ── Background Task (BACKGROUND_TASK_RUNNER_DESIGN.md §7) ──
|
|
289
|
+
// Generic detached long-task surface, cloned from browser-task and
|
|
290
|
+
// classified to the same posture. The only production callers are the
|
|
291
|
+
// DM-agent `background-task` / `background-task-reply` skills and the
|
|
292
|
+
// morning-briefing session, whose curl shim carries `x-read-token`
|
|
293
|
+
// (sufficient for ReadSensitive, never enough for Approve). The same
|
|
294
|
+
// loopback / sec-fetch / channel-attestation defenses as browser-task
|
|
295
|
+
// apply, and every dispatched task surfaces to the user via DM — no
|
|
296
|
+
// covert dispatch path.
|
|
297
|
+
// - POST (spawn) + clarify + cancel are Autonomous, matching the
|
|
298
|
+
// sibling agent-driven write paths (design §7: "same posture as
|
|
299
|
+
// /api/browser-task").
|
|
300
|
+
// - The list + detail reads stay ReadSensitive (NOT Autonomous):
|
|
301
|
+
// the artifact (`report` / `brief` / `draft` / `significance`)
|
|
302
|
+
// carries personal research / audit content, exactly the reason
|
|
303
|
+
// browser-task's reads are ReadSensitive. Reachable from the agent
|
|
304
|
+
// sessions that need them because their curl carries the read
|
|
305
|
+
// token (cf. GET /api/observations, also ReadSensitive, which the
|
|
306
|
+
// briefing already calls).
|
|
307
|
+
"POST /api/background-task": RiskTier.Autonomous,
|
|
308
|
+
"GET /api/background-task": RiskTier.ReadSensitive,
|
|
309
|
+
"GET /api/background-task/{*}": RiskTier.ReadSensitive,
|
|
310
|
+
"POST /api/background-task/{*}/clarify": RiskTier.Autonomous,
|
|
311
|
+
"POST /api/background-task/{*}/cancel": RiskTier.Autonomous,
|
|
288
312
|
"/api/setup": RiskTier.Approve,
|
|
289
313
|
"POST /api/setup/redetect-browsers": RiskTier.Approve,
|
|
290
314
|
// Management Mode Phase 2 — migration endpoint. Redundant with the
|
|
@@ -451,6 +475,22 @@ const API_RISK = {
|
|
|
451
475
|
"PUT /api/observations/{*}/consume": RiskTier.Autonomous,
|
|
452
476
|
"PATCH /api/observations/{*}/consume": RiskTier.Autonomous,
|
|
453
477
|
"DELETE /api/observations/{*}/consume": RiskTier.Autonomous,
|
|
478
|
+
"POST /api/feedback": RiskTier.Autonomous,
|
|
479
|
+
"POST /api/feedback/consume": RiskTier.Autonomous,
|
|
480
|
+
// Read-only lesson-store overview for the dashboard Lessons settings page
|
|
481
|
+
// (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5). Summarises cap utilisation
|
|
482
|
+
// only — lesson prose was redaction-scrubbed at capture — so Autonomous.
|
|
483
|
+
"GET /api/feedback/lessons": RiskTier.Autonomous,
|
|
484
|
+
// Self-tuning verdict endpoint (SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.4).
|
|
485
|
+
// Autonomous + (Phase 3) mandatory owner DM on apply — the exact pattern
|
|
486
|
+
// that replaced the abolished Notify tier; requiring the Approve bearer
|
|
487
|
+
// would put a human back in every loop iteration and defeat the design.
|
|
488
|
+
// Safety is carried by code, not tier: verdicts may only reference
|
|
489
|
+
// daemon-generated single-use recommendation ids from the current cycle,
|
|
490
|
+
// the handler is idempotent per id, and Phase 2 never actuates (shadow).
|
|
491
|
+
"POST /api/tuning/verdicts": RiskTier.Autonomous,
|
|
492
|
+
// Pending-cycle read — knob names + telemetry counts only, no user prose.
|
|
493
|
+
"GET /api/tuning/pending": RiskTier.Autonomous,
|
|
454
494
|
// ── Notification ──
|
|
455
495
|
"/api/notify": RiskTier.Autonomous,
|
|
456
496
|
// ── External Service Proxy ──
|
|
@@ -662,7 +702,7 @@ const API_RISK = {
|
|
|
662
702
|
"PATCH /api/delegated-sync/active-hours": RiskTier.Approve,
|
|
663
703
|
"POST /api/delegated-sync/cadences/": RiskTier.Approve,
|
|
664
704
|
// INTEGRATION-DRIFT-DETECTION-PLAN.md §6.0 — drift-detection chokepoint.
|
|
665
|
-
// Autonomous: the agent's
|
|
705
|
+
// Autonomous: the agent's activity_scan delegated variant POSTs the
|
|
666
706
|
// result of its connector fetch to compute a structural diff. Defense
|
|
667
707
|
// layers (window-key allowlist + per-call audit row) live inside the
|
|
668
708
|
// handler. Daemon-internal callers (CalendarPoller, DelegatedSyncWorker)
|
|
@@ -797,13 +837,22 @@ const API_RISK = {
|
|
|
797
837
|
"GET /api/skill-curation/signals": RiskTier.Autonomous,
|
|
798
838
|
"GET /api/skill-curation/knowledge-map": RiskTier.Autonomous,
|
|
799
839
|
"GET /api/skill-curation/proposals/": RiskTier.Autonomous,
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
805
|
-
//
|
|
806
|
-
|
|
840
|
+
// Run minting (`POST /runs`, exact) is Approve. In production this surface
|
|
841
|
+
// is unused — the dispatcher's `materializeOptimizerWorkdir` mints the
|
|
842
|
+
// runId/runToken directly and never crosses the HTTP boundary (see the
|
|
843
|
+
// route handler comment). Leaving it Autonomous let any Bearer-less local
|
|
844
|
+
// caller (incl. a prompt-injected DM agent) mint a valid optimizer token
|
|
845
|
+
// once curation is opted in, then drive `/proposals` to self-author skill
|
|
846
|
+
// overlays — a least-privilege violation. Approve confines minting to the
|
|
847
|
+
// dashboard/operator; the legitimate optimizer reads its token from the
|
|
848
|
+
// workdir preamble, not this route.
|
|
849
|
+
"POST /api/skill-curation/runs": RiskTier.Approve,
|
|
850
|
+
// The proposal chokepoint and the per-run finalize stay Autonomous — both
|
|
851
|
+
// are gated by the optimizer's runToken (HMAC, scoped to a single run)
|
|
852
|
+
// enforced inside the route, and the legitimate optimizer agent reaches
|
|
853
|
+
// them via curl from its session workdir (no Bearer). Per design §2.1 the
|
|
854
|
+
// chokepoint applies every passing proposal atomically; the only roll-back
|
|
855
|
+
// path is the system-driven auto-revert (`auto-revert.ts`).
|
|
807
856
|
"POST /api/skill-curation/proposals": RiskTier.Autonomous,
|
|
808
857
|
"POST /api/skill-curation/runs/": RiskTier.Autonomous,
|
|
809
858
|
// P22 §6.1 — settings + listing surfaces consumed by the dashboard.
|
|
@@ -878,9 +927,14 @@ export function findExplicitRiskClassification(method, path) {
|
|
|
878
927
|
/* c8 ignore stop */
|
|
879
928
|
if (keyMethod && keyMethod !== method)
|
|
880
929
|
return false;
|
|
881
|
-
return
|
|
930
|
+
return pathPrefixMatches(keyPath, path);
|
|
882
931
|
})
|
|
883
|
-
|
|
932
|
+
// Rank by matched path-prefix length so the most-specific prefix wins.
|
|
933
|
+
// Must compare the path segment, NOT the raw key: the `"METHOD "` token
|
|
934
|
+
// would otherwise lift a shorter, less-specific method-keyed prefix
|
|
935
|
+
// (e.g. `DELETE /api/git`) above a longer path-only one (`/api/github`),
|
|
936
|
+
// silently downgrading the tier. Mirrors the step-3 pattern tiebreaker.
|
|
937
|
+
.sort((a, b) => keyPathOf(b[0]).length - keyPathOf(a[0]).length);
|
|
884
938
|
if (candidates.length > 0)
|
|
885
939
|
return candidates[0][1];
|
|
886
940
|
return null;
|
|
@@ -960,16 +1014,41 @@ function matchesPattern(keyPath, actualPath) {
|
|
|
960
1014
|
}
|
|
961
1015
|
return true;
|
|
962
1016
|
}
|
|
963
|
-
/**
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
|
|
1017
|
+
/** Extract the path portion of an `API_RISK` key: `"METHOD /path"` → `/path`,
|
|
1018
|
+
* or a bare `"/path"` path-only key unchanged. Ranking must compare the path
|
|
1019
|
+
* segment alone — the leading `"METHOD "` token (3–6 chars) would otherwise
|
|
1020
|
+
* inflate a shorter, less-specific method-keyed prefix above a longer
|
|
1021
|
+
* path-only one. */
|
|
1022
|
+
export function keyPathOf(key) {
|
|
1023
|
+
return key.includes(" ") ? key.split(" ")[1] : key;
|
|
1024
|
+
}
|
|
1025
|
+
/** Segment-aware prefix test for step-4 (non-`{*}`) keys. A raw
|
|
1026
|
+
* `path.startsWith(keyPath)` matches a *string* prefix, so `/api/git`
|
|
1027
|
+
* would spuriously match the unrelated sibling `/api/git-accounts` (and,
|
|
1028
|
+
* worse, an unclassified future `/api/git-webhook` — silently inheriting
|
|
1029
|
+
* the sibling's tier instead of failing closed to Approve). A key that
|
|
1030
|
+
* ends in `/` is an explicit subtree catch-all, so a raw `startsWith` is
|
|
1031
|
+
* already the boundary; otherwise the match must be a strict
|
|
1032
|
+
* `/`-delimited descendant. Exact `path === keyPath` is pre-empted by the
|
|
1033
|
+
* step-1/step-2 exact lookups, so step 4 only ever matches descendants.
|
|
1034
|
+
* Mirrors the segment discipline of `matchesPattern` (step 3). */
|
|
1035
|
+
function pathPrefixMatches(keyPath, path) {
|
|
1036
|
+
if (keyPath.endsWith("/"))
|
|
1037
|
+
return path.startsWith(keyPath);
|
|
1038
|
+
return path.startsWith(keyPath + "/");
|
|
1039
|
+
}
|
|
1040
|
+
/** Count characters of the path prefix before the first `{*}` (or the whole
|
|
1041
|
+
* path length if none) — used to rank pattern candidates by specificity.
|
|
1042
|
+
* Defensive against future overlapping `{*}` keys: today no two `{*}` entries
|
|
1043
|
+
* in `API_RISK` match the same `(method, path)` pair, so the sort comparator
|
|
1044
|
+
* never invokes this helper. The function exists so the first overlapping
|
|
1045
|
+
* pair added picks the more specific entry instead of relying on iteration
|
|
1046
|
+
* order. */
|
|
969
1047
|
/* c8 ignore start */
|
|
970
1048
|
function literalPrefixLength(key) {
|
|
971
|
-
const
|
|
972
|
-
|
|
1049
|
+
const keyPath = keyPathOf(key);
|
|
1050
|
+
const idx = keyPath.indexOf("{*}");
|
|
1051
|
+
return idx < 0 ? keyPath.length : idx;
|
|
973
1052
|
}
|
|
974
1053
|
/* c8 ignore stop */
|
|
975
1054
|
/** Strip a trailing `{*}` placeholder from `path` so callers can
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage-1 deterministic gate for the three-stage activity_scan funnel
|
|
3
|
+
* (cost-reduction-structural §B). Pure function over a `ActivityScanSignals`
|
|
4
|
+
* snapshot — no DB handle, no clock, no I/O. The dispatcher computes the
|
|
5
|
+
* snapshot via `computeActivityScanSignals`, then asks this module which
|
|
6
|
+
* stage to enter.
|
|
7
|
+
*
|
|
8
|
+
* The four possible decisions:
|
|
9
|
+
* - `stage0_silent`: consume observations, append a single Agent Log
|
|
10
|
+
* line via the daemon-direct writer, return. No LLM call.
|
|
11
|
+
* - `stage2`: lite-tier triage (only when `stage2Enabled`). Strict
|
|
12
|
+
* JSON-only output decides log_only vs escalate.
|
|
13
|
+
* - `stage3`: existing full activity_scan session.
|
|
14
|
+
*
|
|
15
|
+
* HOURLY_CHECK_GATE_REDESIGN_PLAN.md Phase 4 collapsed the gateMode
|
|
16
|
+
* enum (`off`/`shadow`/`live`) into a single execution path. The gate's
|
|
17
|
+
* verdict is always honoured.
|
|
18
|
+
*/
|
|
19
|
+
import type { ActivityScanSignals } from "../db/activity-scan-signals.js";
|
|
20
|
+
export type ActivityScanGateStage = "stage0_silent" | "stage2" | "stage3";
|
|
21
|
+
export interface ActivityScanGateConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Hours since the last Stage 3 run after which the gate force-runs at
|
|
24
|
+
* least Stage 2 (or Stage 3 if novelty is high). Bounds the worst case
|
|
25
|
+
* where Stage 0/1 keeps short-circuiting on a quiet day.
|
|
26
|
+
*/
|
|
27
|
+
heartbeatHours: number;
|
|
28
|
+
/**
|
|
29
|
+
* When `false`, low-signal cases bypass Stage 2 entirely and route to
|
|
30
|
+
* Stage 3. The cautious default — Stage 2 only takes effect after
|
|
31
|
+
* shadow telemetry validates the decision boundary.
|
|
32
|
+
*/
|
|
33
|
+
stage2Enabled: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Below this threshold, `pendingObsCount` alone does not imply
|
|
36
|
+
* Stage 3. Default 0 — any pending observation hits the low-signal
|
|
37
|
+
* branch which routes to Stage 2 or Stage 3 depending on the flag.
|
|
38
|
+
*/
|
|
39
|
+
pendingObsLowSignalCeiling?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ActivityScanGateDecision {
|
|
42
|
+
stage: ActivityScanGateStage;
|
|
43
|
+
/** Short, telemetry-friendly explanation. */
|
|
44
|
+
reason: string;
|
|
45
|
+
/** Snapshot at decision time (echoed for the audit row). */
|
|
46
|
+
signals: ActivityScanSignals;
|
|
47
|
+
}
|
|
48
|
+
export declare function decideStage(signals: ActivityScanSignals, config: ActivityScanGateConfig): ActivityScanGateDecision;
|
|
49
|
+
/**
|
|
50
|
+
* The dispatcher logs every cron tick to `agent_actions` regardless of
|
|
51
|
+
* which stage runs. Helper that builds the JSON payload from a decision
|
|
52
|
+
* so the schema stays consistent across stages and call-sites.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildGateAuditDetail(decision: ActivityScanGateDecision, extra: {
|
|
55
|
+
appliedDecision: ActivityScanGateStage;
|
|
56
|
+
/** Stage-2 LLM verdict, when Stage 2 ran. */
|
|
57
|
+
stage2Verdict?: "log_only" | "escalate" | "failed";
|
|
58
|
+
/** Forced-run flag from `/api/agent/run-now` etc. */
|
|
59
|
+
forced?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* HOURLY_CHECK_GATE_REDESIGN_PLAN.md §3.5 — true when pre-pass for
|
|
62
|
+
* any non-direct integration failed in `harvestForGate` and the
|
|
63
|
+
* gate force-escalated to `stage3` regardless of the signal
|
|
64
|
+
* verdict. Surfaced in the audit row so dashboards can flag the
|
|
65
|
+
* cautious-escalate path.
|
|
66
|
+
*/
|
|
67
|
+
cautiousEscalate?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Original gate verdict captured BEFORE cautious-escalate
|
|
70
|
+
* overwrote it. Persisted as `pre_escalate_gate_*` so dashboards
|
|
71
|
+
* can answer "what would the gate have said if pre-pass had
|
|
72
|
+
* succeeded?" — distinguishes a tick that was structurally
|
|
73
|
+
* stage3 anyway from one that was forced up from stage0_silent
|
|
74
|
+
* by a transient fetch outage.
|
|
75
|
+
*/
|
|
76
|
+
preEscalateGateStage?: ActivityScanGateStage;
|
|
77
|
+
preEscalateGateReason?: string;
|
|
78
|
+
}): Record<string, unknown>;
|
|
79
|
+
/**
|
|
80
|
+
* Build the `<gate_decision>` block injected into Stage 3's prompt so
|
|
81
|
+
* the routine knows *why* it was escalated and can prioritize.
|
|
82
|
+
*/
|
|
83
|
+
export declare function renderGateDecisionBlock(decision: ActivityScanGateDecision, extra: {
|
|
84
|
+
forced?: boolean;
|
|
85
|
+
cautiousEscalate?: boolean;
|
|
86
|
+
}): string;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage-1 deterministic gate for the three-stage activity_scan funnel
|
|
3
|
+
* (cost-reduction-structural §B). Pure function over a `ActivityScanSignals`
|
|
4
|
+
* snapshot — no DB handle, no clock, no I/O. The dispatcher computes the
|
|
5
|
+
* snapshot via `computeActivityScanSignals`, then asks this module which
|
|
6
|
+
* stage to enter.
|
|
7
|
+
*
|
|
8
|
+
* The four possible decisions:
|
|
9
|
+
* - `stage0_silent`: consume observations, append a single Agent Log
|
|
10
|
+
* line via the daemon-direct writer, return. No LLM call.
|
|
11
|
+
* - `stage2`: lite-tier triage (only when `stage2Enabled`). Strict
|
|
12
|
+
* JSON-only output decides log_only vs escalate.
|
|
13
|
+
* - `stage3`: existing full activity_scan session.
|
|
14
|
+
*
|
|
15
|
+
* HOURLY_CHECK_GATE_REDESIGN_PLAN.md Phase 4 collapsed the gateMode
|
|
16
|
+
* enum (`off`/`shadow`/`live`) into a single execution path. The gate's
|
|
17
|
+
* verdict is always honoured.
|
|
18
|
+
*/
|
|
19
|
+
const HIGH_NOVELTY_FLOOR = 3;
|
|
20
|
+
const ESCALATE_NOVELTY_FLOOR = 2;
|
|
21
|
+
export function decideStage(signals, config) {
|
|
22
|
+
// Heartbeat: even on quiet days, exercise Stage 2/3 every N hours so
|
|
23
|
+
// the gate's signal compute is exercised end-to-end. We pick Stage 3
|
|
24
|
+
// when there is *some* novelty pending, Stage 2 otherwise — Stage 2
|
|
25
|
+
// is the cheaper lite-tier shape and Stage 3 wastes context if there
|
|
26
|
+
// is nothing to act on. If Stage 2 is disabled, the cautious fallback
|
|
27
|
+
// is Stage 3.
|
|
28
|
+
if (signals.hoursSinceLastStage3Run >= config.heartbeatHours) {
|
|
29
|
+
if (numericNovelty(signals.maxNoveltyScore) >= ESCALATE_NOVELTY_FLOOR) {
|
|
30
|
+
return decision("stage3", "heartbeat_due_with_novelty", signals);
|
|
31
|
+
}
|
|
32
|
+
return decision(config.stage2Enabled ? "stage2" : "stage3", "heartbeat_due", signals);
|
|
33
|
+
}
|
|
34
|
+
// Hard escalate: any high-priority signal goes straight to Stage 3.
|
|
35
|
+
if (numericNovelty(signals.maxNoveltyScore) >= HIGH_NOVELTY_FLOOR) {
|
|
36
|
+
return decision("stage3", "high_novelty", signals);
|
|
37
|
+
}
|
|
38
|
+
if (signals.calendarHasConflict) {
|
|
39
|
+
return decision("stage3", "calendar_conflict", signals);
|
|
40
|
+
}
|
|
41
|
+
if (signals.vipMailUnreadCount > 0) {
|
|
42
|
+
return decision("stage3", "vip_mail_unread", signals);
|
|
43
|
+
}
|
|
44
|
+
if (signals.agentPlanOverdueCount > 0) {
|
|
45
|
+
return decision("stage3", "agent_plan_overdue", signals);
|
|
46
|
+
}
|
|
47
|
+
if (signals.scheduleApproachingCount > 0) {
|
|
48
|
+
return decision("stage3", "schedule_approaching", signals);
|
|
49
|
+
}
|
|
50
|
+
// No signals at all → Stage 0 silent.
|
|
51
|
+
if (signals.pendingObsCount === 0 && !signals.calendarHas24hChange) {
|
|
52
|
+
return decision("stage0_silent", "no_signals", signals);
|
|
53
|
+
}
|
|
54
|
+
// Low signals only → Stage 2 if enabled, else Stage 3 (cautious default).
|
|
55
|
+
// The pendingObsLowSignalCeiling lets an operator widen the silent-skip
|
|
56
|
+
// band ("at most N noise observations is still nothing to act on"); the
|
|
57
|
+
// default 0 leaves the design's conservative posture intact.
|
|
58
|
+
const ceiling = Math.max(0, config.pendingObsLowSignalCeiling ?? 0);
|
|
59
|
+
if (signals.pendingObsCount <= ceiling
|
|
60
|
+
&& !signals.calendarHas24hChange) {
|
|
61
|
+
return decision("stage0_silent", "low_signal_under_ceiling", signals);
|
|
62
|
+
}
|
|
63
|
+
return decision(config.stage2Enabled ? "stage2" : "stage3", "low_signal_default", signals);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The dispatcher logs every cron tick to `agent_actions` regardless of
|
|
67
|
+
* which stage runs. Helper that builds the JSON payload from a decision
|
|
68
|
+
* so the schema stays consistent across stages and call-sites.
|
|
69
|
+
*/
|
|
70
|
+
export function buildGateAuditDetail(decision, extra) {
|
|
71
|
+
return {
|
|
72
|
+
stage_reached: extra.appliedDecision,
|
|
73
|
+
gate_stage: decision.stage,
|
|
74
|
+
gate_reason: decision.reason,
|
|
75
|
+
forced: extra.forced ?? false,
|
|
76
|
+
...(extra.stage2Verdict ? { stage2_verdict: extra.stage2Verdict } : {}),
|
|
77
|
+
...(extra.cautiousEscalate ? { cautious_escalate: true } : {}),
|
|
78
|
+
...(extra.preEscalateGateStage
|
|
79
|
+
? { pre_escalate_gate_stage: extra.preEscalateGateStage }
|
|
80
|
+
: {}),
|
|
81
|
+
...(extra.preEscalateGateReason
|
|
82
|
+
? { pre_escalate_gate_reason: extra.preEscalateGateReason }
|
|
83
|
+
: {}),
|
|
84
|
+
signal_snapshot: {
|
|
85
|
+
pendingObsCount: decision.signals.pendingObsCount,
|
|
86
|
+
maxNoveltyScore: decision.signals.maxNoveltyScore,
|
|
87
|
+
noveltyDistribution: decision.signals.noveltyDistribution,
|
|
88
|
+
vipMailUnreadCount: decision.signals.vipMailUnreadCount,
|
|
89
|
+
calendarHas24hChange: decision.signals.calendarHas24hChange,
|
|
90
|
+
calendarHasConflict: decision.signals.calendarHasConflict,
|
|
91
|
+
agentPlanOverdueCount: decision.signals.agentPlanOverdueCount,
|
|
92
|
+
scheduleApproachingCount: decision.signals.scheduleApproachingCount,
|
|
93
|
+
hoursSinceLastStage3Run: serializeHours(decision.signals.hoursSinceLastStage3Run),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build the `<gate_decision>` block injected into Stage 3's prompt so
|
|
99
|
+
* the routine knows *why* it was escalated and can prioritize.
|
|
100
|
+
*/
|
|
101
|
+
export function renderGateDecisionBlock(decision, extra) {
|
|
102
|
+
return [
|
|
103
|
+
"<gate_decision>",
|
|
104
|
+
` triggered_by: ${decision.stage === "stage3" ? "stage1" : "stage2_escalation"}`,
|
|
105
|
+
` reason: ${decision.reason}`,
|
|
106
|
+
` forced: ${extra.forced ? "true" : "false"}`,
|
|
107
|
+
...(extra.cautiousEscalate ? [" cautious_escalate: true"] : []),
|
|
108
|
+
` signals_snapshot: ${JSON.stringify({
|
|
109
|
+
maxNovelty: decision.signals.maxNoveltyScore,
|
|
110
|
+
pendingObs: decision.signals.pendingObsCount,
|
|
111
|
+
vipMail: decision.signals.vipMailUnreadCount,
|
|
112
|
+
calConflict: decision.signals.calendarHasConflict,
|
|
113
|
+
agentPlanOverdue: decision.signals.agentPlanOverdueCount,
|
|
114
|
+
scheduleApproaching: decision.signals.scheduleApproachingCount,
|
|
115
|
+
})}`,
|
|
116
|
+
"</gate_decision>",
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
function numericNovelty(score) {
|
|
120
|
+
// Cautious default — null (no summary done yet) is treated as 2 so a
|
|
121
|
+
// backlog of unsummarized observations does NOT silently skip the
|
|
122
|
+
// routine. The design doc calls this out explicitly under "edge cases".
|
|
123
|
+
return score ?? ESCALATE_NOVELTY_FLOOR;
|
|
124
|
+
}
|
|
125
|
+
function decision(stage, reason, signals) {
|
|
126
|
+
return { stage, reason, signals };
|
|
127
|
+
}
|
|
128
|
+
function serializeHours(value) {
|
|
129
|
+
if (!Number.isFinite(value))
|
|
130
|
+
return null;
|
|
131
|
+
return Number(value.toFixed(2));
|
|
132
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task budget envelope — BACKGROUND_TASK_RUNNER_DESIGN.md §6 / §10.1.
|
|
3
|
+
*
|
|
4
|
+
* Pure resolution of the `(modelId, maxTurns, maxBudgetUsd,
|
|
5
|
+
* executeTimeoutMinutes)` envelope for a background-task worker from
|
|
6
|
+
* three inputs:
|
|
7
|
+
*
|
|
8
|
+
* 1. The per-task `tier` (`lite` | `medium` | `high`) — selects the
|
|
9
|
+
* base turn/budget/timeout envelope.
|
|
10
|
+
* 2. The optional per-task `maxBudgetUsd` override (POST body).
|
|
11
|
+
* 3. The operator-editable `process_backend_config` row for
|
|
12
|
+
* `process_key='background_task'` (model + caps) — same chokepoint
|
|
13
|
+
* as browser-task's `loadBrowserTaskBackendBinding`.
|
|
14
|
+
*
|
|
15
|
+
* The worker is Claude-only (it drives the Claude Agent SDK `query()`
|
|
16
|
+
* loop directly, like browser_task). A `process_backend_config` row that
|
|
17
|
+
* pins a non-Claude backend is refused with `backend_misconfigured` so a
|
|
18
|
+
* mis-set `/settings/models` row fails fast rather than silently doing
|
|
19
|
+
* nothing.
|
|
20
|
+
*
|
|
21
|
+
* 100% coverage gate — the I/O (DB read) lives in
|
|
22
|
+
* `loadBackgroundTaskBinding`; this module is the pure arithmetic.
|
|
23
|
+
*/
|
|
24
|
+
import type { BackgroundTaskTier } from "../../db/background-task-store.js";
|
|
25
|
+
/** Fallback model when the `process_backend_config` row is missing or
|
|
26
|
+
* carries an empty `main_model`. Mirrors the seed default. */
|
|
27
|
+
export declare const BACKGROUND_TASK_FALLBACK_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
28
|
+
export declare const BACKGROUND_TASK_DEFAULT_TIER: BackgroundTaskTier;
|
|
29
|
+
/** Hard upper bounds. The seed sits well below; the caps give the
|
|
30
|
+
* operator room to relax via `/settings/models` while pinning a ceiling
|
|
31
|
+
* no per-task override can blow past. Background tasks are the
|
|
32
|
+
* long-running surface, so the turn / timeout ceilings are far higher
|
|
33
|
+
* than browser-task's (60 turns / 5 min) — a deep research or
|
|
34
|
+
* multi-repo audit legitimately runs for many turns over many minutes. */
|
|
35
|
+
export declare const BACKGROUND_TASK_MAX_TURNS_CAP = 120;
|
|
36
|
+
export declare const BACKGROUND_TASK_MAX_BUDGET_USD_CAP = 15;
|
|
37
|
+
export declare const BACKGROUND_TASK_MAX_EXECUTE_TIMEOUT_MINUTES = 120;
|
|
38
|
+
export interface BackgroundTaskEnvelope {
|
|
39
|
+
modelId: string;
|
|
40
|
+
maxTurns: number;
|
|
41
|
+
maxBudgetUsd: number;
|
|
42
|
+
executeTimeoutMinutes: number;
|
|
43
|
+
}
|
|
44
|
+
interface TierEnvelope {
|
|
45
|
+
maxTurns: number;
|
|
46
|
+
maxBudgetUsd: number;
|
|
47
|
+
executeTimeoutMinutes: number;
|
|
48
|
+
}
|
|
49
|
+
export declare function tierEnvelope(tier: BackgroundTaskTier): TierEnvelope;
|
|
50
|
+
/** Shape of the operator-editable `process_backend_config` row, already
|
|
51
|
+
* read from the DB (or null when absent). */
|
|
52
|
+
export interface BackgroundTaskProcessConfig {
|
|
53
|
+
mainBackend: string;
|
|
54
|
+
mainModel: string | null;
|
|
55
|
+
maxTurns: number | null;
|
|
56
|
+
maxBudgetUsd: number | null;
|
|
57
|
+
}
|
|
58
|
+
export interface ResolveEnvelopeInput {
|
|
59
|
+
tier: BackgroundTaskTier | null;
|
|
60
|
+
/** Per-task budget override (POST body). Clamped to the hard cap. */
|
|
61
|
+
maxBudgetUsd: number | null;
|
|
62
|
+
processConfig: BackgroundTaskProcessConfig | null;
|
|
63
|
+
}
|
|
64
|
+
export type ResolveEnvelopeResult = {
|
|
65
|
+
ok: true;
|
|
66
|
+
envelope: BackgroundTaskEnvelope;
|
|
67
|
+
} | {
|
|
68
|
+
ok: false;
|
|
69
|
+
reason: "backend_misconfigured";
|
|
70
|
+
detail: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Pure envelope resolution. Branches:
|
|
74
|
+
* - processConfig present + `mainBackend !== 'claude'` → REFUSE.
|
|
75
|
+
* - otherwise: model from the config (or fallback); turns from the
|
|
76
|
+
* config (or tier base), clamped; budget = per-task override ?? config
|
|
77
|
+
* budget ?? tier base, clamped; timeout from the tier base, clamped.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveBackgroundTaskEnvelope(input: ResolveEnvelopeInput): ResolveEnvelopeResult;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task budget envelope — BACKGROUND_TASK_RUNNER_DESIGN.md §6 / §10.1.
|
|
3
|
+
*
|
|
4
|
+
* Pure resolution of the `(modelId, maxTurns, maxBudgetUsd,
|
|
5
|
+
* executeTimeoutMinutes)` envelope for a background-task worker from
|
|
6
|
+
* three inputs:
|
|
7
|
+
*
|
|
8
|
+
* 1. The per-task `tier` (`lite` | `medium` | `high`) — selects the
|
|
9
|
+
* base turn/budget/timeout envelope.
|
|
10
|
+
* 2. The optional per-task `maxBudgetUsd` override (POST body).
|
|
11
|
+
* 3. The operator-editable `process_backend_config` row for
|
|
12
|
+
* `process_key='background_task'` (model + caps) — same chokepoint
|
|
13
|
+
* as browser-task's `loadBrowserTaskBackendBinding`.
|
|
14
|
+
*
|
|
15
|
+
* The worker is Claude-only (it drives the Claude Agent SDK `query()`
|
|
16
|
+
* loop directly, like browser_task). A `process_backend_config` row that
|
|
17
|
+
* pins a non-Claude backend is refused with `backend_misconfigured` so a
|
|
18
|
+
* mis-set `/settings/models` row fails fast rather than silently doing
|
|
19
|
+
* nothing.
|
|
20
|
+
*
|
|
21
|
+
* 100% coverage gate — the I/O (DB read) lives in
|
|
22
|
+
* `loadBackgroundTaskBinding`; this module is the pure arithmetic.
|
|
23
|
+
*/
|
|
24
|
+
/** Fallback model when the `process_backend_config` row is missing or
|
|
25
|
+
* carries an empty `main_model`. Mirrors the seed default. */
|
|
26
|
+
export const BACKGROUND_TASK_FALLBACK_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
27
|
+
export const BACKGROUND_TASK_DEFAULT_TIER = "medium";
|
|
28
|
+
/** Hard upper bounds. The seed sits well below; the caps give the
|
|
29
|
+
* operator room to relax via `/settings/models` while pinning a ceiling
|
|
30
|
+
* no per-task override can blow past. Background tasks are the
|
|
31
|
+
* long-running surface, so the turn / timeout ceilings are far higher
|
|
32
|
+
* than browser-task's (60 turns / 5 min) — a deep research or
|
|
33
|
+
* multi-repo audit legitimately runs for many turns over many minutes. */
|
|
34
|
+
export const BACKGROUND_TASK_MAX_TURNS_CAP = 120;
|
|
35
|
+
export const BACKGROUND_TASK_MAX_BUDGET_USD_CAP = 15.0;
|
|
36
|
+
export const BACKGROUND_TASK_MAX_EXECUTE_TIMEOUT_MINUTES = 120;
|
|
37
|
+
/** Per-tier base envelope. `process_backend_config` overrides
|
|
38
|
+
* model/turns/budget; the tier is the source of the execute-timeout
|
|
39
|
+
* (which has no `process_backend_config` column) and the fallback when
|
|
40
|
+
* the config row is absent. */
|
|
41
|
+
const TIER_ENVELOPES = {
|
|
42
|
+
lite: { maxTurns: 15, maxBudgetUsd: 0.5, executeTimeoutMinutes: 10 },
|
|
43
|
+
medium: { maxTurns: 40, maxBudgetUsd: 2.0, executeTimeoutMinutes: 30 },
|
|
44
|
+
high: { maxTurns: 80, maxBudgetUsd: 8.0, executeTimeoutMinutes: 60 },
|
|
45
|
+
};
|
|
46
|
+
export function tierEnvelope(tier) {
|
|
47
|
+
return TIER_ENVELOPES[tier];
|
|
48
|
+
}
|
|
49
|
+
function clampPositive(value, cap, fallback) {
|
|
50
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
51
|
+
return fallback;
|
|
52
|
+
return Math.min(value, cap);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pure envelope resolution. Branches:
|
|
56
|
+
* - processConfig present + `mainBackend !== 'claude'` → REFUSE.
|
|
57
|
+
* - otherwise: model from the config (or fallback); turns from the
|
|
58
|
+
* config (or tier base), clamped; budget = per-task override ?? config
|
|
59
|
+
* budget ?? tier base, clamped; timeout from the tier base, clamped.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveBackgroundTaskEnvelope(input) {
|
|
62
|
+
const tier = input.tier ?? BACKGROUND_TASK_DEFAULT_TIER;
|
|
63
|
+
const base = TIER_ENVELOPES[tier];
|
|
64
|
+
const cfg = input.processConfig;
|
|
65
|
+
if (cfg && cfg.mainBackend !== "claude") {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
reason: "backend_misconfigured",
|
|
69
|
+
detail: `process_backend_config.main_backend='${cfg.mainBackend}' — background_task drives the Claude Agent SDK directly; refusing to dispatch. Set background_task back to claude in /settings/models.`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const modelId = cfg && typeof cfg.mainModel === "string" && cfg.mainModel.length > 0
|
|
73
|
+
? cfg.mainModel
|
|
74
|
+
: BACKGROUND_TASK_FALLBACK_CLAUDE_MODEL;
|
|
75
|
+
const rawTurns = cfg && typeof cfg.maxTurns === "number" && Number.isFinite(cfg.maxTurns)
|
|
76
|
+
? Math.max(1, Math.floor(cfg.maxTurns))
|
|
77
|
+
: base.maxTurns;
|
|
78
|
+
const maxTurns = Math.min(rawTurns, BACKGROUND_TASK_MAX_TURNS_CAP);
|
|
79
|
+
// Per-task override wins, then the operator config, then the tier base.
|
|
80
|
+
const budgetSource = input.maxBudgetUsd != null
|
|
81
|
+
? input.maxBudgetUsd
|
|
82
|
+
: cfg && cfg.maxBudgetUsd != null
|
|
83
|
+
? cfg.maxBudgetUsd
|
|
84
|
+
: base.maxBudgetUsd;
|
|
85
|
+
const maxBudgetUsd = clampPositive(budgetSource, BACKGROUND_TASK_MAX_BUDGET_USD_CAP, base.maxBudgetUsd);
|
|
86
|
+
const executeTimeoutMinutes = Math.min(base.executeTimeoutMinutes, BACKGROUND_TASK_MAX_EXECUTE_TIMEOUT_MINUTES);
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
envelope: { modelId, maxTurns, maxBudgetUsd, executeTimeoutMinutes },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task driver — generic Claude Agent SDK glue for the
|
|
3
|
+
* per-task worker loop. BACKGROUND_TASK_RUNNER_DESIGN.md §4.1.
|
|
4
|
+
*
|
|
5
|
+
* The browser-task driver's analogue, with the entire Playwright /
|
|
6
|
+
* managed-Chromium / allowlist / final-confirm plane removed. A worker
|
|
7
|
+
* is a plain `query()` session seeded with a SELF-CONTAINED brief and a
|
|
8
|
+
* three-tool MCP envelope (`read_memory`, `ask_user`, `finish`) plus the
|
|
9
|
+
* SDK's `WebSearch` / `WebFetch` for research-type work. It opts out of
|
|
10
|
+
* the `<user>` / `<management_rules>` injection (`settingSources:
|
|
11
|
+
* ["project"]`, as browser-task does) — the brief carries the context,
|
|
12
|
+
* the output-language directive, persona hints for the `draft`, and the
|
|
13
|
+
* notification policy / criteria.
|
|
14
|
+
*
|
|
15
|
+
* Responsibilities:
|
|
16
|
+
* 1. Resolve the `(model, maxTurns, maxBudgetUsd, executeTimeout)`
|
|
17
|
+
* envelope from `process_backend_config` + the row's tier/budget
|
|
18
|
+
* (`background-task-budget.ts`), pinned for the task's lifetime.
|
|
19
|
+
* 2. Render a per-task workdir (empty dir + CLAUDE.md agent profile).
|
|
20
|
+
* 3. Drive `query()` until terminal, honouring the AbortController
|
|
21
|
+
* (cancel + timeout) — no Playwright resources to release.
|
|
22
|
+
* 4. Hand back a `DriverRunResult` the runner maps to terminal state /
|
|
23
|
+
* park. On the death paths the artifact is NULL — the runner
|
|
24
|
+
* synthesizes the fail-loud artifact (§4.3).
|
|
25
|
+
*
|
|
26
|
+
* Excluded from the 100% coverage gate — SDK stream consumer. The pure
|
|
27
|
+
* sub-pieces (budget envelope) live in the covered set.
|
|
28
|
+
*/
|
|
29
|
+
import type Database from "better-sqlite3";
|
|
30
|
+
import { type BackgroundTaskRow } from "../../db/background-task-store.js";
|
|
31
|
+
import { type BackgroundTaskEnvelope, type BackgroundTaskProcessConfig } from "./background-task-budget.js";
|
|
32
|
+
import { type BackgroundTaskRuntime } from "./background-task-tools.js";
|
|
33
|
+
import { type BackgroundTaskTransitionEmitter } from "./background-task-transition-events.js";
|
|
34
|
+
/** Read the operator-editable envelope row for `background_task`. */
|
|
35
|
+
export declare function loadBackgroundTaskProcessConfig(db: Database.Database): BackgroundTaskProcessConfig | null;
|
|
36
|
+
export interface DriverDeps {
|
|
37
|
+
db: Database.Database;
|
|
38
|
+
paDataDir: string;
|
|
39
|
+
/** Workspace dir root — resolves the agent-profile MD. */
|
|
40
|
+
workspaceDir: string;
|
|
41
|
+
transitionEmitter?: BackgroundTaskTransitionEmitter;
|
|
42
|
+
/** Vault root for the worker's `read_memory` tool. */
|
|
43
|
+
contextDir: string;
|
|
44
|
+
/** Clarification TTL in ms (`backgroundTaskClarificationTtlMinutes`). */
|
|
45
|
+
clarificationTtlMs: number;
|
|
46
|
+
/** Override for tests; production wires `() => Date.now()`. */
|
|
47
|
+
nowFn?: () => number;
|
|
48
|
+
}
|
|
49
|
+
export interface DriverHandle {
|
|
50
|
+
abortController: AbortController;
|
|
51
|
+
cwd: string;
|
|
52
|
+
runtime: BackgroundTaskRuntime;
|
|
53
|
+
sdkSessionId: string | null;
|
|
54
|
+
binding: BackgroundTaskEnvelope;
|
|
55
|
+
}
|
|
56
|
+
export interface DriverRunResult {
|
|
57
|
+
outcome: "completed" | "yielded_for_clarification" | "no_finish" | "max_turns_exceeded" | "budget_exceeded" | "timeout" | "cancelled" | "sdk_error" | "resume_unavailable" | "backend_misconfigured";
|
|
58
|
+
sdkSessionId: string | null;
|
|
59
|
+
detail?: string | null;
|
|
60
|
+
costUsd: number;
|
|
61
|
+
numTurns: number;
|
|
62
|
+
durationMs: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Acquire a fresh workdir + runtime + binding for `row`. The runner
|
|
66
|
+
* calls this BEFORE `runDriver` so it can stash the handle in its parked
|
|
67
|
+
* map before any turn fires (an ask_user on the first turn must find the
|
|
68
|
+
* handle already there).
|
|
69
|
+
*/
|
|
70
|
+
export declare function prepareDriverHandle(input: {
|
|
71
|
+
deps: DriverDeps;
|
|
72
|
+
row: BackgroundTaskRow;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
ok: true;
|
|
75
|
+
handle: DriverHandle;
|
|
76
|
+
} | {
|
|
77
|
+
ok: false;
|
|
78
|
+
reason: DriverRunResult["outcome"];
|
|
79
|
+
detail?: string;
|
|
80
|
+
}>;
|
|
81
|
+
/** Drive the initial turn — the worker reads the brief and works. */
|
|
82
|
+
export declare function runDriver(deps: DriverDeps, row: BackgroundTaskRow, handle: DriverHandle): Promise<DriverRunResult>;
|
|
83
|
+
/** Resume a parked task after `/clarify` lands the owner's answer. Uses
|
|
84
|
+
* the persisted SDK session id so the prompt cache stays warm. Works both
|
|
85
|
+
* in-process (warm parked handle) and across a daemon restart (the runner
|
|
86
|
+
* reconstructs the handle from the persisted `backend_session_id`); in the
|
|
87
|
+
* cross-restart case a session the SDK can no longer load surfaces as
|
|
88
|
+
* `resume_unavailable`. */
|
|
89
|
+
export declare function resumeDriver(deps: DriverDeps, row: BackgroundTaskRow, handle: DriverHandle, userAnswer: string): Promise<DriverRunResult>;
|
|
90
|
+
/**
|
|
91
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 / Phase 4 — resume a task that was
|
|
92
|
+
* mid-execution when the daemon restarted, using the persisted SDK session
|
|
93
|
+
* id (`backend_session_id`) so the warm transcript + prompt cache survive
|
|
94
|
+
* the restart instead of re-running the brief from scratch. The runner
|
|
95
|
+
* reconstructs the handle (`prepareDriverHandle` recreates the per-task
|
|
96
|
+
* workdir + sets `sdkSessionId` from the row). When the SDK can no longer
|
|
97
|
+
* load the session, this returns `resume_unavailable` and the runner falls
|
|
98
|
+
* back to re-dispatch-from-brief — so resume is a pure optimization with no
|
|
99
|
+
* regression.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resumeFromBootDriver(deps: DriverDeps, row: BackgroundTaskRow, handle: DriverHandle): Promise<DriverRunResult>;
|
|
102
|
+
/** Remove the per-task workdir. Idempotent. Parked tasks (awaiting_user)
|
|
103
|
+
* do NOT call this — the runner keeps the handle in its parked map so
|
|
104
|
+
* /clarify can resume the warm SDK session. */
|
|
105
|
+
export declare function releaseDriverHandle(deps: DriverDeps, handle: DriverHandle): Promise<void>;
|