@aitne/daemon 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.js +8 -5
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/context/permissions.js +3 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +48 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +9 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +245 -7
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +26 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +25 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +39 -15
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +62 -20
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +1 -1
- package/dist/core/context-paths.js +1 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +49 -0
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +17 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +63 -1
- package/dist/core/dispatcher-scheduled-tasks.js +213 -6
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +284 -59
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.js +17 -5
- package/dist/core/feedback/eviction-scorer.js +6 -2
- package/dist/core/feedback/lesson-format.js +9 -4
- package/dist/core/feedback/lesson-injection.d.ts +1 -1
- package/dist/core/feedback/lesson-injection.js +17 -2
- package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
- package/dist/core/feedback/lesson-store-overview.js +8 -4
- package/dist/core/feedback/regeneralization-prep.js +29 -16
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +4 -4
- package/dist/core/injection-policy.js +4 -4
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +12 -0
- package/dist/core/signal-detector.js +53 -9
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +2 -2
- package/dist/core/today-direct-writer.js +1 -1
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/migrations.js +330 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +217 -16
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +75 -11
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +16 -6
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +38 -11
- package/dist/settings/runtime-settings.js +203 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -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
|
+
}
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* the daemon — adapter / service interfaces (`IDashboardStream`,
|
|
6
6
|
* `INotificationManager`, `ISessionManager`, `IMessageRecorder`,
|
|
7
7
|
* `IContextBuilder`, `IAuditLogger`), the `GetTaskFlow` callback, and the
|
|
8
|
-
* value types used at API boundaries (`
|
|
9
|
-
* `
|
|
8
|
+
* value types used at API boundaries (`TriggerActivityScanOptions`,
|
|
9
|
+
* `TriggerActivityScanResult`, `InFlightExecutionInfo`, `SetupMode`,
|
|
10
10
|
* `BangCommandDetail`, `ReplyActivityHandle`). It also hosts two pure
|
|
11
11
|
* helpers that have no dependence on dispatcher instance state:
|
|
12
12
|
* `buildLogErrorContext` (recovers backend/quota failure metadata from a
|
|
13
|
-
* thrown error) and `parseStage2Verdict` (extracts the Stage 2
|
|
13
|
+
* thrown error) and `parseStage2Verdict` (extracts the Stage 2 activity-scan
|
|
14
14
|
* triage JSON verdict from an LLM response).
|
|
15
15
|
*
|
|
16
16
|
* The dispatcher re-exports the public-surface members of this file so that
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* without modification — phase D-1 of `docs/design/appendices/file-split-plan.md`.
|
|
19
19
|
*/
|
|
20
20
|
import type { Event, MessageEvent, AgentResult, BackendId, IntegrationKey, IntegrationState } from "@aitne/shared";
|
|
21
|
+
import type { OutboundAttachmentRef } from "../adapters/types.js";
|
|
21
22
|
import type { SessionInfoPayload } from "../api/chat-binding-query.js";
|
|
22
23
|
import type { DeferredSessionEffects } from "./session-manager.js";
|
|
23
24
|
export interface ReplyActivityHandle {
|
|
@@ -114,6 +115,15 @@ export interface INotificationManager {
|
|
|
114
115
|
* WIKI_BUILDER_DESIGN.md §3.4-bis (completion notification path).
|
|
115
116
|
*/
|
|
116
117
|
replyTo?: MessageReplyTarget;
|
|
118
|
+
/**
|
|
119
|
+
* Outbound media attachments, delivered alongside `message` in a
|
|
120
|
+
* single adapter send. Only honoured on the direct `replyTo` path
|
|
121
|
+
* (the manager hands them to `messageHub.sendToPlatform`); ignored
|
|
122
|
+
* on the proactive/batch paths. Used by the background task-delivery
|
|
123
|
+
* idle branch to re-attach browser-task screenshots inline
|
|
124
|
+
* (BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1).
|
|
125
|
+
*/
|
|
126
|
+
attachments?: readonly OutboundAttachmentRef[];
|
|
117
127
|
}): Promise<void>;
|
|
118
128
|
beginReplyActivity(event: MessageEvent): Promise<ReplyActivityHandle>;
|
|
119
129
|
}
|
|
@@ -268,6 +278,21 @@ export interface IAuditLogger {
|
|
|
268
278
|
trigger: "reactive" | "autonomous";
|
|
269
279
|
backend?: BackendId;
|
|
270
280
|
costSource?: AgentResult["costSource"];
|
|
281
|
+
/**
|
|
282
|
+
* Terminal result override for the success path. Defaults to
|
|
283
|
+
* `"success"`. The only other legal value here is `"partial"` — a
|
|
284
|
+
* session that ended cleanly (no backend error) but failed a post-run
|
|
285
|
+
* outcome check, e.g. RESEARCH_CLUSTER_COST_FIX_PLAN F5's
|
|
286
|
+
* journal-write verification. Hard errors take the `logError` path
|
|
287
|
+
* (`result='failed'`); this override never expresses failure.
|
|
288
|
+
*/
|
|
289
|
+
result?: "success" | "partial";
|
|
290
|
+
/**
|
|
291
|
+
* Outcome-failure marker persisted to `agent_actions.error` when a run
|
|
292
|
+
* is downgraded to `result:"partial"` (F5: `'journal_write_missing'`).
|
|
293
|
+
* Paired with `result:"partial"`; ignored on a plain success.
|
|
294
|
+
*/
|
|
295
|
+
error?: string;
|
|
271
296
|
/**
|
|
272
297
|
* Whether the agent made at least one PUT/PATCH call to /api/context/*.
|
|
273
298
|
* Used for observer-event observability — see Phase 6 of
|
|
@@ -367,13 +392,27 @@ export interface IAuditLogger {
|
|
|
367
392
|
*/
|
|
368
393
|
dailyWrite?: DailyWriteAuditDetail | null;
|
|
369
394
|
}): void;
|
|
370
|
-
logSkip(event: Event, reason: string, trigger: "reactive" | "autonomous"
|
|
395
|
+
logSkip(event: Event, reason: string, trigger: "reactive" | "autonomous",
|
|
396
|
+
/** Optional structured context for the `detail` JSON column (N2/N3). */
|
|
397
|
+
detail?: Record<string, unknown>): void;
|
|
371
398
|
logError(event: Event, error: Error, trigger: "reactive" | "autonomous", context?: {
|
|
372
399
|
durationMs?: number;
|
|
373
400
|
backendId?: BackendId;
|
|
374
401
|
modelId?: string;
|
|
375
402
|
failureKind?: string;
|
|
376
403
|
failureCode?: string;
|
|
404
|
+
/**
|
|
405
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — recovered spend for a failed
|
|
406
|
+
* turn the provider already billed. Only real recovered figures;
|
|
407
|
+
* never a guess (see `AuditLogger.logError`).
|
|
408
|
+
*/
|
|
409
|
+
costUsd?: number;
|
|
410
|
+
costSource?: string;
|
|
411
|
+
tokensInput?: number;
|
|
412
|
+
tokensOutput?: number;
|
|
413
|
+
tokensCacheCreation?: number;
|
|
414
|
+
tokensCacheRead?: number;
|
|
415
|
+
numTurns?: number;
|
|
377
416
|
/**
|
|
378
417
|
* Pre-pass fan-out failure block. Mirrors the `prePass` payload on
|
|
379
418
|
* `logAction` so `MetricsCollector.collectPrePassMetrics` can see
|
|
@@ -474,7 +513,7 @@ export interface BangCommandDetail {
|
|
|
474
513
|
status: "ok" | "skipped" | "unknown" | "invalid_args" | "paused_decline" | "paused_blocked";
|
|
475
514
|
[extra: string]: unknown;
|
|
476
515
|
}
|
|
477
|
-
export type
|
|
516
|
+
export type TriggerActivityScanSkipReason = "morning_routine_active" | "activity_scan_in_progress" | "below_threshold" | "setup_incomplete" | "setup_in_progress" | "vault_degraded" | "user_paused" | "morning_routine_pending_for_today" | "gate_stage0_silent" | "gate_stage2_log_only";
|
|
478
517
|
export type SetupMode = "initial" | "update";
|
|
479
518
|
/**
|
|
480
519
|
* Unwrap the partial-run context the audit logger needs for a failed
|
|
@@ -489,16 +528,16 @@ export declare function buildLogErrorContext(err: unknown, durationMs: number):
|
|
|
489
528
|
failureKind?: string;
|
|
490
529
|
failureCode?: string;
|
|
491
530
|
};
|
|
492
|
-
export interface
|
|
531
|
+
export interface TriggerActivityScanOptions {
|
|
493
532
|
force?: boolean;
|
|
494
533
|
/** Optional model hint — injected as `requestedModel` on the enqueued
|
|
495
|
-
* routine.
|
|
534
|
+
* routine.activity_scan event so the user can force an Opus run from
|
|
496
535
|
* /api/agent/run-now without touching process_backend_config. */
|
|
497
536
|
requestedModel?: "sonnet" | "opus";
|
|
498
537
|
}
|
|
499
|
-
export interface
|
|
538
|
+
export interface TriggerActivityScanResult {
|
|
500
539
|
status: "queued" | "skipped";
|
|
501
|
-
reason?:
|
|
540
|
+
reason?: TriggerActivityScanSkipReason;
|
|
502
541
|
pendingCount?: number;
|
|
503
542
|
minObservations: number;
|
|
504
543
|
forced: boolean;
|