@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,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task worker tools — BACKGROUND_TASK_RUNNER_DESIGN.md §4.1 / §4.3.
|
|
3
|
+
*
|
|
4
|
+
* The generic worker's tool envelope. Unlike browser-task's 11-tool
|
|
5
|
+
* Playwright plane, the background worker gets exactly three tools:
|
|
6
|
+
*
|
|
7
|
+
* - `read_memory(key)` — READ-ONLY access to an allowlisted set of
|
|
8
|
+
* owner memory / profile files so the worker can personalize results
|
|
9
|
+
* itself rather than the brief enumerating everything (§9 / §10.4).
|
|
10
|
+
* The worker MUST NOT write shared memory — results come back as the
|
|
11
|
+
* artifact and the DM agent persists anything memory-worthy.
|
|
12
|
+
* - `ask_user(question, contextSummary)` — write a clarification
|
|
13
|
+
* artifact, park the task (`awaiting_user`), and end the turn. The
|
|
14
|
+
* runner surfaces it through the gated delivery boundary.
|
|
15
|
+
* - `finish(result, draft, notify, significance?)` — WRITE THE ARTIFACT
|
|
16
|
+
* (verbatim `result` + plain `draft` summary + the `notify`
|
|
17
|
+
* disposition the worker evaluated against the spawn-time policy) and
|
|
18
|
+
* complete the task. The runner's reconcile hook reads the artifact
|
|
19
|
+
* and decides delivery.
|
|
20
|
+
*
|
|
21
|
+
* The tools do NOT send DMs or enqueue delivery — they only write to the
|
|
22
|
+
* task store + transition state. Delivery is the runner's job (it owns
|
|
23
|
+
* the `notify` gate + the `task.delivery` enqueue), keeping the
|
|
24
|
+
* disposition decision in one place. This module is store-write glue,
|
|
25
|
+
* excluded from the coverage gate; the pure decision (`notify` evaluation)
|
|
26
|
+
* is the worker's, and the budget arithmetic is covered separately.
|
|
27
|
+
*/
|
|
28
|
+
import type Database from "better-sqlite3";
|
|
29
|
+
import { type McpSdkServerConfigWithInstance } from "@anthropic-ai/claude-agent-sdk";
|
|
30
|
+
import { z } from "zod";
|
|
31
|
+
import { type BackgroundTaskTransitionEmitter } from "./background-task-transition-events.js";
|
|
32
|
+
export declare const BACKGROUND_TASK_MCP_SERVER_NAME = "aitne-task";
|
|
33
|
+
export declare const BACKGROUND_TASK_TOOL_FQNS: readonly ["mcp__aitne-task__read_memory", "mcp__aitne-task__ask_user", "mcp__aitne-task__finish"];
|
|
34
|
+
export declare const MEMORY_KEYS: readonly string[];
|
|
35
|
+
export interface BackgroundTaskRuntime {
|
|
36
|
+
taskId: string;
|
|
37
|
+
db: Database.Database;
|
|
38
|
+
/** Vault root for `read_memory`. */
|
|
39
|
+
contextDir: string;
|
|
40
|
+
/** Clarification TTL in ms (from `backgroundTaskClarificationTtlMinutes`). */
|
|
41
|
+
clarificationTtlMs: number;
|
|
42
|
+
transitionEmitter: BackgroundTaskTransitionEmitter;
|
|
43
|
+
abortSignal: AbortSignal;
|
|
44
|
+
/** Set true once `ask_user` parks the task — read by the runner's
|
|
45
|
+
* post-execute hook to distinguish a clean park from a hang. */
|
|
46
|
+
yieldFlag: {
|
|
47
|
+
current: boolean;
|
|
48
|
+
};
|
|
49
|
+
/** Set true once `finish` writes the artifact — read by the runner to
|
|
50
|
+
* confirm a clean completion vs an SDK-side natural end. */
|
|
51
|
+
finishFlag: {
|
|
52
|
+
current: boolean;
|
|
53
|
+
};
|
|
54
|
+
nowFn?: () => number;
|
|
55
|
+
}
|
|
56
|
+
export declare function createBackgroundTaskRuntime(input: {
|
|
57
|
+
taskId: string;
|
|
58
|
+
db: Database.Database;
|
|
59
|
+
contextDir: string;
|
|
60
|
+
clarificationTtlMs: number;
|
|
61
|
+
abortSignal: AbortSignal;
|
|
62
|
+
transitionEmitter?: BackgroundTaskTransitionEmitter;
|
|
63
|
+
nowFn?: () => number;
|
|
64
|
+
}): BackgroundTaskRuntime;
|
|
65
|
+
/** The three worker tools, bound to a runtime. Exported so tests can
|
|
66
|
+
* invoke a tool's `handler` directly without standing up the MCP
|
|
67
|
+
* transport. */
|
|
68
|
+
export declare function createBackgroundTaskTools(runtime: BackgroundTaskRuntime): (import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
|
|
69
|
+
key: z.ZodEnum<{
|
|
70
|
+
[x: string]: string;
|
|
71
|
+
}>;
|
|
72
|
+
}> | import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
|
|
73
|
+
question: z.ZodString;
|
|
74
|
+
contextSummary: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}> | import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
|
|
76
|
+
result: z.ZodString;
|
|
77
|
+
draft: z.ZodString;
|
|
78
|
+
notify: z.ZodBoolean;
|
|
79
|
+
significance: z.ZodOptional<z.ZodString>;
|
|
80
|
+
}>)[];
|
|
81
|
+
/** Construct the per-task MCP server. Returned config is passed verbatim
|
|
82
|
+
* into `query({ options: { mcpServers: { [BACKGROUND_TASK_MCP_SERVER_NAME]:
|
|
83
|
+
* <return value> } } })`. */
|
|
84
|
+
export declare function createBackgroundTaskMcpServer(runtime: BackgroundTaskRuntime): McpSdkServerConfigWithInstance;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task worker tools — BACKGROUND_TASK_RUNNER_DESIGN.md §4.1 / §4.3.
|
|
3
|
+
*
|
|
4
|
+
* The generic worker's tool envelope. Unlike browser-task's 11-tool
|
|
5
|
+
* Playwright plane, the background worker gets exactly three tools:
|
|
6
|
+
*
|
|
7
|
+
* - `read_memory(key)` — READ-ONLY access to an allowlisted set of
|
|
8
|
+
* owner memory / profile files so the worker can personalize results
|
|
9
|
+
* itself rather than the brief enumerating everything (§9 / §10.4).
|
|
10
|
+
* The worker MUST NOT write shared memory — results come back as the
|
|
11
|
+
* artifact and the DM agent persists anything memory-worthy.
|
|
12
|
+
* - `ask_user(question, contextSummary)` — write a clarification
|
|
13
|
+
* artifact, park the task (`awaiting_user`), and end the turn. The
|
|
14
|
+
* runner surfaces it through the gated delivery boundary.
|
|
15
|
+
* - `finish(result, draft, notify, significance?)` — WRITE THE ARTIFACT
|
|
16
|
+
* (verbatim `result` + plain `draft` summary + the `notify`
|
|
17
|
+
* disposition the worker evaluated against the spawn-time policy) and
|
|
18
|
+
* complete the task. The runner's reconcile hook reads the artifact
|
|
19
|
+
* and decides delivery.
|
|
20
|
+
*
|
|
21
|
+
* The tools do NOT send DMs or enqueue delivery — they only write to the
|
|
22
|
+
* task store + transition state. Delivery is the runner's job (it owns
|
|
23
|
+
* the `notify` gate + the `task.delivery` enqueue), keeping the
|
|
24
|
+
* disposition decision in one place. This module is store-write glue,
|
|
25
|
+
* excluded from the coverage gate; the pure decision (`notify` evaluation)
|
|
26
|
+
* is the worker's, and the budget arithmetic is covered separately.
|
|
27
|
+
*/
|
|
28
|
+
import { readFile } from "node:fs/promises";
|
|
29
|
+
import { randomUUID } from "node:crypto";
|
|
30
|
+
import { createSdkMcpServer, tool, } from "@anthropic-ai/claude-agent-sdk";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { createClarification } from "../../db/background-task-clarifications-store.js";
|
|
33
|
+
import { markAwaitingUser, markTerminal, } from "../../db/background-task-store.js";
|
|
34
|
+
import { CONTEXT_RELATIVE_PATHS, fullPath } from "../../core/context-paths.js";
|
|
35
|
+
import { noopBackgroundTaskTransitionEmitter, } from "./background-task-transition-events.js";
|
|
36
|
+
import { createLogger } from "../../logging.js";
|
|
37
|
+
const logger = createLogger("background-task-tools");
|
|
38
|
+
export const BACKGROUND_TASK_MCP_SERVER_NAME = "aitne-task";
|
|
39
|
+
export const BACKGROUND_TASK_TOOL_FQNS = [
|
|
40
|
+
`mcp__${BACKGROUND_TASK_MCP_SERVER_NAME}__read_memory`,
|
|
41
|
+
`mcp__${BACKGROUND_TASK_MCP_SERVER_NAME}__ask_user`,
|
|
42
|
+
`mcp__${BACKGROUND_TASK_MCP_SERVER_NAME}__finish`,
|
|
43
|
+
];
|
|
44
|
+
/** Per-read output cap so a large memory file can't blow the worker's
|
|
45
|
+
* context window or budget in one tool call. */
|
|
46
|
+
const MEMORY_READ_CHAR_CAP = 8_000;
|
|
47
|
+
/**
|
|
48
|
+
* Allowlist of READ-ONLY memory keys → vault-relative paths. A fixed
|
|
49
|
+
* enum (no user-controlled path component) so there is no traversal
|
|
50
|
+
* surface. Single files only; the worker reads what it needs to
|
|
51
|
+
* personalize a result (the owner's profile, today's state, project
|
|
52
|
+
* context, the management policy).
|
|
53
|
+
*/
|
|
54
|
+
const MEMORY_FILE_ALLOWLIST = {
|
|
55
|
+
today: CONTEXT_RELATIVE_PATHS.today,
|
|
56
|
+
profile: CONTEXT_RELATIVE_PATHS.user.profile,
|
|
57
|
+
people: CONTEXT_RELATIVE_PATHS.user.people,
|
|
58
|
+
work: CONTEXT_RELATIVE_PATHS.user.work,
|
|
59
|
+
goals: CONTEXT_RELATIVE_PATHS.user.goals,
|
|
60
|
+
projects: CONTEXT_RELATIVE_PATHS.projects.index,
|
|
61
|
+
management: CONTEXT_RELATIVE_PATHS.rules.management,
|
|
62
|
+
integrations: CONTEXT_RELATIVE_PATHS.integrations,
|
|
63
|
+
};
|
|
64
|
+
export const MEMORY_KEYS = Object.keys(MEMORY_FILE_ALLOWLIST);
|
|
65
|
+
// The SDK `tool()` helper takes a Zod RAW SHAPE (a `{ key: ZodType }`
|
|
66
|
+
// object), not a `z.object(...)` — mirroring browser-task's schemas.
|
|
67
|
+
const readMemoryArgsSchema = {
|
|
68
|
+
key: z
|
|
69
|
+
.enum(Object.keys(MEMORY_FILE_ALLOWLIST))
|
|
70
|
+
.describe("Which owner memory file to read. One of: "
|
|
71
|
+
+ MEMORY_KEYS.join(", ")
|
|
72
|
+
+ ". Read-only."),
|
|
73
|
+
};
|
|
74
|
+
const askUserArgsSchema = {
|
|
75
|
+
question: z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1)
|
|
78
|
+
.max(2_000)
|
|
79
|
+
.describe("The clarification you need from the owner, phrased plainly. The DM agent weaves this into the conversation."),
|
|
80
|
+
contextSummary: z
|
|
81
|
+
.string()
|
|
82
|
+
.max(2_000)
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Optional one-paragraph recap of where the task is and why you're stuck, so the owner can answer without re-reading the whole brief."),
|
|
85
|
+
};
|
|
86
|
+
const finishArgsSchema = {
|
|
87
|
+
result: z
|
|
88
|
+
.string()
|
|
89
|
+
.min(1)
|
|
90
|
+
.max(100_000)
|
|
91
|
+
.describe("The FULL, verbatim outcome — every finding, number, URL, and id. Persisted unchanged as the fidelity anchor; precise follow-ups read this. Do NOT summarize here."),
|
|
92
|
+
draft: z
|
|
93
|
+
.string()
|
|
94
|
+
.min(1)
|
|
95
|
+
.max(4_000)
|
|
96
|
+
.describe("A plain, human-readable summary in the owner's language. NOT the final DM — the DM agent uses this as grounding / the idle-send body. 1-4 short paragraphs."),
|
|
97
|
+
notify: z
|
|
98
|
+
.boolean()
|
|
99
|
+
.describe("Your disposition vs the spawn-time notification policy. always ⇒ true (even for a '0 issues' result — the owner asked). if_significant ⇒ true ONLY if the brief's concrete criteria are met. silent ⇒ false. When unsure on always, prefer true."),
|
|
100
|
+
significance: z
|
|
101
|
+
.string()
|
|
102
|
+
.max(500)
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("One line on why notify is true/false (e.g. '2 repos red' / 'no criteria met'). Used in the filed-results digest + audit."),
|
|
105
|
+
};
|
|
106
|
+
export function createBackgroundTaskRuntime(input) {
|
|
107
|
+
return {
|
|
108
|
+
taskId: input.taskId,
|
|
109
|
+
db: input.db,
|
|
110
|
+
contextDir: input.contextDir,
|
|
111
|
+
clarificationTtlMs: input.clarificationTtlMs,
|
|
112
|
+
abortSignal: input.abortSignal,
|
|
113
|
+
transitionEmitter: input.transitionEmitter ?? noopBackgroundTaskTransitionEmitter,
|
|
114
|
+
yieldFlag: { current: false },
|
|
115
|
+
finishFlag: { current: false },
|
|
116
|
+
nowFn: input.nowFn,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function textResult(payload, isError = false) {
|
|
120
|
+
return {
|
|
121
|
+
isError,
|
|
122
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function makeReadMemoryTool(runtime) {
|
|
126
|
+
return tool("read_memory", "Read one owner memory / profile file (read-only) to personalize your result. Keys: "
|
|
127
|
+
+ MEMORY_KEYS.join(", ")
|
|
128
|
+
+ ". You cannot write memory — return everything memory-worthy in finish().", readMemoryArgsSchema, async (args) => {
|
|
129
|
+
const relative = MEMORY_FILE_ALLOWLIST[args.key];
|
|
130
|
+
if (!relative) {
|
|
131
|
+
return textResult({ ok: false, error: "unknown_key", detail: `key must be one of: ${MEMORY_KEYS.join(", ")}` }, true);
|
|
132
|
+
}
|
|
133
|
+
const path = fullPath(runtime.contextDir, relative);
|
|
134
|
+
try {
|
|
135
|
+
const raw = await readFile(path, "utf-8");
|
|
136
|
+
const truncated = raw.length > MEMORY_READ_CHAR_CAP;
|
|
137
|
+
const content = truncated ? raw.slice(0, MEMORY_READ_CHAR_CAP) : raw;
|
|
138
|
+
return textResult({
|
|
139
|
+
ok: true,
|
|
140
|
+
key: args.key,
|
|
141
|
+
truncated,
|
|
142
|
+
content: truncated
|
|
143
|
+
? `${content}\n\n[... truncated at ${MEMORY_READ_CHAR_CAP} chars]`
|
|
144
|
+
: content,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Missing file is normal (a fresh vault may not have every file).
|
|
149
|
+
return textResult({
|
|
150
|
+
ok: true,
|
|
151
|
+
key: args.key,
|
|
152
|
+
truncated: false,
|
|
153
|
+
content: "",
|
|
154
|
+
note: "file not present in the vault yet",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function makeAskUserTool(runtime) {
|
|
160
|
+
return tool("ask_user", "Pause for an owner clarification. Writes the question, parks your task, and ends the turn — the DM agent surfaces it and relays the answer back so you resume. Call this and then STOP; do not call further tools this turn.", askUserArgsSchema, async (args) => {
|
|
161
|
+
const now = (runtime.nowFn ?? (() => Date.now()))();
|
|
162
|
+
// running → awaiting_user CAS before writing the clarification row.
|
|
163
|
+
// On a CAS miss the task already transitioned (cancel-while-running)
|
|
164
|
+
// — bail without committing an orphan row the deadline tick would
|
|
165
|
+
// later process for a terminal task.
|
|
166
|
+
const parked = markAwaitingUser(runtime.db, runtime.taskId);
|
|
167
|
+
if (!parked) {
|
|
168
|
+
return textResult({
|
|
169
|
+
ok: false,
|
|
170
|
+
error: "task_not_running",
|
|
171
|
+
detail: "This task is no longer running (it may have been cancelled). Stop now.",
|
|
172
|
+
}, true);
|
|
173
|
+
}
|
|
174
|
+
const id = randomUUID();
|
|
175
|
+
const row = createClarification(runtime.db, {
|
|
176
|
+
id,
|
|
177
|
+
taskId: runtime.taskId,
|
|
178
|
+
question: args.question,
|
|
179
|
+
contextSummary: args.contextSummary ?? null,
|
|
180
|
+
askedAt: now,
|
|
181
|
+
ttlMs: runtime.clarificationTtlMs,
|
|
182
|
+
});
|
|
183
|
+
runtime.yieldFlag.current = true;
|
|
184
|
+
runtime.transitionEmitter.emitFromRow(parked, now);
|
|
185
|
+
return textResult({
|
|
186
|
+
ok: true,
|
|
187
|
+
status: "parked",
|
|
188
|
+
clarificationId: id,
|
|
189
|
+
deadlineAt: row.deadlineAt,
|
|
190
|
+
note: "Your task is parked. STOP now — the owner's answer will resume you.",
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function makeFinishTool(runtime) {
|
|
195
|
+
return tool("finish", "Done. Writes your artifact (verbatim result + plain draft summary + the notify disposition) and completes the task. Do not call any tool after finish — your session ends here.", finishArgsSchema, async (args) => {
|
|
196
|
+
const now = (runtime.nowFn ?? (() => Date.now()))();
|
|
197
|
+
const terminal = markTerminal(runtime.db, {
|
|
198
|
+
id: runtime.taskId,
|
|
199
|
+
state: "completed",
|
|
200
|
+
outcomeDetail: null,
|
|
201
|
+
finishedAt: now,
|
|
202
|
+
report: args.result,
|
|
203
|
+
draft: args.draft,
|
|
204
|
+
notify: args.notify,
|
|
205
|
+
significance: args.significance ?? null,
|
|
206
|
+
});
|
|
207
|
+
if (!terminal) {
|
|
208
|
+
// CAS miss — the task was already cancelled / timed out. Surface
|
|
209
|
+
// it so the agent stops; the artifact is intentionally not forced
|
|
210
|
+
// onto a terminal row.
|
|
211
|
+
return textResult({
|
|
212
|
+
ok: false,
|
|
213
|
+
error: "task_not_active",
|
|
214
|
+
detail: "This task already reached a terminal state; the result was not stored. Stop now.",
|
|
215
|
+
}, true);
|
|
216
|
+
}
|
|
217
|
+
runtime.finishFlag.current = true;
|
|
218
|
+
runtime.transitionEmitter.emitFromRow(terminal, now);
|
|
219
|
+
return textResult({
|
|
220
|
+
ok: true,
|
|
221
|
+
completed: true,
|
|
222
|
+
notify: args.notify,
|
|
223
|
+
state: terminal.state,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/** The three worker tools, bound to a runtime. Exported so tests can
|
|
228
|
+
* invoke a tool's `handler` directly without standing up the MCP
|
|
229
|
+
* transport. */
|
|
230
|
+
export function createBackgroundTaskTools(runtime) {
|
|
231
|
+
return [
|
|
232
|
+
makeReadMemoryTool(runtime),
|
|
233
|
+
makeAskUserTool(runtime),
|
|
234
|
+
makeFinishTool(runtime),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
/** Construct the per-task MCP server. Returned config is passed verbatim
|
|
238
|
+
* into `query({ options: { mcpServers: { [BACKGROUND_TASK_MCP_SERVER_NAME]:
|
|
239
|
+
* <return value> } } })`. */
|
|
240
|
+
export function createBackgroundTaskMcpServer(runtime) {
|
|
241
|
+
return createSdkMcpServer({
|
|
242
|
+
name: BACKGROUND_TASK_MCP_SERVER_NAME,
|
|
243
|
+
version: "1.0.0",
|
|
244
|
+
tools: createBackgroundTaskTools(runtime),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
void logger;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task SSE transition emitter — BACKGROUND_TASK_RUNNER_DESIGN.md
|
|
3
|
+
* §15 (dashboard). Emits a `background_task` named event on the global
|
|
4
|
+
* `/api/events/stream` on every state transition so a future dashboard
|
|
5
|
+
* surface can invalidate its list/detail queries without per-id polling.
|
|
6
|
+
*
|
|
7
|
+
* Per `project_dashboard_testing`: `background_task` arrives on the
|
|
8
|
+
* default `event` stream with a `kind` field — here the named-event
|
|
9
|
+
* channel is `"background_task"`, payload bounded and ASCII-safe.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors `browser-task-transition-events.ts`: a thin telemetry interface
|
|
12
|
+
* separate from the delivery path (which fans user-facing DMs). The
|
|
13
|
+
* payload never carries the full report — only an 80-char title/brief.
|
|
14
|
+
*/
|
|
15
|
+
import type { BackgroundTaskRow } from "../../db/background-task-store.js";
|
|
16
|
+
export interface BackgroundTaskTransitionPayload {
|
|
17
|
+
taskId: string;
|
|
18
|
+
state: BackgroundTaskRow["state"];
|
|
19
|
+
/** Epoch ms — finishedAt for terminal rows, startedAt for running,
|
|
20
|
+
* createdAt for pending. Cache-busting timestamp only. */
|
|
21
|
+
transitionedAt: number;
|
|
22
|
+
/** First 80 chars of the title (or brief), control chars scrubbed. */
|
|
23
|
+
brief: string;
|
|
24
|
+
outcomeDetail: string | null;
|
|
25
|
+
/** Worker disposition once finished (null while in-flight). */
|
|
26
|
+
notify: boolean | null;
|
|
27
|
+
originatingChannel: string | null;
|
|
28
|
+
}
|
|
29
|
+
/** Minimal broadcaster surface — the impl lives in `api/routes/sse.ts`.
|
|
30
|
+
* Declared structurally so the daemon's `services/background-task/*`
|
|
31
|
+
* modules don't depend on the HTTP layer. */
|
|
32
|
+
export interface BroadcastSink {
|
|
33
|
+
broadcastNamedEvent(event: string, data: unknown): Promise<void> | void;
|
|
34
|
+
}
|
|
35
|
+
export interface BackgroundTaskTransitionEmitter {
|
|
36
|
+
emit(payload: BackgroundTaskTransitionPayload): void;
|
|
37
|
+
/** Extract fields from a row + transitionedAt and emit. Returns the
|
|
38
|
+
* payload (or null when row is null) for test chaining. */
|
|
39
|
+
emitFromRow(row: BackgroundTaskRow | null, transitionedAt: number): BackgroundTaskTransitionPayload | null;
|
|
40
|
+
}
|
|
41
|
+
export declare function briefPayload(row: BackgroundTaskRow, transitionedAt: number): BackgroundTaskTransitionPayload;
|
|
42
|
+
export declare const noopBackgroundTaskTransitionEmitter: BackgroundTaskTransitionEmitter;
|
|
43
|
+
export declare function createBackgroundTaskTransitionEmitter(sink: BroadcastSink | null | undefined): BackgroundTaskTransitionEmitter;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-task SSE transition emitter — BACKGROUND_TASK_RUNNER_DESIGN.md
|
|
3
|
+
* §15 (dashboard). Emits a `background_task` named event on the global
|
|
4
|
+
* `/api/events/stream` on every state transition so a future dashboard
|
|
5
|
+
* surface can invalidate its list/detail queries without per-id polling.
|
|
6
|
+
*
|
|
7
|
+
* Per `project_dashboard_testing`: `background_task` arrives on the
|
|
8
|
+
* default `event` stream with a `kind` field — here the named-event
|
|
9
|
+
* channel is `"background_task"`, payload bounded and ASCII-safe.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors `browser-task-transition-events.ts`: a thin telemetry interface
|
|
12
|
+
* separate from the delivery path (which fans user-facing DMs). The
|
|
13
|
+
* payload never carries the full report — only an 80-char title/brief.
|
|
14
|
+
*/
|
|
15
|
+
const CONTROL_CHAR_REGEX = new RegExp("[\\x00-\\x1f\\x7f]", "g");
|
|
16
|
+
export function briefPayload(row, transitionedAt) {
|
|
17
|
+
const source = row.title && row.title.length > 0 ? row.title : row.brief;
|
|
18
|
+
const brief = source.replace(CONTROL_CHAR_REGEX, " ").slice(0, 80);
|
|
19
|
+
return {
|
|
20
|
+
taskId: row.id,
|
|
21
|
+
state: row.state,
|
|
22
|
+
transitionedAt,
|
|
23
|
+
brief,
|
|
24
|
+
outcomeDetail: row.outcomeDetail,
|
|
25
|
+
notify: row.notify,
|
|
26
|
+
originatingChannel: row.originatingChannel,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const noopBackgroundTaskTransitionEmitter = {
|
|
30
|
+
emit() {
|
|
31
|
+
/* no-op */
|
|
32
|
+
},
|
|
33
|
+
emitFromRow(row, transitionedAt) {
|
|
34
|
+
if (!row)
|
|
35
|
+
return null;
|
|
36
|
+
return briefPayload(row, transitionedAt);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export function createBackgroundTaskTransitionEmitter(sink) {
|
|
40
|
+
if (!sink)
|
|
41
|
+
return noopBackgroundTaskTransitionEmitter;
|
|
42
|
+
return {
|
|
43
|
+
emit(payload) {
|
|
44
|
+
void sink.broadcastNamedEvent("background_task", payload);
|
|
45
|
+
},
|
|
46
|
+
emitFromRow(row, transitionedAt) {
|
|
47
|
+
if (!row)
|
|
48
|
+
return null;
|
|
49
|
+
const payload = briefPayload(row, transitionedAt);
|
|
50
|
+
void sink.broadcastNamedEvent("background_task", payload);
|
|
51
|
+
return payload;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -161,7 +161,7 @@ export declare function shouldDenyEgress(url: string, opts?: {
|
|
|
161
161
|
hostnameDenylist?: ReadonlyArray<RegExp>;
|
|
162
162
|
}): Promise<{
|
|
163
163
|
denied: true;
|
|
164
|
-
reason: "hostname" | "cidr" | "invalid_url";
|
|
164
|
+
reason: "hostname" | "cidr" | "invalid_url" | "resolve_error";
|
|
165
165
|
} | {
|
|
166
166
|
denied: false;
|
|
167
167
|
}>;
|
|
@@ -437,16 +437,26 @@ export async function shouldDenyEgress(url, opts) {
|
|
|
437
437
|
return { denied: false };
|
|
438
438
|
}
|
|
439
439
|
if (opts?.resolveIps) {
|
|
440
|
-
let resolved
|
|
440
|
+
let resolved;
|
|
441
441
|
try {
|
|
442
442
|
resolved = await opts.resolveIps(hostname);
|
|
443
443
|
}
|
|
444
444
|
catch {
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
|
|
445
|
+
// Fail CLOSED. This CIDR gate is the primary defence against egress to
|
|
446
|
+
// private / link-local / cloud-metadata IPs (the browser sandbox shares
|
|
447
|
+
// the host network namespace, so there is no packet-layer block behind
|
|
448
|
+
// it). The guard and Chromium resolve the hostname independently — a
|
|
449
|
+
// name that Node's resolver fails on but Chromium's resolver succeeds on
|
|
450
|
+
// (SERVFAIL / timeout / search-domain / IDN differences) would otherwise
|
|
451
|
+
// reach an internal address completely unchecked. Blocking on resolve
|
|
452
|
+
// failure closes that differential-resolver gap; a host that is
|
|
453
|
+
// genuinely unresolvable would fail at the network layer regardless.
|
|
454
|
+
return { denied: true, reason: "resolve_error" };
|
|
455
|
+
}
|
|
456
|
+
if (resolved.length === 0) {
|
|
457
|
+
// No addresses to vet is as unverifiable as a thrown lookup — don't let
|
|
458
|
+
// the host through to Chromium's independent resolution.
|
|
459
|
+
return { denied: true, reason: "resolve_error" };
|
|
450
460
|
}
|
|
451
461
|
for (const ip of resolved) {
|
|
452
462
|
if (matchesCidrDenylist(ip)) {
|
|
@@ -233,7 +233,6 @@ function loadWindowsHelper() {
|
|
|
233
233
|
// a module-scope global — build one via `createRequire` to reach the
|
|
234
234
|
// CommonJS loader. This branch only runs on win32.
|
|
235
235
|
const req = createRequire(import.meta.url);
|
|
236
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
237
236
|
const mod = req("../../../../native/win-appcontainer/loader.js");
|
|
238
237
|
return mod.loadHelper();
|
|
239
238
|
}
|
|
@@ -34,7 +34,7 @@ import { getBrowserTask, markRunning, markRunningFromParked, markTerminal, } fro
|
|
|
34
34
|
import { resolveClarification } from "../../db/browser-task-clarifications-store.js";
|
|
35
35
|
import { createLogger } from "../../logging.js";
|
|
36
36
|
import { prepareDriverHandle, releaseDriverHandle, resumeDriver, runDriver, } from "./browser-task-driver.js";
|
|
37
|
-
import { createInitialSlotState, decideAcquire, decidePark, decideRelease, decideUnpark, } from "./browser-task-slots.js";
|
|
37
|
+
import { createInitialSlotState, decideAcquire, decideCancel, decidePark, decideRelease, decideUnpark, } from "./browser-task-slots.js";
|
|
38
38
|
import { noopBrowserTaskTransitionEmitter, } from "./browser-task-transition-events.js";
|
|
39
39
|
const logger = createLogger("browser-task-runner");
|
|
40
40
|
/**
|
|
@@ -52,6 +52,16 @@ const logger = createLogger("browser-task-runner");
|
|
|
52
52
|
function slotSiteKeyForRow(row) {
|
|
53
53
|
return row.siteKey ?? `__open__:${row.id}`;
|
|
54
54
|
}
|
|
55
|
+
/** True when the slot manager already tracks `taskId` as an active
|
|
56
|
+
* occupant (acquired a slot), even if the DB row still reads `pending`
|
|
57
|
+
* in the narrow acquire→markRunning window. Mirrors the route helper. */
|
|
58
|
+
function slotManagerHasActive(state, taskId) {
|
|
59
|
+
for (const entry of state.active.values()) {
|
|
60
|
+
if (entry.taskId === taskId)
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
55
65
|
export function createSlotStateRef(maxConcurrent) {
|
|
56
66
|
return { state: createInitialSlotState(maxConcurrent) };
|
|
57
67
|
}
|
|
@@ -494,15 +504,50 @@ export function createBrowserTaskRunner(deps) {
|
|
|
494
504
|
// landing in that window is silently dropped and the SDK runs
|
|
495
505
|
// anyway, burning turns / cost.
|
|
496
506
|
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
// parked states (`awaiting_user`/`final_confirm`) always have a
|
|
500
|
-
// tracked handle, so they take the `handle` branch above. Recording
|
|
501
|
-
// a pending-abort for a `pending` row would leak the map entry —
|
|
502
|
-
// nothing ever consumes it (`runDriverFromPending` only drains it
|
|
503
|
-
// after `prepareDriverHandle`, which a pending row never reaches).
|
|
507
|
+
// The parked states (`awaiting_user`/`final_confirm`) always have a
|
|
508
|
+
// tracked handle, so they take the `handle` branch above.
|
|
504
509
|
pendingAborts.set(taskId, reason || "cancel");
|
|
505
510
|
}
|
|
511
|
+
else if (row.state === "pending") {
|
|
512
|
+
// Queued behind the concurrency cap (or in the narrow acquire→
|
|
513
|
+
// markRunning window). The HTTP `/cancel` route handles `pending`
|
|
514
|
+
// itself, but `!stop <id>` (Phase 4) calls `cancel()` directly — so
|
|
515
|
+
// the runner must own this state too, or the bang path reports a
|
|
516
|
+
// false "Stopping…" while the task keeps running.
|
|
517
|
+
if (slotManagerHasActive(deps.slotStateRef.state, taskId)) {
|
|
518
|
+
// `tryAcquire` already promoted the task (slot active) but
|
|
519
|
+
// `markRunning` hasn't flipped the DB row yet — `decideCancel`
|
|
520
|
+
// would throw on an active occupant. Record the abort intent like
|
|
521
|
+
// the running case; the handle registered at `runDriverFromPending`
|
|
522
|
+
// fires it before the SDK loop begins (and drains the map entry,
|
|
523
|
+
// so it does not leak).
|
|
524
|
+
pendingAborts.set(taskId, reason || "cancel");
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
// Genuinely queued — drop the FIFO entry and write the terminal
|
|
528
|
+
// directly (no slot was held, so no release cascade). Mirrors the
|
|
529
|
+
// route's `isPending` path and the background runner.
|
|
530
|
+
try {
|
|
531
|
+
deps.slotStateRef.state = decideCancel(deps.slotStateRef.state, taskId).state;
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
/* c8 ignore start -- defensive: slot promoted between the check and here */
|
|
535
|
+
logger.warn({ err, taskId }, "decideCancel on pending row failed (continuing)");
|
|
536
|
+
/* c8 ignore stop */
|
|
537
|
+
}
|
|
538
|
+
const finishedAt = now();
|
|
539
|
+
const terminal = markTerminal(deps.db, {
|
|
540
|
+
id: taskId,
|
|
541
|
+
state: "cancelled",
|
|
542
|
+
outcomeDetail: `cancelled_in_queue:${reason}`,
|
|
543
|
+
report: null,
|
|
544
|
+
finishedAt,
|
|
545
|
+
});
|
|
546
|
+
emitter.emitFromRow(terminal, finishedAt);
|
|
547
|
+
logger.info({ taskId, reason }, "browser-task cancel (pending → cancelled)");
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
506
551
|
if (handle) {
|
|
507
552
|
// Cancel any pending lite-final-confirm tokens issued by this
|
|
508
553
|
// task — otherwise a gate that was mid-flight leaves a token
|
|
@@ -58,7 +58,7 @@ export interface ProcessBatchResult {
|
|
|
58
58
|
export declare function inferIntegrationKeyFromSource(source: string): IntegrationKey | null;
|
|
59
59
|
/**
|
|
60
60
|
* Normalize pre-pass mail payloads at the ingest chokepoint so the
|
|
61
|
-
*
|
|
61
|
+
* activity-scan gate can read a canonical shape regardless of mode. See
|
|
62
62
|
* HOURLY_CHECK_GATE_REDESIGN_PLAN.md for the full rationale; the rules:
|
|
63
63
|
* - mail integration source AND `payload.raw.from` is a string → set
|
|
64
64
|
* `is_read = 0` and `from_email = <lowercased extracted address>`
|
|
@@ -50,7 +50,7 @@ export function inferIntegrationKeyFromSource(source) {
|
|
|
50
50
|
}
|
|
51
51
|
/** Per-integration source prefixes whose pre-pass payload carries the
|
|
52
52
|
* mail shape (`payload.raw.from`, no `is_read`). Drives
|
|
53
|
-
* {@link normalizeMailObservationPayload} so the
|
|
53
|
+
* {@link normalizeMailObservationPayload} so the activity-scan gate can
|
|
54
54
|
* read a canonical `is_read` + `from_email` shape regardless of mode. */
|
|
55
55
|
const MAIL_OBSERVATION_INTEGRATION_KEYS = new Set(INTEGRATION_KEYS.filter((k) => {
|
|
56
56
|
const partial = INTEGRATION_DESCRIPTORS[k].prePassPartial;
|
|
@@ -65,7 +65,7 @@ function extractEmailAddress(raw) {
|
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* Normalize pre-pass mail payloads at the ingest chokepoint so the
|
|
68
|
-
*
|
|
68
|
+
* activity-scan gate can read a canonical shape regardless of mode. See
|
|
69
69
|
* HOURLY_CHECK_GATE_REDESIGN_PLAN.md for the full rationale; the rules:
|
|
70
70
|
* - mail integration source AND `payload.raw.from` is a string → set
|
|
71
71
|
* `is_read = 0` and `from_email = <lowercased extracted address>`
|