@aitne/daemon 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.js +8 -5
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/context/permissions.js +3 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +48 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +9 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +245 -7
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +26 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +25 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +39 -15
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +62 -20
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +1 -1
- package/dist/core/context-paths.js +1 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +49 -0
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +17 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +63 -1
- package/dist/core/dispatcher-scheduled-tasks.js +213 -6
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +284 -59
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.js +17 -5
- package/dist/core/feedback/eviction-scorer.js +6 -2
- package/dist/core/feedback/lesson-format.js +9 -4
- package/dist/core/feedback/lesson-injection.d.ts +1 -1
- package/dist/core/feedback/lesson-injection.js +17 -2
- package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
- package/dist/core/feedback/lesson-store-overview.js +8 -4
- package/dist/core/feedback/regeneralization-prep.js +29 -16
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +4 -4
- package/dist/core/injection-policy.js +4 -4
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +12 -0
- package/dist/core/signal-detector.js +53 -9
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +2 -2
- package/dist/core/today-direct-writer.js +1 -1
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/migrations.js +330 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +217 -16
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +75 -11
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +16 -6
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +38 -11
- package/dist/settings/runtime-settings.js +203 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -11,28 +11,28 @@ function getLocalHourMinute(date, timeZone) {
|
|
|
11
11
|
const minute = Number(parts.find((part) => part.type === "minute")?.value ?? "0");
|
|
12
12
|
return { hour, minute };
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
if (!config.
|
|
14
|
+
function isActivityScanSlot(date, config) {
|
|
15
|
+
if (!config.activityScanEnabled)
|
|
16
16
|
return false;
|
|
17
17
|
const { hour, minute } = getLocalHourMinute(date, config.timezone || undefined);
|
|
18
18
|
if (hour === config.dayBoundaryHour)
|
|
19
19
|
return false;
|
|
20
|
-
if (hour < config.
|
|
20
|
+
if (hour < config.activityScanActiveStartHour || hour >= config.activityScanActiveEndHour) {
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
|
-
return minute % config.
|
|
23
|
+
return minute % config.activityScanIntervalMinutes === 0;
|
|
24
24
|
}
|
|
25
|
-
function
|
|
26
|
-
if (!config.
|
|
25
|
+
function getNextActivityScan(config) {
|
|
26
|
+
if (!config.activityScanEnabled) {
|
|
27
27
|
return { active: false, nextRunAt: null };
|
|
28
28
|
}
|
|
29
29
|
const now = new Date();
|
|
30
|
-
const active =
|
|
30
|
+
const active = isActivityScanSlot(now, config);
|
|
31
31
|
const start = new Date(now.getTime() + 60_000);
|
|
32
32
|
start.setSeconds(0, 0);
|
|
33
33
|
for (let offset = 0; offset < 48 * 60; offset++) {
|
|
34
34
|
const candidate = new Date(start.getTime() + offset * 60_000);
|
|
35
|
-
if (
|
|
35
|
+
if (isActivityScanSlot(candidate, config)) {
|
|
36
36
|
return { active, nextRunAt: candidate.toISOString() };
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -41,7 +41,7 @@ function getNextHourlyCheck(config) {
|
|
|
41
41
|
export function registerNotificationsRoutes(app, deps) {
|
|
42
42
|
const { db, config } = deps;
|
|
43
43
|
app.get("/dashboard/next-check", (c) => {
|
|
44
|
-
return c.json(
|
|
44
|
+
return c.json(getNextActivityScan(config));
|
|
45
45
|
});
|
|
46
46
|
// STAGE-C-DM-FRESHNESS-PLAN §Task 4 — DM freshness aggregate. Powered
|
|
47
47
|
// by `agent_actions.detail.dm_freshness.*` rows the DM dispatch path
|
|
@@ -368,6 +368,9 @@ export async function handleIntegrationPatch(c, deps) {
|
|
|
368
368
|
const finalNativeSyncEnabled = parsed.data.nativeSyncEnabled === undefined
|
|
369
369
|
? previous.nativeSyncEnabled
|
|
370
370
|
: parsed.data.nativeSyncEnabled;
|
|
371
|
+
const finalFetchTargets = parsed.data.fetchTargets === undefined
|
|
372
|
+
? (previous.fetchTargets ?? [])
|
|
373
|
+
: parsed.data.fetchTargets;
|
|
371
374
|
// §14.7 — synchronously consult the cached probe before committing a
|
|
372
375
|
// mode flip to delegated/native. Per §14.7 the PATCH response path
|
|
373
376
|
// intentionally never spawns a live probe ("no blocking subprocess");
|
|
@@ -470,6 +473,7 @@ export async function handleIntegrationPatch(c, deps) {
|
|
|
470
473
|
...(finalNativeSyncEnabled === false
|
|
471
474
|
? { nativeSyncEnabled: false }
|
|
472
475
|
: {}),
|
|
476
|
+
fetchTargets: finalFetchTargets,
|
|
473
477
|
deniedTools: finalDeniedTools,
|
|
474
478
|
lastChangedAt: stamped,
|
|
475
479
|
});
|
|
@@ -501,7 +505,7 @@ export async function handleIntegrationPatch(c, deps) {
|
|
|
501
505
|
// re-evaluation — the predicate
|
|
502
506
|
// (`hasActiveDelegatedSyncIntegration`) ignores `nativeSyncEnabled`
|
|
503
507
|
// because the worker has no role in native mode (see appendix
|
|
504
|
-
// §"Polling, observers, and the
|
|
508
|
+
// §"Polling, observers, and the activity-scan threshold"). The
|
|
505
509
|
// `nativeSyncEnabled` field is retained on the state row for
|
|
506
510
|
// schema compatibility but toggling it is inert today.
|
|
507
511
|
const syncChanged = (previous.delegatedSyncEnabled ?? true)
|
|
@@ -174,7 +174,7 @@ export function createIntegrationReconcileRoutes(deps) {
|
|
|
174
174
|
* callers bypass the route and write any window key directly. Plan §6.0
|
|
175
175
|
* defense layer 2.
|
|
176
176
|
*
|
|
177
|
-
* Calendar-only by design (post-Phase-5 review): the LLM
|
|
177
|
+
* Calendar-only by design (post-Phase-5 review): the LLM activity_scan
|
|
178
178
|
* Step 0b fetches the same `[now-15min, now+60min)` and `[now, now+24h)`
|
|
179
179
|
* windows the daemon's `delegated-sync-worker` uses for `primary:imminent`
|
|
180
180
|
* and `primary:24h`, so an LLM POST and a worker POST land in the same
|
|
@@ -188,7 +188,7 @@ export function createIntegrationReconcileRoutes(deps) {
|
|
|
188
188
|
* `reconcile.ts:319-345`). The fix is to keep gmail/notion authoring
|
|
189
189
|
* inside the daemon worker only; the LLM consumes drift signals via
|
|
190
190
|
* `GET /api/observations`. The corresponding Step 0a / 0c blocks of
|
|
191
|
-
* `routine.
|
|
191
|
+
* `routine.activity_scan.delegated.<backend>.md` were rewritten to
|
|
192
192
|
* forbid the POST and document the rationale; this allowlist is the
|
|
193
193
|
* defense-in-depth backstop that catches a future overlay rewrite that
|
|
194
194
|
* silently re-introduces the LLM call.
|
|
@@ -11,7 +11,7 @@ export interface NotionRouteDependencies {
|
|
|
11
11
|
/**
|
|
12
12
|
* Optional shared write tracker. All write endpoints pre-mark the target
|
|
13
13
|
* page (`notion:<pageId>`) so NotionPoller attributes the resulting
|
|
14
|
-
* observation to `actor='agent'` and
|
|
14
|
+
* observation to `actor='agent'` and activity_scan's `?actor=user` filter
|
|
15
15
|
* excludes the echo. Without this the agent can observe its own writes
|
|
16
16
|
* and loop.
|
|
17
17
|
*/
|
|
@@ -80,7 +80,7 @@ function resolveSinceParam(rawSince, rawAlias) {
|
|
|
80
80
|
/**
|
|
81
81
|
* INTEGRATION_NATIVE_MODE_DESIGN.md §11.3.1 — map an observation `source`
|
|
82
82
|
* string to the integration key whose flip lock would gate writes against
|
|
83
|
-
* it. The agent's
|
|
83
|
+
* it. The agent's activity_scan / native-mode skill always uses one of the
|
|
84
84
|
* registry's integration keys verbatim as the `source` value (e.g.
|
|
85
85
|
* `"gmail"`, `"google_calendar"`, `"notion"`). Sources outside the
|
|
86
86
|
* registry (Obsidian, Git, messaging adapter, system) are never locked
|
|
@@ -102,7 +102,7 @@ const normalizeMailObservationPayload = normalizeMailObservationPayloadShared;
|
|
|
102
102
|
* cost-reduction-structural §A "Failure modes" — the summary may be
|
|
103
103
|
* outdated when the worker lags far behind the observation moment (e.g.
|
|
104
104
|
* laptop-sleep backlog reclaimed at startup, where the summarizer has
|
|
105
|
-
* caught up by the time
|
|
105
|
+
* caught up by the time activity_scan runs but the underlying source
|
|
106
106
|
* may have shifted in between). Surface a `summaryStale` flag the
|
|
107
107
|
* consumer skill can branch on, rather than asking the LLM to do
|
|
108
108
|
* timestamp arithmetic.
|
|
@@ -201,10 +201,10 @@ export function createObservationRoutes(deps) {
|
|
|
201
201
|
/**
|
|
202
202
|
* POST /observations — record an agent-originated observation.
|
|
203
203
|
*
|
|
204
|
-
* Used by `routine.
|
|
204
|
+
* Used by `routine.activity_scan` to queue `roadmap_candidate` signals
|
|
205
205
|
* (long-horizon intents too weak to write to roadmap.md directly;
|
|
206
206
|
* ROADMAP-REDESIGN §3.4 RFC-C) AND by INTEGRATION_NATIVE_MODE_DESIGN.md
|
|
207
|
-
* §8.3 native-mode
|
|
207
|
+
* §8.3 native-mode activity_scan turns to persist the materialised mail
|
|
208
208
|
* thread / calendar event list the agent just fetched via the main
|
|
209
209
|
* backend's MCP. The DB layer UPSERTs on `(source, ref)` where
|
|
210
210
|
* `consumed_at IS NULL`, so re-posting the same candidate across hourly
|
|
@@ -233,7 +233,7 @@ export function createObservationRoutes(deps) {
|
|
|
233
233
|
// Peek at the raw body BEFORE delegating to `readJsonBody` so we can
|
|
234
234
|
// turn a query-string-shaped body ("limit=30", "actor=user&limit=20")
|
|
235
235
|
// into a method-confusion hint. Production telemetry showed the
|
|
236
|
-
//
|
|
236
|
+
// activity_scan agent sending `POST /api/observations` with body
|
|
237
237
|
// `limit=30`, expecting it to fetch. Forwarding readJsonBody's
|
|
238
238
|
// generic "Unexpected token 'l'" message gave the agent no signal
|
|
239
239
|
// that the right call was `GET /api/observations?limit=30`.
|
|
@@ -554,7 +554,7 @@ export function createObservationRoutes(deps) {
|
|
|
554
554
|
/**
|
|
555
555
|
* Field-level validation contract for `POST /observations/consume`.
|
|
556
556
|
*
|
|
557
|
-
* Without per-field error envelopes, a single Stage-3
|
|
557
|
+
* Without per-field error envelopes, a single Stage-3 activity_scan can
|
|
558
558
|
* burn turns retrying this endpoint with shape variants
|
|
559
559
|
* (`correlation_id` snake_case, stringified ids, the angle-bracket
|
|
560
560
|
* placeholder copied verbatim, per-id paths, etc.). The legacy
|
|
@@ -686,7 +686,7 @@ export function createObservationRoutes(deps) {
|
|
|
686
686
|
* Helpful 405 for `GET /api/observations/consume`. The bulk consume is
|
|
687
687
|
* POST-only — without this handler the request 404s with no actionable
|
|
688
688
|
* detail, and the agent's recovery loop produced 8x retries in one
|
|
689
|
-
* routine.
|
|
689
|
+
* routine.activity_scan session.
|
|
690
690
|
*/
|
|
691
691
|
app.get("/observations/consume", (c) => c.json({
|
|
692
692
|
error: "method_not_allowed",
|
|
@@ -6,7 +6,7 @@ export interface ObsidianRouteDependencies {
|
|
|
6
6
|
/**
|
|
7
7
|
* Optional shared tracker. When present, every write endpoint pre-marks
|
|
8
8
|
* the target vault file so the obsidian-watcher attributes the resulting
|
|
9
|
-
* chokidar event to `actor='agent'` (and
|
|
9
|
+
* chokidar event to `actor='agent'` (and activity_scan's `?actor=user`
|
|
10
10
|
* filter excludes it). Without this the agent can observe its own
|
|
11
11
|
* Obsidian writes and loop.
|
|
12
12
|
*/
|
|
@@ -147,7 +147,11 @@ export function createReceiptRoutes(deps) {
|
|
|
147
147
|
]);
|
|
148
148
|
}
|
|
149
149
|
c.header("Content-Type", row.mime_type);
|
|
150
|
-
|
|
150
|
+
// `row.filename` comes from email-attachment metadata (sender-controlled),
|
|
151
|
+
// so escape characters that could break out of the quoted header value or
|
|
152
|
+
// inject extra header lines — same discipline as the attachments route.
|
|
153
|
+
const safeFilename = row.filename.replace(/["\\\r\n]/g, "_");
|
|
154
|
+
c.header("Content-Disposition", `attachment; filename="${safeFilename}"`);
|
|
151
155
|
return c.body(new Uint8Array(attachment.data));
|
|
152
156
|
});
|
|
153
157
|
/**
|
|
@@ -14,7 +14,7 @@ const logger = createLogger("setup-migrate");
|
|
|
14
14
|
* in-flight cron tick to settle (plan §6.2 step 4). Configurable so
|
|
15
15
|
* tests can override to 0; production default 1s is sufficient because
|
|
16
16
|
* all known cron handlers either stop immediately (observer pollers)
|
|
17
|
-
* or enqueue to the paused EventBus (schedule watcher /
|
|
17
|
+
* or enqueue to the paused EventBus (schedule watcher / activity scan).
|
|
18
18
|
*
|
|
19
19
|
* Plan says "up to 10s" but that's a ceiling; shorter is fine when no
|
|
20
20
|
* cron handler is known to block for that long.
|
package/dist/api/routes/setup.js
CHANGED
|
@@ -296,7 +296,7 @@ export function createSetupRoutes(deps) {
|
|
|
296
296
|
}
|
|
297
297
|
// Engage the autonomous-work gate BEFORE enqueuing the greeting so any
|
|
298
298
|
// concurrent cron tick / ScheduleWatcher poll observes the flag and
|
|
299
|
-
// yields. Without this, a
|
|
299
|
+
// yields. Without this, a activity_scan firing in the same tick could
|
|
300
300
|
// still race the setup conversation and patch today.md, which would
|
|
301
301
|
// mark the owner-DM session stale and orphan the setup mode.
|
|
302
302
|
deps.onSetupStart?.(mode);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* that disallows `/`, `..`, leading dots, and anything outside
|
|
15
15
|
* `[A-Za-z0-9._-]`. Length is capped at 80 characters — the longest
|
|
16
16
|
* bundled key today is ~45 characters
|
|
17
|
-
* (`routine.
|
|
17
|
+
* (`routine.activity_scan.delegated.gemini`), so 80 leaves comfortable
|
|
18
18
|
* headroom without giving an attacker a path-bomb surface.
|
|
19
19
|
*
|
|
20
20
|
* Risk tier: Approve. The task-flow body is dispatcher prose that the
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* that disallows `/`, `..`, leading dots, and anything outside
|
|
15
15
|
* `[A-Za-z0-9._-]`. Length is capped at 80 characters — the longest
|
|
16
16
|
* bundled key today is ~45 characters
|
|
17
|
-
* (`routine.
|
|
17
|
+
* (`routine.activity_scan.delegated.gemini`), so 80 leaves comfortable
|
|
18
18
|
* headroom without giving an attacker a path-bomb surface.
|
|
19
19
|
*
|
|
20
20
|
* Risk tier: Approve. The task-flow body is dispatcher prose that the
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Tuning Review Cycle — verdict endpoint (SELF_TUNING_REVIEW_CYCLE_DESIGN.md
|
|
3
|
+
* §3.3 / §3.4, Phases 2–3).
|
|
4
|
+
*
|
|
5
|
+
* `POST /api/tuning/verdicts` is RiskTier.Autonomous by design — the
|
|
6
|
+
* abolished Notify tier's replacement pattern (Autonomous + mandatory owner
|
|
7
|
+
* DM on every applied change). Safety is carried by code, not tier (§3.4):
|
|
8
|
+
* - verdicts may only reference recommendation ids the daemon itself
|
|
9
|
+
* generated **this cycle** — no free-form key/value from the model;
|
|
10
|
+
* - ids are single-use and expire when the next weekly cycle overwrites
|
|
11
|
+
* the pending blob;
|
|
12
|
+
* - the handler is idempotent per id — a retried POST cannot double-apply
|
|
13
|
+
* (only verdicts *newly recorded by this POST* reach the actuator);
|
|
14
|
+
* - config writes go through the `applyConfigUpdates` chokepoint, which
|
|
15
|
+
* enforces the per-key bounds (P4).
|
|
16
|
+
*
|
|
17
|
+
* Verdicts are always recorded and audited
|
|
18
|
+
* (`agent_actions.action_type='self_tuning.verdict'`); rejection reasons
|
|
19
|
+
* become `self_critique` feedback signals (§3.3 — so repeated bad
|
|
20
|
+
* recommendations depress the rule via the existing lesson loop). While
|
|
21
|
+
* `selfTuningEnabled` is `false` (the shipped default — §7 shadow period)
|
|
22
|
+
* nothing is actuated regardless of verdict and every response carries
|
|
23
|
+
* `shadow: true` with an empty `applied` array. Once the owner flips the
|
|
24
|
+
* flag (the D1 sign-off), `apply` verdicts actuate per the D5 namespace
|
|
25
|
+
* semantics in `core/feedback/tuning-actuator.ts`.
|
|
26
|
+
*/
|
|
27
|
+
import { Hono } from "hono";
|
|
28
|
+
import type { ApiDependencies } from "../server.js";
|
|
29
|
+
export declare function createTuningRoutes(deps: ApiDependencies): Hono;
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Tuning Review Cycle — verdict endpoint (SELF_TUNING_REVIEW_CYCLE_DESIGN.md
|
|
3
|
+
* §3.3 / §3.4, Phases 2–3).
|
|
4
|
+
*
|
|
5
|
+
* `POST /api/tuning/verdicts` is RiskTier.Autonomous by design — the
|
|
6
|
+
* abolished Notify tier's replacement pattern (Autonomous + mandatory owner
|
|
7
|
+
* DM on every applied change). Safety is carried by code, not tier (§3.4):
|
|
8
|
+
* - verdicts may only reference recommendation ids the daemon itself
|
|
9
|
+
* generated **this cycle** — no free-form key/value from the model;
|
|
10
|
+
* - ids are single-use and expire when the next weekly cycle overwrites
|
|
11
|
+
* the pending blob;
|
|
12
|
+
* - the handler is idempotent per id — a retried POST cannot double-apply
|
|
13
|
+
* (only verdicts *newly recorded by this POST* reach the actuator);
|
|
14
|
+
* - config writes go through the `applyConfigUpdates` chokepoint, which
|
|
15
|
+
* enforces the per-key bounds (P4).
|
|
16
|
+
*
|
|
17
|
+
* Verdicts are always recorded and audited
|
|
18
|
+
* (`agent_actions.action_type='self_tuning.verdict'`); rejection reasons
|
|
19
|
+
* become `self_critique` feedback signals (§3.3 — so repeated bad
|
|
20
|
+
* recommendations depress the rule via the existing lesson loop). While
|
|
21
|
+
* `selfTuningEnabled` is `false` (the shipped default — §7 shadow period)
|
|
22
|
+
* nothing is actuated regardless of verdict and every response carries
|
|
23
|
+
* `shadow: true` with an empty `applied` array. Once the owner flips the
|
|
24
|
+
* flag (the D1 sign-off), `apply` verdicts actuate per the D5 namespace
|
|
25
|
+
* semantics in `core/feedback/tuning-actuator.ts`.
|
|
26
|
+
*/
|
|
27
|
+
import { Hono } from "hono";
|
|
28
|
+
import { redactSensitiveString } from "@aitne/shared";
|
|
29
|
+
import { readJsonBody } from "../json-body.js";
|
|
30
|
+
import { applyConfigUpdates } from "../env-writer.js";
|
|
31
|
+
import { createSettingsStore } from "../../settings/settings-store.js";
|
|
32
|
+
import { recordFeedbackSignal } from "../../db/feedback-signals-store.js";
|
|
33
|
+
import { readRuntimeState, writeRuntimeState } from "../../db/runtime-state.js";
|
|
34
|
+
import { SELF_TUNING_NOTIFICATION_TYPE, TUNING_PENDING_CYCLE_STATE_KEY, applyVerdictsToCycle, } from "../../core/feedback/tuning-recommender.js";
|
|
35
|
+
import { actuateApplyVerdicts, } from "../../core/feedback/tuning-actuator.js";
|
|
36
|
+
import { createLogger } from "../../logging.js";
|
|
37
|
+
const logger = createLogger("tuning-api");
|
|
38
|
+
const MAX_REASON_CHARS = 280;
|
|
39
|
+
const VERDICTS = new Set(["apply", "reject", "defer"]);
|
|
40
|
+
function isRecord(value) {
|
|
41
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
function describeType(value) {
|
|
44
|
+
if (value === undefined)
|
|
45
|
+
return "missing";
|
|
46
|
+
if (value === null)
|
|
47
|
+
return "null";
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
return "array";
|
|
50
|
+
return typeof value;
|
|
51
|
+
}
|
|
52
|
+
function sanitizeReason(value) {
|
|
53
|
+
const flattened = value.replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
54
|
+
const truncated = flattened.length <= MAX_REASON_CHARS
|
|
55
|
+
? flattened
|
|
56
|
+
: flattened.slice(0, MAX_REASON_CHARS);
|
|
57
|
+
return redactSensitiveString(truncated).trim();
|
|
58
|
+
}
|
|
59
|
+
export function createTuningRoutes(deps) {
|
|
60
|
+
const app = new Hono();
|
|
61
|
+
const { db, config } = deps;
|
|
62
|
+
/**
|
|
63
|
+
* GET /tuning/pending — the current cycle's recommendations + recorded
|
|
64
|
+
* verdicts, for the owner's shadow-period validation and the dashboard.
|
|
65
|
+
* Autonomous: the blob holds knob names and telemetry counts only — no
|
|
66
|
+
* user prose, no secrets.
|
|
67
|
+
*/
|
|
68
|
+
app.get("/tuning/pending", (c) => {
|
|
69
|
+
const cycle = readRuntimeState(db, TUNING_PENDING_CYCLE_STATE_KEY);
|
|
70
|
+
const live = config?.selfTuningEnabled === true;
|
|
71
|
+
return c.json({
|
|
72
|
+
cycle,
|
|
73
|
+
selfTuningEnabled: live,
|
|
74
|
+
shadow: !live,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
app.post("/tuning/verdicts", async (c) => {
|
|
78
|
+
const parsedBody = await readJsonBody(c);
|
|
79
|
+
if (!parsedBody.ok)
|
|
80
|
+
return parsedBody.response;
|
|
81
|
+
const body = parsedBody.body;
|
|
82
|
+
if (!isRecord(body)) {
|
|
83
|
+
return c.json({
|
|
84
|
+
error: "validation_error",
|
|
85
|
+
message: "Body must be a JSON object",
|
|
86
|
+
expectedShape: '{"cycleId": string, "verdicts": [{"id": string, "verdict": "apply"|"reject"|"defer", "reason": string}]}',
|
|
87
|
+
}, 400);
|
|
88
|
+
}
|
|
89
|
+
const issues = [];
|
|
90
|
+
const cycleId = typeof body.cycleId === "string" ? body.cycleId : null;
|
|
91
|
+
if (!cycleId) {
|
|
92
|
+
issues.push({
|
|
93
|
+
field: "cycleId",
|
|
94
|
+
expected: "string (the cycle attribute of <tuning_recommendations>)",
|
|
95
|
+
got: describeType(body.cycleId),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(body.verdicts) || body.verdicts.length === 0) {
|
|
99
|
+
issues.push({
|
|
100
|
+
field: "verdicts",
|
|
101
|
+
expected: "non-empty array",
|
|
102
|
+
got: describeType(body.verdicts),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const entries = [];
|
|
106
|
+
if (Array.isArray(body.verdicts)) {
|
|
107
|
+
body.verdicts.forEach((raw, index) => {
|
|
108
|
+
if (!isRecord(raw)) {
|
|
109
|
+
issues.push({
|
|
110
|
+
field: `verdicts[${index}]`,
|
|
111
|
+
expected: "object",
|
|
112
|
+
got: describeType(raw),
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const id = typeof raw.id === "string" ? raw.id : null;
|
|
117
|
+
const verdict = typeof raw.verdict === "string" ? raw.verdict : null;
|
|
118
|
+
const reason = typeof raw.reason === "string" ? sanitizeReason(raw.reason) : "";
|
|
119
|
+
if (!id) {
|
|
120
|
+
issues.push({
|
|
121
|
+
field: `verdicts[${index}].id`,
|
|
122
|
+
expected: "string recommendation id",
|
|
123
|
+
got: describeType(raw.id),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (verdict === null || !VERDICTS.has(verdict)) {
|
|
127
|
+
issues.push({
|
|
128
|
+
field: `verdicts[${index}].verdict`,
|
|
129
|
+
expected: "'apply' | 'reject' | 'defer'",
|
|
130
|
+
got: verdict ?? describeType(raw.verdict),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (reason.length === 0) {
|
|
134
|
+
issues.push({
|
|
135
|
+
field: `verdicts[${index}].reason`,
|
|
136
|
+
expected: "non-empty one-line string (max 280 chars)",
|
|
137
|
+
got: describeType(raw.reason),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (id && verdict !== null && VERDICTS.has(verdict) && reason) {
|
|
141
|
+
entries.push({ id, verdict: verdict, reason });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (issues.length > 0) {
|
|
146
|
+
return c.json({
|
|
147
|
+
error: "validation_error",
|
|
148
|
+
message: "Request body failed schema validation",
|
|
149
|
+
issues,
|
|
150
|
+
}, 400);
|
|
151
|
+
}
|
|
152
|
+
const cycle = readRuntimeState(db, TUNING_PENDING_CYCLE_STATE_KEY);
|
|
153
|
+
if (!cycle) {
|
|
154
|
+
return c.json({
|
|
155
|
+
error: "no_pending_cycle",
|
|
156
|
+
message: "No pending tuning cycle exists — recommendations are generated by the weekly review pre-step.",
|
|
157
|
+
}, 409);
|
|
158
|
+
}
|
|
159
|
+
if (cycle.cycleId !== cycleId) {
|
|
160
|
+
// §3.4 — single-use ids: the weekly pre-step overwrote the blob, so the
|
|
161
|
+
// referenced cycle's ids have expired. No replay.
|
|
162
|
+
return c.json({
|
|
163
|
+
error: "cycle_expired",
|
|
164
|
+
message: `Cycle '${cycleId}' is not the pending cycle — its ids have expired.`,
|
|
165
|
+
activeCycleId: cycle.cycleId,
|
|
166
|
+
}, 409);
|
|
167
|
+
}
|
|
168
|
+
const matched = entries.map((entry) => ({
|
|
169
|
+
entry,
|
|
170
|
+
rec: cycle.recommendations.find((rec) => rec.id === entry.id) ?? null,
|
|
171
|
+
}));
|
|
172
|
+
const known = matched.filter((m) => m.rec !== null);
|
|
173
|
+
const unknownIds = [
|
|
174
|
+
...new Set(matched.filter((m) => m.rec === null).map((m) => m.entry.id)),
|
|
175
|
+
];
|
|
176
|
+
if (unknownIds.length > 0) {
|
|
177
|
+
// §3.4 — verdicts may only reference daemon-generated ids from this
|
|
178
|
+
// cycle. Atomic reject: nothing is recorded on a partially-bad batch,
|
|
179
|
+
// so a corrected retry cannot double-record the valid half.
|
|
180
|
+
return c.json({
|
|
181
|
+
error: "unknown_recommendation_ids",
|
|
182
|
+
message: "Verdicts may only reference recommendation ids generated this cycle.",
|
|
183
|
+
unknownIds,
|
|
184
|
+
knownIds: cycle.recommendations.map((rec) => rec.id),
|
|
185
|
+
}, 400);
|
|
186
|
+
}
|
|
187
|
+
const { cycle: updated, results } = applyVerdictsToCycle(cycle, entries, new Date().toISOString());
|
|
188
|
+
writeRuntimeState(db, TUNING_PENDING_CYCLE_STATE_KEY, updated);
|
|
189
|
+
const recordedIds = new Set(results.filter((r) => r.status === "recorded").map((r) => r.id));
|
|
190
|
+
// §3.3 — rejection reasons become self_critique signals so the lesson
|
|
191
|
+
// loop learns which recommendations the judge keeps refusing. Recorded
|
|
192
|
+
// only (idempotency: a retried duplicate never double-posts), and only
|
|
193
|
+
// while the feedback loop is enabled — mirroring POST /api/feedback's
|
|
194
|
+
// kill-switch posture.
|
|
195
|
+
if (config?.feedbackLearningEnabled !== false) {
|
|
196
|
+
for (const { entry, rec } of known) {
|
|
197
|
+
if (entry.verdict !== "reject" || !recordedIds.has(entry.id))
|
|
198
|
+
continue;
|
|
199
|
+
try {
|
|
200
|
+
recordFeedbackSignal(db, {
|
|
201
|
+
source: "self_critique",
|
|
202
|
+
valence: "negative",
|
|
203
|
+
scopeType: "agent",
|
|
204
|
+
scopeRef: null,
|
|
205
|
+
actionKind: "agent_execution",
|
|
206
|
+
actionRef: entry.id,
|
|
207
|
+
agentId: null,
|
|
208
|
+
summary: sanitizeReason(`Tuning recommendation ${rec.rule} (${rec.key}) rejected: ${entry.reason}`),
|
|
209
|
+
evidence: {
|
|
210
|
+
kind: "do-less",
|
|
211
|
+
recommendationId: entry.id,
|
|
212
|
+
rule: rec.rule,
|
|
213
|
+
key: rec.key,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
logger.warn({ err, id: entry.id }, "Failed to record self_critique signal for rejected tuning recommendation");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const live = config?.selfTuningEnabled === true;
|
|
223
|
+
// Telemetry row — the record the owner reads to validate recommendation
|
|
224
|
+
// quality (§7). Failure here must not fail the verdict write: the blob
|
|
225
|
+
// is already persisted.
|
|
226
|
+
try {
|
|
227
|
+
db.prepare(`INSERT INTO agent_actions
|
|
228
|
+
(action_type, trigger, result, detail, started_at, completed_at)
|
|
229
|
+
VALUES ('self_tuning.verdict', 'autonomous', 'success', json(?), datetime('now'), datetime('now'))`).run(JSON.stringify({
|
|
230
|
+
cycleId: cycle.cycleId,
|
|
231
|
+
shadow: !live,
|
|
232
|
+
// `results` is index-aligned with `entries` — applyVerdictsToCycle
|
|
233
|
+
// emits exactly one result per entry, in order.
|
|
234
|
+
verdicts: entries.map((entry, index) => ({
|
|
235
|
+
id: entry.id,
|
|
236
|
+
verdict: entry.verdict,
|
|
237
|
+
reason: entry.reason,
|
|
238
|
+
status: results[index].status,
|
|
239
|
+
})),
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
logger.warn({ err }, "Failed to audit self_tuning.verdict");
|
|
244
|
+
}
|
|
245
|
+
// Phase 3 — Actuate (§3.4, D5 namespace semantics). Gated on the D1
|
|
246
|
+
// flag AND on per-id "recorded" status: duplicates/conflicts from a
|
|
247
|
+
// retried POST never reach the actuator, so a change cannot
|
|
248
|
+
// double-apply — including an `apply` recorded during the shadow period
|
|
249
|
+
// and re-POSTed after the flag flip. The actuator owns ledger writes,
|
|
250
|
+
// `self_tuning.applied` audit rows, and the mandatory owner DM; an
|
|
251
|
+
// actuation failure surfaces in `actuationFailures` without failing the
|
|
252
|
+
// verdict write (already persisted above).
|
|
253
|
+
let actuation = { applied: [], failures: [] };
|
|
254
|
+
if (live) {
|
|
255
|
+
const applyRecs = known
|
|
256
|
+
.filter(({ entry }) => entry.verdict === "apply" && recordedIds.has(entry.id))
|
|
257
|
+
.map(({ rec }) => rec);
|
|
258
|
+
if (applyRecs.length > 0) {
|
|
259
|
+
const settingsStore = createSettingsStore(db);
|
|
260
|
+
const agentConfig = config;
|
|
261
|
+
const sendNotification = deps.sendNotification;
|
|
262
|
+
actuation = await actuateApplyVerdicts({
|
|
263
|
+
db,
|
|
264
|
+
getCurrentValue: (key) => agentConfig[key],
|
|
265
|
+
applyUpdates: (updates) => applyConfigUpdates(agentConfig, settingsStore, updates, { db }),
|
|
266
|
+
...(sendNotification
|
|
267
|
+
? {
|
|
268
|
+
sendDm: async (message) => {
|
|
269
|
+
await sendNotification({
|
|
270
|
+
message,
|
|
271
|
+
notificationType: SELF_TUNING_NOTIFICATION_TYPE,
|
|
272
|
+
priority: "normal",
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
: {}),
|
|
277
|
+
feedbackLearningEnabled: config?.feedbackLearningEnabled,
|
|
278
|
+
}, applyRecs, new Date());
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const counts = { recorded: 0, duplicate: 0, conflict: 0 };
|
|
282
|
+
for (const result of results)
|
|
283
|
+
counts[result.status] += 1;
|
|
284
|
+
logger.info({
|
|
285
|
+
cycleId: cycle.cycleId,
|
|
286
|
+
...counts,
|
|
287
|
+
applied: actuation.applied.length,
|
|
288
|
+
actuationFailures: actuation.failures.length,
|
|
289
|
+
shadow: !live,
|
|
290
|
+
}, "Tuning verdicts recorded");
|
|
291
|
+
return c.json({
|
|
292
|
+
cycleId: cycle.cycleId,
|
|
293
|
+
results,
|
|
294
|
+
recorded: counts.recorded,
|
|
295
|
+
duplicates: counts.duplicate,
|
|
296
|
+
conflicts: counts.conflict,
|
|
297
|
+
shadow: !live,
|
|
298
|
+
applied: actuation.applied,
|
|
299
|
+
actuationFailures: actuation.failures,
|
|
300
|
+
selfTuningEnabled: live,
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
return app;
|
|
304
|
+
}
|