@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
|
@@ -143,6 +143,10 @@ export function aggregateByBilledModel(rows) {
|
|
|
143
143
|
.map(([model, v]) => ({ model, ...v }))
|
|
144
144
|
.sort((a, b) => b.total_cost - a.total_cost);
|
|
145
145
|
}
|
|
146
|
+
// Today's spend-driver list is intentionally capped: beyond the top 15 the
|
|
147
|
+
// rows are long-tail noise (the by-process panel covers aggregate share),
|
|
148
|
+
// and an uncapped list would grow unbounded on chatty days.
|
|
149
|
+
const TODAY_TOP_ACTIONS_LIMIT = 15;
|
|
146
150
|
// `bucketExpr` takes a single shift-modifier parameter (e.g. "+300 minutes")
|
|
147
151
|
// so each query binds it explicitly — see COST_QUERIES for the rationale.
|
|
148
152
|
const COST_PERIOD_SPECS = {
|
|
@@ -211,6 +215,61 @@ export function registerCostApprovalsRoutes(app, deps) {
|
|
|
211
215
|
WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
|
|
212
216
|
AND cost_usd IS NOT NULL`)
|
|
213
217
|
.get(bounds.start, bounds.end);
|
|
218
|
+
// ── Today's spend drivers ──
|
|
219
|
+
// All scoped to the same agent-day bounds as the Today card so the
|
|
220
|
+
// numbers reconcile: Σ byEventType.total_cost === today.costUsd.
|
|
221
|
+
// The windowed byEventType above answers "what cost money this month";
|
|
222
|
+
// these answer "what is costing money *right now*" — the question the
|
|
223
|
+
// owner asks when the Today card looks unexpectedly high.
|
|
224
|
+
const todayTopActions = db
|
|
225
|
+
.prepare(`SELECT id, event_id, action_type, trigger, model_used, model_usage_json, cost_usd,
|
|
226
|
+
tokens_input, tokens_output,
|
|
227
|
+
cache_creation_tokens, cache_read_tokens,
|
|
228
|
+
duration_ms, num_turns,
|
|
229
|
+
result, detail, started_at, completed_at, error
|
|
230
|
+
FROM agent_actions
|
|
231
|
+
WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
|
|
232
|
+
AND cost_usd IS NOT NULL AND cost_usd > 0
|
|
233
|
+
ORDER BY cost_usd DESC, datetime(started_at) DESC
|
|
234
|
+
LIMIT ${TODAY_TOP_ACTIONS_LIMIT}`)
|
|
235
|
+
.all(bounds.start, bounds.end);
|
|
236
|
+
const todayByEventType = db
|
|
237
|
+
.prepare(`SELECT action_type as event_type,
|
|
238
|
+
SUM(cost_usd) as total_cost,
|
|
239
|
+
COUNT(*) as session_count
|
|
240
|
+
FROM agent_actions
|
|
241
|
+
WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
|
|
242
|
+
AND cost_usd IS NOT NULL
|
|
243
|
+
GROUP BY action_type
|
|
244
|
+
ORDER BY total_cost DESC`)
|
|
245
|
+
.all(bounds.start, bounds.end);
|
|
246
|
+
const todayByTrigger = db
|
|
247
|
+
.prepare(`SELECT COALESCE(trigger, 'unknown') as trigger,
|
|
248
|
+
SUM(cost_usd) as total_cost,
|
|
249
|
+
COUNT(*) as session_count
|
|
250
|
+
FROM agent_actions
|
|
251
|
+
WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
|
|
252
|
+
AND cost_usd IS NOT NULL
|
|
253
|
+
GROUP BY 1
|
|
254
|
+
ORDER BY total_cost DESC`)
|
|
255
|
+
.all(bounds.start, bounds.end);
|
|
256
|
+
const todayTokens = db
|
|
257
|
+
.prepare(`SELECT COALESCE(SUM(tokens_input), 0) as input,
|
|
258
|
+
COALESCE(SUM(tokens_output), 0) as output,
|
|
259
|
+
COALESCE(SUM(cache_read_tokens), 0) as cacheRead,
|
|
260
|
+
COALESCE(SUM(cache_creation_tokens), 0) as cacheCreation
|
|
261
|
+
FROM agent_actions
|
|
262
|
+
WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
|
|
263
|
+
AND cost_usd IS NOT NULL`)
|
|
264
|
+
.get(bounds.start, bounds.end);
|
|
265
|
+
const todayFailed = db
|
|
266
|
+
.prepare(`SELECT COALESCE(SUM(cost_usd), 0) as cost,
|
|
267
|
+
COUNT(*) as sessions
|
|
268
|
+
FROM agent_actions
|
|
269
|
+
WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
|
|
270
|
+
AND cost_usd IS NOT NULL
|
|
271
|
+
AND result = 'failed'`)
|
|
272
|
+
.get(bounds.start, bounds.end);
|
|
214
273
|
return c.json({
|
|
215
274
|
period,
|
|
216
275
|
today: { costUsd: today.cost, sessions: today.sessions },
|
|
@@ -219,6 +278,13 @@ export function registerCostApprovalsRoutes(app, deps) {
|
|
|
219
278
|
byEventType,
|
|
220
279
|
byBackend,
|
|
221
280
|
byBackendPeriod,
|
|
281
|
+
todayBreakdown: {
|
|
282
|
+
topActions: todayTopActions,
|
|
283
|
+
byEventType: todayByEventType,
|
|
284
|
+
byTrigger: todayByTrigger,
|
|
285
|
+
tokens: todayTokens,
|
|
286
|
+
failed: { costUsd: todayFailed.cost, sessions: todayFailed.sessions },
|
|
287
|
+
},
|
|
222
288
|
});
|
|
223
289
|
});
|
|
224
290
|
// ── Approvals API ──
|
|
@@ -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
|
|
@@ -168,8 +168,9 @@ export function registerOauthGoogleRoutes(app, deps) {
|
|
|
168
168
|
catch {
|
|
169
169
|
return c.json({ error: "googleapis package not installed" }, 500);
|
|
170
170
|
}
|
|
171
|
-
// Use daemon's own port for the OAuth callback
|
|
172
|
-
|
|
171
|
+
// Use daemon's own port for the OAuth callback.
|
|
172
|
+
// Use 127.0.0.1, not localhost: daemon binds IPv4 loopback only; on Windows localhost resolves to ::1 first and the callback would ECONNREFUSED. Google special-cases loopback redirects for installed-app clients.
|
|
173
|
+
const redirectUri = `http://127.0.0.1:${config.apiPort}/api/config/google-auth/callback`;
|
|
173
174
|
const oauth2Client = new google.auth.OAuth2(clientConfig.client_id, clientConfig.client_secret, redirectUri);
|
|
174
175
|
// Request scopes for Calendar + Gmail
|
|
175
176
|
const scopes = [
|
|
@@ -255,7 +256,8 @@ export function registerOauthGoogleRoutes(app, deps) {
|
|
|
255
256
|
throw new Error("Invalid credentials");
|
|
256
257
|
const mod = await import("googleapis");
|
|
257
258
|
const google = mod.google;
|
|
258
|
-
|
|
259
|
+
// Use 127.0.0.1, not localhost: must byte-match the auth-start redirect_uri (Google re-validates an exact string at getToken); daemon binds IPv4 loopback only, and on Windows localhost resolves to ::1 first.
|
|
260
|
+
const redirectUri = `http://127.0.0.1:${config.apiPort}/api/config/google-auth/callback`;
|
|
259
261
|
const oauth2Client = new google.auth.OAuth2(clientConfig.client_id, clientConfig.client_secret, redirectUri);
|
|
260
262
|
// Exchange authorization code for tokens
|
|
261
263
|
const { tokens } = await oauth2Client.getToken(code);
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { redactSensitiveString } from "@aitne/shared";
|
|
5
|
+
import { readJsonBody } from "../json-body.js";
|
|
6
|
+
import { getAgent } from "../../db/agents-store.js";
|
|
7
|
+
import { consumeFeedbackSignals, countPendingFeedbackSignals, findRecentFeedbackSignal, recordFeedbackSignal, } from "../../db/feedback-signals-store.js";
|
|
8
|
+
import { getContextDir } from "../../config.js";
|
|
9
|
+
import { CONTEXT_RELATIVE_PATHS, agentLessonsPath, } from "../../core/context-paths.js";
|
|
10
|
+
import { GLOBAL_LESSON_ENTRY_CAP, PER_AGENT_LESSON_ENTRY_CAP, } from "../../core/feedback/consolidation-prep.js";
|
|
11
|
+
import { summarizeLessonStore } from "../../core/feedback/lesson-store-overview.js";
|
|
12
|
+
import { isSafeAgentSlug } from "../../core/feedback/scope-parser.js";
|
|
13
|
+
import { createLogger } from "../../logging.js";
|
|
14
|
+
const logger = createLogger("feedback-api");
|
|
15
|
+
const DEDUP_TTL_SECONDS = 10 * 60;
|
|
16
|
+
const MAX_SUMMARY_CHARS = 280;
|
|
17
|
+
const MAX_SCOPE_REF_CHARS = 120;
|
|
18
|
+
const MAX_ACTION_REF_CHARS = 160;
|
|
19
|
+
const MAX_EVIDENCE_STRING_CHARS = 500;
|
|
20
|
+
const ALLOWED_SOURCES = new Set([
|
|
21
|
+
"explicit",
|
|
22
|
+
"self_critique",
|
|
23
|
+
]);
|
|
24
|
+
const ALL_SOURCES = new Set([
|
|
25
|
+
"behavioral",
|
|
26
|
+
"explicit",
|
|
27
|
+
"self_critique",
|
|
28
|
+
]);
|
|
29
|
+
const VALENCES = new Set([
|
|
30
|
+
"positive",
|
|
31
|
+
"negative",
|
|
32
|
+
"neutral",
|
|
33
|
+
"correction",
|
|
34
|
+
]);
|
|
35
|
+
const API_SCOPE_TYPES = new Set([
|
|
36
|
+
"user",
|
|
37
|
+
"agent",
|
|
38
|
+
"agent_slug",
|
|
39
|
+
]);
|
|
40
|
+
const ACTION_KINDS = new Set([
|
|
41
|
+
"notification",
|
|
42
|
+
"agent_execution",
|
|
43
|
+
"vault_write",
|
|
44
|
+
"dm_reply",
|
|
45
|
+
]);
|
|
46
|
+
const KINDS = new Set([
|
|
47
|
+
"preference",
|
|
48
|
+
"correction",
|
|
49
|
+
"do-more",
|
|
50
|
+
"do-less",
|
|
51
|
+
"constraint",
|
|
52
|
+
]);
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
function truncate(value, maxChars) {
|
|
57
|
+
return value.length <= maxChars ? value : value.slice(0, maxChars);
|
|
58
|
+
}
|
|
59
|
+
function sanitizeString(value, maxChars = MAX_EVIDENCE_STRING_CHARS) {
|
|
60
|
+
return redactSensitiveString(truncate(value.replace(/[\u0000-\u001f\u007f]/g, " "), maxChars)).trim();
|
|
61
|
+
}
|
|
62
|
+
function sanitizeEvidence(value, depth = 0) {
|
|
63
|
+
if (depth > 4)
|
|
64
|
+
return "[truncated]";
|
|
65
|
+
if (typeof value === "string")
|
|
66
|
+
return sanitizeString(value);
|
|
67
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null)
|
|
68
|
+
return value;
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
return value.slice(0, 20).map((entry) => sanitizeEvidence(entry, depth + 1));
|
|
71
|
+
}
|
|
72
|
+
if (!isRecord(value))
|
|
73
|
+
return null;
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [key, entry] of Object.entries(value).slice(0, 50)) {
|
|
76
|
+
out[sanitizeString(key, 80)] = sanitizeEvidence(entry, depth + 1);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function describeType(value) {
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
return "missing";
|
|
83
|
+
if (value === null)
|
|
84
|
+
return "null";
|
|
85
|
+
if (Array.isArray(value))
|
|
86
|
+
return "array";
|
|
87
|
+
return typeof value;
|
|
88
|
+
}
|
|
89
|
+
function buildLessonStoreEntry(contextDir, scope, relPath, caps) {
|
|
90
|
+
const full = join(contextDir, relPath);
|
|
91
|
+
if (!existsSync(full)) {
|
|
92
|
+
return {
|
|
93
|
+
scope,
|
|
94
|
+
path: relPath,
|
|
95
|
+
exists: false,
|
|
96
|
+
lastModified: null,
|
|
97
|
+
bytes: 0,
|
|
98
|
+
capBytes: caps.capBytes,
|
|
99
|
+
entries: 0,
|
|
100
|
+
maxEntries: caps.maxEntries,
|
|
101
|
+
active: 0,
|
|
102
|
+
provisional: 0,
|
|
103
|
+
overCap: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const summary = summarizeLessonStore(readFileSync(full, "utf-8"), caps);
|
|
107
|
+
return {
|
|
108
|
+
scope,
|
|
109
|
+
path: relPath,
|
|
110
|
+
exists: true,
|
|
111
|
+
lastModified: statSync(full).mtime.toISOString(),
|
|
112
|
+
...summary,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function createFeedbackRoutes(deps) {
|
|
116
|
+
const app = new Hono();
|
|
117
|
+
const { db, config } = deps;
|
|
118
|
+
/**
|
|
119
|
+
* GET /feedback/lessons — read-only overview of the consolidated lesson
|
|
120
|
+
* stores for the dashboard "view/edit lessons and tune caps/threshold"
|
|
121
|
+
* surface (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5). Lists the global
|
|
122
|
+
* `agent` store (always, so its cap shows even before first write) plus every
|
|
123
|
+
* per-agent `agent:<slug>` store that exists on disk, each with cap-utilisation
|
|
124
|
+
* metrics. The file bodies are read/edited through the existing
|
|
125
|
+
* `GET/PUT /api/context/<path>` chokepoint — this endpoint only enumerates +
|
|
126
|
+
* summarises. `RiskTier.Autonomous` (read-only, no secrets — lesson prose was
|
|
127
|
+
* redaction-scrubbed at capture).
|
|
128
|
+
*/
|
|
129
|
+
app.get("/feedback/lessons", (c) => {
|
|
130
|
+
const contextDir = getContextDir(config, db);
|
|
131
|
+
const globalCaps = {
|
|
132
|
+
capBytes: config.feedbackLessonMaxBytesGlobal,
|
|
133
|
+
maxEntries: GLOBAL_LESSON_ENTRY_CAP,
|
|
134
|
+
};
|
|
135
|
+
const perAgentCaps = {
|
|
136
|
+
capBytes: config.feedbackLessonMaxBytesPerAgent,
|
|
137
|
+
maxEntries: PER_AGENT_LESSON_ENTRY_CAP,
|
|
138
|
+
};
|
|
139
|
+
const stores = [
|
|
140
|
+
buildLessonStoreEntry(contextDir, "agent", CONTEXT_RELATIVE_PATHS.agentLessons, globalCaps),
|
|
141
|
+
];
|
|
142
|
+
const agentsDir = join(contextDir, "policies", "agents");
|
|
143
|
+
if (existsSync(agentsDir)) {
|
|
144
|
+
const slugs = readdirSync(agentsDir, { withFileTypes: true })
|
|
145
|
+
.filter((entry) => entry.isDirectory() && isSafeAgentSlug(entry.name))
|
|
146
|
+
.map((entry) => entry.name)
|
|
147
|
+
.sort();
|
|
148
|
+
for (const slug of slugs) {
|
|
149
|
+
const rel = agentLessonsPath(slug);
|
|
150
|
+
if (!existsSync(join(contextDir, rel)))
|
|
151
|
+
continue;
|
|
152
|
+
stores.push(buildLessonStoreEntry(contextDir, `agent:${slug}`, rel, perAgentCaps));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return c.json({
|
|
156
|
+
enabled: config.feedbackLearningEnabled !== false,
|
|
157
|
+
promotionThreshold: config.feedbackPromotionThreshold,
|
|
158
|
+
pendingSignals: countPendingFeedbackSignals(db),
|
|
159
|
+
stores,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
app.post("/feedback", async (c) => {
|
|
163
|
+
// Master kill-switch (FEEDBACK_LEARNING_LOOP_DESIGN.md §7). When the loop is
|
|
164
|
+
// disabled the daemon-side behavioral sink already short-circuits
|
|
165
|
+
// (`SignalDetector`); mirror that here so explicit / self_critique captures
|
|
166
|
+
// from the always-included DM + review task-flows are dropped too. Otherwise
|
|
167
|
+
// unconsumed rows would accumulate unbounded — the nightly consolidation that
|
|
168
|
+
// would consume them is gated off, and the retention sweep only deletes
|
|
169
|
+
// already-consumed rows. Returns 200 so the calling turn neither errors nor
|
|
170
|
+
// retries. `config` is optional in some test harnesses → default-on.
|
|
171
|
+
if (config?.feedbackLearningEnabled === false) {
|
|
172
|
+
return c.json({ disabled: true });
|
|
173
|
+
}
|
|
174
|
+
const parsedBody = await readJsonBody(c);
|
|
175
|
+
if (!parsedBody.ok)
|
|
176
|
+
return parsedBody.response;
|
|
177
|
+
const body = parsedBody.body;
|
|
178
|
+
if (!isRecord(body)) {
|
|
179
|
+
return c.json({
|
|
180
|
+
error: "validation_error",
|
|
181
|
+
message: "Body must be a JSON object",
|
|
182
|
+
}, 400);
|
|
183
|
+
}
|
|
184
|
+
const issues = [];
|
|
185
|
+
const rawSource = body.source;
|
|
186
|
+
const source = typeof rawSource === "string" ? rawSource : "";
|
|
187
|
+
if (!ALL_SOURCES.has(source)) {
|
|
188
|
+
issues.push({
|
|
189
|
+
field: "source",
|
|
190
|
+
expected: "'explicit' | 'self_critique'",
|
|
191
|
+
got: describeType(rawSource),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else if (!ALLOWED_SOURCES.has(source)) {
|
|
195
|
+
issues.push({
|
|
196
|
+
field: "source",
|
|
197
|
+
expected: "'explicit' | 'self_critique'",
|
|
198
|
+
got: "'behavioral'",
|
|
199
|
+
hint: "Behavioral feedback is daemon-only and is written by SignalDetector.",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const summary = typeof body.summary === "string"
|
|
203
|
+
? sanitizeString(body.summary, MAX_SUMMARY_CHARS)
|
|
204
|
+
: "";
|
|
205
|
+
if (summary.length === 0) {
|
|
206
|
+
issues.push({
|
|
207
|
+
field: "summary",
|
|
208
|
+
expected: "non-empty string",
|
|
209
|
+
got: describeType(body.summary),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const rawValence = body.valence;
|
|
213
|
+
const valence = typeof rawValence === "string" ? rawValence : null;
|
|
214
|
+
if (valence === null || !VALENCES.has(valence)) {
|
|
215
|
+
issues.push({
|
|
216
|
+
field: "valence",
|
|
217
|
+
expected: "'positive' | 'negative' | 'neutral' | 'correction'",
|
|
218
|
+
got: describeType(rawValence),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const rawKind = body.kind;
|
|
222
|
+
const kind = typeof rawKind === "string" ? rawKind : null;
|
|
223
|
+
if (kind !== null && !KINDS.has(kind)) {
|
|
224
|
+
issues.push({
|
|
225
|
+
field: "kind",
|
|
226
|
+
expected: "'preference' | 'correction' | 'do-more' | 'do-less' | 'constraint' (optional)",
|
|
227
|
+
got: kind,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const rawScopeType = body.scope_type;
|
|
231
|
+
const scopeType = typeof rawScopeType === "string" ? rawScopeType : "";
|
|
232
|
+
if (!API_SCOPE_TYPES.has(scopeType)) {
|
|
233
|
+
issues.push({
|
|
234
|
+
field: "scope_type",
|
|
235
|
+
expected: "'user' | 'agent' | 'agent_slug'",
|
|
236
|
+
got: describeType(rawScopeType),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const scopeRef = typeof body.scope_ref === "string"
|
|
240
|
+
? sanitizeString(body.scope_ref, MAX_SCOPE_REF_CHARS)
|
|
241
|
+
: null;
|
|
242
|
+
let agentId = null;
|
|
243
|
+
if (scopeType === "agent_slug") {
|
|
244
|
+
if (!scopeRef) {
|
|
245
|
+
issues.push({
|
|
246
|
+
field: "scope_ref",
|
|
247
|
+
expected: "existing agent slug when scope_type='agent_slug'",
|
|
248
|
+
got: describeType(body.scope_ref),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else if (!getAgent(db, scopeRef)) {
|
|
252
|
+
issues.push({
|
|
253
|
+
field: "scope_ref",
|
|
254
|
+
expected: "existing agent slug",
|
|
255
|
+
got: scopeRef,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
agentId = scopeRef;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const rawActionKind = body.action_kind;
|
|
263
|
+
const actionKind = typeof rawActionKind === "string" ? rawActionKind : null;
|
|
264
|
+
if (actionKind !== null
|
|
265
|
+
&& !ACTION_KINDS.has(actionKind)) {
|
|
266
|
+
issues.push({
|
|
267
|
+
field: "action_kind",
|
|
268
|
+
expected: "'notification' | 'agent_execution' | 'vault_write' | 'dm_reply' (optional)",
|
|
269
|
+
got: actionKind,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const actionRef = typeof body.action_ref === "string"
|
|
273
|
+
? sanitizeString(body.action_ref, MAX_ACTION_REF_CHARS)
|
|
274
|
+
: null;
|
|
275
|
+
if (issues.length > 0) {
|
|
276
|
+
return c.json({
|
|
277
|
+
error: "validation_error",
|
|
278
|
+
message: "Request body failed schema validation",
|
|
279
|
+
issues,
|
|
280
|
+
}, 400);
|
|
281
|
+
}
|
|
282
|
+
const normalizedScopeRef = scopeType === "agent_slug" ? scopeRef : null;
|
|
283
|
+
const deduped = findRecentFeedbackSignal(db, {
|
|
284
|
+
scopeType: scopeType,
|
|
285
|
+
scopeRef: normalizedScopeRef,
|
|
286
|
+
summary,
|
|
287
|
+
withinSeconds: DEDUP_TTL_SECONDS,
|
|
288
|
+
});
|
|
289
|
+
if (deduped) {
|
|
290
|
+
return c.json({ id: deduped.id, deduped: true });
|
|
291
|
+
}
|
|
292
|
+
const evidence = sanitizeEvidence(body.evidence);
|
|
293
|
+
const id = recordFeedbackSignal(db, {
|
|
294
|
+
source: source,
|
|
295
|
+
valence: valence,
|
|
296
|
+
scopeType: scopeType,
|
|
297
|
+
scopeRef: normalizedScopeRef,
|
|
298
|
+
actionKind: actionKind,
|
|
299
|
+
actionRef,
|
|
300
|
+
agentId,
|
|
301
|
+
summary,
|
|
302
|
+
evidence: {
|
|
303
|
+
...(isRecord(evidence)
|
|
304
|
+
? evidence
|
|
305
|
+
: evidence === null
|
|
306
|
+
? {}
|
|
307
|
+
: { value: evidence }),
|
|
308
|
+
...(kind ? { kind } : {}),
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
logger.info({ id, source, scopeType, scopeRef: normalizedScopeRef }, "Feedback signal recorded");
|
|
312
|
+
return c.json({ id });
|
|
313
|
+
});
|
|
314
|
+
app.post("/feedback/consume", async (c) => {
|
|
315
|
+
const parsedBody = await readJsonBody(c);
|
|
316
|
+
if (!parsedBody.ok)
|
|
317
|
+
return parsedBody.response;
|
|
318
|
+
const body = parsedBody.body;
|
|
319
|
+
if (!isRecord(body)) {
|
|
320
|
+
return c.json({
|
|
321
|
+
error: "validation_error",
|
|
322
|
+
message: "Body must be a JSON object",
|
|
323
|
+
expectedShape: '{"ids": number[], "lessonRef"?: string}',
|
|
324
|
+
}, 400);
|
|
325
|
+
}
|
|
326
|
+
if (!Array.isArray(body.ids)) {
|
|
327
|
+
return c.json({
|
|
328
|
+
error: "validation_error",
|
|
329
|
+
message: "'ids' must be an array of integer feedback signal ids",
|
|
330
|
+
expectedShape: '{"ids": number[], "lessonRef"?: string}',
|
|
331
|
+
}, 400);
|
|
332
|
+
}
|
|
333
|
+
const nonInt = body.ids.find((id) => typeof id !== "number" || !Number.isInteger(id));
|
|
334
|
+
if (nonInt !== undefined) {
|
|
335
|
+
return c.json({
|
|
336
|
+
error: "validation_error",
|
|
337
|
+
message: "'ids' must contain only integers",
|
|
338
|
+
got: JSON.stringify(nonInt),
|
|
339
|
+
}, 400);
|
|
340
|
+
}
|
|
341
|
+
const lessonRef = typeof body.lessonRef === "string"
|
|
342
|
+
? sanitizeString(body.lessonRef, 240)
|
|
343
|
+
: null;
|
|
344
|
+
const result = consumeFeedbackSignals(db, body.ids, lessonRef);
|
|
345
|
+
logger.info({ consumed: result.consumed }, "Feedback signals consumed");
|
|
346
|
+
return c.json(result);
|
|
347
|
+
});
|
|
348
|
+
return app;
|
|
349
|
+
}
|
package/dist/api/routes/git.js
CHANGED
|
@@ -113,8 +113,12 @@ export function createGitRoutes(deps) {
|
|
|
113
113
|
}
|
|
114
114
|
// Use || (not ??) so explicit ?ref= (empty string) falls back to default
|
|
115
115
|
const ref = c.req.query("ref") || "HEAD~1..HEAD";
|
|
116
|
-
// Sanitize ref — reject shell metacharacters and flag-like arguments
|
|
117
|
-
|
|
116
|
+
// Sanitize ref — reject shell metacharacters and flag-like arguments.
|
|
117
|
+
// Also reject `:` — `git diff <rev>:<path>` is blob/tree syntax that reads
|
|
118
|
+
// arbitrary committed file content, beyond this endpoint's commit-diff
|
|
119
|
+
// purpose. No legitimate commit/range ref (`HEAD~1..HEAD`, `origin/main`)
|
|
120
|
+
// contains a colon. (execFile already prevents shell injection.)
|
|
121
|
+
if (/[;&|`$]/.test(ref) || ref.startsWith("-") || ref.includes(":")) {
|
|
118
122
|
return respondWithAgentError(c, 400, [
|
|
119
123
|
composeIssue("git.invalid_ref", {
|
|
120
124
|
field: "ref",
|
|
@@ -149,7 +153,10 @@ export function createGitRoutes(deps) {
|
|
|
149
153
|
], { legacyErrorCode: "invalid or missing repo" });
|
|
150
154
|
}
|
|
151
155
|
const hash = c.req.query("hash") || "HEAD";
|
|
152
|
-
|
|
156
|
+
// Reject `:` for the same reason as /git/diff — `git show <rev>:<path>`
|
|
157
|
+
// prints arbitrary committed file content, beyond this endpoint's
|
|
158
|
+
// commit-show purpose. A commit hash / ref never contains a colon.
|
|
159
|
+
if (/[;&|`$]/.test(hash) || hash.startsWith("-") || hash.includes(":")) {
|
|
153
160
|
return respondWithAgentError(c, 400, [
|
|
154
161
|
composeIssue("git.invalid_hash", {
|
|
155
162
|
field: "hash",
|
|
@@ -224,7 +224,11 @@ export function createGitHubRoutes(deps) {
|
|
|
224
224
|
}
|
|
225
225
|
const event = parseGitHubEvent(eventType, payload, resolved.repositoryId);
|
|
226
226
|
if (event) {
|
|
227
|
-
|
|
227
|
+
// `EventBus.put` is async; awaiting it (like the repositories routes do)
|
|
228
|
+
// ensures the event is enqueued before the webhook returns `accepted`
|
|
229
|
+
// and surfaces any enqueue rejection instead of dropping it as an
|
|
230
|
+
// unhandled promise rejection.
|
|
231
|
+
await eventBus.put(event);
|
|
228
232
|
logger.info({ eventType, action: payload.action, repositoryId: resolved.repositoryId }, "GitHub webhook event received");
|
|
229
233
|
}
|
|
230
234
|
// Notify GitWatcher that webhook is alive (adjusts polling frequency)
|
|
@@ -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.
|