@vellumai/assistant 0.3.3 → 0.3.5
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 -0
- package/README.md +45 -18
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +13 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +100 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +391 -1
- package/src/__tests__/call-routes-http.test.ts +27 -2
- package/src/__tests__/channel-approval-routes.test.ts +397 -135
- package/src/__tests__/channel-approvals.test.ts +99 -3
- package/src/__tests__/channel-delivery-store.test.ts +30 -4
- package/src/__tests__/channel-guardian.test.ts +261 -22
- package/src/__tests__/channel-readiness-service.test.ts +257 -0
- package/src/__tests__/config-schema.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-lifecycle.test.ts +636 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +19 -13
- package/src/__tests__/handlers-twilio-config.test.ts +480 -0
- package/src/__tests__/ipc-snapshot.test.ts +63 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +4 -0
- package/src/__tests__/run-orchestrator.test.ts +22 -0
- package/src/__tests__/secret-scanner.test.ts +223 -0
- package/src/__tests__/session-runtime-assembly.test.ts +85 -1
- package/src/__tests__/shell-parser-property.test.ts +357 -2
- package/src/__tests__/sms-messaging-provider.test.ts +125 -0
- package/src/__tests__/system-prompt.test.ts +25 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +34 -1
- package/src/__tests__/twilio-routes.test.ts +39 -3
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +68 -0
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/calls/call-domain.ts +8 -5
- package/src/calls/call-orchestrator.ts +85 -22
- package/src/calls/twilio-config.ts +17 -11
- package/src/calls/twilio-rest.ts +276 -0
- package/src/calls/twilio-routes.ts +39 -1
- package/src/cli/map.ts +6 -0
- package/src/commands/__tests__/cc-command-registry.test.ts +67 -0
- package/src/commands/cc-command-registry.ts +14 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +10 -3
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +199 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +320 -0
- package/src/config/bundled-skills/media-processing/services/capability-registry.ts +137 -0
- package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +280 -0
- package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +144 -0
- package/src/config/bundled-skills/media-processing/services/feedback-store.ts +136 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +261 -0
- package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +95 -0
- package/src/config/bundled-skills/media-processing/services/timeline-service.ts +267 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +301 -0
- package/src/config/bundled-skills/media-processing/tools/detect-events.ts +110 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +190 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +166 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +300 -0
- package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +235 -0
- package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +142 -0
- package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +150 -0
- package/src/config/bundled-skills/messaging/SKILL.md +24 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/defaults.ts +2 -1
- package/src/config/schema.ts +9 -3
- package/src/config/skills.ts +5 -32
- package/src/config/system-prompt.ts +40 -0
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/user-reference.ts +29 -0
- package/src/config/vellum-skills/catalog.json +58 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +118 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +6 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +76 -6
- package/src/daemon/auth-manager.ts +103 -0
- package/src/daemon/computer-use-session.ts +8 -1
- package/src/daemon/config-watcher.ts +253 -0
- package/src/daemon/handlers/config.ts +819 -22
- package/src/daemon/handlers/dictation.ts +182 -0
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +2 -0
- package/src/daemon/handlers/sessions.ts +2 -0
- package/src/daemon/handlers/shared.ts +3 -0
- package/src/daemon/handlers/skills.ts +6 -7
- package/src/daemon/handlers/work-items.ts +15 -7
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +114 -4
- package/src/daemon/ipc-handler.ts +87 -0
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/ride-shotgun-handler.ts +11 -1
- package/src/daemon/server.ts +111 -504
- package/src/daemon/session-agent-loop.ts +10 -15
- package/src/daemon/session-runtime-assembly.ts +115 -44
- package/src/daemon/session-tool-setup.ts +2 -0
- package/src/daemon/session.ts +19 -2
- package/src/inbound/public-ingress-urls.ts +3 -3
- package/src/memory/channel-guardian-store.ts +2 -1
- package/src/memory/db-connection.ts +28 -0
- package/src/memory/db-init.ts +1163 -0
- package/src/memory/db.ts +2 -2007
- package/src/memory/embedding-backend.ts +79 -11
- package/src/memory/indexer.ts +2 -0
- package/src/memory/job-handlers/media-processing.ts +100 -0
- package/src/memory/job-utils.ts +64 -4
- package/src/memory/jobs-store.ts +2 -1
- package/src/memory/jobs-worker.ts +11 -1
- package/src/memory/media-store.ts +759 -0
- package/src/memory/recall-cache.ts +107 -0
- package/src/memory/retriever.ts +36 -2
- package/src/memory/schema-migration.ts +984 -0
- package/src/memory/schema.ts +99 -0
- package/src/memory/search/entity.ts +208 -25
- package/src/memory/search/ranking.ts +6 -1
- package/src/memory/search/types.ts +26 -0
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +204 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/permissions/checker.ts +16 -2
- package/src/permissions/prompter.ts +14 -3
- package/src/permissions/trust-store.ts +7 -0
- package/src/runtime/approval-message-composer.ts +143 -0
- package/src/runtime/channel-approvals.ts +29 -7
- package/src/runtime/channel-guardian-service.ts +44 -18
- package/src/runtime/channel-readiness-service.ts +292 -0
- package/src/runtime/channel-readiness-types.ts +29 -0
- package/src/runtime/gateway-client.ts +2 -1
- package/src/runtime/http-server.ts +65 -28
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-routes.ts +237 -103
- package/src/runtime/routes/run-routes.ts +7 -1
- package/src/runtime/run-orchestrator.ts +43 -3
- package/src/security/secret-scanner.ts +218 -0
- package/src/skills/frontmatter.ts +63 -0
- package/src/skills/slash-commands.ts +23 -0
- package/src/skills/vellum-catalog-remote.ts +107 -0
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/browser/auto-navigate.ts +132 -24
- package/src/tools/browser/browser-manager.ts +67 -61
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/claude-code/claude-code.ts +55 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +10 -2
- package/src/tools/network/web-search.ts +1 -1
- package/src/tools/skills/vellum-catalog.ts +61 -156
- package/src/tools/terminal/parser.ts +21 -5
- package/src/tools/types.ts +2 -0
- package/src/twitter/router.ts +1 -1
- package/src/util/platform.ts +43 -1
- package/src/util/retry.ts +4 -4
|
@@ -26,6 +26,12 @@ mock.module('../util/logger.js', () => ({
|
|
|
26
26
|
}),
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
|
+
mock.module('../config/loader.js', () => ({
|
|
30
|
+
getConfig: () => ({
|
|
31
|
+
secretDetection: { enabled: false },
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
|
|
29
35
|
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
30
36
|
import { createConversation } from '../memory/conversation-store.js';
|
|
31
37
|
import { createRun, getRun, setRunConfirmation } from '../memory/runs-store.js';
|
|
@@ -43,6 +49,8 @@ function makeSessionWithConfirmation(message: ServerMessage): Session {
|
|
|
43
49
|
persistUserMessage: () => undefined as unknown as string,
|
|
44
50
|
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
|
|
45
51
|
setChannelCapabilities: () => {},
|
|
52
|
+
setAssistantId: () => {},
|
|
53
|
+
setGuardianContext: () => {},
|
|
46
54
|
updateClient: (handler: (msg: ServerMessage) => void) => {
|
|
47
55
|
clientHandler = handler;
|
|
48
56
|
},
|
|
@@ -64,6 +72,8 @@ function makeSessionWithEvent(message: ServerMessage): Session {
|
|
|
64
72
|
persistUserMessage: () => undefined as unknown as string,
|
|
65
73
|
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
|
|
66
74
|
setChannelCapabilities: () => {},
|
|
75
|
+
setAssistantId: () => {},
|
|
76
|
+
setGuardianContext: () => {},
|
|
67
77
|
updateClient: () => {},
|
|
68
78
|
runAgentLoop: async (_content: string, _messageId: string, onEvent: (msg: ServerMessage) => void) => {
|
|
69
79
|
onEvent(message);
|
|
@@ -228,6 +238,8 @@ describe('startRun channel capability resolution', () => {
|
|
|
228
238
|
setChannelCapabilities: (caps: ChannelCapabilities | null) => {
|
|
229
239
|
if (caps) capturedCapabilities = caps;
|
|
230
240
|
},
|
|
241
|
+
setAssistantId: () => {},
|
|
242
|
+
setGuardianContext: () => {},
|
|
231
243
|
updateClient: () => {},
|
|
232
244
|
runAgentLoop: async () => {},
|
|
233
245
|
handleConfirmationResponse: () => {},
|
|
@@ -262,6 +274,8 @@ describe('startRun channel capability resolution', () => {
|
|
|
262
274
|
setChannelCapabilities: (caps: ChannelCapabilities | null) => {
|
|
263
275
|
if (caps) capturedCapabilities = caps;
|
|
264
276
|
},
|
|
277
|
+
setAssistantId: () => {},
|
|
278
|
+
setGuardianContext: () => {},
|
|
265
279
|
updateClient: () => {},
|
|
266
280
|
runAgentLoop: async () => {},
|
|
267
281
|
handleConfirmationResponse: () => {},
|
|
@@ -292,6 +306,8 @@ describe('startRun channel capability resolution', () => {
|
|
|
292
306
|
setChannelCapabilities: (caps: ChannelCapabilities | null) => {
|
|
293
307
|
if (caps) capturedCapabilities = caps;
|
|
294
308
|
},
|
|
309
|
+
setAssistantId: () => {},
|
|
310
|
+
setGuardianContext: () => {},
|
|
295
311
|
updateClient: () => {},
|
|
296
312
|
runAgentLoop: async () => {},
|
|
297
313
|
handleConfirmationResponse: () => {},
|
|
@@ -335,6 +351,8 @@ describe('strictSideEffects re-derivation across runs', () => {
|
|
|
335
351
|
persistUserMessage: () => undefined as unknown as string,
|
|
336
352
|
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
|
|
337
353
|
setChannelCapabilities: () => {},
|
|
354
|
+
setAssistantId: () => {},
|
|
355
|
+
setGuardianContext: () => {},
|
|
338
356
|
updateClient: () => {},
|
|
339
357
|
runAgentLoop: async () => {},
|
|
340
358
|
handleConfirmationResponse: () => {},
|
|
@@ -369,6 +387,8 @@ describe('strictSideEffects re-derivation across runs', () => {
|
|
|
369
387
|
persistUserMessage: () => undefined as unknown as string,
|
|
370
388
|
memoryPolicy: { scopeId: 'private-scope', includeDefaultFallback: true, strictSideEffects: true },
|
|
371
389
|
setChannelCapabilities: () => {},
|
|
390
|
+
setAssistantId: () => {},
|
|
391
|
+
setGuardianContext: () => {},
|
|
372
392
|
updateClient: () => {},
|
|
373
393
|
runAgentLoop: async () => {},
|
|
374
394
|
handleConfirmationResponse: () => {},
|
|
@@ -404,6 +424,8 @@ describe('strictSideEffects re-derivation across runs', () => {
|
|
|
404
424
|
persistUserMessage: () => undefined as unknown as string,
|
|
405
425
|
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: true },
|
|
406
426
|
setChannelCapabilities: () => {},
|
|
427
|
+
setAssistantId: () => {},
|
|
428
|
+
setGuardianContext: () => {},
|
|
407
429
|
updateClient: () => {},
|
|
408
430
|
runAgentLoop: async () => {},
|
|
409
431
|
handleConfirmationResponse: () => {},
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
_isPlaceholder,
|
|
7
7
|
_redact,
|
|
8
8
|
_hasSecretContext,
|
|
9
|
+
_tryDecodeBase64,
|
|
10
|
+
_tryDecodePercentEncoded,
|
|
11
|
+
_tryDecodeHexEscapes,
|
|
12
|
+
_tryDecodeContinuousHex,
|
|
9
13
|
type SecretMatch,
|
|
10
14
|
} from '../security/secret-scanner.js';
|
|
11
15
|
|
|
@@ -898,3 +902,222 @@ describe('word-boundary context keywords', () => {
|
|
|
898
902
|
expect(entropy.length).toBeGreaterThan(0);
|
|
899
903
|
});
|
|
900
904
|
});
|
|
905
|
+
|
|
906
|
+
// ---------------------------------------------------------------------------
|
|
907
|
+
// Encoded secret detection — decode + re-scan
|
|
908
|
+
// ---------------------------------------------------------------------------
|
|
909
|
+
describe('encoded secret detection', () => {
|
|
910
|
+
// -- Base64-encoded secrets --
|
|
911
|
+
describe('base64-encoded', () => {
|
|
912
|
+
test('detects base64-encoded Stripe key', () => {
|
|
913
|
+
const secret = 'sk_live_abcdefghijklmnopqrstuvwx';
|
|
914
|
+
const encoded = Buffer.from(secret).toString('base64');
|
|
915
|
+
const input = `config: ${encoded}`;
|
|
916
|
+
const matches = scanText(input);
|
|
917
|
+
const found = matches.find((m) => m.type === 'Stripe Secret Key (base64-encoded)');
|
|
918
|
+
expect(found).toBeDefined();
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test('detects base64-encoded GitHub token', () => {
|
|
922
|
+
const secret = `ghp_${'A'.repeat(36)}`;
|
|
923
|
+
const encoded = Buffer.from(secret).toString('base64');
|
|
924
|
+
const input = `value=${encoded}`;
|
|
925
|
+
const matches = scanText(input);
|
|
926
|
+
const found = matches.find((m) => m.type === 'GitHub Token (base64-encoded)');
|
|
927
|
+
expect(found).toBeDefined();
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test('detects base64-encoded private key header', () => {
|
|
931
|
+
const secret = '-----BEGIN RSA PRIVATE KEY-----';
|
|
932
|
+
const encoded = Buffer.from(secret).toString('base64');
|
|
933
|
+
const input = `data: ${encoded}`;
|
|
934
|
+
const matches = scanText(input);
|
|
935
|
+
const found = matches.find((m) => m.type === 'Private Key (base64-encoded)');
|
|
936
|
+
expect(found).toBeDefined();
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
test('does not flag base64 that decodes to non-secret text', () => {
|
|
940
|
+
const encoded = Buffer.from('Hello, this is just normal text!').toString('base64');
|
|
941
|
+
const input = `data: ${encoded}`;
|
|
942
|
+
const matches = scanText(input);
|
|
943
|
+
const encoded_matches = matches.filter((m) => m.type.includes('base64-encoded'));
|
|
944
|
+
expect(encoded_matches).toHaveLength(0);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
test('does not flag base64 that decodes to binary data', () => {
|
|
948
|
+
// Create a base64 string that decodes to non-printable bytes
|
|
949
|
+
const binary = Buffer.from([0x00, 0x01, 0x02, 0x80, 0xff, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15]);
|
|
950
|
+
const encoded = binary.toString('base64');
|
|
951
|
+
const input = `data: ${encoded}`;
|
|
952
|
+
const matches = scanText(input);
|
|
953
|
+
const encoded_matches = matches.filter((m) => m.type.includes('base64-encoded'));
|
|
954
|
+
expect(encoded_matches).toHaveLength(0);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
test('does not double-count secrets already detected by raw patterns', () => {
|
|
958
|
+
// A JWT is already detected directly — should not be re-detected as base64-encoded
|
|
959
|
+
const header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
|
|
960
|
+
const payload = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
|
|
961
|
+
const signature = 'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
962
|
+
const jwt = `${header}.${payload}.${signature}`;
|
|
963
|
+
const input = `token: ${jwt}`;
|
|
964
|
+
const matches = scanText(input);
|
|
965
|
+
const jwtMatches = matches.filter((m) => m.type === 'JSON Web Token');
|
|
966
|
+
const encodedMatches = matches.filter((m) => m.type.includes('base64-encoded'));
|
|
967
|
+
expect(jwtMatches).toHaveLength(1);
|
|
968
|
+
expect(encodedMatches).toHaveLength(0);
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// -- Percent-encoded secrets --
|
|
973
|
+
describe('percent-encoded', () => {
|
|
974
|
+
test('detects percent-encoded database connection string', () => {
|
|
975
|
+
const secret = 'postgres://user:secret@db.example.com:5432/mydb';
|
|
976
|
+
const encoded = encodeURIComponent(secret);
|
|
977
|
+
const input = `url=${encoded}`;
|
|
978
|
+
const matches = scanText(input);
|
|
979
|
+
const found = matches.find((m) => m.type === 'Database Connection String (percent-encoded)');
|
|
980
|
+
expect(found).toBeDefined();
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
test('detects percent-encoded secret assignment', () => {
|
|
984
|
+
const encoded = 'password%3D%22SuperSecret123%21%22';
|
|
985
|
+
const input = `data=${encoded}`;
|
|
986
|
+
const matches = scanText(input);
|
|
987
|
+
const found = matches.find((m) => m.type.includes('percent-encoded'));
|
|
988
|
+
expect(found).toBeDefined();
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
test('does not flag percent-encoded non-secret text', () => {
|
|
992
|
+
const encoded = 'hello%20world%20this%20is%20normal';
|
|
993
|
+
const input = `text=${encoded}`;
|
|
994
|
+
const matches = scanText(input);
|
|
995
|
+
const encoded_matches = matches.filter((m) => m.type.includes('percent-encoded'));
|
|
996
|
+
expect(encoded_matches).toHaveLength(0);
|
|
997
|
+
});
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// -- Hex-escaped secrets --
|
|
1001
|
+
describe('hex-escaped', () => {
|
|
1002
|
+
test('detects hex-escaped Stripe key', () => {
|
|
1003
|
+
const secret = 'sk_live_abcdefghijklmnopqrstuvwx';
|
|
1004
|
+
const escaped = Array.from(secret).map((c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, '0')}`).join('');
|
|
1005
|
+
const input = `value = "${escaped}"`;
|
|
1006
|
+
const matches = scanText(input);
|
|
1007
|
+
const found = matches.find((m) => m.type === 'Stripe Secret Key (hex-escaped)');
|
|
1008
|
+
expect(found).toBeDefined();
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
test('does not flag hex-escaped non-secret text', () => {
|
|
1012
|
+
const escaped = '\\x48\\x65\\x6c\\x6c\\x6f';
|
|
1013
|
+
const input = `value = "${escaped}"`;
|
|
1014
|
+
const matches = scanText(input);
|
|
1015
|
+
const encoded_matches = matches.filter((m) => m.type.includes('hex-escaped'));
|
|
1016
|
+
expect(encoded_matches).toHaveLength(0);
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// -- Continuous hex-encoded secrets --
|
|
1021
|
+
describe('hex-encoded (continuous)', () => {
|
|
1022
|
+
test('detects hex-encoded GitHub token', () => {
|
|
1023
|
+
const secret = `ghp_${'A'.repeat(36)}`;
|
|
1024
|
+
const hexEncoded = Buffer.from(secret).toString('hex');
|
|
1025
|
+
const input = `payload: ${hexEncoded}`;
|
|
1026
|
+
const matches = scanText(input);
|
|
1027
|
+
const found = matches.find((m) => m.type === 'GitHub Token (hex-encoded)');
|
|
1028
|
+
expect(found).toBeDefined();
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test('detects hex-encoded AWS access key', () => {
|
|
1032
|
+
const secret = 'AKIAIOSFODNN7REALKEY';
|
|
1033
|
+
const hexEncoded = Buffer.from(secret).toString('hex');
|
|
1034
|
+
const input = `data: ${hexEncoded}`;
|
|
1035
|
+
const matches = scanText(input);
|
|
1036
|
+
const found = matches.find((m) => m.type === 'AWS Access Key (hex-encoded)');
|
|
1037
|
+
expect(found).toBeDefined();
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test('does not flag hex strings that decode to non-secret text', () => {
|
|
1041
|
+
const hexEncoded = Buffer.from('This is just normal harmless text').toString('hex');
|
|
1042
|
+
const input = `data: ${hexEncoded}`;
|
|
1043
|
+
const matches = scanText(input);
|
|
1044
|
+
const encoded_matches = matches.filter((m) => m.type.includes('hex-encoded'));
|
|
1045
|
+
expect(encoded_matches).toHaveLength(0);
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
test('does not flag git SHAs or similar hex hashes', () => {
|
|
1049
|
+
// 40-char hex SHA — too short to decode to a meaningful secret
|
|
1050
|
+
const sha = '4b825dc642cb6eb9a060e54bf899d15f13fe1d7a';
|
|
1051
|
+
const input = `commit: ${sha}`;
|
|
1052
|
+
const matches = scanText(input);
|
|
1053
|
+
const encoded_matches = matches.filter((m) => m.type.includes('hex-encoded'));
|
|
1054
|
+
expect(encoded_matches).toHaveLength(0);
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
// -- Redaction of encoded secrets --
|
|
1059
|
+
describe('redaction of encoded secrets', () => {
|
|
1060
|
+
test('redactSecrets replaces base64-encoded secrets', () => {
|
|
1061
|
+
const secret = 'sk_live_abcdefghijklmnopqrstuvwx';
|
|
1062
|
+
const encoded = Buffer.from(secret).toString('base64');
|
|
1063
|
+
const input = `config: ${encoded}`;
|
|
1064
|
+
const result = redactSecrets(input);
|
|
1065
|
+
expect(result).toContain('<redacted type="Stripe Secret Key (base64-encoded)" />');
|
|
1066
|
+
expect(result).not.toContain(encoded);
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
test('redactSecrets replaces hex-encoded secrets', () => {
|
|
1070
|
+
const secret = `ghp_${'A'.repeat(36)}`;
|
|
1071
|
+
const hexEncoded = Buffer.from(secret).toString('hex');
|
|
1072
|
+
const input = `data: ${hexEncoded}`;
|
|
1073
|
+
const result = redactSecrets(input);
|
|
1074
|
+
expect(result).toContain('<redacted type="GitHub Token (hex-encoded)" />');
|
|
1075
|
+
expect(result).not.toContain(hexEncoded);
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
// ---------------------------------------------------------------------------
|
|
1081
|
+
// Decode helper unit tests
|
|
1082
|
+
// ---------------------------------------------------------------------------
|
|
1083
|
+
describe('decode helpers', () => {
|
|
1084
|
+
test('tryDecodeBase64 returns decoded text for valid base64', () => {
|
|
1085
|
+
const encoded = Buffer.from('sk_live_abcdefghijklmnopqrstuvwx').toString('base64');
|
|
1086
|
+
expect(_tryDecodeBase64(encoded)).toBe('sk_live_abcdefghijklmnopqrstuvwx');
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
test('tryDecodeBase64 returns null for binary content', () => {
|
|
1090
|
+
const binary = Buffer.from([0x00, 0x01, 0x80, 0xff]).toString('base64');
|
|
1091
|
+
expect(_tryDecodeBase64(binary)).toBeNull();
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test('tryDecodeBase64 returns null for invalid base64', () => {
|
|
1095
|
+
expect(_tryDecodeBase64('not!!valid!!base64!!')).toBeNull();
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
test('tryDecodePercentEncoded returns decoded text', () => {
|
|
1099
|
+
expect(_tryDecodePercentEncoded('hello%20world%21')).toBe('hello world!');
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
test('tryDecodePercentEncoded returns null when nothing to decode', () => {
|
|
1103
|
+
expect(_tryDecodePercentEncoded('no-encoding-here')).toBeNull();
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
test('tryDecodeHexEscapes returns decoded text', () => {
|
|
1107
|
+
expect(_tryDecodeHexEscapes('\\x48\\x65\\x6c\\x6c\\x6f')).toBe('Hello');
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
test('tryDecodeHexEscapes returns null when no escapes', () => {
|
|
1111
|
+
expect(_tryDecodeHexEscapes('plain text')).toBeNull();
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test('tryDecodeContinuousHex returns decoded text', () => {
|
|
1115
|
+
const hex = Buffer.from('Hello').toString('hex');
|
|
1116
|
+
expect(_tryDecodeContinuousHex(hex)).toBe('Hello');
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
test('tryDecodeContinuousHex returns null for non-printable result', () => {
|
|
1120
|
+
// Hex that decodes to binary
|
|
1121
|
+
expect(_tryDecodeContinuousHex('0001ff80')).toBeNull();
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
@@ -3,12 +3,14 @@ import type { Message } from '../providers/types.js';
|
|
|
3
3
|
import {
|
|
4
4
|
applyRuntimeInjections,
|
|
5
5
|
injectChannelCapabilityContext,
|
|
6
|
+
injectGuardianContext,
|
|
6
7
|
injectTemporalContext,
|
|
7
8
|
resolveChannelCapabilities,
|
|
8
9
|
stripChannelCapabilityContext,
|
|
10
|
+
stripGuardianContext,
|
|
9
11
|
stripTemporalContext,
|
|
10
12
|
} from '../daemon/session-runtime-assembly.js';
|
|
11
|
-
import type { ChannelCapabilities } from '../daemon/session-runtime-assembly.js';
|
|
13
|
+
import type { ChannelCapabilities, GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
12
14
|
import { buildChannelAwarenessSection } from '../config/system-prompt.js';
|
|
13
15
|
|
|
14
16
|
// ---------------------------------------------------------------------------
|
|
@@ -277,6 +279,12 @@ describe('buildChannelAwarenessSection', () => {
|
|
|
277
279
|
const section = buildChannelAwarenessSection();
|
|
278
280
|
expect(section).toContain('computer-control permissions on non-dashboard');
|
|
279
281
|
});
|
|
282
|
+
|
|
283
|
+
test('includes guardian context contract for channel actors', () => {
|
|
284
|
+
const section = buildChannelAwarenessSection();
|
|
285
|
+
expect(section).toContain('<guardian_context>');
|
|
286
|
+
expect(section).toContain('Never infer guardian status');
|
|
287
|
+
});
|
|
280
288
|
});
|
|
281
289
|
|
|
282
290
|
// ---------------------------------------------------------------------------
|
|
@@ -474,3 +482,79 @@ describe('applyRuntimeInjections with temporalContext', () => {
|
|
|
474
482
|
expect(result[0].content.length).toBe(1);
|
|
475
483
|
});
|
|
476
484
|
});
|
|
485
|
+
|
|
486
|
+
// ---------------------------------------------------------------------------
|
|
487
|
+
// guardian_context
|
|
488
|
+
// ---------------------------------------------------------------------------
|
|
489
|
+
|
|
490
|
+
describe('injectGuardianContext', () => {
|
|
491
|
+
const baseUserMessage: Message = {
|
|
492
|
+
role: 'user',
|
|
493
|
+
content: [{ type: 'text', text: 'Can you text me updates?' }],
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
test('prepends guardian_context block to user message', () => {
|
|
497
|
+
const ctx: GuardianRuntimeContext = {
|
|
498
|
+
sourceChannel: 'sms',
|
|
499
|
+
actorRole: 'guardian',
|
|
500
|
+
guardianExternalUserId: 'guardian-user-1',
|
|
501
|
+
guardianChatId: '+15550001111',
|
|
502
|
+
requesterIdentifier: '+15550001111',
|
|
503
|
+
requesterExternalUserId: 'guardian-user-1',
|
|
504
|
+
requesterChatId: '+15550001111',
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const result = injectGuardianContext(baseUserMessage, ctx);
|
|
508
|
+
expect(result.content.length).toBe(2);
|
|
509
|
+
const injected = result.content[0];
|
|
510
|
+
expect(injected.type).toBe('text');
|
|
511
|
+
const text = (injected as { type: 'text'; text: string }).text;
|
|
512
|
+
expect(text).toContain('<guardian_context>');
|
|
513
|
+
expect(text).toContain('actor_role: guardian');
|
|
514
|
+
expect(text).toContain('source_channel: sms');
|
|
515
|
+
expect(text).toContain('</guardian_context>');
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe('stripGuardianContext', () => {
|
|
520
|
+
test('strips guardian_context blocks from user messages', () => {
|
|
521
|
+
const messages: Message[] = [
|
|
522
|
+
{
|
|
523
|
+
role: 'user',
|
|
524
|
+
content: [
|
|
525
|
+
{ type: 'text', text: '<guardian_context>\nactor_role: guardian\n</guardian_context>' },
|
|
526
|
+
{ type: 'text', text: 'Hello' },
|
|
527
|
+
],
|
|
528
|
+
},
|
|
529
|
+
];
|
|
530
|
+
const result = stripGuardianContext(messages);
|
|
531
|
+
expect(result).toHaveLength(1);
|
|
532
|
+
expect(result[0].content).toHaveLength(1);
|
|
533
|
+
expect((result[0].content[0] as { type: 'text'; text: string }).text).toBe('Hello');
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('applyRuntimeInjections with guardianContext', () => {
|
|
538
|
+
const baseMessages: Message[] = [
|
|
539
|
+
{
|
|
540
|
+
role: 'user',
|
|
541
|
+
content: [{ type: 'text', text: 'Help me send this over SMS.' }],
|
|
542
|
+
},
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
test('injects guardian context when provided', () => {
|
|
546
|
+
const result = applyRuntimeInjections(baseMessages, {
|
|
547
|
+
guardianContext: {
|
|
548
|
+
sourceChannel: 'sms',
|
|
549
|
+
actorRole: 'non-guardian',
|
|
550
|
+
guardianExternalUserId: 'guardian-1',
|
|
551
|
+
requesterExternalUserId: 'requester-1',
|
|
552
|
+
requesterIdentifier: '+15550002222',
|
|
553
|
+
requesterChatId: '+15550002222',
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
expect(result).toHaveLength(1);
|
|
557
|
+
expect(result[0].content).toHaveLength(2);
|
|
558
|
+
expect((result[0].content[0] as { type: 'text'; text: string }).text).toContain('<guardian_context>');
|
|
559
|
+
});
|
|
560
|
+
});
|