@vellumai/assistant 0.12.2-staging.7 → 0.12.2

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.
Files changed (120) hide show
  1. package/docs/architecture/memory.md +2 -11
  2. package/docs/desktop-browser-cli.md +2 -4
  3. package/node_modules/@vellumai/environments/src/shell.test.ts +0 -21
  4. package/node_modules/@vellumai/environments/src/shell.ts +0 -24
  5. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +2 -8
  6. package/openapi.yaml +2 -6
  7. package/package.json +1 -1
  8. package/scripts/smoke-desktop-browser-cli.ts +0 -1
  9. package/src/__tests__/agent-loop.test.ts +0 -124
  10. package/src/__tests__/approval-interception-trust-gates.test.ts +0 -40
  11. package/src/__tests__/channel-approval.test.ts +14 -9
  12. package/src/__tests__/conversation-agent-loop.test.ts +0 -25
  13. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
  14. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -138
  15. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  16. package/src/__tests__/subagent-tool-gate-mode.test.ts +0 -169
  17. package/src/__tests__/terminal-tools.test.ts +0 -8
  18. package/src/__tests__/unicode.test.ts +0 -36
  19. package/src/agent/loop.ts +0 -19
  20. package/src/api/index.ts +0 -6
  21. package/src/approvals/approval-primitive.ts +2 -5
  22. package/src/approvals/scoped-approval-grants.ts +2 -6
  23. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -43
  24. package/src/daemon/conversation-agent-loop.ts +0 -2
  25. package/src/daemon/conversation-tool-setup.ts +1 -57
  26. package/src/daemon/conversation.ts +0 -17
  27. package/src/daemon/daemon-control.ts +6 -2
  28. package/src/daemon/orphan-reaper.ts +3 -4
  29. package/src/daemon/tool-setup-types.ts +0 -6
  30. package/src/daemon/wake-conversation-ops.ts +15 -38
  31. package/src/desktop/desktop-automation-lease.test.ts +0 -143
  32. package/src/desktop/desktop-automation-lease.ts +3 -39
  33. package/src/messaging/provider-message-metadata.ts +3 -3
  34. package/src/notifications/__tests__/copy-composer.test.ts +0 -70
  35. package/src/notifications/copy-composer.ts +3 -11
  36. package/src/oauth/seed-providers.ts +30 -0
  37. package/src/persistence/conversation-plugin-facade.ts +0 -13
  38. package/src/persistence/schema/index.ts +0 -1
  39. package/src/persistence/steps.ts +0 -2
  40. package/src/plugin-api/conversation-turn.ts +7 -31
  41. package/src/plugin-api/index.ts +1 -9
  42. package/src/plugins/defaults/memory/AGENTS.md +2 -14
  43. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +0 -43
  44. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +0 -46
  45. package/src/plugins/defaults/memory/buffer-format.ts +0 -40
  46. package/src/plugins/defaults/memory/context-search/agent-runner.ts +2 -1
  47. package/src/plugins/defaults/memory/context-search/format.ts +1 -2
  48. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +1 -2
  49. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -2
  50. package/src/plugins/defaults/memory/graph/capability-seed.ts +2 -1
  51. package/src/plugins/defaults/memory/graph/tool-handlers.ts +42 -1
  52. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  53. package/src/plugins/defaults/memory/injectors.ts +3 -4
  54. package/src/plugins/defaults/memory/memory-retrospective-job.ts +181 -55
  55. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +99 -407
  56. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -10
  57. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +7 -107
  58. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +86 -307
  59. package/src/plugins/defaults/memory/substrate/page-index.ts +1 -2
  60. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +49 -89
  61. package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
  62. package/src/plugins/defaults/memory/tools.ts +1 -1
  63. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
  64. package/src/plugins/defaults/memory/v1/graph/extraction.ts +1 -2
  65. package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
  66. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +0 -5
  67. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +2 -5
  68. package/src/plugins/defaults/memory/v2/reranker.ts +1 -2
  69. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +1 -81
  70. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +0 -87
  71. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +0 -21
  72. package/src/plugins/defaults/memory/v3/card.ts +1 -2
  73. package/src/plugins/defaults/memory/v3/injector.ts +178 -212
  74. package/src/plugins/defaults/memory/v3/orchestrate.ts +22 -86
  75. package/src/plugins/defaults/memory/v3/pool-select.ts +7 -10
  76. package/src/plugins/defaults/memory/v3/sections.ts +1 -2
  77. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +1 -10
  78. package/src/plugins/defaults/tool-result-truncate/terminal.ts +46 -1
  79. package/src/runtime/AGENTS.md +1 -1
  80. package/src/runtime/__tests__/agent-wake.test.ts +1 -86
  81. package/src/runtime/agent-wake.ts +4 -20
  82. package/src/runtime/guardian-action-service.ts +17 -2
  83. package/src/runtime/guardian-reply-router.ts +8 -1
  84. package/src/runtime/routes/channel-route-shared.ts +9 -1
  85. package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
  86. package/src/runtime/routes/desktop-setup-routes.ts +3 -7
  87. package/src/runtime/routes/guardian-approval-interception.ts +0 -24
  88. package/src/runtime/routes/inbound-message-handler.ts +3 -2
  89. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
  90. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -7
  91. package/src/schedule/run-script.ts +2 -2
  92. package/src/tools/host-terminal/host-shell.ts +6 -12
  93. package/src/tools/shared/filesystem/file-ops-service.ts +31 -1
  94. package/src/tools/shared/shell-output.test.ts +0 -10
  95. package/src/tools/shared/shell-output.ts +2 -14
  96. package/src/tools/skills/sandbox-runner.ts +2 -13
  97. package/src/tools/skills/scaffold-managed.ts +1 -2
  98. package/src/tools/terminal/__tests__/safe-env.test.ts +0 -29
  99. package/src/tools/terminal/safe-env.ts +1 -30
  100. package/src/tools/terminal/sanitized-bash.ts +2 -15
  101. package/src/tools/terminal/shell.test.ts +0 -29
  102. package/src/tools/terminal/shell.ts +7 -13
  103. package/src/util/host-process.test.ts +1 -17
  104. package/src/util/host-process.ts +0 -24
  105. package/src/util/unicode.ts +0 -29
  106. package/src/__tests__/db-conversation-tool-surface.test.ts +0 -144
  107. package/src/api/events/desktop-activity-changed.ts +0 -10
  108. package/src/persistence/conversation-tool-surface.ts +0 -86
  109. package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +0 -78
  110. package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +0 -29
  111. package/src/persistence/schema/conversation-tool-surfaces.ts +0 -22
  112. package/src/plugin-api/plugin-channel-turn-trust.test.ts +0 -133
  113. package/src/plugin-api/plugin-channel-turn-trust.ts +0 -71
  114. package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +0 -320
  115. package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +0 -17
  116. package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +0 -161
  117. package/src/plugins/defaults/memory/buffer-file.ts +0 -354
  118. package/src/plugins/defaults/memory/memory-run-evidence.ts +0 -213
  119. package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +0 -34
  120. package/src/tools/terminal/shell-launch.test.ts +0 -162
@@ -43,11 +43,11 @@
43
43
  import {
44
44
  addMessage,
45
45
  type AgentLoopExitReason,
46
+ type ContentBlock,
46
47
  type ConversationRow,
47
48
  deleteConversation,
48
49
  getConversation,
49
50
  getConversationProcessingStartedAt,
50
- getConversationToolSurface,
51
51
  isConversationProcessing,
52
52
  } from "@vellumai/plugin-api";
53
53
 
@@ -105,12 +105,6 @@ import {
105
105
  upsertRetrospectiveState,
106
106
  } from "./memory-retrospective-state.js";
107
107
  import { effectiveSweepLookbackMs } from "./memory-retrospective-sweep.js";
108
- import {
109
- collectSuccessfulToolResultIds,
110
- countDurableToolUses,
111
- extractRememberContents,
112
- hasCommittedTextReply,
113
- } from "./memory-run-evidence.js";
114
108
 
115
109
  const log = getLogger("memory-retrospective-job");
116
110
 
@@ -506,9 +500,10 @@ export async function runForkBasedRetrospective(
506
500
  // Persona + tool-context parity pins derived from the source conversation
507
501
  // (see `resolveSourceParityPins`), both passed unconditionally. The persona
508
502
  // override keeps the system-prompt prefix in parity (and is a review-quality
509
- // fix on its own); the tool-context pin resolves the fork's execution-side
510
- // tool inventory under the source's client context and carries the wire
511
- // surface when the source has no recorded one (`sourceToolSurface` below).
503
+ // fix on its own); the tool-context pin keeps the wire tool surface in
504
+ // parity the fork always runs execution gate mode below, so the source's
505
+ // full tool surface stays on the wire while the allowlist holds at
506
+ // execution time.
512
507
  // Warm both guardian-delivery cache keys (vellum + unfiltered) so the sync
513
508
  // slug resolution inside resolveSourceParityPins (resolveUserSlug(undefined)),
514
509
  // including its any-channel fallback, hits fresh keys instead of falling
@@ -519,16 +514,6 @@ export async function runForkBasedRetrospective(
519
514
  newMessages,
520
515
  );
521
516
 
522
- // The tools array the source's most recent live turn actually sent,
523
- // replayed verbatim on the fork. The wire tool block is the first tier of
524
- // the provider cache prefix (tools → system → messages), and re-deriving it
525
- // on the fork cannot reproduce the source's bytes: this worker's registry
526
- // has no user-plugin tools, host-tool gates read connected clients the
527
- // worker never has, and presence derived from a persisted interface stamp
528
- // misreads clientless turns. `null` (no live turn has recorded a surface)
529
- // leaves the pin's derivation as the wire surface.
530
- const sourceToolSurface = await readSourceToolSurface(sourceConversationId);
531
-
532
517
  // `skipHintInjection: true` because the instruction is already a
533
518
  // persisted message — the wake's hint sandwich would only duplicate it.
534
519
  let wakeSucceeded = false;
@@ -557,18 +542,16 @@ export async function runForkBasedRetrospective(
557
542
  "find_similar_skills",
558
543
  ]
559
544
  : ["remember"],
560
- // Keep the source's full tool surface on the wire: replay the array its
561
- // last live turn sent when one is recorded, otherwise resolve the fork's
562
- // own surface under the source's client context (`toolContextPin`). The
563
- // wire tool block is the first tier of the provider cache prefix
564
- // (tools system messages), so any wire difference busts cache parity
565
- // with the source's live turns, re-creating the cached prefix instead of
545
+ // Always keep the source's full tool surface on the wire and resolve it
546
+ // under the source's client context (`toolContextPin`). The wire tool
547
+ // block is the first tier of the provider cache prefix
548
+ // (tools system messages), so a wire filter busts cache parity with
549
+ // the source's live turns re-creating the cached prefix instead of
566
550
  // reading it. The allowlist still holds at execution time: non-allowlisted
567
551
  // calls are rejected before any executor or side effect runs. See
568
552
  // {@link SubagentToolGateMode} and {@link WakeToolContextPin}.
569
553
  toolGateMode: "execution" as const,
570
554
  toolContextPin,
571
- ...(sourceToolSurface ? { wireToolDefinitions: sourceToolSurface } : {}),
572
555
  // Preactivate skill-management so its authoring tools (`find_similar_skills`
573
556
  // / `scaffold_managed_skill` / the `skill_load` target) are in the turn's
574
557
  // active set from turn 1; the checker's origin-scoped grant then makes them
@@ -748,24 +731,6 @@ function enqueueFollowUpJobs(): string[] {
748
731
  return followUpJobIds;
749
732
  }
750
733
 
751
- /**
752
- * The source's recorded wire tool array, or `null` when none is recorded or
753
- * the read fails (logged; the fork then derives its wire surface).
754
- */
755
- async function readSourceToolSurface(
756
- sourceConversationId: string,
757
- ): ReturnType<typeof getConversationToolSurface> {
758
- try {
759
- return await getConversationToolSurface(sourceConversationId);
760
- } catch (err) {
761
- log.warn(
762
- { err, sourceConversationId },
763
- "memory-retrospective (fork): failed to read the source's recorded tool surface; deriving the wire surface instead",
764
- );
765
- return null;
766
- }
767
- }
768
-
769
734
  /**
770
735
  * The source-derived parity pins the fork wake runs under: the system-prompt
771
736
  * persona override and the tool-resolution context pin. Both exist so the
@@ -1287,16 +1252,177 @@ async function collectRetrospectiveRunEvidence(
1287
1252
  const succeededIds = collectSuccessfulToolResultIds(runMessages);
1288
1253
  return {
1289
1254
  remembers: extractRememberContents(runMessages, succeededIds),
1290
- durableToolCallCount: countDurableToolUses(
1291
- runMessages,
1292
- DURABLE_RETROSPECTIVE_TOOLS,
1293
- succeededIds,
1294
- ),
1295
- durableToolAttemptCount: countDurableToolUses(
1296
- runMessages,
1297
- DURABLE_RETROSPECTIVE_TOOLS,
1298
- null,
1299
- ),
1255
+ durableToolCallCount: countDurableToolUses(runMessages, succeededIds),
1256
+ durableToolAttemptCount: countDurableToolUses(runMessages, null),
1300
1257
  committedTextReply: hasCommittedTextReply(runMessages),
1301
1258
  };
1302
1259
  }
1260
+
1261
+ /**
1262
+ * Whether the LAST persisted assistant row on the run's tail carries a text
1263
+ * block with non-whitespace content. Paired with a model-driven stop and
1264
+ * zero memory-write attempts, that closing reply is the persisted artifact
1265
+ * of a pass that read its window and had nothing to save: the model spoke
1266
+ * and then chose to end the run. Reading only the final row separates it
1267
+ * from a run whose narration went live but whose actual conclusion was
1268
+ * empty, as well as from a response that committed nothing at all
1269
+ * (thinking-only output, an empty content array).
1270
+ */
1271
+ function hasCommittedTextReply(messages: MessageLike[]): boolean {
1272
+ for (let i = messages.length - 1; i >= 0; i--) {
1273
+ const msg = messages[i]!;
1274
+ if (msg.role !== "assistant") {
1275
+ continue;
1276
+ }
1277
+ const blocks = parseMessageBlocks(msg);
1278
+ if (blocks === null) {
1279
+ return false;
1280
+ }
1281
+ return blocks.some(
1282
+ (b) =>
1283
+ b.type === "text" && typeof b.text === "string" && b.text.trim() !== "",
1284
+ );
1285
+ }
1286
+ return false;
1287
+ }
1288
+
1289
+ /**
1290
+ * Ids of `tool_result` blocks on the run's user rows whose execution did not
1291
+ * report an error. Robust to malformed content JSON the same way
1292
+ * `extractRememberContents` is.
1293
+ */
1294
+ function collectSuccessfulToolResultIds(messages: MessageLike[]): Set<string> {
1295
+ const ids = new Set<string>();
1296
+ for (const msg of messages) {
1297
+ if (msg.role !== "user") {
1298
+ continue;
1299
+ }
1300
+ for (const b of parseMessageBlocks(msg) ?? []) {
1301
+ // guard:allow-tool-result-only: success evidence for locally-executed
1302
+ // durable memory tools; server-side web_search_tool_result never
1303
+ // corresponds to a durable write and carries no is_error flag.
1304
+ if (
1305
+ b.type === "tool_result" &&
1306
+ typeof b.tool_use_id === "string" &&
1307
+ b.is_error !== true
1308
+ ) {
1309
+ ids.add(b.tool_use_id);
1310
+ }
1311
+ }
1312
+ }
1313
+ return ids;
1314
+ }
1315
+
1316
+ /**
1317
+ * Count persisted `tool_use` blocks whose `name` is in
1318
+ * {@link DURABLE_RETROSPECTIVE_TOOLS} across the run's assistant rows.
1319
+ * With a `succeededIds` set, only calls whose id has a matching successful
1320
+ * `tool_result` count (verified executions); with `null`, every attempt
1321
+ * counts regardless of outcome.
1322
+ */
1323
+ function countDurableToolUses(
1324
+ messages: MessageLike[],
1325
+ succeededIds: ReadonlySet<string> | null,
1326
+ ): number {
1327
+ let count = 0;
1328
+ for (const msg of messages) {
1329
+ if (msg.role !== "assistant") {
1330
+ continue;
1331
+ }
1332
+ for (const b of parseMessageBlocks(msg) ?? []) {
1333
+ if (
1334
+ b.type === "tool_use" &&
1335
+ DURABLE_RETROSPECTIVE_TOOLS.has(String(b.name)) &&
1336
+ (succeededIds === null ||
1337
+ (typeof b.id === "string" && succeededIds.has(b.id)))
1338
+ ) {
1339
+ count += 1;
1340
+ }
1341
+ }
1342
+ }
1343
+ return count;
1344
+ }
1345
+
1346
+ interface MessageLike {
1347
+ role: string;
1348
+ content: string | ContentBlock[];
1349
+ }
1350
+
1351
+ /**
1352
+ * Parse a message row's content into its block objects, or `null` when the
1353
+ * content is malformed (unparseable JSON, not an array). Non-object entries
1354
+ * are dropped. Every evidence reader in this module goes through this so
1355
+ * malformed rows degrade the same way everywhere: skipped, not propagated.
1356
+ */
1357
+ function parseMessageBlocks(
1358
+ msg: MessageLike,
1359
+ ): Record<string, unknown>[] | null {
1360
+ let blocks: unknown = msg.content;
1361
+ if (typeof blocks === "string") {
1362
+ try {
1363
+ blocks = JSON.parse(blocks);
1364
+ } catch {
1365
+ return null;
1366
+ }
1367
+ }
1368
+ if (!Array.isArray(blocks)) {
1369
+ return null;
1370
+ }
1371
+ return blocks.filter(
1372
+ (block): block is Record<string, unknown> =>
1373
+ typeof block === "object" && block !== null,
1374
+ );
1375
+ }
1376
+
1377
+ /**
1378
+ * Scan an array of message rows for `tool_use` blocks where `name` is
1379
+ * `"remember"` and return the `input.content` strings in order. Robust to
1380
+ * malformed content JSON — unparseable rows are skipped, not propagated.
1381
+ */
1382
+ function extractRememberContents(
1383
+ messages: MessageLike[],
1384
+ succeededIds?: ReadonlySet<string>,
1385
+ ): string[] {
1386
+ const contents: string[] = [];
1387
+ for (const msg of messages) {
1388
+ if (msg.role !== "assistant") {
1389
+ continue;
1390
+ }
1391
+ for (const b of parseMessageBlocks(msg) ?? []) {
1392
+ if (b.type !== "tool_use") {
1393
+ continue;
1394
+ }
1395
+ if (b.name !== "remember") {
1396
+ continue;
1397
+ }
1398
+ // When a success set is provided, only executions that reported a
1399
+ // non-error tool_result contribute facts: a failed remember never
1400
+ // wrote the buffer, and logging its facts would suppress the retry's
1401
+ // re-save via <already_remembered>.
1402
+ if (
1403
+ succeededIds !== undefined &&
1404
+ (typeof b.id !== "string" || !succeededIds.has(b.id))
1405
+ ) {
1406
+ continue;
1407
+ }
1408
+ const input = b.input;
1409
+ if (!input || typeof input !== "object") {
1410
+ continue;
1411
+ }
1412
+ const content = (input as Record<string, unknown>).content;
1413
+ // `remember` accepts a single string or an array of facts (batch form);
1414
+ // flatten both so batched saves still feed the dedup baseline.
1415
+ const facts = Array.isArray(content) ? content : [content];
1416
+ for (const fact of facts) {
1417
+ if (typeof fact !== "string") {
1418
+ continue;
1419
+ }
1420
+ const trimmed = fact.trim();
1421
+ if (trimmed.length > 0) {
1422
+ contents.push(trimmed);
1423
+ }
1424
+ }
1425
+ }
1426
+ }
1427
+ return contents;
1428
+ }