@vellumai/assistant 0.8.11 → 0.8.12-staging.1
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/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +126 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +301 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +219 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +2 -1
- package/src/providers/openai/responses-provider.ts +5 -1
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/plugins-routes.ts +21 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -63,6 +63,86 @@ export const MemoryV3HotSetSchema = z
|
|
|
63
63
|
})
|
|
64
64
|
.describe("Memory v3 hot-set lane (decayed selection frequency) tuning.");
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Fresh-set lane tuning: the top-K pages by most-recent on-disk modification
|
|
68
|
+
* folded into the candidate pool as a stable-prefix lane. Recency covers the
|
|
69
|
+
* window before the other lanes can reach a just-written page (no selection
|
|
70
|
+
* history for the hot set; nothing lexical for the finders on summary-shaped
|
|
71
|
+
* messages).
|
|
72
|
+
*/
|
|
73
|
+
export const MemoryV3FreshSetSchema = z
|
|
74
|
+
.object({
|
|
75
|
+
k: z
|
|
76
|
+
.number({ error: "memory.v3.freshSet.k must be a number" })
|
|
77
|
+
.int("memory.v3.freshSet.k must be an integer")
|
|
78
|
+
.nonnegative("memory.v3.freshSet.k must be a non-negative integer")
|
|
79
|
+
.default(100)
|
|
80
|
+
.describe(
|
|
81
|
+
"Number of most-recently-modified pages included in the fresh-set lane (0 disables the lane). Sized to cover roughly the last day or two of page writes — the recency window conversations reference most.",
|
|
82
|
+
),
|
|
83
|
+
})
|
|
84
|
+
.describe("Memory v3 fresh-set lane (page-modification recency) tuning.");
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Learned-edge lane tuning: a co-selection NPMI association graph over the
|
|
88
|
+
* selection log, expanded alongside the static link graph. Behavioral edges
|
|
89
|
+
* reach association-relevant pages no lexical, semantic, or authored-link
|
|
90
|
+
* lane can surface.
|
|
91
|
+
*/
|
|
92
|
+
export const MemoryV3LearnedEdgesSchema = z
|
|
93
|
+
.object({
|
|
94
|
+
halfLifeDays: z
|
|
95
|
+
.number({ error: "memory.v3.learnedEdges.halfLifeDays must be a number" })
|
|
96
|
+
.positive("memory.v3.learnedEdges.halfLifeDays must be a positive number")
|
|
97
|
+
.default(30)
|
|
98
|
+
.describe(
|
|
99
|
+
"Co-selection decay half-life in days: a selector call this old contributes half the weight of one made now.",
|
|
100
|
+
),
|
|
101
|
+
minCount: z
|
|
102
|
+
.number({ error: "memory.v3.learnedEdges.minCount must be a number" })
|
|
103
|
+
.positive("memory.v3.learnedEdges.minCount must be a positive number")
|
|
104
|
+
.default(3)
|
|
105
|
+
.describe(
|
|
106
|
+
"Minimum decayed co-occurrence mass for a pair to form an edge (the rare-pair noise floor).",
|
|
107
|
+
),
|
|
108
|
+
npmiFloor: z
|
|
109
|
+
.number({ error: "memory.v3.learnedEdges.npmiFloor must be a number" })
|
|
110
|
+
.nonnegative(
|
|
111
|
+
"memory.v3.learnedEdges.npmiFloor must be a non-negative number",
|
|
112
|
+
)
|
|
113
|
+
.default(0.2)
|
|
114
|
+
.describe("Minimum NPMI for a pair to form an edge."),
|
|
115
|
+
maxPerPage: z
|
|
116
|
+
.number({ error: "memory.v3.learnedEdges.maxPerPage must be a number" })
|
|
117
|
+
.int("memory.v3.learnedEdges.maxPerPage must be an integer")
|
|
118
|
+
.nonnegative(
|
|
119
|
+
"memory.v3.learnedEdges.maxPerPage must be a non-negative integer",
|
|
120
|
+
)
|
|
121
|
+
.default(6)
|
|
122
|
+
.describe(
|
|
123
|
+
"Maximum learned out-edges kept per page, strongest NPMI first (0 disables the lane).",
|
|
124
|
+
),
|
|
125
|
+
perSeed: z
|
|
126
|
+
.number({ error: "memory.v3.learnedEdges.perSeed must be a number" })
|
|
127
|
+
.int("memory.v3.learnedEdges.perSeed must be an integer")
|
|
128
|
+
.positive("memory.v3.learnedEdges.perSeed must be a positive integer")
|
|
129
|
+
.default(3)
|
|
130
|
+
.describe(
|
|
131
|
+
"Maximum learned neighbours surfaced per expanded seed each turn.",
|
|
132
|
+
),
|
|
133
|
+
cap: z
|
|
134
|
+
.number({ error: "memory.v3.learnedEdges.cap must be a number" })
|
|
135
|
+
.int("memory.v3.learnedEdges.cap must be an integer")
|
|
136
|
+
.nonnegative("memory.v3.learnedEdges.cap must be a non-negative integer")
|
|
137
|
+
.default(20)
|
|
138
|
+
.describe(
|
|
139
|
+
"Hard cap on total learned-lane surfaced articles per turn (0 disables the pass).",
|
|
140
|
+
),
|
|
141
|
+
})
|
|
142
|
+
.describe(
|
|
143
|
+
"Memory v3 learned-edge lane (co-selection NPMI association graph) tuning.",
|
|
144
|
+
);
|
|
145
|
+
|
|
66
146
|
/**
|
|
67
147
|
* Ephemeral section-spotlight tuning: how many of the current turn's selected
|
|
68
148
|
* finder hits render their matched section into the `<memory_spotlight>`
|
|
@@ -142,6 +222,10 @@ export const MemoryV3ConfigSchema = z
|
|
|
142
222
|
.object({
|
|
143
223
|
prune: MemoryV3PruneSchema.default(MemoryV3PruneSchema.parse({})),
|
|
144
224
|
hotSet: MemoryV3HotSetSchema.default(MemoryV3HotSetSchema.parse({})),
|
|
225
|
+
freshSet: MemoryV3FreshSetSchema.default(MemoryV3FreshSetSchema.parse({})),
|
|
226
|
+
learnedEdges: MemoryV3LearnedEdgesSchema.default(
|
|
227
|
+
MemoryV3LearnedEdgesSchema.parse({}),
|
|
228
|
+
),
|
|
145
229
|
spotlight: MemoryV3SpotlightSchema.default(
|
|
146
230
|
MemoryV3SpotlightSchema.parse({}),
|
|
147
231
|
),
|
|
@@ -161,6 +245,14 @@ export const MemoryV3ConfigSchema = z
|
|
|
161
245
|
.describe(
|
|
162
246
|
"Number of dense-lane articles folded into the candidate pool each turn.",
|
|
163
247
|
),
|
|
248
|
+
replyQueryK: z
|
|
249
|
+
.number({ error: "memory.v3.replyQueryK must be a number" })
|
|
250
|
+
.int("memory.v3.replyQueryK must be an integer")
|
|
251
|
+
.nonnegative("memory.v3.replyQueryK must be a non-negative integer")
|
|
252
|
+
.default(12)
|
|
253
|
+
.describe(
|
|
254
|
+
"Per-lane article budget for the reply-query finder pass: needle and dense each re-run over the assistant's previous message as separate queries (never concatenated with the user's message). 0 disables the pass. Deliberately small next to needleK/denseK — the pass adds the assistant-side retrieval signal, not a second full sweep.",
|
|
255
|
+
),
|
|
164
256
|
edge: MemoryV3EdgeSchema.default(MemoryV3EdgeSchema.parse({})),
|
|
165
257
|
})
|
|
166
258
|
.describe("Memory v3 — section-grain lane retrieval");
|
|
@@ -50,6 +50,27 @@ function buildToolNameById(messages: Message[]): Map<string, string> {
|
|
|
50
50
|
return byId;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Shared eligibility rules for replacing an oversized tool result with the
|
|
55
|
+
* on-disk stub: string content over the size threshold, not an error result,
|
|
56
|
+
* not from an exempt tool (durable instructions like skill bodies), and not
|
|
57
|
+
* already truncated (idempotency marker). Used by both the post-turn pass and
|
|
58
|
+
* the result-time spool pass so the two converge on the same results.
|
|
59
|
+
*/
|
|
60
|
+
export function isTruncationEligible(
|
|
61
|
+
tr: ToolResultContent,
|
|
62
|
+
toolName: string | undefined,
|
|
63
|
+
): boolean {
|
|
64
|
+
if (typeof tr.content !== "string") return false;
|
|
65
|
+
if (tr.content.length <= THRESHOLD_CHARS) return false;
|
|
66
|
+
if (tr.is_error) return false;
|
|
67
|
+
if (toolName !== undefined && TRUNCATION_EXEMPT_TOOLS.has(toolName)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (tr.content.includes(TRUNCATION_MARKER)) return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
/**
|
|
54
75
|
* Deterministic file path for a tool result's full content on disk.
|
|
55
76
|
* Uses the first 12 hex chars of the SHA-256 of the tool_use_id.
|
|
@@ -58,7 +79,10 @@ export function getToolResultFilePath(
|
|
|
58
79
|
conversationDir: string,
|
|
59
80
|
toolUseId: string,
|
|
60
81
|
): string {
|
|
61
|
-
const hash = createHash("sha256")
|
|
82
|
+
const hash = createHash("sha256")
|
|
83
|
+
.update(toolUseId)
|
|
84
|
+
.digest("hex")
|
|
85
|
+
.slice(0, 12);
|
|
62
86
|
return join(conversationDir, TOOL_RESULT_DIR, `${hash}.txt`);
|
|
63
87
|
}
|
|
64
88
|
|
|
@@ -73,7 +97,11 @@ export function buildTruncatedContent(
|
|
|
73
97
|
): string {
|
|
74
98
|
const half = Math.floor(TARGET_CHARS / 2);
|
|
75
99
|
const prefix = safeStringSlice(original, 0, half);
|
|
76
|
-
const suffix = safeStringSlice(
|
|
100
|
+
const suffix = safeStringSlice(
|
|
101
|
+
original,
|
|
102
|
+
original.length - half,
|
|
103
|
+
original.length,
|
|
104
|
+
);
|
|
77
105
|
const omittedChars = original.length - TARGET_CHARS;
|
|
78
106
|
const estimatedTokens = Math.round(omittedChars / 4);
|
|
79
107
|
return `${prefix}\n\n...(${estimatedTokens} tokens omitted ${TRUNCATION_MARKER} ${filePath})\n\n${suffix}`;
|
|
@@ -86,9 +114,7 @@ export function buildTruncatedContent(
|
|
|
86
114
|
* - The full content is persisted to a deterministic file path on disk.
|
|
87
115
|
* - The in-context content is replaced with a prefix/suffix stub.
|
|
88
116
|
*
|
|
89
|
-
* Results are skipped
|
|
90
|
-
* have already been truncated (contain `TRUNCATION_MARKER`), or were produced
|
|
91
|
-
* by a tool in `TRUNCATION_EXEMPT_TOOLS` (durable instructions like skill bodies).
|
|
117
|
+
* Results are skipped unless {@link isTruncationEligible} allows them.
|
|
92
118
|
*
|
|
93
119
|
* Returns a shallow-copied messages array (only modified messages are cloned)
|
|
94
120
|
* and the count of results that were truncated.
|
|
@@ -107,22 +133,10 @@ export function postTurnTruncateToolResults(
|
|
|
107
133
|
if (block.type !== "tool_result") return block;
|
|
108
134
|
const tr = block as ToolResultContent;
|
|
109
135
|
|
|
110
|
-
|
|
111
|
-
if (tr.content.length <= THRESHOLD_CHARS) return block;
|
|
112
|
-
|
|
113
|
-
// Skip error results.
|
|
114
|
-
if (tr.is_error) return block;
|
|
115
|
-
|
|
116
|
-
// Skip results from tools whose output is durable operating instructions
|
|
117
|
-
// (e.g. skill_load); middle-truncating them strips the workflow.
|
|
118
|
-
const toolName = toolNameById.get(tr.tool_use_id);
|
|
119
|
-
if (toolName !== undefined && TRUNCATION_EXEMPT_TOOLS.has(toolName)) {
|
|
136
|
+
if (!isTruncationEligible(tr, toolNameById.get(tr.tool_use_id))) {
|
|
120
137
|
return block;
|
|
121
138
|
}
|
|
122
139
|
|
|
123
|
-
// Skip already-truncated results (idempotency).
|
|
124
|
-
if (tr.content.includes(TRUNCATION_MARKER)) return block;
|
|
125
|
-
|
|
126
140
|
const filePath = getToolResultFilePath(
|
|
127
141
|
options.conversationDir,
|
|
128
142
|
tr.tool_use_id,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { ContentBlock, ToolResultContent } from "../providers/types.js";
|
|
5
|
+
import {
|
|
6
|
+
buildTruncatedContent,
|
|
7
|
+
getToolResultFilePath,
|
|
8
|
+
isTruncationEligible,
|
|
9
|
+
TOOL_RESULT_DIR,
|
|
10
|
+
} from "./post-turn-tool-result-truncation.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AX-tree snapshots have their own dedicated history compactor
|
|
14
|
+
* (`compactAxTreeHistory`) with placeholder semantics tuned for computer-use
|
|
15
|
+
* sessions, and the model needs the freshest tree inline to act on it, so the
|
|
16
|
+
* result-time pass leaves them alone.
|
|
17
|
+
*/
|
|
18
|
+
const AX_TREE_TAG = "<ax-tree>";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tools whose results keep their full content at result time even when
|
|
22
|
+
* oversized. `file_read` is the model's only way to page spooled content back
|
|
23
|
+
* into context: stubbing a read of a `.tool-results/` file would spool a
|
|
24
|
+
* fresh copy and hand back another stub, so oversized content could never be
|
|
25
|
+
* read at all. Explicit reads are therefore honored in full; the post-turn
|
|
26
|
+
* pass still truncates them at turn end, after the model has consumed the
|
|
27
|
+
* content.
|
|
28
|
+
*/
|
|
29
|
+
const RESULT_TIME_STUB_EXEMPT_TOOLS = new Set<string>(["file_read"]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether a tool result is eligible for the result-time spool/stub pass:
|
|
33
|
+
* the post-turn pass's shared rules plus the AX-tree and `file_read`
|
|
34
|
+
* exemptions above.
|
|
35
|
+
*/
|
|
36
|
+
function isSpoolEligible(
|
|
37
|
+
tr: ToolResultContent,
|
|
38
|
+
toolName: string | undefined,
|
|
39
|
+
): boolean {
|
|
40
|
+
if (!isTruncationEligible(tr, toolName)) return false;
|
|
41
|
+
if (toolName !== undefined && RESULT_TIME_STUB_EXEMPT_TOOLS.has(toolName)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (tr.content.includes(AX_TREE_TAG)) return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Spool every oversized tool result in `blocks` to its deterministic
|
|
50
|
+
* `.tool-results/` file and replace the inline content with the post-turn
|
|
51
|
+
* pass's prefix/suffix stub — at result time, before the blocks join history.
|
|
52
|
+
*
|
|
53
|
+
* Because the swap happens before the content is ever sent, the
|
|
54
|
+
* provider-bound history stays strictly append-only across the turn's model
|
|
55
|
+
* calls, which is what keeps the provider's prompt-cache prefix valid:
|
|
56
|
+
* rewriting an earlier message between calls would invalidate the cache from
|
|
57
|
+
* that point on every iteration. The model still gets the head/tail preview
|
|
58
|
+
* plus the on-disk path, so it can page the full content back in with
|
|
59
|
+
* `file_read` (whose results are exempt from this pass) when it actually
|
|
60
|
+
* needs it.
|
|
61
|
+
*
|
|
62
|
+
* Uses the same file paths, stub bytes, and eligibility rules as
|
|
63
|
+
* `postTurnTruncateToolResults`, whose `TRUNCATION_MARKER` guard then skips
|
|
64
|
+
* these results at turn end. Eligible elements of `blocks` are replaced in
|
|
65
|
+
* place (the block objects themselves are not mutated). A result is only
|
|
66
|
+
* stubbed after its file is written, so a stub never points at a missing
|
|
67
|
+
* file; on filesystem errors the remaining blocks keep their full content and
|
|
68
|
+
* the post-turn pass covers them.
|
|
69
|
+
*
|
|
70
|
+
* Returns the number of results spooled and stubbed.
|
|
71
|
+
*/
|
|
72
|
+
export function spoolAndStubOversizedToolResults(
|
|
73
|
+
blocks: ContentBlock[],
|
|
74
|
+
options: {
|
|
75
|
+
conversationDir: string;
|
|
76
|
+
toolNameById: (toolUseId: string) => string | undefined;
|
|
77
|
+
},
|
|
78
|
+
): number {
|
|
79
|
+
let stubbedCount = 0;
|
|
80
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
81
|
+
const block = blocks[i];
|
|
82
|
+
if (block.type !== "tool_result") continue;
|
|
83
|
+
if (!isSpoolEligible(block, options.toolNameById(block.tool_use_id))) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (stubbedCount === 0) {
|
|
88
|
+
mkdirSync(join(options.conversationDir, TOOL_RESULT_DIR), {
|
|
89
|
+
recursive: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const filePath = getToolResultFilePath(
|
|
93
|
+
options.conversationDir,
|
|
94
|
+
block.tool_use_id,
|
|
95
|
+
);
|
|
96
|
+
writeFileSync(filePath, block.content, "utf-8");
|
|
97
|
+
blocks[i] = {
|
|
98
|
+
...block,
|
|
99
|
+
content: buildTruncatedContent(block.content, filePath),
|
|
100
|
+
};
|
|
101
|
+
stubbedCount++;
|
|
102
|
+
}
|
|
103
|
+
return stubbedCount;
|
|
104
|
+
}
|
|
@@ -16,11 +16,12 @@ import type {
|
|
|
16
16
|
} from "../channels/types.js";
|
|
17
17
|
import { getConfig } from "../config/loader.js";
|
|
18
18
|
import { recordEstimate } from "../context/estimator-calibration.js";
|
|
19
|
+
import { stripInjectionsForCompaction } from "../context/strip-injections.js";
|
|
19
20
|
import { getCalibrationProviderKey } from "../context/token-estimator.js";
|
|
20
21
|
import {
|
|
21
22
|
recordCompactionEndBestEffort,
|
|
22
23
|
recordCompactionStartBestEffort,
|
|
23
|
-
} from "../memory/compaction-log-
|
|
24
|
+
} from "../memory/compaction-log-store-clickhouse.js";
|
|
24
25
|
import { projectAssistantMessage } from "../memory/conversation-attention-store.js";
|
|
25
26
|
import {
|
|
26
27
|
deleteMessageById,
|
|
@@ -286,6 +287,14 @@ export interface EventHandlerState {
|
|
|
286
287
|
* never claims content a flush has not yet written.
|
|
287
288
|
*/
|
|
288
289
|
lastPersistedContentSeq: number | undefined;
|
|
290
|
+
/**
|
|
291
|
+
* Pre-compaction history buffered from `context_compacting` start events,
|
|
292
|
+
* keyed by `compactionId`. The paired `compaction_completed` event no
|
|
293
|
+
* longer carries the pre-compaction history, so the dispatcher re-derives
|
|
294
|
+
* the stripped durable base from the buffered start messages. Entries are
|
|
295
|
+
* consumed (deleted) when the end event dispatches.
|
|
296
|
+
*/
|
|
297
|
+
readonly compactionStartMessages: Map<string, Message[]>;
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
/** Immutable context shared across event handlers within a single agent loop run. */
|
|
@@ -301,8 +310,8 @@ export interface EventHandlerDeps {
|
|
|
301
310
|
readonly turnInterfaceContext: TurnInterfaceContext;
|
|
302
311
|
/**
|
|
303
312
|
* Commit a successful inline compaction to durable state. Invoked from the
|
|
304
|
-
* `compaction_completed` dispatch case (when `
|
|
305
|
-
* loop's compaction result and the stripped pre-compaction
|
|
313
|
+
* `compaction_completed` dispatch case (when `compacted`) with the
|
|
314
|
+
* loop's compaction result and the stripped pre-compaction history. Supplied
|
|
306
315
|
* by the orchestrator because the body writes Conversation DB-record fields,
|
|
307
316
|
* projects Slack provenance, and emits transport the loop is intentionally
|
|
308
317
|
* blind to.
|
|
@@ -361,6 +370,7 @@ export function createEventHandlerState(): EventHandlerState {
|
|
|
361
370
|
currentMessageContent: [],
|
|
362
371
|
currentThinkingTimestamps: [],
|
|
363
372
|
lastPersistedContentSeq: undefined,
|
|
373
|
+
compactionStartMessages: new Map(),
|
|
364
374
|
};
|
|
365
375
|
}
|
|
366
376
|
|
|
@@ -2316,6 +2326,9 @@ export async function dispatchAgentEvent(
|
|
|
2316
2326
|
}
|
|
2317
2327
|
case "context_compacting":
|
|
2318
2328
|
recordCompactionStartBestEffort(deps.ctx.conversationId, event);
|
|
2329
|
+
// Buffer the pre-compaction history so the paired end event can
|
|
2330
|
+
// re-derive the stripped durable base.
|
|
2331
|
+
state.compactionStartMessages.set(event.compactionId, event.messages);
|
|
2319
2332
|
deps.ctx.emitActivityState("thinking", "context_compacting", {
|
|
2320
2333
|
requestId: deps.reqId,
|
|
2321
2334
|
statusText: "Compacting context",
|
|
@@ -2329,22 +2342,34 @@ export async function dispatchAgentEvent(
|
|
|
2329
2342
|
// banner.
|
|
2330
2343
|
deps.onEvent(event);
|
|
2331
2344
|
break;
|
|
2332
|
-
case "compaction_completed":
|
|
2333
|
-
// Always commit the
|
|
2334
|
-
// so re-injection re-applies onto the stripped history
|
|
2335
|
-
// pipeline ran but did not compact.
|
|
2336
|
-
//
|
|
2337
|
-
//
|
|
2338
|
-
//
|
|
2339
|
-
//
|
|
2340
|
-
//
|
|
2341
|
-
//
|
|
2345
|
+
case "compaction_completed": {
|
|
2346
|
+
// Always commit the stripped pre-compaction history as the durable
|
|
2347
|
+
// message base so re-injection re-applies onto the stripped history
|
|
2348
|
+
// even when the pipeline ran but did not compact. The base is
|
|
2349
|
+
// re-derived from the buffered start event's messages (the end event
|
|
2350
|
+
// carries only the pipeline's output). When the pipeline did compact,
|
|
2351
|
+
// commit the durable result (DB-record fields, Slack provenance,
|
|
2352
|
+
// SSE) — which overwrites `ctx.messages` with the compacted history.
|
|
2353
|
+
// This runs before the loop's `reinject` hook (the loop awaits this
|
|
2354
|
+
// dispatch), so the committed history is in place in time. A failed
|
|
2355
|
+
// durable commit re-throws below to abort the turn rather than
|
|
2356
|
+
// re-injecting against half-applied state.
|
|
2342
2357
|
recordCompactionEndBestEffort(deps.ctx.conversationId, event);
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2358
|
+
const startMessages = state.compactionStartMessages.get(
|
|
2359
|
+
event.compactionId,
|
|
2360
|
+
);
|
|
2361
|
+
state.compactionStartMessages.delete(event.compactionId);
|
|
2362
|
+
// Fall back to the pipeline's output when the start event was never
|
|
2363
|
+
// buffered — on the no-compaction path it is the stripped input.
|
|
2364
|
+
const strippedBase = startMessages
|
|
2365
|
+
? stripInjectionsForCompaction(startMessages)
|
|
2366
|
+
: event.messages;
|
|
2367
|
+
deps.ctx.messages = strippedBase;
|
|
2368
|
+
if (event.compacted) {
|
|
2369
|
+
await deps.applyCompaction(event, strippedBase);
|
|
2346
2370
|
}
|
|
2347
2371
|
break;
|
|
2372
|
+
}
|
|
2348
2373
|
case "history_stripped":
|
|
2349
2374
|
// Record the history-stripped DB marker right after the loop strips
|
|
2350
2375
|
// injections (before the pipeline). Best-effort: a transient marker
|
|
@@ -255,7 +255,7 @@ export function classifyConversationError(
|
|
|
255
255
|
return {
|
|
256
256
|
code: "PROVIDER_NOT_CONFIGURED",
|
|
257
257
|
userMessage:
|
|
258
|
-
"No compatible provider connection found for this profile. Check your provider connections in Settings.",
|
|
258
|
+
"No compatible provider connection found for this profile. Check your provider connections in Settings → Models & Services.",
|
|
259
259
|
retryable: true,
|
|
260
260
|
debugDetails,
|
|
261
261
|
errorCategory: "provider_not_configured",
|
|
@@ -334,15 +334,6 @@ function classifyCore(
|
|
|
334
334
|
}
|
|
335
335
|
return invalidApiKeyClassification(attribution);
|
|
336
336
|
}
|
|
337
|
-
if (error.statusCode === 401) {
|
|
338
|
-
return {
|
|
339
|
-
code: "PROVIDER_INVALID_KEY",
|
|
340
|
-
userMessage:
|
|
341
|
-
"Your API key is invalid or expired. Update it in Settings or switch to managed mode.",
|
|
342
|
-
retryable: false,
|
|
343
|
-
errorCategory: "provider_invalid_key",
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
337
|
if (error.statusCode === 402) {
|
|
347
338
|
if (isManagedBalanceError(error)) {
|
|
348
339
|
return managedBalanceClassification();
|
|
@@ -543,7 +534,7 @@ function providerBillingClassification(): Omit<
|
|
|
543
534
|
return {
|
|
544
535
|
code: "PROVIDER_BILLING",
|
|
545
536
|
userMessage:
|
|
546
|
-
"Your API provider account or key needs credits. Add funds with the provider or update the key in Settings.",
|
|
537
|
+
"Your API provider account or key needs credits. Add funds with the provider or update the key in Settings → Models & Services.",
|
|
547
538
|
retryable: false,
|
|
548
539
|
errorCategory: "provider_billing",
|
|
549
540
|
};
|
|
@@ -583,8 +574,8 @@ function invalidApiKeyClassification(
|
|
|
583
574
|
return {
|
|
584
575
|
code: "PROVIDER_INVALID_KEY",
|
|
585
576
|
userMessage: target
|
|
586
|
-
? `The API key${target} was rejected by the provider. Update it in Settings.`
|
|
587
|
-
: "Your API key was rejected by the provider. Update it in Settings.",
|
|
577
|
+
? `The API key${target} was rejected by the provider. Update it in Settings → Models & Services.`
|
|
578
|
+
: "Your API key was rejected by the provider. Update it in Settings → Models & Services.",
|
|
588
579
|
retryable: false,
|
|
589
580
|
errorCategory: "provider_invalid_key",
|
|
590
581
|
...(attribution?.connectionName
|
|
@@ -610,8 +601,8 @@ function providerNotConfiguredClassification(
|
|
|
610
601
|
return {
|
|
611
602
|
code: "PROVIDER_NOT_CONFIGURED",
|
|
612
603
|
userMessage: target
|
|
613
|
-
? `No API key configured${target}. Add one in Settings to start chatting.`
|
|
614
|
-
: "No API key configured for inference. Add one in Settings to start chatting.",
|
|
604
|
+
? `No API key configured${target}. Add one in Settings → Models & Services to start chatting.`
|
|
605
|
+
: "No API key configured for inference. Add one in Settings → Models & Services to start chatting.",
|
|
615
606
|
retryable: true,
|
|
616
607
|
errorCategory: "provider_not_configured",
|
|
617
608
|
...(attribution?.connectionName
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
resolveOverrideProfile,
|
|
52
52
|
setConversationHistoryStrippedAt,
|
|
53
53
|
} from "../memory/conversation-crud.js";
|
|
54
|
+
import { getResolvedConversationDirPath } from "../memory/conversation-directories.js";
|
|
54
55
|
import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
55
56
|
import { unwrapMemoryBlock, wrapMemoryBlock } from "../memory/memory-marker.js";
|
|
56
57
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
@@ -638,6 +639,14 @@ export class Conversation {
|
|
|
638
639
|
toolExecutor: toolDefs.length > 0 ? toolExecutor : undefined,
|
|
639
640
|
resolveTools,
|
|
640
641
|
resolveSystemPrompt: resolveSystemPromptCallback,
|
|
642
|
+
resolveConversationDir: () => {
|
|
643
|
+
const conv = getConversation(this.conversationId);
|
|
644
|
+
if (!conv) return null;
|
|
645
|
+
return getResolvedConversationDirPath(
|
|
646
|
+
this.conversationId,
|
|
647
|
+
conv.createdAt,
|
|
648
|
+
);
|
|
649
|
+
},
|
|
641
650
|
});
|
|
642
651
|
createContextWindowManager({
|
|
643
652
|
provider,
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
} from "../credential-execution/startup-timeout.js";
|
|
36
36
|
import { FilingService } from "../filing/filing-service.js";
|
|
37
37
|
import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
38
|
-
import { startHomeContentRefresh } from "../home/home-content-refresh.js";
|
|
39
38
|
import { backfillRelationshipStateIfMissing } from "../home/relationship-state-writer.js";
|
|
40
39
|
import { closeSentry, initSentry, setSentryDeviceId } from "../instrument.js";
|
|
41
40
|
import {
|
|
@@ -98,8 +97,8 @@ import {
|
|
|
98
97
|
listWorkItems,
|
|
99
98
|
updateWorkItem,
|
|
100
99
|
} from "../work-items/work-item-store.js";
|
|
100
|
+
import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/adaptive-thinking-repair.js";
|
|
101
101
|
import { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
|
|
102
|
-
import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/migrations/097-enable-adaptive-thinking-managed-profiles.js";
|
|
103
102
|
import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
|
|
104
103
|
import { runWorkspaceMigrations } from "../workspace/migrations/runner.js";
|
|
105
104
|
import {
|
|
@@ -480,10 +479,6 @@ export async function runDaemon(): Promise<void> {
|
|
|
480
479
|
);
|
|
481
480
|
});
|
|
482
481
|
|
|
483
|
-
// Pre-warm LLM-generated home page content (greeting + suggestion
|
|
484
|
-
// prompts) so the GET handler never triggers LLM calls.
|
|
485
|
-
startHomeContentRefresh();
|
|
486
|
-
|
|
487
482
|
// Backfill injection templates on Slack bot token credentials so the
|
|
488
483
|
// credential proxy can inject Authorization headers. Safe on every startup.
|
|
489
484
|
try {
|
|
@@ -780,20 +775,6 @@ export async function runDaemon(): Promise<void> {
|
|
|
780
775
|
startDiskPressureGuardForLifecycle();
|
|
781
776
|
startOrphanReaper();
|
|
782
777
|
|
|
783
|
-
// Kick off the update bulletin background job AFTER `server.start()`
|
|
784
|
-
// resolves. The conversation store must be initialized before wake
|
|
785
|
-
// calls can resolve targets.
|
|
786
|
-
//
|
|
787
|
-
// Kept fire-and-forget (`void import(...).then(...).catch(...)`) so the
|
|
788
|
-
// daemon never blocks startup on it.
|
|
789
|
-
if (dbReady) {
|
|
790
|
-
void import("../prompts/update-bulletin-job.js")
|
|
791
|
-
.then((m) => m.runUpdateBulletinJobIfNeeded())
|
|
792
|
-
.catch((err) =>
|
|
793
|
-
log.warn({ err }, "Update bulletin job failed — continuing startup"),
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
778
|
// Mutable refs for Qdrant and memory worker so background
|
|
798
779
|
// init can assign them and the shutdown handler always sees the latest value.
|
|
799
780
|
const bgRefs: {
|
|
@@ -7,6 +7,7 @@ import type { ConversationListInvalidatedEvent } from "../../api/events/conversa
|
|
|
7
7
|
import type { ConversationTitleUpdatedEvent } from "../../api/events/conversation-title-updated.js";
|
|
8
8
|
import type { GenerationCancelledEvent } from "../../api/events/generation-cancelled.js";
|
|
9
9
|
import type { GenerationHandoffEvent } from "../../api/events/generation-handoff.js";
|
|
10
|
+
import type { UsageProgressEvent } from "../../api/events/usage-progress.js";
|
|
10
11
|
import type { UsageUpdateEvent } from "../../api/events/usage-update.js";
|
|
11
12
|
import type {
|
|
12
13
|
ChannelId,
|
|
@@ -425,20 +426,6 @@ export interface UndoComplete {
|
|
|
425
426
|
conversationId?: string;
|
|
426
427
|
}
|
|
427
428
|
|
|
428
|
-
/**
|
|
429
|
-
* Emitted after each LLM call with per-call token deltas and estimated cost.
|
|
430
|
-
* Clients accumulate these additively for live-updating usage metrics.
|
|
431
|
-
* This is a UI-only hint — it does not persist to DB or affect billing.
|
|
432
|
-
*/
|
|
433
|
-
export interface UsageProgress {
|
|
434
|
-
type: "usage_progress";
|
|
435
|
-
conversationId: string;
|
|
436
|
-
inputTokens: number;
|
|
437
|
-
outputTokens: number;
|
|
438
|
-
estimatedCost: number;
|
|
439
|
-
model: string;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
429
|
export interface UsageResponse {
|
|
443
430
|
type: "usage_response";
|
|
444
431
|
totalInputTokens: number;
|
|
@@ -550,7 +537,7 @@ export type _ConversationsServerMessages =
|
|
|
550
537
|
| HistoryResponse
|
|
551
538
|
| UndoComplete
|
|
552
539
|
| UsageUpdateEvent
|
|
553
|
-
|
|
|
540
|
+
| UsageProgressEvent
|
|
554
541
|
| UsageResponse
|
|
555
542
|
| ContextCompacted
|
|
556
543
|
| CompactionCircuitOpenEvent
|
|
@@ -42,7 +42,7 @@ export interface TrustContext {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Trust context used by internal background jobs (memory consolidation,
|
|
45
|
-
*
|
|
45
|
+
* scheduled tasks) when invoking the agent loop without
|
|
46
46
|
* an inbound actor identity. The assistant is the guardian over its own
|
|
47
47
|
* internal state, so self-maintenance flows clear the side-effect
|
|
48
48
|
* approval gate. Inbound message conversations resolve trust per-actor
|
|
@@ -114,7 +114,7 @@ mock.module("../../config/loader.js", () => ({
|
|
|
114
114
|
getNestedValue: () => undefined,
|
|
115
115
|
setNestedValue: () => {},
|
|
116
116
|
API_KEY_PROVIDERS: [],
|
|
117
|
-
|
|
117
|
+
_writeQuarantineNotice: () => {},
|
|
118
118
|
}));
|
|
119
119
|
|
|
120
120
|
// Stub prompt helpers.
|