@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
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { formatSqliteDatetime, getAgentDayBoundsUtc, getAgentDayDateStr, getAgentDayProgressMinutes, nowInTimezone, parseSqliteUtcMs, } from "@aitne/shared";
|
|
13
13
|
import { hasActionInWindow } from "../core/schedule-maintenance.js";
|
|
14
|
+
import { getAgentEnabled, getRuntimeWindow } from "../db/agents-store.js";
|
|
15
|
+
import { resolveActivityScanCadence } from "../core/agents/activity-scan-cadence.js";
|
|
14
16
|
/**
|
|
15
17
|
* Days of week on which the boot-time catchup will fire an unrun
|
|
16
18
|
* `routine.weekly_review`. Friday is the canonical slot; Saturday and
|
|
@@ -64,11 +66,10 @@ export function getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndU
|
|
|
64
66
|
routines.push("weekly_review");
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
|
-
// Monthly catchup is gated by the same
|
|
68
|
-
//
|
|
69
|
-
// the Mirror+Prune redesign; operators opt in
|
|
70
|
-
|
|
71
|
-
if (config.monthlyReviewEnabled &&
|
|
69
|
+
// Monthly catchup is gated by the same switch as the scheduler cron:
|
|
70
|
+
// the monthly-review AGENT row's `enabled` (default OFF pre-release until
|
|
71
|
+
// the Mirror+Prune redesign; operators opt in from /agents/monthly-review).
|
|
72
|
+
if (getAgentEnabled(db, "monthly-review", false) &&
|
|
72
73
|
tomorrowLocal.day === 1 &&
|
|
73
74
|
!hasActionInWindow(db, "routine.monthly_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
74
75
|
routines.push("monthly_review");
|
|
@@ -77,11 +78,11 @@ export function getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndU
|
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
80
|
* Decide whether the boot sequence should immediately fire one
|
|
80
|
-
* catch-up `routine.
|
|
81
|
+
* catch-up `routine.activity_scan` (because the cron callback never ran
|
|
81
82
|
* for the current slot — typically because the host was asleep / the
|
|
82
83
|
* daemon was stopped during the slot window).
|
|
83
84
|
*
|
|
84
|
-
* Slot math mirrors `
|
|
85
|
+
* Slot math mirrors `shouldFireActivityScanTickAt` in `scheduler.ts` so the
|
|
85
86
|
* catch-up always lands on the same slot the cron would have fired at.
|
|
86
87
|
*
|
|
87
88
|
* **Wrap-around active hours are NOT supported.** The active-hours
|
|
@@ -101,31 +102,35 @@ export function getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndU
|
|
|
101
102
|
* config-write time, or (b) split the window into two non-wrap
|
|
102
103
|
* ranges in the same call site that consumes them.
|
|
103
104
|
*/
|
|
104
|
-
export function
|
|
105
|
-
|
|
105
|
+
export function shouldCatchUpActivityScan(db, config, now) {
|
|
106
|
+
// `agents.enabled` on the activity-scan row is the single on/off switch
|
|
107
|
+
// (AGENTS_HUB_REDESIGN_PLAN.md §2); cadence comes from the row's
|
|
108
|
+
// runtime_window with the legacy config keys as fallback.
|
|
109
|
+
if (!getAgentEnabled(db, "activity-scan", true)) {
|
|
106
110
|
return false;
|
|
107
111
|
}
|
|
112
|
+
const cadence = resolveActivityScanCadence(getRuntimeWindow(db, "activity-scan"), config);
|
|
108
113
|
const tz = config.timezone || undefined;
|
|
109
114
|
const local = nowInTimezone(tz, now);
|
|
110
|
-
if (local.hours <
|
|
111
|
-
local.hours >=
|
|
115
|
+
if (local.hours < cadence.activeStartHour ||
|
|
116
|
+
local.hours >= cadence.activeEndHour ||
|
|
112
117
|
local.hours === config.dayBoundaryHour) {
|
|
113
118
|
return false;
|
|
114
119
|
}
|
|
115
|
-
// Slot anchors to `activeStartHour`, mirroring
|
|
120
|
+
// Slot anchors to `activeStartHour`, mirroring shouldFireActivityScanTickAt
|
|
116
121
|
// in scheduler.ts so the catch-up function picks the same slot the
|
|
117
122
|
// cron callback would have fired at. The earlier branch already
|
|
118
123
|
// returned false when local.hours < activeStartHour, so the offset is
|
|
119
124
|
// always non-negative here.
|
|
120
|
-
const anchorMinutes =
|
|
125
|
+
const anchorMinutes = cadence.activeStartHour * 60;
|
|
121
126
|
const offsetFromAnchor = local.hours * 60 + local.minutes - anchorMinutes;
|
|
122
|
-
const slotOffsetFromAnchor = Math.floor(offsetFromAnchor /
|
|
123
|
-
|
|
127
|
+
const slotOffsetFromAnchor = Math.floor(offsetFromAnchor / cadence.intervalMinutes) *
|
|
128
|
+
cadence.intervalMinutes;
|
|
124
129
|
const slotMinutesSinceMidnight = anchorMinutes + slotOffsetFromAnchor;
|
|
125
130
|
const dayStartUtc = getAgentDayBoundsUtc(tz, 0, now).start;
|
|
126
131
|
const slotStartMs = parseSqliteUtcMs(dayStartUtc) + slotMinutesSinceMidnight * 60 * 1000;
|
|
127
132
|
const slotStartUtc = formatSqliteDatetime(new Date(slotStartMs));
|
|
128
|
-
return !hasActionInWindow(db, "routine.
|
|
133
|
+
return !hasActionInWindow(db, "routine.activity_scan", slotStartUtc, formatSqliteDatetime(now));
|
|
129
134
|
}
|
|
130
135
|
export function getProgressMinutesForHour(hour, dayBoundaryHour) {
|
|
131
136
|
const scheduledMinutes = hour * 60;
|
|
@@ -147,7 +152,7 @@ export function hasFreshAgentDayTodayMd(todayMdPath, timezone, dayBoundaryHour,
|
|
|
147
152
|
}
|
|
148
153
|
/**
|
|
149
154
|
* Did `routine.morning_routine` complete successfully within the current
|
|
150
|
-
* agent-day window? Used by the pre-routine gate that fronts
|
|
155
|
+
* agent-day window? Used by the pre-routine gate that fronts activity_scan
|
|
151
156
|
* and the review routines (evening / weekly / monthly) so they refuse to
|
|
152
157
|
* run before the day has been properly opened.
|
|
153
158
|
*
|
|
@@ -231,10 +236,10 @@ export function readMorningRoutineStallThresholdMinutes(db) {
|
|
|
231
236
|
* without producing a successful `agent_actions` row. Returns the
|
|
232
237
|
* offending row's metadata if stalled, null when the system is healthy.
|
|
233
238
|
*
|
|
234
|
-
* Pairs with `queueMorningRoutineWake` + the
|
|
239
|
+
* Pairs with `queueMorningRoutineWake` + the activity-scan pre-routine
|
|
235
240
|
* gate. The dedup that keeps `queueMorningRoutineWake` from re-inserting
|
|
236
241
|
* means a stuck wake row leaves the system in a silent freeze — the gate
|
|
237
|
-
* skips `routine.
|
|
242
|
+
* skips `routine.activity_scan`, `routine.evening_review`, etc. forever
|
|
238
243
|
* without surfacing to the user. This helper is the externally visible
|
|
239
244
|
* signal the watchdog uses to break the silence.
|
|
240
245
|
*
|
|
@@ -277,6 +282,154 @@ export function getStalledMorningRoutineWake(db, agentDayConfig, thresholdMinute
|
|
|
277
282
|
ageMinutes,
|
|
278
283
|
};
|
|
279
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Audit-row action types that count as "a morning-routine attempt started"
|
|
287
|
+
* for the missed-fire predicate below. The parent row
|
|
288
|
+
* (`routine.morning_routine`) is written when the pipeline finishes
|
|
289
|
+
* (success or failure); the Stage A row
|
|
290
|
+
* (`routine.morning_routine_today`) is written `in_progress` the moment
|
|
291
|
+
* the agent execution starts, so a hung Stage A is still visible as an
|
|
292
|
+
* attempt. The fetch-window pre-pass is deliberately NOT included — it
|
|
293
|
+
* also runs for hourly/scheduled flows, and counting it would let an
|
|
294
|
+
* unrelated pre-pass mask a dead morning pipeline.
|
|
295
|
+
*/
|
|
296
|
+
export const MORNING_ROUTINE_ATTEMPT_ACTION_TYPES = [
|
|
297
|
+
"routine.morning_routine",
|
|
298
|
+
"routine.morning_routine_today",
|
|
299
|
+
];
|
|
300
|
+
/**
|
|
301
|
+
* Grace period after the agent-day boundary before the missed-fire
|
|
302
|
+
* self-heal may conclude the 04:00 cron tick was swallowed. Wide enough
|
|
303
|
+
* that the normal cron → queueMorningRoutineWake → ScheduleWatcher claim
|
|
304
|
+
* → Stage A start chain has long since produced either a wake row or an
|
|
305
|
+
* attempt row; short enough that a swallowed tick costs at most ~25 min
|
|
306
|
+
* (grace + one self-heal interval) once the machine is awake.
|
|
307
|
+
*/
|
|
308
|
+
export const MORNING_MISSED_FIRE_GRACE_MINUTES = 15;
|
|
309
|
+
/**
|
|
310
|
+
* Epoch ms of the most recent morning-routine attempt (any result,
|
|
311
|
+
* including `in_progress`) started within the current agent-day, or null
|
|
312
|
+
* when nothing has been attempted yet. Pure read.
|
|
313
|
+
*/
|
|
314
|
+
export function getLatestMorningAttemptStartMs(db, agentDayConfig, now) {
|
|
315
|
+
const { start } = getAgentDayBoundsUtc(agentDayConfig.timezone, agentDayConfig.dayBoundaryHour, now);
|
|
316
|
+
const row = db
|
|
317
|
+
.prepare(`SELECT MAX(started_at) AS latest
|
|
318
|
+
FROM agent_actions
|
|
319
|
+
WHERE action_type IN (${MORNING_ROUTINE_ATTEMPT_ACTION_TYPES.map(() => "?").join(",")})
|
|
320
|
+
AND started_at >= ?`)
|
|
321
|
+
.get(...MORNING_ROUTINE_ATTEMPT_ACTION_TYPES, start);
|
|
322
|
+
if (!row.latest)
|
|
323
|
+
return null;
|
|
324
|
+
return parseSqliteUtcMs(row.latest);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Detect the hung-execution variant of the morning-routine silent stall:
|
|
328
|
+
* a wake row claimed to `running` at least `thresholdMinutes` ago with
|
|
329
|
+
* still no `routine.morning_routine` success for the current agent-day
|
|
330
|
+
* (machine slept mid-run and the backend stream died on the network
|
|
331
|
+
* change, the SDK call wedged, or the dispatch event was lost).
|
|
332
|
+
*
|
|
333
|
+
* `queueMorningRoutineWake`'s dedup treats a `running` row as "already
|
|
334
|
+
* in flight" and merges into it forever, so without recovery the day
|
|
335
|
+
* stays frozen until a daemon restart. The caller (scheduler self-heal
|
|
336
|
+
* tick) flips the returned row back to `pending` so the ScheduleWatcher
|
|
337
|
+
* re-claims it; the today-write-lock's wall-clock TTL guarantees a hung
|
|
338
|
+
* original cannot hold the lock against the re-run indefinitely.
|
|
339
|
+
*
|
|
340
|
+
* Staleness is measured from `task_context.claimedAt`, stamped by the
|
|
341
|
+
* ScheduleWatcher at claim time. That is the only signal that survives
|
|
342
|
+
* every confounder: `created_at` predates sleeps that delay the claim,
|
|
343
|
+
* `scheduled_for` is bumped by dedup merges, and attempt audit rows are
|
|
344
|
+
* windowed to the current agent-day (a run that started before the
|
|
345
|
+
* 04:00 boundary and hung across it has no attempt row "today").
|
|
346
|
+
* Invariants this leans on:
|
|
347
|
+
* - boot recovery (`recoverOrphanedRunningSchedules`) clears every
|
|
348
|
+
* `running` row at daemon start, so a live `running` row was always
|
|
349
|
+
* claimed by the current process — which always stamps;
|
|
350
|
+
* - `queueMorningRoutineWake`'s dedup-merge preserves unknown
|
|
351
|
+
* task_context keys, so a 04:00 cron merge cannot strip the stamp.
|
|
352
|
+
* A row without a readable stamp (stamp UPDATE failed, malformed JSON)
|
|
353
|
+
* is left to the alert-only watchdog rather than guessed at.
|
|
354
|
+
*
|
|
355
|
+
* Pure read — the caller owns the UPDATE.
|
|
356
|
+
*/
|
|
357
|
+
export function getRecoverableStalledMorningWake(db, agentDayConfig, thresholdMinutes, now) {
|
|
358
|
+
const reference = now ?? new Date();
|
|
359
|
+
if (morningRoutineRanToday(db, agentDayConfig, reference)) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const row = db
|
|
363
|
+
.prepare(`SELECT id, task_context
|
|
364
|
+
FROM agent_schedule
|
|
365
|
+
WHERE task_type = 'wake'
|
|
366
|
+
AND status = 'running'
|
|
367
|
+
AND json_extract(task_context, '$.routine') = 'morning_routine'
|
|
368
|
+
ORDER BY created_at ASC
|
|
369
|
+
LIMIT 1`)
|
|
370
|
+
.get();
|
|
371
|
+
if (!row)
|
|
372
|
+
return null;
|
|
373
|
+
let claimedAt;
|
|
374
|
+
try {
|
|
375
|
+
// The WHERE's json_extract guarantees task_context is non-null and
|
|
376
|
+
// valid to SQLite's JSON parser — but SQLite ≥3.42 accepts JSON5,
|
|
377
|
+
// which JSON.parse rejects, so the parse can still throw.
|
|
378
|
+
claimedAt = JSON.parse(row.task_context).claimedAt;
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
if (typeof claimedAt !== "string")
|
|
384
|
+
return null;
|
|
385
|
+
const claimedMs = parseSqliteUtcMs(claimedAt);
|
|
386
|
+
if (!Number.isFinite(claimedMs))
|
|
387
|
+
return null;
|
|
388
|
+
const claimedAgeMinutes = Math.floor((reference.getTime() - claimedMs) / 60_000);
|
|
389
|
+
if (claimedAgeMinutes < thresholdMinutes)
|
|
390
|
+
return null;
|
|
391
|
+
return { id: row.id, claimedAgeMinutes };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Detect the missed-fire variant of the morning-routine silent stall:
|
|
395
|
+
* the machine was asleep at the day-boundary minute, node-cron silently
|
|
396
|
+
* dropped the tick (it never replays missed firings), and nothing since
|
|
397
|
+
* has re-queued the routine. Observable signature, all three at once:
|
|
398
|
+
*
|
|
399
|
+
* 1. no morning-routine attempt has *started* this agent-day (any
|
|
400
|
+
* result — a failed/exhausted retry chain counts as attempted, so
|
|
401
|
+
* this self-heal never resurrects a chain that
|
|
402
|
+
* `scheduleMorningRetry` deliberately stopped after MAX_RETRIES);
|
|
403
|
+
* 2. no `pending`/`running` morning wake row exists (the cron, the
|
|
404
|
+
* wake catch-up, and the retry chain all leave one when they are
|
|
405
|
+
* mid-flight);
|
|
406
|
+
* 3. the agent-day is at least `graceMinutes` old, so a healthy cron
|
|
407
|
+
* fire has had ample time to produce 1 or 2.
|
|
408
|
+
*
|
|
409
|
+
* Complements the WakeDetector: gaps shorter than its 5-min threshold
|
|
410
|
+
* that straddle the boundary minute (lid closed 03:59–04:02), or a
|
|
411
|
+
* detector failure, still converge here within one self-heal interval.
|
|
412
|
+
*
|
|
413
|
+
* Pure read — the caller routes through `queueMorningRoutineWake`,
|
|
414
|
+
* whose DB-backed dedup makes double-queueing impossible.
|
|
415
|
+
*/
|
|
416
|
+
export function shouldQueueMissedMorningFire(db, agentDayConfig, graceMinutes, now) {
|
|
417
|
+
const reference = now ?? new Date();
|
|
418
|
+
const progressMinutes = getAgentDayProgressMinutes(agentDayConfig.timezone, agentDayConfig.dayBoundaryHour, reference);
|
|
419
|
+
if (progressMinutes < graceMinutes)
|
|
420
|
+
return false;
|
|
421
|
+
const wakeRow = db
|
|
422
|
+
.prepare(`SELECT 1
|
|
423
|
+
FROM agent_schedule
|
|
424
|
+
WHERE task_type = 'wake'
|
|
425
|
+
AND status IN ('pending', 'running')
|
|
426
|
+
AND json_extract(task_context, '$.routine') = 'morning_routine'
|
|
427
|
+
LIMIT 1`)
|
|
428
|
+
.get();
|
|
429
|
+
if (wakeRow !== undefined)
|
|
430
|
+
return false;
|
|
431
|
+
return (getLatestMorningAttemptStartMs(db, agentDayConfig, reference) === null);
|
|
432
|
+
}
|
|
280
433
|
// P22 — read the operator's chosen cadence for skill curation runs.
|
|
281
434
|
// Mirrors the helper in `core/scheduler.ts` so the dispatcher hook here can
|
|
282
435
|
// resolve cadence at runtime without crossing module boundaries.
|
package/dist/config.js
CHANGED
|
@@ -99,8 +99,10 @@ export function loadDefaultRuntimeSettings() {
|
|
|
99
99
|
// they know the value is now a no-op instead of silently dropping
|
|
100
100
|
// their override. The runtime-settings Zod schema would otherwise
|
|
101
101
|
// ignore the unknown key without any signal.
|
|
102
|
+
// Reads the LEGACY env name on purpose — the warning exists for operators
|
|
103
|
+
// who still carry the pre-redesign variable in their .env.
|
|
102
104
|
if (env("HOURLY_CHECK_GATE_MODE") !== undefined) {
|
|
103
|
-
console.warn("[config] PA_HOURLY_CHECK_GATE_MODE is set but no longer honoured — the
|
|
105
|
+
console.warn("[config] PA_HOURLY_CHECK_GATE_MODE is set but no longer honoured — the activity_scan gate now has a single execution path (see HOURLY_CHECK_GATE_REDESIGN_PLAN.md Phase 4). Remove the env var to silence this warning.");
|
|
104
106
|
}
|
|
105
107
|
const parsed = runtimeSettingsSchema.parse({
|
|
106
108
|
slackOwnerUserId: env("SLACK_OWNER_USER_ID") ?? null,
|
|
@@ -133,6 +135,9 @@ export function loadDefaultRuntimeSettings() {
|
|
|
133
135
|
proactiveForwardChannelTimelineEnabled: parseBooleanOrDefault(env("PROACTIVE_FORWARD_CHANNEL_TIMELINE_ENABLED"), true),
|
|
134
136
|
proactiveForwardForceFreshSession: parseBooleanOrDefault(env("PROACTIVE_FORWARD_FORCE_FRESH_SESSION"), false),
|
|
135
137
|
feedbackLearningEnabled: parseBooleanOrDefault(env("FEEDBACK_LEARNING_ENABLED"), true),
|
|
138
|
+
// SELF_TUNING_REVIEW_CYCLE_DESIGN.md §6 — Phase 3 actuation gate.
|
|
139
|
+
// Default false = shadow mode (recommend + verdict only, no actuation).
|
|
140
|
+
selfTuningEnabled: parseBooleanOrDefault(env("SELF_TUNING_ENABLED"), false),
|
|
136
141
|
feedbackPromotionThreshold: parseNumberOrDefault(env("FEEDBACK_PROMOTION_THRESHOLD"), 2),
|
|
137
142
|
feedbackLessonMaxBytesGlobal: parseNumberOrDefault(env("FEEDBACK_LESSON_MAX_BYTES_GLOBAL"), 8192),
|
|
138
143
|
feedbackLessonMaxBytesPerAgent: parseNumberOrDefault(env("FEEDBACK_LESSON_MAX_BYTES_PER_AGENT"), 4096),
|
|
@@ -148,19 +153,26 @@ export function loadDefaultRuntimeSettings() {
|
|
|
148
153
|
// at fire time so the flag takes effect on the next month-end without
|
|
149
154
|
// restart.
|
|
150
155
|
monthlyReviewEnabled: parseBooleanOrDefault(env("MONTHLY_REVIEW_ENABLED"), false),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
// The `?? env("HOURLY_CHECK_*")` fallbacks honour the pre-rename env
|
|
157
|
+
// names (the agent was "Hourly Check" until v0.1.11) — drop them after
|
|
158
|
+
// a deprecation window alongside LEGACY_RUNTIME_SETTING_KEY_ALIASES.
|
|
159
|
+
activityScanEnabled: parseBooleanOrDefault(env("ACTIVITY_SCAN_ENABLED") ?? env("HOURLY_CHECK_ENABLED"), true),
|
|
160
|
+
// Default 120 (every 2 hours) — see ACTIVITY_SCAN_CADENCE_DEFAULTS.
|
|
161
|
+
activityScanIntervalMinutes: parseNumberOrDefault(env("ACTIVITY_SCAN_INTERVAL_MINUTES") ?? env("HOURLY_CHECK_INTERVAL_MINUTES"), 120),
|
|
162
|
+
activityScanActiveStartHour: parseNumberOrDefault(env("ACTIVITY_SCAN_ACTIVE_START_HOUR") ?? env("HOURLY_CHECK_ACTIVE_START_HOUR"), 4),
|
|
163
|
+
activityScanActiveEndHour: parseNumberOrDefault(env("ACTIVITY_SCAN_ACTIVE_END_HOUR") ?? env("HOURLY_CHECK_ACTIVE_END_HOUR"), 24),
|
|
164
|
+
activityScanMinObservations: parseNumberOrDefault(env("ACTIVITY_SCAN_MIN_OBSERVATIONS") ?? env("HOURLY_CHECK_MIN_OBSERVATIONS"), 1),
|
|
165
|
+
activityScanStage2Enabled: parseBooleanOrDefault(env("ACTIVITY_SCAN_STAGE2_ENABLED") ?? env("HOURLY_CHECK_STAGE2_ENABLED"), false),
|
|
166
|
+
activityScanHeartbeatHours: parseNumberOrDefault(env("ACTIVITY_SCAN_HEARTBEAT_HOURS") ?? env("HOURLY_CHECK_HEARTBEAT_HOURS"), 4),
|
|
167
|
+
activityScanLowSignalPendingCeiling: parseNumberOrDefault(env("ACTIVITY_SCAN_LOW_SIGNAL_PENDING_CEILING")
|
|
168
|
+
?? env("HOURLY_CHECK_LOW_SIGNAL_PENDING_CEILING"), 0),
|
|
169
|
+
activityScanPrePassFreshnessMinutes: parseNumberOrDefault(env("ACTIVITY_SCAN_PRE_PASS_FRESHNESS_MINUTES")
|
|
170
|
+
?? env("HOURLY_CHECK_PRE_PASS_FRESHNESS_MINUTES"), 30),
|
|
160
171
|
authProbeDisabled: parseBooleanOrDefault(env("AUTH_PROBE_DISABLED"), false),
|
|
161
172
|
authPreflightFreshnessMs: parseNumberOrDefault(env("AUTH_PREFLIGHT_FRESHNESS_MS"), 600000),
|
|
162
173
|
schedulePollIntervalSeconds: parseNumberOrDefault(env("SCHEDULE_POLL_INTERVAL_SECONDS"), 5),
|
|
163
174
|
maxBriefingDelayMinutes: parseNumberOrDefault(env("MAX_BRIEFING_DELAY_MINUTES"), 30),
|
|
175
|
+
ownerActivityIdleThresholdMinutes: parseNumberOrDefault(env("OWNER_ACTIVITY_IDLE_THRESHOLD_MINUTES"), 5),
|
|
164
176
|
maxNotificationsPerHour: parseNumberOrDefault(env("MAX_NOTIFICATIONS_PER_HOUR"), 3),
|
|
165
177
|
maxNotificationsPerDay: parseNumberOrDefault(env("MAX_NOTIFICATIONS_PER_DAY"), 12),
|
|
166
178
|
quietHoursStart: envOrDefault("QUIET_HOURS_START", "22:00"),
|
|
@@ -185,7 +197,7 @@ export function loadDefaultRuntimeSettings() {
|
|
|
185
197
|
githubPollIntervalSeconds: parseNumberOrDefault(env("GITHUB_POLL_INTERVAL_SECONDS"), 1800),
|
|
186
198
|
// Matches calendar/git poller cadence. Previously 60s, but 3 databases
|
|
187
199
|
// × 1440 polls/day ≈ 4320 API calls/day was excessive when the only
|
|
188
|
-
// downstream consumer is
|
|
200
|
+
// downstream consumer is activity_scan. Override with
|
|
189
201
|
// PA_NOTION_POLL_INTERVAL_SECONDS if you need closer to real-time
|
|
190
202
|
// Notion sync (at the cost of API quota). If you raise this beyond
|
|
191
203
|
// ~10 min, also raise `NOTION_WRITE_TTL_MS` in `api/routes/notion.ts`
|
|
@@ -202,7 +214,7 @@ export function loadDefaultRuntimeSettings() {
|
|
|
202
214
|
mailIdleFallbackRecoveryMinutes: parseNumberOrDefault(env("MAIL_IDLE_FALLBACK_RECOVERY_MINUTES"), 60),
|
|
203
215
|
mailMaxMessagesPerPoll: parseNumberOrDefault(env("MAIL_MAX_MESSAGES_PER_POLL"), 20),
|
|
204
216
|
mailAuthFailureRetryHours: parseNumberOrDefault(env("MAIL_AUTH_FAILURE_RETRY_HOURS"), 6),
|
|
205
|
-
|
|
217
|
+
activityScanObservationCharBudget: parseNumberOrDefault(env("ACTIVITY_SCAN_OBSERVATION_CHAR_BUDGET") ?? env("HOURLY_OBSERVATION_CHAR_BUDGET"), 8000),
|
|
206
218
|
prePassMaxAttemptsPerIntegration: parseNumberOrDefault(env("PRE_PASS_MAX_ATTEMPTS_PER_INTEGRATION"), 3),
|
|
207
219
|
prePassBackoffMs: parseJsonOrDefault(env("PRE_PASS_BACKOFF_MS"), [1000, 2000, 4000]),
|
|
208
220
|
prePassRetryEscalationTier: parseNullableStringEnv(env("PRE_PASS_RETRY_ESCALATION_TIER")),
|
|
@@ -243,6 +255,8 @@ export function loadDefaultRuntimeSettings() {
|
|
|
243
255
|
// installs pre-seed them.
|
|
244
256
|
primaryLanguage: envOrDefault("PRIMARY_LANGUAGE", "en"),
|
|
245
257
|
vaultMode: env("VAULT_MODE") ?? "plain",
|
|
258
|
+
// Keep-awake posture (macOS caffeinate); see core/sleep-inhibitor.ts.
|
|
259
|
+
preventSleepMode: env("PREVENT_SLEEP_MODE") ?? "ac",
|
|
246
260
|
});
|
|
247
261
|
return normalizeRuntimeSettings(parsed);
|
|
248
262
|
}
|
|
@@ -570,5 +570,37 @@ export declare class BackendDecisiveFailure extends Error {
|
|
|
570
570
|
readonly backendId: BackendId;
|
|
571
571
|
readonly kind: "quota" | "auth" | "max_turns" | "timeout" | "model_unavailable" | "policy_denied" | "other_non_retryable";
|
|
572
572
|
readonly cause: unknown;
|
|
573
|
-
|
|
573
|
+
/**
|
|
574
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — best-effort spend recovered
|
|
575
|
+
* from the failed run when the SDK/CLI surfaced usage before the
|
|
576
|
+
* terminal error (auth rejection mid-run, timeout, transport
|
|
577
|
+
* failure). Same shape as `BackendQuotaError.spend` so the
|
|
578
|
+
* dispatcher's post-hoc audit writer can record what the provider
|
|
579
|
+
* actually billed for a turn that produced no `AgentResult`.
|
|
580
|
+
* `null` when the failure happened before any usage was observed.
|
|
581
|
+
*/
|
|
582
|
+
readonly spend: BackendQuotaSpend | null;
|
|
583
|
+
constructor(backendId: BackendId, kind: "quota" | "auth" | "max_turns" | "timeout" | "model_unavailable" | "policy_denied" | "other_non_retryable", cause: unknown,
|
|
584
|
+
/**
|
|
585
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — best-effort spend recovered
|
|
586
|
+
* from the failed run when the SDK/CLI surfaced usage before the
|
|
587
|
+
* terminal error (auth rejection mid-run, timeout, transport
|
|
588
|
+
* failure). Same shape as `BackendQuotaError.spend` so the
|
|
589
|
+
* dispatcher's post-hoc audit writer can record what the provider
|
|
590
|
+
* actually billed for a turn that produced no `AgentResult`.
|
|
591
|
+
* `null` when the failure happened before any usage was observed.
|
|
592
|
+
*/
|
|
593
|
+
spend?: BackendQuotaSpend | null);
|
|
574
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Recover the spend payload from a backend failover signal, regardless
|
|
597
|
+
* of which of the two error classes carries it. Handles the nested
|
|
598
|
+
* `BackendDecisiveFailure(kind="quota", cause=BackendQuotaError)` wrap
|
|
599
|
+
* the router produces, preferring the inner quota error's spend when
|
|
600
|
+
* both layers carry one. Returns `null` for non-backend errors.
|
|
601
|
+
*
|
|
602
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — shared by the dispatcher's
|
|
603
|
+
* post-hoc audit writer and the pre-pass fan-out runner so both
|
|
604
|
+
* failure paths record the same figure for the same error.
|
|
605
|
+
*/
|
|
606
|
+
export declare function extractBackendSpend(error: unknown): BackendQuotaSpend | null;
|
package/dist/core/agent-core.js
CHANGED
|
@@ -93,11 +93,46 @@ export class BackendDecisiveFailure extends Error {
|
|
|
93
93
|
backendId;
|
|
94
94
|
kind;
|
|
95
95
|
cause;
|
|
96
|
-
|
|
96
|
+
spend;
|
|
97
|
+
constructor(backendId, kind, cause,
|
|
98
|
+
/**
|
|
99
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — best-effort spend recovered
|
|
100
|
+
* from the failed run when the SDK/CLI surfaced usage before the
|
|
101
|
+
* terminal error (auth rejection mid-run, timeout, transport
|
|
102
|
+
* failure). Same shape as `BackendQuotaError.spend` so the
|
|
103
|
+
* dispatcher's post-hoc audit writer can record what the provider
|
|
104
|
+
* actually billed for a turn that produced no `AgentResult`.
|
|
105
|
+
* `null` when the failure happened before any usage was observed.
|
|
106
|
+
*/
|
|
107
|
+
spend = null) {
|
|
97
108
|
super(`${backendId} decisive failure: ${kind}`);
|
|
98
109
|
this.backendId = backendId;
|
|
99
110
|
this.kind = kind;
|
|
100
111
|
this.cause = cause;
|
|
112
|
+
this.spend = spend;
|
|
101
113
|
this.name = "BackendDecisiveFailure";
|
|
102
114
|
}
|
|
103
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Recover the spend payload from a backend failover signal, regardless
|
|
118
|
+
* of which of the two error classes carries it. Handles the nested
|
|
119
|
+
* `BackendDecisiveFailure(kind="quota", cause=BackendQuotaError)` wrap
|
|
120
|
+
* the router produces, preferring the inner quota error's spend when
|
|
121
|
+
* both layers carry one. Returns `null` for non-backend errors.
|
|
122
|
+
*
|
|
123
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — shared by the dispatcher's
|
|
124
|
+
* post-hoc audit writer and the pre-pass fan-out runner so both
|
|
125
|
+
* failure paths record the same figure for the same error.
|
|
126
|
+
*/
|
|
127
|
+
export function extractBackendSpend(error) {
|
|
128
|
+
if (error instanceof BackendQuotaError) {
|
|
129
|
+
return error.spend;
|
|
130
|
+
}
|
|
131
|
+
if (error instanceof BackendDecisiveFailure) {
|
|
132
|
+
if (error.cause instanceof BackendQuotaError && error.cause.spend) {
|
|
133
|
+
return error.cause.spend;
|
|
134
|
+
}
|
|
135
|
+
return error.spend;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity-scan cadence resolution (AGENTS_HUB_REDESIGN_PLAN.md §2).
|
|
3
|
+
*
|
|
4
|
+
* The activity-scan Agent's firing window (interval + active hours) and its
|
|
5
|
+
* observation-threshold gate live on the **agent row** —
|
|
6
|
+
* `agents.metadata_json.runtime_window`, written by `PATCH /api/agents/
|
|
7
|
+
* activity-scan` (`schedule_window` body block) and preserved across loader
|
|
8
|
+
* re-runs / `npm i -g` by `loader.ts:nextMetadata`. The `activityScan*`
|
|
9
|
+
* config keys (`hourlyCheck*` before the v0.1.11 rename) are deprecated but
|
|
10
|
+
* still parsed; they act as the per-field fallback so a value an operator
|
|
11
|
+
* persisted pre-redesign keeps working until they touch the agent-level
|
|
12
|
+
* setting.
|
|
13
|
+
*
|
|
14
|
+
* Resolution order, per field: `runtime_window` override → legacy config key
|
|
15
|
+
* (which itself carries the shipped default). Pure module — callers fetch the
|
|
16
|
+
* stored override via `agents-store.ts:getRuntimeWindow` and supply the live
|
|
17
|
+
* config; this keeps the precedence logic in the 100%-coverage set.
|
|
18
|
+
*/
|
|
19
|
+
/** Field bounds, shared by the PATCH validator and the sanitizer. */
|
|
20
|
+
export declare const RUNTIME_WINDOW_BOUNDS: {
|
|
21
|
+
readonly interval_minutes: {
|
|
22
|
+
readonly min: 5;
|
|
23
|
+
readonly max: 1440;
|
|
24
|
+
};
|
|
25
|
+
readonly active_start_hour: {
|
|
26
|
+
readonly min: 0;
|
|
27
|
+
readonly max: 23;
|
|
28
|
+
};
|
|
29
|
+
readonly active_end_hour: {
|
|
30
|
+
readonly min: 1;
|
|
31
|
+
readonly max: 24;
|
|
32
|
+
};
|
|
33
|
+
readonly min_observations: {
|
|
34
|
+
readonly min: 0;
|
|
35
|
+
readonly max: 1000;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export type RuntimeWindowField = keyof typeof RUNTIME_WINDOW_BOUNDS;
|
|
39
|
+
export declare const RUNTIME_WINDOW_FIELDS: readonly RuntimeWindowField[];
|
|
40
|
+
/**
|
|
41
|
+
* The persisted shape under `metadata_json.runtime_window`. Every field is
|
|
42
|
+
* optional — only operator-touched fields are stored, so an untouched field
|
|
43
|
+
* keeps tracking the config fallback.
|
|
44
|
+
*/
|
|
45
|
+
export interface RuntimeWindowOverride {
|
|
46
|
+
interval_minutes?: number;
|
|
47
|
+
active_start_hour?: number;
|
|
48
|
+
active_end_hour?: number;
|
|
49
|
+
min_observations?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The legacy config keys the resolver falls back to. Fields are optional at
|
|
53
|
+
* the type level so partially-stubbed test configs (and any pre-schema boot
|
|
54
|
+
* edge) resolve to the shipped defaults instead of producing a `NaN` cron.
|
|
55
|
+
*/
|
|
56
|
+
export interface ActivityScanCadenceConfig {
|
|
57
|
+
activityScanIntervalMinutes?: number;
|
|
58
|
+
activityScanActiveStartHour?: number;
|
|
59
|
+
activityScanActiveEndHour?: number;
|
|
60
|
+
activityScanMinObservations?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Shipped defaults — mirror `runtime-settings.ts` (`activityScan*` keys). */
|
|
63
|
+
export declare const ACTIVITY_SCAN_CADENCE_DEFAULTS: {
|
|
64
|
+
readonly intervalMinutes: 120;
|
|
65
|
+
readonly activeStartHour: 4;
|
|
66
|
+
readonly activeEndHour: 24;
|
|
67
|
+
readonly minObservations: 1;
|
|
68
|
+
};
|
|
69
|
+
/** Fully-resolved cadence every consumer (scheduler, gate, API) reads. */
|
|
70
|
+
export interface ResolvedActivityScanCadence {
|
|
71
|
+
intervalMinutes: number;
|
|
72
|
+
activeStartHour: number;
|
|
73
|
+
activeEndHour: number;
|
|
74
|
+
minObservations: number;
|
|
75
|
+
}
|
|
76
|
+
/** True when `value` is an integer within the field's bounds. */
|
|
77
|
+
export declare function isValidRuntimeWindowValue(field: RuntimeWindowField, value: unknown): value is number;
|
|
78
|
+
/**
|
|
79
|
+
* Sanitize a raw `metadata_json.runtime_window` blob (untrusted: hand-edited
|
|
80
|
+
* DBs, older daemons). Out-of-bounds / non-integer fields are dropped — the
|
|
81
|
+
* resolver then falls back to config for them rather than failing the boot.
|
|
82
|
+
*/
|
|
83
|
+
export declare function parseRuntimeWindowOverride(value: unknown): RuntimeWindowOverride;
|
|
84
|
+
export declare function resolveActivityScanCadence(override: RuntimeWindowOverride | undefined, config: ActivityScanCadenceConfig): ResolvedActivityScanCadence;
|
|
85
|
+
export type RuntimeWindowMergeResult = {
|
|
86
|
+
ok: true;
|
|
87
|
+
value: RuntimeWindowOverride;
|
|
88
|
+
cadenceChanged: boolean;
|
|
89
|
+
} | {
|
|
90
|
+
ok: false;
|
|
91
|
+
field: string;
|
|
92
|
+
error: "invalid_field_value" | "invalid_window";
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Merge a PATCH `schedule_window` block onto the stored override. Per-field
|
|
96
|
+
* type/bounds are validated; the cross-field window check (`end > start`) runs
|
|
97
|
+
* against the post-merge **resolved** values so a partial patch can't sneak an
|
|
98
|
+
* empty window past per-field validation. `null` resets a field back to the
|
|
99
|
+
* config fallback. `cadenceChanged` tells the route whether a cron rebuild
|
|
100
|
+
* (`reloadCrons`) is needed — `min_observations` is a fire-time gate and never
|
|
101
|
+
* requires one.
|
|
102
|
+
*/
|
|
103
|
+
export declare function mergeRuntimeWindow(current: RuntimeWindowOverride, patch: Record<string, unknown>, config: ActivityScanCadenceConfig): RuntimeWindowMergeResult;
|