@amodalai/runtime 0.3.48 → 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.
- package/dist/src/agent/completion-tools.integration.test.d.ts +6 -0
- package/dist/src/agent/completion-tools.integration.test.js +263 -0
- package/dist/src/agent/completion-tools.integration.test.js.map +1 -0
- package/dist/src/agent/load-template-plan.integration.test.d.ts +6 -0
- package/dist/src/agent/load-template-plan.integration.test.js +152 -0
- package/dist/src/agent/load-template-plan.integration.test.js.map +1 -0
- package/dist/src/agent/local-server.js +64 -9
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/loop-types.d.ts +12 -2
- package/dist/src/agent/loop.test.js +5 -2
- package/dist/src/agent/loop.test.js.map +1 -1
- package/dist/src/agent/propose-plan.integration.test.d.ts +6 -0
- package/dist/src/agent/propose-plan.integration.test.js +186 -0
- package/dist/src/agent/propose-plan.integration.test.js.map +1 -0
- package/dist/src/agent/routes/package-updates.d.ts +42 -0
- package/dist/src/agent/routes/package-updates.js +207 -0
- package/dist/src/agent/routes/package-updates.js.map +1 -0
- package/dist/src/agent/routes/package-updates.test.d.ts +6 -0
- package/dist/src/agent/routes/package-updates.test.js +25 -0
- package/dist/src/agent/routes/package-updates.test.js.map +1 -0
- package/dist/src/agent/setup-state.integration.test.d.ts +6 -0
- package/dist/src/agent/setup-state.integration.test.js +182 -0
- package/dist/src/agent/setup-state.integration.test.js.map +1 -0
- package/dist/src/agent/snapshot-server.js +1 -0
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/agent/states/executing.d.ts +6 -0
- package/dist/src/agent/states/executing.js +63 -27
- package/dist/src/agent/states/executing.js.map +1 -1
- package/dist/src/agent/states/streaming.js +18 -2
- package/dist/src/agent/states/streaming.js.map +1 -1
- package/dist/src/agent/tool-executor-local.js +11 -2
- package/dist/src/agent/tool-executor-local.js.map +1 -1
- package/dist/src/agent/validate-connection.integration.test.d.ts +6 -0
- package/dist/src/agent/validate-connection.integration.test.js +160 -0
- package/dist/src/agent/validate-connection.integration.test.js.map +1 -0
- package/dist/src/api/create-agent.js +1 -0
- package/dist/src/api/create-agent.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/intent/executor.d.ts +48 -0
- package/dist/src/intent/executor.js +420 -0
- package/dist/src/intent/executor.js.map +1 -0
- package/dist/src/intent/executor.test.d.ts +6 -0
- package/dist/src/intent/executor.test.js +543 -0
- package/dist/src/intent/executor.test.js.map +1 -0
- package/dist/src/intent/index.d.ts +10 -0
- package/dist/src/intent/index.js +9 -0
- package/dist/src/intent/index.js.map +1 -0
- package/dist/src/intent/loader.d.ts +16 -0
- package/dist/src/intent/loader.js +112 -0
- package/dist/src/intent/loader.js.map +1 -0
- package/dist/src/intent/loader.test.d.ts +6 -0
- package/dist/src/intent/loader.test.js +86 -0
- package/dist/src/intent/loader.test.js.map +1 -0
- package/dist/src/intent/matcher.d.ts +26 -0
- package/dist/src/intent/matcher.js +29 -0
- package/dist/src/intent/matcher.js.map +1 -0
- package/dist/src/intent/matcher.test.d.ts +6 -0
- package/dist/src/intent/matcher.test.js +53 -0
- package/dist/src/intent/matcher.test.js.map +1 -0
- package/dist/src/intent/onboarding.e2e.test.d.ts +6 -0
- package/dist/src/intent/onboarding.e2e.test.js +394 -0
- package/dist/src/intent/onboarding.e2e.test.js.map +1 -0
- package/dist/src/routes/ai-stream.js +96 -3
- package/dist/src/routes/ai-stream.js.map +1 -1
- package/dist/src/routes/session-resolver.js +16 -0
- package/dist/src/routes/session-resolver.js.map +1 -1
- package/dist/src/session/credential-scrubber.d.ts +35 -0
- package/dist/src/session/credential-scrubber.js +150 -0
- package/dist/src/session/credential-scrubber.js.map +1 -0
- package/dist/src/session/credential-scrubber.test.d.ts +6 -0
- package/dist/src/session/credential-scrubber.test.js +192 -0
- package/dist/src/session/credential-scrubber.test.js.map +1 -0
- package/dist/src/session/manager.intent.test.d.ts +6 -0
- package/dist/src/session/manager.intent.test.js +197 -0
- package/dist/src/session/manager.intent.test.js.map +1 -0
- package/dist/src/session/manager.js +114 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/session-builder.d.ts +16 -1
- package/dist/src/session/session-builder.js +209 -40
- package/dist/src/session/session-builder.js.map +1 -1
- package/dist/src/session/store.js +48 -2
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/tool-context-factory.js +12 -0
- package/dist/src/session/tool-context-factory.js.map +1 -1
- package/dist/src/session/types.d.ts +12 -1
- package/dist/src/setup/commit-setup.d.ts +94 -0
- package/dist/src/setup/commit-setup.js +154 -0
- package/dist/src/setup/commit-setup.js.map +1 -0
- package/dist/src/setup/commit-setup.test.d.ts +6 -0
- package/dist/src/setup/commit-setup.test.js +310 -0
- package/dist/src/setup/commit-setup.test.js.map +1 -0
- package/dist/src/tools/README.md +270 -0
- package/dist/src/tools/admin-tools.d.ts +27 -0
- package/dist/src/tools/admin-tools.js +734 -0
- package/dist/src/tools/admin-tools.js.map +1 -0
- package/dist/src/tools/agent-package-discovery.test.d.ts +6 -0
- package/dist/src/tools/agent-package-discovery.test.js +90 -0
- package/dist/src/tools/agent-package-discovery.test.js.map +1 -0
- package/dist/src/tools/builtin/ask-choice.d.ts +8 -0
- package/dist/src/tools/builtin/ask-choice.js +54 -0
- package/dist/src/tools/builtin/ask-choice.js.map +1 -0
- package/dist/src/tools/context.d.ts +154 -0
- package/dist/src/tools/context.js +30 -0
- package/dist/src/tools/context.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.d.ts +33 -2
- package/dist/src/tools/custom-tool-adapter.js +38 -1
- package/dist/src/tools/custom-tool-adapter.js.map +1 -1
- package/dist/src/tools/custom-tool-adapter.test.js +48 -0
- package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
- package/dist/src/tools/fetch-url-tool.js +2 -0
- package/dist/src/tools/fetch-url-tool.js.map +1 -1
- package/dist/src/tools/file-tools.js +16 -0
- package/dist/src/tools/file-tools.js.map +1 -1
- package/dist/src/tools/fs/local.test.d.ts +6 -0
- package/dist/src/tools/fs/local.test.js +126 -0
- package/dist/src/tools/fs/local.test.js.map +1 -0
- package/dist/src/tools/index.d.ts +35 -0
- package/dist/src/tools/index.js +11 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.js +2 -0
- package/dist/src/tools/mcp-tool-adapter.js.map +1 -1
- package/dist/src/tools/memory-tool.js +23 -1
- package/dist/src/tools/memory-tool.js.map +1 -1
- package/dist/src/tools/permissions.d.ts +36 -0
- package/dist/src/tools/permissions.js +97 -0
- package/dist/src/tools/permissions.js.map +1 -0
- package/dist/src/tools/permissions.test.d.ts +6 -0
- package/dist/src/tools/permissions.test.js +62 -0
- package/dist/src/tools/permissions.test.js.map +1 -0
- package/dist/src/tools/request-tool.js +2 -0
- package/dist/src/tools/request-tool.js.map +1 -1
- package/dist/src/tools/sdk-context.d.ts +43 -0
- package/dist/src/tools/sdk-context.js +94 -0
- package/dist/src/tools/sdk-context.js.map +1 -0
- package/dist/src/tools/sdk-context.test.d.ts +6 -0
- package/dist/src/tools/sdk-context.test.js +134 -0
- package/dist/src/tools/sdk-context.test.js.map +1 -0
- package/dist/src/tools/store-tools.js +6 -0
- package/dist/src/tools/store-tools.js.map +1 -1
- package/dist/src/tools/types.d.ts +53 -14
- package/dist/src/tools/web-search-tool.js +2 -0
- package/dist/src/tools/web-search-tool.js.map +1 -1
- package/dist/src/types.d.ts +164 -28
- package/dist/src/types.js +9 -3
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -4
- package/dist/src/tools/agent-config-tool.d.ts +0 -7
- package/dist/src/tools/agent-config-tool.js +0 -78
- package/dist/src/tools/agent-config-tool.js.map +0 -1
- package/dist/src/tools/collect-secret-tool.d.ts +0 -7
- package/dist/src/tools/collect-secret-tool.js +0 -47
- package/dist/src/tools/collect-secret-tool.js.map +0 -1
- package/dist/src/tools/fs/http.d.ts +0 -37
- package/dist/src/tools/fs/http.js +0 -88
- package/dist/src/tools/fs/http.js.map +0 -1
- package/dist/src/tools/http-file-tools.d.ts +0 -13
- package/dist/src/tools/http-file-tools.js +0 -146
- 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,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,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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"}
|