@vellumai/assistant 0.5.7 → 0.5.8

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 (197) hide show
  1. package/Dockerfile +2 -1
  2. package/docker-entrypoint.sh +9 -0
  3. package/docs/architecture/memory.md +13 -11
  4. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  6. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  7. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  8. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
  9. package/package.json +1 -1
  10. package/src/__tests__/approval-cascade.test.ts +0 -1
  11. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
  14. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  15. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  16. package/src/__tests__/config-schema.test.ts +2 -0
  17. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  18. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  19. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  20. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  21. package/src/__tests__/conversation-error.test.ts +15 -1
  22. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  23. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  24. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  25. package/src/__tests__/conversation-queue.test.ts +0 -1
  26. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  27. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  28. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  29. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  30. package/src/__tests__/credential-execution-client.test.ts +5 -2
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
  33. package/src/__tests__/credential-security-e2e.test.ts +1 -1
  34. package/src/__tests__/credential-security-invariants.test.ts +2 -5
  35. package/src/__tests__/credentials-cli.test.ts +4 -3
  36. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  37. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  38. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  39. package/src/__tests__/journal-context.test.ts +335 -0
  40. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  41. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  42. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  43. package/src/__tests__/memory-regressions.test.ts +408 -363
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  45. package/src/__tests__/non-member-access-request.test.ts +2 -2
  46. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  47. package/src/__tests__/oauth-cli.test.ts +5 -1
  48. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  49. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  50. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  51. package/src/__tests__/relay-server.test.ts +1 -2
  52. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  53. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  54. package/src/__tests__/secure-keys.test.ts +18 -15
  55. package/src/__tests__/skill-memory.test.ts +17 -3
  56. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  57. package/src/__tests__/stt-hints.test.ts +437 -0
  58. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  59. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  60. package/src/__tests__/voice-quality.test.ts +58 -0
  61. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  62. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
  63. package/src/acp/agent-process.ts +9 -1
  64. package/src/agent/loop.ts +1 -1
  65. package/src/approvals/guardian-request-resolvers.ts +164 -38
  66. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  67. package/src/calls/call-controller.ts +9 -5
  68. package/src/calls/fish-audio-client.ts +26 -14
  69. package/src/calls/stt-hints.ts +189 -0
  70. package/src/calls/tts-text-sanitizer.ts +61 -0
  71. package/src/calls/twilio-routes.ts +32 -4
  72. package/src/calls/voice-quality.ts +15 -3
  73. package/src/calls/voice-session-bridge.ts +1 -0
  74. package/src/cli/commands/avatar.ts +2 -2
  75. package/src/cli/commands/credentials.ts +110 -94
  76. package/src/cli/commands/doctor.ts +2 -2
  77. package/src/cli/commands/keys.ts +7 -7
  78. package/src/cli/commands/memory.ts +1 -1
  79. package/src/cli/commands/oauth/connections.ts +11 -29
  80. package/src/cli/commands/oauth/platform.ts +389 -43
  81. package/src/cli/lib/daemon-credential-client.ts +284 -0
  82. package/src/cli.ts +1 -1
  83. package/src/config/bundled-skills/AGENTS.md +34 -0
  84. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  85. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  86. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  87. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  88. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  89. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  90. package/src/config/bundled-skills/settings/TOOLS.json +46 -1
  91. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  92. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  93. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  94. package/src/config/bundled-tool-registry.ts +4 -0
  95. package/src/config/defaults.ts +0 -2
  96. package/src/config/env-registry.ts +4 -4
  97. package/src/config/env.ts +14 -1
  98. package/src/config/feature-flag-registry.json +1 -1
  99. package/src/config/loader.ts +8 -11
  100. package/src/config/schema.ts +5 -16
  101. package/src/config/schemas/calls.ts +17 -0
  102. package/src/config/schemas/inference.ts +2 -2
  103. package/src/config/schemas/journal.ts +16 -0
  104. package/src/config/schemas/memory-processing.ts +2 -2
  105. package/src/config/types.ts +1 -0
  106. package/src/contacts/contact-store.ts +2 -2
  107. package/src/credential-execution/executable-discovery.ts +1 -1
  108. package/src/credential-execution/startup-timeout.ts +36 -0
  109. package/src/daemon/approval-generators.ts +3 -9
  110. package/src/daemon/conversation-error.ts +13 -1
  111. package/src/daemon/conversation-memory.ts +1 -2
  112. package/src/daemon/conversation-process.ts +18 -1
  113. package/src/daemon/conversation-surfaces.ts +30 -1
  114. package/src/daemon/conversation.ts +20 -9
  115. package/src/daemon/guardian-action-generators.ts +3 -9
  116. package/src/daemon/lifecycle.ts +18 -11
  117. package/src/daemon/message-types/conversations.ts +1 -0
  118. package/src/daemon/server.ts +2 -3
  119. package/src/memory/app-store.ts +31 -0
  120. package/src/memory/db-init.ts +4 -0
  121. package/src/memory/indexer.ts +19 -10
  122. package/src/memory/items-extractor.ts +315 -322
  123. package/src/memory/job-handlers/summarization.ts +26 -16
  124. package/src/memory/jobs-store.ts +33 -1
  125. package/src/memory/journal-memory.ts +214 -0
  126. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  127. package/src/memory/migrations/index.ts +1 -0
  128. package/src/memory/migrations/registry.ts +8 -0
  129. package/src/memory/retriever.test.ts +37 -25
  130. package/src/memory/retriever.ts +24 -49
  131. package/src/memory/schema/memory-core.ts +2 -0
  132. package/src/memory/search/formatting.ts +7 -44
  133. package/src/memory/search/staleness.ts +4 -0
  134. package/src/memory/search/tier-classifier.ts +10 -2
  135. package/src/memory/search/types.ts +2 -5
  136. package/src/memory/task-memory-cleanup.ts +4 -3
  137. package/src/notifications/adapters/slack.ts +168 -6
  138. package/src/notifications/broadcaster.ts +1 -0
  139. package/src/notifications/copy-composer.ts +59 -2
  140. package/src/notifications/signal.ts +2 -0
  141. package/src/notifications/types.ts +2 -0
  142. package/src/prompts/journal-context.ts +133 -0
  143. package/src/prompts/persona-resolver.ts +80 -24
  144. package/src/prompts/system-prompt.ts +8 -0
  145. package/src/prompts/templates/SOUL.md +10 -0
  146. package/src/providers/provider-send-message.ts +3 -32
  147. package/src/providers/registry.ts +2 -139
  148. package/src/providers/types.ts +1 -1
  149. package/src/runtime/access-request-helper.ts +4 -0
  150. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  151. package/src/runtime/auth/route-policy.ts +2 -0
  152. package/src/runtime/gateway-client.ts +47 -4
  153. package/src/runtime/guardian-decision-types.ts +45 -4
  154. package/src/runtime/http-server.ts +5 -2
  155. package/src/runtime/routes/access-request-decision.ts +2 -2
  156. package/src/runtime/routes/app-management-routes.ts +2 -1
  157. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  158. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  159. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  160. package/src/runtime/routes/debug-routes.ts +12 -9
  161. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  162. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  163. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  164. package/src/runtime/routes/identity-routes.ts +1 -1
  165. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  166. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  167. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  168. package/src/runtime/routes/integrations/twilio.ts +52 -10
  169. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  170. package/src/runtime/routes/memory-item-routes.ts +25 -11
  171. package/src/runtime/routes/secret-routes.ts +141 -10
  172. package/src/runtime/routes/tts-routes.ts +11 -1
  173. package/src/security/ces-credential-client.ts +18 -9
  174. package/src/security/ces-rpc-credential-backend.ts +4 -3
  175. package/src/security/credential-backend.ts +10 -4
  176. package/src/security/secure-keys.ts +21 -4
  177. package/src/skills/catalog-install.ts +4 -36
  178. package/src/skills/skill-memory.ts +1 -0
  179. package/src/subagent/manager.ts +2 -5
  180. package/src/tools/acp/spawn.ts +78 -1
  181. package/src/tools/credentials/vault.ts +5 -3
  182. package/src/tools/memory/definitions.ts +3 -2
  183. package/src/tools/memory/handlers.ts +10 -7
  184. package/src/tools/terminal/safe-env.ts +1 -0
  185. package/src/util/browser.ts +15 -0
  186. package/src/util/platform.ts +1 -1
  187. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
  188. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
  189. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  190. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  191. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
  192. package/src/workspace/migrations/registry.ts +4 -0
  193. package/src/workspace/provider-commit-message-generator.ts +12 -21
  194. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  195. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  196. package/src/memory/search/lexical.ts +0 -48
  197. package/src/providers/failover.ts +0 -186
package/Dockerfile CHANGED
@@ -92,6 +92,7 @@ ENV IS_CONTAINERIZED=true
92
92
 
93
93
  # Copy from builder
94
94
  COPY --from=builder /app /app
95
+ RUN chmod +x /app/assistant/docker-entrypoint.sh
95
96
 
96
97
  # Run the daemon + http server
97
- CMD ["bun", "run", "src/daemon/main.ts"]
98
+ CMD ["/app/assistant/docker-entrypoint.sh"]
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ if [ "$(id -u)" = "0" ] && [ "${VELLUM_WORKSPACE_DIR:-}" = "/workspace" ] && [ -d /workspace ]; then
5
+ git config --global --add safe.directory /workspace >/dev/null 2>&1 || true
6
+ git config --global --add safe.directory '/workspace/*' >/dev/null 2>&1 || true
7
+ fi
8
+
9
+ exec bun run src/daemon/main.ts
@@ -273,7 +273,7 @@ The key distinction: normal compaction is a cost-optimized background process th
273
273
 
274
274
  1. Run a recall-heavy turn and inspect `memory_recalled` events in the client trace stream.
275
275
  2. Validate baseline counters:
276
- - `semanticHits`, `recencyHits`
276
+ - `semanticHits`
277
277
  - `tier1Count`, `tier2Count`
278
278
  - `hybridSearchLatencyMs`
279
279
  - `mergedCount`, `selectedCount`, `injectedTokens`, `latencyMs`
@@ -304,16 +304,18 @@ stateDiagram-v2
304
304
  Superseded --> Cleanup : cleanup_stale_superseded_items\n(delete from DB + Qdrant)
305
305
  ```
306
306
 
307
- **Item extraction** uses LLM-powered extraction (with pattern-based fallback) to identify memorable information from conversation messages. Each extracted item belongs to one of six kinds:
308
-
309
- | Kind | Description | Base Lifetime |
310
- | ------------ | ------------------------------------------------- | ------------- |
311
- | `identity` | Personal info, facts, relationships | 6 months |
312
- | `preference` | Likes, dislikes, preferred approaches/tools | 3 months |
313
- | `constraint` | Rules, requirements, directives | 1 month |
314
- | `project` | Project details, repos, tech stacks, action items | 2 weeks |
315
- | `decision` | Choices made, approaches selected | 2 weeks |
316
- | `event` | Deadlines, milestones, meetings, dates | 3 days |
307
+ **Item extraction** uses LLM-powered extraction (with pattern-based fallback) to identify memorable information from conversation messages. Each extracted item belongs to one of eight kinds:
308
+
309
+ | Kind | Description | Base Lifetime |
310
+ | ------------ | ------------------------------------------------------------------ | ------------- |
311
+ | `identity` | Personal info, facts, relationships | 6 months |
312
+ | `preference` | Likes, dislikes, preferred approaches/tools | 3 months |
313
+ | `journal` | Experiential reflections, journal-style notes, forward-looking items | 3 months |
314
+ | `constraint` | Rules, requirements, directives | 1 month |
315
+ | `project` | Project details, repos, tech stacks, action items | 2 weeks |
316
+ | `decision` | Choices made, approaches selected | 2 weeks |
317
+ | `event` | Deadlines, milestones, meetings, dates | 3 days |
318
+ | `capability` | Skill catalog entries (system-generated, not LLM-extracted) | never expires |
317
319
 
318
320
  **Supersession chains** replace the old conflict resolution system. When the LLM extracts a new item that updates an existing one, it sets `supersedes` to the old item's ID and `overrideConfidence` to one of three levels:
319
321
 
@@ -3,7 +3,7 @@
3
3
  * dependencies between index.ts and rpc.ts.
4
4
  */
5
5
 
6
- import { z } from "zod/v4";
6
+ import { z } from "zod";
7
7
 
8
8
  export const RpcErrorSchema = z.object({
9
9
  code: z.string(),
@@ -9,7 +9,7 @@
9
9
  * - Audit record summaries (materialization events)
10
10
  */
11
11
 
12
- import { z } from "zod/v4";
12
+ import { z } from "zod";
13
13
 
14
14
  // ---------------------------------------------------------------------------
15
15
  // Grant proposal types
@@ -20,7 +20,7 @@
20
20
  * is the platform-assigned connection identifier.
21
21
  */
22
22
 
23
- import { z } from "zod/v4";
23
+ import { z } from "zod";
24
24
 
25
25
  // ---------------------------------------------------------------------------
26
26
  // Handle type discriminator
@@ -7,7 +7,7 @@
7
7
  * module so that both sides can depend on it without circular references.
8
8
  */
9
9
 
10
- import { z } from "zod/v4";
10
+ import { z } from "zod";
11
11
  import { RpcErrorSchema } from "./error.js";
12
12
 
13
13
  // ---------------------------------------------------------------------------
@@ -33,7 +33,7 @@
33
33
  * - `list_credentials` — List all credential account names
34
34
  */
35
35
 
36
- import { z } from "zod/v4";
36
+ import { z } from "zod";
37
37
  import {
38
38
  AuditRecordSummarySchema,
39
39
  GrantProposalSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -162,7 +162,6 @@ mock.module("../memory/retriever.js", () => ({
162
162
  injectedText: "",
163
163
 
164
164
  semanticHits: 0,
165
- recencyHits: 0,
166
165
  injectedTokens: 0,
167
166
  latencyMs: 0,
168
167
  }),
@@ -61,7 +61,7 @@ mock.module("../security/secure-keys.js", () => ({
61
61
  getSecureKeyAsync: async (...args: unknown[]) => mockGetSecureKey(...args),
62
62
  setSecureKeyAsync: async () => true,
63
63
  deleteSecureKeyAsync: async () => "deleted",
64
- listSecureKeysAsync: async () => [],
64
+ listSecureKeysAsync: async () => ({ accounts: [], unreachable: false }),
65
65
  _resetBackend: () => {},
66
66
  }));
67
67
 
@@ -42,7 +42,6 @@ mock.module("../config/loader.js", () => {
42
42
  ui: {},
43
43
 
44
44
  provider: "anthropic",
45
- providerOrder: ["anthropic"],
46
45
  calls: {
47
46
  enabled: true,
48
47
  provider: "twilio",
@@ -185,15 +185,15 @@ describe("CesRpcCredentialBackend", () => {
185
185
  const result = await backend.list();
186
186
 
187
187
  expect(callFn).toHaveBeenCalledWith(CesRpcMethod.ListCredentials, {});
188
- expect(result).toEqual(["account-a", "account-b"]);
188
+ expect(result).toEqual({ accounts: ["account-a", "account-b"], unreachable: false });
189
189
  });
190
190
 
191
- test("returns empty array when RPC call throws", async () => {
191
+ test("returns unreachable when RPC call throws", async () => {
192
192
  callFn.mockRejectedValue(new Error("transport error"));
193
193
 
194
194
  const result = await backend.list();
195
195
 
196
- expect(result).toEqual([]);
196
+ expect(result).toEqual({ accounts: [], unreachable: true });
197
197
  });
198
198
  });
199
199
  });
@@ -0,0 +1,40 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { CesClient } from "../credential-execution/client.js";
4
+ import {
5
+ awaitCesClientWithTimeout,
6
+ DEFAULT_CES_STARTUP_TIMEOUT_MS,
7
+ } from "../credential-execution/startup-timeout.js";
8
+
9
+ describe("awaitCesClientWithTimeout", () => {
10
+ test("clears the fallback timer when the CES client resolves first", async () => {
11
+ const onTimeout = mock(() => {});
12
+ const client = { isReady: () => true } as unknown as CesClient;
13
+
14
+ const result = await awaitCesClientWithTimeout(Promise.resolve(client), {
15
+ timeoutMs: 25,
16
+ onTimeout,
17
+ });
18
+
19
+ expect(result).toBe(client);
20
+
21
+ await new Promise((resolve) => setTimeout(resolve, 40));
22
+ expect(onTimeout).not.toHaveBeenCalled();
23
+ });
24
+
25
+ test("returns undefined and runs the fallback handler when the timeout wins", async () => {
26
+ const onTimeout = mock(() => {});
27
+
28
+ const result = await awaitCesClientWithTimeout(new Promise(() => {}), {
29
+ timeoutMs: 10,
30
+ onTimeout,
31
+ });
32
+
33
+ expect(result).toBeUndefined();
34
+ expect(onTimeout).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ test("exports the daemon CES startup timeout constant", () => {
38
+ expect(DEFAULT_CES_STARTUP_TIMEOUT_MS).toBe(20_000);
39
+ });
40
+ });
@@ -204,7 +204,6 @@ describe("z.toJSONSchema integration", () => {
204
204
  expect(properties).toBeDefined();
205
205
  // Check that top-level keys are present
206
206
  expect(properties.services).toBeDefined();
207
- expect(properties.providerOrder).toBeDefined();
208
207
  expect(properties.maxTokens).toBeDefined();
209
208
  expect(properties.calls).toBeDefined();
210
209
  expect(properties.memory).toBeDefined();
@@ -638,6 +638,8 @@ describe("AssistantConfigSchema", () => {
638
638
  language: "en-US",
639
639
  transcriptionProvider: "Deepgram",
640
640
  ttsProvider: "elevenlabs",
641
+ hints: [],
642
+ interruptSensitivity: "low",
641
643
  },
642
644
  callerIdentity: {
643
645
  allowPerCallOverride: true,
@@ -129,7 +129,6 @@ mock.module("../memory/retriever.js", () => ({
129
129
  injectedText: "",
130
130
 
131
131
  semanticHits: 0,
132
- recencyHits: 0,
133
132
  injectedTokens: 0,
134
133
  latencyMs: 0,
135
134
  }),
@@ -168,7 +168,6 @@ mock.module("../memory/retriever.js", () => ({
168
168
  injectedText: "",
169
169
 
170
170
  semanticHits: 0,
171
- recencyHits: 0,
172
171
  injectedTokens: 0,
173
172
  latencyMs: 0,
174
173
  }),
@@ -199,7 +198,6 @@ mock.module("../daemon/conversation-memory.js", () => ({
199
198
  injectedText: "",
200
199
 
201
200
  semanticHits: 0,
202
- recencyHits: 0,
203
201
  injectedTokens: 0,
204
202
  latencyMs: 0,
205
203
  tier1Count: 0,
@@ -158,7 +158,6 @@ mock.module("../memory/retriever.js", () => ({
158
158
  injectedText: "",
159
159
 
160
160
  semanticHits: 0,
161
- recencyHits: 0,
162
161
  injectedTokens: 0,
163
162
  latencyMs: 0,
164
163
  }),
@@ -189,7 +188,6 @@ mock.module("../daemon/conversation-memory.js", () => ({
189
188
  injectedText: "",
190
189
 
191
190
  semanticHits: 0,
192
- recencyHits: 0,
193
191
  injectedTokens: 0,
194
192
  latencyMs: 0,
195
193
  tier1Count: 0,
@@ -614,7 +612,7 @@ describe("session-agent-loop", () => {
614
612
  });
615
613
 
616
614
  describe("LLM request log persistence", () => {
617
- test("record request log prefers the actual provider from failover", async () => {
615
+ test("record request log captures the actual provider name", async () => {
618
616
  const events: ServerMessage[] = [];
619
617
  const rawRequest = {
620
618
  model: "gpt-4.1",
@@ -768,7 +766,7 @@ describe("session-agent-loop", () => {
768
766
  });
769
767
 
770
768
  describe("usage accounting", () => {
771
- test("records the actual provider for failover-served usage", async () => {
769
+ test("records the actual provider for usage accounting", async () => {
772
770
  const events: ServerMessage[] = [];
773
771
 
774
772
  const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
@@ -154,7 +154,6 @@ mock.module("../memory/retriever.js", () => ({
154
154
  injectedText: "",
155
155
 
156
156
  semanticHits: 0,
157
- recencyHits: 0,
158
157
  injectedTokens: 0,
159
158
  latencyMs: 0,
160
159
  }),
@@ -6,7 +6,7 @@ import {
6
6
  classifyConversationError,
7
7
  isUserCancellation,
8
8
  } from "../daemon/conversation-error.js";
9
- import { ProviderError } from "../util/errors.js";
9
+ import { ProviderError, ProviderNotConfiguredError } from "../util/errors.js";
10
10
 
11
11
  describe("isUserCancellation", () => {
12
12
  it("returns false for non-AbortError even when abort flag is set", () => {
@@ -278,6 +278,20 @@ describe("classifyConversationError", () => {
278
278
  });
279
279
  });
280
280
 
281
+ describe("provider not configured errors", () => {
282
+ it("classifies ProviderNotConfiguredError as PROVIDER_NOT_CONFIGURED", () => {
283
+ const err = new ProviderNotConfiguredError("anthropic", []);
284
+ const result = classifyConversationError(err, baseCtx);
285
+ expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
286
+ expect(result.userMessage).toBe(
287
+ "No API key configured for inference. Add one in Settings to start chatting.",
288
+ );
289
+ expect(result.retryable).toBe(true);
290
+ expect(result.errorCategory).toBe("provider_not_configured");
291
+ expect(result.debugDetails).toBeDefined();
292
+ });
293
+ });
294
+
281
295
  describe("streaming corruption errors", () => {
282
296
  const cases = [
283
297
  "Unexpected event order, got message_start before receiving message_stop",
@@ -28,7 +28,7 @@ mock.module("../security/secure-keys.js", () => ({
28
28
  setSecureKeyAsync: async (key?: string, value?: string) =>
29
29
  setSecureKeyMock(key, value),
30
30
  deleteSecureKeyAsync: async () => "deleted" as const,
31
- listSecureKeysAsync: async () => [],
31
+ listSecureKeysAsync: async () => ({ accounts: [], unreachable: false }),
32
32
  _resetBackend: () => {},
33
33
  }));
34
34
 
@@ -119,7 +119,6 @@ mock.module("../memory/retriever.js", () => ({
119
119
  injectedText: "",
120
120
 
121
121
  semanticHits: 0,
122
- recencyHits: 0,
123
122
  injectedTokens: 0,
124
123
  latencyMs: 0,
125
124
  }),
@@ -188,7 +188,6 @@ mock.module("../memory/retriever.js", () => ({
188
188
  injectedText: "",
189
189
 
190
190
  semanticHits: 0,
191
- recencyHits: 0,
192
191
  injectedTokens: 0,
193
192
  latencyMs: 0,
194
193
  }),
@@ -204,7 +204,6 @@ mock.module("../memory/retriever.js", () => ({
204
204
  injectedText: "",
205
205
 
206
206
  semanticHits: 0,
207
- recencyHits: 0,
208
207
  injectedTokens: 0,
209
208
  latencyMs: 0,
210
209
  }),
@@ -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
  },