@aitne/daemon 0.1.10 → 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.js +8 -5
- 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.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/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/context/permissions.js +3 -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 +48 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- 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 +9 -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 +245 -7
- 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 +26 -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 +25 -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 +39 -15
- 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 +62 -20
- 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 +1 -1
- package/dist/core/context-paths.js +1 -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 +49 -0
- 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 +17 -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 +63 -1
- package/dist/core/dispatcher-scheduled-tasks.js +213 -6
- 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 +284 -59
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.js +17 -5
- package/dist/core/feedback/eviction-scorer.js +6 -2
- package/dist/core/feedback/lesson-format.js +9 -4
- package/dist/core/feedback/lesson-injection.d.ts +1 -1
- package/dist/core/feedback/lesson-injection.js +17 -2
- package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
- package/dist/core/feedback/lesson-store-overview.js +8 -4
- package/dist/core/feedback/regeneralization-prep.js +29 -16
- 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 +4 -4
- package/dist/core/injection-policy.js +4 -4
- 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 +12 -0
- package/dist/core/signal-detector.js +53 -9
- 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 +2 -2
- package/dist/core/today-direct-writer.js +1 -1
- 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/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/migrations.js +330 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +217 -16
- 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/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 +75 -11
- 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 +16 -6
- 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/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +38 -11
- package/dist/settings/runtime-settings.js +203 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
package/dist/core/dispatcher.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventPriority, createEvent, getAgentDayBoundsUtc, isMessageEvent, isRoutineEvent, isAgentTaskEvent, isScheduledEvent, isScheduledBrowserTaskEvent, isScheduledDmEvent, isKnowledgeImportEvent, parseSqliteUtcMs, } from "@aitne/shared";
|
|
1
|
+
import { EventPriority, createEvent, getAgentDayBoundsUtc, isBackendId, isMessageEvent, isRoutineEvent, isAgentTaskEvent, isScheduledEvent, isScheduledBrowserTaskEvent, isScheduledBackgroundTaskEvent, isScheduledDmEvent, isTaskDeliveryEvent, isKnowledgeImportEvent, parseSqliteUtcMs, } from "@aitne/shared";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { CONTEXT_RELATIVE_PATHS } from "./context-paths.js";
|
|
@@ -23,15 +23,15 @@ export { parseStage2Verdict, };
|
|
|
23
23
|
import { PromptAssembler } from "./dispatcher-prompt.js";
|
|
24
24
|
import { DispatcherErrorRouter } from "./dispatcher-error-handling.js";
|
|
25
25
|
import { ResultProcessor } from "./dispatcher-result-processor.js";
|
|
26
|
-
import {
|
|
26
|
+
import { ActivityScanCoordinator } from "./dispatcher-activity-scan.js";
|
|
27
27
|
import { morningRoutineRanToday } from "../bootstrap/schedule-helpers.js";
|
|
28
28
|
/**
|
|
29
29
|
* Routine names that depend on `routine.morning_routine` having completed
|
|
30
30
|
* successfully for the current agent-day. The pre-routine gate in
|
|
31
31
|
* `dispatch()` enqueues a morning_routine wake and skips the dependent
|
|
32
|
-
* routine when the predicate trips —
|
|
33
|
-
* inside `
|
|
34
|
-
* point (`
|
|
32
|
+
* routine when the predicate trips — activity_scan is gated separately
|
|
33
|
+
* inside `ActivityScanCoordinator.trigger` because it has its own entry
|
|
34
|
+
* point (`triggerActivityScan`) before any event hits the bus.
|
|
35
35
|
*/
|
|
36
36
|
const REVIEW_ROUTINES_REQUIRING_MORNING = new Set([
|
|
37
37
|
"evening_review",
|
|
@@ -43,8 +43,10 @@ import { MorningRoutinePipelineOrchestrator } from "./morning/orchestrator.js";
|
|
|
43
43
|
import { DailyJournalComposer } from "./morning/daily-journal-composer.js";
|
|
44
44
|
import { randomUUID } from "node:crypto";
|
|
45
45
|
import { RoutineFetchWindowRunner } from "./routine-fetch-window-runner.js";
|
|
46
|
+
import { AutonomousSpawnGate, } from "./spawn-gates.js";
|
|
46
47
|
import { ScheduledTaskRunner, SKILL_CURATION_OPTIMIZER_ALLOWED_TOOLS, } from "./dispatcher-scheduled-tasks.js";
|
|
47
48
|
import { MessageHandler } from "./dispatcher-message-handler.js";
|
|
49
|
+
import { TASK_DELIVERY_GATE_KEYS, handleTaskDeliveryInsideGate, } from "./dispatcher-task-delivery.js";
|
|
48
50
|
export { SKILL_CURATION_OPTIMIZER_ALLOWED_TOOLS };
|
|
49
51
|
const CURRENT_SETUP_MODE_STATE_KEY = "current_setup_mode";
|
|
50
52
|
export class EventDispatcher {
|
|
@@ -128,6 +130,14 @@ export class EventDispatcher {
|
|
|
128
130
|
* instead of leaving the row silently flipped to `failed` in the DB.
|
|
129
131
|
*/
|
|
130
132
|
browserTaskTerminalNotifier = null;
|
|
133
|
+
/**
|
|
134
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §4.2 — the dispatcher routes
|
|
135
|
+
* `scheduled.background_task` events to this runner. Wired at startup
|
|
136
|
+
* from `bootstrap/event-pipeline.ts` via `setBackgroundTaskRunner`.
|
|
137
|
+
* Null when the runner factory has not landed — the dispatch branch
|
|
138
|
+
* flips the row to `failed (runner_unavailable)` so it doesn't park.
|
|
139
|
+
*/
|
|
140
|
+
backgroundTaskRunner = null;
|
|
131
141
|
/**
|
|
132
142
|
* Current setup mode — scope-agnostic flag that survives internal
|
|
133
143
|
* direct-message session refresh (day boundary, stale flag, etc). Previously this
|
|
@@ -153,42 +163,42 @@ export class EventDispatcher {
|
|
|
153
163
|
* keep their own lane. SCHEDULED-DM-IMPLEMENTATION-PLAN §3.6 — also
|
|
154
164
|
* used by `scheduled.dm` to acquire BOTH owner-facing scopes in
|
|
155
165
|
* lex-sorted (deadlock-free) order. */
|
|
156
|
-
sessionGates
|
|
166
|
+
sessionGates;
|
|
157
167
|
/** Dedup guard: timestamp of the last roadmap_refresh emission */
|
|
158
168
|
lastRoadmapRefreshEmitMs = 0;
|
|
159
169
|
morningRoutineInProgress = false;
|
|
160
|
-
|
|
170
|
+
activityScanInProgress = false;
|
|
161
171
|
/**
|
|
162
172
|
* Wall-clock timestamp (ms since epoch) of the most recent flip of
|
|
163
|
-
* `
|
|
173
|
+
* `activityScanInProgress` to `true`, or `null` when the flag is false.
|
|
164
174
|
*
|
|
165
|
-
* Paired with `
|
|
175
|
+
* Paired with `ACTIVITY_SCAN_FLAG_MAX_AGE_MS` to break the silent-stall
|
|
166
176
|
* pattern where the flag is set true at enqueue time but the matching
|
|
167
177
|
* `dispatchSafe` finally never runs — currently possible when the
|
|
168
178
|
* EventBus evicts/drops the queued routine event under `put()` pressure
|
|
169
179
|
* (heap-js drops the lowest-priority entry silently when `heap.size() >=
|
|
170
180
|
* maxSize=1000`). Without the timestamp the flag stays `true` until
|
|
171
181
|
* process restart and every subsequent hourly tick short-circuits with
|
|
172
|
-
* `
|
|
182
|
+
* `activity_scan_in_progress`.
|
|
173
183
|
*
|
|
174
|
-
* Read side (`
|
|
184
|
+
* Read side (`isActivityScanInProgress` callback below) checks the age
|
|
175
185
|
* and auto-clears when it exceeds the bound, surfacing the recovery via
|
|
176
186
|
* a warn log so the operator sees the EventBus pressure event.
|
|
177
187
|
*/
|
|
178
|
-
|
|
188
|
+
activityScanInProgressAt = null;
|
|
179
189
|
/**
|
|
180
|
-
* Upper bound for how long `
|
|
190
|
+
* Upper bound for how long `activityScanInProgress=true` can plausibly
|
|
181
191
|
* be valid before we treat it as stuck and force-clear.
|
|
182
192
|
*
|
|
183
|
-
* Sized generously above the realistic Stage-3
|
|
184
|
-
* (fetch_window pre-pass ~30–60 s + Sonnet
|
|
193
|
+
* Sized generously above the realistic Stage-3 activity_scan ceiling
|
|
194
|
+
* (fetch_window pre-pass ~30–60 s + Sonnet activity_scan session
|
|
185
195
|
* ~1–3 min) so a slow but normal run is never falsely cleared. Sized
|
|
186
196
|
* well below an entire agent-day so a stuck flag recovers within a
|
|
187
197
|
* single hourly cron cycle's worst case (default cadence 60 min).
|
|
188
198
|
* The 30-minute window is comfortably outside any plausible "still
|
|
189
199
|
* running" interpretation and inside one full hourly slot.
|
|
190
200
|
*/
|
|
191
|
-
static
|
|
201
|
+
static ACTIVITY_SCAN_FLAG_MAX_AGE_MS = 30 * 60 * 1000;
|
|
192
202
|
/**
|
|
193
203
|
* P22 §3.4 — wired by `index.ts` after the daemon's data dir + skills root
|
|
194
204
|
* are known. Returns a `{runId, runToken, workdirPath, targetSkills}` tuple
|
|
@@ -216,12 +226,19 @@ export class EventDispatcher {
|
|
|
216
226
|
* and older code paths that don't wire the store. When null, the
|
|
217
227
|
* dispatcher skips attachment staging + outbound collection. */
|
|
218
228
|
attachmentStore = null;
|
|
229
|
+
/** Injected lazily via `setTaskDeliveryAssetResolver` — optional.
|
|
230
|
+
* Resolves a task's deliverable assets (browser-task screenshots +
|
|
231
|
+
* worker-written files) to outbound attachments for the `task.delivery`
|
|
232
|
+
* idle + active branches (the ingest hook is constructed after this
|
|
233
|
+
* dispatcher, so it is wired post-construction like the attachment
|
|
234
|
+
* store). When null, task-delivery DMs are text-only. */
|
|
235
|
+
taskDeliveryAssetResolver = null;
|
|
219
236
|
/** Injected lazily via `setDelegatedSyncRefresh` — optional. When null,
|
|
220
|
-
*
|
|
237
|
+
* activity scan fires without first refreshing delegated-mode snapshots,
|
|
221
238
|
* matching the pre-Phase-9 behaviour. Wired in production when at
|
|
222
239
|
* least one integration is in delegated mode. See
|
|
223
240
|
* `docs/design/appendices/delegated-sync-opt-in.md` and the worker's
|
|
224
|
-
* `
|
|
241
|
+
* `runDisabledCadencesForActivityScan` method. */
|
|
225
242
|
delegatedSyncRefresh = null;
|
|
226
243
|
/**
|
|
227
244
|
* Injected lazily via `setQueueMorningRoutineWake` (wired in `index.ts`
|
|
@@ -240,7 +257,7 @@ export class EventDispatcher {
|
|
|
240
257
|
* Injected lazily via `setEventBroadcaster` — optional. When wired, the
|
|
241
258
|
* dispatcher emits `routine_started` / `routine_completed` SSE events at
|
|
242
259
|
* the `dispatchSafe` chokepoint so the dashboard can render real-time
|
|
243
|
-
* progress for autonomous routines (morning_routine,
|
|
260
|
+
* progress for autonomous routines (morning_routine, activity_scan,
|
|
244
261
|
* roadmap_refresh, evening/weekly/monthly reviews, etc.).
|
|
245
262
|
*
|
|
246
263
|
* Failure to broadcast is non-fatal: the throw is swallowed and logged
|
|
@@ -317,21 +334,38 @@ export class EventDispatcher {
|
|
|
317
334
|
*/
|
|
318
335
|
resultProcessor;
|
|
319
336
|
/**
|
|
320
|
-
* Phase D-2 coordinator: owns `
|
|
337
|
+
* Phase D-2 coordinator: owns `triggerActivityScan` and the
|
|
321
338
|
* cost-reduction-structural §B three-stage gate. Borrows live
|
|
322
|
-
* accessors for the dispatcher's `
|
|
339
|
+
* accessors for the dispatcher's `activityScanInProgress` flag so the
|
|
323
340
|
* pre-existing C1 atomic check-and-set semantics survive the split.
|
|
324
341
|
*/
|
|
325
342
|
/**
|
|
326
343
|
* docs/design/appendices/routine-data-acquisition.md Phase 4 / D1 — shared pre-pass
|
|
327
|
-
* runner for `routine.fetch_window`. Injected into
|
|
344
|
+
* runner for `routine.fetch_window`. Injected into ActivityScanCoordinator
|
|
328
345
|
* (D3), MorningRoutineRunner (D2), and ScheduledTaskRunner (D4) so
|
|
329
346
|
* every routine that has rows in `ROUTINE_WINDOWS` gets the same
|
|
330
347
|
* fetcher session ahead of its parent dispatch. Pure helper, no
|
|
331
348
|
* mutable state of its own.
|
|
332
349
|
*/
|
|
333
350
|
fetchWindowRunner;
|
|
334
|
-
|
|
351
|
+
/**
|
|
352
|
+
* PREPASS_COST_REDUCTION_PLAN.md N2 — offline (backend-API-host DNS) +
|
|
353
|
+
* cached-auth spawn gate for autonomous sessions. Shared with the
|
|
354
|
+
* pre-pass fan-out runner so both layers reuse one DNS verdict cache.
|
|
355
|
+
*/
|
|
356
|
+
spawnGate;
|
|
357
|
+
/**
|
|
358
|
+
* Last spawn-gate skip *audit write* per (schedule-or-type, reason) —
|
|
359
|
+
* ms epoch. A released schedule row is re-claimed by the
|
|
360
|
+
* ScheduleWatcher every poll tick (default 5s), so an hours-long
|
|
361
|
+
* offline window would otherwise INSERT thousands of identical
|
|
362
|
+
* `result='skipped'` rows. The DB row release/claim churn is bounded
|
|
363
|
+
* (UPDATEs to one row); the audit INSERT is what must be throttled.
|
|
364
|
+
* In-memory on purpose: worst case after a restart is one extra row.
|
|
365
|
+
*/
|
|
366
|
+
spawnGateSkipAuditAt = new Map();
|
|
367
|
+
static SPAWN_GATE_SKIP_AUDIT_THROTTLE_MS = 10 * 60 * 1000;
|
|
368
|
+
activityScan;
|
|
335
369
|
/**
|
|
336
370
|
* Phase D-2 coordinator: owns morning-routine execution end-to-end
|
|
337
371
|
* (lock acquisition, prompt-variant selection, retry chain, today.md
|
|
@@ -363,7 +397,7 @@ export class EventDispatcher {
|
|
|
363
397
|
* thin shims that forward into this handler.
|
|
364
398
|
*/
|
|
365
399
|
messageHandler;
|
|
366
|
-
constructor(eventBus, agentRouter, contextBuilder, getTaskFlow, notificationMgr, sessionMgr, messageRecorder, audit, db, config, todayWriteLock, services, roadmapWriteLock, writeTracker) {
|
|
400
|
+
constructor(eventBus, agentRouter, contextBuilder, getTaskFlow, notificationMgr, sessionMgr, messageRecorder, audit, db, config, todayWriteLock, services, roadmapWriteLock, writeTracker, sessionGates) {
|
|
367
401
|
this.eventBus = eventBus;
|
|
368
402
|
this.agentRouter = agentRouter;
|
|
369
403
|
this.contextBuilder = contextBuilder;
|
|
@@ -378,6 +412,7 @@ export class EventDispatcher {
|
|
|
378
412
|
this.services = services;
|
|
379
413
|
this.roadmapWriteLock = roadmapWriteLock;
|
|
380
414
|
this.writeTracker = writeTracker;
|
|
415
|
+
this.sessionGates = sessionGates ?? new SessionGateRegistry();
|
|
381
416
|
this.reactiveSem = new Semaphore(config.maxReactiveSessions);
|
|
382
417
|
this.autonomousSem = new Semaphore(config.maxConcurrentSessions);
|
|
383
418
|
const messageColumns = new Set(this.db.pragma("table_info(messages)").map((column) => column.name));
|
|
@@ -407,10 +442,17 @@ export class EventDispatcher {
|
|
|
407
442
|
recordExecutionOutcome: (event, outcome) => this.agentExecutionTracker?.recordOutcome(event.correlationId, outcome),
|
|
408
443
|
});
|
|
409
444
|
// docs/design/appendices/routine-data-acquisition.md Phase 4 / D1 — shared pre-pass
|
|
410
|
-
// runner consumed by
|
|
445
|
+
// runner consumed by ActivityScanCoordinator (D3), MorningRoutineRunner
|
|
411
446
|
// (D2), and ScheduledTaskRunner.executeDefault (D4). Constructed
|
|
412
447
|
// before all three so it can be injected as a dep rather than
|
|
413
448
|
// lazily resolved.
|
|
449
|
+
// PREPASS_COST_REDUCTION_PLAN.md N2 — shared offline/auth spawn gate.
|
|
450
|
+
// One instance for the dispatcher's autonomous-event gate AND the
|
|
451
|
+
// pre-pass fan-out runner so the per-host DNS verdict cache (~60s)
|
|
452
|
+
// is shared across both layers within a tick.
|
|
453
|
+
this.spawnGate = new AutonomousSpawnGate(this.db, {
|
|
454
|
+
authFreshnessMs: this.config.authPreflightFreshnessMs,
|
|
455
|
+
});
|
|
414
456
|
this.fetchWindowRunner = new RoutineFetchWindowRunner({
|
|
415
457
|
db: this.db,
|
|
416
458
|
config: this.config,
|
|
@@ -418,6 +460,7 @@ export class EventDispatcher {
|
|
|
418
460
|
agentRouter: this.agentRouter,
|
|
419
461
|
audit: this.audit,
|
|
420
462
|
prompt: this.prompt,
|
|
463
|
+
spawnGate: this.spawnGate,
|
|
421
464
|
getActiveMailAccounts: () => this.getActiveMailAccounts(),
|
|
422
465
|
// Live accessor so the SSE broadcaster wired later via
|
|
423
466
|
// `setEventBroadcaster` (after dispatcher construction in
|
|
@@ -427,7 +470,7 @@ export class EventDispatcher {
|
|
|
427
470
|
// skips its pre-pass progress emits cleanly.
|
|
428
471
|
getEventBroadcaster: () => this.eventBroadcaster,
|
|
429
472
|
});
|
|
430
|
-
this.
|
|
473
|
+
this.activityScan = new ActivityScanCoordinator({
|
|
431
474
|
db: this.db,
|
|
432
475
|
config: this.config,
|
|
433
476
|
eventBus: this.eventBus,
|
|
@@ -438,27 +481,27 @@ export class EventDispatcher {
|
|
|
438
481
|
prompt: this.prompt,
|
|
439
482
|
fetchWindowRunner: this.fetchWindowRunner,
|
|
440
483
|
getDelegatedSyncRefresh: () => this.delegatedSyncRefresh,
|
|
441
|
-
|
|
442
|
-
this.
|
|
443
|
-
this.
|
|
484
|
+
setActivityScanInProgress: (value) => {
|
|
485
|
+
this.activityScanInProgress = value;
|
|
486
|
+
this.activityScanInProgressAt = value ? Date.now() : null;
|
|
444
487
|
},
|
|
445
|
-
|
|
446
|
-
if (!this.
|
|
488
|
+
isActivityScanInProgress: () => {
|
|
489
|
+
if (!this.activityScanInProgress)
|
|
447
490
|
return false;
|
|
448
|
-
// Stale-flag recovery — see `
|
|
491
|
+
// Stale-flag recovery — see `activityScanInProgressAt` doc-comment.
|
|
449
492
|
// The branch fires only when an enqueued event never reached
|
|
450
493
|
// `dispatchSafe`'s finally (EventBus eviction is the realistic
|
|
451
494
|
// cause; a future code path that forgets to reset the flag would
|
|
452
495
|
// also self-heal here within one cron cycle).
|
|
453
|
-
if (this.
|
|
454
|
-
const ageMs = Date.now() - this.
|
|
455
|
-
if (ageMs > EventDispatcher.
|
|
496
|
+
if (this.activityScanInProgressAt !== null) {
|
|
497
|
+
const ageMs = Date.now() - this.activityScanInProgressAt;
|
|
498
|
+
if (ageMs > EventDispatcher.ACTIVITY_SCAN_FLAG_MAX_AGE_MS) {
|
|
456
499
|
logger.warn({
|
|
457
500
|
ageMs,
|
|
458
|
-
maxAgeMs: EventDispatcher.
|
|
459
|
-
}, "
|
|
460
|
-
this.
|
|
461
|
-
this.
|
|
501
|
+
maxAgeMs: EventDispatcher.ACTIVITY_SCAN_FLAG_MAX_AGE_MS,
|
|
502
|
+
}, "activityScanInProgress flag exceeded max age — auto-clearing (likely EventBus drop or missed dispatchSafe finally)");
|
|
503
|
+
this.activityScanInProgress = false;
|
|
504
|
+
this.activityScanInProgressAt = null;
|
|
462
505
|
return false;
|
|
463
506
|
}
|
|
464
507
|
}
|
|
@@ -573,7 +616,7 @@ export class EventDispatcher {
|
|
|
573
616
|
diagnoseTodayMdState: () => this.scheduledTasks.diagnoseTodayMdState(),
|
|
574
617
|
isRoadmapStale: () => this.isRoadmapStale(),
|
|
575
618
|
emitRoadmapRefresh: (source) => this.emitRoadmapRefresh(source),
|
|
576
|
-
|
|
619
|
+
triggerActivityScan: (source) => this.triggerActivityScan(source),
|
|
577
620
|
pipelineOrchestrator,
|
|
578
621
|
});
|
|
579
622
|
this.scheduledTasks = new ScheduledTaskRunner({
|
|
@@ -617,6 +660,8 @@ export class EventDispatcher {
|
|
|
617
660
|
getBangCommandRegistry: () => this.bangCommandRegistry,
|
|
618
661
|
getPurchaseHandler: () => this.purchaseHandler,
|
|
619
662
|
getFinalConfirmHandler: () => this.finalConfirmHandler,
|
|
663
|
+
getBackgroundTaskRunner: () => this.backgroundTaskRunner,
|
|
664
|
+
getBrowserTaskRunner: () => this.browserTaskRunner,
|
|
620
665
|
getCurrentSetupMode: () => this.currentSetupMode,
|
|
621
666
|
beginSetupMode: (mode) => this.beginSetupMode(mode),
|
|
622
667
|
lookupCustomBangCommandForEvent: (event) => this.lookupCustomBangCommandForEvent(event),
|
|
@@ -684,6 +729,15 @@ export class EventDispatcher {
|
|
|
684
729
|
getBrowserTaskRunner() {
|
|
685
730
|
return this.browserTaskRunner;
|
|
686
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §4.2 — wire the generic
|
|
734
|
+
* background-task runner so the `scheduled.background_task` dispatch
|
|
735
|
+
* branch can hand fire-time events to it. Pairs with the
|
|
736
|
+
* `event-pipeline.ts` `createBackgroundTaskRunner` factory call.
|
|
737
|
+
*/
|
|
738
|
+
setBackgroundTaskRunner(runner) {
|
|
739
|
+
this.backgroundTaskRunner = runner;
|
|
740
|
+
}
|
|
687
741
|
/**
|
|
688
742
|
* BROWSER_TASK_REDESIGN_PLAN.md §7 — wire the terminal-state DM
|
|
689
743
|
* emitter used by the `scheduled.browser_task` failure paths (see
|
|
@@ -713,6 +767,14 @@ export class EventDispatcher {
|
|
|
713
767
|
setAttachmentStore(store) {
|
|
714
768
|
this.attachmentStore = store;
|
|
715
769
|
}
|
|
770
|
+
/** BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 (delivery assets) — inject the
|
|
771
|
+
* asset resolver used by the `task.delivery` idle + active branches to
|
|
772
|
+
* attach a task's deliverable files (screenshots, PDF/PPTX/PNG/docs)
|
|
773
|
+
* inline. Wired post-construction because the underlying
|
|
774
|
+
* dashboard-ingest hook is built after this dispatcher. */
|
|
775
|
+
setTaskDeliveryAssetResolver(resolver) {
|
|
776
|
+
this.taskDeliveryAssetResolver = resolver;
|
|
777
|
+
}
|
|
716
778
|
/** Inject the local-Whisper voice transcriber. Optional — when unset,
|
|
717
779
|
* inbound audio attachments are passed to the backend with a path-only
|
|
718
780
|
* reference (the pre-feature behaviour). */
|
|
@@ -721,7 +783,7 @@ export class EventDispatcher {
|
|
|
721
783
|
}
|
|
722
784
|
/**
|
|
723
785
|
* Inject the delegated-sync refresh callback. Called from
|
|
724
|
-
* `
|
|
786
|
+
* `triggerActivityScan` before the gate decision so any cadence the
|
|
725
787
|
* operator left opted-OUT (post-Phase-9 default) populates fresh
|
|
726
788
|
* Gmail / Notion observations the agent can then consume.
|
|
727
789
|
*
|
|
@@ -731,7 +793,7 @@ export class EventDispatcher {
|
|
|
731
793
|
* dispatcher holding a stale reference.
|
|
732
794
|
*
|
|
733
795
|
* Pass `null` to detach (e.g. when no delegated integration exists).
|
|
734
|
-
* The
|
|
796
|
+
* The activity scan then proceeds without a refresh — equivalent to the
|
|
735
797
|
* pre-injection behaviour.
|
|
736
798
|
*/
|
|
737
799
|
setDelegatedSyncRefresh(fn) {
|
|
@@ -739,7 +801,7 @@ export class EventDispatcher {
|
|
|
739
801
|
}
|
|
740
802
|
/**
|
|
741
803
|
* Wire the scheduler's `queueMorningRoutineWake` so the pre-routine
|
|
742
|
-
* gate (
|
|
804
|
+
* gate (activity_scan + evening/weekly/monthly review) can self-recover
|
|
743
805
|
* after a missed 04:00 cron fire. Wired once in `index.ts` after both
|
|
744
806
|
* the dispatcher and scheduler are constructed; passing `null` detaches.
|
|
745
807
|
* When unset, the gate logs a warning and still skips the dependent
|
|
@@ -766,6 +828,16 @@ export class EventDispatcher {
|
|
|
766
828
|
setAgentExecutionTracker(tracker) {
|
|
767
829
|
this.agentExecutionTracker = tracker;
|
|
768
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Resolve the user-Agent slug owning an in-flight firing, for stamping
|
|
833
|
+
* `agent_id` into quiet-hours-deferred DM rows (QUIET_HOURS_HARDENING_PLAN
|
|
834
|
+
* Phase 1 — the `/api/notify` gate coalesces per Agent so an hourly Agent
|
|
835
|
+
* firing five times overnight yields one combined DM). `null` when no
|
|
836
|
+
* tracker is wired or no execution is active for the correlation id.
|
|
837
|
+
*/
|
|
838
|
+
agentIdForCorrelation(correlationId) {
|
|
839
|
+
return this.agentExecutionTracker?.currentAgentId(correlationId) ?? null;
|
|
840
|
+
}
|
|
769
841
|
/**
|
|
770
842
|
* Open an execution row for an agent-resolvable firing (§8.1), called from
|
|
771
843
|
* `dispatchSafe` after the setup / cost gates pass so a skipped firing never
|
|
@@ -974,7 +1046,7 @@ export class EventDispatcher {
|
|
|
974
1046
|
/**
|
|
975
1047
|
* Enter setup mode. Called from `POST /setup/start` so the warm gate
|
|
976
1048
|
* engages the moment the user opens the dashboard setup flow — before any
|
|
977
|
-
* agent turn runs — so concurrent
|
|
1049
|
+
* agent turn runs — so concurrent activity_scan / morning routine / scheduled
|
|
978
1050
|
* wake work cannot race with the setup conversation. Persisted to
|
|
979
1051
|
* `runtime_state` so the flag survives daemon restart.
|
|
980
1052
|
*/
|
|
@@ -1035,8 +1107,8 @@ export class EventDispatcher {
|
|
|
1035
1107
|
if (this.morningRoutineInProgress) {
|
|
1036
1108
|
executions.push({ kind: "routine", key: "morning_routine" });
|
|
1037
1109
|
}
|
|
1038
|
-
if (this.
|
|
1039
|
-
executions.push({ kind: "routine", key: "
|
|
1110
|
+
if (this.activityScanInProgress) {
|
|
1111
|
+
executions.push({ kind: "routine", key: "activity_scan" });
|
|
1040
1112
|
}
|
|
1041
1113
|
const runningTasks = this.db
|
|
1042
1114
|
.prepare(`SELECT id, task_type, task_description
|
|
@@ -1054,7 +1126,7 @@ export class EventDispatcher {
|
|
|
1054
1126
|
return executions;
|
|
1055
1127
|
}
|
|
1056
1128
|
/**
|
|
1057
|
-
* Gate for autonomous background work (cron routines,
|
|
1129
|
+
* Gate for autonomous background work (cron routines, activity_scan,
|
|
1058
1130
|
* scheduled wake tasks, startup catchup, calendar-poller reactive events).
|
|
1059
1131
|
*
|
|
1060
1132
|
* Two layers:
|
|
@@ -1143,7 +1215,7 @@ export class EventDispatcher {
|
|
|
1143
1215
|
/**
|
|
1144
1216
|
* Check whether this autonomous event should be skipped because the daily
|
|
1145
1217
|
* autonomous cost cap has been exceeded. Uses priority-based degradation:
|
|
1146
|
-
*
|
|
1218
|
+
* activity_scan (lowest priority, skipped first) → roadmap_refresh →
|
|
1147
1219
|
* evening_review → morning_routine (highest, last to be cut).
|
|
1148
1220
|
*
|
|
1149
1221
|
* Lower-priority events are skipped at 100% of cap; higher-priority events
|
|
@@ -1169,7 +1241,7 @@ export class EventDispatcher {
|
|
|
1169
1241
|
? event.routine
|
|
1170
1242
|
: null;
|
|
1171
1243
|
const thresholds = {
|
|
1172
|
-
|
|
1244
|
+
activity_scan: 1.0, // skipped first (at 100% of cap)
|
|
1173
1245
|
roadmap_refresh: 1.2, // skipped at 120%
|
|
1174
1246
|
evening_review: 1.5, // skipped at 150%
|
|
1175
1247
|
morning_routine: 2.0, // last to be cut (only at 200%)
|
|
@@ -1177,6 +1249,45 @@ export class EventDispatcher {
|
|
|
1177
1249
|
const threshold = routine ? (thresholds[routine] ?? 1.0) : 1.0;
|
|
1178
1250
|
return todayCost >= cap * threshold;
|
|
1179
1251
|
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Resolve the candidate backends for an autonomous event and run the
|
|
1254
|
+
* N2 spawn gates against them. Fail-open on every internal error
|
|
1255
|
+
* (binding resolution included) — the gate exists to save sessions
|
|
1256
|
+
* that would deterministically fail, never to block live ones.
|
|
1257
|
+
* Returns `null` when the gate could not be evaluated.
|
|
1258
|
+
*/
|
|
1259
|
+
async evaluateAutonomousSpawnGate(event) {
|
|
1260
|
+
try {
|
|
1261
|
+
// Scheduled rows / integration cron events can pin a backend via
|
|
1262
|
+
// `requestedBackendId`; the router's backend-only override branch
|
|
1263
|
+
// then routes to exactly that backend WITHOUT a fallback. Mirror
|
|
1264
|
+
// that contract here: gating a pinned row on the *default* binding
|
|
1265
|
+
// would keep skipping it while its pinned backend is healthy (and
|
|
1266
|
+
// re-skip every watcher tick until the wrong backend recovered).
|
|
1267
|
+
const pinned = event
|
|
1268
|
+
.requestedBackendId;
|
|
1269
|
+
if (typeof pinned === "string" && isBackendId(pinned)) {
|
|
1270
|
+
return await this.spawnGate.evaluate([pinned]);
|
|
1271
|
+
}
|
|
1272
|
+
// No pin → event-type default binding. Process-key overrides that
|
|
1273
|
+
// some dispatch branches apply (e.g. `agent.task`, morning stage
|
|
1274
|
+
// keys) are approximated by this default: a mismatch is possible
|
|
1275
|
+
// only when the operator routed that specific process key to a
|
|
1276
|
+
// different backend, and the gate's fail-open posture bounds the
|
|
1277
|
+
// cost to one tick of latency during a partial outage.
|
|
1278
|
+
const binding = this.agentRouter.resolveBinding(event);
|
|
1279
|
+
const candidates = [binding.main.backendId];
|
|
1280
|
+
if (binding.fallback
|
|
1281
|
+
&& binding.fallback.backendId !== binding.main.backendId) {
|
|
1282
|
+
candidates.push(binding.fallback.backendId);
|
|
1283
|
+
}
|
|
1284
|
+
return await this.spawnGate.evaluate(candidates);
|
|
1285
|
+
}
|
|
1286
|
+
catch (err) {
|
|
1287
|
+
logger.warn({ err, eventType: event.type }, "Spawn-gate binding resolution failed — failing open");
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1180
1291
|
async handleEvent(event) {
|
|
1181
1292
|
try {
|
|
1182
1293
|
await this.handleEventInner(event);
|
|
@@ -1198,12 +1309,12 @@ export class EventDispatcher {
|
|
|
1198
1309
|
}
|
|
1199
1310
|
}
|
|
1200
1311
|
/**
|
|
1201
|
-
* Public entry point. Delegates to the
|
|
1312
|
+
* Public entry point. Delegates to the ActivityScanCoordinator.
|
|
1202
1313
|
* The dispatcher keeps the wrapper because tests + the cron entry
|
|
1203
|
-
* call `dispatcher.
|
|
1314
|
+
* call `dispatcher.triggerActivityScan(source, opts)` directly.
|
|
1204
1315
|
*/
|
|
1205
|
-
async
|
|
1206
|
-
return this.
|
|
1316
|
+
async triggerActivityScan(source, options = {}) {
|
|
1317
|
+
return this.activityScan.trigger(source, options);
|
|
1207
1318
|
}
|
|
1208
1319
|
/**
|
|
1209
1320
|
* Advisory check: is a morning routine execution or retry currently in
|
|
@@ -1218,7 +1329,7 @@ export class EventDispatcher {
|
|
|
1218
1329
|
*
|
|
1219
1330
|
* Public (not private) because Phase 4's `AuthHealthMonitor.checkAll()`
|
|
1220
1331
|
* shares the same skip-while-morning-routine-active invariant as the
|
|
1221
|
-
*
|
|
1332
|
+
* activity scan, and injects this method as an option so a probe tick
|
|
1222
1333
|
* running concurrently with morning routine can no-op cleanly. See
|
|
1223
1334
|
* `docs/design/09-safety-cost.md` §9.5.4.
|
|
1224
1335
|
*/
|
|
@@ -1257,6 +1368,38 @@ export class EventDispatcher {
|
|
|
1257
1368
|
.run(event.scheduleId);
|
|
1258
1369
|
}
|
|
1259
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Throttle for spawn-gate skip audit rows. A released schedule row is
|
|
1373
|
+
* due immediately, so the watcher re-claims it every poll tick (5s
|
|
1374
|
+
* default) for the whole outage — without this, one offline day per
|
|
1375
|
+
* pending row writes ~17k identical agent_actions rows. Keyed by
|
|
1376
|
+
* (schedule id | event type) × reason so distinct routines and
|
|
1377
|
+
* distinct reasons each still get their own first row, and a reason
|
|
1378
|
+
* flip (offline → auth_unhealthy) is recorded promptly.
|
|
1379
|
+
*/
|
|
1380
|
+
shouldWriteSpawnGateSkipAudit(event, reason) {
|
|
1381
|
+
const subject = isScheduledEvent(event) && event.scheduleId
|
|
1382
|
+
? `schedule:${event.scheduleId}`
|
|
1383
|
+
: `type:${event.type}`;
|
|
1384
|
+
const key = `${subject}|${reason}`;
|
|
1385
|
+
const now = Date.now();
|
|
1386
|
+
const last = this.spawnGateSkipAuditAt.get(key);
|
|
1387
|
+
if (last !== undefined
|
|
1388
|
+
&& now - last < EventDispatcher.SPAWN_GATE_SKIP_AUDIT_THROTTLE_MS) {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
// Opportunistic prune so a long outage across many schedule rows
|
|
1392
|
+
// cannot grow the map unbounded.
|
|
1393
|
+
if (this.spawnGateSkipAuditAt.size > 256) {
|
|
1394
|
+
for (const [k, ts] of this.spawnGateSkipAuditAt) {
|
|
1395
|
+
if (now - ts >= EventDispatcher.SPAWN_GATE_SKIP_AUDIT_THROTTLE_MS) {
|
|
1396
|
+
this.spawnGateSkipAuditAt.delete(k);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
this.spawnGateSkipAuditAt.set(key, now);
|
|
1401
|
+
return true;
|
|
1402
|
+
}
|
|
1260
1403
|
async dispatchSafe(event) {
|
|
1261
1404
|
const trigger = this.isReactive(event) ? "reactive" : "autonomous";
|
|
1262
1405
|
const startMs = Date.now();
|
|
@@ -1294,7 +1437,7 @@ export class EventDispatcher {
|
|
|
1294
1437
|
}
|
|
1295
1438
|
// Autonomous daily cost cap — safety net distinct from removed Phase 9
|
|
1296
1439
|
// maxDailyCostUsd (which blanket-blocked all sessions including DMs).
|
|
1297
|
-
// Reactive sessions always pass. Degradation priority:
|
|
1440
|
+
// Reactive sessions always pass. Degradation priority: activity_scan is
|
|
1298
1441
|
// skipped first, morning_routine last.
|
|
1299
1442
|
if (this.shouldSkipForCostCap(event)) {
|
|
1300
1443
|
this.releaseClaimedSchedule(event);
|
|
@@ -1302,6 +1445,36 @@ export class EventDispatcher {
|
|
|
1302
1445
|
logger.info({ eventType: event.type, source: event.source }, "Event skipped — autonomous daily cost cap exceeded");
|
|
1303
1446
|
return;
|
|
1304
1447
|
}
|
|
1448
|
+
// PREPASS_COST_REDUCTION_PLAN.md N2 — offline + auth spawn gates.
|
|
1449
|
+
// Skip the spawn only when EVERY candidate backend (main +
|
|
1450
|
+
// fallback) is non-viable: backend API host unresolvable
|
|
1451
|
+
// (`reason='offline'`) or auth confirmed bad in a fresh cache
|
|
1452
|
+
// (`reason='auth_unhealthy'`). A scheduled row is released back
|
|
1453
|
+
// to `pending` so the next watcher tick retries — the skip costs
|
|
1454
|
+
// at most one tick of latency, and only during a window where
|
|
1455
|
+
// the session would have failed anyway. Reactive work (user DMs)
|
|
1456
|
+
// is exempt by construction: a user-visible attempt + error beats
|
|
1457
|
+
// silent suppression.
|
|
1458
|
+
const gateDecision = await this.evaluateAutonomousSpawnGate(event);
|
|
1459
|
+
if (gateDecision?.skip) {
|
|
1460
|
+
this.releaseClaimedSchedule(event);
|
|
1461
|
+
const reason = gateDecision.reason ?? "offline";
|
|
1462
|
+
if (this.shouldWriteSpawnGateSkipAudit(event, reason)) {
|
|
1463
|
+
this.audit.logSkip(event, reason, trigger, {
|
|
1464
|
+
spawnGate: { backends: gateDecision.backends },
|
|
1465
|
+
});
|
|
1466
|
+
logger.info({
|
|
1467
|
+
eventType: event.type,
|
|
1468
|
+
source: event.source,
|
|
1469
|
+
reason,
|
|
1470
|
+
backends: gateDecision.backends,
|
|
1471
|
+
}, "Event skipped — autonomous spawn gate (offline / auth-unhealthy backends)");
|
|
1472
|
+
}
|
|
1473
|
+
else {
|
|
1474
|
+
logger.debug({ eventType: event.type, source: event.source, reason }, "Event skipped — spawn gate (audit row throttled, same skip already recorded)");
|
|
1475
|
+
}
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1305
1478
|
}
|
|
1306
1479
|
// AGENT_DEFINITIONS_DESIGN.md §8.1 — open the execution row after the
|
|
1307
1480
|
// setup / cost gates pass (a gated firing records no execution) and
|
|
@@ -1350,9 +1523,9 @@ export class EventDispatcher {
|
|
|
1350
1523
|
await this.errorRouter.handleError(event, err);
|
|
1351
1524
|
}
|
|
1352
1525
|
finally {
|
|
1353
|
-
if (isRoutineEvent(event) && event.routine === "
|
|
1354
|
-
this.
|
|
1355
|
-
this.
|
|
1526
|
+
if (isRoutineEvent(event) && event.routine === "activity_scan") {
|
|
1527
|
+
this.activityScanInProgress = false;
|
|
1528
|
+
this.activityScanInProgressAt = null;
|
|
1356
1529
|
}
|
|
1357
1530
|
}
|
|
1358
1531
|
}
|
|
@@ -1428,7 +1601,7 @@ export class EventDispatcher {
|
|
|
1428
1601
|
await this.scheduledTasks.executeSkillCurationRoutine(event);
|
|
1429
1602
|
}
|
|
1430
1603
|
else {
|
|
1431
|
-
//
|
|
1604
|
+
// activity_scan, evening_review, weekly_review, monthly_review
|
|
1432
1605
|
// Tier is resolved from process-key defaults by BackendRouter.
|
|
1433
1606
|
await this.scheduledTasks.executeDefault(event);
|
|
1434
1607
|
}
|
|
@@ -1472,6 +1645,19 @@ export class EventDispatcher {
|
|
|
1472
1645
|
await this.scheduledTasks.executeScheduledTask(event);
|
|
1473
1646
|
});
|
|
1474
1647
|
}
|
|
1648
|
+
else if (isTaskDeliveryEvent(event)) {
|
|
1649
|
+
await this.runWithSessionGates([...TASK_DELIVERY_GATE_KEYS], async () => {
|
|
1650
|
+
await handleTaskDeliveryInsideGate({
|
|
1651
|
+
db: this.db,
|
|
1652
|
+
config: this.config,
|
|
1653
|
+
notificationMgr: this.notificationMgr,
|
|
1654
|
+
executeScheduledTask: (scheduledEvent) => this.scheduledTasks.executeScheduledTask(scheduledEvent),
|
|
1655
|
+
...(this.taskDeliveryAssetResolver
|
|
1656
|
+
? { resolveAssets: this.taskDeliveryAssetResolver }
|
|
1657
|
+
: {}),
|
|
1658
|
+
}, event);
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1475
1661
|
else if (isAgentTaskEvent(event)) {
|
|
1476
1662
|
// scheduled.task — no gate, retains existing parallel-execution
|
|
1477
1663
|
// behavior. (scheduled.dm subtype is handled above.)
|
|
@@ -1484,10 +1670,49 @@ export class EventDispatcher {
|
|
|
1484
1670
|
// logic; here we wire it into the agent_schedule lifecycle.
|
|
1485
1671
|
await this.handleScheduledBrowserTaskDispatch(event);
|
|
1486
1672
|
}
|
|
1673
|
+
else if (isScheduledBackgroundTaskEvent(event)) {
|
|
1674
|
+
// BACKGROUND_TASK_RUNNER_DESIGN.md §4.2 — fire-time row creation +
|
|
1675
|
+
// runner handoff, wired into the agent_schedule lifecycle exactly
|
|
1676
|
+
// like scheduled.browser_task.
|
|
1677
|
+
await this.handleScheduledBackgroundTaskDispatch(event);
|
|
1678
|
+
}
|
|
1487
1679
|
else {
|
|
1488
1680
|
await this.scheduledTasks.executeDefault(event);
|
|
1489
1681
|
}
|
|
1490
1682
|
}
|
|
1683
|
+
/**
|
|
1684
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §4.2 — dispatch branch for
|
|
1685
|
+
* `scheduled.background_task`. Defers to `handleScheduledBackgroundTask`
|
|
1686
|
+
* (validation + row creation + runner handoff) and translates the
|
|
1687
|
+
* outcome into the `agent_schedule.status` write.
|
|
1688
|
+
*/
|
|
1689
|
+
async handleScheduledBackgroundTaskDispatch(event) {
|
|
1690
|
+
const { handleScheduledBackgroundTask } = await import("./dispatcher-scheduled-background-task.js");
|
|
1691
|
+
const outcome = await handleScheduledBackgroundTask({ db: this.db, runner: this.backgroundTaskRunner }, event);
|
|
1692
|
+
const succeeded = outcome.kind === "dispatched" || outcome.kind === "row_already_exists";
|
|
1693
|
+
this.db
|
|
1694
|
+
.prepare("UPDATE agent_schedule SET status = ? WHERE id = ? AND status = 'running'")
|
|
1695
|
+
.run(succeeded ? "completed" : "failed", event.scheduleId);
|
|
1696
|
+
if (!succeeded) {
|
|
1697
|
+
try {
|
|
1698
|
+
this.db
|
|
1699
|
+
.prepare(`INSERT INTO agent_actions
|
|
1700
|
+
(action_type, detail, result, started_at, completed_at)
|
|
1701
|
+
VALUES (?, ?, 'failure', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`)
|
|
1702
|
+
.run("background_task.scheduled_dispatch_failed", JSON.stringify({
|
|
1703
|
+
scheduleId: event.scheduleId,
|
|
1704
|
+
kind: outcome.kind,
|
|
1705
|
+
...("taskId" in outcome ? { taskId: outcome.taskId } : {}),
|
|
1706
|
+
...("reason" in outcome ? { reason: outcome.reason } : {}),
|
|
1707
|
+
}));
|
|
1708
|
+
}
|
|
1709
|
+
catch (auditErr) {
|
|
1710
|
+
/* c8 ignore start -- defensive */
|
|
1711
|
+
logger.warn({ err: auditErr, scheduleId: event.scheduleId, kind: outcome.kind }, "failed to record background_task.scheduled_dispatch_failed audit row");
|
|
1712
|
+
/* c8 ignore stop */
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1491
1716
|
/**
|
|
1492
1717
|
* BROWSER_TASK_REDESIGN_PLAN.md §6.2 + §7 — dispatch branch for
|
|
1493
1718
|
* `scheduled.browser_task`. Defers the heavy lifting to
|
|
@@ -51,7 +51,7 @@ export interface DmFreshnessAggregate {
|
|
|
51
51
|
* `agent_log_lag_minutes=0` by construction (the snapshot is built at
|
|
52
52
|
* dispatch time), so including them would drag the percentile toward 0
|
|
53
53
|
* and hide the cohort the plan §6 acceptance threshold targets
|
|
54
|
-
* ("p95 ≤ 60 — i.e. resumed turns are typically within an
|
|
54
|
+
* ("p95 ≤ 60 — i.e. resumed turns are typically within an activity_scan
|
|
55
55
|
* cadence of session start"). When `resumedTurns === 0`, both
|
|
56
56
|
* percentiles are 0 — there is no lag to report.
|
|
57
57
|
*/
|