@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
|
@@ -7,7 +7,25 @@ import { createLogger } from "../logging.js";
|
|
|
7
7
|
import { processMailBatch } from "../services/mail-classifier.js";
|
|
8
8
|
import { isIntegrationPollerless } from "../core/integration-lifecycle.js";
|
|
9
9
|
import { emptyIngestionStats, processClassifiedMailBatch, } from "../services/mail-ingestion.js";
|
|
10
|
+
import { raceWithAbort } from "../core/abort-utils.js";
|
|
10
11
|
const logger = createLogger("mail-poller");
|
|
12
|
+
/**
|
|
13
|
+
* Wall-clock cap on the per-account `pollSince` paging loop. Matches the
|
|
14
|
+
* NotionPoller tick cap. Without it, a half-dead connection (typical after
|
|
15
|
+
* machine sleep kills the TCP socket but the server never RSTs) hangs the
|
|
16
|
+
* account's poll forever — the tick-level `inFlight` flag then blocks every
|
|
17
|
+
* subsequent tick for ALL accounts until daemon restart. The aborted call
|
|
18
|
+
* leaks per the {@link raceWithAbort} contract; the transport's own timeout
|
|
19
|
+
* eventually reaps it.
|
|
20
|
+
*/
|
|
21
|
+
const ACCOUNT_POLL_TIMEOUT_MS = 4 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Wall-clock cap on `startIdle` (connect + mailboxOpen round-trips). Shorter
|
|
24
|
+
* than the poll cap — IDLE setup is two cheap round-trips; if they don't
|
|
25
|
+
* complete in a minute the socket is dead and the next tick should retry
|
|
26
|
+
* with a fresh connection.
|
|
27
|
+
*/
|
|
28
|
+
const IDLE_START_TIMEOUT_MS = 60 * 1000;
|
|
11
29
|
/**
|
|
12
30
|
* Unified mail poller (§3.4). Iterates over `registry.listActiveAccounts()`
|
|
13
31
|
* and calls `provider.pollSince()` per account, recording one aggregated
|
|
@@ -187,6 +205,19 @@ export class MailPoller {
|
|
|
187
205
|
const provider = await this.registry.getProvider(account.id);
|
|
188
206
|
if (!provider || !hasIdleSupport(provider))
|
|
189
207
|
return;
|
|
208
|
+
const aborter = new AbortController();
|
|
209
|
+
const timer = setTimeout(() => {
|
|
210
|
+
aborter.abort(new Error(`mail_idle_start_timeout:${account.id}:${IDLE_START_TIMEOUT_MS}ms`));
|
|
211
|
+
}, IDLE_START_TIMEOUT_MS);
|
|
212
|
+
timer.unref?.();
|
|
213
|
+
try {
|
|
214
|
+
await raceWithAbort(this.startIdleForAccount(account, provider), aborter.signal);
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async startIdleForAccount(account, provider) {
|
|
190
221
|
await provider.startIdle({
|
|
191
222
|
onDirty: () => {
|
|
192
223
|
// INTEGRATION_NATIVE_MODE_DESIGN.md §6.2 — the tick-level filter
|
|
@@ -335,9 +366,14 @@ export class MailPoller {
|
|
|
335
366
|
// are immediately available; we walk them in this same tick (§3.4).
|
|
336
367
|
let pages = 0;
|
|
337
368
|
const maxPages = 5;
|
|
369
|
+
const aborter = new AbortController();
|
|
370
|
+
const timer = setTimeout(() => {
|
|
371
|
+
aborter.abort(new Error(`mail_poll_timeout:${account.id}:${ACCOUNT_POLL_TIMEOUT_MS}ms`));
|
|
372
|
+
}, ACCOUNT_POLL_TIMEOUT_MS);
|
|
373
|
+
timer.unref?.();
|
|
338
374
|
try {
|
|
339
375
|
while (!drained && pages < maxPages) {
|
|
340
|
-
const result = await provider.pollSince(cursor, this.maxMessagesPerPoll);
|
|
376
|
+
const result = await raceWithAbort(provider.pollSince(cursor, this.maxMessagesPerPoll), aborter.signal);
|
|
341
377
|
cursor = result.nextCursor;
|
|
342
378
|
drained = result.drained;
|
|
343
379
|
aggregated = aggregated.concat(result.messages);
|
|
@@ -353,6 +389,9 @@ export class MailPoller {
|
|
|
353
389
|
});
|
|
354
390
|
return;
|
|
355
391
|
}
|
|
392
|
+
finally {
|
|
393
|
+
clearTimeout(timer);
|
|
394
|
+
}
|
|
356
395
|
this.registry.recordPollTick(account.id, { success: true });
|
|
357
396
|
// Forget prior re-consent DM cadence so a future failure can DM immediately
|
|
358
397
|
// instead of silently waiting out `mailAuthFailureRetryHours` (§V3).
|
|
@@ -405,7 +444,7 @@ export class MailPoller {
|
|
|
405
444
|
// Observe only what actually landed in local state. If upsert failed,
|
|
406
445
|
// `mail_messages_index` has no row for the provider's reported
|
|
407
446
|
// `userMessages` — emitting the observation anyway would tell the
|
|
408
|
-
//
|
|
447
|
+
// activity-scan signal compute (and downstream skill flows) that N new
|
|
409
448
|
// mails exist, and the agent would then query the index and find
|
|
410
449
|
// zero. Worse: when the next tick retries the upsert successfully it
|
|
411
450
|
// emits a second observation, double-counting the same batch. Gate
|
|
@@ -420,7 +459,7 @@ export class MailPoller {
|
|
|
420
459
|
ref: `${account.id}-${Date.now()}`,
|
|
421
460
|
changeType: "created",
|
|
422
461
|
// External-sender mail is owner-relevant signal, not internal-system
|
|
423
|
-
// bookkeeping. The
|
|
462
|
+
// bookkeeping. The activity-scan threshold gate, the silent-gate
|
|
424
463
|
// consumption path, and the `observations` skill all filter to
|
|
425
464
|
// `actor='user'` (see dispatcher.ts:1540, 1832 + skills/observations
|
|
426
465
|
// SKILL.md). Marking this `system` would invisibly excise mail from
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Today only Claude (Anthropic Messages API) is implemented. Codex and
|
|
5
5
|
* Gemini fall back to `unsupported` — the worker translates that into a
|
|
6
|
-
* `summary_status='skipped'` row so the
|
|
6
|
+
* `summary_status='skipped'` row so the activity_scan skill drops back to
|
|
7
7
|
* the legacy fetch-on-doubt pattern. This keeps the summarizer optional
|
|
8
8
|
* — non-Claude operators don't pay for it but also don't get the
|
|
9
|
-
* downstream
|
|
9
|
+
* downstream activity_scan savings until per-backend support lands.
|
|
10
10
|
*
|
|
11
11
|
* The client deliberately avoids the agent SDK's session machinery: a
|
|
12
12
|
* one-shot summarizer with no tools doesn't need workdir + skills + MCP
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Today only Claude (Anthropic Messages API) is implemented. Codex and
|
|
5
5
|
* Gemini fall back to `unsupported` — the worker translates that into a
|
|
6
|
-
* `summary_status='skipped'` row so the
|
|
6
|
+
* `summary_status='skipped'` row so the activity_scan skill drops back to
|
|
7
7
|
* the legacy fetch-on-doubt pattern. This keeps the summarizer optional
|
|
8
8
|
* — non-Claude operators don't pay for it but also don't get the
|
|
9
|
-
* downstream
|
|
9
|
+
* downstream activity_scan savings until per-backend support lands.
|
|
10
10
|
*
|
|
11
11
|
* The client deliberately avoids the agent SDK's session machinery: a
|
|
12
12
|
* one-shot summarizer with no tools doesn't need workdir + skills + MCP
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Backpressure: when the queue depth exceeds `queueDepthLimit` the
|
|
20
20
|
* worker drops new arrivals to `summary_status='skipped'` directly,
|
|
21
|
-
* skipping the LLM call. The
|
|
21
|
+
* skipping the LLM call. The activity_scan skill falls back to legacy
|
|
22
22
|
* fetch-on-doubt for those rows.
|
|
23
23
|
*/
|
|
24
24
|
import type Database from "better-sqlite3";
|
|
@@ -77,7 +77,7 @@ export declare class ObservationSummarizerWorker implements Observer {
|
|
|
77
77
|
* Public entry — invoked by the recordObservation hook. Drops new
|
|
78
78
|
* arrivals to `summary_status='skipped'` directly when the in-memory
|
|
79
79
|
* queue is over capacity. The DB row's `summary_status` reflects the
|
|
80
|
-
* actual state so
|
|
80
|
+
* actual state so activity_scan can route accordingly.
|
|
81
81
|
*
|
|
82
82
|
* `bypassBackpressure` is reserved for the startup reclaim sweep where
|
|
83
83
|
* the rows are already in DB-pending state — applying backpressure
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Backpressure: when the queue depth exceeds `queueDepthLimit` the
|
|
20
20
|
* worker drops new arrivals to `summary_status='skipped'` directly,
|
|
21
|
-
* skipping the LLM call. The
|
|
21
|
+
* skipping the LLM call. The activity_scan skill falls back to legacy
|
|
22
22
|
* fetch-on-doubt for those rows.
|
|
23
23
|
*/
|
|
24
24
|
import { getObservationForSummarization, listObservationsAwaitingSummary, setObservationEnqueueHook, updateObservationSummary, } from "../../db/observations.js";
|
|
@@ -117,7 +117,7 @@ export class ObservationSummarizerWorker {
|
|
|
117
117
|
* Public entry — invoked by the recordObservation hook. Drops new
|
|
118
118
|
* arrivals to `summary_status='skipped'` directly when the in-memory
|
|
119
119
|
* queue is over capacity. The DB row's `summary_status` reflects the
|
|
120
|
-
* actual state so
|
|
120
|
+
* actual state so activity_scan can route accordingly.
|
|
121
121
|
*
|
|
122
122
|
* `bypassBackpressure` is reserved for the startup reclaim sweep where
|
|
123
123
|
* the rows are already in DB-pending state — applying backpressure
|
|
@@ -229,7 +229,7 @@ export class ObservationSummarizerWorker {
|
|
|
229
229
|
const result = await this.callLlm(prompt);
|
|
230
230
|
if (!result.ok) {
|
|
231
231
|
// auth_missing is a user-config issue, not a per-row failure. Mark
|
|
232
|
-
// the row 'skipped' so the
|
|
232
|
+
// the row 'skipped' so the activity_scan fallback path picks it up
|
|
233
233
|
// (same posture as `unsupported_backend`) and warn at most once
|
|
234
234
|
// per cooldown window so a missing ANTHROPIC_API_KEY does not spam
|
|
235
235
|
// the log with one entry per pending observation.
|
|
@@ -241,7 +241,7 @@ export class ObservationSummarizerWorker {
|
|
|
241
241
|
backend: this.client.backendId,
|
|
242
242
|
model: this.client.modelId,
|
|
243
243
|
cooldownMs: ObservationSummarizerWorker.AUTH_MISSING_WARN_COOLDOWN_MS,
|
|
244
|
-
hint: "Set ANTHROPIC_API_KEY in env or store it via the dashboard. Pending rows are being marked 'skipped' and the
|
|
244
|
+
hint: "Set ANTHROPIC_API_KEY in env or store it via the dashboard. Pending rows are being marked 'skipped' and the activity_scan fallback path will read them directly.",
|
|
245
245
|
}, "Summarizer LLM auth missing — falling back to skip, future warnings suppressed within cooldown");
|
|
246
246
|
}
|
|
247
247
|
this.persist(row.id, { status: "skipped", summaryText: null, novelty: null });
|
|
@@ -33,7 +33,7 @@ export interface ObsidianWatcherOptions {
|
|
|
33
33
|
* **Agent-write suppression**: `AgentWriteTracker` pre-marks files the
|
|
34
34
|
* agent writes via `/api/context/*`. When a watcher fires for one of
|
|
35
35
|
* those, we silently drop the observation instead of recording it with
|
|
36
|
-
* `actor='agent'`. Downstream consumers (
|
|
36
|
+
* `actor='agent'`. Downstream consumers (activity-scan skill) already
|
|
37
37
|
* filter by `actor='user'`, so an agent row has no consumer; leaving it
|
|
38
38
|
* out of the table entirely prevents unbounded growth from the agent's
|
|
39
39
|
* own write traffic.
|
|
@@ -21,7 +21,7 @@ const DIFF_PREVIEW_CAP = 2000;
|
|
|
21
21
|
* **Agent-write suppression**: `AgentWriteTracker` pre-marks files the
|
|
22
22
|
* agent writes via `/api/context/*`. When a watcher fires for one of
|
|
23
23
|
* those, we silently drop the observation instead of recording it with
|
|
24
|
-
* `actor='agent'`. Downstream consumers (
|
|
24
|
+
* `actor='agent'`. Downstream consumers (activity-scan skill) already
|
|
25
25
|
* filter by `actor='user'`, so an agent row has no consumer; leaving it
|
|
26
26
|
* out of the table entirely prevents unbounded growth from the agent's
|
|
27
27
|
* own write traffic.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AgentWriteTracker — short-lived, in-memory record of paths the agent is
|
|
3
3
|
* currently writing via the daemon API. Observers consult it to classify
|
|
4
4
|
* file-change events as `actor='agent'` vs `actor='user'`, which gives the
|
|
5
|
-
*
|
|
5
|
+
* activity-scan dispatcher a way to ignore its own writes.
|
|
6
6
|
*
|
|
7
7
|
* Two marking modes:
|
|
8
8
|
* 1. **Content-hash mode** — caller passes the exact bytes they wrote.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* polled sources (Notion, Calendar) whose observation lag is measured in
|
|
27
27
|
* minutes, the caller MUST pass `opts.ttlMs` large enough to outlive the
|
|
28
28
|
* poll cadence — otherwise every agent write is seen as a fresh user
|
|
29
|
-
* edit and the
|
|
29
|
+
* edit and the activity_scan can loop on its own output.
|
|
30
30
|
*/
|
|
31
31
|
export declare class AgentWriteTracker {
|
|
32
32
|
private readonly ttlMs;
|
|
@@ -34,7 +34,7 @@ export declare class AgentWriteTracker {
|
|
|
34
34
|
/**
|
|
35
35
|
* Parallel commit-tracking map keyed by `<repoPath>::<sha-lower>`. Used by
|
|
36
36
|
* `GitWatcher` to flip observations of agent-originated commits from
|
|
37
|
-
* `actor='user'` / `'unknown'` to `actor='agent'` so the
|
|
37
|
+
* `actor='user'` / `'unknown'` to `actor='agent'` so the activity_scan
|
|
38
38
|
* pending-floor does not count the daemon's own commits as user
|
|
39
39
|
* activity (C1).
|
|
40
40
|
*/
|
|
@@ -64,7 +64,7 @@ export declare class AgentWriteTracker {
|
|
|
64
64
|
* Register a git SHA the daemon just committed in `repoPath`. The next
|
|
65
65
|
* `GitWatcher` observation of that SHA is flipped from `actor='user'` /
|
|
66
66
|
* `'unknown'` to `actor='agent'` via `isAgentCommit`, which keeps the
|
|
67
|
-
*
|
|
67
|
+
* activity_scan pending-floor from counting the daemon's own commits as
|
|
68
68
|
* user activity (the loop bug described in C1).
|
|
69
69
|
*
|
|
70
70
|
* Production callers pass the full 40-char SHA from `git rev-parse HEAD`.
|
|
@@ -13,7 +13,7 @@ const DEFAULT_COMMIT_TTL_MS = 15 * 60_000;
|
|
|
13
13
|
* AgentWriteTracker — short-lived, in-memory record of paths the agent is
|
|
14
14
|
* currently writing via the daemon API. Observers consult it to classify
|
|
15
15
|
* file-change events as `actor='agent'` vs `actor='user'`, which gives the
|
|
16
|
-
*
|
|
16
|
+
* activity-scan dispatcher a way to ignore its own writes.
|
|
17
17
|
*
|
|
18
18
|
* Two marking modes:
|
|
19
19
|
* 1. **Content-hash mode** — caller passes the exact bytes they wrote.
|
|
@@ -37,7 +37,7 @@ const DEFAULT_COMMIT_TTL_MS = 15 * 60_000;
|
|
|
37
37
|
* polled sources (Notion, Calendar) whose observation lag is measured in
|
|
38
38
|
* minutes, the caller MUST pass `opts.ttlMs` large enough to outlive the
|
|
39
39
|
* poll cadence — otherwise every agent write is seen as a fresh user
|
|
40
|
-
* edit and the
|
|
40
|
+
* edit and the activity_scan can loop on its own output.
|
|
41
41
|
*/
|
|
42
42
|
export class AgentWriteTracker {
|
|
43
43
|
ttlMs;
|
|
@@ -45,7 +45,7 @@ export class AgentWriteTracker {
|
|
|
45
45
|
/**
|
|
46
46
|
* Parallel commit-tracking map keyed by `<repoPath>::<sha-lower>`. Used by
|
|
47
47
|
* `GitWatcher` to flip observations of agent-originated commits from
|
|
48
|
-
* `actor='user'` / `'unknown'` to `actor='agent'` so the
|
|
48
|
+
* `actor='user'` / `'unknown'` to `actor='agent'` so the activity_scan
|
|
49
49
|
* pending-floor does not count the daemon's own commits as user
|
|
50
50
|
* activity (C1).
|
|
51
51
|
*/
|
|
@@ -114,7 +114,7 @@ export class AgentWriteTracker {
|
|
|
114
114
|
* Register a git SHA the daemon just committed in `repoPath`. The next
|
|
115
115
|
* `GitWatcher` observation of that SHA is flipped from `actor='user'` /
|
|
116
116
|
* `'unknown'` to `actor='agent'` via `isAgentCommit`, which keeps the
|
|
117
|
-
*
|
|
117
|
+
* activity_scan pending-floor from counting the daemon's own commits as
|
|
118
118
|
* user activity (the loop bug described in C1).
|
|
119
119
|
*
|
|
120
120
|
* Production callers pass the full 40-char SHA from `git rev-parse HEAD`.
|
package/dist/safety/audit.d.ts
CHANGED
|
@@ -58,6 +58,20 @@ export declare class AuditLogger implements IAuditLogger {
|
|
|
58
58
|
trigger: "reactive" | "autonomous";
|
|
59
59
|
backend?: AgentResult["backendId"];
|
|
60
60
|
costSource?: AgentResult["costSource"];
|
|
61
|
+
/**
|
|
62
|
+
* Terminal result override (default `"success"`). `"partial"` records a
|
|
63
|
+
* session that ended cleanly but failed a post-run outcome check
|
|
64
|
+
* (RESEARCH_CLUSTER_COST_FIX_PLAN F5). `"partial"` is a legal
|
|
65
|
+
* `agent_actions.result` value (schema CHECK). Hard errors use
|
|
66
|
+
* `logError` instead.
|
|
67
|
+
*/
|
|
68
|
+
result?: "success" | "partial";
|
|
69
|
+
/**
|
|
70
|
+
* Outcome-failure marker written to `agent_actions.error` when paired
|
|
71
|
+
* with `result:"partial"` (F5: `'journal_write_missing'`). Omitted on a
|
|
72
|
+
* plain success so the column stays NULL (legacy row shape).
|
|
73
|
+
*/
|
|
74
|
+
error?: string;
|
|
61
75
|
contextUpdated?: boolean;
|
|
62
76
|
advisorCallCount?: number;
|
|
63
77
|
/**
|
|
@@ -113,7 +127,15 @@ export declare class AuditLogger implements IAuditLogger {
|
|
|
113
127
|
modelId?: string;
|
|
114
128
|
}): number;
|
|
115
129
|
private findInProgressRowId;
|
|
116
|
-
logSkip(event: Event, reason: string, trigger: "reactive" | "autonomous"
|
|
130
|
+
logSkip(event: Event, reason: string, trigger: "reactive" | "autonomous",
|
|
131
|
+
/**
|
|
132
|
+
* Optional structured context persisted to the `detail` JSON column.
|
|
133
|
+
* Used by the N2 spawn gates (`detail.spawnGate` — per-backend
|
|
134
|
+
* offline/auth verdicts) and the N3 pre-pass plan-assembly drop rows
|
|
135
|
+
* (`detail.prePass.skipReason`) so skip telemetry is queryable
|
|
136
|
+
* without parsing the `error` string. PREPASS_COST_REDUCTION_PLAN.md.
|
|
137
|
+
*/
|
|
138
|
+
detail?: Record<string, unknown>): void;
|
|
117
139
|
/**
|
|
118
140
|
* Chat-attachments Phase 1 — log a successful upload (inbound or outbound)
|
|
119
141
|
* to `agent_actions`. Shares the `agent_actions` table with agent-turn
|
|
@@ -143,10 +165,12 @@ export declare class AuditLogger implements IAuditLogger {
|
|
|
143
165
|
* hit `max_budget_usd` because the row was written with no
|
|
144
166
|
* timing/backend/model info.
|
|
145
167
|
*
|
|
146
|
-
* Tokens / cost / num_turns are
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
168
|
+
* Tokens / cost / num_turns are passed ONLY when the caller holds a
|
|
169
|
+
* real recovered spend figure (PREPASS_COST_REDUCTION_PLAN.md N1 —
|
|
170
|
+
* post-hoc budget kills, partial stream aborts). Callers without one
|
|
171
|
+
* must omit them: a fabricated value here would corrupt the cost
|
|
172
|
+
* dials. duration_ms + backend + failure shape are always
|
|
173
|
+
* recoverable and that's the baseline record.
|
|
150
174
|
*/
|
|
151
175
|
context?: {
|
|
152
176
|
durationMs?: number;
|
|
@@ -154,6 +178,20 @@ export declare class AuditLogger implements IAuditLogger {
|
|
|
154
178
|
modelId?: string;
|
|
155
179
|
failureKind?: string;
|
|
156
180
|
failureCode?: string;
|
|
181
|
+
/**
|
|
182
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — recovered spend for a failed
|
|
183
|
+
* turn the provider already billed. Pass ONLY a real recovered
|
|
184
|
+
* figure (BackendQuotaSpend / partial-usage snapshot), never a
|
|
185
|
+
* guess — the historical contract that failure rows carry no
|
|
186
|
+
* fabricated cost still holds for callers without one.
|
|
187
|
+
*/
|
|
188
|
+
costUsd?: number;
|
|
189
|
+
costSource?: string;
|
|
190
|
+
tokensInput?: number;
|
|
191
|
+
tokensOutput?: number;
|
|
192
|
+
tokensCacheCreation?: number;
|
|
193
|
+
tokensCacheRead?: number;
|
|
194
|
+
numTurns?: number;
|
|
157
195
|
/**
|
|
158
196
|
* Pre-pass fan-out failure block. Mirrors the `prePass` payload on
|
|
159
197
|
* `logAction` so `MetricsCollector.collectPrePassMetrics` can see
|
package/dist/safety/audit.js
CHANGED
|
@@ -52,7 +52,7 @@ export class AuditLogger {
|
|
|
52
52
|
this.agentIdResolver = resolver;
|
|
53
53
|
}
|
|
54
54
|
logAction(params) {
|
|
55
|
-
const { event, model, costUsd, usage, modelUsage, durationMs, numTurns, trigger, backend, costSource, contextUpdated = false, advisorCallCount = 0, dmFreshness, prePass, dailyWrite, } = params;
|
|
55
|
+
const { event, model, costUsd, usage, modelUsage, durationMs, numTurns, trigger, backend, costSource, result = "success", error, contextUpdated = false, advisorCallCount = 0, dmFreshness, prePass, dailyWrite, } = params;
|
|
56
56
|
try {
|
|
57
57
|
const modelUsageJson = Object.keys(modelUsage).length > 0
|
|
58
58
|
? JSON.stringify(modelUsage)
|
|
@@ -81,7 +81,7 @@ export class AuditLogger {
|
|
|
81
81
|
usage.outputTokens,
|
|
82
82
|
durationMs,
|
|
83
83
|
numTurns,
|
|
84
|
-
|
|
84
|
+
result,
|
|
85
85
|
];
|
|
86
86
|
if (this.hasCacheCreationTokensColumn) {
|
|
87
87
|
columns.splice(7, 0, "cache_creation_tokens");
|
|
@@ -137,6 +137,16 @@ export class AuditLogger {
|
|
|
137
137
|
columns.splice(columns.length - 2, 0, "agent_id");
|
|
138
138
|
values.splice(values.length, 0, resolvedAgentId);
|
|
139
139
|
}
|
|
140
|
+
// RESEARCH_CLUSTER_COST_FIX_PLAN F5 — persist the outcome-failure
|
|
141
|
+
// marker for a run downgraded to `result:"partial"`. Spliced like
|
|
142
|
+
// every other optional column (at `columns.length - 2`, appended to
|
|
143
|
+
// `values`) so the lockstep invariant the in_progress UPSERT loop
|
|
144
|
+
// relies on (`columns.length - 2 === values.length`) holds. A plain
|
|
145
|
+
// success omits it, leaving the column NULL (legacy row shape).
|
|
146
|
+
if (error !== undefined) {
|
|
147
|
+
columns.splice(columns.length - 2, 0, "error");
|
|
148
|
+
values.splice(values.length, 0, error);
|
|
149
|
+
}
|
|
140
150
|
const detailPayload = {};
|
|
141
151
|
// STAGE-C-DM-FRESHNESS-PLAN §Task 4 — persist DM freshness telemetry
|
|
142
152
|
// into `detail`. Inserted only when supplied so non-DM rows stay
|
|
@@ -330,7 +340,15 @@ export class AuditLogger {
|
|
|
330
340
|
return null;
|
|
331
341
|
}
|
|
332
342
|
}
|
|
333
|
-
logSkip(event, reason, trigger
|
|
343
|
+
logSkip(event, reason, trigger,
|
|
344
|
+
/**
|
|
345
|
+
* Optional structured context persisted to the `detail` JSON column.
|
|
346
|
+
* Used by the N2 spawn gates (`detail.spawnGate` — per-backend
|
|
347
|
+
* offline/auth verdicts) and the N3 pre-pass plan-assembly drop rows
|
|
348
|
+
* (`detail.prePass.skipReason`) so skip telemetry is queryable
|
|
349
|
+
* without parsing the `error` string. PREPASS_COST_REDUCTION_PLAN.md.
|
|
350
|
+
*/
|
|
351
|
+
detail) {
|
|
334
352
|
try {
|
|
335
353
|
// AGENT_DEFINITIONS_DESIGN.md §8.1 — stamp the owning Agent when the
|
|
336
354
|
// in-flight firing resolved to one (e.g. a review routine skipped by the
|
|
@@ -338,17 +356,28 @@ export class AuditLogger {
|
|
|
338
356
|
// skips (setup-mode / cost-cap) resolve to NULL — no execution context
|
|
339
357
|
// exists yet — which is the legacy row shape.
|
|
340
358
|
const resolvedAgentId = this.agentIdResolver?.(event) ?? null;
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
359
|
+
const columns = ["event_id", "action_type", "trigger", "result", "error"];
|
|
360
|
+
const values = [
|
|
361
|
+
event.correlationId,
|
|
362
|
+
event.type,
|
|
363
|
+
trigger,
|
|
364
|
+
"skipped",
|
|
365
|
+
reason,
|
|
366
|
+
];
|
|
367
|
+
if (resolvedAgentId !== null) {
|
|
368
|
+
columns.push("agent_id");
|
|
369
|
+
values.push(resolvedAgentId);
|
|
370
|
+
}
|
|
371
|
+
if (detail !== undefined) {
|
|
372
|
+
columns.push("detail");
|
|
373
|
+
values.push(JSON.stringify(detail));
|
|
374
|
+
}
|
|
375
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
376
|
+
const insertResult = this.db
|
|
377
|
+
.prepare(`INSERT INTO agent_actions
|
|
378
|
+
(${columns.join(", ")}, started_at)
|
|
379
|
+
VALUES (${placeholders}, datetime('now'))`)
|
|
380
|
+
.run(...values);
|
|
352
381
|
this.emitInsertedRow(Number(insertResult.lastInsertRowid), event.type);
|
|
353
382
|
}
|
|
354
383
|
catch (err) {
|
|
@@ -416,10 +445,12 @@ export class AuditLogger {
|
|
|
416
445
|
* hit `max_budget_usd` because the row was written with no
|
|
417
446
|
* timing/backend/model info.
|
|
418
447
|
*
|
|
419
|
-
* Tokens / cost / num_turns are
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
448
|
+
* Tokens / cost / num_turns are passed ONLY when the caller holds a
|
|
449
|
+
* real recovered spend figure (PREPASS_COST_REDUCTION_PLAN.md N1 —
|
|
450
|
+
* post-hoc budget kills, partial stream aborts). Callers without one
|
|
451
|
+
* must omit them: a fabricated value here would corrupt the cost
|
|
452
|
+
* dials. duration_ms + backend + failure shape are always
|
|
453
|
+
* recoverable and that's the baseline record.
|
|
423
454
|
*/
|
|
424
455
|
context) {
|
|
425
456
|
try {
|
|
@@ -464,6 +495,43 @@ export class AuditLogger {
|
|
|
464
495
|
columns.splice(columns.length - 2, 0, "backend");
|
|
465
496
|
values.splice(values.length, 0, context.backendId);
|
|
466
497
|
}
|
|
498
|
+
// PREPASS_COST_REDUCTION_PLAN.md N1 — recovered spend for a failed
|
|
499
|
+
// turn the provider already billed (post-hoc budget kill, partial
|
|
500
|
+
// stream abort, timeout-with-usage). Only callers that hold a real
|
|
501
|
+
// recovered figure pass these; the historical "no guessed values"
|
|
502
|
+
// contract still applies to everyone else.
|
|
503
|
+
if (typeof context?.costUsd === "number" && context.costUsd >= 0) {
|
|
504
|
+
columns.splice(columns.length - 2, 0, "cost_usd");
|
|
505
|
+
values.splice(values.length, 0, context.costUsd);
|
|
506
|
+
}
|
|
507
|
+
if (this.hasCostSourceColumn && context?.costSource) {
|
|
508
|
+
columns.splice(columns.length - 2, 0, "cost_source");
|
|
509
|
+
values.splice(values.length, 0, context.costSource);
|
|
510
|
+
}
|
|
511
|
+
if (typeof context?.tokensInput === "number" && context.tokensInput >= 0) {
|
|
512
|
+
columns.splice(columns.length - 2, 0, "tokens_input");
|
|
513
|
+
values.splice(values.length, 0, context.tokensInput);
|
|
514
|
+
}
|
|
515
|
+
if (typeof context?.tokensOutput === "number" && context.tokensOutput >= 0) {
|
|
516
|
+
columns.splice(columns.length - 2, 0, "tokens_output");
|
|
517
|
+
values.splice(values.length, 0, context.tokensOutput);
|
|
518
|
+
}
|
|
519
|
+
if (this.hasCacheCreationTokensColumn
|
|
520
|
+
&& typeof context?.tokensCacheCreation === "number"
|
|
521
|
+
&& context.tokensCacheCreation >= 0) {
|
|
522
|
+
columns.splice(columns.length - 2, 0, "cache_creation_tokens");
|
|
523
|
+
values.splice(values.length, 0, context.tokensCacheCreation);
|
|
524
|
+
}
|
|
525
|
+
if (this.hasCacheReadTokensColumn
|
|
526
|
+
&& typeof context?.tokensCacheRead === "number"
|
|
527
|
+
&& context.tokensCacheRead >= 0) {
|
|
528
|
+
columns.splice(columns.length - 2, 0, "cache_read_tokens");
|
|
529
|
+
values.splice(values.length, 0, context.tokensCacheRead);
|
|
530
|
+
}
|
|
531
|
+
if (typeof context?.numTurns === "number" && context.numTurns > 0) {
|
|
532
|
+
columns.splice(columns.length - 2, 0, "num_turns");
|
|
533
|
+
values.splice(values.length, 0, context.numTurns);
|
|
534
|
+
}
|
|
467
535
|
// AGENT_DEFINITIONS_DESIGN.md §8.1 — stamp the owning Agent when the
|
|
468
536
|
// in-flight firing resolved to one, so a FAILED routine's audit row is
|
|
469
537
|
// attributable to its Agent (the same resolver `logAction` uses). The
|
|
@@ -62,6 +62,12 @@ export interface AuditableRoute {
|
|
|
62
62
|
path: string;
|
|
63
63
|
}
|
|
64
64
|
export declare function auditRiskClassifications(routes: ReadonlyArray<AuditableRoute>): AuditableRoute[];
|
|
65
|
+
/** Extract the path portion of an `API_RISK` key: `"METHOD /path"` → `/path`,
|
|
66
|
+
* or a bare `"/path"` path-only key unchanged. Ranking must compare the path
|
|
67
|
+
* segment alone — the leading `"METHOD "` token (3–6 chars) would otherwise
|
|
68
|
+
* inflate a shorter, less-specific method-keyed prefix above a longer
|
|
69
|
+
* path-only one. */
|
|
70
|
+
export declare function keyPathOf(key: string): string;
|
|
65
71
|
/**
|
|
66
72
|
* Return every `/api/*` path that `API_RISK` classifies as
|
|
67
73
|
* `RiskTier.ReadSensitive` for GET requests, with `{*}` placeholders
|
|
@@ -285,6 +285,30 @@ const API_RISK = {
|
|
|
285
285
|
"GET /api/browser-task/{*}/screenshots/{*}": RiskTier.ReadSensitive,
|
|
286
286
|
"POST /api/browser-task/{*}/clarify": RiskTier.Autonomous,
|
|
287
287
|
"POST /api/browser-task/{*}/cancel": RiskTier.Autonomous,
|
|
288
|
+
// ── Background Task (BACKGROUND_TASK_RUNNER_DESIGN.md §7) ──
|
|
289
|
+
// Generic detached long-task surface, cloned from browser-task and
|
|
290
|
+
// classified to the same posture. The only production callers are the
|
|
291
|
+
// DM-agent `background-task` / `background-task-reply` skills and the
|
|
292
|
+
// morning-briefing session, whose curl shim carries `x-read-token`
|
|
293
|
+
// (sufficient for ReadSensitive, never enough for Approve). The same
|
|
294
|
+
// loopback / sec-fetch / channel-attestation defenses as browser-task
|
|
295
|
+
// apply, and every dispatched task surfaces to the user via DM — no
|
|
296
|
+
// covert dispatch path.
|
|
297
|
+
// - POST (spawn) + clarify + cancel are Autonomous, matching the
|
|
298
|
+
// sibling agent-driven write paths (design §7: "same posture as
|
|
299
|
+
// /api/browser-task").
|
|
300
|
+
// - The list + detail reads stay ReadSensitive (NOT Autonomous):
|
|
301
|
+
// the artifact (`report` / `brief` / `draft` / `significance`)
|
|
302
|
+
// carries personal research / audit content, exactly the reason
|
|
303
|
+
// browser-task's reads are ReadSensitive. Reachable from the agent
|
|
304
|
+
// sessions that need them because their curl carries the read
|
|
305
|
+
// token (cf. GET /api/observations, also ReadSensitive, which the
|
|
306
|
+
// briefing already calls).
|
|
307
|
+
"POST /api/background-task": RiskTier.Autonomous,
|
|
308
|
+
"GET /api/background-task": RiskTier.ReadSensitive,
|
|
309
|
+
"GET /api/background-task/{*}": RiskTier.ReadSensitive,
|
|
310
|
+
"POST /api/background-task/{*}/clarify": RiskTier.Autonomous,
|
|
311
|
+
"POST /api/background-task/{*}/cancel": RiskTier.Autonomous,
|
|
288
312
|
"/api/setup": RiskTier.Approve,
|
|
289
313
|
"POST /api/setup/redetect-browsers": RiskTier.Approve,
|
|
290
314
|
// Management Mode Phase 2 — migration endpoint. Redundant with the
|
|
@@ -457,6 +481,16 @@ const API_RISK = {
|
|
|
457
481
|
// (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5). Summarises cap utilisation
|
|
458
482
|
// only — lesson prose was redaction-scrubbed at capture — so Autonomous.
|
|
459
483
|
"GET /api/feedback/lessons": RiskTier.Autonomous,
|
|
484
|
+
// Self-tuning verdict endpoint (SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.4).
|
|
485
|
+
// Autonomous + (Phase 3) mandatory owner DM on apply — the exact pattern
|
|
486
|
+
// that replaced the abolished Notify tier; requiring the Approve bearer
|
|
487
|
+
// would put a human back in every loop iteration and defeat the design.
|
|
488
|
+
// Safety is carried by code, not tier: verdicts may only reference
|
|
489
|
+
// daemon-generated single-use recommendation ids from the current cycle,
|
|
490
|
+
// the handler is idempotent per id, and Phase 2 never actuates (shadow).
|
|
491
|
+
"POST /api/tuning/verdicts": RiskTier.Autonomous,
|
|
492
|
+
// Pending-cycle read — knob names + telemetry counts only, no user prose.
|
|
493
|
+
"GET /api/tuning/pending": RiskTier.Autonomous,
|
|
460
494
|
// ── Notification ──
|
|
461
495
|
"/api/notify": RiskTier.Autonomous,
|
|
462
496
|
// ── External Service Proxy ──
|
|
@@ -668,7 +702,7 @@ const API_RISK = {
|
|
|
668
702
|
"PATCH /api/delegated-sync/active-hours": RiskTier.Approve,
|
|
669
703
|
"POST /api/delegated-sync/cadences/": RiskTier.Approve,
|
|
670
704
|
// INTEGRATION-DRIFT-DETECTION-PLAN.md §6.0 — drift-detection chokepoint.
|
|
671
|
-
// Autonomous: the agent's
|
|
705
|
+
// Autonomous: the agent's activity_scan delegated variant POSTs the
|
|
672
706
|
// result of its connector fetch to compute a structural diff. Defense
|
|
673
707
|
// layers (window-key allowlist + per-call audit row) live inside the
|
|
674
708
|
// handler. Daemon-internal callers (CalendarPoller, DelegatedSyncWorker)
|
|
@@ -893,9 +927,14 @@ export function findExplicitRiskClassification(method, path) {
|
|
|
893
927
|
/* c8 ignore stop */
|
|
894
928
|
if (keyMethod && keyMethod !== method)
|
|
895
929
|
return false;
|
|
896
|
-
return
|
|
930
|
+
return pathPrefixMatches(keyPath, path);
|
|
897
931
|
})
|
|
898
|
-
|
|
932
|
+
// Rank by matched path-prefix length so the most-specific prefix wins.
|
|
933
|
+
// Must compare the path segment, NOT the raw key: the `"METHOD "` token
|
|
934
|
+
// would otherwise lift a shorter, less-specific method-keyed prefix
|
|
935
|
+
// (e.g. `DELETE /api/git`) above a longer path-only one (`/api/github`),
|
|
936
|
+
// silently downgrading the tier. Mirrors the step-3 pattern tiebreaker.
|
|
937
|
+
.sort((a, b) => keyPathOf(b[0]).length - keyPathOf(a[0]).length);
|
|
899
938
|
if (candidates.length > 0)
|
|
900
939
|
return candidates[0][1];
|
|
901
940
|
return null;
|
|
@@ -975,16 +1014,41 @@ function matchesPattern(keyPath, actualPath) {
|
|
|
975
1014
|
}
|
|
976
1015
|
return true;
|
|
977
1016
|
}
|
|
978
|
-
/**
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
|
|
1017
|
+
/** Extract the path portion of an `API_RISK` key: `"METHOD /path"` → `/path`,
|
|
1018
|
+
* or a bare `"/path"` path-only key unchanged. Ranking must compare the path
|
|
1019
|
+
* segment alone — the leading `"METHOD "` token (3–6 chars) would otherwise
|
|
1020
|
+
* inflate a shorter, less-specific method-keyed prefix above a longer
|
|
1021
|
+
* path-only one. */
|
|
1022
|
+
export function keyPathOf(key) {
|
|
1023
|
+
return key.includes(" ") ? key.split(" ")[1] : key;
|
|
1024
|
+
}
|
|
1025
|
+
/** Segment-aware prefix test for step-4 (non-`{*}`) keys. A raw
|
|
1026
|
+
* `path.startsWith(keyPath)` matches a *string* prefix, so `/api/git`
|
|
1027
|
+
* would spuriously match the unrelated sibling `/api/git-accounts` (and,
|
|
1028
|
+
* worse, an unclassified future `/api/git-webhook` — silently inheriting
|
|
1029
|
+
* the sibling's tier instead of failing closed to Approve). A key that
|
|
1030
|
+
* ends in `/` is an explicit subtree catch-all, so a raw `startsWith` is
|
|
1031
|
+
* already the boundary; otherwise the match must be a strict
|
|
1032
|
+
* `/`-delimited descendant. Exact `path === keyPath` is pre-empted by the
|
|
1033
|
+
* step-1/step-2 exact lookups, so step 4 only ever matches descendants.
|
|
1034
|
+
* Mirrors the segment discipline of `matchesPattern` (step 3). */
|
|
1035
|
+
function pathPrefixMatches(keyPath, path) {
|
|
1036
|
+
if (keyPath.endsWith("/"))
|
|
1037
|
+
return path.startsWith(keyPath);
|
|
1038
|
+
return path.startsWith(keyPath + "/");
|
|
1039
|
+
}
|
|
1040
|
+
/** Count characters of the path prefix before the first `{*}` (or the whole
|
|
1041
|
+
* path length if none) — used to rank pattern candidates by specificity.
|
|
1042
|
+
* Defensive against future overlapping `{*}` keys: today no two `{*}` entries
|
|
1043
|
+
* in `API_RISK` match the same `(method, path)` pair, so the sort comparator
|
|
1044
|
+
* never invokes this helper. The function exists so the first overlapping
|
|
1045
|
+
* pair added picks the more specific entry instead of relying on iteration
|
|
1046
|
+
* order. */
|
|
984
1047
|
/* c8 ignore start */
|
|
985
1048
|
function literalPrefixLength(key) {
|
|
986
|
-
const
|
|
987
|
-
|
|
1049
|
+
const keyPath = keyPathOf(key);
|
|
1050
|
+
const idx = keyPath.indexOf("{*}");
|
|
1051
|
+
return idx < 0 ? keyPath.length : idx;
|
|
988
1052
|
}
|
|
989
1053
|
/* c8 ignore stop */
|
|
990
1054
|
/** Strip a trailing `{*}` placeholder from `path` so callers can
|