@vellumai/assistant 0.5.7 → 0.5.9

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 (205) hide show
  1. package/Dockerfile +2 -1
  2. package/docker-entrypoint.sh +9 -0
  3. package/docs/architecture/memory.md +13 -11
  4. package/eslint.config.mjs +0 -31
  5. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  6. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  7. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  8. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  9. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
  10. package/package.json +1 -1
  11. package/src/__tests__/approval-cascade.test.ts +0 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  13. package/src/__tests__/call-controller.test.ts +0 -1
  14. package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
  15. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  16. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  17. package/src/__tests__/config-schema.test.ts +2 -0
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  19. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  20. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  21. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  22. package/src/__tests__/conversation-error.test.ts +15 -1
  23. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  24. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  25. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  26. package/src/__tests__/conversation-queue.test.ts +0 -1
  27. package/src/__tests__/conversation-runtime-assembly.test.ts +227 -0
  28. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  29. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  30. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  32. package/src/__tests__/credential-execution-client.test.ts +5 -2
  33. package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
  34. package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
  35. package/src/__tests__/credential-security-e2e.test.ts +1 -1
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -5
  37. package/src/__tests__/credentials-cli.test.ts +4 -3
  38. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  39. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  40. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  41. package/src/__tests__/journal-context.test.ts +335 -0
  42. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  43. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  44. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  45. package/src/__tests__/memory-regressions.test.ts +408 -363
  46. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  47. package/src/__tests__/non-member-access-request.test.ts +2 -2
  48. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  49. package/src/__tests__/oauth-cli.test.ts +5 -1
  50. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  51. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  52. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  53. package/src/__tests__/relay-server.test.ts +1 -2
  54. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  55. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  56. package/src/__tests__/secure-keys.test.ts +18 -15
  57. package/src/__tests__/skill-memory.test.ts +17 -3
  58. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  59. package/src/__tests__/stt-hints.test.ts +437 -0
  60. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  61. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  62. package/src/__tests__/voice-quality.test.ts +58 -0
  63. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  64. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
  65. package/src/acp/agent-process.ts +9 -1
  66. package/src/agent/loop.ts +1 -1
  67. package/src/approvals/guardian-request-resolvers.ts +164 -38
  68. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  69. package/src/calls/call-controller.ts +9 -5
  70. package/src/calls/fish-audio-client.ts +26 -14
  71. package/src/calls/stt-hints.ts +189 -0
  72. package/src/calls/tts-text-sanitizer.ts +61 -0
  73. package/src/calls/twilio-routes.ts +32 -4
  74. package/src/calls/voice-quality.ts +15 -3
  75. package/src/calls/voice-session-bridge.ts +1 -0
  76. package/src/cli/commands/avatar.ts +2 -2
  77. package/src/cli/commands/credentials.ts +110 -94
  78. package/src/cli/commands/doctor.ts +2 -2
  79. package/src/cli/commands/keys.ts +7 -7
  80. package/src/cli/commands/memory.ts +1 -1
  81. package/src/cli/commands/oauth/connections.ts +11 -29
  82. package/src/cli/commands/oauth/platform.ts +389 -43
  83. package/src/cli/lib/daemon-credential-client.ts +284 -0
  84. package/src/cli.ts +1 -1
  85. package/src/config/bundled-skills/AGENTS.md +34 -0
  86. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  87. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  88. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  89. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  90. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  91. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  92. package/src/config/bundled-skills/settings/TOOLS.json +46 -1
  93. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  94. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  95. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  96. package/src/config/bundled-tool-registry.ts +4 -0
  97. package/src/config/defaults.ts +0 -2
  98. package/src/config/env-registry.ts +4 -4
  99. package/src/config/env.ts +14 -1
  100. package/src/config/feature-flag-registry.json +1 -1
  101. package/src/config/loader.ts +8 -11
  102. package/src/config/schema.ts +5 -16
  103. package/src/config/schemas/calls.ts +17 -0
  104. package/src/config/schemas/inference.ts +2 -2
  105. package/src/config/schemas/journal.ts +16 -0
  106. package/src/config/schemas/memory-processing.ts +2 -2
  107. package/src/config/types.ts +1 -0
  108. package/src/contacts/contact-store.ts +2 -2
  109. package/src/credential-execution/executable-discovery.ts +1 -1
  110. package/src/credential-execution/startup-timeout.ts +36 -0
  111. package/src/daemon/approval-generators.ts +3 -9
  112. package/src/daemon/conversation-agent-loop.ts +6 -0
  113. package/src/daemon/conversation-error.ts +13 -1
  114. package/src/daemon/conversation-memory.ts +1 -2
  115. package/src/daemon/conversation-process.ts +18 -1
  116. package/src/daemon/conversation-runtime-assembly.ts +61 -1
  117. package/src/daemon/conversation-surfaces.ts +30 -1
  118. package/src/daemon/conversation.ts +20 -9
  119. package/src/daemon/guardian-action-generators.ts +3 -9
  120. package/src/daemon/lifecycle.ts +18 -11
  121. package/src/daemon/message-types/conversations.ts +1 -0
  122. package/src/daemon/server.ts +2 -3
  123. package/src/memory/app-store.ts +31 -0
  124. package/src/memory/db-init.ts +4 -0
  125. package/src/memory/indexer.ts +19 -10
  126. package/src/memory/items-extractor.ts +315 -322
  127. package/src/memory/job-handlers/summarization.ts +26 -16
  128. package/src/memory/jobs-store.ts +33 -1
  129. package/src/memory/journal-memory.ts +214 -0
  130. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  131. package/src/memory/migrations/index.ts +1 -0
  132. package/src/memory/migrations/registry.ts +8 -0
  133. package/src/memory/retriever.test.ts +37 -25
  134. package/src/memory/retriever.ts +24 -49
  135. package/src/memory/schema/memory-core.ts +2 -0
  136. package/src/memory/search/formatting.ts +7 -44
  137. package/src/memory/search/staleness.ts +4 -0
  138. package/src/memory/search/tier-classifier.ts +10 -2
  139. package/src/memory/search/types.ts +2 -5
  140. package/src/memory/task-memory-cleanup.ts +4 -3
  141. package/src/notifications/adapters/slack.ts +168 -6
  142. package/src/notifications/broadcaster.ts +1 -0
  143. package/src/notifications/copy-composer.ts +59 -2
  144. package/src/notifications/signal.ts +2 -0
  145. package/src/notifications/types.ts +2 -0
  146. package/src/prompts/journal-context.ts +133 -0
  147. package/src/prompts/persona-resolver.ts +80 -24
  148. package/src/prompts/system-prompt.ts +30 -0
  149. package/src/prompts/templates/NOW.md +26 -0
  150. package/src/prompts/templates/SOUL.md +20 -0
  151. package/src/prompts/update-bulletin-format.ts +0 -2
  152. package/src/providers/provider-send-message.ts +3 -32
  153. package/src/providers/registry.ts +2 -139
  154. package/src/providers/types.ts +1 -1
  155. package/src/runtime/access-request-helper.ts +4 -0
  156. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  157. package/src/runtime/auth/route-policy.ts +2 -0
  158. package/src/runtime/gateway-client.ts +47 -4
  159. package/src/runtime/guardian-decision-types.ts +45 -4
  160. package/src/runtime/http-server.ts +5 -2
  161. package/src/runtime/routes/access-request-decision.ts +2 -2
  162. package/src/runtime/routes/app-management-routes.ts +2 -1
  163. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  164. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  165. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  166. package/src/runtime/routes/debug-routes.ts +12 -9
  167. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  168. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  169. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  170. package/src/runtime/routes/identity-routes.ts +1 -1
  171. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  172. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  173. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  174. package/src/runtime/routes/integrations/twilio.ts +52 -10
  175. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  176. package/src/runtime/routes/memory-item-routes.ts +25 -11
  177. package/src/runtime/routes/secret-routes.ts +141 -10
  178. package/src/runtime/routes/tts-routes.ts +11 -1
  179. package/src/security/ces-credential-client.ts +18 -9
  180. package/src/security/ces-rpc-credential-backend.ts +4 -3
  181. package/src/security/credential-backend.ts +10 -4
  182. package/src/security/secure-keys.ts +21 -4
  183. package/src/skills/catalog-install.ts +4 -36
  184. package/src/skills/inline-command-expansions.ts +7 -7
  185. package/src/skills/skill-memory.ts +1 -0
  186. package/src/subagent/manager.ts +2 -5
  187. package/src/tools/acp/spawn.ts +78 -1
  188. package/src/tools/credentials/vault.ts +5 -3
  189. package/src/tools/memory/definitions.ts +3 -2
  190. package/src/tools/memory/handlers.ts +10 -7
  191. package/src/tools/sensitive-output-placeholders.ts +2 -2
  192. package/src/tools/terminal/safe-env.ts +1 -0
  193. package/src/util/browser.ts +15 -0
  194. package/src/util/platform.ts +1 -1
  195. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
  196. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
  197. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  198. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  199. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
  200. package/src/workspace/migrations/registry.ts +4 -0
  201. package/src/workspace/provider-commit-message-generator.ts +12 -21
  202. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  203. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  204. package/src/memory/search/lexical.ts +0 -48
  205. package/src/providers/failover.ts +0 -186
@@ -11,6 +11,7 @@ import {
11
11
  injectChannelCapabilityContext,
12
12
  injectChannelCommandContext,
13
13
  injectInboundActorContext,
14
+ injectNowScratchpad,
14
15
  injectTemporalContext,
15
16
  injectTurnContext,
16
17
  isGroupChatType,
@@ -18,6 +19,8 @@ import {
18
19
  stripChannelCapabilityContext,
19
20
  stripChannelTurnContext,
20
21
  stripInboundActorContext,
22
+ stripInjectedContext,
23
+ stripNowScratchpad,
21
24
  stripTemporalContext,
22
25
  } from "../daemon/conversation-runtime-assembly.js";
23
26
  import type { Message } from "../providers/types.js";
@@ -1318,6 +1321,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1318
1321
  canonicalActorIdentity: "user-1",
1319
1322
  trustClass: "guardian",
1320
1323
  } as InboundActorContext,
1324
+ nowScratchpad: "Current focus: shipping PR 3",
1321
1325
  isNonInteractive: true,
1322
1326
  };
1323
1327
 
@@ -1337,6 +1341,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1337
1341
  expect(allText).toContain("<turn_context>");
1338
1342
  expect(allText).toContain("<inbound_actor_context>");
1339
1343
  expect(allText).toContain("<non_interactive_context>");
1344
+ expect(allText).toContain("<now_scratchpad>");
1340
1345
  });
1341
1346
 
1342
1347
  test("explicit mode: 'full' behaves the same as default", () => {
@@ -1353,6 +1358,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1353
1358
  expect(allText).toContain("<temporal_context>");
1354
1359
  expect(allText).toContain("<channel_command_context>");
1355
1360
  expect(allText).toContain("<active_workspace>");
1361
+ expect(allText).toContain("<now_scratchpad>");
1356
1362
  });
1357
1363
 
1358
1364
  test("minimal mode skips high-token optional blocks", () => {
@@ -1370,6 +1376,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1370
1376
  expect(allText).not.toContain("<temporal_context>");
1371
1377
  expect(allText).not.toContain("<channel_command_context>");
1372
1378
  expect(allText).not.toContain("<active_workspace>");
1379
+ expect(allText).not.toContain("<now_scratchpad>");
1373
1380
  });
1374
1381
 
1375
1382
  test("minimal mode preserves safety-critical blocks", () => {
@@ -1417,3 +1424,223 @@ describe("applyRuntimeInjections — injection mode", () => {
1417
1424
  expect(texts).toContain("Hello");
1418
1425
  });
1419
1426
  });
1427
+
1428
+ // ---------------------------------------------------------------------------
1429
+ // injectNowScratchpad
1430
+ // ---------------------------------------------------------------------------
1431
+
1432
+ describe("injectNowScratchpad", () => {
1433
+ const baseUserMessage: Message = {
1434
+ role: "user",
1435
+ content: [{ type: "text", text: "What should I work on?" }],
1436
+ };
1437
+
1438
+ test("appends now_scratchpad block to user message", () => {
1439
+ const result = injectNowScratchpad(
1440
+ baseUserMessage,
1441
+ "Current focus: shipping PR 3",
1442
+ );
1443
+ expect(result.content.length).toBe(2);
1444
+ // Original content comes first
1445
+ expect((result.content[0] as { type: "text"; text: string }).text).toBe(
1446
+ "What should I work on?",
1447
+ );
1448
+ // Scratchpad is appended (not prepended)
1449
+ const injected = result.content[1];
1450
+ expect(injected.type).toBe("text");
1451
+ const text = (injected as { type: "text"; text: string }).text;
1452
+ expect(text).toBe(
1453
+ "<now_scratchpad>\nCurrent focus: shipping PR 3\n</now_scratchpad>",
1454
+ );
1455
+ });
1456
+
1457
+ test("preserves existing multi-block content and appends at end", () => {
1458
+ const multiBlockMessage: Message = {
1459
+ role: "user",
1460
+ content: [
1461
+ { type: "text", text: "First block" },
1462
+ { type: "text", text: "Second block" },
1463
+ ],
1464
+ };
1465
+
1466
+ const result = injectNowScratchpad(multiBlockMessage, "scratchpad notes");
1467
+ expect(result.content.length).toBe(3);
1468
+ expect((result.content[0] as { type: "text"; text: string }).text).toBe(
1469
+ "First block",
1470
+ );
1471
+ expect((result.content[1] as { type: "text"; text: string }).text).toBe(
1472
+ "Second block",
1473
+ );
1474
+ expect(
1475
+ (result.content[2] as { type: "text"; text: string }).text,
1476
+ ).toContain("<now_scratchpad>");
1477
+ });
1478
+ });
1479
+
1480
+ // ---------------------------------------------------------------------------
1481
+ // stripNowScratchpad
1482
+ // ---------------------------------------------------------------------------
1483
+
1484
+ describe("stripNowScratchpad", () => {
1485
+ test("strips now_scratchpad blocks from user messages", () => {
1486
+ const messages: Message[] = [
1487
+ {
1488
+ role: "user",
1489
+ content: [
1490
+ { type: "text", text: "Hello" },
1491
+ {
1492
+ type: "text",
1493
+ text: "<now_scratchpad>\nSome notes\n</now_scratchpad>",
1494
+ },
1495
+ ],
1496
+ },
1497
+ {
1498
+ role: "assistant",
1499
+ content: [{ type: "text", text: "Hi there" }],
1500
+ },
1501
+ ];
1502
+
1503
+ const result = stripNowScratchpad(messages);
1504
+
1505
+ expect(result.length).toBe(2);
1506
+ expect(result[0].content.length).toBe(1);
1507
+ expect((result[0].content[0] as { type: "text"; text: string }).text).toBe(
1508
+ "Hello",
1509
+ );
1510
+ // Assistant message untouched
1511
+ expect(result[1].content.length).toBe(1);
1512
+ });
1513
+
1514
+ test("removes user messages that only contain now_scratchpad", () => {
1515
+ const messages: Message[] = [
1516
+ {
1517
+ role: "user",
1518
+ content: [
1519
+ {
1520
+ type: "text",
1521
+ text: "<now_scratchpad>\nSome notes\n</now_scratchpad>",
1522
+ },
1523
+ ],
1524
+ },
1525
+ ];
1526
+
1527
+ const result = stripNowScratchpad(messages);
1528
+ expect(result.length).toBe(0);
1529
+ });
1530
+
1531
+ test("leaves messages without now_scratchpad untouched", () => {
1532
+ const messages: Message[] = [
1533
+ {
1534
+ role: "user",
1535
+ content: [{ type: "text", text: "Normal message" }],
1536
+ },
1537
+ ];
1538
+
1539
+ const result = stripNowScratchpad(messages);
1540
+ expect(result.length).toBe(1);
1541
+ expect(result[0]).toBe(messages[0]); // Same reference — untouched
1542
+ });
1543
+ });
1544
+
1545
+ // ---------------------------------------------------------------------------
1546
+ // stripInjectedContext removes now_scratchpad blocks
1547
+ // ---------------------------------------------------------------------------
1548
+
1549
+ describe("stripInjectedContext with now_scratchpad", () => {
1550
+ test("strips now_scratchpad blocks alongside other injections", () => {
1551
+ const messages: Message[] = [
1552
+ {
1553
+ role: "user",
1554
+ content: [
1555
+ {
1556
+ type: "text",
1557
+ text: "<channel_capabilities>\nchannel: telegram\n</channel_capabilities>",
1558
+ },
1559
+ { type: "text", text: "Hello" },
1560
+ {
1561
+ type: "text",
1562
+ text: "<now_scratchpad>\nCurrent focus\n</now_scratchpad>",
1563
+ },
1564
+ ],
1565
+ },
1566
+ ];
1567
+
1568
+ const result = stripInjectedContext(messages);
1569
+ expect(result.length).toBe(1);
1570
+ expect(result[0].content.length).toBe(1);
1571
+ expect((result[0].content[0] as { type: "text"; text: string }).text).toBe(
1572
+ "Hello",
1573
+ );
1574
+ });
1575
+ });
1576
+
1577
+ // ---------------------------------------------------------------------------
1578
+ // applyRuntimeInjections with nowScratchpad
1579
+ // ---------------------------------------------------------------------------
1580
+
1581
+ describe("applyRuntimeInjections with nowScratchpad", () => {
1582
+ const baseMessages: Message[] = [
1583
+ {
1584
+ role: "user",
1585
+ content: [{ type: "text", text: "What should I do?" }],
1586
+ },
1587
+ ];
1588
+
1589
+ test("injects now_scratchpad block when provided", () => {
1590
+ const result = applyRuntimeInjections(baseMessages, {
1591
+ nowScratchpad: "Current focus: fix the bug",
1592
+ });
1593
+
1594
+ expect(result.length).toBe(1);
1595
+ expect(result[0].content.length).toBe(2);
1596
+ const injected = result[0].content[1];
1597
+ const text = (injected as { type: "text"; text: string }).text;
1598
+ expect(text).toContain("<now_scratchpad>");
1599
+ expect(text).toContain("Current focus: fix the bug");
1600
+ });
1601
+
1602
+ test("appended block appears after user's original text content", () => {
1603
+ const result = applyRuntimeInjections(baseMessages, {
1604
+ nowScratchpad: "scratchpad notes",
1605
+ });
1606
+
1607
+ // Original text is first
1608
+ expect(
1609
+ (result[0].content[0] as { type: "text"; text: string }).text,
1610
+ ).toBe("What should I do?");
1611
+ // Scratchpad is appended after
1612
+ expect(
1613
+ (result[0].content[1] as { type: "text"; text: string }).text,
1614
+ ).toContain("<now_scratchpad>");
1615
+ });
1616
+
1617
+ test("does not inject when nowScratchpad is null", () => {
1618
+ const result = applyRuntimeInjections(baseMessages, {
1619
+ nowScratchpad: null,
1620
+ });
1621
+
1622
+ expect(result.length).toBe(1);
1623
+ expect(result[0].content.length).toBe(1);
1624
+ });
1625
+
1626
+ test("does not inject when nowScratchpad is omitted", () => {
1627
+ const result = applyRuntimeInjections(baseMessages, {});
1628
+
1629
+ expect(result.length).toBe(1);
1630
+ expect(result[0].content.length).toBe(1);
1631
+ });
1632
+
1633
+ test("skipped in minimal mode", () => {
1634
+ const result = applyRuntimeInjections(baseMessages, {
1635
+ nowScratchpad: "Current focus: fix the bug",
1636
+ mode: "minimal",
1637
+ });
1638
+
1639
+ const allText = result[0].content
1640
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
1641
+ .map((b) => b.text)
1642
+ .join("\n");
1643
+
1644
+ expect(allText).not.toContain("<now_scratchpad>");
1645
+ });
1646
+ });
@@ -130,7 +130,6 @@ mock.module("../memory/retriever.js", () => ({
130
130
  injectedText: "",
131
131
 
132
132
  semanticHits: 0,
133
- recencyHits: 0,
134
133
  injectedTokens: 0,
135
134
  latencyMs: 0,
136
135
  }),
@@ -137,7 +137,6 @@ mock.module("../memory/retriever.js", () => ({
137
137
  injectedText: "",
138
138
 
139
139
  semanticHits: 0,
140
- recencyHits: 0,
141
140
  injectedTokens: 0,
142
141
  latencyMs: 0,
143
142
  }),
@@ -149,7 +149,6 @@ mock.module("../memory/retriever.js", () => ({
149
149
  model: "mock",
150
150
  injectedText: "",
151
151
  semanticHits: 0,
152
- recencyHits: 0,
153
152
  mergedCount: 0,
154
153
  selectedCount: 0,
155
154
  injectedTokens: 0,
@@ -145,7 +145,6 @@ mock.module("../memory/retriever.js", () => ({
145
145
  model: "mock",
146
146
  injectedText: "",
147
147
  semanticHits: 0,
148
- recencyHits: 0,
149
148
  mergedCount: 0,
150
149
  selectedCount: 0,
151
150
  injectedTokens: 0,
@@ -84,6 +84,9 @@ describe("local CES discovery", () => {
84
84
  if (result.mode === "unavailable") {
85
85
  expect(result.reason).toContain("CES executable not found");
86
86
  expect(result.mode).toBe("unavailable");
87
+ } else if (result.mode === "local-source") {
88
+ // Source entry point exists in the monorepo — verify the success shape.
89
+ expect(result.sourcePath).toBeTruthy();
87
90
  } else {
88
91
  // Binary exists in this environment — verify the success shape.
89
92
  expect(result.mode).toBe("local");
@@ -95,9 +98,9 @@ describe("local CES discovery", () => {
95
98
 
96
99
  test("never returns a fallback or in-process mode", () => {
97
100
  const result = discoverLocalCes();
98
- // The result must be either "local" (with a valid path) or "unavailable".
101
+ // The result must be "local", "local-source", or "unavailable".
99
102
  // There must never be a fallback mode like "in-process" or "degraded".
100
- expect(["local", "unavailable"]).toContain(result.mode);
103
+ expect(["local", "local-source", "unavailable"]).toContain(result.mode);
101
104
  });
102
105
  });
103
106
 
@@ -2,8 +2,8 @@
2
2
  * Tests for CES (Credential Execution Service) feature gates.
3
3
  *
4
4
  * Verifies:
5
- * - All CES flags default to disabled (safe dark-launch).
6
- * - Each flag can be enabled independently via config overrides.
5
+ * - Each CES flag defaults to its registry-declared value.
6
+ * - Each flag can be toggled independently via config overrides.
7
7
  * - Enabling CES flags does not implicitly change unrelated approval
8
8
  * behavior or existing feature flags.
9
9
  */
@@ -54,28 +54,37 @@ const ALL_CES_FLAG_KEYS = [
54
54
  CES_MANAGED_SIDECAR_FLAG_KEY,
55
55
  ] as const;
56
56
 
57
- /** All CES predicate functions paired with their flag keys. */
57
+ /** All CES predicate functions paired with their flag keys and expected defaults. */
58
58
  const ALL_CES_PREDICATES = [
59
- { name: "isCesToolsEnabled", fn: isCesToolsEnabled, key: CES_TOOLS_FLAG_KEY },
59
+ {
60
+ name: "isCesToolsEnabled",
61
+ fn: isCesToolsEnabled,
62
+ key: CES_TOOLS_FLAG_KEY,
63
+ defaultEnabled: false,
64
+ },
60
65
  {
61
66
  name: "isCesShellLockdownEnabled",
62
67
  fn: isCesShellLockdownEnabled,
63
68
  key: CES_SHELL_LOCKDOWN_FLAG_KEY,
69
+ defaultEnabled: false,
64
70
  },
65
71
  {
66
72
  name: "isCesSecureInstallEnabled",
67
73
  fn: isCesSecureInstallEnabled,
68
74
  key: CES_SECURE_INSTALL_FLAG_KEY,
75
+ defaultEnabled: false,
69
76
  },
70
77
  {
71
78
  name: "isCesGrantAuditEnabled",
72
79
  fn: isCesGrantAuditEnabled,
73
80
  key: CES_GRANT_AUDIT_FLAG_KEY,
81
+ defaultEnabled: false,
74
82
  },
75
83
  {
76
84
  name: "isCesManagedSidecarEnabled",
77
85
  fn: isCesManagedSidecarEnabled,
78
86
  key: CES_MANAGED_SIDECAR_FLAG_KEY,
87
+ defaultEnabled: true,
79
88
  },
80
89
  ] as const;
81
90
 
@@ -92,21 +101,23 @@ describe("CES flag key format", () => {
92
101
  });
93
102
 
94
103
  // ---------------------------------------------------------------------------
95
- // Default-safe: all CES flags disabled by default
104
+ // Defaults: each CES flag matches its registry-declared default
96
105
  // ---------------------------------------------------------------------------
97
106
 
98
- describe("CES flags default safely (all disabled)", () => {
99
- for (const { name, fn } of ALL_CES_PREDICATES) {
100
- test(`${name} returns false with no config overrides`, () => {
107
+ describe("CES flags match registry defaults", () => {
108
+ for (const { name, fn, defaultEnabled } of ALL_CES_PREDICATES) {
109
+ test(`${name} returns ${defaultEnabled} with no config overrides`, () => {
101
110
  const config = makeConfig();
102
- expect(fn(config)).toBe(false);
111
+ expect(fn(config)).toBe(defaultEnabled);
103
112
  });
104
113
  }
105
114
 
106
- for (const key of ALL_CES_FLAG_KEYS) {
107
- test(`isAssistantFeatureFlagEnabled('${key}') returns false with no overrides`, () => {
115
+ for (const pred of ALL_CES_PREDICATES) {
116
+ test(`isAssistantFeatureFlagEnabled('${pred.key}') returns ${pred.defaultEnabled} with no overrides`, () => {
108
117
  const config = makeConfig();
109
- expect(isAssistantFeatureFlagEnabled(key, config)).toBe(false);
118
+ expect(isAssistantFeatureFlagEnabled(pred.key, config)).toBe(
119
+ pred.defaultEnabled,
120
+ );
110
121
  });
111
122
  }
112
123
  });
@@ -115,7 +126,7 @@ describe("CES flags default safely (all disabled)", () => {
115
126
  // Independent enablement: each flag can be enabled without affecting others
116
127
  // ---------------------------------------------------------------------------
117
128
 
118
- describe("CES flags can be enabled independently", () => {
129
+ describe("CES flags can be toggled independently", () => {
119
130
  for (const { name, fn, key } of ALL_CES_PREDICATES) {
120
131
  test(`enabling ${key} makes ${name} return true`, () => {
121
132
  _setOverridesForTesting({ [key]: true });
@@ -123,12 +134,16 @@ describe("CES flags can be enabled independently", () => {
123
134
  expect(fn(config)).toBe(true);
124
135
  });
125
136
 
126
- test(`enabling ${key} does not enable other CES flags`, () => {
137
+ test(`enabling ${key} does not change other CES flags from their defaults`, () => {
127
138
  _setOverridesForTesting({ [key]: true });
128
139
  const config = makeConfig();
129
- for (const { fn: otherFn, key: otherKey } of ALL_CES_PREDICATES) {
140
+ for (const {
141
+ fn: otherFn,
142
+ key: otherKey,
143
+ defaultEnabled,
144
+ } of ALL_CES_PREDICATES) {
130
145
  if (otherKey === key) continue;
131
- expect(otherFn(config)).toBe(false);
146
+ expect(otherFn(config)).toBe(defaultEnabled);
132
147
  }
133
148
  });
134
149
  }
@@ -446,9 +446,9 @@ describe("managed OAuth materialization through CES sidecar", () => {
446
446
  // ---------------------------------------------------------------------------
447
447
 
448
448
  describe("feature-flag rollback safety", () => {
449
- test("managed sidecar flag defaults to false (safe dark-launch)", () => {
449
+ test("managed sidecar flag defaults to true (enabled by default)", () => {
450
450
  const config = makeConfig();
451
- expect(isCesManagedSidecarEnabled(config)).toBe(false);
451
+ expect(isCesManagedSidecarEnabled(config)).toBe(true);
452
452
  });
453
453
 
454
454
  test("managed sidecar flag can be explicitly enabled", () => {
@@ -66,7 +66,7 @@ mock.module("../security/secure-keys.js", () => {
66
66
  setSecureKeyAsync: async (key: string, value: string) =>
67
67
  syncSet(key, value),
68
68
  deleteSecureKeyAsync: async (key: string) => syncDelete(key),
69
- listSecureKeysAsync: async () => [...storedKeys.keys()],
69
+ listSecureKeysAsync: async () => ({ accounts: [...storedKeys.keys()], unreachable: false }),
70
70
  };
71
71
  });
72
72
 
@@ -180,8 +180,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
180
180
  "calls/twilio-rest.ts", // Twilio REST API credential lookup
181
181
  "calls/fish-audio-client.ts", // Fish Audio TTS API key lookup
182
182
  "runtime/channel-invite-transports/telegram.ts", // Telegram invite transport bot token lookup
183
- "cli/commands/keys.ts", // CLI credential management commands
184
- "cli/commands/credentials.ts", // CLI credential management commands
183
+ "cli/lib/daemon-credential-client.ts", // CLI-to-daemon credential routing intermediary
185
184
  "messaging/providers/telegram-bot/adapter.ts", // Telegram bot token lookup for connectivity check
186
185
  "runtime/channel-readiness-service.ts", // channel readiness probes for Telegram connectivity
187
186
  "messaging/providers/whatsapp/adapter.ts", // WhatsApp credential lookup for connectivity check
@@ -198,9 +197,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
198
197
  "daemon/conversation-messaging.ts", // credential storage during session messaging
199
198
  "runtime/routes/settings-routes.ts", // settings routes OAuth credential lookup (client_secret)
200
199
  "oauth/oauth-store.ts", // OAuth provider disconnect (delete stored tokens)
201
- "cli/commands/oauth/connections.ts", // CLI OAuth connection delete (legacy credential cleanup)
202
200
  "oauth/manual-token-connection.ts", // manual-token provider backfill (keychain credential existence check)
203
- "cli/commands/doctor.ts", // CLI diagnostic API key verification via secure storage
204
201
  "workspace/provider-commit-message-generator.ts", // commit message generation provider key lookup
205
202
  "config/bundled-skills/transcribe/tools/transcribe-media.ts", // transcription tool API key lookup
206
203
  "config/bundled-skills/image-studio/tools/media-generate-image.ts", // image generation tool API key lookup
@@ -213,7 +210,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
213
210
  "memory/embedding-backend.ts", // embedding backend API key lookup
214
211
  "daemon/providers-setup.ts", // provider initialization API key lookup
215
212
  "workspace/migrations/006-services-config.ts", // services config migration reads provider API keys
216
- "cli/commands/avatar.ts", // CLI avatar generation API key lookup
213
+ "workspace/migrations/018-rekey-compound-credential-keys.ts", // re-key compound credential storage keys
217
214
  "config/bundled-skills/slack/tools/shared.ts", // Slack skill bot token lookup
218
215
  "daemon/conversation-process.ts", // masked provider key display
219
216
  "daemon/handlers/config-model.ts", // masked provider key display
@@ -40,9 +40,10 @@ mock.module("../security/secure-keys.js", () => ({
40
40
  }
41
41
  return "not-found";
42
42
  },
43
- listSecureKeysAsync: async (): Promise<string[]> => {
44
- return [...secureKeyStore.keys()];
45
- },
43
+ listSecureKeysAsync: async () => ({
44
+ accounts: [...secureKeyStore.keys()],
45
+ unreachable: false,
46
+ }),
46
47
  getSecureKeyAsync: async (account: string): Promise<string | undefined> => {
47
48
  return secureKeyStore.get(account);
48
49
  },
@@ -0,0 +1,123 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { credentialKey } from "../security/credential-key.js";
4
+
5
+ let fallbackValues = new Map<string, string>();
6
+
7
+ mock.module("../config/env.js", () => ({
8
+ getRuntimeHttpHost: () => "127.0.0.1",
9
+ getRuntimeHttpPort: () => 4123,
10
+ }));
11
+
12
+ mock.module("../daemon/daemon-control.js", () => ({
13
+ healthCheckHost: (host: string) => host,
14
+ isHttpHealthy: async () => true,
15
+ }));
16
+
17
+ mock.module("../runtime/auth/token-service.js", () => ({
18
+ initAuthSigningKey: () => {},
19
+ loadOrCreateSigningKey: () => "signing-key",
20
+ mintDaemonDeliveryToken: () => "daemon-token",
21
+ }));
22
+
23
+ mock.module("../security/secure-keys.js", () => ({
24
+ deleteSecureKeyAsync: async () => "deleted" as const,
25
+ getSecureKeyAsync: async (account: string) => fallbackValues.get(account),
26
+ getSecureKeyResultAsync: async (account: string) => ({
27
+ value: fallbackValues.get(account),
28
+ unreachable: false,
29
+ }),
30
+ setSecureKeyAsync: async () => true,
31
+ }));
32
+
33
+ mock.module("../util/logger.js", () => ({
34
+ getLogger: () =>
35
+ new Proxy({} as Record<string, unknown>, {
36
+ get: () => () => {},
37
+ }),
38
+ }));
39
+
40
+ import {
41
+ getSecureKeyResultViaDaemon,
42
+ getSecureKeyViaDaemon,
43
+ } from "../cli/lib/daemon-credential-client.js";
44
+
45
+ const originalFetch = globalThis.fetch;
46
+ const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
47
+
48
+ function getRequestBody(index = 0): Record<string, unknown> {
49
+ const body = fetchCalls[index]?.init?.body;
50
+ if (typeof body !== "string") {
51
+ throw new Error("Expected fetch body to be a JSON string");
52
+ }
53
+ return JSON.parse(body) as Record<string, unknown>;
54
+ }
55
+
56
+ beforeEach(() => {
57
+ fallbackValues = new Map();
58
+ fetchCalls.length = 0;
59
+ const mockFetch = mock(
60
+ async (input: string | URL | Request, init?: RequestInit) => {
61
+ const url =
62
+ typeof input === "string"
63
+ ? input
64
+ : input instanceof URL
65
+ ? input.toString()
66
+ : input.url;
67
+ fetchCalls.push({ url, init });
68
+ return new Response(
69
+ JSON.stringify({ found: true, value: "secret-value" }),
70
+ {
71
+ status: 200,
72
+ headers: { "Content-Type": "application/json" },
73
+ },
74
+ );
75
+ },
76
+ );
77
+ globalThis.fetch = mockFetch as unknown as typeof globalThis.fetch;
78
+ });
79
+
80
+ afterEach(() => {
81
+ globalThis.fetch = originalFetch;
82
+ });
83
+
84
+ describe("daemon credential read requests", () => {
85
+ test("keeps provider secrets on the api_key path", async () => {
86
+ const value = await getSecureKeyViaDaemon("openai");
87
+
88
+ expect(value).toBe("secret-value");
89
+ expect(fetchCalls).toHaveLength(1);
90
+ expect(fetchCalls[0]?.url).toBe("http://127.0.0.1:4123/v1/secrets/read");
91
+ expect(getRequestBody()).toEqual({
92
+ type: "api_key",
93
+ name: "openai",
94
+ reveal: true,
95
+ });
96
+ });
97
+
98
+ test("converts canonical credential keys into credential reads", async () => {
99
+ const value = await getSecureKeyViaDaemon(
100
+ credentialKey("vellum", "platform_base_url"),
101
+ );
102
+
103
+ expect(value).toBe("secret-value");
104
+ expect(getRequestBody()).toEqual({
105
+ type: "credential",
106
+ name: "vellum:platform_base_url",
107
+ reveal: true,
108
+ });
109
+ });
110
+
111
+ test("preserves compound credential service names on metadata reads", async () => {
112
+ const result = await getSecureKeyResultViaDaemon(
113
+ credentialKey("integration:google", "client_secret"),
114
+ );
115
+
116
+ expect(result).toEqual({ value: "secret-value", unreachable: false });
117
+ expect(getRequestBody()).toEqual({
118
+ type: "credential",
119
+ name: "integration:google:client_secret",
120
+ reveal: true,
121
+ });
122
+ });
123
+ });
@@ -128,6 +128,7 @@ describe("TwiML parameter propagation", () => {
128
128
  transcriptionProvider: "deepgram",
129
129
  ttsProvider: "google",
130
130
  voice: "en-US-Standard-A",
131
+ interruptSensitivity: "low",
131
132
  };
132
133
 
133
134
  test("includes verificationSessionId as Parameter when provided", () => {