@vellumai/assistant 0.6.0 → 0.6.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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/openapi.yaml +538 -3
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/checker.test.ts +38 -6
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +72 -105
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +62 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/agent/loop.ts +6 -0
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/cli/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/connect.ts +14 -5
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
- package/src/config/bundled-skills/gmail/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +158 -65
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +300 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +18 -0
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +63 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +199 -157
- package/src/daemon/message-types/conversations.ts +25 -6
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +89 -9
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +136 -107
- package/src/memory/conversation-group-migration.ts +1 -1
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -15
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/permissions/checker.ts +12 -1
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -0
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -28,7 +28,10 @@ import { handleUserMessageSignal } from "../signals/user-message.js";
|
|
|
28
28
|
import { DebouncerMap } from "../util/debounce.js";
|
|
29
29
|
import { getLogger } from "../util/logger.js";
|
|
30
30
|
import {
|
|
31
|
+
AVATAR_IMAGE_FILENAME,
|
|
32
|
+
getAvatarDir,
|
|
31
33
|
getSignalsDir,
|
|
34
|
+
getSoundsDir,
|
|
32
35
|
getWorkspaceDir,
|
|
33
36
|
getWorkspaceSkillsDir,
|
|
34
37
|
} from "../util/platform.js";
|
|
@@ -110,7 +113,12 @@ export class ConfigWatcher {
|
|
|
110
113
|
* files change and conversations need to be evicted for reload.
|
|
111
114
|
* `onIdentityChanged` is called when IDENTITY.md changes on disk.
|
|
112
115
|
*/
|
|
113
|
-
start(
|
|
116
|
+
start(
|
|
117
|
+
onConversationEvict: () => void,
|
|
118
|
+
onIdentityChanged?: () => void,
|
|
119
|
+
onSoundsConfigChanged?: () => void,
|
|
120
|
+
onAvatarChanged?: () => void,
|
|
121
|
+
): void {
|
|
114
122
|
const workspaceDir = getWorkspaceDir();
|
|
115
123
|
|
|
116
124
|
const workspaceHandlers: Record<string, () => void> = {
|
|
@@ -175,6 +183,13 @@ export class ConfigWatcher {
|
|
|
175
183
|
"workspace directory for config/prompt changes",
|
|
176
184
|
);
|
|
177
185
|
|
|
186
|
+
if (onSoundsConfigChanged) {
|
|
187
|
+
this.startSoundsWatcher(onSoundsConfigChanged);
|
|
188
|
+
}
|
|
189
|
+
if (onAvatarChanged) {
|
|
190
|
+
this.startAvatarWatcher(onAvatarChanged);
|
|
191
|
+
}
|
|
192
|
+
|
|
178
193
|
this.startFeatureFlagsWatcher();
|
|
179
194
|
this.startSignalsWatcher();
|
|
180
195
|
this.startSkillsWatchers(onConversationEvict);
|
|
@@ -188,6 +203,69 @@ export class ConfigWatcher {
|
|
|
188
203
|
this.watchers = [];
|
|
189
204
|
}
|
|
190
205
|
|
|
206
|
+
private startSoundsWatcher(onSoundsConfigChanged: () => void): void {
|
|
207
|
+
const soundsDir = getSoundsDir();
|
|
208
|
+
try {
|
|
209
|
+
if (!existsSync(soundsDir)) {
|
|
210
|
+
mkdirSync(soundsDir, { recursive: true });
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
// If we can't create it, watching will also fail — handled below.
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const watcher = watch(soundsDir, (_eventType, filename) => {
|
|
218
|
+
if (!filename) return;
|
|
219
|
+
this.debounceTimers.schedule("file:sounds", () => {
|
|
220
|
+
log.info(
|
|
221
|
+
{ file: String(filename) },
|
|
222
|
+
"Sounds directory changed, notifying clients",
|
|
223
|
+
);
|
|
224
|
+
onSoundsConfigChanged();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
this.watchers.push(watcher);
|
|
228
|
+
log.info({ dir: soundsDir }, "Watching sounds directory for changes");
|
|
229
|
+
} catch (err) {
|
|
230
|
+
log.warn(
|
|
231
|
+
{ err, dir: soundsDir },
|
|
232
|
+
"Failed to watch sounds directory. Sound config changes will require a restart.",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private startAvatarWatcher(onAvatarChanged: () => void): void {
|
|
238
|
+
const avatarDir = getAvatarDir();
|
|
239
|
+
try {
|
|
240
|
+
if (!existsSync(avatarDir)) {
|
|
241
|
+
mkdirSync(avatarDir, { recursive: true });
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
// If we can't create it, watching will also fail — handled below.
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const watcher = watch(avatarDir, (_eventType, filename) => {
|
|
249
|
+
if (!filename) return;
|
|
250
|
+
if (String(filename) !== AVATAR_IMAGE_FILENAME) return;
|
|
251
|
+
this.debounceTimers.schedule("file:avatar", () => {
|
|
252
|
+
log.info(
|
|
253
|
+
{ file: String(filename) },
|
|
254
|
+
"Avatar image changed, notifying clients",
|
|
255
|
+
);
|
|
256
|
+
onAvatarChanged();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
this.watchers.push(watcher);
|
|
260
|
+
log.info({ dir: avatarDir }, "Watching avatar directory for changes");
|
|
261
|
+
} catch (err) {
|
|
262
|
+
log.warn(
|
|
263
|
+
{ err, dir: avatarDir },
|
|
264
|
+
"Failed to watch avatar directory. Avatar changes will require a restart.",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
191
269
|
private startFeatureFlagsWatcher(): void {
|
|
192
270
|
const protectedDir = process.env.GATEWAY_SECURITY_DIR
|
|
193
271
|
? process.env.GATEWAY_SECURITY_DIR
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../memory/llm-request-log-store.js";
|
|
28
28
|
import { backfillMemoryRecallLogMessageId } from "../memory/memory-recall-log-store.js";
|
|
29
29
|
import type { ContentBlock, ImageContent } from "../providers/types.js";
|
|
30
|
+
import { ProviderError } from "../util/errors.js";
|
|
30
31
|
import { getLogger } from "../util/logger.js";
|
|
31
32
|
import type { DirectiveRequest } from "./assistant-attachments.js";
|
|
32
33
|
import {
|
|
@@ -630,6 +631,25 @@ export function handleError(
|
|
|
630
631
|
state.orderingErrorDetected = true;
|
|
631
632
|
state.deferredOrderingError = event.error.message;
|
|
632
633
|
} else {
|
|
634
|
+
if (classified.errorCategory === "provider_api_error") {
|
|
635
|
+
log.error(
|
|
636
|
+
{
|
|
637
|
+
conversationId: deps.ctx.conversationId,
|
|
638
|
+
errorCode: classified.code,
|
|
639
|
+
errorCategory: classified.errorCategory,
|
|
640
|
+
statusCode:
|
|
641
|
+
event.error instanceof ProviderError
|
|
642
|
+
? event.error.statusCode
|
|
643
|
+
: undefined,
|
|
644
|
+
provider:
|
|
645
|
+
event.error instanceof ProviderError
|
|
646
|
+
? event.error.provider
|
|
647
|
+
: undefined,
|
|
648
|
+
errorMessage: event.error.message,
|
|
649
|
+
},
|
|
650
|
+
"Provider rejected request with unclassified 4xx error",
|
|
651
|
+
);
|
|
652
|
+
}
|
|
633
653
|
deps.onEvent(
|
|
634
654
|
buildConversationErrorMessage(deps.ctx.conversationId, classified),
|
|
635
655
|
);
|
|
@@ -44,10 +44,7 @@ import {
|
|
|
44
44
|
updateConversationTitle,
|
|
45
45
|
updateMessageMetadata,
|
|
46
46
|
} from "../memory/conversation-crud.js";
|
|
47
|
-
import {
|
|
48
|
-
rebuildConversationDiskViewFromDbState,
|
|
49
|
-
syncMessageToDisk,
|
|
50
|
-
} from "../memory/conversation-disk-view.js";
|
|
47
|
+
import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
|
|
51
48
|
import {
|
|
52
49
|
isReplaceableTitle,
|
|
53
50
|
queueGenerateConversationTitle,
|
|
@@ -91,30 +88,30 @@ import {
|
|
|
91
88
|
classifyConversationError,
|
|
92
89
|
isUserCancellation,
|
|
93
90
|
} from "./conversation-error.js";
|
|
94
|
-
import { consolidateAssistantMessages } from "./conversation-history.js";
|
|
95
91
|
import { raceWithTimeout } from "./conversation-media-retry.js";
|
|
96
92
|
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
97
93
|
import type { QueueDrainReason } from "./conversation-queue-manager.js";
|
|
98
94
|
import type {
|
|
99
95
|
ActiveSurfaceContext,
|
|
100
96
|
ChannelCapabilities,
|
|
101
|
-
ChannelTurnContextParams,
|
|
102
97
|
InboundActorContext,
|
|
103
98
|
InjectionMode,
|
|
104
|
-
InterfaceTurnContextParams,
|
|
105
99
|
TrustContext,
|
|
106
100
|
} from "./conversation-runtime-assembly.js";
|
|
107
101
|
import {
|
|
108
102
|
applyRuntimeInjections,
|
|
103
|
+
buildUnifiedTurnContextBlock,
|
|
104
|
+
findLastInjectedNowContent,
|
|
109
105
|
inboundActorContextFromTrust,
|
|
110
106
|
inboundActorContextFromTrustContext,
|
|
111
107
|
readNowScratchpad,
|
|
112
|
-
|
|
108
|
+
readPkbContext,
|
|
109
|
+
stripInjectionsForCompaction,
|
|
113
110
|
} from "./conversation-runtime-assembly.js";
|
|
114
111
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
115
112
|
import { resolveTrustClass } from "./conversation-tool-setup.js";
|
|
116
113
|
import { recordUsage } from "./conversation-usage.js";
|
|
117
|
-
import {
|
|
114
|
+
import { formatTurnTimestamp } from "./date-context.js";
|
|
118
115
|
import { deepRepairHistory, repairHistory } from "./history-repair.js";
|
|
119
116
|
import type {
|
|
120
117
|
DynamicPageSurfaceData,
|
|
@@ -123,6 +120,7 @@ import type {
|
|
|
123
120
|
SurfaceType,
|
|
124
121
|
UsageStats,
|
|
125
122
|
} from "./message-protocol.js";
|
|
123
|
+
import type { MemoryRecalled } from "./message-types/memory.js";
|
|
126
124
|
import type { TraceEmitter } from "./trace-emitter.js";
|
|
127
125
|
|
|
128
126
|
const log = getLogger("conversation-agent-loop");
|
|
@@ -502,6 +500,7 @@ export async function runAgentLoopImpl(
|
|
|
502
500
|
}
|
|
503
501
|
|
|
504
502
|
const isFirstMessage = ctx.messages.length === 1;
|
|
503
|
+
let shouldInjectWorkspace = isFirstMessage;
|
|
505
504
|
|
|
506
505
|
const compactCheck = ctx.contextWindowManager.shouldCompact(ctx.messages);
|
|
507
506
|
if (compactCheck.needed) {
|
|
@@ -556,6 +555,7 @@ export async function runAgentLoopImpl(
|
|
|
556
555
|
compacted.summaryCacheReadInputTokens ?? 0,
|
|
557
556
|
collapseRawResponses(compacted.summaryRawResponses),
|
|
558
557
|
);
|
|
558
|
+
shouldInjectWorkspace = true;
|
|
559
559
|
}
|
|
560
560
|
|
|
561
561
|
const state = createEventHandlerState();
|
|
@@ -599,8 +599,8 @@ export async function runAgentLoopImpl(
|
|
|
599
599
|
|
|
600
600
|
let runMessages = ctx.messages;
|
|
601
601
|
|
|
602
|
-
// Memory graph retrieval — dispatches to context-load / per-turn
|
|
603
|
-
//
|
|
602
|
+
// Memory graph retrieval — dispatches to context-load / per-turn based on
|
|
603
|
+
// conversation state.
|
|
604
604
|
const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
|
|
605
605
|
if (isTrustedActor) {
|
|
606
606
|
const graphResult = await ctx.graphMemory.prepareMemory(
|
|
@@ -627,26 +627,65 @@ export async function runAgentLoopImpl(
|
|
|
627
627
|
}
|
|
628
628
|
}
|
|
629
629
|
|
|
630
|
+
const m = graphResult.metrics;
|
|
631
|
+
|
|
630
632
|
try {
|
|
631
633
|
recordMemoryRecallLog({
|
|
632
634
|
conversationId: ctx.conversationId,
|
|
633
635
|
enabled: true,
|
|
634
636
|
degraded: false,
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
637
|
+
provider: m?.embeddingProvider ?? undefined,
|
|
638
|
+
model: m?.embeddingModel ?? undefined,
|
|
639
|
+
semanticHits: m?.semanticHits ?? 0,
|
|
640
|
+
mergedCount: m?.mergedCount ?? 0,
|
|
641
|
+
selectedCount: m?.selectedCount ?? 0,
|
|
642
|
+
tier1Count: m?.tier1Count ?? 0,
|
|
643
|
+
tier2Count: m?.tier2Count ?? 0,
|
|
644
|
+
hybridSearchLatencyMs: m?.hybridSearchLatencyMs ?? 0,
|
|
645
|
+
sparseVectorUsed: m?.sparseVectorUsed ?? false,
|
|
642
646
|
injectedTokens: graphResult.injectedTokens,
|
|
643
647
|
latencyMs: graphResult.latencyMs,
|
|
644
|
-
topCandidatesJson: []
|
|
648
|
+
topCandidatesJson: (m?.topCandidates ?? []).map((c) => ({
|
|
649
|
+
key: c.nodeId,
|
|
650
|
+
type: c.type,
|
|
651
|
+
kind: "graph",
|
|
652
|
+
finalScore: c.score,
|
|
653
|
+
semantic: c.semanticSimilarity,
|
|
654
|
+
recency: c.recencyBoost,
|
|
655
|
+
})),
|
|
656
|
+
injectedText: graphResult.injectedBlockText ?? undefined,
|
|
645
657
|
reason: `graph:${graphResult.mode}`,
|
|
658
|
+
queryContext: m?.queryContext ?? undefined,
|
|
646
659
|
});
|
|
647
660
|
} catch (err) {
|
|
648
661
|
log.warn({ err }, "Failed to persist memory recall log (non-fatal)");
|
|
649
662
|
}
|
|
663
|
+
|
|
664
|
+
if (m) {
|
|
665
|
+
const memoryRecalledEvent: MemoryRecalled = {
|
|
666
|
+
type: "memory_recalled",
|
|
667
|
+
provider: m.embeddingProvider ?? "unknown",
|
|
668
|
+
model: m.embeddingModel ?? "unknown",
|
|
669
|
+
semanticHits: m.semanticHits,
|
|
670
|
+
mergedCount: m.mergedCount,
|
|
671
|
+
selectedCount: m.selectedCount,
|
|
672
|
+
tier1Count: m.tier1Count,
|
|
673
|
+
tier2Count: m.tier2Count,
|
|
674
|
+
hybridSearchLatencyMs: m.hybridSearchLatencyMs,
|
|
675
|
+
sparseVectorUsed: m.sparseVectorUsed,
|
|
676
|
+
injectedTokens: graphResult.injectedTokens,
|
|
677
|
+
latencyMs: graphResult.latencyMs,
|
|
678
|
+
topCandidates: m.topCandidates.map((c) => ({
|
|
679
|
+
key: c.nodeId,
|
|
680
|
+
type: c.type,
|
|
681
|
+
kind: "graph",
|
|
682
|
+
finalScore: c.score,
|
|
683
|
+
semantic: c.semanticSimilarity,
|
|
684
|
+
recency: c.recencyBoost,
|
|
685
|
+
})),
|
|
686
|
+
};
|
|
687
|
+
onEvent(memoryRecalledEvent);
|
|
688
|
+
}
|
|
650
689
|
}
|
|
651
690
|
|
|
652
691
|
// Build active surface context
|
|
@@ -678,37 +717,20 @@ export async function runAgentLoopImpl(
|
|
|
678
717
|
|
|
679
718
|
ctx.refreshWorkspaceTopLevelContextIfNeeded();
|
|
680
719
|
|
|
681
|
-
// Compute fresh
|
|
720
|
+
// Compute fresh turn timestamp for date grounding.
|
|
682
721
|
// Absolute "now" is always anchored to assistant host clock, while local
|
|
683
722
|
// date semantics prefer configured user timezone, then recalled memory.
|
|
684
723
|
const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
685
724
|
const configuredUserTimeZone = getConfig().ui.userTimezone ?? null;
|
|
686
725
|
const recalledUserTimeZone = null;
|
|
687
|
-
const
|
|
726
|
+
const timestamp = formatTurnTimestamp({
|
|
688
727
|
hostTimeZone,
|
|
689
728
|
configuredUserTimeZone,
|
|
690
729
|
userTimeZone: recalledUserTimeZone,
|
|
691
730
|
});
|
|
692
731
|
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
// message, even if a newer message from a different channel arrived since.
|
|
696
|
-
const channelTurnContext: ChannelTurnContextParams = {
|
|
697
|
-
turnContext: capturedTurnChannelContext,
|
|
698
|
-
conversationOriginChannel: getConversationOriginChannel(
|
|
699
|
-
ctx.conversationId,
|
|
700
|
-
),
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
const interfaceTurnContext: InterfaceTurnContextParams = {
|
|
704
|
-
turnContext: capturedTurnInterfaceContext,
|
|
705
|
-
conversationOriginInterface: getConversationOriginInterface(
|
|
706
|
-
ctx.conversationId,
|
|
707
|
-
),
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
// Resolve the inbound actor context for the model's <inbound_actor_context>
|
|
711
|
-
// block. When the conversation carries enough identity info, use the unified
|
|
732
|
+
// Resolve the inbound actor context for the unified <turn_context> block.
|
|
733
|
+
// When the conversation carries enough identity info, use the unified
|
|
712
734
|
// actor trust resolver so member status/policy and guardian binding details
|
|
713
735
|
// are fresh for this turn. The conversation runtime context remains the source
|
|
714
736
|
// for policy gating; this block is model-facing grounding metadata.
|
|
@@ -729,22 +751,53 @@ export async function runAgentLoopImpl(
|
|
|
729
751
|
}
|
|
730
752
|
}
|
|
731
753
|
|
|
754
|
+
// Build unified turn context block that replaces the separate temporal,
|
|
755
|
+
// channel, interface, and actor context blocks.
|
|
756
|
+
const interfaceName =
|
|
757
|
+
capturedTurnInterfaceContext.userMessageInterface ?? undefined;
|
|
758
|
+
const channelName =
|
|
759
|
+
capturedTurnChannelContext?.userMessageChannel ?? undefined;
|
|
760
|
+
const isGuardian =
|
|
761
|
+
resolvedInboundActorContext?.trustClass === "guardian" ||
|
|
762
|
+
!resolvedInboundActorContext;
|
|
763
|
+
const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
|
|
764
|
+
isGuardian
|
|
765
|
+
? { timestamp, interfaceName, channelName }
|
|
766
|
+
: {
|
|
767
|
+
timestamp,
|
|
768
|
+
interfaceName,
|
|
769
|
+
channelName,
|
|
770
|
+
actorContext: resolvedInboundActorContext,
|
|
771
|
+
},
|
|
772
|
+
);
|
|
773
|
+
|
|
732
774
|
// The `remember` tool handles scratchpad-style memory writes directly to the graph.
|
|
733
775
|
|
|
734
776
|
const isInteractiveResolved =
|
|
735
777
|
options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
|
|
736
778
|
|
|
779
|
+
// Only inject NOW.md if it changed since the last injection in the
|
|
780
|
+
// conversation. Keeping the previous injection in place avoids mutating
|
|
781
|
+
// historical user messages and preserves the cached prefix.
|
|
782
|
+
const currentNowContent = readNowScratchpad();
|
|
783
|
+
const lastInjectedNow = findLastInjectedNowContent(ctx.messages);
|
|
784
|
+
const nowScratchpad =
|
|
785
|
+
currentNowContent !== lastInjectedNow ? currentNowContent : null;
|
|
786
|
+
|
|
787
|
+
// Read PKB always-loaded files (INDEX, essentials, threads, buffer)
|
|
788
|
+
const currentPkbContent = readPkbContext();
|
|
789
|
+
|
|
737
790
|
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
738
791
|
const injectionOpts = {
|
|
739
792
|
activeSurface,
|
|
740
|
-
workspaceTopLevelContext:
|
|
793
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
794
|
+
? ctx.workspaceTopLevelContext
|
|
795
|
+
: null,
|
|
741
796
|
channelCapabilities: ctx.channelCapabilities ?? null,
|
|
742
797
|
channelCommandContext: ctx.commandIntent ?? null,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
temporalContext,
|
|
747
|
-
nowScratchpad: readNowScratchpad(),
|
|
798
|
+
unifiedTurnContext: unifiedTurnContextStr,
|
|
799
|
+
pkbContext: currentPkbContent,
|
|
800
|
+
nowScratchpad,
|
|
748
801
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
749
802
|
transportHints: ctx.transportHints ?? null,
|
|
750
803
|
isNonInteractive: !isInteractiveResolved,
|
|
@@ -860,11 +913,20 @@ export async function runAgentLoopImpl(
|
|
|
860
913
|
ctx.graphMemory.onCompacted(
|
|
861
914
|
step.compactionResult.compactedPersistedMessages,
|
|
862
915
|
);
|
|
916
|
+
shouldInjectWorkspace = true;
|
|
863
917
|
}
|
|
864
918
|
|
|
865
|
-
// Re-inject with potentially downgraded injection mode
|
|
919
|
+
// Re-inject with potentially downgraded injection mode.
|
|
920
|
+
// When compaction ran it strips existing NOW.md / PKB blocks, so we
|
|
921
|
+
// must re-inject the current content. Otherwise rely on the deduplicated
|
|
922
|
+
// value from injectionOpts to avoid duplicate injection.
|
|
866
923
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
867
924
|
...injectionOpts,
|
|
925
|
+
pkbContext: currentPkbContent,
|
|
926
|
+
...(step.compactionResult?.compacted && { nowScratchpad: currentNowContent }),
|
|
927
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
928
|
+
? ctx.workspaceTopLevelContext
|
|
929
|
+
: null,
|
|
868
930
|
mode: currentInjectionMode,
|
|
869
931
|
});
|
|
870
932
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -994,7 +1056,7 @@ export async function runAgentLoopImpl(
|
|
|
994
1056
|
|
|
995
1057
|
// Strip injected context from updated history before compacting,
|
|
996
1058
|
// so we compact the "raw" persistent messages.
|
|
997
|
-
const rawHistory =
|
|
1059
|
+
const rawHistory = stripInjectionsForCompaction(updatedHistory);
|
|
998
1060
|
ctx.messages = rawHistory;
|
|
999
1061
|
|
|
1000
1062
|
ctx.emitActivityState(
|
|
@@ -1048,14 +1110,21 @@ export async function runAgentLoopImpl(
|
|
|
1048
1110
|
midLoopCompact.summaryCacheReadInputTokens ?? 0,
|
|
1049
1111
|
collapseRawResponses(midLoopCompact.summaryRawResponses),
|
|
1050
1112
|
);
|
|
1051
|
-
ctx.graphMemory.onCompacted(
|
|
1052
|
-
|
|
1053
|
-
);
|
|
1113
|
+
ctx.graphMemory.onCompacted(midLoopCompact.compactedPersistedMessages);
|
|
1114
|
+
shouldInjectWorkspace = true;
|
|
1054
1115
|
}
|
|
1055
1116
|
|
|
1056
|
-
// Re-inject runtime context and re-enter the agent loop
|
|
1117
|
+
// Re-inject runtime context and re-enter the agent loop.
|
|
1118
|
+
// stripInjectionsForCompaction() unconditionally removed the existing
|
|
1119
|
+
// NOW.md block from ctx.messages above, so we must always re-inject
|
|
1120
|
+
// the current content regardless of whether compaction actually ran.
|
|
1057
1121
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1058
1122
|
...injectionOpts,
|
|
1123
|
+
pkbContext: currentPkbContent,
|
|
1124
|
+
nowScratchpad: currentNowContent,
|
|
1125
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1126
|
+
? ctx.workspaceTopLevelContext
|
|
1127
|
+
: null,
|
|
1059
1128
|
mode: currentInjectionMode,
|
|
1060
1129
|
});
|
|
1061
1130
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -1134,7 +1203,7 @@ export async function runAgentLoopImpl(
|
|
|
1134
1203
|
// convergence loop operates on the full (larger) history.
|
|
1135
1204
|
if (state.contextTooLargeDetected) {
|
|
1136
1205
|
if (updatedHistory.length > preRunHistoryLength) {
|
|
1137
|
-
ctx.messages =
|
|
1206
|
+
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1138
1207
|
preRepairMessages = updatedHistory;
|
|
1139
1208
|
preRunHistoryLength = updatedHistory.length;
|
|
1140
1209
|
}
|
|
@@ -1254,10 +1323,18 @@ export async function runAgentLoopImpl(
|
|
|
1254
1323
|
ctx.graphMemory.onCompacted(
|
|
1255
1324
|
step.compactionResult.compactedPersistedMessages,
|
|
1256
1325
|
);
|
|
1326
|
+
shouldInjectWorkspace = true;
|
|
1257
1327
|
}
|
|
1258
1328
|
|
|
1329
|
+
// ctx.messages has been stripped (line 1206/1373) so NOW.md must
|
|
1330
|
+
// always be re-injected regardless of whether compaction ran.
|
|
1259
1331
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1260
1332
|
...injectionOpts,
|
|
1333
|
+
pkbContext: currentPkbContent,
|
|
1334
|
+
nowScratchpad: currentNowContent,
|
|
1335
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1336
|
+
? ctx.workspaceTopLevelContext
|
|
1337
|
+
: null,
|
|
1261
1338
|
mode: currentInjectionMode,
|
|
1262
1339
|
});
|
|
1263
1340
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -1295,7 +1372,7 @@ export async function runAgentLoopImpl(
|
|
|
1295
1372
|
// tier operates on up-to-date history instead of stale
|
|
1296
1373
|
// pre-rerun messages.
|
|
1297
1374
|
if (updatedHistory.length > preRunHistoryLength) {
|
|
1298
|
-
ctx.messages =
|
|
1375
|
+
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1299
1376
|
preRepairMessages = updatedHistory;
|
|
1300
1377
|
preRunHistoryLength = updatedHistory.length;
|
|
1301
1378
|
}
|
|
@@ -1368,14 +1445,23 @@ export async function runAgentLoopImpl(
|
|
|
1368
1445
|
ctx.graphMemory.onCompacted(
|
|
1369
1446
|
emergencyCompact.compactedPersistedMessages,
|
|
1370
1447
|
);
|
|
1448
|
+
shouldInjectWorkspace = true;
|
|
1371
1449
|
}
|
|
1372
1450
|
|
|
1451
|
+
// ctx.messages was already stripped before the convergence
|
|
1452
|
+
// loop, so NOW.md must always be re-injected here.
|
|
1373
1453
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1374
1454
|
...injectionOpts,
|
|
1455
|
+
pkbContext: currentPkbContent,
|
|
1456
|
+
nowScratchpad: currentNowContent,
|
|
1457
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1458
|
+
? ctx.workspaceTopLevelContext
|
|
1459
|
+
: null,
|
|
1375
1460
|
mode: currentInjectionMode,
|
|
1376
1461
|
});
|
|
1377
1462
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1378
|
-
const memResult =
|
|
1463
|
+
const memResult =
|
|
1464
|
+
ctx.graphMemory.reinjectCachedMemory(runMessages);
|
|
1379
1465
|
runMessages = memResult.runMessages;
|
|
1380
1466
|
}
|
|
1381
1467
|
preRepairMessages = runMessages;
|
|
@@ -1479,10 +1565,18 @@ export async function runAgentLoopImpl(
|
|
|
1479
1565
|
ctx.graphMemory.onCompacted(
|
|
1480
1566
|
emergencyCompact.compactedPersistedMessages,
|
|
1481
1567
|
);
|
|
1568
|
+
shouldInjectWorkspace = true;
|
|
1482
1569
|
}
|
|
1483
1570
|
|
|
1571
|
+
// ctx.messages was already stripped before the convergence
|
|
1572
|
+
// loop, so NOW.md must always be re-injected here.
|
|
1484
1573
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1485
1574
|
...injectionOpts,
|
|
1575
|
+
pkbContext: currentPkbContent,
|
|
1576
|
+
nowScratchpad: currentNowContent,
|
|
1577
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1578
|
+
? ctx.workspaceTopLevelContext
|
|
1579
|
+
: null,
|
|
1486
1580
|
mode: currentInjectionMode,
|
|
1487
1581
|
});
|
|
1488
1582
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -1626,7 +1720,11 @@ export async function runAgentLoopImpl(
|
|
|
1626
1720
|
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
1627
1721
|
);
|
|
1628
1722
|
|
|
1629
|
-
|
|
1723
|
+
// Persist injections in history: runtime-injected context stays on
|
|
1724
|
+
// historical user messages so the conversation prefix is stable for
|
|
1725
|
+
// Anthropic's prefix caching. Stripping only happens during
|
|
1726
|
+
// compaction/overflow recovery (where a cache miss is expected).
|
|
1727
|
+
ctx.messages = restoredHistory;
|
|
1630
1728
|
|
|
1631
1729
|
emitUsage(
|
|
1632
1730
|
ctx,
|
|
@@ -1877,15 +1975,10 @@ export async function runAgentLoopImpl(
|
|
|
1877
1975
|
// Clear at turn end so they never leak into subsequent unrelated messages.
|
|
1878
1976
|
ctx.commandIntent = undefined;
|
|
1879
1977
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
);
|
|
1885
|
-
if (didMutateHistory) {
|
|
1886
|
-
rebuildConversationDiskViewFromDbState(ctx.conversationId);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1978
|
+
// Consolidation deferred to compaction: keeping assistant + tool_result
|
|
1979
|
+
// messages unconsolidated preserves the exact message structure sent to
|
|
1980
|
+
// the API, enabling stable prefix caching across turns. Compaction
|
|
1981
|
+
// consolidates when it summarizes old messages (cache miss is expected).
|
|
1889
1982
|
|
|
1890
1983
|
ctx.drainQueue(yieldedForHandoff ? "checkpoint_handoff" : "loop_complete");
|
|
1891
1984
|
|
|
@@ -68,14 +68,13 @@ export function findLastUndoableUserMessageIndex(messages: Message[]): number {
|
|
|
68
68
|
// ── Qdrant Vector Cleanup ────────────────────────────────────────────
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Delete Qdrant vector entries for the given segment
|
|
71
|
+
* Delete Qdrant vector entries for the given segment IDs.
|
|
72
72
|
* Individual deletion failures are logged and enqueued as retry jobs
|
|
73
73
|
* to prevent silently orphaned vectors.
|
|
74
74
|
*/
|
|
75
75
|
export async function cleanupQdrantVectors(
|
|
76
76
|
conversationId: string,
|
|
77
77
|
segmentIds: string[],
|
|
78
|
-
orphanedItemIds: string[],
|
|
79
78
|
): Promise<void> {
|
|
80
79
|
let qdrant: ReturnType<typeof getQdrantClient>;
|
|
81
80
|
try {
|
|
@@ -84,15 +83,12 @@ export async function cleanupQdrantVectors(
|
|
|
84
83
|
return; // Qdrant not initialized — nothing to clean up.
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
if (segmentIds.length === 0
|
|
86
|
+
if (segmentIds.length === 0) return;
|
|
88
87
|
|
|
89
88
|
const targets: Array<{ targetType: string; targetId: string }> = [];
|
|
90
89
|
for (const segId of segmentIds) {
|
|
91
90
|
targets.push({ targetType: "segment", targetId: segId });
|
|
92
91
|
}
|
|
93
|
-
for (const itemId of orphanedItemIds) {
|
|
94
|
-
targets.push({ targetType: "item", targetId: itemId });
|
|
95
|
-
}
|
|
96
92
|
|
|
97
93
|
const results = await Promise.allSettled(
|
|
98
94
|
targets.map((t) =>
|
|
@@ -124,7 +120,6 @@ export async function cleanupQdrantVectors(
|
|
|
124
120
|
succeeded,
|
|
125
121
|
failed,
|
|
126
122
|
segments: segmentIds.length,
|
|
127
|
-
items: orphanedItemIds.length,
|
|
128
123
|
},
|
|
129
124
|
"Cleaned up Qdrant vectors after regenerate",
|
|
130
125
|
);
|
|
@@ -187,20 +182,17 @@ export function consolidateAssistantMessages(
|
|
|
187
182
|
// Still delete internal tool_result messages even if only one assistant message,
|
|
188
183
|
// and collect IDs for vector cleanup
|
|
189
184
|
const allSegmentIds: string[] = [];
|
|
190
|
-
const allOrphanedItemIds: string[] = [];
|
|
191
185
|
for (const id of messagesToDelete) {
|
|
192
186
|
const deleted = deleteMessageById(id);
|
|
193
187
|
didMutate = true;
|
|
194
188
|
allSegmentIds.push(...deleted.segmentIds);
|
|
195
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
196
189
|
}
|
|
197
190
|
|
|
198
191
|
// Clean up Qdrant vectors (fire-and-forget)
|
|
199
|
-
if (allSegmentIds.length > 0
|
|
192
|
+
if (allSegmentIds.length > 0) {
|
|
200
193
|
cleanupQdrantVectors(
|
|
201
194
|
conversationId,
|
|
202
195
|
allSegmentIds,
|
|
203
|
-
allOrphanedItemIds,
|
|
204
196
|
).catch((err) => {
|
|
205
197
|
log.warn(
|
|
206
198
|
{ err, conversationId },
|
|
@@ -322,24 +314,20 @@ export function consolidateAssistantMessages(
|
|
|
322
314
|
// Delete the other assistant messages and internal tool_result messages,
|
|
323
315
|
// and collect IDs for vector cleanup
|
|
324
316
|
const allSegmentIds: string[] = [];
|
|
325
|
-
const allOrphanedItemIds: string[] = [];
|
|
326
317
|
for (let i = 1; i < messagesToConsolidate.length; i++) {
|
|
327
318
|
const deleted = deleteMessageById(messagesToConsolidate[i].id);
|
|
328
319
|
allSegmentIds.push(...deleted.segmentIds);
|
|
329
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
330
320
|
}
|
|
331
321
|
for (const id of messagesToDelete) {
|
|
332
322
|
const deleted = deleteMessageById(id);
|
|
333
323
|
allSegmentIds.push(...deleted.segmentIds);
|
|
334
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
335
324
|
}
|
|
336
325
|
|
|
337
326
|
// Clean up Qdrant vectors (fire-and-forget)
|
|
338
|
-
if (allSegmentIds.length > 0
|
|
327
|
+
if (allSegmentIds.length > 0) {
|
|
339
328
|
cleanupQdrantVectors(
|
|
340
329
|
conversationId,
|
|
341
330
|
allSegmentIds,
|
|
342
|
-
allOrphanedItemIds,
|
|
343
331
|
).catch((err) => {
|
|
344
332
|
log.warn(
|
|
345
333
|
{ err, conversationId },
|
|
@@ -539,18 +527,15 @@ export async function regenerate(
|
|
|
539
527
|
|
|
540
528
|
// Delete each message via deleteMessageById and collect IDs for Qdrant cleanup.
|
|
541
529
|
const allSegmentIds: string[] = [];
|
|
542
|
-
const allOrphanedItemIds: string[] = [];
|
|
543
530
|
for (const msg of messagesToDelete) {
|
|
544
531
|
const deleted = deleteMessageById(msg.id);
|
|
545
532
|
allSegmentIds.push(...deleted.segmentIds);
|
|
546
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
547
533
|
}
|
|
548
534
|
|
|
549
535
|
// Clean up Qdrant vectors (fire-and-forget).
|
|
550
536
|
cleanupQdrantVectors(
|
|
551
537
|
conversation.conversationId,
|
|
552
538
|
allSegmentIds,
|
|
553
|
-
allOrphanedItemIds,
|
|
554
539
|
).catch((err) => {
|
|
555
540
|
log.warn(
|
|
556
541
|
{ err, conversationId: conversation.conversationId },
|