vellum 0.2.13 → 0.2.14
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/README.md +32 -0
- package/bun.lock +2 -2
- package/docs/skills.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
- package/src/__tests__/browser-skill-endstate.test.ts +6 -6
- package/src/__tests__/call-bridge.test.ts +105 -13
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +113 -0
- package/src/__tests__/call-routes-http.test.ts +246 -6
- package/src/__tests__/channel-approval-routes.test.ts +438 -0
- package/src/__tests__/channel-approval.test.ts +266 -0
- package/src/__tests__/channel-approvals.test.ts +393 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/checker.test.ts +607 -1048
- package/src/__tests__/cli.test.ts +1 -56
- package/src/__tests__/config-schema.test.ts +137 -18
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +2 -0
- package/src/__tests__/contacts-tools.test.ts +3 -3
- package/src/__tests__/contradiction-checker.test.ts +99 -1
- package/src/__tests__/credential-security-invariants.test.ts +22 -6
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/elevenlabs-client.test.ts +62 -0
- package/src/__tests__/ephemeral-permissions.test.ts +73 -23
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
- package/src/__tests__/handlers-slack-config.test.ts +2 -1
- package/src/__tests__/handlers-telegram-config.test.ts +855 -0
- package/src/__tests__/handlers-twitter-config.test.ts +141 -1
- package/src/__tests__/hooks-runner.test.ts +6 -2
- package/src/__tests__/host-file-edit-tool.test.ts +124 -0
- package/src/__tests__/host-file-read-tool.test.ts +62 -0
- package/src/__tests__/host-file-write-tool.test.ts +59 -0
- package/src/__tests__/host-shell-tool.test.ts +251 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ipc-snapshot.test.ts +100 -41
- package/src/__tests__/ipc-validate.test.ts +50 -0
- package/src/__tests__/key-migration.test.ts +23 -0
- package/src/__tests__/memory-regressions.test.ts +99 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/oauth-callback-registry.test.ts +11 -4
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +4 -6
- package/src/__tests__/public-ingress-urls.test.ts +34 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
- package/src/__tests__/recurrence-engine.test.ts +9 -0
- package/src/__tests__/recurrence-types.test.ts +8 -0
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/runtime-runs.test.ts +1 -25
- package/src/__tests__/schedule-store.test.ts +16 -14
- package/src/__tests__/schedule-tools.test.ts +83 -0
- package/src/__tests__/scheduler-recurrence.test.ts +111 -10
- package/src/__tests__/secret-allowlist.test.ts +18 -17
- package/src/__tests__/secret-ingress-handler.test.ts +11 -0
- package/src/__tests__/secret-scanner.test.ts +43 -0
- package/src/__tests__/session-conflict-gate.test.ts +442 -6
- package/src/__tests__/session-init.benchmark.test.ts +3 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -1
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
- package/src/__tests__/subagent-tools.test.ts +637 -54
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +2 -2
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +85 -151
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/trust-store.test.ts +27 -453
- package/src/__tests__/twilio-provider.test.ts +153 -3
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +4 -4
- package/src/__tests__/twilio-routes.test.ts +17 -262
- package/src/__tests__/twitter-auth-handler.test.ts +2 -1
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/calls/call-bridge.ts +92 -19
- package/src/calls/call-domain.ts +157 -5
- package/src/calls/call-orchestrator.ts +93 -7
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +8 -0
- package/src/calls/elevenlabs-config.ts +7 -5
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +32 -37
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +29 -7
- package/src/cli/twitter.ts +200 -21
- package/src/cli.ts +1 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +142 -34
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
- package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
- package/src/config/bundled-skills/twitter/SKILL.md +103 -17
- package/src/config/defaults.ts +10 -4
- package/src/config/schema.ts +80 -21
- package/src/config/types.ts +1 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
- package/src/daemon/assistant-attachments.ts +4 -2
- package/src/daemon/handlers/apps.ts +69 -0
- package/src/daemon/handlers/config.ts +543 -24
- package/src/daemon/handlers/index.ts +1 -0
- package/src/daemon/handlers/sessions.ts +22 -6
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +5 -20
- package/src/daemon/ipc-contract-inventory.json +28 -0
- package/src/daemon/ipc-contract.ts +168 -10
- package/src/daemon/ipc-validate.ts +17 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/server.ts +78 -72
- package/src/daemon/session-attachments.ts +1 -1
- package/src/daemon/session-conflict-gate.ts +62 -6
- package/src/daemon/session-notifiers.ts +1 -1
- package/src/daemon/session-process.ts +62 -3
- package/src/daemon/session-tool-setup.ts +1 -2
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/video-thumbnail.ts +5 -3
- package/src/hooks/manager.ts +5 -9
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +21 -0
- package/src/memory/conflict-intent.ts +47 -4
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +9 -1
- package/src/memory/contradiction-checker.ts +28 -0
- package/src/memory/conversation-key-store.ts +15 -0
- package/src/memory/db.ts +81 -0
- package/src/memory/embedding-local.ts +3 -13
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/job-handlers/conflict.ts +22 -2
- package/src/memory/jobs-worker.ts +67 -28
- package/src/memory/runs-store.ts +54 -7
- package/src/memory/schema.ts +20 -0
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +1 -0
- package/src/permissions/checker.ts +48 -44
- package/src/permissions/prompter.ts +0 -4
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +76 -53
- package/src/permissions/types.ts +0 -19
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/providers/retry.ts +12 -37
- package/src/runtime/assistant-event-hub.ts +41 -4
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +71 -0
- package/src/runtime/channel-approvals.ts +145 -0
- package/src/runtime/gateway-client.ts +16 -0
- package/src/runtime/http-server.ts +29 -9
- package/src/runtime/routes/call-routes.ts +52 -2
- package/src/runtime/routes/channel-routes.ts +296 -16
- package/src/runtime/routes/events-routes.ts +97 -28
- package/src/runtime/routes/run-routes.ts +2 -7
- package/src/runtime/run-orchestrator.ts +0 -3
- package/src/schedule/recurrence-engine.ts +26 -2
- package/src/schedule/recurrence-types.ts +1 -1
- package/src/schedule/schedule-store.ts +12 -3
- package/src/security/secret-scanner.ts +7 -0
- package/src/tasks/ephemeral-permissions.ts +0 -2
- package/src/tasks/task-scheduler.ts +2 -1
- package/src/tools/calls/call-start.ts +8 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +6 -135
- package/src/tools/network/web-search.ts +9 -32
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/terminal/parser.ts +16 -18
- package/src/tools/types.ts +4 -11
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +29 -4
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/truncate.ts +1 -1
- package/src/workspace/git-service.ts +129 -112
- package/src/tools/contacts/contact-merge.ts +0 -55
- package/src/tools/contacts/contact-search.ts +0 -58
- package/src/tools/contacts/contact-upsert.ts +0 -64
- package/src/tools/playbooks/index.ts +0 -4
- package/src/tools/playbooks/playbook-create.ts +0 -96
- package/src/tools/playbooks/playbook-delete.ts +0 -52
- package/src/tools/playbooks/playbook-list.ts +0 -74
- package/src/tools/playbooks/playbook-update.ts +0 -111
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
ClientMessage,
|
|
5
5
|
ServerMessage,
|
|
6
6
|
} from '../daemon/ipc-protocol.js';
|
|
7
|
-
import type { ConfirmationRequest } from '../daemon/ipc-contract.js';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Snapshot tests for every IPC message type.
|
|
@@ -218,6 +217,8 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
218
217
|
pattern: 'git *',
|
|
219
218
|
scope: '/projects/my-app',
|
|
220
219
|
decision: 'allow',
|
|
220
|
+
allowHighRisk: true,
|
|
221
|
+
executionTarget: 'host',
|
|
221
222
|
},
|
|
222
223
|
trust_rules_list: {
|
|
223
224
|
type: 'trust_rules_list',
|
|
@@ -327,6 +328,28 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
327
328
|
type: 'app_preview_request',
|
|
328
329
|
appId: 'app-001',
|
|
329
330
|
},
|
|
331
|
+
app_history_request: {
|
|
332
|
+
type: 'app_history_request',
|
|
333
|
+
appId: 'app-001',
|
|
334
|
+
limit: 25,
|
|
335
|
+
},
|
|
336
|
+
app_diff_request: {
|
|
337
|
+
type: 'app_diff_request',
|
|
338
|
+
appId: 'app-001',
|
|
339
|
+
fromCommit: 'abc123def456',
|
|
340
|
+
toCommit: '789abc123def',
|
|
341
|
+
},
|
|
342
|
+
app_file_at_version_request: {
|
|
343
|
+
type: 'app_file_at_version_request',
|
|
344
|
+
appId: 'app-001',
|
|
345
|
+
path: 'index.html',
|
|
346
|
+
commitHash: 'abc123def456',
|
|
347
|
+
},
|
|
348
|
+
app_restore_request: {
|
|
349
|
+
type: 'app_restore_request',
|
|
350
|
+
appId: 'app-001',
|
|
351
|
+
commitHash: 'abc123def456',
|
|
352
|
+
},
|
|
330
353
|
share_app_cloud: {
|
|
331
354
|
type: 'share_app_cloud',
|
|
332
355
|
appId: 'app-001',
|
|
@@ -351,6 +374,10 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
351
374
|
type: 'twitter_integration_config',
|
|
352
375
|
action: 'get',
|
|
353
376
|
},
|
|
377
|
+
telegram_config: {
|
|
378
|
+
type: 'telegram_config',
|
|
379
|
+
action: 'get',
|
|
380
|
+
},
|
|
354
381
|
twitter_auth_start: {
|
|
355
382
|
type: 'twitter_auth_start',
|
|
356
383
|
},
|
|
@@ -516,6 +543,17 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
516
543
|
identity_get: {
|
|
517
544
|
type: 'identity_get',
|
|
518
545
|
},
|
|
546
|
+
tool_permission_simulate: {
|
|
547
|
+
type: 'tool_permission_simulate',
|
|
548
|
+
toolName: 'bash',
|
|
549
|
+
input: { command: 'rm -rf /tmp/test' },
|
|
550
|
+
workingDir: '/projects/my-app',
|
|
551
|
+
isInteractive: true,
|
|
552
|
+
forcePromptSideEffects: false,
|
|
553
|
+
},
|
|
554
|
+
tool_names_list: {
|
|
555
|
+
type: 'tool_names_list',
|
|
556
|
+
},
|
|
519
557
|
};
|
|
520
558
|
|
|
521
559
|
// ---------------------------------------------------------------------------
|
|
@@ -605,9 +643,6 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
605
643
|
},
|
|
606
644
|
sandboxed: false,
|
|
607
645
|
sessionId: 'sess-001',
|
|
608
|
-
principalKind: 'skill',
|
|
609
|
-
principalId: 'my-skill',
|
|
610
|
-
principalVersion: 'sha256:abcdef1234567890',
|
|
611
646
|
},
|
|
612
647
|
message_complete: {
|
|
613
648
|
type: 'message_complete',
|
|
@@ -1169,6 +1204,14 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
1169
1204
|
localClientConfigured: true,
|
|
1170
1205
|
connected: false,
|
|
1171
1206
|
},
|
|
1207
|
+
telegram_config_response: {
|
|
1208
|
+
type: 'telegram_config_response',
|
|
1209
|
+
success: true,
|
|
1210
|
+
hasBotToken: true,
|
|
1211
|
+
botUsername: 'my_test_bot',
|
|
1212
|
+
connected: true,
|
|
1213
|
+
hasWebhookSecret: true,
|
|
1214
|
+
},
|
|
1172
1215
|
twitter_auth_result: {
|
|
1173
1216
|
type: 'twitter_auth_result',
|
|
1174
1217
|
success: true,
|
|
@@ -1195,6 +1238,37 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
1195
1238
|
appId: 'app-001',
|
|
1196
1239
|
preview: 'base64-png-data',
|
|
1197
1240
|
},
|
|
1241
|
+
app_history_response: {
|
|
1242
|
+
type: 'app_history_response',
|
|
1243
|
+
appId: 'app-001',
|
|
1244
|
+
versions: [
|
|
1245
|
+
{
|
|
1246
|
+
commitHash: 'abc123def456',
|
|
1247
|
+
message: 'Initial app commit',
|
|
1248
|
+
timestamp: 1700000000,
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
commitHash: '789abc123def',
|
|
1252
|
+
message: 'Update landing page',
|
|
1253
|
+
timestamp: 1700001000,
|
|
1254
|
+
},
|
|
1255
|
+
],
|
|
1256
|
+
},
|
|
1257
|
+
app_diff_response: {
|
|
1258
|
+
type: 'app_diff_response',
|
|
1259
|
+
appId: 'app-001',
|
|
1260
|
+
diff: 'diff --git a/index.html b/index.html',
|
|
1261
|
+
},
|
|
1262
|
+
app_file_at_version_response: {
|
|
1263
|
+
type: 'app_file_at_version_response',
|
|
1264
|
+
appId: 'app-001',
|
|
1265
|
+
path: 'index.html',
|
|
1266
|
+
content: '<html><body>Hello</body></html>',
|
|
1267
|
+
},
|
|
1268
|
+
app_restore_response: {
|
|
1269
|
+
type: 'app_restore_response',
|
|
1270
|
+
success: true,
|
|
1271
|
+
},
|
|
1198
1272
|
ui_surface_undo_result: {
|
|
1199
1273
|
type: 'ui_surface_undo_result',
|
|
1200
1274
|
sessionId: 'sess-001',
|
|
@@ -1490,6 +1564,28 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
1490
1564
|
emoji: '✨',
|
|
1491
1565
|
home: '~/workspace',
|
|
1492
1566
|
},
|
|
1567
|
+
tool_permission_simulate_response: {
|
|
1568
|
+
type: 'tool_permission_simulate_response',
|
|
1569
|
+
success: true,
|
|
1570
|
+
decision: 'prompt',
|
|
1571
|
+
riskLevel: 'high',
|
|
1572
|
+
reason: 'No matching trust rule; tool requires approval',
|
|
1573
|
+
promptPayload: {
|
|
1574
|
+
allowlistOptions: [
|
|
1575
|
+
{ label: 'Allow rm commands', description: 'Allow rm commands', pattern: 'bash:rm *' },
|
|
1576
|
+
],
|
|
1577
|
+
scopeOptions: [
|
|
1578
|
+
{ label: 'In /projects/my-app', scope: '/projects/my-app' },
|
|
1579
|
+
],
|
|
1580
|
+
persistentDecisionsAllowed: true,
|
|
1581
|
+
},
|
|
1582
|
+
executionTarget: 'host',
|
|
1583
|
+
matchedRuleId: undefined,
|
|
1584
|
+
},
|
|
1585
|
+
tool_names_list_response: {
|
|
1586
|
+
type: 'tool_names_list_response',
|
|
1587
|
+
names: ['bash', 'file_read', 'file_write'],
|
|
1588
|
+
},
|
|
1493
1589
|
};
|
|
1494
1590
|
|
|
1495
1591
|
// ---------------------------------------------------------------------------
|
|
@@ -1601,43 +1697,6 @@ describe('IPC message snapshots', () => {
|
|
|
1601
1697
|
});
|
|
1602
1698
|
});
|
|
1603
1699
|
|
|
1604
|
-
// Baseline assertions for principal context in confirmation_request.
|
|
1605
|
-
describe('confirmation principal context baselines', () => {
|
|
1606
|
-
test('confirmation_request includes principal context fields', () => {
|
|
1607
|
-
const req = serverMessages.confirmation_request as ConfirmationRequest;
|
|
1608
|
-
expect(req.principalKind).toBe('skill');
|
|
1609
|
-
expect(req.principalId).toBe('my-skill');
|
|
1610
|
-
expect(req.principalVersion).toBe('sha256:abcdef1234567890');
|
|
1611
|
-
});
|
|
1612
|
-
|
|
1613
|
-
test('confirmation_request principal fields are optional (backward-compatible)', () => {
|
|
1614
|
-
// Core tools omit principal fields — verify the contract allows it
|
|
1615
|
-
const minimal: typeof serverMessages.confirmation_request = {
|
|
1616
|
-
type: 'confirmation_request',
|
|
1617
|
-
requestId: 'req-minimal',
|
|
1618
|
-
toolName: 'bash',
|
|
1619
|
-
input: { command: 'ls' },
|
|
1620
|
-
riskLevel: 'low',
|
|
1621
|
-
allowlistOptions: [],
|
|
1622
|
-
scopeOptions: [],
|
|
1623
|
-
};
|
|
1624
|
-
const serialized = serialize(minimal);
|
|
1625
|
-
const parsed = JSON.parse(serialized.trimEnd());
|
|
1626
|
-
expect(parsed.principalKind).toBeUndefined();
|
|
1627
|
-
expect(parsed.principalId).toBeUndefined();
|
|
1628
|
-
expect(parsed.principalVersion).toBeUndefined();
|
|
1629
|
-
});
|
|
1630
|
-
|
|
1631
|
-
test('confirmation_request round-trips with all principal fields', () => {
|
|
1632
|
-
const req = serverMessages.confirmation_request;
|
|
1633
|
-
const serialized = serialize(req);
|
|
1634
|
-
const parsed = JSON.parse(serialized.trimEnd());
|
|
1635
|
-
expect(parsed.principalKind).toBe('skill');
|
|
1636
|
-
expect(parsed.principalId).toBe('my-skill');
|
|
1637
|
-
expect(parsed.principalVersion).toBe('sha256:abcdef1234567890');
|
|
1638
|
-
});
|
|
1639
|
-
});
|
|
1640
|
-
|
|
1641
1700
|
// Baseline assertions for error-related message contracts.
|
|
1642
1701
|
// These document the current shape before error handling modernization.
|
|
1643
1702
|
describe('error message baselines', () => {
|
|
@@ -295,6 +295,55 @@ describe('IPC Validate', () => {
|
|
|
295
295
|
if (!result.valid) expect(result.reason).toContain('non-empty string "actionId"');
|
|
296
296
|
});
|
|
297
297
|
|
|
298
|
+
// ─── High-risk: add_trust_rule ──────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
test('accepts valid add_trust_rule', () => {
|
|
301
|
+
const result = validateClientMessage({
|
|
302
|
+
type: 'add_trust_rule',
|
|
303
|
+
toolName: 'Bash',
|
|
304
|
+
pattern: '*',
|
|
305
|
+
scope: 'global',
|
|
306
|
+
decision: 'allow',
|
|
307
|
+
});
|
|
308
|
+
expect(result.valid).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('accepts all valid add_trust_rule decisions', () => {
|
|
312
|
+
for (const decision of ['allow', 'deny', 'ask']) {
|
|
313
|
+
const result = validateClientMessage({
|
|
314
|
+
type: 'add_trust_rule',
|
|
315
|
+
toolName: 'Bash',
|
|
316
|
+
pattern: '*',
|
|
317
|
+
scope: 'global',
|
|
318
|
+
decision,
|
|
319
|
+
});
|
|
320
|
+
expect(result.valid).toBe(true);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('rejects add_trust_rule without toolName', () => {
|
|
325
|
+
const result = validateClientMessage({
|
|
326
|
+
type: 'add_trust_rule',
|
|
327
|
+
pattern: '*',
|
|
328
|
+
scope: 'global',
|
|
329
|
+
decision: 'allow',
|
|
330
|
+
});
|
|
331
|
+
expect(result.valid).toBe(false);
|
|
332
|
+
if (!result.valid) expect(result.reason).toContain('non-empty string "toolName"');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('rejects add_trust_rule with invalid decision', () => {
|
|
336
|
+
const result = validateClientMessage({
|
|
337
|
+
type: 'add_trust_rule',
|
|
338
|
+
toolName: 'Bash',
|
|
339
|
+
pattern: '*',
|
|
340
|
+
scope: 'global',
|
|
341
|
+
decision: 'always_allow',
|
|
342
|
+
});
|
|
343
|
+
expect(result.valid).toBe(false);
|
|
344
|
+
if (!result.valid) expect(result.reason).toContain('"decision" must be one of');
|
|
345
|
+
});
|
|
346
|
+
|
|
298
347
|
// ─── Extra properties are tolerated ─────────────────────────────────
|
|
299
348
|
|
|
300
349
|
test('allows extra properties on known types', () => {
|
|
@@ -335,6 +384,7 @@ describe('IPC Validate', () => {
|
|
|
335
384
|
secret_response: { requestId: 'r1' },
|
|
336
385
|
ui_surface_action: { sessionId: 's1', surfaceId: 'sf1', actionId: 'a1' },
|
|
337
386
|
ipc_blob_probe: { probeId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', nonceSha256: 'abc123' },
|
|
387
|
+
add_trust_rule: { toolName: 'Bash', pattern: '*', scope: 'global', decision: 'allow' },
|
|
338
388
|
document_save: { surfaceId: 'doc1', conversationId: 'c1', title: 'Doc', content: 'text', wordCount: 1 },
|
|
339
389
|
document_load: { surfaceId: 'doc1' },
|
|
340
390
|
document_list: {},
|
|
@@ -31,6 +31,9 @@ mock.module('../util/platform.js', () => ({
|
|
|
31
31
|
const logsDir = join(TEST_DIR, 'logs');
|
|
32
32
|
if (!existsSync(logsDir)) mkdirSync(logsDir, { recursive: true });
|
|
33
33
|
},
|
|
34
|
+
migrateToWorkspaceLayout: () => {},
|
|
35
|
+
migrateToDataLayout: () => {},
|
|
36
|
+
migratePath: () => {},
|
|
34
37
|
isMacOS: () => false,
|
|
35
38
|
isLinux: () => false,
|
|
36
39
|
isWindows: () => false,
|
|
@@ -45,8 +48,23 @@ import { getSecureKey } from '../security/secure-keys.js';
|
|
|
45
48
|
// Tests
|
|
46
49
|
// ---------------------------------------------------------------------------
|
|
47
50
|
|
|
51
|
+
// API key env vars that loadConfig checks — must be cleared during tests
|
|
52
|
+
// so they don't override the migrated values under test.
|
|
53
|
+
const API_KEY_ENV_VARS = [
|
|
54
|
+
'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GEMINI_API_KEY',
|
|
55
|
+
'OLLAMA_API_KEY', 'FIREWORKS_API_KEY', 'OPENROUTER_API_KEY',
|
|
56
|
+
'BRAVE_API_KEY', 'PERPLEXITY_API_KEY',
|
|
57
|
+
];
|
|
58
|
+
|
|
48
59
|
describe('key migration', () => {
|
|
60
|
+
const savedEnv: Record<string, string | undefined> = {};
|
|
61
|
+
|
|
49
62
|
beforeEach(() => {
|
|
63
|
+
// Save and clear API key env vars
|
|
64
|
+
for (const key of API_KEY_ENV_VARS) {
|
|
65
|
+
savedEnv[key] = process.env[key];
|
|
66
|
+
delete process.env[key];
|
|
67
|
+
}
|
|
50
68
|
if (existsSync(TEST_DIR)) {
|
|
51
69
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
52
70
|
}
|
|
@@ -62,6 +80,11 @@ describe('key migration', () => {
|
|
|
62
80
|
_setStorePath(null);
|
|
63
81
|
_setBackend(undefined);
|
|
64
82
|
invalidateConfigCache();
|
|
83
|
+
// Restore API key env vars
|
|
84
|
+
for (const key of API_KEY_ENV_VARS) {
|
|
85
|
+
if (savedEnv[key] === undefined) delete process.env[key];
|
|
86
|
+
else process.env[key] = savedEnv[key]!;
|
|
87
|
+
}
|
|
65
88
|
});
|
|
66
89
|
|
|
67
90
|
test('[experimental] migrates plaintext apiKeys from config.json to secure storage', () => {
|
|
@@ -1216,6 +1216,105 @@ describe('Memory regressions', () => {
|
|
|
1216
1216
|
}
|
|
1217
1217
|
});
|
|
1218
1218
|
|
|
1219
|
+
test('background conflict resolver dismisses transient/non-durable conflicts without LLM call', async () => {
|
|
1220
|
+
const db = getDb();
|
|
1221
|
+
const now = 1_700_001_500_000;
|
|
1222
|
+
const originalConflictsEnabled = TEST_CONFIG.memory.conflicts.enabled;
|
|
1223
|
+
TEST_CONFIG.memory.conflicts.enabled = true;
|
|
1224
|
+
|
|
1225
|
+
try {
|
|
1226
|
+
db.insert(conversations).values({
|
|
1227
|
+
id: 'conv-conflicts-transient',
|
|
1228
|
+
title: null,
|
|
1229
|
+
createdAt: now,
|
|
1230
|
+
updatedAt: now,
|
|
1231
|
+
totalInputTokens: 0,
|
|
1232
|
+
totalOutputTokens: 0,
|
|
1233
|
+
totalEstimatedCost: 0,
|
|
1234
|
+
contextSummary: null,
|
|
1235
|
+
contextCompactedMessageCount: 0,
|
|
1236
|
+
contextCompactedAt: null,
|
|
1237
|
+
}).run();
|
|
1238
|
+
|
|
1239
|
+
db.insert(messages).values({
|
|
1240
|
+
id: 'msg-conflicts-transient',
|
|
1241
|
+
conversationId: 'conv-conflicts-transient',
|
|
1242
|
+
role: 'user',
|
|
1243
|
+
content: JSON.stringify([{ type: 'text', text: 'Keep the new one instead.' }]),
|
|
1244
|
+
createdAt: now + 1,
|
|
1245
|
+
}).run();
|
|
1246
|
+
|
|
1247
|
+
// Create a transient conflict: PR tracking statements should be dismissed
|
|
1248
|
+
db.insert(memoryItems).values([
|
|
1249
|
+
{
|
|
1250
|
+
id: 'item-conflict-existing-transient',
|
|
1251
|
+
kind: 'preference',
|
|
1252
|
+
subject: 'pr-tracking',
|
|
1253
|
+
statement: 'Currently tracking PR #42 for review.',
|
|
1254
|
+
status: 'active',
|
|
1255
|
+
confidence: 0.8,
|
|
1256
|
+
fingerprint: 'fp-conflict-existing-transient',
|
|
1257
|
+
verificationState: 'assistant_inferred',
|
|
1258
|
+
scopeId: 'scope-conflicts-transient',
|
|
1259
|
+
firstSeenAt: now - 10_000,
|
|
1260
|
+
lastSeenAt: now - 5_000,
|
|
1261
|
+
validFrom: now - 10_000,
|
|
1262
|
+
invalidAt: null,
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
id: 'item-conflict-candidate-transient',
|
|
1266
|
+
kind: 'preference',
|
|
1267
|
+
subject: 'pr-tracking',
|
|
1268
|
+
statement: 'Currently tracking PR #99 for review.',
|
|
1269
|
+
status: 'pending_clarification',
|
|
1270
|
+
confidence: 0.8,
|
|
1271
|
+
fingerprint: 'fp-conflict-candidate-transient',
|
|
1272
|
+
verificationState: 'assistant_inferred',
|
|
1273
|
+
scopeId: 'scope-conflicts-transient',
|
|
1274
|
+
firstSeenAt: now - 9_000,
|
|
1275
|
+
lastSeenAt: now - 4_000,
|
|
1276
|
+
validFrom: now - 9_000,
|
|
1277
|
+
invalidAt: null,
|
|
1278
|
+
},
|
|
1279
|
+
]).run();
|
|
1280
|
+
|
|
1281
|
+
const conflict = createOrUpdatePendingConflict({
|
|
1282
|
+
scopeId: 'scope-conflicts-transient',
|
|
1283
|
+
existingItemId: 'item-conflict-existing-transient',
|
|
1284
|
+
candidateItemId: 'item-conflict-candidate-transient',
|
|
1285
|
+
relationship: 'ambiguous_contradiction',
|
|
1286
|
+
});
|
|
1287
|
+
db.update(memoryItemConflicts)
|
|
1288
|
+
.set({ createdAt: now, updatedAt: now })
|
|
1289
|
+
.where(eq(memoryItemConflicts.id, conflict.id))
|
|
1290
|
+
.run();
|
|
1291
|
+
|
|
1292
|
+
enqueueResolvePendingConflictsForMessageJob('msg-conflicts-transient', 'scope-conflicts-transient');
|
|
1293
|
+
const processed = await runMemoryJobsOnce();
|
|
1294
|
+
expect(processed).toBe(1);
|
|
1295
|
+
|
|
1296
|
+
const updatedConflict = getConflictById(conflict.id);
|
|
1297
|
+
expect(updatedConflict?.status).toBe('dismissed');
|
|
1298
|
+
expect(updatedConflict?.resolutionNote).toContain('conflict policy');
|
|
1299
|
+
|
|
1300
|
+
// Memory items should remain untouched (no LLM resolution was attempted)
|
|
1301
|
+
const existing = db
|
|
1302
|
+
.select()
|
|
1303
|
+
.from(memoryItems)
|
|
1304
|
+
.where(eq(memoryItems.id, 'item-conflict-existing-transient'))
|
|
1305
|
+
.get();
|
|
1306
|
+
const candidate = db
|
|
1307
|
+
.select()
|
|
1308
|
+
.from(memoryItems)
|
|
1309
|
+
.where(eq(memoryItems.id, 'item-conflict-candidate-transient'))
|
|
1310
|
+
.get();
|
|
1311
|
+
expect(existing?.status).toBe('active');
|
|
1312
|
+
expect(candidate?.status).toBe('pending_clarification');
|
|
1313
|
+
} finally {
|
|
1314
|
+
TEST_CONFIG.memory.conflicts.enabled = originalConflictsEnabled;
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1219
1318
|
test('cleanup job enqueue is deduped and retention overrides upgrade payload', () => {
|
|
1220
1319
|
const db = getDb();
|
|
1221
1320
|
|
|
@@ -398,7 +398,7 @@ describe('Memory retrieval benchmark', () => {
|
|
|
398
398
|
}
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
-
test('recall.latencyMs tracks wall-clock within
|
|
401
|
+
test('recall.latencyMs tracks wall-clock within 50% tolerance', async () => {
|
|
402
402
|
const conversationId = 'conv-bench-wallclock';
|
|
403
403
|
const now = 1_700_500_000_000;
|
|
404
404
|
seedMemoryItems(conversationId, 500, now);
|
|
@@ -61,13 +61,20 @@ describe('OAuth callback registry', () => {
|
|
|
61
61
|
|
|
62
62
|
test('TTL expiry rejects callback with timeout error', async () => {
|
|
63
63
|
const promise = new Promise<string>((resolve, reject) => {
|
|
64
|
-
registerPendingCallback('state-ttl', resolve, reject,
|
|
64
|
+
registerPendingCallback('state-ttl', resolve, reject, 100);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
//
|
|
68
|
-
|
|
67
|
+
// Attach a catch handler immediately to prevent unhandled rejection
|
|
68
|
+
// during the sleep. We capture the error and verify it afterwards.
|
|
69
|
+
let caughtError: Error | undefined;
|
|
70
|
+
const guarded = promise.catch((err) => { caughtError = err; });
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
// Wait for the TTL to expire (generous margin)
|
|
73
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
74
|
+
await guarded;
|
|
75
|
+
|
|
76
|
+
expect(caughtError).toBeDefined();
|
|
77
|
+
expect(caughtError!.message).toBe('OAuth callback timed out');
|
|
71
78
|
|
|
72
79
|
// After expiry, consume should return false
|
|
73
80
|
const consumed = consumeCallback('state-ttl', 'late-code');
|