@aitne/daemon 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.js +8 -5
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/context/permissions.js +3 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +48 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +9 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +245 -7
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +26 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +25 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +39 -15
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +62 -20
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +1 -1
- package/dist/core/context-paths.js +1 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +49 -0
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +17 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +63 -1
- package/dist/core/dispatcher-scheduled-tasks.js +213 -6
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +284 -59
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.js +17 -5
- package/dist/core/feedback/eviction-scorer.js +6 -2
- package/dist/core/feedback/lesson-format.js +9 -4
- package/dist/core/feedback/lesson-injection.d.ts +1 -1
- package/dist/core/feedback/lesson-injection.js +17 -2
- package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
- package/dist/core/feedback/lesson-store-overview.js +8 -4
- package/dist/core/feedback/regeneralization-prep.js +29 -16
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +4 -4
- package/dist/core/injection-policy.js +4 -4
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +12 -0
- package/dist/core/signal-detector.js +53 -9
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +2 -2
- package/dist/core/today-direct-writer.js +1 -1
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/migrations.js +330 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +217 -16
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +75 -11
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +16 -6
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +38 -11
- package/dist/settings/runtime-settings.js +203 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/background-task/* — BACKGROUND_TASK_RUNNER_DESIGN.md §7.
|
|
3
|
+
*
|
|
4
|
+
* The generic detached-task surface. The DM agent POSTs a self-contained
|
|
5
|
+
* brief, acks, and ends its turn; the runner runs the worker detached and
|
|
6
|
+
* writes an artifact; the delivery boundary surfaces it. GET /:id returns
|
|
7
|
+
* the artifact (the DM agent's "read the result" affordance for precise
|
|
8
|
+
* follow-ups). /clarify relays the owner's answer; /cancel aborts.
|
|
9
|
+
*
|
|
10
|
+
* Routes (§7):
|
|
11
|
+
* POST /api/background-task spawn (or schedule via scheduleAt)
|
|
12
|
+
* GET /api/background-task list
|
|
13
|
+
* GET /api/background-task/:id the artifact (full detail)
|
|
14
|
+
* POST /api/background-task/:id/clarify answer a pending clarification
|
|
15
|
+
* POST /api/background-task/:id/cancel abort in-flight task
|
|
16
|
+
*
|
|
17
|
+
* Excluded from the 100% coverage gate — route glue. The pure pieces
|
|
18
|
+
* (budget envelope, slot manager) are 100%-covered peers.
|
|
19
|
+
*/
|
|
20
|
+
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { Hono } from "hono";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
import { formatSqliteDatetime } from "@aitne/shared";
|
|
24
|
+
import { countBackgroundTasks, createBackgroundTask, findRecentDuplicateBackgroundTask, getBackgroundTask, listBackgroundTasks, markTerminal, } from "../../db/background-task-store.js";
|
|
25
|
+
import { getOpenClarificationForTask, listClarificationsForTask, resolveClarification, } from "../../db/background-task-clarifications-store.js";
|
|
26
|
+
import { listPrimaryChannels, channelRef, } from "../../db/browser-automation-purchase-primary-channels-store.js";
|
|
27
|
+
import { selectDefaultOwnerChannel } from "../../messaging/owner-channels.js";
|
|
28
|
+
import { createBackgroundTaskTransitionEmitter } from "../../services/background-task/background-task-transition-events.js";
|
|
29
|
+
import { createLogger } from "../../logging.js";
|
|
30
|
+
import { readJsonBody } from "../json-body.js";
|
|
31
|
+
const logger = createLogger("background-task-routes");
|
|
32
|
+
const postBodySchema = z.object({
|
|
33
|
+
brief: z.string().min(1).max(16_384),
|
|
34
|
+
title: z.string().min(1).max(200).optional(),
|
|
35
|
+
notificationPolicy: z
|
|
36
|
+
.enum(["always", "if_significant", "silent"])
|
|
37
|
+
.optional()
|
|
38
|
+
.default("always"),
|
|
39
|
+
// Phase 4 if_significant criteria DSL (§4.3) — optional structured
|
|
40
|
+
// conditions the worker checks one-by-one. Each is a concrete, atomic
|
|
41
|
+
// condition ("any repo's main build is red"); the worker sets
|
|
42
|
+
// notify=true iff ANY is met. Up to 12, each ≤500 chars.
|
|
43
|
+
significanceCriteria: z
|
|
44
|
+
.array(z.string().min(1).max(500))
|
|
45
|
+
.max(12)
|
|
46
|
+
.optional(),
|
|
47
|
+
tier: z.enum(["lite", "medium", "high"]).optional(),
|
|
48
|
+
maxBudgetUsd: z.number().positive().max(15).optional(),
|
|
49
|
+
originatingChannel: z.string().optional(),
|
|
50
|
+
correlationId: z.string().optional(),
|
|
51
|
+
scheduleAt: z.string().datetime({ offset: true }).optional(),
|
|
52
|
+
});
|
|
53
|
+
const clarifyBodySchema = z.object({
|
|
54
|
+
clarificationId: z.string().uuid().optional(),
|
|
55
|
+
answer: z.string().min(1).max(8192),
|
|
56
|
+
});
|
|
57
|
+
const cancelBodySchema = z
|
|
58
|
+
.object({ reason: z.string().max(256).optional() })
|
|
59
|
+
.optional();
|
|
60
|
+
function toWire(row) {
|
|
61
|
+
return {
|
|
62
|
+
id: row.id,
|
|
63
|
+
brief: row.brief,
|
|
64
|
+
title: row.title,
|
|
65
|
+
state: row.state,
|
|
66
|
+
notificationPolicy: row.notificationPolicy,
|
|
67
|
+
significanceCriteria: row.significanceCriteria,
|
|
68
|
+
report: row.report,
|
|
69
|
+
draft: row.draft,
|
|
70
|
+
notify: row.notify,
|
|
71
|
+
significance: row.significance,
|
|
72
|
+
artifactPath: row.artifactPath,
|
|
73
|
+
outcomeDetail: row.outcomeDetail,
|
|
74
|
+
originatingChannel: row.originatingChannel,
|
|
75
|
+
correlationId: row.correlationId,
|
|
76
|
+
scheduleRowId: row.scheduleRowId,
|
|
77
|
+
tier: row.tier,
|
|
78
|
+
maxBudgetUsd: row.maxBudgetUsd,
|
|
79
|
+
createdAt: row.createdAt,
|
|
80
|
+
startedAt: row.startedAt,
|
|
81
|
+
finishedAt: row.finishedAt,
|
|
82
|
+
deliveredAt: row.deliveredAt,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Resolve the originating channel: header / body preferred, intersected
|
|
86
|
+
* with the primary set ∪ the owner's most-recent DM channel. A null
|
|
87
|
+
* result files the artifact but cannot DM it. */
|
|
88
|
+
function resolveOriginatingChannel(deps, headerChannel, bodyChannel) {
|
|
89
|
+
const requested = headerChannel ?? bodyChannel ?? null;
|
|
90
|
+
const primary = listPrimaryChannels(deps.db).map((row) => channelRef(row.platform, row.channelId));
|
|
91
|
+
const ownerDefault = selectDefaultOwnerChannel(deps.db);
|
|
92
|
+
const ownerDefaultRef = ownerDefault
|
|
93
|
+
? channelRef(ownerDefault.platform, ownerDefault.channelId)
|
|
94
|
+
: null;
|
|
95
|
+
if (requested) {
|
|
96
|
+
if (primary.includes(requested))
|
|
97
|
+
return requested;
|
|
98
|
+
return primary[0] ?? ownerDefaultRef;
|
|
99
|
+
}
|
|
100
|
+
return primary[0] ?? ownerDefaultRef;
|
|
101
|
+
}
|
|
102
|
+
export function createBackgroundTaskRoutes(deps) {
|
|
103
|
+
const app = new Hono();
|
|
104
|
+
const transitionEmitter = createBackgroundTaskTransitionEmitter(deps.eventBroadcaster ?? null);
|
|
105
|
+
// ── POST /api/background-task ─────────────────────────────────────────
|
|
106
|
+
app.post("/background-task", async (c) => {
|
|
107
|
+
const body = await readJsonBody(c);
|
|
108
|
+
if (!body.ok)
|
|
109
|
+
return body.response;
|
|
110
|
+
const parsed = postBodySchema.safeParse(body.body);
|
|
111
|
+
if (!parsed.success) {
|
|
112
|
+
return c.json({ error: "validation_error", details: parsed.error.flatten() }, 400);
|
|
113
|
+
}
|
|
114
|
+
const input = parsed.data;
|
|
115
|
+
const resolvedChannel = resolveOriginatingChannel(deps, c.req.header("x-pa-channel-ref"), input.originatingChannel);
|
|
116
|
+
if (resolvedChannel === null) {
|
|
117
|
+
logger.warn({}, "background-task: no originating channel resolvable — the result will be filed but cannot be DMed");
|
|
118
|
+
}
|
|
119
|
+
const id = randomUUID();
|
|
120
|
+
const title = input.title ?? null;
|
|
121
|
+
// ── Scheduled path ──────────────────────────────────────────────
|
|
122
|
+
if (input.scheduleAt !== undefined) {
|
|
123
|
+
const scheduledAtMs = Date.parse(input.scheduleAt);
|
|
124
|
+
if (!Number.isFinite(scheduledAtMs)) {
|
|
125
|
+
return c.json({ error: "invalid_schedule_at", detail: "scheduleAt must parse as ISO 8601." }, 400);
|
|
126
|
+
}
|
|
127
|
+
const nowMs = Date.now();
|
|
128
|
+
if (scheduledAtMs < nowMs - 60_000) {
|
|
129
|
+
return c.json({
|
|
130
|
+
error: "schedule_at_in_past",
|
|
131
|
+
detail: `scheduleAt resolves to more than 60s in the past (delta=${nowMs - scheduledAtMs}ms).`,
|
|
132
|
+
}, 400);
|
|
133
|
+
}
|
|
134
|
+
const scheduleContext = {
|
|
135
|
+
preGeneratedTaskId: id,
|
|
136
|
+
brief: input.brief,
|
|
137
|
+
title,
|
|
138
|
+
notificationPolicy: input.notificationPolicy,
|
|
139
|
+
significanceCriteria: input.significanceCriteria ?? null,
|
|
140
|
+
tier: input.tier ?? null,
|
|
141
|
+
maxBudgetUsd: input.maxBudgetUsd ?? null,
|
|
142
|
+
originatingChannel: resolvedChannel,
|
|
143
|
+
};
|
|
144
|
+
const correlationId = input.correlationId ?? randomUUID();
|
|
145
|
+
const label = title ?? input.brief.slice(0, 200);
|
|
146
|
+
const insertResult = deps.db
|
|
147
|
+
.prepare(`INSERT INTO agent_schedule
|
|
148
|
+
(scheduled_for, task_type, task_description, task_prompt, task_context, correlation_id, model, status)
|
|
149
|
+
VALUES (?, 'background_task', ?, ?, ?, ?, NULL, 'pending')`)
|
|
150
|
+
.run(formatSqliteDatetime(new Date(scheduledAtMs)), label, input.brief, JSON.stringify(scheduleContext), correlationId);
|
|
151
|
+
const scheduleRowId = Number(insertResult.lastInsertRowid);
|
|
152
|
+
logger.info({ taskId: id, scheduleRowId, scheduledAt: input.scheduleAt }, "background-task scheduled — fire-time row creation deferred to dispatcher");
|
|
153
|
+
return c.json({ taskId: id, status: "scheduled", scheduledFor: scheduledAtMs, scheduleRowId }, 202);
|
|
154
|
+
}
|
|
155
|
+
// ── Brief-dedup (§10.3 / Phase 4) ───────────────────────────────
|
|
156
|
+
// A replayed trigger (the RESEARCH_CLUSTER_COST_FIX_PLAN runaway
|
|
157
|
+
// class) or an over-eager agent POSTing the same brief repeatedly
|
|
158
|
+
// within minutes would otherwise spawn N detached workers. Collapse
|
|
159
|
+
// onto the first still-relevant identical task. `0` disables.
|
|
160
|
+
const dedupWindowMinutes = deps.config?.backgroundTaskDedupWindowMinutes ?? 0;
|
|
161
|
+
if (dedupWindowMinutes > 0) {
|
|
162
|
+
const existing = findRecentDuplicateBackgroundTask(deps.db, {
|
|
163
|
+
brief: input.brief,
|
|
164
|
+
tier: input.tier ?? null,
|
|
165
|
+
sinceMs: Date.now() - dedupWindowMinutes * 60_000,
|
|
166
|
+
});
|
|
167
|
+
if (existing) {
|
|
168
|
+
logger.info({
|
|
169
|
+
taskId: existing.id,
|
|
170
|
+
state: existing.state,
|
|
171
|
+
windowMinutes: dedupWindowMinutes,
|
|
172
|
+
}, "background-task dedup — identical brief within window; returning existing task instead of spawning a duplicate");
|
|
173
|
+
return c.json({
|
|
174
|
+
taskId: existing.id,
|
|
175
|
+
status: existing.state,
|
|
176
|
+
deduplicated: true,
|
|
177
|
+
row: toWire(existing),
|
|
178
|
+
}, 202);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const createdAt = Date.now();
|
|
182
|
+
const row = createBackgroundTask(deps.db, {
|
|
183
|
+
id,
|
|
184
|
+
brief: input.brief,
|
|
185
|
+
title,
|
|
186
|
+
notificationPolicy: input.notificationPolicy,
|
|
187
|
+
significanceCriteria: input.significanceCriteria ?? null,
|
|
188
|
+
originatingChannel: resolvedChannel,
|
|
189
|
+
correlationId: input.correlationId ?? null,
|
|
190
|
+
scheduleRowId: null,
|
|
191
|
+
tier: input.tier ?? null,
|
|
192
|
+
maxBudgetUsd: input.maxBudgetUsd ?? null,
|
|
193
|
+
createdAt,
|
|
194
|
+
});
|
|
195
|
+
transitionEmitter.emitFromRow(row, createdAt);
|
|
196
|
+
if (deps.backgroundTaskRunner) {
|
|
197
|
+
void deps.backgroundTaskRunner.runFromPost(id).catch((err) => {
|
|
198
|
+
logger.error({ err, taskId: id }, "background-task runner threw on dispatch — task left in pending state");
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// No runner wired (tests / lite installs) — synthetic terminal so
|
|
203
|
+
// the row doesn't hang in pending.
|
|
204
|
+
const finishedAt = Date.now();
|
|
205
|
+
const terminal = markTerminal(deps.db, {
|
|
206
|
+
id,
|
|
207
|
+
state: "failed",
|
|
208
|
+
outcomeDetail: "runner_unavailable",
|
|
209
|
+
finishedAt,
|
|
210
|
+
});
|
|
211
|
+
transitionEmitter.emitFromRow(terminal, finishedAt);
|
|
212
|
+
}
|
|
213
|
+
const postDispatchRow = getBackgroundTask(deps.db, id) ?? row;
|
|
214
|
+
return c.json({ taskId: id, status: postDispatchRow.state, row: toWire(postDispatchRow) }, 202);
|
|
215
|
+
});
|
|
216
|
+
// ── GET /api/background-task ──────────────────────────────────────────
|
|
217
|
+
app.get("/background-task", (c) => {
|
|
218
|
+
const stateQuery = c.req.query("state");
|
|
219
|
+
const states = stateQuery
|
|
220
|
+
? stateQuery.split(",").filter((s) => s.length > 0)
|
|
221
|
+
: undefined;
|
|
222
|
+
// §10.5 — the filed-results digest / "did that monitor run?" pull:
|
|
223
|
+
// `notify=false` + `sinceHours=N` narrows to recently-filed results.
|
|
224
|
+
const notifyQuery = c.req.query("notify");
|
|
225
|
+
const notify = notifyQuery === "true" || notifyQuery === "1"
|
|
226
|
+
? true
|
|
227
|
+
: notifyQuery === "false" || notifyQuery === "0"
|
|
228
|
+
? false
|
|
229
|
+
: undefined;
|
|
230
|
+
const sinceHours = Number(c.req.query("sinceHours"));
|
|
231
|
+
const finishedSinceMs = Number.isFinite(sinceHours) && sinceHours > 0
|
|
232
|
+
? Date.now() - sinceHours * 3_600_000
|
|
233
|
+
: undefined;
|
|
234
|
+
const limit = Math.min(200, Math.max(0, Number(c.req.query("limit")) || 50));
|
|
235
|
+
const offset = Math.max(0, Number(c.req.query("offset")) || 0);
|
|
236
|
+
const filter = { states, notify, finishedSinceMs };
|
|
237
|
+
const rows = listBackgroundTasks(deps.db, { ...filter, limit, offset });
|
|
238
|
+
const total = countBackgroundTasks(deps.db, filter);
|
|
239
|
+
return c.json({ tasks: rows.map(toWire), total, limit, offset });
|
|
240
|
+
});
|
|
241
|
+
// ── GET /api/background-task/:id (the artifact) ───────────────────────
|
|
242
|
+
app.get("/background-task/:id", (c) => {
|
|
243
|
+
const id = c.req.param("id");
|
|
244
|
+
const row = getBackgroundTask(deps.db, id);
|
|
245
|
+
if (!row)
|
|
246
|
+
return c.json({ error: "not_found" }, 404);
|
|
247
|
+
return c.json({
|
|
248
|
+
...toWire(row),
|
|
249
|
+
clarifications: listClarificationsForTask(deps.db, id),
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
// ── POST /api/background-task/:id/clarify ─────────────────────────────
|
|
253
|
+
app.post("/background-task/:id/clarify", async (c) => {
|
|
254
|
+
const id = c.req.param("id");
|
|
255
|
+
const body = await readJsonBody(c);
|
|
256
|
+
if (!body.ok)
|
|
257
|
+
return body.response;
|
|
258
|
+
const parsed = clarifyBodySchema.safeParse(body.body);
|
|
259
|
+
if (!parsed.success) {
|
|
260
|
+
return c.json({ error: "validation_error", details: parsed.error.flatten() }, 400);
|
|
261
|
+
}
|
|
262
|
+
const row = getBackgroundTask(deps.db, id);
|
|
263
|
+
if (!row)
|
|
264
|
+
return c.json({ error: "not_found" }, 404);
|
|
265
|
+
if (row.state !== "awaiting_user") {
|
|
266
|
+
return c.json({ error: "not_awaiting_user", currentState: row.state }, 409);
|
|
267
|
+
}
|
|
268
|
+
// Resolve the explicit clarificationId, or the single open one.
|
|
269
|
+
const clarificationId = parsed.data.clarificationId
|
|
270
|
+
?? getOpenClarificationForTask(deps.db, id)?.id
|
|
271
|
+
?? null;
|
|
272
|
+
if (!clarificationId) {
|
|
273
|
+
return c.json({ error: "no_open_clarification" }, 409);
|
|
274
|
+
}
|
|
275
|
+
const resolved = resolveClarification(deps.db, {
|
|
276
|
+
id: clarificationId,
|
|
277
|
+
answer: parsed.data.answer,
|
|
278
|
+
answeredAt: Date.now(),
|
|
279
|
+
});
|
|
280
|
+
if (!resolved.ok) {
|
|
281
|
+
const status = resolved.reason === "not_found"
|
|
282
|
+
? 404
|
|
283
|
+
: resolved.reason === "expired"
|
|
284
|
+
? 410
|
|
285
|
+
: 409;
|
|
286
|
+
return c.json({ error: resolved.reason ?? "clarify_failed" }, status);
|
|
287
|
+
}
|
|
288
|
+
if (deps.backgroundTaskRunner) {
|
|
289
|
+
void deps.backgroundTaskRunner
|
|
290
|
+
.resumeAfterClarification({
|
|
291
|
+
taskId: id,
|
|
292
|
+
clarificationId,
|
|
293
|
+
answer: parsed.data.answer,
|
|
294
|
+
})
|
|
295
|
+
.catch((err) => {
|
|
296
|
+
logger.error({ err, taskId: id, clarificationId }, "background-task resumeAfterClarification threw — task left parked");
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
logger.warn({ taskId: id, clarificationId }, "background-task clarify recorded but no runner wired — task cannot resume.");
|
|
301
|
+
}
|
|
302
|
+
return c.json({ ok: true, clarification: resolved.row });
|
|
303
|
+
});
|
|
304
|
+
// ── POST /api/background-task/:id/cancel ──────────────────────────────
|
|
305
|
+
app.post("/background-task/:id/cancel", async (c) => {
|
|
306
|
+
const id = c.req.param("id");
|
|
307
|
+
const body = await readJsonBody(c);
|
|
308
|
+
const rawBody = body.ok ? body.body : undefined;
|
|
309
|
+
const parsed = cancelBodySchema.safeParse(rawBody);
|
|
310
|
+
const reason = parsed.success ? parsed.data?.reason ?? "user_cancel" : "user_cancel";
|
|
311
|
+
const row = getBackgroundTask(deps.db, id);
|
|
312
|
+
if (!row)
|
|
313
|
+
return c.json({ error: "not_found" }, 404);
|
|
314
|
+
if (row.state === "completed"
|
|
315
|
+
|| row.state === "failed"
|
|
316
|
+
|| row.state === "timeout"
|
|
317
|
+
|| row.state === "cancelled") {
|
|
318
|
+
return c.json({ error: "already_terminal", currentState: row.state }, 409);
|
|
319
|
+
}
|
|
320
|
+
if (deps.backgroundTaskRunner) {
|
|
321
|
+
await deps.backgroundTaskRunner.cancel(id, reason);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
const finishedAt = Date.now();
|
|
325
|
+
const updated = markTerminal(deps.db, {
|
|
326
|
+
id,
|
|
327
|
+
state: "cancelled",
|
|
328
|
+
outcomeDetail: reason,
|
|
329
|
+
finishedAt,
|
|
330
|
+
});
|
|
331
|
+
transitionEmitter.emitFromRow(updated, finishedAt);
|
|
332
|
+
return c.json({ ok: true, row: updated ? toWire(updated) : null });
|
|
333
|
+
}
|
|
334
|
+
const after = getBackgroundTask(deps.db, id);
|
|
335
|
+
return c.json({ ok: true, row: after ? toWire(after) : null });
|
|
336
|
+
});
|
|
337
|
+
return app;
|
|
338
|
+
}
|
|
@@ -63,10 +63,18 @@ export function createBrowserHistoryRoutes(deps) {
|
|
|
63
63
|
return c.json({ error: "not_found" }, 404);
|
|
64
64
|
}
|
|
65
65
|
const days = listClusterDailyDeltas(deps.db, detail.rootTaskId, agentDayBoundary(deps), { dayLimit: 31 });
|
|
66
|
+
// RESEARCH_CLUSTER_COST_FIX_PLAN rev5 — stamp each bucket with
|
|
67
|
+
// whether its agent day has ended. The current agent day's bucket is
|
|
68
|
+
// still accumulating (a day-boundary or wake-catch-up run fires
|
|
69
|
+
// mid-day, so the bucket only covers visits up to "now"); consumers
|
|
70
|
+
// that persist day entries (the append-only research journal) must
|
|
71
|
+
// skip incomplete buckets or they freeze an undercounted day forever.
|
|
72
|
+
// ISO agent-day strings compare lexically = chronologically.
|
|
73
|
+
const today = todayKey(deps);
|
|
66
74
|
return c.json(browserHistoryClusterDeltaResponseSchema.parse({
|
|
67
75
|
slug,
|
|
68
76
|
generatedAt: new Date().toISOString(),
|
|
69
|
-
days,
|
|
77
|
+
days: days.map((day) => ({ ...day, complete: day.date < today })),
|
|
70
78
|
}));
|
|
71
79
|
});
|
|
72
80
|
app.get("/browser-history/research-clusters/:slug", (c) => {
|
|
@@ -52,8 +52,9 @@ export const CONTEXT_WRITE_PERMISSIONS = {
|
|
|
52
52
|
"policies/management-captures/*": ["PUT", "PATCH"],
|
|
53
53
|
"policies/routines/_index": ["PUT", "PATCH"],
|
|
54
54
|
"policies/routines/*": ["PUT", "PATCH"],
|
|
55
|
-
//
|
|
56
|
-
//
|
|
55
|
+
// Legacy custom-routine files (inert since the Agents-hub redesign —
|
|
56
|
+
// recurring work is an Agent now). Writes stay validated and DELETE
|
|
57
|
+
// remains so the agent can clean a leftover file up when the user asks.
|
|
57
58
|
"policies/routines/custom/*": ["PUT", "PATCH", "DELETE"],
|
|
58
59
|
// User Agent definitions (AGENT_DEFINITIONS_DESIGN.md §9.5 / §3.3). The
|
|
59
60
|
// dashboard's "+ New Agent" scaffold and the YAML editor write user Agents
|
|
@@ -186,9 +186,6 @@ export function registerSnapshotsRoutes(app, ctx) {
|
|
|
186
186
|
if (shouldRefreshPromptContext(path, "PUT")) {
|
|
187
187
|
notifyPromptContextChanged(deps, path, `context_restore_snapshot:${path}`, { path, method: "RESTORE" });
|
|
188
188
|
}
|
|
189
|
-
if (path.startsWith("policies/routines/custom/")) {
|
|
190
|
-
deps.onCustomRoutinesChanged?.();
|
|
191
|
-
}
|
|
192
189
|
const writtenStat = statSync(fullPath);
|
|
193
190
|
logger.info({
|
|
194
191
|
path,
|
|
@@ -320,9 +320,6 @@ export function registerWriteRoutes(app, ctx) {
|
|
|
320
320
|
if (shouldRefreshPromptContext(path, "PUT")) {
|
|
321
321
|
notifyPromptContextChanged(deps, path, `context_put:${path}`, { path, method: "PUT" });
|
|
322
322
|
}
|
|
323
|
-
if (path.startsWith("policies/routines/custom/")) {
|
|
324
|
-
deps.onCustomRoutinesChanged?.();
|
|
325
|
-
}
|
|
326
323
|
const writtenStat = statSync(fullPath);
|
|
327
324
|
logger.info({
|
|
328
325
|
path,
|
|
@@ -530,9 +527,6 @@ export function registerWriteRoutes(app, ctx) {
|
|
|
530
527
|
previousContent: fileContent,
|
|
531
528
|
});
|
|
532
529
|
}
|
|
533
|
-
if (path.startsWith("policies/routines/custom/")) {
|
|
534
|
-
deps.onCustomRoutinesChanged?.();
|
|
535
|
-
}
|
|
536
530
|
logger.info({ path, method: "PATCH", mode }, "Content appended to file");
|
|
537
531
|
return c.json({ status: "appended" });
|
|
538
532
|
}
|
|
@@ -608,9 +602,6 @@ export function registerWriteRoutes(app, ctx) {
|
|
|
608
602
|
previousContent: fileContent,
|
|
609
603
|
});
|
|
610
604
|
}
|
|
611
|
-
if (path.startsWith("policies/routines/custom/")) {
|
|
612
|
-
deps.onCustomRoutinesChanged?.();
|
|
613
|
-
}
|
|
614
605
|
logger.info({ path, method: "PATCH", mode }, "Frontmatter merged");
|
|
615
606
|
return c.json({ status: "merged" });
|
|
616
607
|
}
|
|
@@ -724,9 +715,6 @@ export function registerWriteRoutes(app, ctx) {
|
|
|
724
715
|
previousContent: fileContent,
|
|
725
716
|
});
|
|
726
717
|
}
|
|
727
|
-
if (path.startsWith("policies/routines/custom/")) {
|
|
728
|
-
deps.onCustomRoutinesChanged?.();
|
|
729
|
-
}
|
|
730
718
|
deps.onIndexableContextChange?.(`${path}${target.ext}`);
|
|
731
719
|
const resultStatus = mode === "append" ? "appended" : mode === "replace" ? "replaced" : "cleared";
|
|
732
720
|
logger.info({ path, method: "PATCH", section, mode, removedCount, trimmedCount }, "Context section " + resultStatus);
|
|
@@ -735,8 +723,9 @@ export function registerWriteRoutes(app, ctx) {
|
|
|
735
723
|
});
|
|
736
724
|
// DELETE /context/* — File delete (currently limited to `routines/custom/*`
|
|
737
725
|
// via the write-permission whitelist). B-007 §5.8 Q3: the agent retires a
|
|
738
|
-
// custom routine after the user confirms.
|
|
739
|
-
//
|
|
726
|
+
// custom routine after the user confirms. (Legacy path — custom routines
|
|
727
|
+
// no longer fire; they were converted to user Agents at the Agents-hub
|
|
728
|
+
// redesign. The delete surface stays so leftover files can be removed.)
|
|
740
729
|
app.delete("/context/*", (c) => {
|
|
741
730
|
const path = normalizeContextPath(c.req.path.replace("/api/context/", ""));
|
|
742
731
|
const contextDir = getCurrentContextDir();
|
|
@@ -770,9 +759,6 @@ export function registerWriteRoutes(app, ctx) {
|
|
|
770
759
|
const existing = readFileSync(fullPath, "utf-8");
|
|
771
760
|
const snapshotId = saveSnapshot(path, existing, "api_delete", true);
|
|
772
761
|
unlinkSync(fullPath);
|
|
773
|
-
if (path.startsWith("policies/routines/custom/")) {
|
|
774
|
-
deps.onCustomRoutinesChanged?.();
|
|
775
|
-
}
|
|
776
762
|
deps.onIndexableContextChange?.(path);
|
|
777
763
|
logger.info({ path, method: "DELETE", snapshotId: snapshotId ?? undefined }, "Context file deleted");
|
|
778
764
|
return c.json({ status: "deleted", snapshotId: snapshotId ?? 0 });
|
|
@@ -4,6 +4,7 @@ import { EDITABLE_RUNTIME_KEY_TUPLE, getBackendIds, normalizeAgentDisplayName, }
|
|
|
4
4
|
import { applyConfigUpdates } from "../../env-writer.js";
|
|
5
5
|
import { runDefaultSchedulesReconciler } from "../../../core/context/default-schedules-runner.js";
|
|
6
6
|
import { syncDmSessionTimesToQuietHours } from "../../../core/quiet-hours-sync.js";
|
|
7
|
+
import { retimeDeferredDmRows, retimeDeferredRunRows, } from "../../../db/deferred-dm.js";
|
|
7
8
|
import { getContextDir } from "../../../config.js";
|
|
8
9
|
import { CONTEXT_RELATIVE_PATHS } from "../../../core/context-paths.js";
|
|
9
10
|
import { createLogger, toSafeErrorMessage } from "../../../logging.js";
|
|
@@ -31,13 +32,13 @@ const PUBLIC_CONFIG_RUNTIME_KEYS = [
|
|
|
31
32
|
"sessionTimeoutDashboardMinutes",
|
|
32
33
|
"timezone",
|
|
33
34
|
"dayBoundaryHour",
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
// The legacy activity-scan / monthly-review gate + cadence keys
|
|
36
|
+
// (activityScanEnabled, activityScanIntervalMinutes, activityScanActive*,
|
|
37
|
+
// activityScanMinObservations, monthlyReviewEnabled) left this surface at
|
|
38
|
+
// the Agents-hub redesign: `agents.enabled` + the activity-scan row's
|
|
39
|
+
// runtime_window own them now (PATCH /api/agents/activity-scan). The keys
|
|
40
|
+
// remain valid in runtimeSettingsSchema as resolver fallbacks
|
|
41
|
+
// (AGENTS_HUB_REDESIGN_PLAN.md §2).
|
|
41
42
|
"authProbeDisabled",
|
|
42
43
|
"authPreflightFreshnessMs",
|
|
43
44
|
"maxNotificationsPerHour",
|
|
@@ -303,14 +304,18 @@ export function registerConfigRoutes(app, deps) {
|
|
|
303
304
|
if (Object.keys(result.errors).length > 0 && result.updated.length === 0) {
|
|
304
305
|
return c.json({ error: "validation_failed", details: result.errors }, 400);
|
|
305
306
|
}
|
|
306
|
-
// Hot-reload cron schedules when schedule-related config changes
|
|
307
|
+
// Hot-reload cron schedules when schedule-related config changes.
|
|
308
|
+
// The activityScan* entries are legacy-API back-compat only: the dashboard
|
|
309
|
+
// no longer surfaces them (agent-row runtime_window owns the cadence), but
|
|
310
|
+
// a direct PATCH of the deprecated keys must still rebuild the cron so the
|
|
311
|
+
// resolver fallback change takes effect.
|
|
307
312
|
const SCHEDULE_KEYS = [
|
|
308
313
|
"dayBoundaryHour",
|
|
309
314
|
"timezone",
|
|
310
|
-
"
|
|
311
|
-
"
|
|
312
|
-
"
|
|
313
|
-
"
|
|
315
|
+
"activityScanEnabled",
|
|
316
|
+
"activityScanIntervalMinutes",
|
|
317
|
+
"activityScanActiveStartHour",
|
|
318
|
+
"activityScanActiveEndHour",
|
|
314
319
|
];
|
|
315
320
|
if (result.updated.some((k) => SCHEDULE_KEYS.includes(k))) {
|
|
316
321
|
deps.onScheduleConfigChanged?.();
|
|
@@ -342,6 +347,37 @@ export function registerConfigRoutes(app, deps) {
|
|
|
342
347
|
logger.warn({ err }, "syncDmSessionTimesToQuietHours threw after dashboard config PATCH");
|
|
343
348
|
}
|
|
344
349
|
}
|
|
350
|
+
// QUIET_HOURS_HARDENING_PLAN.md Phase 1 follow-up — pending
|
|
351
|
+
// quiet-hours-deferred DM rows (`task_context.deferred_from`) were
|
|
352
|
+
// stamped with the *old* window's end; retime them so a widened
|
|
353
|
+
// window doesn't fire them inside the new quiet hours and a
|
|
354
|
+
// narrowed one doesn't hold them past the new edge. Phase 2's
|
|
355
|
+
// deferred RUN rows (`task_context.quiet_hours_deferred` — agent.task
|
|
356
|
+
// opt-in + browser_task) get the same treatment; for them only the
|
|
357
|
+
// narrowed/disabled direction matters (a widened window re-defers at
|
|
358
|
+
// claim time anyway). `timezone` is in the trigger set because the
|
|
359
|
+
// window's *absolute* position is tz-relative — an unchanged
|
|
360
|
+
// "22:00→08:00" still moves on the UTC axis when the tz changes, and
|
|
361
|
+
// deferred DM rows are delivered by `handleDirectDm`, which skips the
|
|
362
|
+
// quiet-hours check by design.
|
|
363
|
+
if ((result.updated.includes("quietHoursEnd")
|
|
364
|
+
|| result.updated.includes("quietHoursStart")
|
|
365
|
+
|| result.updated.includes("timezone"))
|
|
366
|
+
&& /^\d{2}:\d{2}$/.test(config.quietHoursStart)
|
|
367
|
+
&& /^\d{2}:\d{2}$/.test(config.quietHoursEnd)) {
|
|
368
|
+
const window = {
|
|
369
|
+
start: config.quietHoursStart,
|
|
370
|
+
end: config.quietHoursEnd,
|
|
371
|
+
timezone: config.timezone || undefined,
|
|
372
|
+
};
|
|
373
|
+
try {
|
|
374
|
+
retimeDeferredDmRows(db, window);
|
|
375
|
+
retimeDeferredRunRows(db, window);
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
logger.warn({ err }, "Retiming quiet-hours-deferred rows threw after dashboard config PATCH");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
345
381
|
// Hot-refresh DM session skill dirs when enabledMailProviders changes
|
|
346
382
|
// through the generic config PATCH. The per-endpoint `PATCH /mail/providers`
|
|
347
383
|
// handler already drives this through MailAccountRegistry.onScopeChanged,
|
|
@@ -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 ──
|