@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
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({
|
|
@@ -587,6 +630,7 @@ export class EventDispatcher {
|
|
|
587
630
|
morningRoutine: this.morningRoutine,
|
|
588
631
|
fetchWindowRunner: this.fetchWindowRunner,
|
|
589
632
|
roadmapWriteLock: this.roadmapWriteLock,
|
|
633
|
+
todayWriteLock: this.todayWriteLock,
|
|
590
634
|
writeTracker: this.writeTracker,
|
|
591
635
|
getConfiguredServices: () => this.getConfiguredServices(),
|
|
592
636
|
getActiveMailAccounts: () => this.getActiveMailAccounts(),
|
|
@@ -616,6 +660,8 @@ export class EventDispatcher {
|
|
|
616
660
|
getBangCommandRegistry: () => this.bangCommandRegistry,
|
|
617
661
|
getPurchaseHandler: () => this.purchaseHandler,
|
|
618
662
|
getFinalConfirmHandler: () => this.finalConfirmHandler,
|
|
663
|
+
getBackgroundTaskRunner: () => this.backgroundTaskRunner,
|
|
664
|
+
getBrowserTaskRunner: () => this.browserTaskRunner,
|
|
619
665
|
getCurrentSetupMode: () => this.currentSetupMode,
|
|
620
666
|
beginSetupMode: (mode) => this.beginSetupMode(mode),
|
|
621
667
|
lookupCustomBangCommandForEvent: (event) => this.lookupCustomBangCommandForEvent(event),
|
|
@@ -683,6 +729,15 @@ export class EventDispatcher {
|
|
|
683
729
|
getBrowserTaskRunner() {
|
|
684
730
|
return this.browserTaskRunner;
|
|
685
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
|
+
}
|
|
686
741
|
/**
|
|
687
742
|
* BROWSER_TASK_REDESIGN_PLAN.md §7 — wire the terminal-state DM
|
|
688
743
|
* emitter used by the `scheduled.browser_task` failure paths (see
|
|
@@ -712,6 +767,14 @@ export class EventDispatcher {
|
|
|
712
767
|
setAttachmentStore(store) {
|
|
713
768
|
this.attachmentStore = store;
|
|
714
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
|
+
}
|
|
715
778
|
/** Inject the local-Whisper voice transcriber. Optional — when unset,
|
|
716
779
|
* inbound audio attachments are passed to the backend with a path-only
|
|
717
780
|
* reference (the pre-feature behaviour). */
|
|
@@ -720,7 +783,7 @@ export class EventDispatcher {
|
|
|
720
783
|
}
|
|
721
784
|
/**
|
|
722
785
|
* Inject the delegated-sync refresh callback. Called from
|
|
723
|
-
* `
|
|
786
|
+
* `triggerActivityScan` before the gate decision so any cadence the
|
|
724
787
|
* operator left opted-OUT (post-Phase-9 default) populates fresh
|
|
725
788
|
* Gmail / Notion observations the agent can then consume.
|
|
726
789
|
*
|
|
@@ -730,7 +793,7 @@ export class EventDispatcher {
|
|
|
730
793
|
* dispatcher holding a stale reference.
|
|
731
794
|
*
|
|
732
795
|
* Pass `null` to detach (e.g. when no delegated integration exists).
|
|
733
|
-
* The
|
|
796
|
+
* The activity scan then proceeds without a refresh — equivalent to the
|
|
734
797
|
* pre-injection behaviour.
|
|
735
798
|
*/
|
|
736
799
|
setDelegatedSyncRefresh(fn) {
|
|
@@ -738,7 +801,7 @@ export class EventDispatcher {
|
|
|
738
801
|
}
|
|
739
802
|
/**
|
|
740
803
|
* Wire the scheduler's `queueMorningRoutineWake` so the pre-routine
|
|
741
|
-
* gate (
|
|
804
|
+
* gate (activity_scan + evening/weekly/monthly review) can self-recover
|
|
742
805
|
* after a missed 04:00 cron fire. Wired once in `index.ts` after both
|
|
743
806
|
* the dispatcher and scheduler are constructed; passing `null` detaches.
|
|
744
807
|
* When unset, the gate logs a warning and still skips the dependent
|
|
@@ -765,6 +828,16 @@ export class EventDispatcher {
|
|
|
765
828
|
setAgentExecutionTracker(tracker) {
|
|
766
829
|
this.agentExecutionTracker = tracker;
|
|
767
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
|
+
}
|
|
768
841
|
/**
|
|
769
842
|
* Open an execution row for an agent-resolvable firing (§8.1), called from
|
|
770
843
|
* `dispatchSafe` after the setup / cost gates pass so a skipped firing never
|
|
@@ -803,10 +876,21 @@ export class EventDispatcher {
|
|
|
803
876
|
const scheduleRowId = isScheduledEvent(event) && event.scheduleId !== undefined
|
|
804
877
|
? event.scheduleId
|
|
805
878
|
: null;
|
|
806
|
-
|
|
879
|
+
// `begin` opens the rollup row AND returns the resolved Agent slug (or null
|
|
880
|
+
// for a firing that resolves to no Agent — reactive DMs, legacy tasks).
|
|
881
|
+
const agentId = this.agentExecutionTracker.begin(event.correlationId, resolution, {
|
|
807
882
|
scheduleRowId,
|
|
808
883
|
trigger: this.resolveExecutionTrigger(event, taskContext),
|
|
809
884
|
});
|
|
885
|
+
// FEEDBACK_LEARNING_LOOP_DESIGN.md §5 Phase 4 — thread the resolved slug onto
|
|
886
|
+
// the event so `ContextBuilder` can inject this Agent's own
|
|
887
|
+
// `policies/agents/<slug>/lessons.md` (scope `agent:<slug>`). This runs in
|
|
888
|
+
// `dispatchSafe` before `dispatch(event)`, so the stamp is visible to the
|
|
889
|
+
// builder downstream; the morning routine propagates it to Stage A via the
|
|
890
|
+
// `{...parent.data}` spread in `composeStageAEvent`. No-op for unbound runs.
|
|
891
|
+
if (agentId !== null) {
|
|
892
|
+
event.data.agentId = agentId;
|
|
893
|
+
}
|
|
810
894
|
}
|
|
811
895
|
/** Classify an execution's trigger for the rollup row (§5.2). */
|
|
812
896
|
resolveExecutionTrigger(event, taskContext) {
|
|
@@ -962,7 +1046,7 @@ export class EventDispatcher {
|
|
|
962
1046
|
/**
|
|
963
1047
|
* Enter setup mode. Called from `POST /setup/start` so the warm gate
|
|
964
1048
|
* engages the moment the user opens the dashboard setup flow — before any
|
|
965
|
-
* agent turn runs — so concurrent
|
|
1049
|
+
* agent turn runs — so concurrent activity_scan / morning routine / scheduled
|
|
966
1050
|
* wake work cannot race with the setup conversation. Persisted to
|
|
967
1051
|
* `runtime_state` so the flag survives daemon restart.
|
|
968
1052
|
*/
|
|
@@ -1023,8 +1107,8 @@ export class EventDispatcher {
|
|
|
1023
1107
|
if (this.morningRoutineInProgress) {
|
|
1024
1108
|
executions.push({ kind: "routine", key: "morning_routine" });
|
|
1025
1109
|
}
|
|
1026
|
-
if (this.
|
|
1027
|
-
executions.push({ kind: "routine", key: "
|
|
1110
|
+
if (this.activityScanInProgress) {
|
|
1111
|
+
executions.push({ kind: "routine", key: "activity_scan" });
|
|
1028
1112
|
}
|
|
1029
1113
|
const runningTasks = this.db
|
|
1030
1114
|
.prepare(`SELECT id, task_type, task_description
|
|
@@ -1042,7 +1126,7 @@ export class EventDispatcher {
|
|
|
1042
1126
|
return executions;
|
|
1043
1127
|
}
|
|
1044
1128
|
/**
|
|
1045
|
-
* Gate for autonomous background work (cron routines,
|
|
1129
|
+
* Gate for autonomous background work (cron routines, activity_scan,
|
|
1046
1130
|
* scheduled wake tasks, startup catchup, calendar-poller reactive events).
|
|
1047
1131
|
*
|
|
1048
1132
|
* Two layers:
|
|
@@ -1131,7 +1215,7 @@ export class EventDispatcher {
|
|
|
1131
1215
|
/**
|
|
1132
1216
|
* Check whether this autonomous event should be skipped because the daily
|
|
1133
1217
|
* autonomous cost cap has been exceeded. Uses priority-based degradation:
|
|
1134
|
-
*
|
|
1218
|
+
* activity_scan (lowest priority, skipped first) → roadmap_refresh →
|
|
1135
1219
|
* evening_review → morning_routine (highest, last to be cut).
|
|
1136
1220
|
*
|
|
1137
1221
|
* Lower-priority events are skipped at 100% of cap; higher-priority events
|
|
@@ -1157,7 +1241,7 @@ export class EventDispatcher {
|
|
|
1157
1241
|
? event.routine
|
|
1158
1242
|
: null;
|
|
1159
1243
|
const thresholds = {
|
|
1160
|
-
|
|
1244
|
+
activity_scan: 1.0, // skipped first (at 100% of cap)
|
|
1161
1245
|
roadmap_refresh: 1.2, // skipped at 120%
|
|
1162
1246
|
evening_review: 1.5, // skipped at 150%
|
|
1163
1247
|
morning_routine: 2.0, // last to be cut (only at 200%)
|
|
@@ -1165,6 +1249,45 @@ export class EventDispatcher {
|
|
|
1165
1249
|
const threshold = routine ? (thresholds[routine] ?? 1.0) : 1.0;
|
|
1166
1250
|
return todayCost >= cap * threshold;
|
|
1167
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
|
+
}
|
|
1168
1291
|
async handleEvent(event) {
|
|
1169
1292
|
try {
|
|
1170
1293
|
await this.handleEventInner(event);
|
|
@@ -1186,12 +1309,12 @@ export class EventDispatcher {
|
|
|
1186
1309
|
}
|
|
1187
1310
|
}
|
|
1188
1311
|
/**
|
|
1189
|
-
* Public entry point. Delegates to the
|
|
1312
|
+
* Public entry point. Delegates to the ActivityScanCoordinator.
|
|
1190
1313
|
* The dispatcher keeps the wrapper because tests + the cron entry
|
|
1191
|
-
* call `dispatcher.
|
|
1314
|
+
* call `dispatcher.triggerActivityScan(source, opts)` directly.
|
|
1192
1315
|
*/
|
|
1193
|
-
async
|
|
1194
|
-
return this.
|
|
1316
|
+
async triggerActivityScan(source, options = {}) {
|
|
1317
|
+
return this.activityScan.trigger(source, options);
|
|
1195
1318
|
}
|
|
1196
1319
|
/**
|
|
1197
1320
|
* Advisory check: is a morning routine execution or retry currently in
|
|
@@ -1206,7 +1329,7 @@ export class EventDispatcher {
|
|
|
1206
1329
|
*
|
|
1207
1330
|
* Public (not private) because Phase 4's `AuthHealthMonitor.checkAll()`
|
|
1208
1331
|
* shares the same skip-while-morning-routine-active invariant as the
|
|
1209
|
-
*
|
|
1332
|
+
* activity scan, and injects this method as an option so a probe tick
|
|
1210
1333
|
* running concurrently with morning routine can no-op cleanly. See
|
|
1211
1334
|
* `docs/design/09-safety-cost.md` §9.5.4.
|
|
1212
1335
|
*/
|
|
@@ -1245,6 +1368,38 @@ export class EventDispatcher {
|
|
|
1245
1368
|
.run(event.scheduleId);
|
|
1246
1369
|
}
|
|
1247
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
|
+
}
|
|
1248
1403
|
async dispatchSafe(event) {
|
|
1249
1404
|
const trigger = this.isReactive(event) ? "reactive" : "autonomous";
|
|
1250
1405
|
const startMs = Date.now();
|
|
@@ -1282,7 +1437,7 @@ export class EventDispatcher {
|
|
|
1282
1437
|
}
|
|
1283
1438
|
// Autonomous daily cost cap — safety net distinct from removed Phase 9
|
|
1284
1439
|
// maxDailyCostUsd (which blanket-blocked all sessions including DMs).
|
|
1285
|
-
// Reactive sessions always pass. Degradation priority:
|
|
1440
|
+
// Reactive sessions always pass. Degradation priority: activity_scan is
|
|
1286
1441
|
// skipped first, morning_routine last.
|
|
1287
1442
|
if (this.shouldSkipForCostCap(event)) {
|
|
1288
1443
|
this.releaseClaimedSchedule(event);
|
|
@@ -1290,6 +1445,36 @@ export class EventDispatcher {
|
|
|
1290
1445
|
logger.info({ eventType: event.type, source: event.source }, "Event skipped — autonomous daily cost cap exceeded");
|
|
1291
1446
|
return;
|
|
1292
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
|
+
}
|
|
1293
1478
|
}
|
|
1294
1479
|
// AGENT_DEFINITIONS_DESIGN.md §8.1 — open the execution row after the
|
|
1295
1480
|
// setup / cost gates pass (a gated firing records no execution) and
|
|
@@ -1338,9 +1523,9 @@ export class EventDispatcher {
|
|
|
1338
1523
|
await this.errorRouter.handleError(event, err);
|
|
1339
1524
|
}
|
|
1340
1525
|
finally {
|
|
1341
|
-
if (isRoutineEvent(event) && event.routine === "
|
|
1342
|
-
this.
|
|
1343
|
-
this.
|
|
1526
|
+
if (isRoutineEvent(event) && event.routine === "activity_scan") {
|
|
1527
|
+
this.activityScanInProgress = false;
|
|
1528
|
+
this.activityScanInProgressAt = null;
|
|
1344
1529
|
}
|
|
1345
1530
|
}
|
|
1346
1531
|
}
|
|
@@ -1416,7 +1601,7 @@ export class EventDispatcher {
|
|
|
1416
1601
|
await this.scheduledTasks.executeSkillCurationRoutine(event);
|
|
1417
1602
|
}
|
|
1418
1603
|
else {
|
|
1419
|
-
//
|
|
1604
|
+
// activity_scan, evening_review, weekly_review, monthly_review
|
|
1420
1605
|
// Tier is resolved from process-key defaults by BackendRouter.
|
|
1421
1606
|
await this.scheduledTasks.executeDefault(event);
|
|
1422
1607
|
}
|
|
@@ -1460,6 +1645,19 @@ export class EventDispatcher {
|
|
|
1460
1645
|
await this.scheduledTasks.executeScheduledTask(event);
|
|
1461
1646
|
});
|
|
1462
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
|
+
}
|
|
1463
1661
|
else if (isAgentTaskEvent(event)) {
|
|
1464
1662
|
// scheduled.task — no gate, retains existing parallel-execution
|
|
1465
1663
|
// behavior. (scheduled.dm subtype is handled above.)
|
|
@@ -1472,10 +1670,49 @@ export class EventDispatcher {
|
|
|
1472
1670
|
// logic; here we wire it into the agent_schedule lifecycle.
|
|
1473
1671
|
await this.handleScheduledBrowserTaskDispatch(event);
|
|
1474
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
|
+
}
|
|
1475
1679
|
else {
|
|
1476
1680
|
await this.scheduledTasks.executeDefault(event);
|
|
1477
1681
|
}
|
|
1478
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
|
+
}
|
|
1479
1716
|
/**
|
|
1480
1717
|
* BROWSER_TASK_REDESIGN_PLAN.md §6.2 + §7 — dispatch branch for
|
|
1481
1718
|
* `scheduled.browser_task`. Defers the heavy lifting to
|