@aitne/daemon 0.1.9 → 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.d.ts +1 -0
- package/dist/api/env-writer.js +17 -7
- 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-schedule.js +5 -1
- 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/apple-calendar.js +4 -1
- 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/calendar.js +12 -2
- package/dist/api/routes/context/path-resolve.js +6 -1
- package/dist/api/routes/context/permissions.js +12 -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 +58 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/dashboard/oauth-google.js +5 -3
- package/dist/api/routes/feedback.d.ts +3 -0
- package/dist/api/routes/feedback.js +349 -0
- package/dist/api/routes/git.js +10 -3
- package/dist/api/routes/github.js +5 -1
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/mcp.js +65 -13
- 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 +12 -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 +246 -8
- 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 +32 -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 +38 -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 +47 -18
- 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 +193 -5
- 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 +11 -1
- package/dist/core/context-paths.js +17 -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 +50 -1
- 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 +24 -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 +104 -1
- package/dist/core/dispatcher-scheduled-tasks.js +480 -8
- 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 +297 -60
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.d.ts +94 -0
- package/dist/core/feedback/consolidation-prep.js +254 -0
- package/dist/core/feedback/eviction-scorer.d.ts +81 -0
- package/dist/core/feedback/eviction-scorer.js +136 -0
- package/dist/core/feedback/lesson-format.d.ts +79 -0
- package/dist/core/feedback/lesson-format.js +199 -0
- package/dist/core/feedback/lesson-injection.d.ts +98 -0
- package/dist/core/feedback/lesson-injection.js +174 -0
- package/dist/core/feedback/lesson-merge.d.ts +51 -0
- package/dist/core/feedback/lesson-merge.js +88 -0
- package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
- package/dist/core/feedback/lesson-store-overview.js +42 -0
- package/dist/core/feedback/promotion-gate.d.ts +69 -0
- package/dist/core/feedback/promotion-gate.js +117 -0
- package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
- package/dist/core/feedback/regeneralization-prep.js +152 -0
- package/dist/core/feedback/scope-parser.d.ts +86 -0
- package/dist/core/feedback/scope-parser.js +141 -0
- 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 +83 -1
- package/dist/core/injection-policy.js +61 -3
- 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 +51 -1
- package/dist/core/signal-detector.js +321 -24
- 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 +60 -14
- package/dist/core/today-direct-writer.js +90 -13
- 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/wiki/wiki-fts.js +13 -6
- 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/feedback-signals-store.d.ts +77 -0
- package/dist/db/feedback-signals-store.js +144 -0
- package/dist/db/migrations.js +380 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +260 -22
- 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/always-disallowed.d.ts +1 -1
- package/dist/safety/always-disallowed.js +39 -0
- 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 +97 -18
- 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 +34 -8
- package/dist/services/browser-history/lifecycle/platform.js +44 -2
- 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/mcp/probe.js +30 -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 +45 -12
- package/dist/settings/runtime-settings.js +215 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Daemon-direct
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Daemon-direct, lock-aware writes to today.md that run *before* (or
|
|
3
|
+
* instead of) an agent session — bypassing `/api/context/*` because there
|
|
4
|
+
* is no subprocess to issue the curl call from. Two operations live here:
|
|
5
|
+
*
|
|
6
|
+
* 1. {@link appendAgentLogLine} — append a single `## Agent Log` bullet.
|
|
7
|
+
* Used by the three-stage activity_scan gate (cost-reduction-structural
|
|
8
|
+
* §B) on the stage0_silent / stage2_log_only paths so a "no-op" cron
|
|
9
|
+
* tick still leaves an audit trail without paying for an LLM session.
|
|
10
|
+
* 2. {@link ensureTodaySkeleton} — seed the canonical empty skeleton when
|
|
11
|
+
* today.md is **absent**, so a section-only refresh routine has a valid
|
|
12
|
+
* PATCH target instead of 404-ing and budget-burning on full-file PUTs.
|
|
7
13
|
*
|
|
8
14
|
* Why this lives in the daemon (not /api/context/* via curl):
|
|
9
15
|
* - These paths run *before* the agent is spawned. There is no
|
|
@@ -12,20 +18,22 @@
|
|
|
12
18
|
* `context-staleness.ts`) — the same tier the PATCH route already
|
|
13
19
|
* classifies for `## Agent Log` appends. Bypassing the route is
|
|
14
20
|
* fine because we never touch the prompt-context-changed hook here.
|
|
15
|
-
* - The today-write-lock invariant is preserved:
|
|
16
|
-
* mutating the file, so morning_routine and direct writes
|
|
17
|
-
* interleave.
|
|
21
|
+
* - The today-write-lock invariant is preserved: both functions acquire
|
|
22
|
+
* it before mutating the file, so morning_routine and direct writes
|
|
23
|
+
* never interleave.
|
|
18
24
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* Synthesis boundary: `appendAgentLogLine` NEVER synthesizes structure —
|
|
26
|
+
* a missing / malformed / heading-less file returns false and the gate
|
|
27
|
+
* caller proceeds. `ensureTodaySkeleton` synthesizes ONLY the empty
|
|
28
|
+
* skeleton, ONLY when the file is entirely absent, and never touches a
|
|
29
|
+
* present file. Neither populates today.md — full creation and repair stay
|
|
30
|
+
* the morning routine's job.
|
|
24
31
|
*/
|
|
25
32
|
import { existsSync, readFileSync } from "node:fs";
|
|
26
33
|
import { writeFileAtomically } from "./atomic-write.js";
|
|
27
34
|
import { serializeContextFileWrite } from "./context-file-serializer.js";
|
|
28
35
|
import { fullPath, CONTEXT_RELATIVE_PATHS } from "./context-paths.js";
|
|
36
|
+
import { FALLBACK_PLACEHOLDERS } from "./skeleton.js";
|
|
29
37
|
import { createLogger } from "../logging.js";
|
|
30
38
|
const logger = createLogger("today-direct-writer");
|
|
31
39
|
const AGENT_LOG_HEADER = "## Agent Log";
|
|
@@ -87,6 +95,75 @@ export async function appendAgentLogLine(input) {
|
|
|
87
95
|
input.todayWriteLock.release(lock.lockId);
|
|
88
96
|
}
|
|
89
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Canonical empty `today.md` skeleton, reused byte-for-byte from the
|
|
100
|
+
* boot-time seeder (`skeleton.ts`) so a refresh-path seed and a
|
|
101
|
+
* fresh-install seed produce identical structure. `skeleton.test.ts`
|
|
102
|
+
* asserts this placeholder matches `agent-assets/templates/state/today.md`
|
|
103
|
+
* byte-for-byte, so the two definitions never drift. The non-null
|
|
104
|
+
* assertion is safe: the key is a literal entry of `FALLBACK_PLACEHOLDERS`.
|
|
105
|
+
*/
|
|
106
|
+
const TODAY_SKELETON = FALLBACK_PLACEHOLDERS[CONTEXT_RELATIVE_PATHS.today];
|
|
107
|
+
/**
|
|
108
|
+
* Guarantee a `today.md` working surface exists before a section-only
|
|
109
|
+
* refresh routine (`routine.today_refresh`) assumes it.
|
|
110
|
+
*
|
|
111
|
+
* `rotateDayFiles()` intentionally renames `today.md` → `yesterday.md` at
|
|
112
|
+
* the day boundary and relies on the morning routine to recreate the
|
|
113
|
+
* dated file. When the morning routine has not run yet — or failed (e.g.
|
|
114
|
+
* a quota/budget death with no fallback backend) — `today.md` is absent
|
|
115
|
+
* and the refresh task flow's `PATCH section=user_schedule` 404s. The
|
|
116
|
+
* agent then improvises full-file `PUT`s, which the strict
|
|
117
|
+
* `validateTodayContent` schema rejects line-by-line; on a single-backend
|
|
118
|
+
* binding with a tight per-turn budget that loop tips into
|
|
119
|
+
* `BackendQuotaError(max_budget_usd)` and the refresh dies without ever
|
|
120
|
+
* writing the file — the "Refresh Today does nothing" symptom.
|
|
121
|
+
*
|
|
122
|
+
* This deterministic pre-step removes that whole failure mode: when the
|
|
123
|
+
* file is **entirely absent** we seed the canonical empty skeleton so the
|
|
124
|
+
* agent's section PATCH always has a valid target. A file that already
|
|
125
|
+
* exists is left byte-untouched — a valid dated file OR the legacy
|
|
126
|
+
* `# Today` bridge stub both accept the section PATCH (the route's
|
|
127
|
+
* `allowLegacyToday` branch). We never repair a malformed-but-present
|
|
128
|
+
* file and never overwrite user content; full creation/repair stays the
|
|
129
|
+
* morning routine's job. The seeded skeleton is dateless (`# Today`), so
|
|
130
|
+
* it does NOT satisfy `hasCurrentAgentDayTodayMd()` and the pending
|
|
131
|
+
* morning-routine retry still fires and upgrades it.
|
|
132
|
+
*
|
|
133
|
+
* Lock-aware exactly like {@link appendAgentLogLine}: if the morning
|
|
134
|
+
* routine holds the today-write-lock (mid-creation) we skip and let it
|
|
135
|
+
* win — the refresh session then 409-defers on its own PATCH.
|
|
136
|
+
*/
|
|
137
|
+
export async function ensureTodaySkeleton(input) {
|
|
138
|
+
const lock = input.todayWriteLock.acquire();
|
|
139
|
+
if (!lock.ok) {
|
|
140
|
+
logger.info({ holder: lock.holder }, "Skipping today.md skeleton seed — today-write-lock held");
|
|
141
|
+
return { seeded: false, reason: "lock_unavailable" };
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const path = fullPath(input.contextDir, CONTEXT_RELATIVE_PATHS.today);
|
|
145
|
+
return await serializeContextFileWrite(path, () => {
|
|
146
|
+
if (existsSync(path)) {
|
|
147
|
+
return {
|
|
148
|
+
seeded: false,
|
|
149
|
+
reason: "already_present",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
writeFileAtomically(path, TODAY_SKELETON);
|
|
154
|
+
logger.info({ path }, "Seeded today.md skeleton for refresh — file was absent (morning routine not yet run for the agent-day)");
|
|
155
|
+
return { seeded: true };
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
logger.error({ err, path }, "Failed to seed today.md skeleton");
|
|
159
|
+
return { seeded: false, reason: "io_error" };
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
input.todayWriteLock.release(lock.lockId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
90
167
|
/**
|
|
91
168
|
* Splice a new bullet line into the `## Agent Log` section, immediately
|
|
92
169
|
* before the next `## ` heading or end-of-file. Returns null when the
|
|
@@ -13,8 +13,9 @@ export interface TodayWriteLockManager {
|
|
|
13
13
|
export declare class InMemoryTodayWriteLockManager implements TodayWriteLockManager {
|
|
14
14
|
private readonly timeoutMs;
|
|
15
15
|
private holder;
|
|
16
|
-
private
|
|
16
|
+
private expiresAtMs;
|
|
17
17
|
constructor(timeoutMs: number);
|
|
18
|
+
private expireIfStale;
|
|
18
19
|
acquire(): {
|
|
19
20
|
ok: true;
|
|
20
21
|
lockId: string;
|
|
@@ -38,8 +39,9 @@ export declare function getTodayWriteLockTimeoutMs(executeTimeoutMinutes: number
|
|
|
38
39
|
export declare class MigrationLock {
|
|
39
40
|
private readonly timeoutMs;
|
|
40
41
|
private holder;
|
|
41
|
-
private
|
|
42
|
+
private expiresAtMs;
|
|
42
43
|
constructor(timeoutMs: number);
|
|
44
|
+
private expireIfStale;
|
|
43
45
|
acquire(): {
|
|
44
46
|
ok: true;
|
|
45
47
|
lockId: string;
|
|
@@ -4,44 +4,50 @@ const logger = createLogger("today-write-lock");
|
|
|
4
4
|
export class InMemoryTodayWriteLockManager {
|
|
5
5
|
timeoutMs;
|
|
6
6
|
holder = null;
|
|
7
|
-
|
|
7
|
+
expiresAtMs = 0;
|
|
8
8
|
constructor(timeoutMs) {
|
|
9
9
|
this.timeoutMs = timeoutMs;
|
|
10
10
|
}
|
|
11
|
+
// Expiry is wall-clock (Date.now) checked lazily on access, not a
|
|
12
|
+
// setTimeout: Node timers run on the monotonic clock and don't advance
|
|
13
|
+
// while the machine sleeps, so a timer armed before sleep would hold the
|
|
14
|
+
// lock up to the whole sleep duration past its intended TTL.
|
|
15
|
+
expireIfStale() {
|
|
16
|
+
if (this.holder && Date.now() >= this.expiresAtMs) {
|
|
17
|
+
logger.warn({ lockId: this.holder }, "Today write lock expired by timeout");
|
|
18
|
+
this.holder = null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
acquire() {
|
|
22
|
+
this.expireIfStale();
|
|
12
23
|
if (this.holder) {
|
|
13
24
|
logger.debug({ existingHolder: this.holder }, "Lock acquire rejected — already held");
|
|
14
25
|
return { ok: false, holder: this.holder };
|
|
15
26
|
}
|
|
16
27
|
const lockId = randomUUID();
|
|
17
28
|
this.holder = lockId;
|
|
18
|
-
this.
|
|
19
|
-
logger.warn({ lockId: this.holder }, "Today write lock expired by timeout");
|
|
20
|
-
this.holder = null;
|
|
21
|
-
this.timer = null;
|
|
22
|
-
}, this.timeoutMs);
|
|
29
|
+
this.expiresAtMs = Date.now() + this.timeoutMs;
|
|
23
30
|
logger.debug({ lockId }, "Today write lock acquired");
|
|
24
31
|
return { ok: true, lockId };
|
|
25
32
|
}
|
|
26
33
|
release(lockId) {
|
|
34
|
+
this.expireIfStale();
|
|
27
35
|
if (!this.holder || this.holder !== lockId) {
|
|
28
36
|
return false;
|
|
29
37
|
}
|
|
30
38
|
this.holder = null;
|
|
31
|
-
if (this.timer) {
|
|
32
|
-
clearTimeout(this.timer);
|
|
33
|
-
this.timer = null;
|
|
34
|
-
}
|
|
35
39
|
logger.debug({ lockId }, "Today write lock released");
|
|
36
40
|
return true;
|
|
37
41
|
}
|
|
38
42
|
isHeldBy(lockId) {
|
|
43
|
+
this.expireIfStale();
|
|
39
44
|
if (!this.holder) {
|
|
40
45
|
return false;
|
|
41
46
|
}
|
|
42
47
|
return this.holder === lockId;
|
|
43
48
|
}
|
|
44
49
|
getHolder() {
|
|
50
|
+
this.expireIfStale();
|
|
45
51
|
return this.holder;
|
|
46
52
|
}
|
|
47
53
|
}
|
|
@@ -62,41 +68,45 @@ export function getTodayWriteLockTimeoutMs(executeTimeoutMinutes) {
|
|
|
62
68
|
export class MigrationLock {
|
|
63
69
|
timeoutMs;
|
|
64
70
|
holder = null;
|
|
65
|
-
|
|
71
|
+
expiresAtMs = 0;
|
|
66
72
|
constructor(timeoutMs) {
|
|
67
73
|
this.timeoutMs = timeoutMs;
|
|
68
74
|
}
|
|
75
|
+
// Wall-clock lazy expiry — see InMemoryTodayWriteLockManager for why
|
|
76
|
+
// this is not a setTimeout.
|
|
77
|
+
expireIfStale() {
|
|
78
|
+
if (this.holder && Date.now() >= this.expiresAtMs) {
|
|
79
|
+
logger.warn({ lockId: this.holder }, "Migration lock expired by timeout");
|
|
80
|
+
this.holder = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
69
83
|
acquire() {
|
|
84
|
+
this.expireIfStale();
|
|
70
85
|
if (this.holder) {
|
|
71
86
|
logger.debug({ existingHolder: this.holder }, "Migration lock rejected — already held");
|
|
72
87
|
return { ok: false, holder: this.holder };
|
|
73
88
|
}
|
|
74
89
|
const lockId = randomUUID();
|
|
75
90
|
this.holder = lockId;
|
|
76
|
-
this.
|
|
77
|
-
logger.warn({ lockId: this.holder }, "Migration lock expired by timeout");
|
|
78
|
-
this.holder = null;
|
|
79
|
-
this.timer = null;
|
|
80
|
-
}, this.timeoutMs);
|
|
91
|
+
this.expiresAtMs = Date.now() + this.timeoutMs;
|
|
81
92
|
logger.debug({ lockId }, "Migration lock acquired");
|
|
82
93
|
return { ok: true, lockId };
|
|
83
94
|
}
|
|
84
95
|
release(lockId) {
|
|
96
|
+
this.expireIfStale();
|
|
85
97
|
if (!this.holder || this.holder !== lockId) {
|
|
86
98
|
return false;
|
|
87
99
|
}
|
|
88
100
|
this.holder = null;
|
|
89
|
-
if (this.timer) {
|
|
90
|
-
clearTimeout(this.timer);
|
|
91
|
-
this.timer = null;
|
|
92
|
-
}
|
|
93
101
|
logger.debug({ lockId }, "Migration lock released");
|
|
94
102
|
return true;
|
|
95
103
|
}
|
|
96
104
|
isHeld() {
|
|
105
|
+
this.expireIfStale();
|
|
97
106
|
return this.holder !== null;
|
|
98
107
|
}
|
|
99
108
|
getHolder() {
|
|
109
|
+
this.expireIfStale();
|
|
100
110
|
return this.holder;
|
|
101
111
|
}
|
|
102
112
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default tick cadence. One minute keeps detection latency low while the
|
|
3
|
+
* per-tick work (one Date.now() subtraction) is negligible.
|
|
4
|
+
*/
|
|
5
|
+
export declare const WAKE_DETECTOR_INTERVAL_MS = 60000;
|
|
6
|
+
/**
|
|
7
|
+
* Minimum unexplained gap between ticks that counts as a sleep / suspend /
|
|
8
|
+
* forward clock jump. Five minutes is far above any plausible event-loop
|
|
9
|
+
* stall on a healthy daemon, and far below the shortest sleep that can
|
|
10
|
+
* swallow a cron tick worth catching up (the activity scan's default
|
|
11
|
+
* 60-minute cadence).
|
|
12
|
+
*/
|
|
13
|
+
export declare const WAKE_GAP_THRESHOLD_MS: number;
|
|
14
|
+
export interface WakeDetectorOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Invoked once per detected wake with the gap length. Errors (sync or
|
|
17
|
+
* async) are caught and logged — the detector keeps ticking.
|
|
18
|
+
*/
|
|
19
|
+
onWake: (gapMs: number) => void | Promise<void>;
|
|
20
|
+
intervalMs?: number;
|
|
21
|
+
gapThresholdMs?: number;
|
|
22
|
+
/** Injectable wall clock for tests. */
|
|
23
|
+
now?: () => number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Detects machine sleep / suspend / forward clock jumps from inside the
|
|
27
|
+
* process.
|
|
28
|
+
*
|
|
29
|
+
* node-cron (and every other timer in the daemon) runs on Node's timer
|
|
30
|
+
* wheel, which does not fire while the host is suspended — a cron tick
|
|
31
|
+
* scheduled inside a sleep window is silently lost, not replayed on wake.
|
|
32
|
+
* The boot-time catchup in `bootstrap/catchup.ts` covers daemon *restarts*,
|
|
33
|
+
* but a long sleep with the process still alive had no equivalent until
|
|
34
|
+
* this detector.
|
|
35
|
+
*
|
|
36
|
+
* Mechanism: a short `setInterval` notes the wall-clock time of each tick.
|
|
37
|
+
* Timers freeze during sleep, so the first tick after wake observes a
|
|
38
|
+
* wall-clock gap of roughly the sleep duration; anything above
|
|
39
|
+
* `gapThresholdMs` beyond the expected interval fires `onWake`. Backward
|
|
40
|
+
* clock jumps are ignored — there is nothing to catch up when time moves
|
|
41
|
+
* backward, and the next tick re-baselines automatically.
|
|
42
|
+
*/
|
|
43
|
+
export declare class WakeDetector {
|
|
44
|
+
private readonly onWake;
|
|
45
|
+
private readonly intervalMs;
|
|
46
|
+
private readonly gapThresholdMs;
|
|
47
|
+
private readonly now;
|
|
48
|
+
private timer;
|
|
49
|
+
private lastTickMs;
|
|
50
|
+
constructor(options: WakeDetectorOptions);
|
|
51
|
+
start(): void;
|
|
52
|
+
stop(): void;
|
|
53
|
+
/** Test seam — exercises one tick without waiting on real timers. */
|
|
54
|
+
tick(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createLogger } from "../logging.js";
|
|
2
|
+
const logger = createLogger("wake-detector");
|
|
3
|
+
/**
|
|
4
|
+
* Default tick cadence. One minute keeps detection latency low while the
|
|
5
|
+
* per-tick work (one Date.now() subtraction) is negligible.
|
|
6
|
+
*/
|
|
7
|
+
export const WAKE_DETECTOR_INTERVAL_MS = 60_000;
|
|
8
|
+
/**
|
|
9
|
+
* Minimum unexplained gap between ticks that counts as a sleep / suspend /
|
|
10
|
+
* forward clock jump. Five minutes is far above any plausible event-loop
|
|
11
|
+
* stall on a healthy daemon, and far below the shortest sleep that can
|
|
12
|
+
* swallow a cron tick worth catching up (the activity scan's default
|
|
13
|
+
* 60-minute cadence).
|
|
14
|
+
*/
|
|
15
|
+
export const WAKE_GAP_THRESHOLD_MS = 5 * 60_000;
|
|
16
|
+
/**
|
|
17
|
+
* Detects machine sleep / suspend / forward clock jumps from inside the
|
|
18
|
+
* process.
|
|
19
|
+
*
|
|
20
|
+
* node-cron (and every other timer in the daemon) runs on Node's timer
|
|
21
|
+
* wheel, which does not fire while the host is suspended — a cron tick
|
|
22
|
+
* scheduled inside a sleep window is silently lost, not replayed on wake.
|
|
23
|
+
* The boot-time catchup in `bootstrap/catchup.ts` covers daemon *restarts*,
|
|
24
|
+
* but a long sleep with the process still alive had no equivalent until
|
|
25
|
+
* this detector.
|
|
26
|
+
*
|
|
27
|
+
* Mechanism: a short `setInterval` notes the wall-clock time of each tick.
|
|
28
|
+
* Timers freeze during sleep, so the first tick after wake observes a
|
|
29
|
+
* wall-clock gap of roughly the sleep duration; anything above
|
|
30
|
+
* `gapThresholdMs` beyond the expected interval fires `onWake`. Backward
|
|
31
|
+
* clock jumps are ignored — there is nothing to catch up when time moves
|
|
32
|
+
* backward, and the next tick re-baselines automatically.
|
|
33
|
+
*/
|
|
34
|
+
export class WakeDetector {
|
|
35
|
+
onWake;
|
|
36
|
+
intervalMs;
|
|
37
|
+
gapThresholdMs;
|
|
38
|
+
now;
|
|
39
|
+
timer = null;
|
|
40
|
+
lastTickMs = 0;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.onWake = options.onWake;
|
|
43
|
+
this.intervalMs = options.intervalMs ?? WAKE_DETECTOR_INTERVAL_MS;
|
|
44
|
+
this.gapThresholdMs = options.gapThresholdMs ?? WAKE_GAP_THRESHOLD_MS;
|
|
45
|
+
this.now = options.now ?? Date.now;
|
|
46
|
+
}
|
|
47
|
+
start() {
|
|
48
|
+
if (this.timer)
|
|
49
|
+
return;
|
|
50
|
+
this.lastTickMs = this.now();
|
|
51
|
+
this.timer = setInterval(() => this.tick(), this.intervalMs);
|
|
52
|
+
this.timer.unref?.();
|
|
53
|
+
}
|
|
54
|
+
stop() {
|
|
55
|
+
if (!this.timer)
|
|
56
|
+
return;
|
|
57
|
+
clearInterval(this.timer);
|
|
58
|
+
this.timer = null;
|
|
59
|
+
}
|
|
60
|
+
/** Test seam — exercises one tick without waiting on real timers. */
|
|
61
|
+
tick() {
|
|
62
|
+
const current = this.now();
|
|
63
|
+
const gapMs = current - this.lastTickMs - this.intervalMs;
|
|
64
|
+
this.lastTickMs = current;
|
|
65
|
+
if (gapMs < this.gapThresholdMs)
|
|
66
|
+
return;
|
|
67
|
+
logger.warn({ gapMinutes: Math.round(gapMs / 60_000) }, "Wall-clock gap detected (machine sleep or clock jump) — running wake catch-up");
|
|
68
|
+
try {
|
|
69
|
+
const result = this.onWake(gapMs);
|
|
70
|
+
if (result && typeof result.then === "function") {
|
|
71
|
+
result.then(undefined, (err) => {
|
|
72
|
+
logger.error({ err }, "Wake catch-up handler failed");
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
logger.error({ err }, "Wake catch-up handler threw");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* time. The lock is held until the dispatcher releases it via
|
|
13
13
|
* `releaseWikiCompileLock` in `executeDefault`'s `finally` block.
|
|
14
14
|
* - The lock is purely in-process; that matches the dispatcher's
|
|
15
|
-
* existing concurrency invariants (`
|
|
15
|
+
* existing concurrency invariants (`activityScanInProgress`,
|
|
16
16
|
* `morningRoutineActive` are also in-memory flags). A second
|
|
17
17
|
* daemon process would not see the lock — but the daemon is
|
|
18
18
|
* single-process by design.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* time. The lock is held until the dispatcher releases it via
|
|
13
13
|
* `releaseWikiCompileLock` in `executeDefault`'s `finally` block.
|
|
14
14
|
* - The lock is purely in-process; that matches the dispatcher's
|
|
15
|
-
* existing concurrency invariants (`
|
|
15
|
+
* existing concurrency invariants (`activityScanInProgress`,
|
|
16
16
|
* `morningRoutineActive` are also in-memory flags). A second
|
|
17
17
|
* daemon process would not see the lock — but the daemon is
|
|
18
18
|
* single-process by design.
|
|
@@ -110,15 +110,22 @@ function extractTitleAndBody(content) {
|
|
|
110
110
|
return { title, body };
|
|
111
111
|
}
|
|
112
112
|
function stripFrontmatter(content) {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// Obsidian vaults authored/synced on Windows (or checked out under
|
|
114
|
+
// git core.autocrlf=true) are CRLF. The frontmatter fence gate below is
|
|
115
|
+
// LF-only, so without this normalize a `---\r\n…---\r\n` block leaks into
|
|
116
|
+
// the indexed body and the title fallback never populates. Indexed
|
|
117
|
+
// body/title are search tokens only and never round-trip to disk, so
|
|
118
|
+
// collapsing interior CRLF to LF is harmless (LF input is unchanged).
|
|
119
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
120
|
+
if (!normalized.startsWith("---\n")) {
|
|
121
|
+
return { frontmatterKeys: {}, body: normalized };
|
|
115
122
|
}
|
|
116
|
-
const end =
|
|
123
|
+
const end = normalized.indexOf("\n---\n", 4);
|
|
117
124
|
if (end < 0) {
|
|
118
|
-
return { frontmatterKeys: {}, body:
|
|
125
|
+
return { frontmatterKeys: {}, body: normalized };
|
|
119
126
|
}
|
|
120
|
-
const frontmatter =
|
|
121
|
-
const body =
|
|
127
|
+
const frontmatter = normalized.slice(4, end);
|
|
128
|
+
const body = normalized.slice(end + 5);
|
|
122
129
|
const keys = {};
|
|
123
130
|
for (const line of frontmatter.split("\n")) {
|
|
124
131
|
const match = line.match(/^title:\s*(.*?)\s*$/);
|
package/dist/core/workdir.js
CHANGED
|
@@ -25,7 +25,7 @@ import { createLogger } from "../logging.js";
|
|
|
25
25
|
import { SkillsCompiler } from "./skills-compiler.js";
|
|
26
26
|
import { EMPTY_MAIL_ACCOUNTS_MD, renderMailAccountsMd, } from "./skills-compiler-tree.js";
|
|
27
27
|
import { refreshSkillIndexBlock } from "./skills-compiler-skill-index.js";
|
|
28
|
-
import { getProfileForEvent, resolveSkillManifest, resolveSkillManifestForProcess, } from "./skills-manifest.js";
|
|
28
|
+
import { eventTypeAcceptsUserSkills, getProfileForEvent, resolveSkillManifest, resolveSkillManifestForProcess, } from "./skills-manifest.js";
|
|
29
29
|
import { ensureDaemonApiCli } from "./daemon-api-cli.js";
|
|
30
30
|
import { computeInstructionAssetStatus, readInstructionStampManifest, sessionInstructionAssetsStale, writeInstructionAssetStamp, } from "./release-assets.js";
|
|
31
31
|
const logger = createLogger("workdir");
|
|
@@ -252,11 +252,15 @@ export function createSessionWorkdir(projectRoot, eventType, userSkillsDir, opti
|
|
|
252
252
|
// turns, manifest now resolves to a different skill set).
|
|
253
253
|
{ processKey: options?.processKey ?? eventType, skillSlugs: deployed.skills });
|
|
254
254
|
ensureDaemonApiCli(sessionDir);
|
|
255
|
-
// User skills: every skill the user has authored,
|
|
256
|
-
//
|
|
257
|
-
//
|
|
255
|
+
// User skills: every skill the user has authored, EXCEPT for
|
|
256
|
+
// narrow-persona keys (wiki.* / routine.research_*) which run a tight
|
|
257
|
+
// built-in manifest and would only be diluted by the owner's general
|
|
258
|
+
// skill library — see `eventTypeAcceptsUserSkills`. Uses the
|
|
259
|
+
// manifest-backed sync so the initial population is consistent with the
|
|
260
|
+
// per-message sync the dispatcher runs on Opus events.
|
|
258
261
|
let userSync = null;
|
|
259
|
-
if (userSkillsDir
|
|
262
|
+
if (userSkillsDir &&
|
|
263
|
+
eventTypeAcceptsUserSkills(options?.processKey ?? eventType)) {
|
|
260
264
|
userSync = syncAllUserSkills(sessionDir, userSkillsDir);
|
|
261
265
|
// docs/design/appendices/skills-unification.md Phase 1 §R4 — splice user-authored
|
|
262
266
|
// slugs into the `<skill-index>` block AFTER they land on disk.
|
|
@@ -419,7 +423,12 @@ export function ensureSessionWorkdir(projectRoot, dataDir, dbSessionId, eventTyp
|
|
|
419
423
|
// CONTEXT_VAULT_REDESIGN — falling back there would resurrect the
|
|
420
424
|
// Obsidian-mode divergence bug v4 V11 fixed.
|
|
421
425
|
const userSkillsDir = join(options?.contextDir ?? join(dataDir, "context"), "policies", "skills");
|
|
422
|
-
|
|
426
|
+
// Narrow-persona keys (wiki.* / routine.research_*) skip the owner's
|
|
427
|
+
// user-skill library; their tight built-in manifest is the whole
|
|
428
|
+
// surface. See `eventTypeAcceptsUserSkills`.
|
|
429
|
+
const userSync = eventTypeAcceptsUserSkills(options?.processKey ?? eventType)
|
|
430
|
+
? syncAllUserSkills(sessionDir, userSkillsDir)
|
|
431
|
+
: null;
|
|
423
432
|
// docs/design/appendices/skills-unification.md Phase 1 §R4 — fold user-authored slugs
|
|
424
433
|
// into the `<skill-index>` block now that they're on disk. Idempotent
|
|
425
434
|
// and inexpensive (single instruction-file rewrite).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage-0 signal compute for the three-stage activity_scan gate
|
|
3
|
+
* (cost-reduction-structural §B). Pure DB-read shape: every value is
|
|
4
|
+
* derived from existing tables (`observations`, `mail_messages_index`,
|
|
5
|
+
* `agent_schedule`, `agent_actions`) plus the optional today.md content
|
|
6
|
+
* passed in by the caller. No LLM call, no filesystem I/O of its own —
|
|
7
|
+
* the dispatcher injects the today.md snapshot (when available) so this
|
|
8
|
+
* module stays unit-testable from a synthetic Database handle.
|
|
9
|
+
*
|
|
10
|
+
* The shape mirrors the design doc 1:1 so the gate (Stage 1) can be
|
|
11
|
+
* a pure function over `ActivityScanSignals` + a config block.
|
|
12
|
+
*/
|
|
13
|
+
import type Database from "better-sqlite3";
|
|
14
|
+
export interface ActivityScanSignals {
|
|
15
|
+
/** Post dedup-against-today user-actor pending observation count. */
|
|
16
|
+
pendingObsCount: number;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum `novelty_score` across pending user observations whose
|
|
19
|
+
* `summary_status='done'`. `null` when no observation has a done
|
|
20
|
+
* summary yet — the gate treats null as a cautious mid-novelty default
|
|
21
|
+
* (see decideStage).
|
|
22
|
+
*/
|
|
23
|
+
maxNoveltyScore: number | null;
|
|
24
|
+
/** Histogram of novelty levels across pending+done observations. */
|
|
25
|
+
noveltyDistribution: {
|
|
26
|
+
low: number;
|
|
27
|
+
mid: number;
|
|
28
|
+
high: number;
|
|
29
|
+
};
|
|
30
|
+
/** Count of unread mail rows whose sender is in the VIP list. */
|
|
31
|
+
vipMailUnreadCount: number;
|
|
32
|
+
/** True when at least one pending calendar observation exists. */
|
|
33
|
+
calendarHas24hChange: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* True when at least two pending calendar observations carry
|
|
36
|
+
* overlapping `start`/`end` ISO timestamps in their JSON payload
|
|
37
|
+
* within the next 24 hours. Tolerant of missing fields — when the
|
|
38
|
+
* payload shape isn't recognizable, returns false (the LLM still
|
|
39
|
+
* sees the underlying observation in Stage 3 if escalated).
|
|
40
|
+
*/
|
|
41
|
+
calendarHasConflict: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Number of `## Agent Plan` rows in today.md whose HH:MM is before
|
|
44
|
+
* `now` in the agent timezone. Best-effort regex parse — when
|
|
45
|
+
* today.md is missing or unparseable, returns 0.
|
|
46
|
+
*/
|
|
47
|
+
agentPlanOverdueCount: number;
|
|
48
|
+
/** Count of `agent_schedule` rows due in the next 6 hours. */
|
|
49
|
+
scheduleApproachingCount: number;
|
|
50
|
+
/**
|
|
51
|
+
* Hours since the last activity_scan that escalated to Stage 3 (or
|
|
52
|
+
* before the gate was deployed, since the last activity_scan run at
|
|
53
|
+
* all). `Infinity` when no row has been recorded yet.
|
|
54
|
+
*/
|
|
55
|
+
hoursSinceLastStage3Run: number;
|
|
56
|
+
}
|
|
57
|
+
export interface ComputeActivityScanSignalsOptions {
|
|
58
|
+
/** Owner-VIP mail addresses, lowercased exact-match. */
|
|
59
|
+
vipMailSenders?: readonly string[];
|
|
60
|
+
/** Look-ahead window for `scheduleApproachingCount`. Default 6h. */
|
|
61
|
+
scheduleHorizonHours?: number;
|
|
62
|
+
/** Look-ahead window for calendar conflict detection. Default 24h. */
|
|
63
|
+
calendarHorizonHours?: number;
|
|
64
|
+
/** today.md content snapshot (or null when missing/unreadable). */
|
|
65
|
+
todayMd?: string | null;
|
|
66
|
+
/** Wall-clock anchor — injectable for tests. Defaults to `new Date()`. */
|
|
67
|
+
now?: Date;
|
|
68
|
+
/**
|
|
69
|
+
* Agent timezone (IANA name, e.g. `Asia/Tokyo`) used to compare
|
|
70
|
+
* `## Agent Plan` HH:MM rows against `now`. When omitted, falls back
|
|
71
|
+
* to the JS engine's local timezone — fine in the common single-user
|
|
72
|
+
* deployment but wrong if the daemon runs in UTC and the operator
|
|
73
|
+
* pinned a different `config.timezone`.
|
|
74
|
+
*/
|
|
75
|
+
agentTimezone?: string;
|
|
76
|
+
}
|
|
77
|
+
export declare function computeActivityScanSignals(db: Database.Database, options?: ComputeActivityScanSignalsOptions): ActivityScanSignals;
|