@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
|
@@ -7,6 +7,7 @@ import { OBSERVATIONS_MCP_SERVER_NAME, createObservationsMcpServer, } from "../.
|
|
|
7
7
|
import { parseMcpToolName } from "../../services/mcp/risk.js";
|
|
8
8
|
import { logMcpToolCall, updateMcpToolCallResult } from "../../services/mcp/tool-audit.js";
|
|
9
9
|
import { BackendQuotaError, BackendDecisiveFailure, } from "../agent-core.js";
|
|
10
|
+
import { PriceFetcher } from "./price-fetcher.js";
|
|
10
11
|
import { flattenToolResultContent } from "../../services/delegated-tool-runtime.js";
|
|
11
12
|
import { runDelegatedTool as runDelegatedToolFn, runDelegatedTask as runDelegatedTaskFn, } from "./claude-delegated.js";
|
|
12
13
|
import { createSessionWorkdir, cleanupSessionWorkdir } from "../workdir.js";
|
|
@@ -15,7 +16,7 @@ import { buildDaemonApiCliEnv } from "../daemon-api-cli.js";
|
|
|
15
16
|
import { createLogger } from "../../logging.js";
|
|
16
17
|
import { DEFAULT_CLAUDE_HIGH_MODEL, DEFAULT_CLAUDE_MEDIUM_MODEL, findRegisteredModel, getModelsForBackend, } from "./model-registry.js";
|
|
17
18
|
import { ALWAYS_DISALLOWED_TOOLS } from "../../safety/always-disallowed.js";
|
|
18
|
-
import { loadFetchWindowSystemPrompt, resetFetchWindowSystemPromptForTest, } from "../
|
|
19
|
+
import { loadFetchWindowSystemPrompt, loadSlimSystemPrompt, resetFetchWindowSystemPromptForTest, } from "../slim-system-prompt-loader.js";
|
|
19
20
|
import { CliPathCache } from "./cli-utils.js";
|
|
20
21
|
import { extractSilentApiErrors, logSilentApiErrors, } from "./silent-api-error-detector.js";
|
|
21
22
|
import { CLAUDE_PROBE_TOOLS_PROMPT, computeDelegatedClaudeTools, computeNativeClaudeTools, describeClaudeProbeResultError, extractClaudeProbeTools, } from "./claude-probe.js";
|
|
@@ -74,35 +75,56 @@ const logger = createLogger("claude-code-core");
|
|
|
74
75
|
*/
|
|
75
76
|
const CLAUDE_SDK_SETTING_SOURCES = ["user", "project"];
|
|
76
77
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
78
|
+
* Slim, lite-tier process keys swap the verbose `preset: "claude_code"`
|
|
79
|
+
* system prompt (~30 K tokens of built-in tool descriptions, the skills
|
|
80
|
+
* index, the memory-system docs, and tone/style guidance the key never
|
|
81
|
+
* uses) for a tight custom systemPrompt string. SDK 0.2.98 has no
|
|
82
|
+
* `presetOptions` granularity to drop sub-sections of the preset, so a
|
|
83
|
+
* string prompt is the only lever. `buildSystemPrompt` resolves membership
|
|
84
|
+
* through the shared registry in `core/slim-system-prompt-loader.ts`
|
|
85
|
+
* (`loadSlimSystemPrompt`) — the SAME loader the `SkillsCompiler` uses to
|
|
86
|
+
* write the byte-identical body into Codex / Gemini AGENTS.md / GEMINI.md,
|
|
87
|
+
* so adding a slim key is a one-line registry edit that wires both backends.
|
|
88
|
+
* Per-key agent profiles + task-flow bodies still ship the operational
|
|
89
|
+
* rules; the slim system prompt only sets the broad stance, and the SDK
|
|
90
|
+
* still loads the per-cwd CLAUDE.md the SkillsCompiler materializes.
|
|
91
|
+
* - `routine.fetch_window` — docs/design/appendices/fetch-window-cost-reduction.md
|
|
92
|
+
* Phase 1 / 1.5.
|
|
93
|
+
* - `routine.research_cluster_update` — RESEARCH_CLUSTER_COST_FIX_PLAN.md F4.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Slim keys whose Claude SDK session ALSO sheds the daemon user's `~/.claude`
|
|
97
|
+
* scope: `settingSources` drops to `["project"]` and `strictMcpConfig` is
|
|
98
|
+
* forced on. On a dev machine the `"user"` source pulls in the user's plugin
|
|
99
|
+
* SKILL.md tree (~178 files) + the ~25 K-token user-scope claude.ai MCP
|
|
100
|
+
* connector schemas (`mcp__claude_ai_*`) into EVERY session's prompt-cache
|
|
101
|
+
* prefix (RESEARCH_CLUSTER_COST_FIX_PLAN.md RC4). Dropping it is pure win
|
|
102
|
+
* for a key that reaches no integration through those connectors.
|
|
103
|
+
*
|
|
104
|
+
* This is a STRICT SUBSET of the slim-system-prompt keys, NOT the same set:
|
|
105
|
+
* a key only qualifies when it serves NO native-mode integration. In native
|
|
106
|
+
* integration mode the fetcher reaches Gmail / Calendar / Notion precisely
|
|
107
|
+
* through the user-scope claude.ai connectors, so `routine.fetch_window` keeps
|
|
108
|
+
* `["user", "project"]` and is deliberately ABSENT here even though it has a
|
|
109
|
+
* slim system prompt. `routine.research_cluster_update` only ever curls the
|
|
110
|
+
* daemon's own browser-history + context REST API (no claude.ai connector),
|
|
111
|
+
* so shedding the user scope cannot starve it.
|
|
85
112
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* prompt only sets the broad stance, and the SDK still loads the per-cwd
|
|
93
|
-
* CLAUDE.md the SkillsCompiler materializes per session.
|
|
113
|
+
* `strictMcpConfig` is defense-in-depth on top of the `settingSources` drop:
|
|
114
|
+
* it shuts out any settings-file-sourced MCP server, while the daemon's own
|
|
115
|
+
* servers (including the in-process `aitne-observations` server) are passed
|
|
116
|
+
* programmatically via `options.mcpServers` (`composeMcpServers`) which
|
|
117
|
+
* `strictMcpConfig` does not touch. Typed `ReadonlySet<ProcessKey>` so a
|
|
118
|
+
* key rename in @aitne/shared lights up at the literal below.
|
|
94
119
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// silently-dead branch in `buildSystemPrompt`.
|
|
99
|
-
const FETCH_WINDOW_PROCESS_KEY = "routine.fetch_window";
|
|
120
|
+
const USER_SCOPE_SHED_PROCESS_KEYS = new Set([
|
|
121
|
+
"routine.research_cluster_update",
|
|
122
|
+
]);
|
|
100
123
|
/**
|
|
101
|
-
* Test-only surface: lets `claude-code-core.test.ts` exercise the
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* keeps working after Phase 1.5's hoist.
|
|
124
|
+
* Test-only surface: lets `claude-code-core.test.ts` exercise the slim
|
|
125
|
+
* prompt loader without reaching into module internals via `as any` casts.
|
|
126
|
+
* Re-exports the shared loaders (hoisted to `core/slim-system-prompt-loader.ts`)
|
|
127
|
+
* so the existing fetch_window test import path keeps working.
|
|
106
128
|
*/
|
|
107
129
|
export const _testInternals = {
|
|
108
130
|
loadFetchWindowSystemPrompt,
|
|
@@ -129,9 +151,81 @@ export const _testInternals = {
|
|
|
129
151
|
* question based on the CLI / SDK's own startup cost, not on
|
|
130
152
|
* pattern-matching against one of the existing three.
|
|
131
153
|
*/
|
|
154
|
+
// ── Partial-spend recovery (PREPASS_COST_REDUCTION_PLAN.md N1) ────────────
|
|
155
|
+
//
|
|
156
|
+
// The SDK populates authoritative usage/cost only on the terminal `result`
|
|
157
|
+
// stream message. When the stream aborts before that message arrives —
|
|
158
|
+
// the SDK's `max_budget_usd` kill, a wall-clock timeout, a transport
|
|
159
|
+
// failure — the run's spend would otherwise be unrecoverable: the thrown
|
|
160
|
+
// error carries no usage, and the dispatcher's post-hoc audit writer
|
|
161
|
+
// (`recordPostHocBudgetSpend`) drops payload-less errors. The accumulator
|
|
162
|
+
// below sums per-assistant-message usage during `consumeStream` so a
|
|
163
|
+
// partial figure exists at throw time; `executeOnce` / `executeResumeOnce`
|
|
164
|
+
// stamp the snapshot onto the propagating error via a symbol property,
|
|
165
|
+
// and `classifyExecutionError` / `toBackendQuotaError` lift it onto the
|
|
166
|
+
// classified `BackendQuotaError` / `BackendDecisiveFailure`.
|
|
167
|
+
/** Carrier property for the partial-spend snapshot on a propagating error. */
|
|
168
|
+
const PARTIAL_SPEND_PROP = Symbol("aitne.claudePartialSpend");
|
|
169
|
+
function createPartialUsageAccumulator() {
|
|
170
|
+
return {
|
|
171
|
+
usage: {
|
|
172
|
+
inputTokens: 0,
|
|
173
|
+
outputTokens: 0,
|
|
174
|
+
cacheCreationInputTokens: 0,
|
|
175
|
+
cacheReadInputTokens: 0,
|
|
176
|
+
},
|
|
177
|
+
numTurns: 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Fold one SDK assistant message's API-call usage into the accumulator.
|
|
182
|
+
* The SDK reports usage per API call on each assistant message; summing
|
|
183
|
+
* them approximates the run's total the same way the terminal result
|
|
184
|
+
* message would have.
|
|
185
|
+
*/
|
|
186
|
+
function recordAssistantUsage(acc, rawUsage) {
|
|
187
|
+
acc.numTurns += 1;
|
|
188
|
+
if (typeof rawUsage !== "object" || rawUsage === null)
|
|
189
|
+
return;
|
|
190
|
+
const u = rawUsage;
|
|
191
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
192
|
+
acc.usage.inputTokens += num(u.input_tokens);
|
|
193
|
+
acc.usage.outputTokens += num(u.output_tokens);
|
|
194
|
+
acc.usage.cacheCreationInputTokens += num(u.cache_creation_input_tokens);
|
|
195
|
+
acc.usage.cacheReadInputTokens += num(u.cache_read_input_tokens);
|
|
196
|
+
}
|
|
197
|
+
function accumulatorSawUsage(acc) {
|
|
198
|
+
return (acc.usage.inputTokens > 0
|
|
199
|
+
|| acc.usage.outputTokens > 0
|
|
200
|
+
|| acc.usage.cacheCreationInputTokens > 0
|
|
201
|
+
|| acc.usage.cacheReadInputTokens > 0);
|
|
202
|
+
}
|
|
203
|
+
function attachPartialSpend(error, spend) {
|
|
204
|
+
if (typeof error !== "object" || error === null)
|
|
205
|
+
return;
|
|
206
|
+
try {
|
|
207
|
+
Object.defineProperty(error, PARTIAL_SPEND_PROP, {
|
|
208
|
+
value: spend,
|
|
209
|
+
enumerable: false,
|
|
210
|
+
configurable: true,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Frozen/sealed error object — losing the snapshot is acceptable;
|
|
215
|
+
// the row simply stays payload-less like before N1.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Visible for testing. */
|
|
219
|
+
export function getAttachedPartialSpend(error) {
|
|
220
|
+
if (typeof error !== "object" || error === null)
|
|
221
|
+
return null;
|
|
222
|
+
const value = error[PARTIAL_SPEND_PROP];
|
|
223
|
+
return value ? value : null;
|
|
224
|
+
}
|
|
132
225
|
export class ClaudeCodeCore {
|
|
133
226
|
config;
|
|
134
227
|
writeTracker;
|
|
228
|
+
priceFetcher;
|
|
135
229
|
backendId = "claude";
|
|
136
230
|
static RETRY_DELAY_MS = 5 * 60 * 1000;
|
|
137
231
|
static MAX_RETRIES = 1;
|
|
@@ -176,12 +270,26 @@ export class ClaudeCodeCore {
|
|
|
176
270
|
* Shared AgentWriteTracker. When present, the Write/Edit PreToolUse hook
|
|
177
271
|
* pre-marks vault-scoped writes so the ObsidianWatcher attributes the
|
|
178
272
|
* resulting chokidar event to `actor='agent'` instead of `'user'`. Without
|
|
179
|
-
* this wiring, the
|
|
273
|
+
* this wiring, the activity_scan dispatcher would re-discover the agent's
|
|
180
274
|
* own vault writes every cycle and loop.
|
|
181
275
|
*/
|
|
182
|
-
writeTracker
|
|
276
|
+
writeTracker,
|
|
277
|
+
/**
|
|
278
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — used only to estimate the
|
|
279
|
+
* dollar figure of a partial spend snapshot when the SDK stream
|
|
280
|
+
* terminates abnormally (budget abort, timeout, transport failure).
|
|
281
|
+
* Success-path cost still comes from the SDK's own metering.
|
|
282
|
+
* Defaulted like the CLI cores' fetchers so bootstrap stays
|
|
283
|
+
* unchanged; guarded on `dataDir` because several unit tests
|
|
284
|
+
* construct the core with a partial config — those fall back to
|
|
285
|
+
* cap-floor-only estimation in `stampPartialSpend`.
|
|
286
|
+
*/
|
|
287
|
+
priceFetcher = config.dataDir
|
|
288
|
+
? new PriceFetcher(config.dataDir)
|
|
289
|
+
: undefined) {
|
|
183
290
|
this.config = config;
|
|
184
291
|
this.writeTracker = writeTracker;
|
|
292
|
+
this.priceFetcher = priceFetcher;
|
|
185
293
|
this.warnOnMissingCriticalTools();
|
|
186
294
|
this.cliPathCache = new CliPathCache("claude");
|
|
187
295
|
}
|
|
@@ -332,26 +440,27 @@ export class ClaudeCodeCore {
|
|
|
332
440
|
};
|
|
333
441
|
}
|
|
334
442
|
buildSystemPrompt(processKey) {
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
// /
|
|
340
|
-
// the skills index — every byte of
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
// (
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
443
|
+
// Slim process keys (RESEARCH_CLUSTER_COST_FIX_PLAN.md F4 generalizes the
|
|
444
|
+
// fetch-window-cost-reduction.md Phase 1 precedent) pay the full preset
|
|
445
|
+
// prompt cost on every dispatch (~30 K cache_create tokens per session).
|
|
446
|
+
// These keys never use Skill / Read / Write / Edit / Glob / Grep / Task /
|
|
447
|
+
// WebFetch / WebSearch / NotebookEdit / EnterPlanMode / ScheduleWakeup,
|
|
448
|
+
// the memory-system documentation, or the skills index — every byte of
|
|
449
|
+
// preset for those is wasted cache creation amortized over only a few
|
|
450
|
+
// turns. Replace the preset with a small custom string sourced from the
|
|
451
|
+
// shared registry (`core/slim-system-prompt-loader.ts`); the SkillsCompiler
|
|
452
|
+
// materializes the byte-identical body into AGENTS.md / GEMINI.md for CLI
|
|
453
|
+
// parity. Operational rules still ship via the per-cwd CLAUDE.md profile +
|
|
454
|
+
// task-flow body the SkillsCompiler materializes per session.
|
|
347
455
|
//
|
|
348
456
|
// Trade-off — `excludeDynamicSections` is a no-op when systemPrompt is
|
|
349
457
|
// a string (per SDK 0.2.98 docs: "Has no effect when systemPrompt is a
|
|
350
458
|
// string (custom prompt)"), but the entire string IS byte-stable
|
|
351
459
|
// across sessions, so the prompt-cache prefix is naturally cacheable
|
|
352
460
|
// on the same axis without needing the flag.
|
|
353
|
-
|
|
354
|
-
|
|
461
|
+
const slimSystemPrompt = loadSlimSystemPrompt(processKey);
|
|
462
|
+
if (slimSystemPrompt !== null) {
|
|
463
|
+
return slimSystemPrompt;
|
|
355
464
|
}
|
|
356
465
|
// Character is NOT appended here — Phase 2 of the Character feature
|
|
357
466
|
// (see docs/design/15-character.md §15.4.3) moved the injection into
|
|
@@ -383,6 +492,33 @@ export class ClaudeCodeCore {
|
|
|
383
492
|
excludeDynamicSections: true,
|
|
384
493
|
};
|
|
385
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Resolve the SDK `settingSources` for a session. Returns `["project"]`
|
|
497
|
+
* for `USER_SCOPE_SHED_PROCESS_KEYS` (dropping the daemon user's `~/.claude`
|
|
498
|
+
* scope — plugin SKILL.md tree + claude.ai connector schemas — from the
|
|
499
|
+
* prompt-cache prefix) and the default `["user", "project"]` otherwise. A
|
|
500
|
+
* fresh array per call: the SDK option type is mutable `SettingSource[]`.
|
|
501
|
+
*
|
|
502
|
+
* Applied at both `query()` sites (`executeOnce`, `executeResumeOnce`).
|
|
503
|
+
* Resume carries no `processKey` (it is always a reactive DM continuation,
|
|
504
|
+
* never a slim routine), so it always resolves to the full default.
|
|
505
|
+
*/
|
|
506
|
+
resolveSettingSources(processKey) {
|
|
507
|
+
if (processKey !== undefined && USER_SCOPE_SHED_PROCESS_KEYS.has(processKey)) {
|
|
508
|
+
return ["project"];
|
|
509
|
+
}
|
|
510
|
+
return [...CLAUDE_SDK_SETTING_SOURCES];
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Whether to force `strictMcpConfig` for a session — true exactly for the
|
|
514
|
+
* `USER_SCOPE_SHED_PROCESS_KEYS`, as defense-in-depth on top of the
|
|
515
|
+
* `settingSources` drop (shuts out settings-file-sourced MCP servers; the
|
|
516
|
+
* daemon's own servers are passed programmatically and unaffected). Resume
|
|
517
|
+
* carries no `processKey`, so it never qualifies.
|
|
518
|
+
*/
|
|
519
|
+
resolveStrictMcpConfig(processKey) {
|
|
520
|
+
return processKey !== undefined && USER_SCOPE_SHED_PROCESS_KEYS.has(processKey);
|
|
521
|
+
}
|
|
386
522
|
/**
|
|
387
523
|
* Expand CLI-style aliases ("opus", "sonnet") to their current canonical
|
|
388
524
|
* API IDs. Unrecognised strings pass through unchanged so custom or
|
|
@@ -452,6 +588,9 @@ export class ClaudeCodeCore {
|
|
|
452
588
|
nativeToolCount: nativeTools.length,
|
|
453
589
|
sessionDeniedToolCount: sessionDeniedTools.length,
|
|
454
590
|
}, "Agent execute started");
|
|
591
|
+
// Declared outside the try so the catch can stamp a partial-spend
|
|
592
|
+
// snapshot onto the propagating error (PREPASS_COST_REDUCTION_PLAN.md N1).
|
|
593
|
+
const partialUsage = createPartialUsageAccumulator();
|
|
455
594
|
try {
|
|
456
595
|
const allowMode = this.config.claudeExecutionPermissionMode === "allow";
|
|
457
596
|
// P22 §3.4 step 4 — when the dispatcher pins a per-execute
|
|
@@ -462,7 +601,7 @@ export class ClaudeCodeCore {
|
|
|
462
601
|
// ALWAYS_DISALLOWED_TOOLS layer still applies.
|
|
463
602
|
//
|
|
464
603
|
// An EMPTY array (`[]`) is a deliberate "no tools" clamp — used by
|
|
465
|
-
// `routine.
|
|
604
|
+
// `routine.activity_scan.triage` (JSON-only triage spawn) and Stage B
|
|
466
605
|
// of the morning-routine pipeline (daily-journal-daemon-write.md §3
|
|
467
606
|
// corollary). A pre-2026-05-24 version of this gate required
|
|
468
607
|
// `length > 0`, which silently fell through to the default `dontAsk`
|
|
@@ -540,7 +679,14 @@ export class ClaudeCodeCore {
|
|
|
540
679
|
const mcpServers = this.composeMcpServers(mcp.claudeMcpServers);
|
|
541
680
|
return mcpServers ? { mcpServers } : {};
|
|
542
681
|
})(),
|
|
543
|
-
|
|
682
|
+
// RESEARCH_CLUSTER_COST_FIX_PLAN.md F4 — `USER_SCOPE_SHED_PROCESS_KEYS`
|
|
683
|
+
// drop to `["project"]` (+ `strictMcpConfig`) to shed the daemon
|
|
684
|
+
// user's `~/.claude` scope from the prompt-cache prefix; all other
|
|
685
|
+
// keys keep `["user", "project"]`.
|
|
686
|
+
settingSources: this.resolveSettingSources(params.processKey),
|
|
687
|
+
...(this.resolveStrictMcpConfig(params.processKey)
|
|
688
|
+
? { strictMcpConfig: true }
|
|
689
|
+
: {}),
|
|
544
690
|
// When the per-execute clamp is active we already swapped Allow
|
|
545
691
|
// mode back to strict `dontAsk` + an explicit allowedTools list.
|
|
546
692
|
// The PreToolUse hooks must follow the same posture: keeping
|
|
@@ -553,11 +699,12 @@ export class ClaudeCodeCore {
|
|
|
553
699
|
...this.buildAdvisorSettings(),
|
|
554
700
|
},
|
|
555
701
|
});
|
|
556
|
-
const result = await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, event.type), this.config.executeTimeoutMinutes);
|
|
702
|
+
const result = await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, event.type, partialUsage), this.config.executeTimeoutMinutes);
|
|
557
703
|
logger.info({ eventType: event.type, model: actualModelId, durationMs: result.durationMs, costUsd: result.costUsd, numTurns: result.numTurns, isError: result.isError }, "Agent execute completed");
|
|
558
704
|
return result;
|
|
559
705
|
}
|
|
560
706
|
catch (err) {
|
|
707
|
+
this.stampPartialSpend(err, partialUsage, actualModelId, startMs, maxBudgetUsd);
|
|
561
708
|
logger.error({ err, eventType: event.type, model: actualModelId, durationMs: Date.now() - startMs }, "Agent execute failed");
|
|
562
709
|
throw err;
|
|
563
710
|
}
|
|
@@ -646,13 +793,24 @@ export class ClaudeCodeCore {
|
|
|
646
793
|
const mcpServers = this.composeMcpServers(mcp.claudeMcpServers);
|
|
647
794
|
return mcpServers ? { mcpServers } : {};
|
|
648
795
|
})(),
|
|
649
|
-
|
|
796
|
+
// Resume is always a reactive DM continuation (no `processKey`), so
|
|
797
|
+
// `resolveSettingSources()` returns the full `["user", "project"]`;
|
|
798
|
+
// routed through the helper for a single source of truth with the
|
|
799
|
+
// execute path (RESEARCH_CLUSTER_COST_FIX_PLAN.md F4).
|
|
800
|
+
settingSources: this.resolveSettingSources(),
|
|
650
801
|
hooks: this.getSecurityHooks(allowMode),
|
|
651
802
|
includePartialMessages: !!streamCallbacks,
|
|
652
803
|
...this.buildAdvisorSettings(),
|
|
653
804
|
},
|
|
654
805
|
});
|
|
655
|
-
|
|
806
|
+
const partialUsage = createPartialUsageAccumulator();
|
|
807
|
+
try {
|
|
808
|
+
return await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, "message.received", partialUsage), this.config.executeTimeoutMinutes);
|
|
809
|
+
}
|
|
810
|
+
catch (err) {
|
|
811
|
+
this.stampPartialSpend(err, partialUsage, actualModelId, startMs, maxBudgetUsd);
|
|
812
|
+
throw err;
|
|
813
|
+
}
|
|
656
814
|
}
|
|
657
815
|
async runWithRetry(fn, context) {
|
|
658
816
|
let lastError;
|
|
@@ -700,6 +858,61 @@ export class ClaudeCodeCore {
|
|
|
700
858
|
}
|
|
701
859
|
return ClaudeCodeCore.NETWORK_ERROR_MESSAGE_PATTERN.test(this.getErrorMessage(error));
|
|
702
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — build a spend snapshot from the
|
|
863
|
+
* stream's partial-usage accumulator and stamp it onto the propagating
|
|
864
|
+
* error so `classifyExecutionError` / `toBackendQuotaError` (which only
|
|
865
|
+
* see the error object) can lift it onto the classified failure.
|
|
866
|
+
*
|
|
867
|
+
* Dollar figure: estimated from the accumulated tokens via the shared
|
|
868
|
+
* price fetcher. For the SDK's `max_budget_usd` abort the figure is
|
|
869
|
+
* additionally floored at the cap — the SDK's own metering crossed it,
|
|
870
|
+
* so any lower estimate (e.g. usage observed only for the first few
|
|
871
|
+
* messages) would under-report what was actually billed. `costSource`
|
|
872
|
+
* is `"sdk_partial"` to mark the figure as a partial reconstruction.
|
|
873
|
+
*
|
|
874
|
+
* No-op when nothing recordable exists: no usage observed AND the
|
|
875
|
+
* error is not a budget abort with a known cap.
|
|
876
|
+
*/
|
|
877
|
+
stampPartialSpend(error, acc, modelId, startMs, maxBudgetUsd) {
|
|
878
|
+
try {
|
|
879
|
+
if (error instanceof BackendQuotaError
|
|
880
|
+
|| error instanceof BackendDecisiveFailure) {
|
|
881
|
+
// Already classified upstream (carries its own spend or lack of
|
|
882
|
+
// one) — re-stamping could only disagree with the classified
|
|
883
|
+
// payload.
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const isBudgetAbort = isClaudeCodeMaxBudgetError(error);
|
|
887
|
+
const sawUsage = accumulatorSawUsage(acc);
|
|
888
|
+
if (!sawUsage && !(isBudgetAbort && typeof maxBudgetUsd === "number")) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const estimated = sawUsage && this.priceFetcher
|
|
892
|
+
? this.priceFetcher.estimateUsageCost({
|
|
893
|
+
backendId: this.backendId,
|
|
894
|
+
modelId,
|
|
895
|
+
usage: acc.usage,
|
|
896
|
+
fallbackModel: findRegisteredModel(this.backendId, modelId),
|
|
897
|
+
}).costUsd
|
|
898
|
+
: 0;
|
|
899
|
+
const costUsd = isBudgetAbort
|
|
900
|
+
? Math.max(estimated, maxBudgetUsd ?? 0)
|
|
901
|
+
: estimated;
|
|
902
|
+
attachPartialSpend(error, {
|
|
903
|
+
usage: { ...acc.usage },
|
|
904
|
+
costUsd,
|
|
905
|
+
modelId,
|
|
906
|
+
numTurns: acc.numTurns,
|
|
907
|
+
durationMs: Date.now() - startMs,
|
|
908
|
+
costSource: "sdk_partial",
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
catch (stampErr) {
|
|
912
|
+
// Best-effort telemetry — never mask the original failure.
|
|
913
|
+
logger.warn({ err: stampErr, modelId }, "Failed to stamp partial spend onto Claude execution error");
|
|
914
|
+
}
|
|
915
|
+
}
|
|
703
916
|
/** Visible for testing. */
|
|
704
917
|
classifyExecutionError(error) {
|
|
705
918
|
if (error instanceof BackendQuotaError ||
|
|
@@ -710,23 +923,28 @@ export class ClaudeCodeCore {
|
|
|
710
923
|
if (quotaError) {
|
|
711
924
|
return quotaError;
|
|
712
925
|
}
|
|
926
|
+
const partialSpend = getAttachedPartialSpend(error);
|
|
713
927
|
if (error instanceof AgentTimeoutError) {
|
|
714
|
-
return new BackendDecisiveFailure(this.backendId, "timeout", error);
|
|
928
|
+
return new BackendDecisiveFailure(this.backendId, "timeout", error, partialSpend);
|
|
715
929
|
}
|
|
716
930
|
if (this.isAuthError(error)) {
|
|
717
|
-
return new BackendDecisiveFailure(this.backendId, "auth", error);
|
|
931
|
+
return new BackendDecisiveFailure(this.backendId, "auth", error, partialSpend);
|
|
718
932
|
}
|
|
719
|
-
return new BackendDecisiveFailure(this.backendId, "other_non_retryable", error);
|
|
933
|
+
return new BackendDecisiveFailure(this.backendId, "other_non_retryable", error, partialSpend);
|
|
720
934
|
}
|
|
721
935
|
toBackendQuotaError(error) {
|
|
722
936
|
if (isClaudeCodeMaxBudgetError(error)) {
|
|
723
|
-
|
|
937
|
+
// The partial-spend snapshot stamped by `stampPartialSpend` is the
|
|
938
|
+
// only usage figure that exists for a budget abort — the SDK kills
|
|
939
|
+
// the stream before the terminal `result` message
|
|
940
|
+
// (PREPASS_COST_REDUCTION_PLAN.md N1).
|
|
941
|
+
return new BackendQuotaError(this.backendId, "max_budget_usd", null, this.getErrorMessage(error), getAttachedPartialSpend(error));
|
|
724
942
|
}
|
|
725
943
|
if (!isClaudeCodeQuotaError(error)) {
|
|
726
944
|
return null;
|
|
727
945
|
}
|
|
728
946
|
const hint = extractClaudeCodeQuotaResetHint(error);
|
|
729
|
-
return new BackendQuotaError(this.backendId, this.getErrorCode(error) ?? "rate_limited", hint ? this.toBackendQuotaResetHint(hint) : null, this.getErrorMessage(error));
|
|
947
|
+
return new BackendQuotaError(this.backendId, this.getErrorCode(error) ?? "rate_limited", hint ? this.toBackendQuotaResetHint(hint) : null, this.getErrorMessage(error), getAttachedPartialSpend(error));
|
|
730
948
|
}
|
|
731
949
|
toBackendQuotaResetHint(hint) {
|
|
732
950
|
return {
|
|
@@ -914,7 +1132,14 @@ export class ClaudeCodeCore {
|
|
|
914
1132
|
const models = [...configuredModels, ...getModelsForBackend(this.backendId)];
|
|
915
1133
|
return models.filter((model, index, list) => list.findIndex((candidate) => candidate.modelId === model.modelId) === index);
|
|
916
1134
|
}
|
|
917
|
-
async consumeStream(stream, model, startMs, streamCallbacks, eventType
|
|
1135
|
+
async consumeStream(stream, model, startMs, streamCallbacks, eventType,
|
|
1136
|
+
/**
|
|
1137
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — live per-message usage sink the
|
|
1138
|
+
* caller keeps a reference to. When the stream throws before the
|
|
1139
|
+
* terminal `result` message, the caller stamps a spend snapshot built
|
|
1140
|
+
* from this accumulator onto the propagating error.
|
|
1141
|
+
*/
|
|
1142
|
+
partialUsage) {
|
|
918
1143
|
let output = "";
|
|
919
1144
|
let streamedOutput = "";
|
|
920
1145
|
let sessionId = null;
|
|
@@ -966,6 +1191,9 @@ export class ClaudeCodeCore {
|
|
|
966
1191
|
// Track Bash tool_use blocks that hit the context API + server-side
|
|
967
1192
|
// advisor tool invocations.
|
|
968
1193
|
const assistantMsg = message;
|
|
1194
|
+
if (partialUsage) {
|
|
1195
|
+
recordAssistantUsage(partialUsage, assistantMsg.message?.usage);
|
|
1196
|
+
}
|
|
969
1197
|
const blocks = assistantMsg.message?.content;
|
|
970
1198
|
if (Array.isArray(blocks)) {
|
|
971
1199
|
for (const block of blocks) {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* message must update BOTH detectors.
|
|
26
26
|
* Forcing those shapes through this module would be over-abstraction.
|
|
27
27
|
*/
|
|
28
|
-
import type { BackendId } from "@aitne/shared";
|
|
28
|
+
import type { BackendId, BackendUsage } from "@aitne/shared";
|
|
29
29
|
import { BackendDecisiveFailure, BackendQuotaError, type BackendQuotaSpend } from "../agent-core.js";
|
|
30
30
|
import type { PriceFetcher } from "./price-fetcher.js";
|
|
31
31
|
/**
|
|
@@ -66,6 +66,24 @@ export declare function assertPromptCostWithinMaxBudget(params: {
|
|
|
66
66
|
modelId: string;
|
|
67
67
|
priceFetcher: PriceFetcher;
|
|
68
68
|
}): void;
|
|
69
|
+
/**
|
|
70
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — recover a best-effort spend payload
|
|
71
|
+
* for a CLI run that failed after the provider already billed tokens.
|
|
72
|
+
* Returns `null` when the JSONL stream never surfaced usage (failure
|
|
73
|
+
* before the first `turn.completed` / stats event), so callers can pass
|
|
74
|
+
* the result straight to `classifyCliFailure` without an empty-usage
|
|
75
|
+
* guard. The dollar figure is a price-fetcher estimate from the observed
|
|
76
|
+
* token totals — same path the success branch uses — so a failed and a
|
|
77
|
+
* successful run with identical usage report identical cost.
|
|
78
|
+
*/
|
|
79
|
+
export declare function recoverCliFailureSpend(params: {
|
|
80
|
+
backendId: BackendId;
|
|
81
|
+
priceFetcher: PriceFetcher;
|
|
82
|
+
usage: BackendUsage;
|
|
83
|
+
modelId: string;
|
|
84
|
+
numTurns: number;
|
|
85
|
+
durationMs: number;
|
|
86
|
+
}): BackendQuotaSpend | null;
|
|
69
87
|
/**
|
|
70
88
|
* Optional pre-auth classifier — given a failure `message`, returns a
|
|
71
89
|
* `BackendDecisiveFailure` when it owns the message, else `null` to fall
|
|
@@ -97,4 +115,14 @@ export declare function classifyCliFailure(params: {
|
|
|
97
115
|
rateLimitPattern: RegExp;
|
|
98
116
|
authPattern: RegExp;
|
|
99
117
|
extraClassifier?: CliFailureExtraClassifier;
|
|
118
|
+
/**
|
|
119
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — best-effort spend recovered from
|
|
120
|
+
* the failed run's JSONL stream (usage totals + price-fetcher
|
|
121
|
+
* estimate). Attached to every error constructed here so the
|
|
122
|
+
* dispatcher's post-hoc audit writer can record what the provider
|
|
123
|
+
* already billed for a turn that produced no `AgentResult`. Errors
|
|
124
|
+
* returned by `extraClassifier` keep their own (usually absent) spend
|
|
125
|
+
* — the classifier owns the full construction of those.
|
|
126
|
+
*/
|
|
127
|
+
spend?: BackendQuotaSpend | null;
|
|
100
128
|
}): BackendQuotaError | BackendDecisiveFailure;
|
|
@@ -53,6 +53,40 @@ export function assertPromptCostWithinMaxBudget(params) {
|
|
|
53
53
|
}
|
|
54
54
|
throw new BackendQuotaError(backendId, "max_budget_usd", null, `${label} estimated prompt cost $${costUsd.toFixed(4)} exceeded the per-turn budget limit $${maxBudgetUsd.toFixed(2)} for ${modelId}.`);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — recover a best-effort spend payload
|
|
58
|
+
* for a CLI run that failed after the provider already billed tokens.
|
|
59
|
+
* Returns `null` when the JSONL stream never surfaced usage (failure
|
|
60
|
+
* before the first `turn.completed` / stats event), so callers can pass
|
|
61
|
+
* the result straight to `classifyCliFailure` without an empty-usage
|
|
62
|
+
* guard. The dollar figure is a price-fetcher estimate from the observed
|
|
63
|
+
* token totals — same path the success branch uses — so a failed and a
|
|
64
|
+
* successful run with identical usage report identical cost.
|
|
65
|
+
*/
|
|
66
|
+
export function recoverCliFailureSpend(params) {
|
|
67
|
+
const { backendId, priceFetcher, usage, modelId, numTurns, durationMs } = params;
|
|
68
|
+
const sawUsage = usage.inputTokens > 0
|
|
69
|
+
|| usage.outputTokens > 0
|
|
70
|
+
|| usage.cacheCreationInputTokens > 0
|
|
71
|
+
|| usage.cacheReadInputTokens > 0;
|
|
72
|
+
if (!sawUsage) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const { costUsd, costSource } = priceFetcher.estimateUsageCost({
|
|
76
|
+
backendId,
|
|
77
|
+
modelId,
|
|
78
|
+
usage,
|
|
79
|
+
fallbackModel: findRegisteredModel(backendId, modelId),
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
usage,
|
|
83
|
+
costUsd,
|
|
84
|
+
modelId,
|
|
85
|
+
numTurns: numTurns || 1,
|
|
86
|
+
durationMs,
|
|
87
|
+
costSource,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
56
90
|
/**
|
|
57
91
|
* Shared failure-classification skeleton for the CLI backends. Maps a raw
|
|
58
92
|
* failure `message` to the dispatcher's failover signals in a fixed order:
|
|
@@ -72,24 +106,25 @@ export function assertPromptCostWithinMaxBudget(params) {
|
|
|
72
106
|
*/
|
|
73
107
|
export function classifyCliFailure(params) {
|
|
74
108
|
const { backendId, message, rateLimitPattern, authPattern, extraClassifier } = params;
|
|
109
|
+
const spend = params.spend ?? null;
|
|
75
110
|
if (isMaxBudgetMessage(message)) {
|
|
76
|
-
return new BackendQuotaError(backendId, "max_budget_usd", null, message);
|
|
111
|
+
return new BackendQuotaError(backendId, "max_budget_usd", null, message, spend);
|
|
77
112
|
}
|
|
78
113
|
if (rateLimitPattern.test(message)) {
|
|
79
114
|
// Best-effort reset-time extraction so the dashboard can surface
|
|
80
115
|
// "quota resets at HH:MM (TZ)" instead of a bare "rate_limited" tag.
|
|
81
116
|
// Falls through to null when no reset-time pattern matches.
|
|
82
|
-
return new BackendQuotaError(backendId, "rate_limited", extractGenericQuotaResetHint(message), message);
|
|
117
|
+
return new BackendQuotaError(backendId, "rate_limited", extractGenericQuotaResetHint(message), message, spend);
|
|
83
118
|
}
|
|
84
119
|
const extra = extraClassifier?.(message, backendId);
|
|
85
120
|
if (extra) {
|
|
86
121
|
return extra;
|
|
87
122
|
}
|
|
88
123
|
if (authPattern.test(message)) {
|
|
89
|
-
return new BackendDecisiveFailure(backendId, "auth", new Error(message));
|
|
124
|
+
return new BackendDecisiveFailure(backendId, "auth", new Error(message), spend);
|
|
90
125
|
}
|
|
91
126
|
if (/timed out|timeout/i.test(message)) {
|
|
92
|
-
return new BackendDecisiveFailure(backendId, "timeout", new Error(message));
|
|
127
|
+
return new BackendDecisiveFailure(backendId, "timeout", new Error(message), spend);
|
|
93
128
|
}
|
|
94
|
-
return new BackendDecisiveFailure(backendId, "other_non_retryable", new Error(message));
|
|
129
|
+
return new BackendDecisiveFailure(backendId, "other_non_retryable", new Error(message), spend);
|
|
95
130
|
}
|
|
@@ -63,6 +63,12 @@ export declare class CodexCore implements IAgentCore {
|
|
|
63
63
|
private buildArgs;
|
|
64
64
|
private pickSummaryModel;
|
|
65
65
|
private classifyFailure;
|
|
66
|
+
/**
|
|
67
|
+
* PREPASS_COST_REDUCTION_PLAN.md N1 — spend recovered from the failed
|
|
68
|
+
* run's JSONL usage so terminal errors carry what the provider already
|
|
69
|
+
* billed. Null when the stream never reported usage.
|
|
70
|
+
*/
|
|
71
|
+
private recoverFailureSpend;
|
|
66
72
|
private assertWithinMaxBudget;
|
|
67
73
|
private assertPromptWithinMaxBudget;
|
|
68
74
|
/**
|