@amodalai/runtime 0.3.49 → 0.3.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/src/agent/completion-tools.integration.test.d.ts +6 -0
  2. package/dist/src/agent/completion-tools.integration.test.js +263 -0
  3. package/dist/src/agent/completion-tools.integration.test.js.map +1 -0
  4. package/dist/src/agent/load-template-plan.integration.test.d.ts +6 -0
  5. package/dist/src/agent/load-template-plan.integration.test.js +152 -0
  6. package/dist/src/agent/load-template-plan.integration.test.js.map +1 -0
  7. package/dist/src/agent/local-server.js +64 -9
  8. package/dist/src/agent/local-server.js.map +1 -1
  9. package/dist/src/agent/loop-types.d.ts +12 -2
  10. package/dist/src/agent/loop.test.js +5 -2
  11. package/dist/src/agent/loop.test.js.map +1 -1
  12. package/dist/src/agent/propose-plan.integration.test.d.ts +6 -0
  13. package/dist/src/agent/propose-plan.integration.test.js +186 -0
  14. package/dist/src/agent/propose-plan.integration.test.js.map +1 -0
  15. package/dist/src/agent/routes/package-updates.d.ts +42 -0
  16. package/dist/src/agent/routes/package-updates.js +207 -0
  17. package/dist/src/agent/routes/package-updates.js.map +1 -0
  18. package/dist/src/agent/routes/package-updates.test.d.ts +6 -0
  19. package/dist/src/agent/routes/package-updates.test.js +25 -0
  20. package/dist/src/agent/routes/package-updates.test.js.map +1 -0
  21. package/dist/src/agent/setup-state.integration.test.d.ts +6 -0
  22. package/dist/src/agent/setup-state.integration.test.js +182 -0
  23. package/dist/src/agent/setup-state.integration.test.js.map +1 -0
  24. package/dist/src/agent/snapshot-server.js +1 -0
  25. package/dist/src/agent/snapshot-server.js.map +1 -1
  26. package/dist/src/agent/states/executing.d.ts +6 -0
  27. package/dist/src/agent/states/executing.js +63 -27
  28. package/dist/src/agent/states/executing.js.map +1 -1
  29. package/dist/src/agent/states/streaming.js +18 -2
  30. package/dist/src/agent/states/streaming.js.map +1 -1
  31. package/dist/src/agent/tool-executor-local.js +11 -2
  32. package/dist/src/agent/tool-executor-local.js.map +1 -1
  33. package/dist/src/agent/validate-connection.integration.test.d.ts +6 -0
  34. package/dist/src/agent/validate-connection.integration.test.js +160 -0
  35. package/dist/src/agent/validate-connection.integration.test.js.map +1 -0
  36. package/dist/src/api/create-agent.js +1 -0
  37. package/dist/src/api/create-agent.js.map +1 -1
  38. package/dist/src/index.d.ts +2 -0
  39. package/dist/src/index.js +9 -0
  40. package/dist/src/index.js.map +1 -1
  41. package/dist/src/intent/executor.d.ts +48 -0
  42. package/dist/src/intent/executor.js +420 -0
  43. package/dist/src/intent/executor.js.map +1 -0
  44. package/dist/src/intent/executor.test.d.ts +6 -0
  45. package/dist/src/intent/executor.test.js +543 -0
  46. package/dist/src/intent/executor.test.js.map +1 -0
  47. package/dist/src/intent/index.d.ts +10 -0
  48. package/dist/src/intent/index.js +9 -0
  49. package/dist/src/intent/index.js.map +1 -0
  50. package/dist/src/intent/loader.d.ts +16 -0
  51. package/dist/src/intent/loader.js +112 -0
  52. package/dist/src/intent/loader.js.map +1 -0
  53. package/dist/src/intent/loader.test.d.ts +6 -0
  54. package/dist/src/intent/loader.test.js +86 -0
  55. package/dist/src/intent/loader.test.js.map +1 -0
  56. package/dist/src/intent/matcher.d.ts +26 -0
  57. package/dist/src/intent/matcher.js +29 -0
  58. package/dist/src/intent/matcher.js.map +1 -0
  59. package/dist/src/intent/matcher.test.d.ts +6 -0
  60. package/dist/src/intent/matcher.test.js +53 -0
  61. package/dist/src/intent/matcher.test.js.map +1 -0
  62. package/dist/src/intent/onboarding.e2e.test.d.ts +6 -0
  63. package/dist/src/intent/onboarding.e2e.test.js +394 -0
  64. package/dist/src/intent/onboarding.e2e.test.js.map +1 -0
  65. package/dist/src/routes/ai-stream.js +96 -3
  66. package/dist/src/routes/ai-stream.js.map +1 -1
  67. package/dist/src/routes/session-resolver.js +16 -0
  68. package/dist/src/routes/session-resolver.js.map +1 -1
  69. package/dist/src/session/credential-scrubber.d.ts +35 -0
  70. package/dist/src/session/credential-scrubber.js +150 -0
  71. package/dist/src/session/credential-scrubber.js.map +1 -0
  72. package/dist/src/session/credential-scrubber.test.d.ts +6 -0
  73. package/dist/src/session/credential-scrubber.test.js +192 -0
  74. package/dist/src/session/credential-scrubber.test.js.map +1 -0
  75. package/dist/src/session/manager.intent.test.d.ts +6 -0
  76. package/dist/src/session/manager.intent.test.js +197 -0
  77. package/dist/src/session/manager.intent.test.js.map +1 -0
  78. package/dist/src/session/manager.js +114 -0
  79. package/dist/src/session/manager.js.map +1 -1
  80. package/dist/src/session/session-builder.d.ts +16 -1
  81. package/dist/src/session/session-builder.js +209 -41
  82. package/dist/src/session/session-builder.js.map +1 -1
  83. package/dist/src/session/store.js +48 -2
  84. package/dist/src/session/store.js.map +1 -1
  85. package/dist/src/session/tool-context-factory.js +12 -0
  86. package/dist/src/session/tool-context-factory.js.map +1 -1
  87. package/dist/src/session/types.d.ts +12 -1
  88. package/dist/src/setup/commit-setup.d.ts +94 -0
  89. package/dist/src/setup/commit-setup.js +154 -0
  90. package/dist/src/setup/commit-setup.js.map +1 -0
  91. package/dist/src/setup/commit-setup.test.d.ts +6 -0
  92. package/dist/src/setup/commit-setup.test.js +310 -0
  93. package/dist/src/setup/commit-setup.test.js.map +1 -0
  94. package/dist/src/tools/README.md +270 -0
  95. package/dist/src/tools/admin-tools.d.ts +27 -0
  96. package/dist/src/tools/admin-tools.js +734 -0
  97. package/dist/src/tools/admin-tools.js.map +1 -0
  98. package/dist/src/tools/agent-package-discovery.test.d.ts +6 -0
  99. package/dist/src/tools/agent-package-discovery.test.js +90 -0
  100. package/dist/src/tools/agent-package-discovery.test.js.map +1 -0
  101. package/dist/src/tools/builtin/ask-choice.d.ts +8 -0
  102. package/dist/src/tools/builtin/ask-choice.js +54 -0
  103. package/dist/src/tools/builtin/ask-choice.js.map +1 -0
  104. package/dist/src/tools/context.d.ts +154 -0
  105. package/dist/src/tools/context.js +30 -0
  106. package/dist/src/tools/context.js.map +1 -0
  107. package/dist/src/tools/custom-tool-adapter.d.ts +33 -2
  108. package/dist/src/tools/custom-tool-adapter.js +38 -1
  109. package/dist/src/tools/custom-tool-adapter.js.map +1 -1
  110. package/dist/src/tools/custom-tool-adapter.test.js +48 -0
  111. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
  112. package/dist/src/tools/fetch-url-tool.js +2 -0
  113. package/dist/src/tools/fetch-url-tool.js.map +1 -1
  114. package/dist/src/tools/file-tools.js +16 -0
  115. package/dist/src/tools/file-tools.js.map +1 -1
  116. package/dist/src/tools/fs/local.test.d.ts +6 -0
  117. package/dist/src/tools/fs/local.test.js +126 -0
  118. package/dist/src/tools/fs/local.test.js.map +1 -0
  119. package/dist/src/tools/index.d.ts +35 -0
  120. package/dist/src/tools/index.js +11 -0
  121. package/dist/src/tools/index.js.map +1 -0
  122. package/dist/src/tools/mcp-tool-adapter.js +2 -0
  123. package/dist/src/tools/mcp-tool-adapter.js.map +1 -1
  124. package/dist/src/tools/memory-tool.js +23 -1
  125. package/dist/src/tools/memory-tool.js.map +1 -1
  126. package/dist/src/tools/permissions.d.ts +36 -0
  127. package/dist/src/tools/permissions.js +97 -0
  128. package/dist/src/tools/permissions.js.map +1 -0
  129. package/dist/src/tools/permissions.test.d.ts +6 -0
  130. package/dist/src/tools/permissions.test.js +62 -0
  131. package/dist/src/tools/permissions.test.js.map +1 -0
  132. package/dist/src/tools/request-tool.js +2 -0
  133. package/dist/src/tools/request-tool.js.map +1 -1
  134. package/dist/src/tools/sdk-context.d.ts +43 -0
  135. package/dist/src/tools/sdk-context.js +94 -0
  136. package/dist/src/tools/sdk-context.js.map +1 -0
  137. package/dist/src/tools/sdk-context.test.d.ts +6 -0
  138. package/dist/src/tools/sdk-context.test.js +134 -0
  139. package/dist/src/tools/sdk-context.test.js.map +1 -0
  140. package/dist/src/tools/store-tools.js +6 -0
  141. package/dist/src/tools/store-tools.js.map +1 -1
  142. package/dist/src/tools/types.d.ts +53 -14
  143. package/dist/src/tools/web-search-tool.js +2 -0
  144. package/dist/src/tools/web-search-tool.js.map +1 -1
  145. package/dist/src/types.d.ts +164 -28
  146. package/dist/src/types.js +9 -3
  147. package/dist/src/types.js.map +1 -1
  148. package/dist/tsconfig.tsbuildinfo +1 -1
  149. package/package.json +16 -4
  150. package/dist/src/tools/agent-config-tool.d.ts +0 -7
  151. package/dist/src/tools/agent-config-tool.js +0 -78
  152. package/dist/src/tools/agent-config-tool.js.map +0 -1
  153. package/dist/src/tools/collect-secret-tool.d.ts +0 -7
  154. package/dist/src/tools/collect-secret-tool.js +0 -47
  155. package/dist/src/tools/collect-secret-tool.js.map +0 -1
  156. package/dist/src/tools/fs/http.d.ts +0 -37
  157. package/dist/src/tools/fs/http.js +0 -88
  158. package/dist/src/tools/fs/http.js.map +0 -1
  159. package/dist/src/tools/http-file-tools.d.ts +0 -13
  160. package/dist/src/tools/http-file-tools.js +0 -146
  161. package/dist/src/tools/http-file-tools.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Scrub credential-shaped substrings from a string. Returns the
8
+ * input unchanged when no match is found; otherwise returns a copy
9
+ * with each match replaced by `[REDACTED]` (or `KEY=[REDACTED]`
10
+ * for env-var lines).
11
+ */
12
+ export declare function scrubCredentials(text: string): string;
13
+ /**
14
+ * Scrub the messages array from a session's PersistedSession before
15
+ * write. Walks every user-role message and replaces credential-shaped
16
+ * substrings inside text content.
17
+ *
18
+ * Assistant messages aren't scrubbed because:
19
+ * 1. The model isn't supposed to echo credentials (and the F.4
20
+ * prompt rule plus this scrubber's coverage of user input mean
21
+ * it never sees them in the first place).
22
+ * 2. Scrubbing assistant output could chew on legitimately
23
+ * credential-shaped strings the agent quotes from a connection
24
+ * package's docs (e.g. "the format is sk_live_…").
25
+ *
26
+ * Tool-result messages aren't scrubbed because they're already
27
+ * structured by the tool and the chat surface; if a tool returns a
28
+ * raw token in its output, that's a tool bug to fix in the tool, not
29
+ * a generic scrubber concern.
30
+ *
31
+ * The function is shape-tolerant — it walks any object/array and
32
+ * applies the scrubber to string-valued `text` / `content` fields
33
+ * inside user-role messages. Anything else passes through unchanged.
34
+ */
35
+ export declare function scrubMessagesForPersistence(messages: readonly unknown[]): unknown[];
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Credential scrubber — Phase F.5 of the admin-setup build plan.
8
+ *
9
+ * Belt-and-suspenders for the inline-paste credential redirect rule
10
+ * (F.4): if the user pastes a token in chat anyway, the runtime
11
+ * still sees the raw text in the active SSE stream so the agent can
12
+ * recognize and redirect — but anything written to the database via
13
+ * `sessionToRow` is sanitized to `[REDACTED]`.
14
+ *
15
+ * The scrubber runs at the persistence boundary, not on the
16
+ * in-memory message array. The agent's reasoning context keeps the
17
+ * raw text for one turn (it needs to see the credential to recognize
18
+ * it as one); only the persisted history is sanitized.
19
+ */
20
+ const REDACTED = '[REDACTED]';
21
+ // ---------------------------------------------------------------------------
22
+ // Patterns
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Well-known credential prefixes. Each entry is a single regex that
26
+ * matches the whole token; we replace with `[REDACTED]` to fully
27
+ * sanitize. Adding a new vendor: extend the alternation; tests pin
28
+ * the existing matches.
29
+ */
30
+ const TOKEN_PREFIX_PATTERNS = [
31
+ // Slack: xoxb-, xoxp-, xapp-, xoxa-, xoxr-, xoxs- followed by dash-separated segments.
32
+ /\bxox[abprs]-[A-Za-z0-9-]{20,}/g,
33
+ // Slack app-level token: xapp-1-...
34
+ /\bxapp-\d+-[A-Za-z0-9-]{20,}/g,
35
+ // Stripe (and Stripe-style) live + test keys: sk_live_..., sk_test_..., pk_live_..., pk_test_...
36
+ /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
37
+ // Anthropic Claude API keys: sk-ant-api03-...
38
+ /\bsk-ant-(?:api|sid)\d+-[A-Za-z0-9_-]{16,}/g,
39
+ // OpenAI API keys: sk-... (older format) and sk-proj-... (project-scoped).
40
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{32,}/g,
41
+ // GitHub PATs / fine-grained / install tokens: ghp_, gho_, ghr_, ghs_, gha_, github_pat_
42
+ /\bgh[opsra]_[A-Za-z0-9]{30,}/g,
43
+ /\bgithub_pat_[A-Za-z0-9_]{40,}/g,
44
+ // Google API keys: AIza...
45
+ /\bAIza[A-Za-z0-9_-]{30,}/g,
46
+ // AWS access key id: AKIA + 16 chars; secret keys are 40-char base64ish.
47
+ /\bAKIA[A-Z0-9]{14,20}/g,
48
+ // Resend API keys: re_... + 20+ chars.
49
+ /\bre_[A-Za-z0-9]{20,}/g,
50
+ // SendGrid API keys: SG.<24>.<43+>
51
+ /\bSG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{40,}/g,
52
+ // Twilio auth tokens: 32-char hex (without a public prefix). Only
53
+ // match when adjacent to a "TWILIO_AUTH_TOKEN" hint so we don't
54
+ // chew on every 32-char hex string. Handled via env-var line scrub
55
+ // below; intentionally not added as a standalone pattern.
56
+ // HuggingFace user access tokens: hf_...
57
+ /\bhf_[A-Za-z0-9]{30,}/g,
58
+ ];
59
+ /**
60
+ * `KEY=VALUE` env-var line shape. Matches a line that opens with an
61
+ * uppercase env-var-style key followed by `=` and 16+ non-whitespace
62
+ * characters. Catches `SLACK_BOT_TOKEN=xoxb-…` even when the value
63
+ * format isn't in our prefix list (e.g. raw 32-char hex tokens like
64
+ * Twilio's auth token).
65
+ *
66
+ * The match is intentionally line-anchored — `KEY=VALUE` mid-prose
67
+ * is ambiguous (could be code-snippet output the agent should see).
68
+ * If it shows up at the start of a line, treat as a credential.
69
+ *
70
+ * We preserve the key name (so the agent / user can see what they
71
+ * tried to paste) and replace only the value with `[REDACTED]`.
72
+ */
73
+ const ENV_VAR_LINE_PATTERN = /^(\s*)([A-Z][A-Z0-9_]{2,}=)([^\s]{16,})$/gm;
74
+ // ---------------------------------------------------------------------------
75
+ // Public API
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * Scrub credential-shaped substrings from a string. Returns the
79
+ * input unchanged when no match is found; otherwise returns a copy
80
+ * with each match replaced by `[REDACTED]` (or `KEY=[REDACTED]`
81
+ * for env-var lines).
82
+ */
83
+ export function scrubCredentials(text) {
84
+ if (typeof text !== 'string' || text.length === 0)
85
+ return text;
86
+ let scrubbed = text;
87
+ for (const pattern of TOKEN_PREFIX_PATTERNS) {
88
+ scrubbed = scrubbed.replace(pattern, REDACTED);
89
+ }
90
+ scrubbed = scrubbed.replace(ENV_VAR_LINE_PATTERN, (_match, indent, keyEq) => `${indent}${keyEq}${REDACTED}`);
91
+ return scrubbed;
92
+ }
93
+ /**
94
+ * Scrub the messages array from a session's PersistedSession before
95
+ * write. Walks every user-role message and replaces credential-shaped
96
+ * substrings inside text content.
97
+ *
98
+ * Assistant messages aren't scrubbed because:
99
+ * 1. The model isn't supposed to echo credentials (and the F.4
100
+ * prompt rule plus this scrubber's coverage of user input mean
101
+ * it never sees them in the first place).
102
+ * 2. Scrubbing assistant output could chew on legitimately
103
+ * credential-shaped strings the agent quotes from a connection
104
+ * package's docs (e.g. "the format is sk_live_…").
105
+ *
106
+ * Tool-result messages aren't scrubbed because they're already
107
+ * structured by the tool and the chat surface; if a tool returns a
108
+ * raw token in its output, that's a tool bug to fix in the tool, not
109
+ * a generic scrubber concern.
110
+ *
111
+ * The function is shape-tolerant — it walks any object/array and
112
+ * applies the scrubber to string-valued `text` / `content` fields
113
+ * inside user-role messages. Anything else passes through unchanged.
114
+ */
115
+ export function scrubMessagesForPersistence(messages) {
116
+ return messages.map((message) => {
117
+ if (!isObject(message))
118
+ return message;
119
+ if (message['role'] !== 'user')
120
+ return message;
121
+ return scrubUserMessage(message);
122
+ });
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Internals
126
+ // ---------------------------------------------------------------------------
127
+ function scrubUserMessage(message) {
128
+ const content = message['content'];
129
+ if (typeof content === 'string') {
130
+ return { ...message, content: scrubCredentials(content) };
131
+ }
132
+ if (Array.isArray(content)) {
133
+ return { ...message, content: content.map(scrubContentPart) };
134
+ }
135
+ return message;
136
+ }
137
+ function scrubContentPart(part) {
138
+ if (!isObject(part))
139
+ return part;
140
+ // The Vercel AI SDK ModelMessage user-content shape: {type: 'text', text: '...'}
141
+ // or {type: 'image', image: '...', mediaType: '...'}. We only scrub text parts.
142
+ if (part['type'] === 'text' && typeof part['text'] === 'string') {
143
+ return { ...part, text: scrubCredentials(part['text']) };
144
+ }
145
+ return part;
146
+ }
147
+ function isObject(value) {
148
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
149
+ }
150
+ //# sourceMappingURL=credential-scrubber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-scrubber.js","sourceRoot":"","sources":["../../../src/session/credential-scrubber.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,192 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Phase F.5 — credential scrubber tests. Pin each token-prefix
8
+ * pattern, the env-var line shape, and the user-message-only walking
9
+ * behaviour. The asserted leakage check at the bottom is the
10
+ * load-bearing guarantee: no recognized credential shape survives
11
+ * `scrubMessagesForPersistence`.
12
+ */
13
+ import { describe, expect, it } from 'vitest';
14
+ import { scrubCredentials, scrubMessagesForPersistence, } from './credential-scrubber.js';
15
+ describe('scrubCredentials — token prefix patterns', () => {
16
+ it('redacts Slack bot, user, app, and admin tokens', () => {
17
+ // Synthetic fixtures: structurally match the scrubber regex
18
+ // (`xox[abprs]-[A-Za-z0-9-]{20,}` etc.) but use EXAMPLE / TEST
19
+ // markers instead of realistic-looking segments so the values
20
+ // can't be misread as real Slack tokens by secret scanners.
21
+ const tokens = [
22
+ 'xoxb-EXAMPLE-EXAMPLE-EXAMPLEEXAMPLEEXAMPLE',
23
+ 'xoxp-EXAMPLE-EXAMPLE-EXAMPLEEXAMPLEEXAMPLE',
24
+ 'xoxa-1-EXAMPLE-EXAMPLE-EXAMPLEEXAMPLEEXAMPLE',
25
+ 'xapp-1-EXAMPLE-EXAMPLE-EXAMPLEEXAMPLEEXAMPLE',
26
+ ];
27
+ for (const t of tokens) {
28
+ const scrubbed = scrubCredentials(`my token is ${t} please keep`);
29
+ expect(scrubbed).toContain('[REDACTED]');
30
+ expect(scrubbed).not.toContain(t);
31
+ }
32
+ });
33
+ it('redacts Stripe live + test keys (sk_/pk_/rk_)', () => {
34
+ const tokens = [
35
+ 'sk_live_abcdef0123456789ABCDEF',
36
+ 'sk_test_abcdef0123456789ABCDEF',
37
+ 'pk_live_abcdef0123456789ABCDEF',
38
+ 'rk_test_abcdef0123456789ABCDEF',
39
+ ];
40
+ for (const t of tokens) {
41
+ expect(scrubCredentials(`use ${t} now`)).not.toContain(t);
42
+ }
43
+ });
44
+ it('redacts Anthropic API keys', () => {
45
+ const t = 'sk-ant-api03-aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890';
46
+ expect(scrubCredentials(`KEY: ${t}`)).not.toContain(t);
47
+ });
48
+ it('redacts OpenAI API keys (legacy + project-scoped)', () => {
49
+ const legacy = 'sk-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789aB';
50
+ const proj = 'sk-proj-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789aB';
51
+ expect(scrubCredentials(`legacy ${legacy}`)).not.toContain(legacy);
52
+ expect(scrubCredentials(`proj ${proj}`)).not.toContain(proj);
53
+ });
54
+ it('redacts GitHub PATs and fine-grained tokens', () => {
55
+ const tokens = [
56
+ 'ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789',
57
+ 'gho_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789',
58
+ 'ghs_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789',
59
+ 'github_pat_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789_extra',
60
+ ];
61
+ for (const t of tokens) {
62
+ expect(scrubCredentials(`token: ${t}`)).not.toContain(t);
63
+ }
64
+ });
65
+ it('redacts Google API keys (AIza prefix)', () => {
66
+ const t = 'AIzaSyABCDEFGHIJKLMNOPQRSTUVWXYZ0123456';
67
+ expect(scrubCredentials(`google: ${t}`)).not.toContain(t);
68
+ });
69
+ it('redacts AWS access key IDs (AKIA prefix)', () => {
70
+ const t = 'AKIAIOSFODNN7EXAMPLE';
71
+ expect(scrubCredentials(`AWS ${t}`)).not.toContain(t);
72
+ });
73
+ it('redacts Resend, SendGrid, and HuggingFace tokens', () => {
74
+ const resend = 're_aBcDeFgHiJkLmNoPqRsTuV';
75
+ const sendgrid = 'SG.aBcDeFgHiJkLmNoPqRsTu.aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789aBcDe';
76
+ const hf = 'hf_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345';
77
+ expect(scrubCredentials(`r ${resend}`)).not.toContain(resend);
78
+ expect(scrubCredentials(`s ${sendgrid}`)).not.toContain(sendgrid);
79
+ expect(scrubCredentials(`h ${hf}`)).not.toContain(hf);
80
+ });
81
+ });
82
+ describe('scrubCredentials — env-var line shape', () => {
83
+ it('redacts KEY=VALUE on its own line, preserving the key', () => {
84
+ const input = 'TWILIO_AUTH_TOKEN=abcdef0123456789abcdef0123456789';
85
+ const out = scrubCredentials(input);
86
+ expect(out).toBe('TWILIO_AUTH_TOKEN=[REDACTED]');
87
+ });
88
+ it('handles indented env-var lines and multi-line .env paste', () => {
89
+ const input = [
90
+ 'SLACK_BOT_TOKEN=xoxb-EXAMPLE-EXAMPLEEXAMPLEEXAMPLE',
91
+ ' TWILIO_AUTH_TOKEN=abcdef0123456789abcdef0123456789',
92
+ 'OPENAI_API_KEY=sk-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789aB',
93
+ ].join('\n');
94
+ const out = scrubCredentials(input);
95
+ expect(out).not.toContain('xoxb-EXAMPLE');
96
+ expect(out).not.toContain('abcdef0123456789abcdef0123456789');
97
+ expect(out).not.toContain('sk-aBcDeFgHiJkLmNoPqRsTuV');
98
+ expect(out).toContain('SLACK_BOT_TOKEN=');
99
+ expect(out).toContain('TWILIO_AUTH_TOKEN=');
100
+ expect(out).toContain('OPENAI_API_KEY=');
101
+ expect(out.split('[REDACTED]').length - 1).toBeGreaterThanOrEqual(3);
102
+ });
103
+ it('does not match KEY=VALUE mid-prose (line-anchored)', () => {
104
+ // No anchor → not an env-var line. The token regex still wouldn't
105
+ // match a 16-char alpha string, so we expect the input to pass
106
+ // through unchanged.
107
+ const input = 'set FOO=somevaluethatislong16 in the shell';
108
+ expect(scrubCredentials(input)).toBe(input);
109
+ });
110
+ });
111
+ describe('scrubCredentials — passthrough', () => {
112
+ it('returns plain text unchanged', () => {
113
+ const input = 'Connect Slack via OAuth and pick a channel.';
114
+ expect(scrubCredentials(input)).toBe(input);
115
+ });
116
+ it('handles empty + non-string input', () => {
117
+ expect(scrubCredentials('')).toBe('');
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- defensive type guard test
119
+ expect(scrubCredentials(null)).toBe(null);
120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- defensive type guard test
121
+ expect(scrubCredentials(undefined)).toBe(undefined);
122
+ });
123
+ it('does not chew arbitrary base64-ish strings without recognized prefix', () => {
124
+ const input = 'commit hash abcdef0123456789deadbeef0123456789cafef00d';
125
+ expect(scrubCredentials(input)).toBe(input);
126
+ });
127
+ });
128
+ describe('scrubMessagesForPersistence', () => {
129
+ it('scrubs string content of user messages', () => {
130
+ const messages = [
131
+ { role: 'user', content: 'my key is sk_live_abcdef0123456789ABCDEF, keep secret' },
132
+ ];
133
+ const out = scrubMessagesForPersistence(messages);
134
+ const first = out[0];
135
+ expect(first.role).toBe('user');
136
+ expect(first.content).toContain('[REDACTED]');
137
+ expect(first.content).not.toContain('sk_live_');
138
+ });
139
+ it('scrubs text parts inside structured user content', () => {
140
+ const messages = [
141
+ {
142
+ role: 'user',
143
+ content: [
144
+ { type: 'text', text: 'before sk-ant-api03-aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890 after' },
145
+ { type: 'image', image: 'data:...', mediaType: 'image/png' },
146
+ ],
147
+ },
148
+ ];
149
+ const out = scrubMessagesForPersistence(messages);
150
+ const parts = out[0].content;
151
+ expect(parts[0]?.['text']).toContain('[REDACTED]');
152
+ expect(parts[0]?.['text']).not.toContain('sk-ant-api03');
153
+ // Image part untouched.
154
+ expect(parts[1]).toEqual({ type: 'image', image: 'data:...', mediaType: 'image/png' });
155
+ });
156
+ it('does not scrub assistant or tool messages', () => {
157
+ const assistantText = 'API key format is sk_live_abcdef0123456789ABCDEF';
158
+ const messages = [
159
+ { role: 'assistant', content: assistantText },
160
+ { role: 'tool', content: [{ type: 'tool-result', toolName: 'foo', output: 'sk_live_abcdef0123456789ABCDEF' }] },
161
+ ];
162
+ const out = scrubMessagesForPersistence(messages);
163
+ expect(out[0].content).toBe(assistantText);
164
+ // Tool message passes through unchanged.
165
+ expect(out[1]).toEqual(messages[1]);
166
+ });
167
+ it('passes through non-object entries unchanged', () => {
168
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- shape-tolerant input check
169
+ const messages = ['unexpected string', 42, null];
170
+ const out = scrubMessagesForPersistence(messages);
171
+ expect(out).toEqual(messages);
172
+ });
173
+ it('no-leakage guarantee: every recognized pattern is gone from persisted form', () => {
174
+ const known = [
175
+ 'xoxb-EXAMPLE-EXAMPLE-EXAMPLEEXAMPLEEXAMPLE',
176
+ 'sk_live_abcdef0123456789ABCDEF',
177
+ 'sk-ant-api03-aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890',
178
+ 'sk-proj-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789aB',
179
+ 'ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789',
180
+ 'AIzaSyABCDEFGHIJKLMNOPQRSTUVWXYZ0123456',
181
+ 'AKIAIOSFODNN7EXAMPLE',
182
+ 'hf_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345',
183
+ ];
184
+ const blob = `please ignore: ${known.join(' and ')}`;
185
+ const out = scrubMessagesForPersistence([{ role: 'user', content: blob }]);
186
+ const persisted = JSON.stringify(out);
187
+ for (const t of known) {
188
+ expect(persisted).not.toContain(t);
189
+ }
190
+ });
191
+ });
192
+ //# sourceMappingURL=credential-scrubber.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-scrubber.test.js","sourceRoot":"","sources":["../../../src/session/credential-scrubber.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Phase 2 hook test — verifies `runMessage` short-circuits past the
8
+ * agent loop when an intent matches, and falls through to the LLM
9
+ * when it doesn't. Doesn't require a database; uses the in-memory
10
+ * SessionManager + a stub provider that throws if the LLM gets
11
+ * invoked (so a leaked agent-loop call fails the test).
12
+ */
13
+ import { describe, it, expect } from 'vitest';
14
+ import { z } from 'zod';
15
+ import { StandaloneSessionManager } from './manager.js';
16
+ import { SSEEventType } from '../types.js';
17
+ import { createLogger } from '../logger.js';
18
+ import { createToolRegistry } from '../tools/registry.js';
19
+ const logger = createLogger({ component: 'test:manager-intent' });
20
+ // ---------------------------------------------------------------------------
21
+ // Stubs
22
+ // ---------------------------------------------------------------------------
23
+ /** Provider that records whether streamText was called. Pass-through
24
+ * text response when invoked so fall-through paths still produce a
25
+ * valid SSE stream. */
26
+ function recordingProvider() {
27
+ let calls = 0;
28
+ return {
29
+ model: 'test-model',
30
+ provider: 'test-provider',
31
+ languageModel: {},
32
+ streamText() {
33
+ calls++;
34
+ const usage = { inputTokens: 1, outputTokens: 1, totalTokens: 2 };
35
+ const events = [
36
+ { type: 'text-delta', textDelta: 'fallthrough text' },
37
+ { type: 'finish', usage },
38
+ ];
39
+ async function* fullStream() {
40
+ for (const e of events)
41
+ yield e;
42
+ }
43
+ async function* textStream() {
44
+ yield 'fallthrough text';
45
+ }
46
+ return {
47
+ fullStream: fullStream(),
48
+ textStream: textStream(),
49
+ usage: Promise.resolve(usage),
50
+ text: Promise.resolve('fallthrough text'),
51
+ responseMessages: Promise.resolve([{ role: 'assistant', content: 'fallthrough text' }]),
52
+ };
53
+ },
54
+ generateText: () => Promise.reject(new Error('not used')),
55
+ streamTextCalled: () => calls,
56
+ };
57
+ }
58
+ function permissive() {
59
+ return { check: () => ({ allowed: true }) };
60
+ }
61
+ function makeOpts(intents, provider) {
62
+ const reg = createToolRegistry();
63
+ const echoTool = {
64
+ description: 'echo',
65
+ parameters: z.object({ slug: z.string() }),
66
+ readOnly: true,
67
+ runningLabel: "Echoing '{{slug}}'",
68
+ completedLabel: "Echoed '{{slug}}'",
69
+ execute: async (params) => ({ echoed: params.slug }),
70
+ };
71
+ reg.register('echo', echoTool);
72
+ return {
73
+ provider,
74
+ toolRegistry: reg,
75
+ permissionChecker: permissive(),
76
+ systemPrompt: 'test',
77
+ intents,
78
+ };
79
+ }
80
+ async function collect(gen) {
81
+ const out = [];
82
+ for await (const e of gen)
83
+ out.push(e);
84
+ return out;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Tests
88
+ // ---------------------------------------------------------------------------
89
+ describe('SessionManager.runMessage — intent routing', () => {
90
+ it('runs the matched intent and skips the LLM entirely', async () => {
91
+ const installIntent = {
92
+ id: 'install',
93
+ regex: /^Set up template '(.+)'\.?$/,
94
+ handle: async (ctx) => {
95
+ await ctx.callTool('echo', { slug: ctx.match[1] });
96
+ return {};
97
+ },
98
+ };
99
+ const provider = recordingProvider();
100
+ const mgr = new StandaloneSessionManager({ logger });
101
+ const session = mgr.create(makeOpts([installIntent], provider));
102
+ const events = await collect(mgr.runMessage(session.id, "Set up template 'marketing-digest'."));
103
+ // LLM not invoked.
104
+ expect(provider.streamTextCalled()).toBe(0);
105
+ const types = events.map((e) => e.type);
106
+ expect(types).toEqual([
107
+ SSEEventType.ToolCallStart,
108
+ SSEEventType.ToolCallResult,
109
+ SSEEventType.Done,
110
+ ]);
111
+ // Tool call's params carry the captured slug.
112
+ const start = events[0];
113
+ expect(start.tool_name).toBe('echo');
114
+ expect(start.parameters['slug']).toBe('marketing-digest');
115
+ // session.messages has user + synthetic assistant + tool result.
116
+ const stored = mgr.get(session.id)?.messages ?? [];
117
+ expect(stored).toHaveLength(3);
118
+ expect(stored[0].role).toBe('user');
119
+ expect(stored[1].role).toBe('assistant');
120
+ expect(stored[2].role).toBe('tool');
121
+ });
122
+ it('falls through to the LLM when no intent matches', async () => {
123
+ const noMatchIntent = {
124
+ id: 'never',
125
+ regex: /^never going to match$/,
126
+ handle: async () => null,
127
+ };
128
+ const provider = recordingProvider();
129
+ const mgr = new StandaloneSessionManager({ logger });
130
+ const session = mgr.create(makeOpts([noMatchIntent], provider));
131
+ await collect(mgr.runMessage(session.id, 'something completely different'));
132
+ expect(provider.streamTextCalled()).toBe(1);
133
+ });
134
+ it('falls through to the LLM when handler returns null pre-commit', async () => {
135
+ const precheckIntent = {
136
+ id: 'precheck',
137
+ regex: /^run$/,
138
+ handle: async () => null, // bail before any tool call
139
+ };
140
+ const provider = recordingProvider();
141
+ const mgr = new StandaloneSessionManager({ logger });
142
+ const session = mgr.create(makeOpts([precheckIntent], provider));
143
+ await collect(mgr.runMessage(session.id, 'run'));
144
+ expect(provider.streamTextCalled()).toBe(1);
145
+ });
146
+ it('skips intent routing for multimodal (image-bearing) turns', async () => {
147
+ const wouldMatchIntent = {
148
+ id: 'wouldmatch',
149
+ regex: /^describe$/,
150
+ handle: async () => {
151
+ throw new Error('intent should not have run for image turn');
152
+ },
153
+ };
154
+ const provider = recordingProvider();
155
+ const mgr = new StandaloneSessionManager({ logger });
156
+ const session = mgr.create(makeOpts([wouldMatchIntent], provider));
157
+ // Anthropic supports vision, so images flow through to the LLM.
158
+ Object.assign(session, { providerName: 'anthropic' });
159
+ await collect(mgr.runMessage(session.id, 'describe', {
160
+ images: [{ mimeType: 'image/png', data: 'iVBORw0KGgo=' }],
161
+ }));
162
+ // LLM was called (image turn bypasses intents) and intent didn't run.
163
+ expect(provider.streamTextCalled()).toBe(1);
164
+ });
165
+ it('agents with no intents behave exactly like before', async () => {
166
+ const provider = recordingProvider();
167
+ const mgr = new StandaloneSessionManager({ logger });
168
+ const session = mgr.create(makeOpts([], provider));
169
+ await collect(mgr.runMessage(session.id, 'hello'));
170
+ expect(provider.streamTextCalled()).toBe(1);
171
+ });
172
+ it('intent returning {continue: true} runs tools AND invokes the LLM with the new state', async () => {
173
+ const continueIntent = {
174
+ id: 'half-deterministic',
175
+ regex: /^do half$/,
176
+ handle: async (ctx) => {
177
+ await ctx.callTool('echo', { slug: 'half' });
178
+ return { continue: true };
179
+ },
180
+ };
181
+ const provider = recordingProvider();
182
+ const mgr = new StandaloneSessionManager({ logger });
183
+ const session = mgr.create(makeOpts([continueIntent], provider));
184
+ await collect(mgr.runMessage(session.id, 'do half'));
185
+ // LLM was invoked exactly once, after the intent's tool ran.
186
+ expect(provider.streamTextCalled()).toBe(1);
187
+ // Stored messages: user, assistant (intent's tool call), tool
188
+ // (intent's tool result), assistant (LLM's response).
189
+ const stored = mgr.get(session.id)?.messages ?? [];
190
+ expect(stored.length).toBeGreaterThanOrEqual(4);
191
+ expect(stored[0].role).toBe('user');
192
+ expect(stored[1].role).toBe('assistant'); // intent
193
+ expect(stored[2].role).toBe('tool'); // intent's tool result
194
+ expect(stored[3].role).toBe('assistant'); // LLM continuation
195
+ });
196
+ });
197
+ //# sourceMappingURL=manager.intent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.intent.test.js","sourceRoot":"","sources":["../../../src/session/manager.intent.test.ts"],"names":[],"mappings":"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"}