@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,50 @@
|
|
|
1
|
+
import { formatSqliteDatetime, getAgentDayBoundsUtc } from "@aitne/shared";
|
|
2
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
3
|
+
/**
|
|
4
|
+
* Count semantics mirror the pre-extraction `NotificationManager`
|
|
5
|
+
* implementation byte-for-byte: a multi-channel dispatch counts once
|
|
6
|
+
* (DISTINCT on dispatch_id, falling back to the row id for legacy rows
|
|
7
|
+
* with an empty dispatch_id), only `delivered` rows count, and
|
|
8
|
+
* `message.received` reply forwards never count against proactive budget.
|
|
9
|
+
*/
|
|
10
|
+
export function evaluateNotificationRateLimit(db, opts, now = new Date()) {
|
|
11
|
+
const hourFloor = formatSqliteDatetime(new Date(now.getTime() - HOUR_MS));
|
|
12
|
+
const hourly = db
|
|
13
|
+
.prepare(`SELECT COUNT(DISTINCT CASE
|
|
14
|
+
WHEN dispatch_id != '' THEN dispatch_id
|
|
15
|
+
ELSE CAST(id AS TEXT)
|
|
16
|
+
END) as cnt,
|
|
17
|
+
MIN(created_at) as oldest
|
|
18
|
+
FROM notification_log
|
|
19
|
+
WHERE status = 'delivered'
|
|
20
|
+
AND COALESCE(notification_type, '') != 'message.received'
|
|
21
|
+
AND created_at > ?`)
|
|
22
|
+
.get(hourFloor);
|
|
23
|
+
if (hourly.cnt >= opts.maxNotificationsPerHour) {
|
|
24
|
+
// The window opens when the oldest in-window delivery ages past 1 h.
|
|
25
|
+
// `oldest` is non-null whenever cnt > 0; the cnt===0 ∧ limit<=0 corner
|
|
26
|
+
// (a zero/negative configured cap) degrades to "retry at now + 1 h".
|
|
27
|
+
const oldestMs = hourly.oldest
|
|
28
|
+
? new Date(`${hourly.oldest.replace(" ", "T")}Z`).getTime()
|
|
29
|
+
: now.getTime();
|
|
30
|
+
return {
|
|
31
|
+
limited: true,
|
|
32
|
+
retryAfter: formatSqliteDatetime(new Date(oldestMs + HOUR_MS)),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const bounds = getAgentDayBoundsUtc(opts.timezone || undefined, opts.dayBoundaryHour, now);
|
|
36
|
+
const daily = db
|
|
37
|
+
.prepare(`SELECT COUNT(DISTINCT CASE
|
|
38
|
+
WHEN dispatch_id != '' THEN dispatch_id
|
|
39
|
+
ELSE CAST(id AS TEXT)
|
|
40
|
+
END) as cnt
|
|
41
|
+
FROM notification_log
|
|
42
|
+
WHERE status = 'delivered'
|
|
43
|
+
AND COALESCE(notification_type, '') != 'message.received'
|
|
44
|
+
AND created_at >= ? AND created_at < ?`)
|
|
45
|
+
.get(bounds.start, bounds.end);
|
|
46
|
+
if (daily.cnt >= opts.maxNotificationsPerDay) {
|
|
47
|
+
return { limited: true, retryAfter: bounds.end };
|
|
48
|
+
}
|
|
49
|
+
return { limited: false, retryAfter: null };
|
|
50
|
+
}
|
|
@@ -31,7 +31,7 @@ export declare function createPromptInjectionBudget(maxBytes?: number): PromptIn
|
|
|
31
31
|
* - `policies/mcp.md` — MCP usage rules (B-003; inject when any MCP enabled)
|
|
32
32
|
* - `policies/journal-format.md` — daily journal format (morning routine)
|
|
33
33
|
* - `policies/redaction.md` — secret patterns (all flows)
|
|
34
|
-
* - `policies/routines/
|
|
34
|
+
* - `policies/routines/activity-scan.md` — activity scan list
|
|
35
35
|
* - `policies/routines/morning.md` — 04:00 checks (morning routine)
|
|
36
36
|
* - `policies/routines/custom/<slug>.md` — per-custom-routine check list
|
|
37
37
|
*
|
|
@@ -51,8 +51,8 @@ export const POLICY_FILE_REGISTRY = {
|
|
|
51
51
|
injectIf: (ctx) => ctx.flags?.mcpEnabled === true,
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
|
-
"routine.
|
|
55
|
-
{ path: CONTEXT_RELATIVE_PATHS.routines.
|
|
54
|
+
"routine.activity_scan": [
|
|
55
|
+
{ path: CONTEXT_RELATIVE_PATHS.routines.activityScan, label: "Activity scans" },
|
|
56
56
|
],
|
|
57
57
|
"routine.morning_routine": [
|
|
58
58
|
{
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Freshness-window helpers for the
|
|
2
|
+
* Freshness-window helpers for the activity_scan pre-pass harvester
|
|
3
3
|
* (HOURLY_CHECK_GATE_REDESIGN_PLAN.md §3.4).
|
|
4
4
|
*
|
|
5
5
|
* The pre-pass fetcher writes a `runtime_state` row keyed by integration
|
|
6
|
-
* after every successful per-integration completion. The
|
|
6
|
+
* after every successful per-integration completion. The activity_scan
|
|
7
7
|
* coordinator's `harvestForGate` reads the row to decide whether the
|
|
8
8
|
* window is fresh enough to skip pre-pass on this tick.
|
|
9
9
|
*
|
|
10
10
|
* The key prefix is intentionally shared across every routine that
|
|
11
|
-
* spawns `routine.fetch_window` (morning_routine,
|
|
11
|
+
* spawns `routine.fetch_window` (morning_routine, activity_scan,
|
|
12
12
|
* evening_review, weekly_review, today_refresh). Sharing means a
|
|
13
13
|
* morning_routine that just ran at 04:00 suppresses the 05:00
|
|
14
|
-
*
|
|
14
|
+
* activity_scan pre-pass — exactly what we want to avoid double-fetching.
|
|
15
15
|
*
|
|
16
16
|
* Module is intentionally trivial — separated so both the runner (writer)
|
|
17
17
|
* and the coordinator (reader) can depend on a single string-builder
|
package/dist/core/retention.d.ts
CHANGED
|
@@ -90,6 +90,11 @@ export interface RetentionResult {
|
|
|
90
90
|
* rows are never counted here; boot-recovery owns them.
|
|
91
91
|
*/
|
|
92
92
|
browserTask: number;
|
|
93
|
+
/**
|
|
94
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §6 — terminal `background_task` rows
|
|
95
|
+
* pruned during this sweep (children cascade via FK).
|
|
96
|
+
*/
|
|
97
|
+
backgroundTask: number;
|
|
93
98
|
/**
|
|
94
99
|
* BROWSER_TASK_REDESIGN_PLAN.md §5 — pending lite-final-confirm
|
|
95
100
|
* tokens past their 5-min TTL flipped to `expired` during this sweep
|
package/dist/core/retention.js
CHANGED
|
@@ -6,6 +6,7 @@ import { expireStalePurchaseTokens, scrubRotatedPurchaseTokens, sweepOrphanedCon
|
|
|
6
6
|
import { deletePurchaseRepliesOlderThan } from "../db/browser-automation-purchase-replies-store.js";
|
|
7
7
|
import { deleteWorkflowRunsOlderThan } from "../db/browser-automation-store.js";
|
|
8
8
|
import { deleteTerminalBrowserTasksOlderThan } from "../db/browser-task-store.js";
|
|
9
|
+
import { deleteTerminalBackgroundTasksOlderThan } from "../db/background-task-store.js";
|
|
9
10
|
import { expireStaleLiteFinalConfirmTokens, scrubRotatedLiteFinalConfirmTokens, } from "../db/browser-task-final-confirm-tokens-store.js";
|
|
10
11
|
import { cleanupConsumedObservations, getStalePendingObservationStats, } from "../db/observations.js";
|
|
11
12
|
import { pruneOldMcpToolCalls } from "../services/mcp/tool-audit.js";
|
|
@@ -61,6 +62,16 @@ const RETENTION_DAYS = {
|
|
|
61
62
|
* sweep itself is broken and we should not paper over it.
|
|
62
63
|
*/
|
|
63
64
|
browserTask: TRACE_RETENTION_DAYS,
|
|
65
|
+
/**
|
|
66
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §6 — terminal `background_task` rows
|
|
67
|
+
* age out at 30 days. Unlike browser_task there are no trace
|
|
68
|
+
* screenshots to keep in sync; 30 days keeps a month of completed-task
|
|
69
|
+
* history so a late "what did that find?" follow-up can still
|
|
70
|
+
* `GET /api/background-task/:id`. Children
|
|
71
|
+
* (`background_task_clarifications`) cascade via FK. Non-terminal rows
|
|
72
|
+
* are NEVER deleted — boot re-dispatch owns them.
|
|
73
|
+
*/
|
|
74
|
+
backgroundTask: 30,
|
|
64
75
|
/**
|
|
65
76
|
* BROWSER_TASK_REDESIGN_PLAN.md §14.11 Q#6 — lite-final-confirm tokens
|
|
66
77
|
* carry the same `!~xxxxxxxx` shape as B-4 purchase tokens. Mirror the
|
|
@@ -79,7 +90,7 @@ const RETENTION_DAYS = {
|
|
|
79
90
|
skillCurationRunningMaxHours: 24,
|
|
80
91
|
tempFiles: 1,
|
|
81
92
|
/**
|
|
82
|
-
* Pending observations are NEVER deleted by retention (the
|
|
93
|
+
* Pending observations are NEVER deleted by retention (the activity_scan
|
|
83
94
|
* dispatcher owns consumption). After this many days unconsumed, retention
|
|
84
95
|
* logs a warning so the operator notices a stalled pipeline.
|
|
85
96
|
*/
|
|
@@ -193,6 +204,7 @@ export function runRetentionCleanup(db, config) {
|
|
|
193
204
|
browserAutomationPurchaseTokensScrubbed: 0,
|
|
194
205
|
browserAutomationPurchaseRepliesDeleted: 0,
|
|
195
206
|
browserTask: 0,
|
|
207
|
+
backgroundTask: 0,
|
|
196
208
|
browserTaskFinalConfirmTokensExpired: 0,
|
|
197
209
|
browserTaskFinalConfirmTokensScrubbed: 0,
|
|
198
210
|
ftsOptimized: false,
|
|
@@ -232,6 +244,7 @@ export function runRetentionCleanup(db, config) {
|
|
|
232
244
|
browserAutomationPurchaseTokensScrubbed: 0,
|
|
233
245
|
browserAutomationPurchaseRepliesDeleted: 0,
|
|
234
246
|
browserTask: 0,
|
|
247
|
+
backgroundTask: 0,
|
|
235
248
|
browserTaskFinalConfirmTokensExpired: 0,
|
|
236
249
|
browserTaskFinalConfirmTokensScrubbed: 0,
|
|
237
250
|
};
|
|
@@ -380,10 +393,12 @@ export function runRetentionCleanup(db, config) {
|
|
|
380
393
|
scrubRotatedLiteFinalConfirmTokens(db, tokenScrubCutoff);
|
|
381
394
|
const browserTaskCutoff = now - RETENTION_DAYS.browserTask * 86_400_000;
|
|
382
395
|
counts.browserTask = deleteTerminalBrowserTasksOlderThan(db, browserTaskCutoff);
|
|
396
|
+
const backgroundTaskCutoff = now - RETENTION_DAYS.backgroundTask * 86_400_000;
|
|
397
|
+
counts.backgroundTask = deleteTerminalBackgroundTasksOlderThan(db, backgroundTaskCutoff);
|
|
383
398
|
}
|
|
384
399
|
catch (err) {
|
|
385
400
|
/* c8 ignore next 5 */
|
|
386
|
-
logger.warn({ err }, "browser_task retention sweep skipped (tables missing)");
|
|
401
|
+
logger.warn({ err }, "browser_task / background_task retention sweep skipped (tables missing)");
|
|
387
402
|
}
|
|
388
403
|
})();
|
|
389
404
|
// Transaction committed — safe to copy counts into result.
|
|
@@ -416,6 +431,7 @@ export function runRetentionCleanup(db, config) {
|
|
|
416
431
|
result.imminentEventNotifications = counts.imminentEventNotifications;
|
|
417
432
|
result.browserAutomationWorkflows = counts.browserAutomationWorkflows;
|
|
418
433
|
result.browserTask = counts.browserTask;
|
|
434
|
+
result.backgroundTask = counts.backgroundTask;
|
|
419
435
|
result.browserTaskFinalConfirmTokensExpired =
|
|
420
436
|
counts.browserTaskFinalConfirmTokensExpired;
|
|
421
437
|
result.browserTaskFinalConfirmTokensScrubbed =
|
|
@@ -433,7 +449,7 @@ export function runRetentionCleanup(db, config) {
|
|
|
433
449
|
result.attachmentOrphanRows = attachmentCleanup.orphanRows;
|
|
434
450
|
result.attachmentDanglingRows = attachmentCleanup.danglingRows;
|
|
435
451
|
result.attachmentUntrackedDirs = attachmentCleanup.untrackedDirs;
|
|
436
|
-
// Surface stale pending observations so a stalled
|
|
452
|
+
// Surface stale pending observations so a stalled activity_scan pipeline
|
|
437
453
|
// becomes visible in daemon logs. Pending rows are intentionally not
|
|
438
454
|
// deleted (see RETENTION_DAYS.stalePendingObservationsWarn comment).
|
|
439
455
|
const stalePending = getStalePendingObservationStats(db, RETENTION_DAYS.stalePendingObservationsWarn);
|
|
@@ -442,7 +458,7 @@ export function runRetentionCleanup(db, config) {
|
|
|
442
458
|
stalePendingCount: stalePending.count,
|
|
443
459
|
oldestObservedAt: stalePending.oldestObservedAt,
|
|
444
460
|
thresholdDays: RETENTION_DAYS.stalePendingObservationsWarn,
|
|
445
|
-
}, "Stale pending observations detected —
|
|
461
|
+
}, "Stale pending observations detected — activity_scan may be skipping or stalled");
|
|
446
462
|
}
|
|
447
463
|
// ── FTS5 segment optimization ──
|
|
448
464
|
//
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PromptInjectionBudget } from "./policy-files.js";
|
|
2
|
-
export type ReviewFlowSlug = "
|
|
2
|
+
export type ReviewFlowSlug = "activity-scan" | "morning" | "evening" | "weekly" | "monthly" | "roadmap";
|
|
3
3
|
interface ReviewFlowConfig {
|
|
4
4
|
flow: ReviewFlowSlug;
|
|
5
5
|
dossierPath: string;
|
|
@@ -6,10 +6,10 @@ import { POLICY_FILE_MAX_BYTES, createPromptInjectionBudget, } from "./policy-fi
|
|
|
6
6
|
import { createLogger } from "../logging.js";
|
|
7
7
|
const logger = createLogger("review-context");
|
|
8
8
|
const REVIEW_FLOW_BY_PROCESS_KEY = {
|
|
9
|
-
"routine.
|
|
10
|
-
flow: "
|
|
11
|
-
dossierPath: dossierPath("
|
|
12
|
-
dossierLabel: "
|
|
9
|
+
"routine.activity_scan": {
|
|
10
|
+
flow: "activity-scan",
|
|
11
|
+
dossierPath: dossierPath("activity-scan"),
|
|
12
|
+
dossierLabel: "Activity scan dossier",
|
|
13
13
|
},
|
|
14
14
|
"routine.morning_routine": {
|
|
15
15
|
flow: "morning",
|
|
@@ -295,7 +295,12 @@ function reviewFlowsMatch(raw, flow) {
|
|
|
295
295
|
.split(/[,;/\s]+/)
|
|
296
296
|
.map((token) => token.trim())
|
|
297
297
|
.filter(Boolean);
|
|
298
|
-
|
|
298
|
+
if (tokens.includes(flow))
|
|
299
|
+
return true;
|
|
300
|
+
// v0.1.10 → v0.1.11 rename: user-vault `_index.md` rows written before the
|
|
301
|
+
// rename tag the activity-scan flow as "hourly". Accept the legacy token
|
|
302
|
+
// until the index reconciler has naturally rewritten those rows.
|
|
303
|
+
return flow === "activity-scan" && tokens.includes("hourly");
|
|
299
304
|
}
|
|
300
305
|
function sanitizeContextIndexPath(rawPath) {
|
|
301
306
|
const path = rawPath.trim().replace(/^\.\//, "");
|
|
@@ -31,8 +31,9 @@ export interface RoadmapWriteLockManager {
|
|
|
31
31
|
export declare class InMemoryRoadmapWriteLockManager implements RoadmapWriteLockManager {
|
|
32
32
|
private readonly timeoutMs;
|
|
33
33
|
private holder;
|
|
34
|
-
private
|
|
34
|
+
private expiresAtMs;
|
|
35
35
|
constructor(timeoutMs: number);
|
|
36
|
+
private expireIfStale;
|
|
36
37
|
acquire(): {
|
|
37
38
|
ok: true;
|
|
38
39
|
lockId: string;
|
|
@@ -4,44 +4,49 @@ const logger = createLogger("roadmap-write-lock");
|
|
|
4
4
|
export class InMemoryRoadmapWriteLockManager {
|
|
5
5
|
timeoutMs;
|
|
6
6
|
holder = null;
|
|
7
|
-
|
|
7
|
+
expiresAtMs = 0;
|
|
8
8
|
constructor(timeoutMs) {
|
|
9
9
|
this.timeoutMs = timeoutMs;
|
|
10
10
|
}
|
|
11
|
+
// Wall-clock lazy expiry — mirrors today-write-lock.ts. A setTimeout
|
|
12
|
+
// here would freeze across machine sleep (monotonic clock) and hold
|
|
13
|
+
// the lock long past its TTL after wake.
|
|
14
|
+
expireIfStale() {
|
|
15
|
+
if (this.holder && Date.now() >= this.expiresAtMs) {
|
|
16
|
+
logger.warn({ lockId: this.holder }, "Roadmap write lock expired by timeout");
|
|
17
|
+
this.holder = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
11
20
|
acquire() {
|
|
21
|
+
this.expireIfStale();
|
|
12
22
|
if (this.holder) {
|
|
13
23
|
logger.debug({ existingHolder: this.holder }, "Lock acquire rejected — already held");
|
|
14
24
|
return { ok: false, holder: this.holder };
|
|
15
25
|
}
|
|
16
26
|
const lockId = randomUUID();
|
|
17
27
|
this.holder = lockId;
|
|
18
|
-
this.
|
|
19
|
-
logger.warn({ lockId: this.holder }, "Roadmap write lock expired by timeout");
|
|
20
|
-
this.holder = null;
|
|
21
|
-
this.timer = null;
|
|
22
|
-
}, this.timeoutMs);
|
|
28
|
+
this.expiresAtMs = Date.now() + this.timeoutMs;
|
|
23
29
|
logger.debug({ lockId }, "Roadmap write lock acquired");
|
|
24
30
|
return { ok: true, lockId };
|
|
25
31
|
}
|
|
26
32
|
release(lockId) {
|
|
33
|
+
this.expireIfStale();
|
|
27
34
|
if (!this.holder || this.holder !== lockId) {
|
|
28
35
|
return false;
|
|
29
36
|
}
|
|
30
37
|
this.holder = null;
|
|
31
|
-
if (this.timer) {
|
|
32
|
-
clearTimeout(this.timer);
|
|
33
|
-
this.timer = null;
|
|
34
|
-
}
|
|
35
38
|
logger.debug({ lockId }, "Roadmap write lock released");
|
|
36
39
|
return true;
|
|
37
40
|
}
|
|
38
41
|
isHeldBy(lockId) {
|
|
42
|
+
this.expireIfStale();
|
|
39
43
|
if (!this.holder) {
|
|
40
44
|
return false;
|
|
41
45
|
}
|
|
42
46
|
return this.holder === lockId;
|
|
43
47
|
}
|
|
44
48
|
getHolder() {
|
|
49
|
+
this.expireIfStale();
|
|
45
50
|
return this.holder;
|
|
46
51
|
}
|
|
47
52
|
}
|
|
@@ -27,12 +27,45 @@
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
import { type BackendId, type IntegrationKey, type IntegrationState } from "@aitne/shared";
|
|
30
|
-
import { type RoutineWindowKey } from "./routine-windows.js";
|
|
30
|
+
import { type RoutineWindowKey, type WindowSymbol } from "./routine-windows.js";
|
|
31
31
|
/**
|
|
32
32
|
* Per-mode predicate string the partial filters on. Matches the predicate
|
|
33
33
|
* names used by `applyIntegrationModeFilter` in `@aitne/shared`.
|
|
34
34
|
*/
|
|
35
35
|
export type AcquisitionFetchMode = "direct" | "delegated-same" | "delegated-cross" | "native";
|
|
36
|
+
/**
|
|
37
|
+
* Why a (window × integration) cell was dropped at plan-assembly time —
|
|
38
|
+
* PREPASS_COST_REDUCTION_PLAN.md N3. Before N3 these drops vanished
|
|
39
|
+
* without a trace; the runner now writes one `skipped` audit row per
|
|
40
|
+
* (integration × reason) group so the deferred no-surface streak skip
|
|
41
|
+
* (R5) and the empty-window backoff (R4) can be sized from data.
|
|
42
|
+
*
|
|
43
|
+
* - `no_state` — integration absent from the `readIntegrations` snapshot.
|
|
44
|
+
* - `no_binding` — delegated/native mode with a null backend binding.
|
|
45
|
+
* - `disabled` — integration mode is explicitly `disabled`.
|
|
46
|
+
* - `unknown_mode` — unrecognized mode string (forward-compat guard).
|
|
47
|
+
* - `no_window_query` — `WINDOW_QUERIES` has no cell for the
|
|
48
|
+
* (window, integration, mode) tuple where one was expected — a
|
|
49
|
+
* genuine catalog hole.
|
|
50
|
+
* - `no_accounts` — direct-mode per-account fan-out with zero active
|
|
51
|
+
* accounts for the integration.
|
|
52
|
+
* - `no_fetch_targets` — Notion routine fetches require an explicit
|
|
53
|
+
* user allowlist so the pre-pass cannot scan the whole workspace.
|
|
54
|
+
* - `direct_inline_prefetch` — the catalog *deliberately* omits the
|
|
55
|
+
* `direct` cell because the daemon serves that data inline
|
|
56
|
+
* (ContextBuilder pre-fetch / REST route) and a pre-pass row would
|
|
57
|
+
* double-fetch (cf. `cal_morning_7d` in routine-windows.ts). Working
|
|
58
|
+
* as designed, so the runner does NOT write an audit row for it —
|
|
59
|
+
* counting it as a drop would pollute the R4/R5 sizing data the N3
|
|
60
|
+
* audit stream exists to provide.
|
|
61
|
+
*/
|
|
62
|
+
export type AcquisitionPlanDropReason = "no_state" | "no_binding" | "disabled" | "unknown_mode" | "no_window_query" | "no_accounts" | "no_fetch_targets" | "direct_inline_prefetch";
|
|
63
|
+
/** One dropped (window × integration) cell. */
|
|
64
|
+
export interface AcquisitionPlanDrop {
|
|
65
|
+
integration: IntegrationKey;
|
|
66
|
+
window: WindowSymbol;
|
|
67
|
+
reason: AcquisitionPlanDropReason;
|
|
68
|
+
}
|
|
36
69
|
export interface AcquisitionAccount {
|
|
37
70
|
/**
|
|
38
71
|
* Integration key the account belongs to. Today only `gmail` and
|
|
@@ -215,3 +248,16 @@ export interface AcquisitionSubPlan {
|
|
|
215
248
|
* `scoped="<key>"` attribute and the partition itself.
|
|
216
249
|
*/
|
|
217
250
|
export declare function splitAcquisitionPlanByIntegration(input: BuildAcquisitionPlanInput): readonly AcquisitionSubPlan[];
|
|
251
|
+
/**
|
|
252
|
+
* `splitAcquisitionPlanByIntegration` + the drop trace —
|
|
253
|
+
* PREPASS_COST_REDUCTION_PLAN.md N3. The fan-out runner consumes this
|
|
254
|
+
* variant so every (window × integration) cell dropped at plan-assembly
|
|
255
|
+
* time can be surfaced as a `skipped` audit row instead of vanishing.
|
|
256
|
+
* Same purity / ordering / row-preservation contract as the wrapper
|
|
257
|
+
* above.
|
|
258
|
+
*/
|
|
259
|
+
export interface AcquisitionPlanAssembly {
|
|
260
|
+
subPlans: readonly AcquisitionSubPlan[];
|
|
261
|
+
drops: readonly AcquisitionPlanDrop[];
|
|
262
|
+
}
|
|
263
|
+
export declare function buildAcquisitionPlanAssembly(input: BuildAcquisitionPlanInput): AcquisitionPlanAssembly;
|
|
@@ -111,7 +111,7 @@ function integrationsForWindow(symbol) {
|
|
|
111
111
|
*/
|
|
112
112
|
function resolveFetchMode(integration, state, sessionBackend) {
|
|
113
113
|
if (!state)
|
|
114
|
-
return
|
|
114
|
+
return "no_state";
|
|
115
115
|
switch (state.mode) {
|
|
116
116
|
case "direct":
|
|
117
117
|
return "direct";
|
|
@@ -131,21 +131,31 @@ function resolveFetchMode(integration, state, sessionBackend) {
|
|
|
131
131
|
}
|
|
132
132
|
return "delegated-cross";
|
|
133
133
|
}
|
|
134
|
-
return
|
|
134
|
+
return "no_binding";
|
|
135
135
|
case "native":
|
|
136
136
|
// Native binding must match the session backend. Otherwise the
|
|
137
137
|
// partial's `mode:native:<key>` block would be filtered out by
|
|
138
138
|
// `applyIntegrationModeFilter` anyway — skip the row to avoid
|
|
139
|
-
// emitting a `<fetch>` that no branch can handle.
|
|
139
|
+
// emitting a `<fetch>` that no branch can handle. With
|
|
140
|
+
// per-integration backend routing (`resolveIntegrationBackend`)
|
|
141
|
+
// the caller passes the integration's own `nativeBackend` here, so
|
|
142
|
+
// in practice this branch only drops rows whose binding is null.
|
|
140
143
|
if (state.nativeBackend === sessionBackend)
|
|
141
144
|
return "native";
|
|
142
|
-
return
|
|
145
|
+
return "no_binding";
|
|
143
146
|
case "disabled":
|
|
144
|
-
return
|
|
147
|
+
return "disabled";
|
|
145
148
|
default:
|
|
146
|
-
return
|
|
149
|
+
return "unknown_mode";
|
|
147
150
|
}
|
|
148
151
|
}
|
|
152
|
+
/** Narrow a `resolveFetchMode` result to the fetch-mode side of the union. */
|
|
153
|
+
function isFetchMode(value) {
|
|
154
|
+
return (value === "direct"
|
|
155
|
+
|| value === "delegated-same"
|
|
156
|
+
|| value === "delegated-cross"
|
|
157
|
+
|| value === "native");
|
|
158
|
+
}
|
|
149
159
|
/**
|
|
150
160
|
* Resolve the backend the sub-session for this integration MUST run on
|
|
151
161
|
* so that the partial body's resolved `mode:` block has a working wire
|
|
@@ -170,9 +180,9 @@ function resolveFetchMode(integration, state, sessionBackend) {
|
|
|
170
180
|
* backend keeps the pre-pass tier predictable.
|
|
171
181
|
* - `direct`: REST via curl to the daemon — sub-session stays on
|
|
172
182
|
* `defaultBackend`.
|
|
173
|
-
* - `disabled` / no state: irrelevant (`resolveFetchMode` returns
|
|
174
|
-
*
|
|
175
|
-
* `defaultBackend` is a no-op safety default.
|
|
183
|
+
* - `disabled` / no state: irrelevant (`resolveFetchMode` returns a
|
|
184
|
+
* drop reason and the row is dropped before backend matters);
|
|
185
|
+
* returning `defaultBackend` is a no-op safety default.
|
|
176
186
|
*
|
|
177
187
|
* The function is intentionally `null`-free — every call site benefits
|
|
178
188
|
* from a guaranteed backend so the per-integration spawn path never has
|
|
@@ -277,6 +287,11 @@ function renderFetchRow(row) {
|
|
|
277
287
|
if (row.label !== undefined) {
|
|
278
288
|
parts.push(`label="${xmlAttr(row.label)}"`);
|
|
279
289
|
}
|
|
290
|
+
// `collectFetchRows` only attaches a non-empty allowlist (empty drops
|
|
291
|
+
// the row with `no_fetch_targets`), so defined ⇒ renderable.
|
|
292
|
+
if (row.fetchTargets !== undefined) {
|
|
293
|
+
parts.push(`targets='${xmlQueryAttr(JSON.stringify(row.fetchTargets))}'`);
|
|
294
|
+
}
|
|
280
295
|
// Single-quote delimiter on `query=` — see `xmlQueryAttr` rationale.
|
|
281
296
|
parts.push(`query='${xmlQueryAttr(row.query)}'`);
|
|
282
297
|
return ` <fetch ${parts.join(" ")} />`;
|
|
@@ -291,31 +306,65 @@ function renderFetchRow(row) {
|
|
|
291
306
|
* monolithic block and the union of per-integration sub-plan blocks
|
|
292
307
|
* carry bit-identical row sequences.
|
|
293
308
|
*/
|
|
294
|
-
function collectFetchRows(input
|
|
309
|
+
function collectFetchRows(input,
|
|
310
|
+
/**
|
|
311
|
+
* N3 observability hook — invoked once per dropped (window ×
|
|
312
|
+
* integration) cell with the drop reason. Optional so the render-only
|
|
313
|
+
* consumers (`buildAcquisitionPlan`, `rebuildSubPlanForBackend`) pay
|
|
314
|
+
* nothing.
|
|
315
|
+
*/
|
|
316
|
+
onDrop) {
|
|
295
317
|
const rows = [];
|
|
296
318
|
const specs = ROUTINE_WINDOWS[input.routine];
|
|
297
319
|
for (const spec of specs) {
|
|
298
320
|
const integrations = integrationsForWindow(spec.window);
|
|
299
321
|
for (const integration of integrations) {
|
|
300
322
|
const state = input.integrations[integration];
|
|
323
|
+
// Notion rows carry the user's fetch-target allowlist (undefined
|
|
324
|
+
// for every other integration). Resolved up here — before the mode
|
|
325
|
+
// guards — but the empty-allowlist drop stays AFTER them, so a
|
|
326
|
+
// disabled / unbound Notion row keeps its mode-derived drop reason
|
|
327
|
+
// and `no_fetch_targets` means exactly "active but unconfigured".
|
|
328
|
+
const fetchTargets = integration === "notion" ? (state?.fetchTargets ?? []) : undefined;
|
|
301
329
|
// Per-integration backend resolution. Was: `input.sessionBackend`
|
|
302
330
|
// (single backend for the whole plan), which caused `resolveFetchMode`
|
|
303
|
-
// to
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
331
|
+
// to drop rows for native bindings on a different backend. With
|
|
332
|
+
// `resolveIntegrationBackend` the sub-session is routed to the
|
|
333
|
+
// integration's actual bound backend (`nativeBackend`, or
|
|
334
|
+
// `delegatedBackend` for userManagedConnector), and
|
|
335
|
+
// `resolveFetchMode` then matches on the correct backend so the
|
|
308
336
|
// row survives. The runner uses `requiredBackend` (bubbled up via
|
|
309
337
|
// `FetchRow`) to spawn each sub-session on the right backend via
|
|
310
338
|
// `BackendRouter.resolveBinding({ requestedBackendId })`.
|
|
311
339
|
const requiredBackend = resolveIntegrationBackend(integration, state, input.sessionBackend);
|
|
312
340
|
const fetchMode = resolveFetchMode(integration, state, requiredBackend);
|
|
313
|
-
if (fetchMode
|
|
341
|
+
if (!isFetchMode(fetchMode)) {
|
|
342
|
+
onDrop?.({ integration, window: spec.window, reason: fetchMode });
|
|
314
343
|
continue;
|
|
344
|
+
}
|
|
315
345
|
const queryTemplate = lookupQuery(spec.window, integration, fetchMode);
|
|
316
|
-
if (queryTemplate === undefined)
|
|
346
|
+
if (queryTemplate === undefined) {
|
|
347
|
+
// `integrationsForWindow` only yields integrations present in
|
|
348
|
+
// the catalog for this window, so an undefined template means
|
|
349
|
+
// the MODE cell is missing. For `direct` that is the documented
|
|
350
|
+
// intentional pattern (cf. `cal_morning_7d` in
|
|
351
|
+
// routine-windows.ts): the daemon serves the data inline, so the
|
|
352
|
+
// pre-pass must not double-fetch. Any other mode is a genuine
|
|
353
|
+
// catalog hole.
|
|
354
|
+
onDrop?.({
|
|
355
|
+
integration,
|
|
356
|
+
window: spec.window,
|
|
357
|
+
reason: fetchMode === "direct"
|
|
358
|
+
? "direct_inline_prefetch"
|
|
359
|
+
: "no_window_query",
|
|
360
|
+
});
|
|
317
361
|
continue;
|
|
362
|
+
}
|
|
318
363
|
const query = substituteAcquisitionTokens(queryTemplate, input.timestamps);
|
|
364
|
+
if (fetchTargets !== undefined && fetchTargets.length === 0) {
|
|
365
|
+
onDrop?.({ integration, window: spec.window, reason: "no_fetch_targets" });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
319
368
|
// perAccount fan-out is meaningful only in `direct` mode, where the
|
|
320
369
|
// daemon stores per-account OAuth tokens and polls each. In
|
|
321
370
|
// `delegated-same` / `delegated-cross` / `native` the integration's
|
|
@@ -332,6 +381,9 @@ function collectFetchRows(input) {
|
|
|
332
381
|
// surface so the partials never need to defend against them.
|
|
333
382
|
if (spec.perAccount && fetchMode === "direct") {
|
|
334
383
|
const accountRows = input.accounts.filter((a) => a.integration === integration);
|
|
384
|
+
if (accountRows.length === 0) {
|
|
385
|
+
onDrop?.({ integration, window: spec.window, reason: "no_accounts" });
|
|
386
|
+
}
|
|
335
387
|
for (const account of accountRows) {
|
|
336
388
|
rows.push({
|
|
337
389
|
integration,
|
|
@@ -340,6 +392,7 @@ function collectFetchRows(input) {
|
|
|
340
392
|
accountId: account.accountId,
|
|
341
393
|
label: account.label,
|
|
342
394
|
query,
|
|
395
|
+
fetchTargets,
|
|
343
396
|
requiredBackend,
|
|
344
397
|
});
|
|
345
398
|
}
|
|
@@ -350,6 +403,7 @@ function collectFetchRows(input) {
|
|
|
350
403
|
mode: fetchMode,
|
|
351
404
|
window: spec.window,
|
|
352
405
|
query,
|
|
406
|
+
fetchTargets,
|
|
353
407
|
requiredBackend,
|
|
354
408
|
});
|
|
355
409
|
}
|
|
@@ -418,9 +472,13 @@ export function buildAcquisitionPlan(input) {
|
|
|
418
472
|
* `scoped="<key>"` attribute and the partition itself.
|
|
419
473
|
*/
|
|
420
474
|
export function splitAcquisitionPlanByIntegration(input) {
|
|
421
|
-
|
|
475
|
+
return buildAcquisitionPlanAssembly(input).subPlans;
|
|
476
|
+
}
|
|
477
|
+
export function buildAcquisitionPlanAssembly(input) {
|
|
478
|
+
const drops = [];
|
|
479
|
+
const rows = collectFetchRows(input, (drop) => drops.push(drop));
|
|
422
480
|
if (rows.length === 0)
|
|
423
|
-
return [];
|
|
481
|
+
return { subPlans: [], drops };
|
|
424
482
|
// Group rows by integration while keeping insertion order inside each
|
|
425
483
|
// group (the `ROUTINE_WINDOWS` walk order from `collectFetchRows`).
|
|
426
484
|
const groups = new Map();
|
|
@@ -453,5 +511,5 @@ export function splitAcquisitionPlanByIntegration(input) {
|
|
|
453
511
|
requiredBackend,
|
|
454
512
|
});
|
|
455
513
|
}
|
|
456
|
-
return out;
|
|
514
|
+
return { subPlans: out, drops };
|
|
457
515
|
}
|
|
@@ -84,10 +84,13 @@ function isFetchFailedWithStatus(err, predicate) {
|
|
|
84
84
|
/**
|
|
85
85
|
* Sentinel error-status strings the runner classifies as deterministic
|
|
86
86
|
* (no point retrying — the next attempt re-emits the same bytes and
|
|
87
|
-
* re-trips the same gate).
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* the
|
|
87
|
+
* re-trips the same gate). The emitting side of the contract is the
|
|
88
|
+
* agent profile's `fetch-failed` bullet
|
|
89
|
+
* (`agent-assets/agent-profiles/routine-fetch-window.md`), which
|
|
90
|
+
* instructs the agent to set `status:"permission-denied"` when its own
|
|
91
|
+
* permission layer blocked a tool call; the runner interprets the
|
|
92
|
+
* strings here, and any future telemetry that filters on them
|
|
93
|
+
* references the same vocabulary.
|
|
91
94
|
*
|
|
92
95
|
* See `RETRY_REASONS.DETERMINISTIC_FAILURE` for the rationale and the
|
|
93
96
|
* specific gates these sentinels are paired with — and for why
|