@aitne/daemon 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.d.ts +1 -0
- package/dist/api/env-writer.js +17 -7
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent-schedule.js +5 -1
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/apple-calendar.js +4 -1
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/calendar.js +12 -2
- package/dist/api/routes/context/path-resolve.js +6 -1
- package/dist/api/routes/context/permissions.js +12 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +58 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/dashboard/oauth-google.js +5 -3
- package/dist/api/routes/feedback.d.ts +3 -0
- package/dist/api/routes/feedback.js +349 -0
- package/dist/api/routes/git.js +10 -3
- package/dist/api/routes/github.js +5 -1
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/mcp.js +65 -13
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +12 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +246 -8
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +32 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +38 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +47 -18
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +193 -5
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +11 -1
- package/dist/core/context-paths.js +17 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +50 -1
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +24 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
- package/dist/core/dispatcher-scheduled-tasks.js +480 -8
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +297 -60
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.d.ts +94 -0
- package/dist/core/feedback/consolidation-prep.js +254 -0
- package/dist/core/feedback/eviction-scorer.d.ts +81 -0
- package/dist/core/feedback/eviction-scorer.js +136 -0
- package/dist/core/feedback/lesson-format.d.ts +79 -0
- package/dist/core/feedback/lesson-format.js +199 -0
- package/dist/core/feedback/lesson-injection.d.ts +98 -0
- package/dist/core/feedback/lesson-injection.js +174 -0
- package/dist/core/feedback/lesson-merge.d.ts +51 -0
- package/dist/core/feedback/lesson-merge.js +88 -0
- package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
- package/dist/core/feedback/lesson-store-overview.js +42 -0
- package/dist/core/feedback/promotion-gate.d.ts +69 -0
- package/dist/core/feedback/promotion-gate.js +117 -0
- package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
- package/dist/core/feedback/regeneralization-prep.js +152 -0
- package/dist/core/feedback/scope-parser.d.ts +86 -0
- package/dist/core/feedback/scope-parser.js +141 -0
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +83 -1
- package/dist/core/injection-policy.js +61 -3
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +51 -1
- package/dist/core/signal-detector.js +321 -24
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +60 -14
- package/dist/core/today-direct-writer.js +90 -13
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/wiki/wiki-fts.js +13 -6
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/feedback-signals-store.d.ts +77 -0
- package/dist/db/feedback-signals-store.js +144 -0
- package/dist/db/migrations.js +380 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +260 -22
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/always-disallowed.d.ts +1 -1
- package/dist/safety/always-disallowed.js +39 -0
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +97 -18
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +34 -8
- package/dist/services/browser-history/lifecycle/platform.js +44 -2
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/mcp/probe.js +30 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +45 -12
- package/dist/settings/runtime-settings.js +215 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
package/dist/api/routes/mcp.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { join } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
4
|
import { Hono } from "hono";
|
|
7
5
|
import { z } from "zod";
|
|
8
6
|
import { BACKEND_IDS } from "@aitne/shared";
|
|
@@ -13,6 +11,7 @@ import { deleteAllMcpSecrets, deleteMcpServer, disableAllMcpServers, DuplicateMc
|
|
|
13
11
|
import { McpServerIdSchema, MCP_RISK_TIERS, MCP_TRANSPORTS, } from "../../services/mcp/types.js";
|
|
14
12
|
import { probeMcpServer } from "../../services/mcp/probe.js";
|
|
15
13
|
import { listMcpToolCalls } from "../../services/mcp/tool-audit.js";
|
|
14
|
+
import { runLineCommand } from "../../core/backends/cli-utils.js";
|
|
16
15
|
const logger = createLogger("mcp-api");
|
|
17
16
|
const BackendIdSchema = z.enum(BACKEND_IDS);
|
|
18
17
|
const CreateInputSchema = z.object({
|
|
@@ -383,6 +382,24 @@ export function createMcpRoutes(deps) {
|
|
|
383
382
|
composeIssue("mcp.not_found", { field: "id", received: id }),
|
|
384
383
|
]);
|
|
385
384
|
}
|
|
385
|
+
// Validate keyName against the server's declared keys, mirroring the PUT
|
|
386
|
+
// handler. Without this guard DELETE accepted any raw `keyName` from the
|
|
387
|
+
// URL and removed the corresponding `mcp:<id>:<keyName>` blob — an
|
|
388
|
+
// asymmetry that let a caller target blobs the server never declared.
|
|
389
|
+
const keys = new Set([...server.envKeys, ...server.headerKeys]);
|
|
390
|
+
if (!keys.has(keyName)) {
|
|
391
|
+
return respondWithAgentError(c, 400, [
|
|
392
|
+
composeIssue("mcp.unknown_key", {
|
|
393
|
+
field: "keyName",
|
|
394
|
+
received: keyName,
|
|
395
|
+
expected: `one of ${[...keys].join(", ")}`,
|
|
396
|
+
}),
|
|
397
|
+
], {
|
|
398
|
+
legacyFields: {
|
|
399
|
+
message: `keyName must be declared in envKeys/headerKeys: ${keyName}`,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
386
403
|
await deleteAllMcpSecrets(blobStore, id, [keyName]);
|
|
387
404
|
return c.json({ status: "deleted" });
|
|
388
405
|
});
|
|
@@ -433,10 +450,44 @@ export function createMcpRoutes(deps) {
|
|
|
433
450
|
});
|
|
434
451
|
}
|
|
435
452
|
try {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
453
|
+
// Route through runLineCommand, not a bare execFile("gemini"): on
|
|
454
|
+
// Windows the npm-installed Gemini CLI is a `gemini.cmd` batch shim,
|
|
455
|
+
// which a shell:false spawn of the bare name cannot resolve (no PATHEXT)
|
|
456
|
+
// — so this dashboard install was 100% non-functional on Windows.
|
|
457
|
+
// runLineCommand's resolveWin32Invocation resolves the name via PATHEXT
|
|
458
|
+
// and launches the `.cmd` through an escaped cmd.exe wrapper (no
|
|
459
|
+
// shell:true, no metachar re-parse). The args are a static const, so
|
|
460
|
+
// there is no injection dimension regardless.
|
|
461
|
+
const result = await runLineCommand({
|
|
462
|
+
command: "gemini",
|
|
463
|
+
args: [...args],
|
|
464
|
+
cwd: homedir(),
|
|
465
|
+
timeoutMs: 120_000,
|
|
439
466
|
});
|
|
467
|
+
const stdout = result.stdoutLines.join("\n");
|
|
468
|
+
const stderr = result.stderrLines.join("\n");
|
|
469
|
+
// Contract remap: execFile REJECTS on non-zero exit, but runLineCommand
|
|
470
|
+
// RESOLVES with exitCode !== 0 (it rejects only on a spawn-level error).
|
|
471
|
+
// Branch on exitCode/timedOut so an OAuth-required / version-mismatch
|
|
472
|
+
// failure still maps to the 502 install_failed path instead of being
|
|
473
|
+
// mis-reported as ok:true.
|
|
474
|
+
if (result.timedOut || (result.exitCode ?? 0) !== 0) {
|
|
475
|
+
const message = result.timedOut
|
|
476
|
+
? "gemini install command timed out after 120s"
|
|
477
|
+
: stderr || stdout || `gemini exited with code ${result.exitCode}`;
|
|
478
|
+
logger.warn({ kind, args, exitCode: result.exitCode, timedOut: result.timedOut }, "gemini install command failed");
|
|
479
|
+
return respondWithAgentError(c, 502, [composeIssue("mcp.install_failed", { field: "gemini", received: message })], {
|
|
480
|
+
legacyFields: {
|
|
481
|
+
ok: false,
|
|
482
|
+
kind,
|
|
483
|
+
command: ["gemini", ...args].join(" "),
|
|
484
|
+
message,
|
|
485
|
+
stdout,
|
|
486
|
+
stderr,
|
|
487
|
+
exitCode: result.exitCode,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
440
491
|
logger.info({ kind, args, stdoutLen: stdout.length }, "gemini install command completed");
|
|
441
492
|
return c.json({
|
|
442
493
|
ok: true,
|
|
@@ -448,12 +499,14 @@ export function createMcpRoutes(deps) {
|
|
|
448
499
|
});
|
|
449
500
|
}
|
|
450
501
|
catch (err) {
|
|
502
|
+
// Spawn-level failure only: runLineCommand rejects via child.once("error")
|
|
503
|
+
// with the raw spawn error (code:"ENOENT" when the bare/resolved name is
|
|
504
|
+
// unresolvable). On Windows, resolveWin32Invocation returns null for an
|
|
505
|
+
// unresolvable bare "gemini" so spawn still ENOENTs naturally — the 503
|
|
506
|
+
// gemini_cli_not_found path is preserved.
|
|
451
507
|
const message = toSafeErrorMessage(err);
|
|
452
|
-
// execFile rejects with the spawn error AND attaches stdout/stderr
|
|
453
|
-
// / code on the rejection value. Surface them when present so the
|
|
454
|
-
// dashboard can show OAuth-required / version-mismatch hints.
|
|
455
508
|
const e = err;
|
|
456
|
-
logger.warn({ kind, args, code: e.code, message }, "gemini install command failed");
|
|
509
|
+
logger.warn({ kind, args, code: e.code, message }, "gemini install command spawn failed");
|
|
457
510
|
const code = e.code === "ENOENT" ? "mcp.gemini_cli_not_found" : "mcp.install_failed";
|
|
458
511
|
const status = e.code === "ENOENT" ? 503 : 502;
|
|
459
512
|
return respondWithAgentError(c, status, [composeIssue(code, { field: "gemini", received: message })], {
|
|
@@ -462,16 +515,15 @@ export function createMcpRoutes(deps) {
|
|
|
462
515
|
kind,
|
|
463
516
|
command: ["gemini", ...args].join(" "),
|
|
464
517
|
message,
|
|
465
|
-
stdout:
|
|
466
|
-
stderr:
|
|
467
|
-
exitCode:
|
|
518
|
+
stdout: "",
|
|
519
|
+
stderr: "",
|
|
520
|
+
exitCode: null,
|
|
468
521
|
},
|
|
469
522
|
});
|
|
470
523
|
}
|
|
471
524
|
});
|
|
472
525
|
return app;
|
|
473
526
|
}
|
|
474
|
-
const execFileAsync = promisify(execFile);
|
|
475
527
|
/**
|
|
476
528
|
* Pre-spawn idempotency check. Returns true when the target install is
|
|
477
529
|
* already present on disk, so the route can short-circuit without
|
|
@@ -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
|
+
}
|