@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.
- package/Dockerfile +2 -1
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/memory.md +13 -11
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
- package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -5
- package/src/__tests__/credentials-cli.test.ts +4 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +2 -2
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secure-keys.test.ts +18 -15
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/call-controller.ts +9 -5
- package/src/calls/fish-audio-client.ts +26 -14
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +32 -4
- package/src/calls/voice-quality.ts +15 -3
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/credentials.ts +110 -94
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/platform.ts +389 -43
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +46 -1
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +4 -4
- package/src/config/env.ts +14 -1
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +8 -11
- package/src/config/schema.ts +5 -16
- package/src/config/schemas/calls.ts +17 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/types.ts +1 -0
- package/src/contacts/contact-store.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation.ts +20 -9
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/lifecycle.ts +18 -11
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/server.ts +2 -3
- package/src/memory/app-store.ts +31 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +315 -322
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +33 -1
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +80 -24
- package/src/prompts/system-prompt.ts +8 -0
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +2 -139
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/route-policy.ts +2 -0
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +5 -2
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +1 -1
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +25 -11
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/tts-routes.ts +11 -1
- package/src/security/ces-credential-client.ts +18 -9
- package/src/security/ces-rpc-credential-backend.ts +4 -3
- package/src/security/credential-backend.ts +10 -4
- package/src/security/secure-keys.ts +21 -4
- package/src/skills/catalog-install.ts +4 -36
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/util/browser.ts +15 -0
- package/src/util/platform.ts +1 -1
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/memory/search/lexical.ts +0 -48
- 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 ["
|
|
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
|
|
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
|
|
308
|
-
|
|
309
|
-
| Kind | Description
|
|
310
|
-
| ------------ |
|
|
311
|
-
| `identity` | Personal info, facts, relationships
|
|
312
|
-
| `preference` | Likes, dislikes, preferred approaches/tools
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
| `
|
|
316
|
-
| `
|
|
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
|
|
|
@@ -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
|
|
10
|
+
import { z } from "zod";
|
|
11
11
|
import { RpcErrorSchema } from "./error.js";
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -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
|
|
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();
|
|
@@ -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
|
|
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
|
|
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) => {
|
|
@@ -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
|
|
|
@@ -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
|
|
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
|
-
* -
|
|
6
|
-
* - Each flag can be
|
|
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
|
-
{
|
|
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
|
-
//
|
|
104
|
+
// Defaults: each CES flag matches its registry-declared default
|
|
96
105
|
// ---------------------------------------------------------------------------
|
|
97
106
|
|
|
98
|
-
describe("CES flags
|
|
99
|
-
for (const { name, fn } of ALL_CES_PREDICATES) {
|
|
100
|
-
test(`${name} returns
|
|
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(
|
|
111
|
+
expect(fn(config)).toBe(defaultEnabled);
|
|
103
112
|
});
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
for (const
|
|
107
|
-
test(`isAssistantFeatureFlagEnabled('${key}') returns
|
|
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(
|
|
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
|
|
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
|
|
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 {
|
|
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(
|
|
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
|
|
449
|
+
test("managed sidecar flag defaults to true (enabled by default)", () => {
|
|
450
450
|
const config = makeConfig();
|
|
451
|
-
expect(isCesManagedSidecarEnabled(config)).toBe(
|
|
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/
|
|
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
|
-
"
|
|
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 ()
|
|
44
|
-
|
|
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
|
},
|