@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
|
@@ -36,7 +36,7 @@ export declare function substituteCron(expr: string, config: {
|
|
|
36
36
|
* expression. Returns a non-fatal warning string on mismatch, or `null` when
|
|
37
37
|
* they agree.
|
|
38
38
|
*
|
|
39
|
-
* `registryExpr` is `null` for the runtime-window builtins (`
|
|
39
|
+
* `registryExpr` is `null` for the runtime-window builtins (`activity-scan`)
|
|
40
40
|
* whose cadence is not a fixed expression — drift is meaningless there, so the
|
|
41
41
|
* check is a no-op (returns `null`, §5.5.1). Whitespace differences are
|
|
42
42
|
* normalised away before comparing.
|
|
@@ -57,7 +57,7 @@ function normalizeCron(expr) {
|
|
|
57
57
|
* expression. Returns a non-fatal warning string on mismatch, or `null` when
|
|
58
58
|
* they agree.
|
|
59
59
|
*
|
|
60
|
-
* `registryExpr` is `null` for the runtime-window builtins (`
|
|
60
|
+
* `registryExpr` is `null` for the runtime-window builtins (`activity-scan`)
|
|
61
61
|
* whose cadence is not a fixed expression — drift is meaningless there, so the
|
|
62
62
|
* check is a no-op (returns `null`, §5.5.1). Whitespace differences are
|
|
63
63
|
* normalised away before comparing.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
/**
|
|
3
|
+
* One-time converter: legacy custom routines → user Agents
|
|
4
|
+
* (AGENTS_HUB_REDESIGN_PLAN.md §3).
|
|
5
|
+
*
|
|
6
|
+
* The retired `CustomRoutineScheduler` fired `policies/routines/custom/
|
|
7
|
+
* <slug>.md` files through its own node-cron path — invisible to `/agents`,
|
|
8
|
+
* no metrics, no execution history, a second enable switch. This converter
|
|
9
|
+
* runs at boot BEFORE the agents loader (so the loader pairs the fresh
|
|
10
|
+
* definitions with `recurring_schedules` rows in the same pass), turning each
|
|
11
|
+
* valid spec into `policies/agents/<slug>/agent.md`:
|
|
12
|
+
*
|
|
13
|
+
* - frontmatter: cron schedule, tier, `max_budget_usd`, the spec's
|
|
14
|
+
* `enabled` (a disabled routine migrates disabled — intent preserved);
|
|
15
|
+
* - body: the routine's body after the legacy frontmatter (the `## Checks`
|
|
16
|
+
* section), which the Agents pipeline uses as `task_prompt`.
|
|
17
|
+
*
|
|
18
|
+
* The source file is never deleted (user vault content): it is rewritten in
|
|
19
|
+
* place with `enabled: false` + a `migrated_to_agent:` marker so it is
|
|
20
|
+
* visibly inert. Invalid specs are left untouched and logged — they never
|
|
21
|
+
* fired under the old scheduler either.
|
|
22
|
+
*
|
|
23
|
+
* Idempotent via the `runtime_state` flag; a fresh install (no custom dir)
|
|
24
|
+
* is a flagged no-op.
|
|
25
|
+
*/
|
|
26
|
+
export declare const CUSTOM_ROUTINES_MIGRATED_KEY = "custom_routines.migrated_to_agents";
|
|
27
|
+
export interface CustomRoutineMigrationOptions {
|
|
28
|
+
/** Context-vault root (the directory holding `policies/`). */
|
|
29
|
+
contextDir: string;
|
|
30
|
+
/** User agents root — `<contextDir>/policies/agents`. */
|
|
31
|
+
userDir: string;
|
|
32
|
+
/** IANA timezone the generated cron schedule is pinned to. */
|
|
33
|
+
timezone: string;
|
|
34
|
+
now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
export interface CustomRoutineMigrationResult {
|
|
37
|
+
/** False when the runtime_state flag showed the migration already ran. */
|
|
38
|
+
applied: boolean;
|
|
39
|
+
migrated: Array<{
|
|
40
|
+
fromSlug: string;
|
|
41
|
+
toSlug: string;
|
|
42
|
+
}>;
|
|
43
|
+
skipped: Array<{
|
|
44
|
+
slug: string;
|
|
45
|
+
reason: string;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Body after the closing `---` fence, or the whole content if no frontmatter.
|
|
50
|
+
* Exported for direct unit tests — callers only ever pass parse-validated
|
|
51
|
+
* content, so the defensive branches are unreachable through the public path.
|
|
52
|
+
*/
|
|
53
|
+
export declare function stripLegacyFrontmatter(content: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Mark a migrated source file inert: `enabled: false` + a
|
|
56
|
+
* `migrated_to_agent:` marker appended inside the frontmatter block.
|
|
57
|
+
* Exported for direct unit tests (see stripLegacyFrontmatter note).
|
|
58
|
+
*/
|
|
59
|
+
export declare function markSourceMigrated(content: string, toSlug: string): string;
|
|
60
|
+
export declare function migrateCustomRoutinesToAgents(db: Database.Database, opts: CustomRoutineMigrationOptions): CustomRoutineMigrationResult;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { agentDefinitionSchema } from "@aitne/shared";
|
|
4
|
+
import { enumerateCustomRoutines, } from "../custom-routines.js";
|
|
5
|
+
import { CONTEXT_RELATIVE_PATHS } from "../context-paths.js";
|
|
6
|
+
import { renderAgentMarkdown } from "./agent-frontmatter.js";
|
|
7
|
+
import { definitionToFrontmatter } from "./loader.js";
|
|
8
|
+
import { isBuiltinAgentSlug } from "./builtin-registry.js";
|
|
9
|
+
import { getAgent } from "../../db/agents-store.js";
|
|
10
|
+
import { readRuntimeState, writeRuntimeState } from "../../db/runtime-state.js";
|
|
11
|
+
import { createLogger } from "../../logging.js";
|
|
12
|
+
const logger = createLogger("custom-routine-migration");
|
|
13
|
+
/**
|
|
14
|
+
* One-time converter: legacy custom routines → user Agents
|
|
15
|
+
* (AGENTS_HUB_REDESIGN_PLAN.md §3).
|
|
16
|
+
*
|
|
17
|
+
* The retired `CustomRoutineScheduler` fired `policies/routines/custom/
|
|
18
|
+
* <slug>.md` files through its own node-cron path — invisible to `/agents`,
|
|
19
|
+
* no metrics, no execution history, a second enable switch. This converter
|
|
20
|
+
* runs at boot BEFORE the agents loader (so the loader pairs the fresh
|
|
21
|
+
* definitions with `recurring_schedules` rows in the same pass), turning each
|
|
22
|
+
* valid spec into `policies/agents/<slug>/agent.md`:
|
|
23
|
+
*
|
|
24
|
+
* - frontmatter: cron schedule, tier, `max_budget_usd`, the spec's
|
|
25
|
+
* `enabled` (a disabled routine migrates disabled — intent preserved);
|
|
26
|
+
* - body: the routine's body after the legacy frontmatter (the `## Checks`
|
|
27
|
+
* section), which the Agents pipeline uses as `task_prompt`.
|
|
28
|
+
*
|
|
29
|
+
* The source file is never deleted (user vault content): it is rewritten in
|
|
30
|
+
* place with `enabled: false` + a `migrated_to_agent:` marker so it is
|
|
31
|
+
* visibly inert. Invalid specs are left untouched and logged — they never
|
|
32
|
+
* fired under the old scheduler either.
|
|
33
|
+
*
|
|
34
|
+
* Idempotent via the `runtime_state` flag; a fresh install (no custom dir)
|
|
35
|
+
* is a flagged no-op.
|
|
36
|
+
*/
|
|
37
|
+
export const CUSTOM_ROUTINES_MIGRATED_KEY = "custom_routines.migrated_to_agents";
|
|
38
|
+
/**
|
|
39
|
+
* Body after the closing `---` fence, or the whole content if no frontmatter.
|
|
40
|
+
* Exported for direct unit tests — callers only ever pass parse-validated
|
|
41
|
+
* content, so the defensive branches are unreachable through the public path.
|
|
42
|
+
*/
|
|
43
|
+
export function stripLegacyFrontmatter(content) {
|
|
44
|
+
if (!content.startsWith("---\n") && !content.startsWith("---\r\n"))
|
|
45
|
+
return content;
|
|
46
|
+
const afterOpen = content.startsWith("---\r\n") ? 5 : 4;
|
|
47
|
+
const endIdx = content.indexOf("\n---", afterOpen - 1);
|
|
48
|
+
if (endIdx < 0)
|
|
49
|
+
return content;
|
|
50
|
+
const afterFence = content.indexOf("\n", endIdx + 1);
|
|
51
|
+
return afterFence < 0 ? "" : content.slice(afterFence + 1);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mark a migrated source file inert: `enabled: false` + a
|
|
55
|
+
* `migrated_to_agent:` marker appended inside the frontmatter block.
|
|
56
|
+
* Exported for direct unit tests (see stripLegacyFrontmatter note).
|
|
57
|
+
*/
|
|
58
|
+
export function markSourceMigrated(content, toSlug) {
|
|
59
|
+
let out = content.replace(/^enabled\s*:.*$/m, "enabled: false");
|
|
60
|
+
if (!/^migrated_to_agent\s*:/m.test(out)) {
|
|
61
|
+
// Insert before the closing fence of the first frontmatter block.
|
|
62
|
+
const afterOpen = out.startsWith("---\r\n") ? 5 : 4;
|
|
63
|
+
const endIdx = out.indexOf("\n---", afterOpen - 1);
|
|
64
|
+
if (endIdx >= 0) {
|
|
65
|
+
out = `${out.slice(0, endIdx)}\nmigrated_to_agent: ${toSlug}${out.slice(endIdx)}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function titleize(slug) {
|
|
71
|
+
return slug
|
|
72
|
+
.split("-")
|
|
73
|
+
.map((part) => (part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part))
|
|
74
|
+
.join(" ");
|
|
75
|
+
}
|
|
76
|
+
function resolveTargetSlug(db, userDir, slug) {
|
|
77
|
+
const candidates = [slug, `custom-${slug}`];
|
|
78
|
+
for (const candidate of candidates) {
|
|
79
|
+
if (isBuiltinAgentSlug(candidate))
|
|
80
|
+
continue;
|
|
81
|
+
if (getAgent(db, candidate) !== null)
|
|
82
|
+
continue;
|
|
83
|
+
if (existsSync(join(userDir, candidate, "agent.md")))
|
|
84
|
+
continue;
|
|
85
|
+
return candidate;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
export function migrateCustomRoutinesToAgents(db, opts) {
|
|
90
|
+
if (readRuntimeState(db, CUSTOM_ROUTINES_MIGRATED_KEY) !== null) {
|
|
91
|
+
return { applied: false, migrated: [], skipped: [] };
|
|
92
|
+
}
|
|
93
|
+
const now = opts.now ?? Date.now;
|
|
94
|
+
const { specs, errors } = enumerateCustomRoutines(opts.contextDir);
|
|
95
|
+
const migrated = [];
|
|
96
|
+
const skipped = [];
|
|
97
|
+
for (const { slug, error } of errors) {
|
|
98
|
+
skipped.push({ slug, reason: `parse_error:${error.kind}` });
|
|
99
|
+
logger.warn({ slug, error }, "Custom routine not migrated (parse error) — left as-is");
|
|
100
|
+
}
|
|
101
|
+
for (const spec of specs) {
|
|
102
|
+
try {
|
|
103
|
+
const result = migrateOne(db, opts, spec);
|
|
104
|
+
if (result.ok) {
|
|
105
|
+
migrated.push({ fromSlug: spec.slug, toSlug: result.toSlug });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
skipped.push({ slug: spec.slug, reason: result.reason });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
skipped.push({ slug: spec.slug, reason: "write_failed" });
|
|
113
|
+
logger.error({ err, slug: spec.slug }, "Custom routine migration failed for slug");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
writeRuntimeState(db, CUSTOM_ROUTINES_MIGRATED_KEY, new Date(now()).toISOString());
|
|
117
|
+
if (migrated.length > 0 || skipped.length > 0) {
|
|
118
|
+
logger.info({ migrated, skipped }, "Custom routines migrated to user Agents");
|
|
119
|
+
}
|
|
120
|
+
return { applied: true, migrated, skipped };
|
|
121
|
+
}
|
|
122
|
+
function migrateOne(db, opts, spec) {
|
|
123
|
+
const toSlug = resolveTargetSlug(db, opts.userDir, spec.slug);
|
|
124
|
+
if (toSlug === null) {
|
|
125
|
+
logger.warn({ slug: spec.slug }, "Custom routine not migrated (slug collision)");
|
|
126
|
+
return { ok: false, reason: "slug_collision" };
|
|
127
|
+
}
|
|
128
|
+
const sourcePath = join(opts.contextDir, CONTEXT_RELATIVE_PATHS.routines.customDir, `${spec.slug}.md`);
|
|
129
|
+
const sourceContent = readFileSync(sourcePath, "utf-8");
|
|
130
|
+
const body = stripLegacyFrontmatter(sourceContent).trim();
|
|
131
|
+
const def = agentDefinitionSchema.parse({
|
|
132
|
+
slug: toSlug,
|
|
133
|
+
name: titleize(spec.slug),
|
|
134
|
+
description: `Migrated from custom routine "${spec.slug}".`,
|
|
135
|
+
kind: "user",
|
|
136
|
+
enabled: spec.enabled,
|
|
137
|
+
schedule: { kind: "cron", expression: spec.cron, timezone: opts.timezone },
|
|
138
|
+
backend: { process_key: "agent.task", tier: spec.backendTier },
|
|
139
|
+
limits: { max_budget_usd: spec.maxBudgetUsd },
|
|
140
|
+
});
|
|
141
|
+
const markdown = renderAgentMarkdown(definitionToFrontmatter(def), body.length > 0 ? body : def.description);
|
|
142
|
+
const dir = join(opts.userDir, toSlug);
|
|
143
|
+
mkdirSync(dir, { recursive: true });
|
|
144
|
+
writeFileSync(join(dir, "agent.md"), markdown, "utf-8");
|
|
145
|
+
// Mark the source inert only AFTER the agent.md write succeeded.
|
|
146
|
+
writeFileSync(sourcePath, markSourceMigrated(sourceContent, toSlug), "utf-8");
|
|
147
|
+
logger.info({ from: spec.slug, to: toSlug }, "Custom routine migrated to user Agent");
|
|
148
|
+
return { ok: true, toSlug };
|
|
149
|
+
}
|
|
@@ -3,7 +3,7 @@ import type Database from "better-sqlite3";
|
|
|
3
3
|
* `agent.firing_blocked` audit throttle (AGENT_DEFINITIONS_DESIGN.md §12.3).
|
|
4
4
|
*
|
|
5
5
|
* When a disabled built-in Agent's cron would have fired, the scheduler records
|
|
6
|
-
* the suppressed firing — but a routine on a tight cadence (
|
|
6
|
+
* the suppressed firing — but a routine on a tight cadence (activity_scan) could
|
|
7
7
|
* otherwise write dozens of identical audit rows a day. The throttle keeps **at
|
|
8
8
|
* most one** `agent_actions(action_type='agent.firing_blocked')` row per
|
|
9
9
|
* `(agent_id, agent-day)`; every subsequent suppression in the same agent-day
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hourly-check cadence resolution (AGENTS_HUB_REDESIGN_PLAN.md §2).
|
|
3
|
+
*
|
|
4
|
+
* The hourly-check 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
|
+
* hourly-check` (`schedule_window` body block) and preserved across loader
|
|
8
|
+
* re-runs / `npm i -g` by `loader.ts:nextMetadata`. The legacy `hourlyCheck*`
|
|
9
|
+
* config keys are deprecated but still parsed; they act as the per-field
|
|
10
|
+
* fallback so a value an operator persisted pre-redesign keeps working until
|
|
11
|
+
* they touch the agent-level setting.
|
|
12
|
+
*
|
|
13
|
+
* Resolution order, per field: `runtime_window` override → legacy config key
|
|
14
|
+
* (which itself carries the shipped default). Pure module — callers fetch the
|
|
15
|
+
* stored override via `agents-store.ts:getRuntimeWindow` and supply the live
|
|
16
|
+
* config; this keeps the precedence logic in the 100%-coverage set.
|
|
17
|
+
*/
|
|
18
|
+
/** Field bounds, shared by the PATCH validator and the sanitizer. */
|
|
19
|
+
export declare const RUNTIME_WINDOW_BOUNDS: {
|
|
20
|
+
readonly interval_minutes: {
|
|
21
|
+
readonly min: 5;
|
|
22
|
+
readonly max: 1440;
|
|
23
|
+
};
|
|
24
|
+
readonly active_start_hour: {
|
|
25
|
+
readonly min: 0;
|
|
26
|
+
readonly max: 23;
|
|
27
|
+
};
|
|
28
|
+
readonly active_end_hour: {
|
|
29
|
+
readonly min: 1;
|
|
30
|
+
readonly max: 24;
|
|
31
|
+
};
|
|
32
|
+
readonly min_observations: {
|
|
33
|
+
readonly min: 0;
|
|
34
|
+
readonly max: 1000;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type RuntimeWindowField = keyof typeof RUNTIME_WINDOW_BOUNDS;
|
|
38
|
+
export declare const RUNTIME_WINDOW_FIELDS: readonly RuntimeWindowField[];
|
|
39
|
+
/**
|
|
40
|
+
* The persisted shape under `metadata_json.runtime_window`. Every field is
|
|
41
|
+
* optional — only operator-touched fields are stored, so an untouched field
|
|
42
|
+
* keeps tracking the config fallback.
|
|
43
|
+
*/
|
|
44
|
+
export interface RuntimeWindowOverride {
|
|
45
|
+
interval_minutes?: number;
|
|
46
|
+
active_start_hour?: number;
|
|
47
|
+
active_end_hour?: number;
|
|
48
|
+
min_observations?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The legacy config keys the resolver falls back to. Fields are optional at
|
|
52
|
+
* the type level so partially-stubbed test configs (and any pre-schema boot
|
|
53
|
+
* edge) resolve to the shipped defaults instead of producing a `NaN` cron.
|
|
54
|
+
*/
|
|
55
|
+
export interface HourlyCadenceConfig {
|
|
56
|
+
hourlyCheckIntervalMinutes?: number;
|
|
57
|
+
hourlyCheckActiveStartHour?: number;
|
|
58
|
+
hourlyCheckActiveEndHour?: number;
|
|
59
|
+
hourlyCheckMinObservations?: number;
|
|
60
|
+
}
|
|
61
|
+
/** Shipped defaults — mirror `runtime-settings.ts` (`hourlyCheck*` keys). */
|
|
62
|
+
export declare const HOURLY_CADENCE_DEFAULTS: {
|
|
63
|
+
readonly intervalMinutes: 60;
|
|
64
|
+
readonly activeStartHour: 4;
|
|
65
|
+
readonly activeEndHour: 24;
|
|
66
|
+
readonly minObservations: 1;
|
|
67
|
+
};
|
|
68
|
+
/** Fully-resolved cadence every consumer (scheduler, gate, API) reads. */
|
|
69
|
+
export interface ResolvedHourlyCadence {
|
|
70
|
+
intervalMinutes: number;
|
|
71
|
+
activeStartHour: number;
|
|
72
|
+
activeEndHour: number;
|
|
73
|
+
minObservations: number;
|
|
74
|
+
}
|
|
75
|
+
/** True when `value` is an integer within the field's bounds. */
|
|
76
|
+
export declare function isValidRuntimeWindowValue(field: RuntimeWindowField, value: unknown): value is number;
|
|
77
|
+
/**
|
|
78
|
+
* Sanitize a raw `metadata_json.runtime_window` blob (untrusted: hand-edited
|
|
79
|
+
* DBs, older daemons). Out-of-bounds / non-integer fields are dropped — the
|
|
80
|
+
* resolver then falls back to config for them rather than failing the boot.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseRuntimeWindowOverride(value: unknown): RuntimeWindowOverride;
|
|
83
|
+
export declare function resolveHourlyCadence(override: RuntimeWindowOverride | undefined, config: HourlyCadenceConfig): ResolvedHourlyCadence;
|
|
84
|
+
export type RuntimeWindowMergeResult = {
|
|
85
|
+
ok: true;
|
|
86
|
+
value: RuntimeWindowOverride;
|
|
87
|
+
cadenceChanged: boolean;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
field: string;
|
|
91
|
+
error: "invalid_field_value" | "invalid_window";
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Merge a PATCH `schedule_window` block onto the stored override. Per-field
|
|
95
|
+
* type/bounds are validated; the cross-field window check (`end > start`) runs
|
|
96
|
+
* against the post-merge **resolved** values so a partial patch can't sneak an
|
|
97
|
+
* empty window past per-field validation. `null` resets a field back to the
|
|
98
|
+
* config fallback. `cadenceChanged` tells the route whether a cron rebuild
|
|
99
|
+
* (`reloadCrons`) is needed — `min_observations` is a fire-time gate and never
|
|
100
|
+
* requires one.
|
|
101
|
+
*/
|
|
102
|
+
export declare function mergeRuntimeWindow(current: RuntimeWindowOverride, patch: Record<string, unknown>, config: HourlyCadenceConfig): RuntimeWindowMergeResult;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hourly-check cadence resolution (AGENTS_HUB_REDESIGN_PLAN.md §2).
|
|
3
|
+
*
|
|
4
|
+
* The hourly-check 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
|
+
* hourly-check` (`schedule_window` body block) and preserved across loader
|
|
8
|
+
* re-runs / `npm i -g` by `loader.ts:nextMetadata`. The legacy `hourlyCheck*`
|
|
9
|
+
* config keys are deprecated but still parsed; they act as the per-field
|
|
10
|
+
* fallback so a value an operator persisted pre-redesign keeps working until
|
|
11
|
+
* they touch the agent-level setting.
|
|
12
|
+
*
|
|
13
|
+
* Resolution order, per field: `runtime_window` override → legacy config key
|
|
14
|
+
* (which itself carries the shipped default). Pure module — callers fetch the
|
|
15
|
+
* stored override via `agents-store.ts:getRuntimeWindow` and supply the live
|
|
16
|
+
* config; this keeps the precedence logic in the 100%-coverage set.
|
|
17
|
+
*/
|
|
18
|
+
/** Field bounds, shared by the PATCH validator and the sanitizer. */
|
|
19
|
+
export const RUNTIME_WINDOW_BOUNDS = {
|
|
20
|
+
interval_minutes: { min: 5, max: 1440 },
|
|
21
|
+
active_start_hour: { min: 0, max: 23 },
|
|
22
|
+
active_end_hour: { min: 1, max: 24 },
|
|
23
|
+
min_observations: { min: 0, max: 1000 },
|
|
24
|
+
};
|
|
25
|
+
export const RUNTIME_WINDOW_FIELDS = Object.keys(RUNTIME_WINDOW_BOUNDS);
|
|
26
|
+
/** Shipped defaults — mirror `runtime-settings.ts` (`hourlyCheck*` keys). */
|
|
27
|
+
export const HOURLY_CADENCE_DEFAULTS = {
|
|
28
|
+
intervalMinutes: 60,
|
|
29
|
+
activeStartHour: 4,
|
|
30
|
+
activeEndHour: 24,
|
|
31
|
+
minObservations: 1,
|
|
32
|
+
};
|
|
33
|
+
/** True when `value` is an integer within the field's bounds. */
|
|
34
|
+
export function isValidRuntimeWindowValue(field, value) {
|
|
35
|
+
if (typeof value !== "number" || !Number.isInteger(value))
|
|
36
|
+
return false;
|
|
37
|
+
const { min, max } = RUNTIME_WINDOW_BOUNDS[field];
|
|
38
|
+
return value >= min && value <= max;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Sanitize a raw `metadata_json.runtime_window` blob (untrusted: hand-edited
|
|
42
|
+
* DBs, older daemons). Out-of-bounds / non-integer fields are dropped — the
|
|
43
|
+
* resolver then falls back to config for them rather than failing the boot.
|
|
44
|
+
*/
|
|
45
|
+
export function parseRuntimeWindowOverride(value) {
|
|
46
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
47
|
+
return {};
|
|
48
|
+
const raw = value;
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const field of RUNTIME_WINDOW_FIELDS) {
|
|
51
|
+
const v = raw[field];
|
|
52
|
+
if (isValidRuntimeWindowValue(field, v))
|
|
53
|
+
out[field] = v;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the effective cadence: per-field `runtime_window` override, else the
|
|
59
|
+
* legacy config key. A start ≥ end pairing (possible when an override touches
|
|
60
|
+
* only one side of an old config window) is repaired by widening the end to
|
|
61
|
+
* `start + 1` so `buildHourlyCronExpr` always receives a non-empty window.
|
|
62
|
+
*/
|
|
63
|
+
/** Pick `v` when it is a finite number, else `fallback`. */
|
|
64
|
+
function num(v, fallback) {
|
|
65
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
66
|
+
}
|
|
67
|
+
export function resolveHourlyCadence(override, config) {
|
|
68
|
+
const o = override ?? {};
|
|
69
|
+
const activeStartHour = o.active_start_hour
|
|
70
|
+
?? num(config.hourlyCheckActiveStartHour, HOURLY_CADENCE_DEFAULTS.activeStartHour);
|
|
71
|
+
let activeEndHour = o.active_end_hour
|
|
72
|
+
?? num(config.hourlyCheckActiveEndHour, HOURLY_CADENCE_DEFAULTS.activeEndHour);
|
|
73
|
+
if (activeEndHour <= activeStartHour)
|
|
74
|
+
activeEndHour = Math.min(activeStartHour + 1, 24);
|
|
75
|
+
return {
|
|
76
|
+
intervalMinutes: o.interval_minutes
|
|
77
|
+
?? num(config.hourlyCheckIntervalMinutes, HOURLY_CADENCE_DEFAULTS.intervalMinutes),
|
|
78
|
+
activeStartHour,
|
|
79
|
+
activeEndHour,
|
|
80
|
+
minObservations: o.min_observations
|
|
81
|
+
?? num(config.hourlyCheckMinObservations, HOURLY_CADENCE_DEFAULTS.minObservations),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Merge a PATCH `schedule_window` block onto the stored override. Per-field
|
|
86
|
+
* type/bounds are validated; the cross-field window check (`end > start`) runs
|
|
87
|
+
* against the post-merge **resolved** values so a partial patch can't sneak an
|
|
88
|
+
* empty window past per-field validation. `null` resets a field back to the
|
|
89
|
+
* config fallback. `cadenceChanged` tells the route whether a cron rebuild
|
|
90
|
+
* (`reloadCrons`) is needed — `min_observations` is a fire-time gate and never
|
|
91
|
+
* requires one.
|
|
92
|
+
*/
|
|
93
|
+
export function mergeRuntimeWindow(current, patch, config) {
|
|
94
|
+
const next = { ...current };
|
|
95
|
+
let cadenceChanged = false;
|
|
96
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
97
|
+
if (!RUNTIME_WINDOW_FIELDS.includes(key)) {
|
|
98
|
+
return { ok: false, field: `schedule_window.${key}`, error: "invalid_field_value" };
|
|
99
|
+
}
|
|
100
|
+
const field = key;
|
|
101
|
+
if (value === null) {
|
|
102
|
+
if (next[field] !== undefined) {
|
|
103
|
+
delete next[field];
|
|
104
|
+
if (field !== "min_observations")
|
|
105
|
+
cadenceChanged = true;
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!isValidRuntimeWindowValue(field, value)) {
|
|
110
|
+
return { ok: false, field: `schedule_window.${field}`, error: "invalid_field_value" };
|
|
111
|
+
}
|
|
112
|
+
if (next[field] !== value) {
|
|
113
|
+
next[field] = value;
|
|
114
|
+
if (field !== "min_observations")
|
|
115
|
+
cadenceChanged = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const requestedEnd = next.active_end_hour
|
|
119
|
+
?? num(config.hourlyCheckActiveEndHour, HOURLY_CADENCE_DEFAULTS.activeEndHour);
|
|
120
|
+
const requestedStart = next.active_start_hour
|
|
121
|
+
?? num(config.hourlyCheckActiveStartHour, HOURLY_CADENCE_DEFAULTS.activeStartHour);
|
|
122
|
+
if (requestedEnd <= requestedStart) {
|
|
123
|
+
return { ok: false, field: "schedule_window.active_end_hour", error: "invalid_window" };
|
|
124
|
+
}
|
|
125
|
+
return { ok: true, value: next, cadenceChanged };
|
|
126
|
+
}
|
|
@@ -4,6 +4,8 @@ import { createLogger } from "../../logging.js";
|
|
|
4
4
|
import { AgentEnabledCache, loadAgents, resolveTimezone, } from "./loader.js";
|
|
5
5
|
import { startAgentsWatcher, } from "./loader-watcher.js";
|
|
6
6
|
import { createRecurringSchedulePort } from "./recurring-schedule-adapter.js";
|
|
7
|
+
import { reconcileConfigGates } from "./config-gate-reconcile.js";
|
|
8
|
+
import { migrateCustomRoutinesToAgents } from "./custom-routine-migration.js";
|
|
7
9
|
import { listAllSkillSlugs } from "../release-assets.js";
|
|
8
10
|
import { resolveUserSkillsRoot } from "../user-skills-root.js";
|
|
9
11
|
/**
|
|
@@ -75,7 +77,28 @@ export function buildAgentLoadOptions(deps) {
|
|
|
75
77
|
*/
|
|
76
78
|
export function bootstrapAgents(deps) {
|
|
77
79
|
const opts = buildAgentLoadOptions(deps);
|
|
80
|
+
// One-time legacy custom-routine conversion (AGENTS_HUB_REDESIGN_PLAN §3).
|
|
81
|
+
// Runs BEFORE loadAgents so the loader pairs the generated user agent.md
|
|
82
|
+
// files with recurring_schedules rows in this same boot pass. Never throws
|
|
83
|
+
// out of boot.
|
|
84
|
+
try {
|
|
85
|
+
migrateCustomRoutinesToAgents(deps.db, {
|
|
86
|
+
contextDir: getContextDir(deps.config, deps.db),
|
|
87
|
+
userDir: opts.userDir,
|
|
88
|
+
timezone: resolveTimezone(undefined, deps.config.timezone),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
logger.error({ err }, "Custom-routine migration failed (continuing boot)");
|
|
93
|
+
}
|
|
78
94
|
const result = loadAgents(deps.db, opts);
|
|
95
|
+
// One-time legacy gate carry-over (activityScanEnabled / monthlyReviewEnabled
|
|
96
|
+
// → agents.enabled). Must run AFTER the loader has seeded the rows;
|
|
97
|
+
// runtime_state-flagged so it never re-applies (AGENTS_HUB_REDESIGN_PLAN §2).
|
|
98
|
+
reconcileConfigGates(deps.db, {
|
|
99
|
+
activityScanEnabled: deps.config.activityScanEnabled,
|
|
100
|
+
monthlyReviewEnabled: deps.config.monthlyReviewEnabled,
|
|
101
|
+
});
|
|
79
102
|
if (result.invalid.length > 0) {
|
|
80
103
|
logger.warn({ count: result.invalid.length, slugs: result.invalid.map((d) => d.slug) }, "Some Agent definitions failed to load (surfaced under /api/agents include_invalid)");
|
|
81
104
|
}
|
|
@@ -51,6 +51,14 @@ export interface RecurringCreateInput {
|
|
|
51
51
|
tier: string | null;
|
|
52
52
|
backendId: string | null;
|
|
53
53
|
recurrence: AgentRecurrenceSpec;
|
|
54
|
+
/**
|
|
55
|
+
* Row-local flags copied into every materialised `agent_schedule` row by
|
|
56
|
+
* `generateNextScheduleRow` (the `pin_to_quiet_hours_end` precedent). Today
|
|
57
|
+
* carries `defer_in_quiet_hours: true` for opted-in Agents
|
|
58
|
+
* (QUIET_HOURS_HARDENING_PLAN.md §6); omitted/empty otherwise (the store
|
|
59
|
+
* defaults the column to `{}`).
|
|
60
|
+
*/
|
|
61
|
+
taskContext?: Record<string, unknown>;
|
|
54
62
|
}
|
|
55
63
|
/** Divergent-field patch applied when reconciling a paired recurring row (YAML wins). */
|
|
56
64
|
export interface RecurringUpdateInput {
|
|
@@ -61,6 +69,9 @@ export interface RecurringUpdateInput {
|
|
|
61
69
|
tier?: string | null;
|
|
62
70
|
backendId?: string | null;
|
|
63
71
|
recurrence?: AgentRecurrenceSpec;
|
|
72
|
+
/** Full replacement `task_context` (the loader merges off the row's current
|
|
73
|
+
* value, so unrelated keys survive a flag flip). */
|
|
74
|
+
taskContext?: Record<string, unknown>;
|
|
64
75
|
}
|
|
65
76
|
/**
|
|
66
77
|
* Recurring-schedule pairing + auto-import port (§6.1 step 5 / §6.5). The
|
|
@@ -162,6 +173,14 @@ export declare function validateDefinition(def: AgentDefinition, source: AgentSo
|
|
|
162
173
|
* row with no override) lets the YAML win.
|
|
163
174
|
*/
|
|
164
175
|
export declare function resolveEnabled(baseEnabled: boolean, existing: AgentDTO | null, fileMtimeMs: number): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Render the subset of an `AgentDefinition` worth persisting to an auto-imported
|
|
178
|
+
* `agent.md` frontmatter. Schema defaults (limits/tools/on_error) are omitted
|
|
179
|
+
* to keep the generated file readable; they re-populate on the next parse.
|
|
180
|
+
* Exported for the custom-routine migration, which materialises user Agents
|
|
181
|
+
* through the same shape (AGENTS_HUB_REDESIGN_PLAN.md §3).
|
|
182
|
+
*/
|
|
183
|
+
export declare function definitionToFrontmatter(def: AgentDefinition): Record<string, unknown>;
|
|
165
184
|
/**
|
|
166
185
|
* Load every Agent definition into the `agents` table. Never throws; per-file
|
|
167
186
|
* failures land in {@link LoadAgentsResult.invalid}. Order: auto-import (writes
|
|
@@ -32,7 +32,7 @@ import { cronToRecurrenceSpec, recurrenceSpecToCron, } from "./recurrence-conver
|
|
|
32
32
|
* the real daemon modules.
|
|
33
33
|
*/
|
|
34
34
|
const baseLogger = createLogger("agents-loader");
|
|
35
|
-
/** A built-in whose registry `cronExpression` is `null` (
|
|
35
|
+
/** A built-in whose registry `cronExpression` is `null` (activity-scan) still
|
|
36
36
|
* needs a schema-valid `schedule.expression` when synthesised from the
|
|
37
37
|
* registry. The loader never schedules from it (the runtime window owns the
|
|
38
38
|
* cadence, §5.5.1); this literal is self-documenting only and drift-free
|
|
@@ -236,6 +236,12 @@ function nextMetadata(existing, hashChanged) {
|
|
|
236
236
|
if (prior.override_snapshot !== undefined) {
|
|
237
237
|
meta.override_snapshot = prior.override_snapshot;
|
|
238
238
|
}
|
|
239
|
+
// Runtime-window cadence overrides (activity-scan interval / active hours /
|
|
240
|
+
// observation gate) are operator state exactly like override_snapshot — they
|
|
241
|
+
// must survive every loader re-run and `npm i -g`.
|
|
242
|
+
if (prior.runtime_window !== undefined) {
|
|
243
|
+
meta.runtime_window = prior.runtime_window;
|
|
244
|
+
}
|
|
239
245
|
const priorVersion = typeof prior.version_counter === "number" ? prior.version_counter : 0;
|
|
240
246
|
meta.version_counter = hashChanged ? priorVersion + 1 : Math.max(priorVersion, 1);
|
|
241
247
|
// last_error is intentionally dropped: this path runs only for a definition
|
|
@@ -372,6 +378,14 @@ function pairRecurring(def, expression, body, existing, port, resolvedTz, resolv
|
|
|
372
378
|
tier: def.backend.tier,
|
|
373
379
|
backendId: def.backend.backend_id,
|
|
374
380
|
recurrence: spec,
|
|
381
|
+
// QUIET_HOURS_HARDENING_PLAN.md §6 — `generateNextScheduleRow` spreads the
|
|
382
|
+
// row's `task_context` into every materialised `agent_schedule` row, so the
|
|
383
|
+
// scheduler can read the flag row-locally at claim time (no `agents` join).
|
|
384
|
+
// Opt-in only: an absent key keeps default-false context clean (the
|
|
385
|
+
// `pin_to_quiet_hours_end` convention on `dm_session` rows).
|
|
386
|
+
taskContext: def.schedule.defer_in_quiet_hours
|
|
387
|
+
? { defer_in_quiet_hours: true }
|
|
388
|
+
: {},
|
|
375
389
|
};
|
|
376
390
|
const existingId = existing?.recurringScheduleId ?? null;
|
|
377
391
|
if (existingId !== null) {
|
|
@@ -403,6 +417,22 @@ function pairRecurring(def, expression, body, existing, port, resolvedTz, resolv
|
|
|
403
417
|
// Structural compare so a re-parsed-but-identical spec is not a "change".
|
|
404
418
|
if (stableStringify(row.recurrence) !== stableStringify(spec))
|
|
405
419
|
patch.recurrence = spec;
|
|
420
|
+
// Reconcile the quiet-hours opt-in (YAML wins), merging off the row's
|
|
421
|
+
// current context so unrelated keys survive the flip. A `taskContext`
|
|
422
|
+
// update does NOT cancel + re-materialise the pending row (only
|
|
423
|
+
// recurrence/enabled do), so a pending materialisation keeps the prior
|
|
424
|
+
// flag until its next generation — the same one-cycle staleness the
|
|
425
|
+
// model/tier pins already have.
|
|
426
|
+
const rowOptedIn = row.taskContext.defer_in_quiet_hours === true;
|
|
427
|
+
const wantOptIn = def.schedule.defer_in_quiet_hours;
|
|
428
|
+
if (rowOptedIn !== wantOptIn) {
|
|
429
|
+
const nextContext = { ...row.taskContext };
|
|
430
|
+
if (wantOptIn)
|
|
431
|
+
nextContext.defer_in_quiet_hours = true;
|
|
432
|
+
else
|
|
433
|
+
delete nextContext.defer_in_quiet_hours;
|
|
434
|
+
patch.taskContext = nextContext;
|
|
435
|
+
}
|
|
406
436
|
if (Object.keys(patch).length > 0)
|
|
407
437
|
port.update(existingId, patch);
|
|
408
438
|
return existingId;
|
|
@@ -504,8 +534,10 @@ function autoImportOrphans(db, opts, port, result, importedSlugs, logger, now) {
|
|
|
504
534
|
* Render the subset of an `AgentDefinition` worth persisting to an auto-imported
|
|
505
535
|
* `agent.md` frontmatter. Schema defaults (limits/tools/on_error) are omitted
|
|
506
536
|
* to keep the generated file readable; they re-populate on the next parse.
|
|
537
|
+
* Exported for the custom-routine migration, which materialises user Agents
|
|
538
|
+
* through the same shape (AGENTS_HUB_REDESIGN_PLAN.md §3).
|
|
507
539
|
*/
|
|
508
|
-
function definitionToFrontmatter(def) {
|
|
540
|
+
export function definitionToFrontmatter(def) {
|
|
509
541
|
// Auto-import always builds a cron schedule with a resolved expression +
|
|
510
542
|
// timezone, so both are emitted unconditionally (no dead-branch guards).
|
|
511
543
|
const backend = { process_key: def.backend.process_key };
|
|
@@ -36,7 +36,7 @@ import type { AgentDefinition } from "@aitne/shared";
|
|
|
36
36
|
* (no target). The loader resolves the real enabled-state from the
|
|
37
37
|
* `agents.enabled_overridden_at` column via the §6.4 timestamp comparison.
|
|
38
38
|
*/
|
|
39
|
-
export declare const MERGEABLE_OVERRIDE_PATHS: readonly ["enabled", "enabled_overridden_at", "backend.tier", "backend.model", "limits.max_turns", "limits.max_budget_usd", "limits.timeout_minutes", "on_error.notify_owner"];
|
|
39
|
+
export declare const MERGEABLE_OVERRIDE_PATHS: readonly ["enabled", "enabled_overridden_at", "backend.tier", "backend.model", "backend.backend_id", "limits.max_turns", "limits.max_budget_usd", "limits.timeout_minutes", "on_error.notify_owner"];
|
|
40
40
|
export type MergeableOverridePath = (typeof MERGEABLE_OVERRIDE_PATHS)[number];
|
|
41
41
|
/** True when `path` is one of the allow-listed override keys. */
|
|
42
42
|
export declare function isMergeableOverridePath(path: string): path is MergeableOverridePath;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGENT_TIERS, OVERRIDE_EDIT_PATHS } from "@aitne/shared";
|
|
1
|
+
import { AGENT_TIERS, BACKEND_IDS, OVERRIDE_EDIT_PATHS } from "@aitne/shared";
|
|
2
2
|
/**
|
|
3
3
|
* Built-in override merge (AGENT_DEFINITIONS_DESIGN.md §6.4.1).
|
|
4
4
|
*
|
|
@@ -53,6 +53,9 @@ export function isMergeableOverridePath(path) {
|
|
|
53
53
|
function isAgentTier(value) {
|
|
54
54
|
return typeof value === "string" && AGENT_TIERS.includes(value);
|
|
55
55
|
}
|
|
56
|
+
function isBackendIdValue(value) {
|
|
57
|
+
return typeof value === "string" && BACKEND_IDS.includes(value);
|
|
58
|
+
}
|
|
56
59
|
function isPositiveInt(value) {
|
|
57
60
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
58
61
|
}
|
|
@@ -86,6 +89,11 @@ function applyOverridePath(def, path, value) {
|
|
|
86
89
|
def.backend.model = value;
|
|
87
90
|
}
|
|
88
91
|
break;
|
|
92
|
+
case "backend.backend_id":
|
|
93
|
+
if (value === null || isBackendIdValue(value)) {
|
|
94
|
+
def.backend.backend_id = value;
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
89
97
|
case "limits.max_turns":
|
|
90
98
|
if (isPositiveInt(value))
|
|
91
99
|
def.limits.max_turns = value;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* is the monthly `onMissingDay` policy, which cron cannot carry — it is dropped
|
|
35
35
|
* on conversion (a `daysOfMonth` cron uses node-cron's own missing-day
|
|
36
36
|
* behaviour). Sub-hourly intervals (every N minutes) are NOT representable —
|
|
37
|
-
* only the built-in
|
|
37
|
+
* only the built-in activity-scan supports those, via config.
|
|
38
38
|
*/
|
|
39
39
|
/** Loader-local mirror of the daemon `recurrence_rule` JSON (§2.2). Covers the
|
|
40
40
|
* four cron-representable frequencies (`hourly` via the step-form cron). The
|