@vellumai/assistant 0.4.49 → 0.4.50
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 +24 -33
- package/README.md +3 -3
- package/docs/architecture/memory.md +180 -119
- package/package.json +2 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -0
- package/src/__tests__/credential-vault.test.ts +13 -1
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +572 -5
- package/src/__tests__/oauth-store.test.ts +120 -6
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +132 -0
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +43 -5
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +31 -2
- package/src/cli/commands/oauth/connections.ts +431 -97
- package/src/cli/commands/oauth/providers.ts +15 -1
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +4 -10
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +31 -3
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +12 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/instrument.ts +61 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/oauth/byo-connection.test.ts +8 -1
- package/src/oauth/oauth-store.ts +113 -27
- package/src/oauth/seed-providers.ts +6 -0
- package/src/oauth/token-persistence.ts +11 -3
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/conversation-routes.ts +9 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +2 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +36 -36
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +1 -3
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -12,6 +12,8 @@ export interface ClassifiedSessionError {
|
|
|
12
12
|
userMessage: string;
|
|
13
13
|
retryable: boolean;
|
|
14
14
|
debugDetails?: string;
|
|
15
|
+
/** Machine-readable error category for log report metadata and triage. */
|
|
16
|
+
errorCategory: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
// Network-level error patterns (connection refused, timeout, DNS, reset)
|
|
@@ -68,6 +70,21 @@ const PROVIDER_API_PATTERNS = [
|
|
|
68
70
|
/gateway timeout/i,
|
|
69
71
|
];
|
|
70
72
|
|
|
73
|
+
// Provider ordering error patterns (tool_use/tool_result mismatches)
|
|
74
|
+
const ORDERING_ERROR_PATTERNS = [
|
|
75
|
+
/tool_result.*not immediately after.*tool_use/i,
|
|
76
|
+
/tool_use.*must have.*tool_result/i,
|
|
77
|
+
/tool_use_id.*without.*tool_result/i,
|
|
78
|
+
/tool_result.*tool_use_id.*not found/i,
|
|
79
|
+
/messages.*invalid.*order/i,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// Web-search-specific ordering error patterns
|
|
83
|
+
const WEB_SEARCH_ORDERING_PATTERNS = [
|
|
84
|
+
/web_search.*tool_use.*without/i,
|
|
85
|
+
/web_search.*tool_result/i,
|
|
86
|
+
];
|
|
87
|
+
|
|
71
88
|
// User-initiated cancellation patterns — these should NOT produce session_error
|
|
72
89
|
const CANCEL_PATTERNS = [/abort/i, /cancel/i];
|
|
73
90
|
|
|
@@ -131,6 +148,7 @@ export function classifySessionError(
|
|
|
131
148
|
userMessage: `Could not regenerate the response. ${base.userMessage}`,
|
|
132
149
|
retryable: true,
|
|
133
150
|
debugDetails,
|
|
151
|
+
errorCategory: `regenerate:${base.errorCategory}`,
|
|
134
152
|
};
|
|
135
153
|
}
|
|
136
154
|
|
|
@@ -155,8 +173,10 @@ function classifyCore(
|
|
|
155
173
|
if (error.statusCode === 413) {
|
|
156
174
|
return {
|
|
157
175
|
code: "CONTEXT_TOO_LARGE",
|
|
158
|
-
userMessage:
|
|
176
|
+
userMessage:
|
|
177
|
+
"This conversation is too long. Please start a new thread.",
|
|
159
178
|
retryable: false,
|
|
179
|
+
errorCategory: "context_too_large",
|
|
160
180
|
};
|
|
161
181
|
}
|
|
162
182
|
if (error.statusCode === 401) {
|
|
@@ -164,13 +184,15 @@ function classifyCore(
|
|
|
164
184
|
code: "PROVIDER_BILLING",
|
|
165
185
|
userMessage: "Your API key is invalid or expired.",
|
|
166
186
|
retryable: false,
|
|
187
|
+
errorCategory: "provider_billing",
|
|
167
188
|
};
|
|
168
189
|
}
|
|
169
190
|
if (error.statusCode === 429) {
|
|
170
191
|
return {
|
|
171
192
|
code: "PROVIDER_RATE_LIMIT",
|
|
172
|
-
userMessage: "The AI provider is
|
|
193
|
+
userMessage: "The AI provider is busy. Please try again in a moment.",
|
|
173
194
|
retryable: true,
|
|
195
|
+
errorCategory: "rate_limit",
|
|
174
196
|
};
|
|
175
197
|
}
|
|
176
198
|
if (error.statusCode >= 500) {
|
|
@@ -178,15 +200,35 @@ function classifyCore(
|
|
|
178
200
|
code: "PROVIDER_API",
|
|
179
201
|
userMessage: "The AI provider returned a server error.",
|
|
180
202
|
retryable: true,
|
|
203
|
+
errorCategory: "provider_server_error",
|
|
181
204
|
};
|
|
182
205
|
}
|
|
183
|
-
// 4xx (non-429) — check for context-too-large
|
|
206
|
+
// 4xx (non-429) — check for context-too-large, ordering errors, then generic fallback
|
|
184
207
|
if (error.statusCode >= 400) {
|
|
185
208
|
if (isContextTooLarge(message)) {
|
|
186
209
|
return {
|
|
187
210
|
code: "CONTEXT_TOO_LARGE",
|
|
188
|
-
userMessage:
|
|
211
|
+
userMessage:
|
|
212
|
+
"This conversation is too long. Please start a new thread.",
|
|
189
213
|
retryable: false,
|
|
214
|
+
errorCategory: "context_too_large",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (isWebSearchOrderingError(message)) {
|
|
218
|
+
return {
|
|
219
|
+
code: "PROVIDER_WEB_SEARCH",
|
|
220
|
+
userMessage:
|
|
221
|
+
"An internal error occurred with web search. Retrying...",
|
|
222
|
+
retryable: true,
|
|
223
|
+
errorCategory: "web_search_ordering",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (isOrderingError(message)) {
|
|
227
|
+
return {
|
|
228
|
+
code: "PROVIDER_ORDERING",
|
|
229
|
+
userMessage: "An internal error occurred. Retrying...",
|
|
230
|
+
retryable: true,
|
|
231
|
+
errorCategory: "tool_ordering",
|
|
190
232
|
};
|
|
191
233
|
}
|
|
192
234
|
if (/credit balance is too low|insufficient.*credits?/i.test(message)) {
|
|
@@ -194,6 +236,7 @@ function classifyCore(
|
|
|
194
236
|
code: "PROVIDER_BILLING",
|
|
195
237
|
userMessage: "Your API key has insufficient credits.",
|
|
196
238
|
retryable: false,
|
|
239
|
+
errorCategory: "provider_billing",
|
|
197
240
|
};
|
|
198
241
|
}
|
|
199
242
|
if (
|
|
@@ -205,12 +248,14 @@ function classifyCore(
|
|
|
205
248
|
code: "PROVIDER_BILLING",
|
|
206
249
|
userMessage: "Your API key is invalid.",
|
|
207
250
|
retryable: false,
|
|
251
|
+
errorCategory: "provider_billing",
|
|
208
252
|
};
|
|
209
253
|
}
|
|
210
254
|
return {
|
|
211
255
|
code: "PROVIDER_API",
|
|
212
256
|
userMessage: "The AI provider rejected the request.",
|
|
213
257
|
retryable: true,
|
|
258
|
+
errorCategory: "provider_api_error",
|
|
214
259
|
};
|
|
215
260
|
}
|
|
216
261
|
}
|
|
@@ -224,6 +269,16 @@ export function isContextTooLarge(message: string): boolean {
|
|
|
224
269
|
return CONTEXT_TOO_LARGE_PATTERNS.some((p) => p.test(message));
|
|
225
270
|
}
|
|
226
271
|
|
|
272
|
+
/** Check whether an error message indicates a web-search-specific ordering failure. */
|
|
273
|
+
export function isWebSearchOrderingError(message: string): boolean {
|
|
274
|
+
return WEB_SEARCH_ORDERING_PATTERNS.some((p) => p.test(message));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Check whether an error message indicates a tool_use/tool_result ordering failure. */
|
|
278
|
+
export function isOrderingError(message: string): boolean {
|
|
279
|
+
return ORDERING_ERROR_PATTERNS.some((p) => p.test(message));
|
|
280
|
+
}
|
|
281
|
+
|
|
227
282
|
function classifyByMessage(
|
|
228
283
|
message: string,
|
|
229
284
|
): Omit<ClassifiedSessionError, "debugDetails"> {
|
|
@@ -231,8 +286,9 @@ function classifyByMessage(
|
|
|
231
286
|
if (isContextTooLarge(message)) {
|
|
232
287
|
return {
|
|
233
288
|
code: "CONTEXT_TOO_LARGE",
|
|
234
|
-
userMessage: "This conversation
|
|
289
|
+
userMessage: "This conversation is too long. Please start a new thread.",
|
|
235
290
|
retryable: false,
|
|
291
|
+
errorCategory: "context_too_large",
|
|
236
292
|
};
|
|
237
293
|
}
|
|
238
294
|
|
|
@@ -241,12 +297,33 @@ function classifyByMessage(
|
|
|
241
297
|
if (pattern.test(message)) {
|
|
242
298
|
return {
|
|
243
299
|
code: "PROVIDER_RATE_LIMIT",
|
|
244
|
-
userMessage: "The AI provider is
|
|
300
|
+
userMessage: "The AI provider is busy. Please try again in a moment.",
|
|
245
301
|
retryable: true,
|
|
302
|
+
errorCategory: "rate_limit",
|
|
246
303
|
};
|
|
247
304
|
}
|
|
248
305
|
}
|
|
249
306
|
|
|
307
|
+
// Web-search ordering errors (before general ordering errors)
|
|
308
|
+
if (isWebSearchOrderingError(message)) {
|
|
309
|
+
return {
|
|
310
|
+
code: "PROVIDER_WEB_SEARCH",
|
|
311
|
+
userMessage: "An internal error occurred with web search. Retrying...",
|
|
312
|
+
retryable: true,
|
|
313
|
+
errorCategory: "web_search_ordering",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// General tool_use/tool_result ordering errors
|
|
318
|
+
if (isOrderingError(message)) {
|
|
319
|
+
return {
|
|
320
|
+
code: "PROVIDER_ORDERING",
|
|
321
|
+
userMessage: "An internal error occurred. Retrying...",
|
|
322
|
+
retryable: true,
|
|
323
|
+
errorCategory: "tool_ordering",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
250
327
|
// Network errors (before timeout so "connection timeout" is classified as network)
|
|
251
328
|
for (const pattern of NETWORK_PATTERNS) {
|
|
252
329
|
if (pattern.test(message)) {
|
|
@@ -254,6 +331,7 @@ function classifyByMessage(
|
|
|
254
331
|
code: "PROVIDER_NETWORK",
|
|
255
332
|
userMessage: "Could not connect to the AI provider.",
|
|
256
333
|
retryable: true,
|
|
334
|
+
errorCategory: "provider_network",
|
|
257
335
|
};
|
|
258
336
|
}
|
|
259
337
|
}
|
|
@@ -265,6 +343,7 @@ function classifyByMessage(
|
|
|
265
343
|
code: "PROVIDER_API",
|
|
266
344
|
userMessage: "The AI provider returned a server error.",
|
|
267
345
|
retryable: true,
|
|
346
|
+
errorCategory: "provider_server_error",
|
|
268
347
|
};
|
|
269
348
|
}
|
|
270
349
|
}
|
|
@@ -277,6 +356,7 @@ function classifyByMessage(
|
|
|
277
356
|
code: "PROVIDER_API",
|
|
278
357
|
userMessage: "The request to the AI provider timed out.",
|
|
279
358
|
retryable: true,
|
|
359
|
+
errorCategory: "provider_timeout",
|
|
280
360
|
};
|
|
281
361
|
}
|
|
282
362
|
}
|
|
@@ -288,6 +368,7 @@ function classifyByMessage(
|
|
|
288
368
|
code: "SESSION_ABORTED",
|
|
289
369
|
userMessage: "The request was interrupted.",
|
|
290
370
|
retryable: true,
|
|
371
|
+
errorCategory: "session_aborted",
|
|
291
372
|
};
|
|
292
373
|
}
|
|
293
374
|
}
|
|
@@ -308,6 +389,7 @@ function classifyByMessage(
|
|
|
308
389
|
code: "SESSION_PROCESSING_FAILED",
|
|
309
390
|
userMessage,
|
|
310
391
|
retryable: false,
|
|
392
|
+
errorCategory: "processing_failed",
|
|
311
393
|
};
|
|
312
394
|
}
|
|
313
395
|
|
|
@@ -325,5 +407,6 @@ export function buildSessionErrorMessage(
|
|
|
325
407
|
userMessage: classified.userMessage,
|
|
326
408
|
retryable: classified.retryable,
|
|
327
409
|
debugDetails: classified.debugDetails,
|
|
410
|
+
errorCategory: classified.errorCategory,
|
|
328
411
|
};
|
|
329
412
|
}
|
|
@@ -21,6 +21,14 @@ const log = getLogger("session-history");
|
|
|
21
21
|
|
|
22
22
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
23
23
|
|
|
24
|
+
function isToolResultBlock(
|
|
25
|
+
block: ContentBlock | Record<string, unknown>,
|
|
26
|
+
): boolean {
|
|
27
|
+
return (
|
|
28
|
+
block.type === "tool_result" || block.type === "web_search_tool_result"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
function isUndoableUserMessage(message: Message): boolean {
|
|
25
33
|
if (message.role !== "user") return false;
|
|
26
34
|
if (getSummaryFromContextMessage(message) != null) return false;
|
|
@@ -30,7 +38,7 @@ function isUndoableUserMessage(message: Message): boolean {
|
|
|
30
38
|
// (e.g. after repairHistory merges a tool_result turn with a user prompt) are still
|
|
31
39
|
// undoable because they contain real user content.
|
|
32
40
|
const hasNonToolResultContent = message.content.some(
|
|
33
|
-
(block) => block
|
|
41
|
+
(block) => !isToolResultBlock(block),
|
|
34
42
|
);
|
|
35
43
|
if (!hasNonToolResultContent) return false;
|
|
36
44
|
return true;
|
|
@@ -143,7 +151,9 @@ export function consolidateAssistantMessages(
|
|
|
143
151
|
const content = JSON.parse(msg.content);
|
|
144
152
|
const isToolResultOnly =
|
|
145
153
|
Array.isArray(content) &&
|
|
146
|
-
content.every((block
|
|
154
|
+
content.every((block: Record<string, unknown>) =>
|
|
155
|
+
isToolResultBlock(block),
|
|
156
|
+
) &&
|
|
147
157
|
content.length > 0;
|
|
148
158
|
if (isToolResultOnly) {
|
|
149
159
|
internalToolResultMessages.push(msg);
|
|
@@ -229,8 +239,8 @@ export function consolidateAssistantMessages(
|
|
|
229
239
|
try {
|
|
230
240
|
const content = JSON.parse(msg.content);
|
|
231
241
|
if (Array.isArray(content)) {
|
|
232
|
-
const toolResultBlocks = content.filter(
|
|
233
|
-
(b
|
|
242
|
+
const toolResultBlocks = content.filter((b: Record<string, unknown>) =>
|
|
243
|
+
isToolResultBlock(b),
|
|
234
244
|
);
|
|
235
245
|
log.info(
|
|
236
246
|
{
|
|
@@ -253,8 +263,8 @@ export function consolidateAssistantMessages(
|
|
|
253
263
|
const toolUseBlocksInConsolidated = consolidatedContent.filter(
|
|
254
264
|
(b) => b.type === "tool_use",
|
|
255
265
|
).length;
|
|
256
|
-
const toolResultBlocksInConsolidated = consolidatedContent.filter(
|
|
257
|
-
(b)
|
|
266
|
+
const toolResultBlocksInConsolidated = consolidatedContent.filter((b) =>
|
|
267
|
+
isToolResultBlock(b),
|
|
258
268
|
).length;
|
|
259
269
|
log.info(
|
|
260
270
|
{
|
|
@@ -471,7 +481,7 @@ export async function regenerate(
|
|
|
471
481
|
if (
|
|
472
482
|
Array.isArray(parsed) &&
|
|
473
483
|
parsed.length > 0 &&
|
|
474
|
-
parsed.every((b: Record<string, unknown>) => b
|
|
484
|
+
parsed.every((b: Record<string, unknown>) => isToolResultBlock(b))
|
|
475
485
|
) {
|
|
476
486
|
continue; // Skip tool_result-only user messages
|
|
477
487
|
}
|
|
@@ -69,7 +69,7 @@ export function stripMediaPayloadsForRetry(messages: Message[]): {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
if (
|
|
72
|
-
block.type === "tool_result" &&
|
|
72
|
+
block.type === "tool_result" && // guard:allow-tool-result-only — web_search_tool_result has no contentBlocks
|
|
73
73
|
block.contentBlocks &&
|
|
74
74
|
block.contentBlocks.length > 0
|
|
75
75
|
) {
|
|
@@ -143,7 +143,10 @@ function fileBlockToStub(
|
|
|
143
143
|
function isToolResultOnlyMessage(message: Message): boolean {
|
|
144
144
|
return (
|
|
145
145
|
message.content.length > 0 &&
|
|
146
|
-
message.content.every(
|
|
146
|
+
message.content.every(
|
|
147
|
+
(block) =>
|
|
148
|
+
block.type === "tool_result" || block.type === "web_search_tool_result",
|
|
149
|
+
)
|
|
147
150
|
);
|
|
148
151
|
}
|
|
149
152
|
|
|
@@ -158,6 +161,7 @@ export function countMediaBlocks(messages: Message[]): number {
|
|
|
158
161
|
if (block.type === "image" || block.type === "file") {
|
|
159
162
|
count++;
|
|
160
163
|
} else if (block.type === "tool_result" && block.contentBlocks) {
|
|
164
|
+
// guard:allow-tool-result-only — web_search_tool_result has no contentBlocks
|
|
161
165
|
for (const cb of block.contentBlocks) {
|
|
162
166
|
if (cb.type === "image" || cb.type === "file") {
|
|
163
167
|
count++;
|
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
2
|
import { estimatePromptTokens } from "../context/token-estimator.js";
|
|
3
|
-
import { getMemoryConflictAndCleanupStats } from "../memory/admin.js";
|
|
4
|
-
import { compileDynamicProfile } from "../memory/profile-compiler.js";
|
|
5
3
|
import { buildMemoryQuery } from "../memory/query-builder.js";
|
|
6
4
|
import { computeRecallBudget } from "../memory/retrieval-budget.js";
|
|
7
5
|
import {
|
|
8
6
|
buildMemoryRecall,
|
|
9
7
|
injectMemoryRecallAsSeparateMessage,
|
|
10
|
-
injectMemoryRecallIntoUserMessage,
|
|
11
8
|
} from "../memory/retriever.js";
|
|
12
9
|
import type { ScopePolicyOverride } from "../memory/search/types.js";
|
|
13
10
|
import type { Message } from "../providers/types.js";
|
|
14
11
|
import type { Provider } from "../providers/types.js";
|
|
15
12
|
import type { ServerMessage } from "./message-protocol.js";
|
|
16
|
-
import type { ConflictGate } from "./session-conflict-gate.js";
|
|
17
|
-
import { injectDynamicProfileIntoUserMessage } from "./session-dynamic-profile.js";
|
|
18
|
-
|
|
19
|
-
export type RecallInjectionStrategy =
|
|
20
|
-
| "prepend_user_block"
|
|
21
|
-
| "separate_context_message";
|
|
22
13
|
|
|
23
14
|
export interface MemoryRecallResult {
|
|
24
15
|
runMessages: Message[];
|
|
25
16
|
recall: Awaited<ReturnType<typeof buildMemoryRecall>>;
|
|
26
|
-
dynamicProfile: { text: string };
|
|
27
|
-
recallInjectionStrategy: RecallInjectionStrategy;
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
export interface MemoryPrepareContext {
|
|
@@ -32,12 +21,9 @@ export interface MemoryPrepareContext {
|
|
|
32
21
|
messages: Message[];
|
|
33
22
|
systemPrompt: string;
|
|
34
23
|
provider: Provider;
|
|
35
|
-
conflictGate: ConflictGate;
|
|
36
24
|
scopeId: string;
|
|
37
25
|
includeDefaultFallback: boolean;
|
|
38
26
|
trustClass: "guardian" | "trusted_contact" | "unknown";
|
|
39
|
-
/** When false (e.g. scheduled tasks), skip conflict gate evaluation. */
|
|
40
|
-
isInteractive?: boolean;
|
|
41
27
|
}
|
|
42
28
|
|
|
43
29
|
/**
|
|
@@ -48,14 +34,43 @@ function isToolResultOnlyUserTurn(message: Message | undefined): boolean {
|
|
|
48
34
|
return (
|
|
49
35
|
message?.role === "user" &&
|
|
50
36
|
message.content.length > 0 &&
|
|
51
|
-
message.content.every(
|
|
37
|
+
message.content.every(
|
|
38
|
+
(block) =>
|
|
39
|
+
block.type === "tool_result" || block.type === "web_search_tool_result",
|
|
40
|
+
)
|
|
52
41
|
);
|
|
53
42
|
}
|
|
54
43
|
|
|
55
44
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
45
|
+
* Fast gate that determines whether the current turn warrants memory
|
|
46
|
+
* retrieval. Returns `false` for mechanical no-ops (empty content,
|
|
47
|
+
* tool-result-only) so the full memory pipeline can be skipped.
|
|
48
|
+
* Runs in microseconds — no external calls.
|
|
49
|
+
*
|
|
50
|
+
* Note: We intentionally avoid character-length heuristics here.
|
|
51
|
+
* Short messages like "What did I say?" or "My preferences?" are
|
|
52
|
+
* legitimate memory queries. Per AGENTS.md, judgement calls about
|
|
53
|
+
* message value should be routed through the daemon, not hardcoded.
|
|
54
|
+
*/
|
|
55
|
+
export function needsMemory(messages: Message[], content: string): boolean {
|
|
56
|
+
// Empty or whitespace-only content — mechanical validation, nothing to query
|
|
57
|
+
if (!content || content.trim().length === 0) return false;
|
|
58
|
+
|
|
59
|
+
// Tool-result-only turns (assistant tool loop)
|
|
60
|
+
const latestMessage = messages[messages.length - 1];
|
|
61
|
+
if (isToolResultOnlyUserTurn(latestMessage)) return false;
|
|
62
|
+
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build memory recall for a single agent loop turn using the V2 hybrid
|
|
68
|
+
* pipeline. Returns the augmented run messages and metadata for
|
|
69
|
+
* downstream event emission.
|
|
70
|
+
*
|
|
71
|
+
* The V2 pipeline always uses `separate_context_message` injection
|
|
72
|
+
* strategy (user + assistant ack pair). When injection text is empty,
|
|
73
|
+
* no synthetic messages are added.
|
|
59
74
|
*/
|
|
60
75
|
export async function prepareMemoryContext(
|
|
61
76
|
ctx: MemoryPrepareContext,
|
|
@@ -65,104 +80,42 @@ export async function prepareMemoryContext(
|
|
|
65
80
|
onEvent: (msg: ServerMessage) => void,
|
|
66
81
|
): Promise<MemoryRecallResult> {
|
|
67
82
|
// Provenance-based trust gating: untrusted actors skip all memory operations
|
|
68
|
-
//
|
|
69
|
-
// influencing memory-augmented responses.
|
|
83
|
+
// to prevent untrusted content from influencing memory-augmented responses.
|
|
70
84
|
const isTrustedActor = ctx.trustClass === "guardian";
|
|
71
85
|
|
|
86
|
+
// Build a no-op result that skips the entire memory pipeline.
|
|
87
|
+
const noopResult = (): MemoryRecallResult => ({
|
|
88
|
+
runMessages: ctx.messages,
|
|
89
|
+
recall: {
|
|
90
|
+
enabled: false,
|
|
91
|
+
degraded: false,
|
|
92
|
+
injectedText: "",
|
|
93
|
+
semanticHits: 0,
|
|
94
|
+
recencyHits: 0,
|
|
95
|
+
mergedCount: 0,
|
|
96
|
+
selectedCount: 0,
|
|
97
|
+
injectedTokens: 0,
|
|
98
|
+
latencyMs: 0,
|
|
99
|
+
topCandidates: [],
|
|
100
|
+
tier1Count: 0,
|
|
101
|
+
tier2Count: 0,
|
|
102
|
+
} as Awaited<ReturnType<typeof buildMemoryRecall>>,
|
|
103
|
+
});
|
|
104
|
+
|
|
72
105
|
if (!isTrustedActor) {
|
|
73
|
-
return
|
|
74
|
-
runMessages: ctx.messages,
|
|
75
|
-
recall: {
|
|
76
|
-
enabled: false,
|
|
77
|
-
degraded: false,
|
|
78
|
-
injectedText: "",
|
|
79
|
-
lexicalHits: 0,
|
|
80
|
-
semanticHits: 0,
|
|
81
|
-
recencyHits: 0,
|
|
82
|
-
entityHits: 0,
|
|
83
|
-
relationSeedEntityCount: 0,
|
|
84
|
-
relationTraversedEdgeCount: 0,
|
|
85
|
-
relationNeighborEntityCount: 0,
|
|
86
|
-
relationExpandedItemCount: 0,
|
|
87
|
-
earlyTerminated: false,
|
|
88
|
-
mergedCount: 0,
|
|
89
|
-
selectedCount: 0,
|
|
90
|
-
rerankApplied: false,
|
|
91
|
-
injectedTokens: 0,
|
|
92
|
-
latencyMs: 0,
|
|
93
|
-
topCandidates: [],
|
|
94
|
-
} as Awaited<ReturnType<typeof buildMemoryRecall>>,
|
|
95
|
-
dynamicProfile: { text: "" },
|
|
96
|
-
recallInjectionStrategy: "prepend_user_block",
|
|
97
|
-
};
|
|
106
|
+
return noopResult();
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const latestMessage = ctx.messages[ctx.messages.length - 1];
|
|
105
|
-
if (isToolResultOnlyUserTurn(latestMessage)) {
|
|
106
|
-
return {
|
|
107
|
-
runMessages: ctx.messages,
|
|
108
|
-
recall: {
|
|
109
|
-
enabled: false,
|
|
110
|
-
degraded: false,
|
|
111
|
-
injectedText: "",
|
|
112
|
-
lexicalHits: 0,
|
|
113
|
-
semanticHits: 0,
|
|
114
|
-
recencyHits: 0,
|
|
115
|
-
entityHits: 0,
|
|
116
|
-
relationSeedEntityCount: 0,
|
|
117
|
-
relationTraversedEdgeCount: 0,
|
|
118
|
-
relationNeighborEntityCount: 0,
|
|
119
|
-
relationExpandedItemCount: 0,
|
|
120
|
-
earlyTerminated: false,
|
|
121
|
-
mergedCount: 0,
|
|
122
|
-
selectedCount: 0,
|
|
123
|
-
rerankApplied: false,
|
|
124
|
-
injectedTokens: 0,
|
|
125
|
-
latencyMs: 0,
|
|
126
|
-
topCandidates: [],
|
|
127
|
-
} as Awaited<ReturnType<typeof buildMemoryRecall>>,
|
|
128
|
-
dynamicProfile: { text: "" },
|
|
129
|
-
recallInjectionStrategy: "prepend_user_block",
|
|
130
|
-
};
|
|
109
|
+
// Gate: skip the entire memory pipeline for mechanical no-ops (empty
|
|
110
|
+
// content, tool-result-only turns).
|
|
111
|
+
if (!needsMemory(ctx.messages, content)) {
|
|
112
|
+
return noopResult();
|
|
131
113
|
}
|
|
132
114
|
|
|
133
115
|
const runtimeConfig = getConfig();
|
|
134
|
-
const memoryEnabled = runtimeConfig.memory?.enabled !== false;
|
|
135
116
|
|
|
136
|
-
//
|
|
137
|
-
// but do not return any user-facing payload. Non-interactive sessions skip
|
|
138
|
-
// entirely since there is no human context for conflict evaluation.
|
|
139
|
-
const isInteractive = ctx.isInteractive !== false;
|
|
140
|
-
const conflictConfig =
|
|
141
|
-
memoryEnabled && isInteractive
|
|
142
|
-
? runtimeConfig.memory?.conflicts
|
|
143
|
-
: undefined;
|
|
144
|
-
if (conflictConfig) {
|
|
145
|
-
await ctx.conflictGate.evaluate(content, conflictConfig, ctx.scopeId);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Dynamic profile
|
|
149
|
-
const profileConfig = memoryEnabled
|
|
150
|
-
? runtimeConfig.memory?.profile
|
|
151
|
-
: undefined;
|
|
152
|
-
const dynamicProfile = profileConfig?.enabled
|
|
153
|
-
? compileDynamicProfile({
|
|
154
|
-
scopeId: ctx.scopeId,
|
|
155
|
-
includeDefaultFallback: ctx.includeDefaultFallback,
|
|
156
|
-
maxInjectTokensOverride: profileConfig.maxInjectTokens,
|
|
157
|
-
})
|
|
158
|
-
: { text: "" };
|
|
159
|
-
|
|
160
|
-
// Memory recall
|
|
117
|
+
// Memory recall via the V2 hybrid pipeline
|
|
161
118
|
const recallQuery = buildMemoryQuery(content, ctx.messages);
|
|
162
|
-
const recallInjectionStrategy: RecallInjectionStrategy =
|
|
163
|
-
(runtimeConfig.memory?.retrieval?.injectionStrategy as
|
|
164
|
-
| RecallInjectionStrategy
|
|
165
|
-
| undefined) ?? "prepend_user_block";
|
|
166
119
|
const dynamicBudgetConfig = runtimeConfig.memory?.retrieval?.dynamicBudget;
|
|
167
120
|
const recallBudget = dynamicBudgetConfig?.enabled
|
|
168
121
|
? computeRecallBudget({
|
|
@@ -196,8 +149,6 @@ export async function prepareMemoryContext(
|
|
|
196
149
|
scopePolicyOverride,
|
|
197
150
|
},
|
|
198
151
|
);
|
|
199
|
-
const memoryStatus = getMemoryConflictAndCleanupStats();
|
|
200
|
-
|
|
201
152
|
onEvent({
|
|
202
153
|
type: "memory_status",
|
|
203
154
|
enabled: recall.enabled,
|
|
@@ -212,32 +163,18 @@ export async function prepareMemoryContext(
|
|
|
212
163
|
reason: recall.reason,
|
|
213
164
|
provider: recall.provider,
|
|
214
165
|
model: recall.model,
|
|
215
|
-
conflictsPending: memoryStatus.conflicts.pending,
|
|
216
|
-
conflictsResolved: memoryStatus.conflicts.resolved,
|
|
217
|
-
oldestPendingConflictAgeMs: memoryStatus.conflicts.oldestPendingAgeMs,
|
|
218
|
-
cleanupResolvedJobsPending: memoryStatus.cleanup.resolvedBacklog,
|
|
219
|
-
cleanupSupersededJobsPending: memoryStatus.cleanup.supersededBacklog,
|
|
220
|
-
cleanupResolvedJobsCompleted24h: memoryStatus.cleanup.resolvedCompleted24h,
|
|
221
|
-
cleanupSupersededJobsCompleted24h:
|
|
222
|
-
memoryStatus.cleanup.supersededCompleted24h,
|
|
223
166
|
});
|
|
224
167
|
|
|
225
|
-
// Inject recall into messages
|
|
168
|
+
// Inject recall into messages using separate_context_message strategy.
|
|
169
|
+
// When injection text is empty, skip injection entirely (no synthetic messages).
|
|
226
170
|
let runMessages = ctx.messages;
|
|
227
171
|
if (recall.injectedText.length > 0) {
|
|
228
172
|
const userTail = ctx.messages[ctx.messages.length - 1];
|
|
229
173
|
if (userTail && userTail.role === "user") {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
);
|
|
235
|
-
} else {
|
|
236
|
-
runMessages = [
|
|
237
|
-
...ctx.messages.slice(0, -1),
|
|
238
|
-
injectMemoryRecallIntoUserMessage(userTail, recall.injectedText),
|
|
239
|
-
];
|
|
240
|
-
}
|
|
174
|
+
runMessages = injectMemoryRecallAsSeparateMessage(
|
|
175
|
+
ctx.messages,
|
|
176
|
+
recall.injectedText,
|
|
177
|
+
);
|
|
241
178
|
onEvent({
|
|
242
179
|
type: "memory_recalled",
|
|
243
180
|
provider: recall.provider ?? "unknown",
|
|
@@ -249,18 +186,14 @@ export async function prepareMemoryContext(
|
|
|
249
186
|
fallbackSources: [...recall.degradation.fallbackSources],
|
|
250
187
|
}
|
|
251
188
|
: undefined,
|
|
252
|
-
lexicalHits: recall.lexicalHits,
|
|
253
189
|
semanticHits: recall.semanticHits,
|
|
254
190
|
recencyHits: recall.recencyHits,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
relationExpandedItemCount: recall.relationExpandedItemCount,
|
|
260
|
-
earlyTerminated: recall.earlyTerminated,
|
|
191
|
+
tier1Count: recall.tier1Count ?? 0,
|
|
192
|
+
tier2Count: recall.tier2Count ?? 0,
|
|
193
|
+
hybridSearchLatencyMs: recall.hybridSearchMs ?? 0,
|
|
194
|
+
sparseVectorUsed: recall.sparseVectorUsed ?? false,
|
|
261
195
|
mergedCount: recall.mergedCount,
|
|
262
196
|
selectedCount: recall.selectedCount,
|
|
263
|
-
rerankApplied: recall.rerankApplied,
|
|
264
197
|
injectedTokens: recall.injectedTokens,
|
|
265
198
|
latencyMs: recall.latencyMs,
|
|
266
199
|
topCandidates: recall.topCandidates,
|
|
@@ -268,21 +201,8 @@ export async function prepareMemoryContext(
|
|
|
268
201
|
}
|
|
269
202
|
}
|
|
270
203
|
|
|
271
|
-
// Inject dynamic profile
|
|
272
|
-
if (dynamicProfile.text.length > 0) {
|
|
273
|
-
const userTail = runMessages[runMessages.length - 1];
|
|
274
|
-
if (userTail && userTail.role === "user") {
|
|
275
|
-
runMessages = [
|
|
276
|
-
...runMessages.slice(0, -1),
|
|
277
|
-
injectDynamicProfileIntoUserMessage(userTail, dynamicProfile.text),
|
|
278
|
-
];
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
204
|
return {
|
|
283
205
|
runMessages,
|
|
284
206
|
recall,
|
|
285
|
-
dynamicProfile,
|
|
286
|
-
recallInjectionStrategy,
|
|
287
207
|
};
|
|
288
208
|
}
|
|
@@ -92,6 +92,8 @@ export interface ProcessSessionContext {
|
|
|
92
92
|
readonly usageStats: UsageStats;
|
|
93
93
|
/** Request-scoped skill IDs preactivated via slash resolution. */
|
|
94
94
|
preactivatedSkillIds?: string[];
|
|
95
|
+
/** Add a skill ID to the preactivated set without replacing existing entries. */
|
|
96
|
+
addPreactivatedSkillId(id: string): void;
|
|
95
97
|
/** Assistant identity — used for scoping notification preferences. */
|
|
96
98
|
readonly assistantId?: string;
|
|
97
99
|
trustContext?: TrustContext;
|
|
@@ -140,7 +142,8 @@ export interface ProcessSessionContext {
|
|
|
140
142
|
| "message_complete"
|
|
141
143
|
| "generation_cancelled"
|
|
142
144
|
| "error_terminal"
|
|
143
|
-
| "preview_start"
|
|
145
|
+
| "preview_start"
|
|
146
|
+
| "context_compacting",
|
|
144
147
|
anchor?: "assistant_turn" | "user_turn" | "global",
|
|
145
148
|
requestId?: string,
|
|
146
149
|
statusText?: string,
|
|
@@ -224,6 +227,11 @@ export async function drainQueue(
|
|
|
224
227
|
const next = session.queue.shift();
|
|
225
228
|
if (!next) return;
|
|
226
229
|
|
|
230
|
+
// Reset per-turn preactivation so a prior iteration (e.g. an unknown-slash
|
|
231
|
+
// from a desktop source that skips runAgentLoop) can't leak CU preactivation
|
|
232
|
+
// into the next queued message.
|
|
233
|
+
session.preactivatedSkillIds = undefined;
|
|
234
|
+
|
|
227
235
|
log.info(
|
|
228
236
|
{
|
|
229
237
|
conversationId: session.conversationId,
|
|
@@ -283,6 +291,7 @@ export async function drainQueue(
|
|
|
283
291
|
const sourceInterface = interfaceCtx?.userMessageInterface;
|
|
284
292
|
if (sourceInterface === "macos" || sourceInterface === "ios") {
|
|
285
293
|
session.restoreProxyAvailability();
|
|
294
|
+
session.addPreactivatedSkillId("computer-use");
|
|
286
295
|
}
|
|
287
296
|
}
|
|
288
297
|
|