@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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { AgentTaskEvent, TaskDeliveryAsset, TaskDeliveryEvent } from "@aitne/shared";
|
|
3
|
+
import type { AgentConfig } from "../config.js";
|
|
4
|
+
import type { OutboundAttachmentRef } from "../adapters/types.js";
|
|
5
|
+
import type { INotificationManager } from "./dispatcher-types.js";
|
|
6
|
+
export declare const TASK_DELIVERY_GATE_KEYS: readonly ["owner_dm:owner", "dashboard_chat:dashboard"];
|
|
7
|
+
export interface BrowserTaskDeliveryEventInput {
|
|
8
|
+
taskId: string;
|
|
9
|
+
originatingChannel: string | null;
|
|
10
|
+
title: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createBrowserTaskResultDeliveryEvent(input: BrowserTaskDeliveryEventInput & {
|
|
13
|
+
report: string;
|
|
14
|
+
screenshotKeys?: readonly string[];
|
|
15
|
+
}): TaskDeliveryEvent;
|
|
16
|
+
export declare function createBrowserTaskClarificationDeliveryEvent(input: BrowserTaskDeliveryEventInput & {
|
|
17
|
+
clarificationId: string;
|
|
18
|
+
question: string;
|
|
19
|
+
contextSummary: string | null;
|
|
20
|
+
screenshotKey: string | null;
|
|
21
|
+
}): TaskDeliveryEvent;
|
|
22
|
+
export interface BackgroundTaskResultDeliveryInput {
|
|
23
|
+
taskId: string;
|
|
24
|
+
originatingChannel: string | null;
|
|
25
|
+
title: string;
|
|
26
|
+
/** Worker-authored summary — the idle-send body / active-turn grounding. */
|
|
27
|
+
draft: string;
|
|
28
|
+
/** Verbatim result — injected into the active delivery turn so the DM
|
|
29
|
+
* agent can weave full detail without a tool round-trip. */
|
|
30
|
+
report: string;
|
|
31
|
+
/** Worker-produced deliverable files (PDF / PPTX / PNG / docs) to attach
|
|
32
|
+
* to the result DM. Omit / empty when the task produced no files. */
|
|
33
|
+
assets?: readonly TaskDeliveryAsset[];
|
|
34
|
+
}
|
|
35
|
+
export declare function createBackgroundTaskResultDeliveryEvent(input: BackgroundTaskResultDeliveryInput): TaskDeliveryEvent;
|
|
36
|
+
export declare function createBackgroundTaskClarificationDeliveryEvent(input: {
|
|
37
|
+
taskId: string;
|
|
38
|
+
originatingChannel: string | null;
|
|
39
|
+
title: string;
|
|
40
|
+
clarificationId: string;
|
|
41
|
+
question: string;
|
|
42
|
+
contextSummary: string | null;
|
|
43
|
+
}): TaskDeliveryEvent;
|
|
44
|
+
/**
|
|
45
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4, opt-in)
|
|
46
|
+
* — wrap a routine autonomous forward as a `task.delivery` event so it
|
|
47
|
+
* flows through the same gate + activity-branch machinery: an active owner
|
|
48
|
+
* gets a woven delivery turn, an idle owner the verbatim send + record.
|
|
49
|
+
* Carries no DB row (synthetic id, no `delivered_at` recovery — autonomous
|
|
50
|
+
* forwards are fire-and-forget, exactly as the verbatim path is today).
|
|
51
|
+
*/
|
|
52
|
+
export declare function createAutonomousForwardDeliveryEvent(input: {
|
|
53
|
+
content: string;
|
|
54
|
+
originatingChannel: string | null;
|
|
55
|
+
title?: string;
|
|
56
|
+
correlationId?: string | null;
|
|
57
|
+
}): TaskDeliveryEvent;
|
|
58
|
+
export interface TaskDeliveryHandlerDeps {
|
|
59
|
+
db: Database.Database;
|
|
60
|
+
config: AgentConfig;
|
|
61
|
+
notificationMgr: INotificationManager;
|
|
62
|
+
executeScheduledTask(event: AgentTaskEvent): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a task's deliverable assets (browser-task trace screenshots
|
|
65
|
+
* and/or worker-written files: PDF / PPTX / PNG / docs) to outbound
|
|
66
|
+
* attachments for the platform the DM lands on. Used by BOTH the idle
|
|
67
|
+
* direct-send and the active delivery turn so the owner receives the
|
|
68
|
+
* actual files inline (BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 — delivery
|
|
69
|
+
* assets). Injected from bootstrap (closure over `paDataDir` + the
|
|
70
|
+
* dashboard ingest hook); absent in unit tests, where assets are simply
|
|
71
|
+
* omitted. Best-effort — must resolve to `[]` rather than throw.
|
|
72
|
+
*/
|
|
73
|
+
resolveAssets?(platform: string, assets: readonly TaskDeliveryAsset[]): Promise<readonly OutboundAttachmentRef[]>;
|
|
74
|
+
nowFn?: () => number;
|
|
75
|
+
}
|
|
76
|
+
/** The asset key the dispatch arm puts resolved refs under on the
|
|
77
|
+
* synthetic `scheduled.dm` event so the result processor can attach them
|
|
78
|
+
* to the woven reply. */
|
|
79
|
+
export declare const TASK_DELIVERY_ATTACHMENTS_KEY = "task_delivery_attachments";
|
|
80
|
+
export declare function handleTaskDeliveryInsideGate(deps: TaskDeliveryHandlerDeps, event: TaskDeliveryEvent): Promise<void>;
|
|
81
|
+
export declare function enqueueUndeliveredBrowserTaskDeliveries(params: {
|
|
82
|
+
db: Database.Database;
|
|
83
|
+
eventBus: {
|
|
84
|
+
put(event: TaskDeliveryEvent): Promise<void>;
|
|
85
|
+
};
|
|
86
|
+
nowMs?: number;
|
|
87
|
+
limit?: number;
|
|
88
|
+
}): Promise<number>;
|
|
89
|
+
/**
|
|
90
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 — delivery recovery sweep for
|
|
91
|
+
* background tasks. Re-enqueues `task.delivery` events for completed
|
|
92
|
+
* notify=true artifacts whose DM was never sent/recorded
|
|
93
|
+
* (`delivered_at IS NULL`) plus undelivered open clarifications. Run on
|
|
94
|
+
* the housekeeping tick (boot + periodic). Idempotent against the
|
|
95
|
+
* in-gate message-existence dedup — a re-enqueue after a successful send
|
|
96
|
+
* only back-fills `delivered_at`, never double-sends (§4.4).
|
|
97
|
+
*/
|
|
98
|
+
export declare function enqueueUndeliveredBackgroundTaskDeliveries(params: {
|
|
99
|
+
db: Database.Database;
|
|
100
|
+
eventBus: {
|
|
101
|
+
put(event: TaskDeliveryEvent): Promise<void>;
|
|
102
|
+
};
|
|
103
|
+
nowMs?: number;
|
|
104
|
+
limit?: number;
|
|
105
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { EventPriority, createEvent, } from "@aitne/shared";
|
|
4
|
+
import { getBrowserTask, listUndeliveredBrowserTaskReports, markBrowserTaskDelivered, } from "../db/browser-task-store.js";
|
|
5
|
+
import { getClarification, listUndeliveredClarifications, markClarificationDelivered, } from "../db/browser-task-clarifications-store.js";
|
|
6
|
+
import { getBackgroundTask, listUndeliveredBackgroundTaskReports, markBackgroundTaskDelivered, } from "../db/background-task-store.js";
|
|
7
|
+
import { getClarification as getBackgroundClarification, listUndeliveredClarifications as listUndeliveredBackgroundClarifications, markClarificationDelivered as markBackgroundClarificationDelivered, } from "../db/background-task-clarifications-store.js";
|
|
8
|
+
import { parseChannelRef } from "../db/browser-automation-purchase-primary-channels-store.js";
|
|
9
|
+
import { isInQuietHoursAt } from "./quiet-hours.js";
|
|
10
|
+
import { DASHBOARD_CHAT_SCOPE, DASHBOARD_SCOPE_KEY, OWNER_DM_SCOPE, OWNER_SCOPE_KEY, } from "../messaging/constants.js";
|
|
11
|
+
import { classifyOwnerDmActivity, } from "./context-builder-conversation.js";
|
|
12
|
+
import { recordProactiveForwardDeliveries, } from "./channel-timeline.js";
|
|
13
|
+
import { createLogger } from "../logging.js";
|
|
14
|
+
const logger = createLogger("dispatcher-task-delivery");
|
|
15
|
+
export const TASK_DELIVERY_GATE_KEYS = [
|
|
16
|
+
`${OWNER_DM_SCOPE}:${OWNER_SCOPE_KEY}`,
|
|
17
|
+
`${DASHBOARD_CHAT_SCOPE}:${DASHBOARD_SCOPE_KEY}`,
|
|
18
|
+
];
|
|
19
|
+
const REPORT_DRAFT_CAP = 3_800;
|
|
20
|
+
export function createBrowserTaskResultDeliveryEvent(input) {
|
|
21
|
+
const draft = input.report.length > REPORT_DRAFT_CAP
|
|
22
|
+
? `${input.report.slice(0, REPORT_DRAFT_CAP)}\n\n[... truncated; open the browser-task detail page for the full report]`
|
|
23
|
+
: input.report;
|
|
24
|
+
return Object.assign(createEvent({
|
|
25
|
+
type: "task.delivery",
|
|
26
|
+
source: "browser_task",
|
|
27
|
+
priority: EventPriority.HIGH,
|
|
28
|
+
data: {},
|
|
29
|
+
}), {
|
|
30
|
+
taskContext: {
|
|
31
|
+
taskKind: "browser_task",
|
|
32
|
+
taskId: input.taskId,
|
|
33
|
+
deliveryType: "task_result",
|
|
34
|
+
title: input.title,
|
|
35
|
+
draft,
|
|
36
|
+
report: input.report,
|
|
37
|
+
originatingChannel: input.originatingChannel,
|
|
38
|
+
screenshotKeys: [...(input.screenshotKeys ?? [])],
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function createBrowserTaskClarificationDeliveryEvent(input) {
|
|
43
|
+
const draft = [
|
|
44
|
+
`Browser task "${input.title}" needs your input.`,
|
|
45
|
+
`Question: ${input.question}`,
|
|
46
|
+
input.contextSummary ? `Context: ${input.contextSummary}` : null,
|
|
47
|
+
"Reply here with the answer; I will pass it back to the task.",
|
|
48
|
+
]
|
|
49
|
+
.filter((line) => line !== null && line.length > 0)
|
|
50
|
+
.join("\n");
|
|
51
|
+
return Object.assign(createEvent({
|
|
52
|
+
type: "task.delivery",
|
|
53
|
+
source: "browser_task",
|
|
54
|
+
priority: EventPriority.HIGH,
|
|
55
|
+
data: {},
|
|
56
|
+
}), {
|
|
57
|
+
taskContext: {
|
|
58
|
+
taskKind: "browser_task",
|
|
59
|
+
taskId: input.taskId,
|
|
60
|
+
deliveryType: "task_clarification",
|
|
61
|
+
title: input.title,
|
|
62
|
+
draft,
|
|
63
|
+
report: input.question,
|
|
64
|
+
originatingChannel: input.originatingChannel,
|
|
65
|
+
clarificationId: input.clarificationId,
|
|
66
|
+
contextSummary: input.contextSummary,
|
|
67
|
+
screenshotKeys: input.screenshotKey ? [input.screenshotKey] : [],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function createBackgroundTaskResultDeliveryEvent(input) {
|
|
72
|
+
return Object.assign(createEvent({
|
|
73
|
+
type: "task.delivery",
|
|
74
|
+
source: "background_task",
|
|
75
|
+
priority: EventPriority.HIGH,
|
|
76
|
+
data: {},
|
|
77
|
+
}), {
|
|
78
|
+
taskContext: {
|
|
79
|
+
taskKind: "background_task",
|
|
80
|
+
taskId: input.taskId,
|
|
81
|
+
deliveryType: "task_result",
|
|
82
|
+
title: input.title,
|
|
83
|
+
// The worker-authored `draft` IS the natural-language summary;
|
|
84
|
+
// unlike browser_task (which truncates the raw report), the
|
|
85
|
+
// background worker already produced a clean draft + a separate
|
|
86
|
+
// verbatim report. Idle sends the draft; the active turn reads
|
|
87
|
+
// the full report (injected below) and weaves.
|
|
88
|
+
draft: input.draft,
|
|
89
|
+
report: input.report,
|
|
90
|
+
originatingChannel: input.originatingChannel,
|
|
91
|
+
screenshotKeys: [],
|
|
92
|
+
assets: [...(input.assets ?? [])],
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export function createBackgroundTaskClarificationDeliveryEvent(input) {
|
|
97
|
+
const draft = [
|
|
98
|
+
`The background task "${input.title}" needs your input.`,
|
|
99
|
+
`Question: ${input.question}`,
|
|
100
|
+
input.contextSummary ? `Context: ${input.contextSummary}` : null,
|
|
101
|
+
"Reply here with the answer; I will pass it back to the task.",
|
|
102
|
+
]
|
|
103
|
+
.filter((line) => line !== null && line.length > 0)
|
|
104
|
+
.join("\n");
|
|
105
|
+
return Object.assign(createEvent({
|
|
106
|
+
type: "task.delivery",
|
|
107
|
+
source: "background_task",
|
|
108
|
+
priority: EventPriority.HIGH,
|
|
109
|
+
data: {},
|
|
110
|
+
}), {
|
|
111
|
+
taskContext: {
|
|
112
|
+
taskKind: "background_task",
|
|
113
|
+
taskId: input.taskId,
|
|
114
|
+
deliveryType: "task_clarification",
|
|
115
|
+
title: input.title,
|
|
116
|
+
draft,
|
|
117
|
+
report: input.question,
|
|
118
|
+
originatingChannel: input.originatingChannel,
|
|
119
|
+
clarificationId: input.clarificationId,
|
|
120
|
+
contextSummary: input.contextSummary,
|
|
121
|
+
screenshotKeys: [],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4, opt-in)
|
|
127
|
+
* — wrap a routine autonomous forward as a `task.delivery` event so it
|
|
128
|
+
* flows through the same gate + activity-branch machinery: an active owner
|
|
129
|
+
* gets a woven delivery turn, an idle owner the verbatim send + record.
|
|
130
|
+
* Carries no DB row (synthetic id, no `delivered_at` recovery — autonomous
|
|
131
|
+
* forwards are fire-and-forget, exactly as the verbatim path is today).
|
|
132
|
+
*/
|
|
133
|
+
export function createAutonomousForwardDeliveryEvent(input) {
|
|
134
|
+
return Object.assign(createEvent({
|
|
135
|
+
type: "task.delivery",
|
|
136
|
+
source: "autonomous_forward",
|
|
137
|
+
priority: EventPriority.HIGH,
|
|
138
|
+
...(input.correlationId ? { correlationId: input.correlationId } : {}),
|
|
139
|
+
data: {},
|
|
140
|
+
}), {
|
|
141
|
+
taskContext: {
|
|
142
|
+
taskKind: "autonomous_forward",
|
|
143
|
+
taskId: randomUUID(),
|
|
144
|
+
deliveryType: "task_result",
|
|
145
|
+
title: input.title ?? "update",
|
|
146
|
+
// The forward content is both the idle-send body and the active
|
|
147
|
+
// turn's grounding — there is no separate verbatim/summary split.
|
|
148
|
+
draft: input.content,
|
|
149
|
+
report: input.content,
|
|
150
|
+
originatingChannel: input.originatingChannel,
|
|
151
|
+
screenshotKeys: [],
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** The asset key the dispatch arm puts resolved refs under on the
|
|
156
|
+
* synthetic `scheduled.dm` event so the result processor can attach them
|
|
157
|
+
* to the woven reply. */
|
|
158
|
+
export const TASK_DELIVERY_ATTACHMENTS_KEY = "task_delivery_attachments";
|
|
159
|
+
/**
|
|
160
|
+
* The canonical deliverable-asset list for a payload. Prefers an explicit
|
|
161
|
+
* `assets` manifest (background-task workers populate it); falls back to
|
|
162
|
+
* folding legacy browser-task `screenshotKeys` into screenshot assets so
|
|
163
|
+
* the Phase-1 source keeps working unchanged. Empty ⇒ nothing to present.
|
|
164
|
+
*/
|
|
165
|
+
function effectiveAssets(payload) {
|
|
166
|
+
if (payload.assets && payload.assets.length > 0) {
|
|
167
|
+
return payload.assets;
|
|
168
|
+
}
|
|
169
|
+
return (payload.screenshotKeys ?? []).map((key) => ({
|
|
170
|
+
filename: basename(key),
|
|
171
|
+
kind: "screenshot",
|
|
172
|
+
screenshotKey: key,
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The agent-facing slice of the asset list — filename / kind / label only.
|
|
177
|
+
* Internal locations (screenshotKey, absolute path) are deliberately
|
|
178
|
+
* withheld from the LLM context: the daemon attaches the bytes, and the
|
|
179
|
+
* agent references assets by name/kind. Detail/paths for a later re-send
|
|
180
|
+
* come from the artifact API, not the conversation.
|
|
181
|
+
*/
|
|
182
|
+
function assetManifest(assets) {
|
|
183
|
+
return assets.map((a) => ({
|
|
184
|
+
filename: a.filename,
|
|
185
|
+
kind: a.kind,
|
|
186
|
+
...(a.label ? { label: a.label } : {}),
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
async function resolveDeliveryAttachments(deps, platform, payload) {
|
|
190
|
+
const assets = effectiveAssets(payload);
|
|
191
|
+
if (assets.length === 0 || !deps.resolveAssets)
|
|
192
|
+
return [];
|
|
193
|
+
try {
|
|
194
|
+
return await deps.resolveAssets(platform, assets);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
// Assets are a best-effort enrichment of the text draft — a resolver
|
|
198
|
+
// failure must never block the result/clarification DM itself.
|
|
199
|
+
logger.warn({ err, taskId: payload.taskId, deliveryType: payload.deliveryType }, "task.delivery asset resolution failed; sending text only");
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export async function handleTaskDeliveryInsideGate(deps, event) {
|
|
204
|
+
const payload = event.taskContext;
|
|
205
|
+
if (payload.taskKind !== "browser_task"
|
|
206
|
+
&& payload.taskKind !== "background_task"
|
|
207
|
+
&& payload.taskKind !== "autonomous_forward") {
|
|
208
|
+
logger.warn({ taskKind: payload.taskKind, taskId: payload.taskId }, "task.delivery skipped: unsupported task kind");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const now = deps.nowFn?.() ?? Date.now();
|
|
212
|
+
// `autonomous_forward` carries no DB row, so the row-keyed dedup /
|
|
213
|
+
// delivered_at checks below do not apply — it is fire-and-forget (the
|
|
214
|
+
// verbatim path it replaces had no recovery either). Browser/background
|
|
215
|
+
// task deliveries keep the full idempotency contract.
|
|
216
|
+
if (payload.taskKind !== "autonomous_forward") {
|
|
217
|
+
if (await backfillDeliveredAtIfMessageExists(deps.db, payload, now)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (isDeliveryAlreadyMarked(deps.db, payload)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// No deliverable channel ⇒ the report/clarification is still filed on
|
|
225
|
+
// the row, but there is nowhere to DM it (`browser_task.originating_channel`
|
|
226
|
+
// is nullable — e.g. synthetic / channel-less runs). Mark delivered so
|
|
227
|
+
// the boot/periodic recovery sweep (which selects `delivered_at IS NULL`)
|
|
228
|
+
// does not re-enqueue this task on every 30 s tick, and so the active
|
|
229
|
+
// branch never spends a full LLM turn on something it cannot deliver.
|
|
230
|
+
// Matches the pre-Phase-1 notifier's "skip once" behaviour, minus the
|
|
231
|
+
// churn. Logged as a degrade per the project's "no silent drops" posture.
|
|
232
|
+
const channel = resolveDeliveryChannel(payload);
|
|
233
|
+
if (!channel) {
|
|
234
|
+
logger.warn({
|
|
235
|
+
taskId: payload.taskId,
|
|
236
|
+
deliveryType: payload.deliveryType,
|
|
237
|
+
originatingChannel: payload.originatingChannel,
|
|
238
|
+
}, "task.delivery dropped: no parseable originating channel; report "
|
|
239
|
+
+ "filed on the row, marking delivered to stop recovery churn");
|
|
240
|
+
markDeliveredAt(deps.db, payload, now);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const activity = classifyOwnerDmActivity({ db: deps.db, config: deps.config }, now);
|
|
244
|
+
if (activity === "active") {
|
|
245
|
+
await deliverActive(deps, event, activity, channel);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// Idle/asleep + quiet hours ⇒ defer. The design (§4.5 / §10.6) routes
|
|
249
|
+
// the idle branch through the proactive quiet-hours suppression so a
|
|
250
|
+
// task finishing at 03:00 does not ping a sleeping owner. The idle send
|
|
251
|
+
// uses `replyTo` (to hit the originating channel), which bypasses
|
|
252
|
+
// NotificationManager's own quiet-hours gate — so we gate here instead.
|
|
253
|
+
// Leaving `delivered_at` NULL lets the boot/periodic recovery sweep
|
|
254
|
+
// re-deliver once the window lifts (and re-classify: if the owner is
|
|
255
|
+
// active by then, it weaves into the live thread instead).
|
|
256
|
+
if (isQuietHoursNow(deps.config, now)) {
|
|
257
|
+
logger.info({
|
|
258
|
+
taskId: payload.taskId,
|
|
259
|
+
deliveryType: payload.deliveryType,
|
|
260
|
+
}, "task.delivery idle send deferred — quiet hours; recovery sweep will "
|
|
261
|
+
+ "re-deliver after the window");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
await deliverIdle(deps, event, channel);
|
|
265
|
+
}
|
|
266
|
+
function isQuietHoursNow(config, nowMs) {
|
|
267
|
+
return isInQuietHoursAt(new Date(nowMs), {
|
|
268
|
+
start: config.quietHoursStart,
|
|
269
|
+
end: config.quietHoursEnd,
|
|
270
|
+
timezone: config.timezone || undefined,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async function deliverActive(deps, event, activity, channel) {
|
|
274
|
+
const payload = event.taskContext;
|
|
275
|
+
const attachments = await resolveDeliveryAttachments(deps, channel.platform, payload);
|
|
276
|
+
const scheduledEvent = createScheduledDmDeliveryEvent(event, activity, attachments, channel);
|
|
277
|
+
try {
|
|
278
|
+
await deps.executeScheduledTask(scheduledEvent);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
logger.warn({ err, taskId: payload.taskId, deliveryType: payload.deliveryType }, "task.delivery active turn failed; falling back to direct draft");
|
|
282
|
+
// Reuse the already-resolved attachments — re-resolving would
|
|
283
|
+
// re-ingest dashboard files and could diverge from what the active
|
|
284
|
+
// turn was given.
|
|
285
|
+
await deliverIdle(deps, event, channel, attachments);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const now = deps.nowFn?.() ?? Date.now();
|
|
289
|
+
if (await backfillDeliveredAtIfMessageExists(deps.db, payload, now)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
logger.warn({ taskId: payload.taskId, deliveryType: payload.deliveryType }, "task.delivery active turn produced no tagged message; falling back to direct draft");
|
|
293
|
+
await deliverIdle(deps, event, channel, attachments);
|
|
294
|
+
}
|
|
295
|
+
async function deliverIdle(deps, event, channel, preResolvedAttachments) {
|
|
296
|
+
const payload = event.taskContext;
|
|
297
|
+
const attachments = preResolvedAttachments
|
|
298
|
+
?? (await resolveDeliveryAttachments(deps, channel.platform, payload));
|
|
299
|
+
await deps.notificationMgr.send(payload.draft, event, {
|
|
300
|
+
priority: "normal",
|
|
301
|
+
category: "agent",
|
|
302
|
+
replyTo: {
|
|
303
|
+
platform: channel.platform,
|
|
304
|
+
channel: channel.channelId,
|
|
305
|
+
threadId: null,
|
|
306
|
+
},
|
|
307
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
308
|
+
});
|
|
309
|
+
const dispatchId = randomUUID();
|
|
310
|
+
const now = deps.nowFn?.() ?? Date.now();
|
|
311
|
+
const recordAndMark = deps.db.transaction(() => {
|
|
312
|
+
recordProactiveForwardDeliveries({
|
|
313
|
+
db: deps.db,
|
|
314
|
+
config: deps.config,
|
|
315
|
+
deliveries: [
|
|
316
|
+
{
|
|
317
|
+
platform: channel.platform,
|
|
318
|
+
channel: channel.channelId,
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
content: payload.draft,
|
|
322
|
+
dispatchId,
|
|
323
|
+
dispatchIds: [dispatchId],
|
|
324
|
+
notificationType: notificationTypeFor(payload),
|
|
325
|
+
extraMetadata: taskDeliveryMetadata(payload, false),
|
|
326
|
+
});
|
|
327
|
+
markDeliveredAt(deps.db, payload, now);
|
|
328
|
+
});
|
|
329
|
+
recordAndMark();
|
|
330
|
+
}
|
|
331
|
+
function createScheduledDmDeliveryEvent(event, activity, attachments, channel) {
|
|
332
|
+
const payload = event.taskContext;
|
|
333
|
+
const manifest = assetManifest(effectiveAssets(payload));
|
|
334
|
+
return Object.assign(createEvent({
|
|
335
|
+
type: "scheduled.dm",
|
|
336
|
+
source: "task.delivery",
|
|
337
|
+
priority: EventPriority.HIGH,
|
|
338
|
+
correlationId: event.correlationId,
|
|
339
|
+
data: {
|
|
340
|
+
reply_target: {
|
|
341
|
+
platform: channel.platform,
|
|
342
|
+
channel: channel.channelId,
|
|
343
|
+
threadId: null,
|
|
344
|
+
// Audit-only label (not consumed for routing) — use the actual task
|
|
345
|
+
// kind so background_task / autonomous_forward weaves aren't all
|
|
346
|
+
// mislabelled as browser_task in telemetry.
|
|
347
|
+
sender: payload.taskKind,
|
|
348
|
+
},
|
|
349
|
+
task_delivery_record: {
|
|
350
|
+
notificationType: notificationTypeFor(payload),
|
|
351
|
+
metadata: taskDeliveryMetadata(payload, true),
|
|
352
|
+
},
|
|
353
|
+
// Resolved deliverable files — the result processor attaches these to
|
|
354
|
+
// the woven reply so the owner receives them inline (the active turn
|
|
355
|
+
// is a no-tool DM turn and cannot self-attach). Empty ⇒ omitted.
|
|
356
|
+
...(attachments.length > 0
|
|
357
|
+
? { [TASK_DELIVERY_ATTACHMENTS_KEY]: [...attachments] }
|
|
358
|
+
: {}),
|
|
359
|
+
},
|
|
360
|
+
}), {
|
|
361
|
+
task: `task delivery: ${payload.title}`,
|
|
362
|
+
taskContext: {
|
|
363
|
+
task_delivery: {
|
|
364
|
+
taskKind: payload.taskKind,
|
|
365
|
+
taskId: payload.taskId,
|
|
366
|
+
deliveryType: payload.deliveryType,
|
|
367
|
+
title: payload.title,
|
|
368
|
+
draft: payload.draft,
|
|
369
|
+
report: payload.report ?? payload.draft,
|
|
370
|
+
clarificationId: payload.clarificationId ?? null,
|
|
371
|
+
contextSummary: payload.contextSummary ?? null,
|
|
372
|
+
// Agent-facing asset manifest (filename/kind/label only). Present
|
|
373
|
+
// only when the task produced deliverables — the skill references
|
|
374
|
+
// them, and the daemon attaches the bytes. Empty ⇒ no asset block.
|
|
375
|
+
assets: manifest,
|
|
376
|
+
activity,
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function resolveDeliveryChannel(payload) {
|
|
382
|
+
return payload.originatingChannel
|
|
383
|
+
? parseChannelRef(payload.originatingChannel)
|
|
384
|
+
: null;
|
|
385
|
+
}
|
|
386
|
+
function notificationTypeFor(payload) {
|
|
387
|
+
if (payload.taskKind === "autonomous_forward")
|
|
388
|
+
return "proactive_forward";
|
|
389
|
+
return payload.deliveryType === "task_clarification"
|
|
390
|
+
? "task_clarification"
|
|
391
|
+
: "task_result";
|
|
392
|
+
}
|
|
393
|
+
function taskDeliveryMetadata(payload, activeTurn) {
|
|
394
|
+
return {
|
|
395
|
+
taskKind: payload.taskKind,
|
|
396
|
+
taskId: payload.taskId,
|
|
397
|
+
...(activeTurn ? { deliveredTaskId: payload.taskId } : {}),
|
|
398
|
+
deliveryType: payload.deliveryType,
|
|
399
|
+
...(payload.clarificationId
|
|
400
|
+
? { clarificationId: payload.clarificationId }
|
|
401
|
+
: {}),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function isDeliveryAlreadyMarked(db, payload) {
|
|
405
|
+
if (payload.deliveryType === "task_result") {
|
|
406
|
+
const deliveredAt = payload.taskKind === "background_task"
|
|
407
|
+
? (getBackgroundTask(db, payload.taskId)?.deliveredAt ?? null)
|
|
408
|
+
: (getBrowserTask(db, payload.taskId)?.deliveredAt ?? null);
|
|
409
|
+
return deliveredAt !== null;
|
|
410
|
+
}
|
|
411
|
+
if (!payload.clarificationId)
|
|
412
|
+
return false;
|
|
413
|
+
const clarDeliveredAt = payload.taskKind === "background_task"
|
|
414
|
+
? (getBackgroundClarification(db, payload.clarificationId)?.deliveredAt ?? null)
|
|
415
|
+
: (getClarification(db, payload.clarificationId)?.deliveredAt ?? null);
|
|
416
|
+
return clarDeliveredAt !== null;
|
|
417
|
+
}
|
|
418
|
+
async function backfillDeliveredAtIfMessageExists(db, payload, nowMs) {
|
|
419
|
+
if (!hasExistingTaskDeliveryMessage(db, payload))
|
|
420
|
+
return false;
|
|
421
|
+
markDeliveredAt(db, payload, nowMs);
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
function hasExistingTaskDeliveryMessage(db, payload) {
|
|
425
|
+
const params = [
|
|
426
|
+
notificationTypeFor(payload),
|
|
427
|
+
payload.taskKind,
|
|
428
|
+
payload.taskId,
|
|
429
|
+
payload.taskId,
|
|
430
|
+
];
|
|
431
|
+
const clarificationClause = payload.clarificationId
|
|
432
|
+
? "AND json_extract(m.metadata, '$.clarificationId') = ?"
|
|
433
|
+
: "";
|
|
434
|
+
if (payload.clarificationId) {
|
|
435
|
+
params.push(payload.clarificationId);
|
|
436
|
+
}
|
|
437
|
+
const row = db
|
|
438
|
+
.prepare(`SELECT 1 AS found
|
|
439
|
+
FROM messages m
|
|
440
|
+
WHERE m.role = 'assistant'
|
|
441
|
+
AND json_extract(m.metadata, '$.notificationType') = ?
|
|
442
|
+
AND json_extract(m.metadata, '$.taskKind') = ?
|
|
443
|
+
AND (
|
|
444
|
+
json_extract(m.metadata, '$.taskId') = ?
|
|
445
|
+
OR json_extract(m.metadata, '$.deliveredTaskId') = ?
|
|
446
|
+
)
|
|
447
|
+
${clarificationClause}
|
|
448
|
+
LIMIT 1`)
|
|
449
|
+
.get(...params);
|
|
450
|
+
return row !== undefined;
|
|
451
|
+
}
|
|
452
|
+
function markDeliveredAt(db, payload, nowMs) {
|
|
453
|
+
// `autonomous_forward` is fire-and-forget with no backing task row
|
|
454
|
+
// (its `taskId` is a synthetic UUID). There is nothing to stamp
|
|
455
|
+
// `delivered_at` on — writing to `browser_task` here would be an
|
|
456
|
+
// UPDATE against the wrong table that matches zero rows.
|
|
457
|
+
if (payload.taskKind === "autonomous_forward")
|
|
458
|
+
return;
|
|
459
|
+
if (payload.deliveryType === "task_result") {
|
|
460
|
+
if (payload.taskKind === "background_task") {
|
|
461
|
+
markBackgroundTaskDelivered(db, payload.taskId, nowMs);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
markBrowserTaskDelivered(db, payload.taskId, nowMs);
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (payload.clarificationId) {
|
|
469
|
+
if (payload.taskKind === "background_task") {
|
|
470
|
+
markBackgroundClarificationDelivered(db, payload.clarificationId, nowMs);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
markClarificationDelivered(db, payload.clarificationId, nowMs);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
export async function enqueueUndeliveredBrowserTaskDeliveries(params) {
|
|
478
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
479
|
+
const limit = params.limit ?? 20;
|
|
480
|
+
let enqueued = 0;
|
|
481
|
+
// Recovery is text-only for reports: the `finish`-tool screenshot keys
|
|
482
|
+
// are not persisted on the `browser_task` row, so a report re-delivered
|
|
483
|
+
// after a crash cannot re-attach its screenshots (clarifications can —
|
|
484
|
+
// their `screenshotKey` IS persisted, see below). Acceptable: recovery
|
|
485
|
+
// only fires on the rare write-then-crash-before-DM window, and the text
|
|
486
|
+
// report still reaches the owner. (Open clarifications keep their image.)
|
|
487
|
+
for (const row of listUndeliveredBrowserTaskReports(params.db, limit)) {
|
|
488
|
+
if (!row.report)
|
|
489
|
+
continue;
|
|
490
|
+
await params.eventBus.put(createBrowserTaskResultDeliveryEvent({
|
|
491
|
+
taskId: row.id,
|
|
492
|
+
originatingChannel: row.originatingChannel,
|
|
493
|
+
title: row.description,
|
|
494
|
+
report: row.report,
|
|
495
|
+
}));
|
|
496
|
+
enqueued += 1;
|
|
497
|
+
}
|
|
498
|
+
// The list query INNER JOINs `browser_task` (state='awaiting_user') and
|
|
499
|
+
// folds in the task's channel + description, so no second fetch — and no
|
|
500
|
+
// unreachable "task missing" guard — is needed.
|
|
501
|
+
for (const clarification of listUndeliveredClarifications(params.db, nowMs, limit)) {
|
|
502
|
+
await params.eventBus.put(createBrowserTaskClarificationDeliveryEvent({
|
|
503
|
+
taskId: clarification.taskId,
|
|
504
|
+
originatingChannel: clarification.taskOriginatingChannel,
|
|
505
|
+
title: clarification.taskDescription,
|
|
506
|
+
clarificationId: clarification.id,
|
|
507
|
+
question: clarification.question,
|
|
508
|
+
contextSummary: clarification.contextSummary,
|
|
509
|
+
screenshotKey: clarification.screenshotKey,
|
|
510
|
+
}));
|
|
511
|
+
enqueued += 1;
|
|
512
|
+
}
|
|
513
|
+
return enqueued;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 — delivery recovery sweep for
|
|
517
|
+
* background tasks. Re-enqueues `task.delivery` events for completed
|
|
518
|
+
* notify=true artifacts whose DM was never sent/recorded
|
|
519
|
+
* (`delivered_at IS NULL`) plus undelivered open clarifications. Run on
|
|
520
|
+
* the housekeeping tick (boot + periodic). Idempotent against the
|
|
521
|
+
* in-gate message-existence dedup — a re-enqueue after a successful send
|
|
522
|
+
* only back-fills `delivered_at`, never double-sends (§4.4).
|
|
523
|
+
*/
|
|
524
|
+
export async function enqueueUndeliveredBackgroundTaskDeliveries(params) {
|
|
525
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
526
|
+
const limit = params.limit ?? 20;
|
|
527
|
+
let enqueued = 0;
|
|
528
|
+
for (const row of listUndeliveredBackgroundTaskReports(params.db, limit)) {
|
|
529
|
+
if (!row.draft)
|
|
530
|
+
continue;
|
|
531
|
+
await params.eventBus.put(createBackgroundTaskResultDeliveryEvent({
|
|
532
|
+
taskId: row.id,
|
|
533
|
+
originatingChannel: row.originatingChannel,
|
|
534
|
+
title: row.title ?? row.brief.slice(0, 80),
|
|
535
|
+
draft: row.draft,
|
|
536
|
+
report: row.report ?? row.draft,
|
|
537
|
+
}));
|
|
538
|
+
enqueued += 1;
|
|
539
|
+
}
|
|
540
|
+
// As above, the clarification list JOINs `background_task` and folds in
|
|
541
|
+
// the task's channel + title/brief, so the sweep needs no re-fetch and no
|
|
542
|
+
// unreachable "task missing" guard.
|
|
543
|
+
for (const clarification of listUndeliveredBackgroundClarifications(params.db, nowMs, limit)) {
|
|
544
|
+
await params.eventBus.put(createBackgroundTaskClarificationDeliveryEvent({
|
|
545
|
+
taskId: clarification.taskId,
|
|
546
|
+
originatingChannel: clarification.taskOriginatingChannel,
|
|
547
|
+
title: clarification.taskTitle ?? clarification.taskBrief.slice(0, 80),
|
|
548
|
+
clarificationId: clarification.id,
|
|
549
|
+
question: clarification.question,
|
|
550
|
+
contextSummary: clarification.contextSummary,
|
|
551
|
+
}));
|
|
552
|
+
enqueued += 1;
|
|
553
|
+
}
|
|
554
|
+
return enqueued;
|
|
555
|
+
}
|