@gaunt-sloth/core 2.0.0-alpha.19 → 2.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/config/defaults.js +6 -4
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts +29 -0
- package/dist/config/loader.js +158 -8
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +45 -0
- package/dist/config/schema.js +45 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +17 -0
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/types.d.ts +60 -0
- package/dist/config/types.js.map +1 -1
- package/dist/constants.d.ts +11 -1
- package/dist/constants.js +11 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +28 -1
- package/dist/core/GthAbstractAgent.js +227 -33
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthLangChainAgent.js +31 -14
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/providers/geminiSchemaSanitizer.d.ts +52 -0
- package/dist/providers/geminiSchemaSanitizer.js +201 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/google-genai.js +4 -1
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/ollama.d.ts +18 -4
- package/dist/providers/ollama.js +55 -37
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openrouter.js +5 -0
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +4 -1
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/runtime/conversation.d.ts +59 -0
- package/dist/runtime/conversation.js +137 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +3 -2
- package/dist/runtime/singleShot.js +16 -4
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/debugDump.d.ts +30 -8
- package/dist/utils/debugDump.js +133 -10
- package/dist/utils/debugDump.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +238 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +92 -0
- package/dist/utils/systemPromptNotes.js +114 -0
- package/dist/utils/systemPromptNotes.js.map +1 -1
- package/dist/utils/systemUtils.d.ts +1 -1
- package/dist/utils/systemUtils.js +7 -2
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- package/package.json +5 -1
- package/schema/gsloth-config.schema.json +91 -0
package/dist/utils/debugDump.js
CHANGED
|
@@ -3,8 +3,9 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { inspect } from 'node:util';
|
|
5
5
|
import { ensureGlobalGslothDir } from '#src/utils/globalConfigUtils.js';
|
|
6
|
-
import { getSlothVersion } from '#src/utils/systemUtils.js';
|
|
6
|
+
import { env, getSlothVersion } from '#src/utils/systemUtils.js';
|
|
7
7
|
import { getDebugLogBuffer } from '#src/utils/debugUtils.js';
|
|
8
|
+
import { REDACTED, collectSecretValues, redactText, redactValue, } from '#src/utils/redactSecrets.js';
|
|
8
9
|
/**
|
|
9
10
|
* Best-effort git repo state (branch/remote/dirty). Uses `execFileSync` — NOT the existing
|
|
10
11
|
* `execAsync` helper in systemUtils.ts (a promisified wrapper used elsewhere for `gh` calls) —
|
|
@@ -71,21 +72,143 @@ function safeStringify(value) {
|
|
|
71
72
|
return inspect(value, { showHidden: false, depth: 5, colors: false });
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The filesystem-safe directory-name segment for one dump: the ISO timestamp with `:` and `.`
|
|
77
|
+
* (illegal on Windows, noisy everywhere) replaced by `-`. This is the ONLY path component
|
|
78
|
+
* debugDump generates — and the only one it is responsible for sanitizing. The parent it is joined
|
|
79
|
+
* under (the global `~/.gsloth` dir) is supplied by the environment and may legitimately carry a
|
|
80
|
+
* drive-letter colon on Windows (`C:\…`), which is not ours to strip. Exported so the invariant
|
|
81
|
+
* "the generated segment is colon-free" is testable on any platform without asserting anything
|
|
82
|
+
* about the (platform-dependent) parent path (GS2-50).
|
|
83
|
+
*/
|
|
84
|
+
export function debugDumpDirName(date = new Date()) {
|
|
85
|
+
return date.toISOString().replace(/[:.]/g, '-');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* GS2-54 (gap 3) — a live LangChain chat model is detected structurally, NOT by field name: a real
|
|
89
|
+
* `BaseChatModel` exposes `_llmType()` and `invoke()` as functions. This duck-type is the LOAD-BEARING
|
|
90
|
+
* invariant that lets us strip a *live* model to a descriptor (below) WITHOUT also flattening a plain
|
|
91
|
+
* `{ type, model, apiKey }` config object — the latter must keep flowing through {@link redactValue}'s
|
|
92
|
+
* field-masking (key kept, value masked). Do NOT "simplify" this into an unconditional strip.
|
|
93
|
+
*/
|
|
94
|
+
function isLiveChatModel(llm) {
|
|
95
|
+
if (llm == null || typeof llm !== 'object')
|
|
96
|
+
return false;
|
|
97
|
+
const m = llm;
|
|
98
|
+
return typeof m._llmType === 'function' || typeof m.invoke === 'function';
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* GS2-54 (gap 3) — a short, secret-free descriptor for a live chat model, mirroring the spirit of
|
|
102
|
+
* `configCommand.ts`'s `redactConfigForPrint`. Never returns the live object or any of its internals
|
|
103
|
+
* (client instances, cached kwargs, inline keys) — only a `type` + `model` label. Each field probe
|
|
104
|
+
* is guarded so a hostile/exotic getter can't throw out of here.
|
|
105
|
+
*/
|
|
106
|
+
function describeLiveModel(config, llm) {
|
|
107
|
+
const m = llm;
|
|
108
|
+
let type = 'unknown';
|
|
109
|
+
try {
|
|
110
|
+
const t = (typeof m._llmType === 'function' ? m._llmType() : undefined) ??
|
|
111
|
+
(Array.isArray(m.lc_namespace) ? m.lc_namespace.join('/') : undefined) ??
|
|
112
|
+
(typeof config.type === 'string'
|
|
113
|
+
? config.type
|
|
114
|
+
: undefined) ??
|
|
115
|
+
m.constructor?.name;
|
|
116
|
+
if (typeof t === 'string' && t.length > 0)
|
|
117
|
+
type = t;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// keep 'unknown' — a throwing getter must not defeat the strip
|
|
121
|
+
}
|
|
122
|
+
let model = 'unknown';
|
|
123
|
+
try {
|
|
124
|
+
const md = config.modelDisplayName;
|
|
125
|
+
const mdl = (typeof md === 'string' && md.length > 0 ? md : undefined) ??
|
|
126
|
+
(typeof m.model === 'string' ? m.model : undefined) ??
|
|
127
|
+
(typeof m.modelName === 'string' ? m.modelName : undefined);
|
|
128
|
+
if (typeof mdl === 'string' && mdl.length > 0)
|
|
129
|
+
model = mdl;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// keep 'unknown'
|
|
133
|
+
}
|
|
134
|
+
return { type, model };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* GS2-54 (gap 3) — defense-in-depth for the CONFIG artifact: return a shallow clone of `config` with
|
|
138
|
+
* a *live* `llm` (a `BaseChatModel`) replaced by a `{ type, model }` descriptor, so the model's large
|
|
139
|
+
* internal surface (client objects, cached kwargs, inline keys) never reaches disk — rather than
|
|
140
|
+
* relying on {@link redactValue}'s field-masking alone. PURE: never mutates the caller's config.
|
|
141
|
+
* FAIL-SAFE: on any error, or when `llm` is absent / not a live model, return the ORIGINAL config
|
|
142
|
+
* unchanged so {@link redactValue}'s existing GS2-47 masking still applies (never less-redacted).
|
|
143
|
+
*/
|
|
144
|
+
function stripLiveModelForConfig(config) {
|
|
145
|
+
try {
|
|
146
|
+
if (config == null || typeof config !== 'object' || Array.isArray(config))
|
|
147
|
+
return config;
|
|
148
|
+
const record = config;
|
|
149
|
+
if (!('llm' in record) || !isLiveChatModel(record.llm))
|
|
150
|
+
return config;
|
|
151
|
+
return { ...record, llm: describeLiveModel(record, record.llm) };
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return config; // fail safe: fall back to redactValue over the untouched config
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* GS2-47 — render the CONFIG artifact: {@link redactValue} applies sensitive-field masking (keys
|
|
159
|
+
* kept, values masked) plus literal/pattern string redaction, then {@link safeStringify}. Fail-safe:
|
|
160
|
+
* any throw yields the fully-withheld marker rather than raw content ("redact more on error, never
|
|
161
|
+
* write a raw artifact because redaction hiccuped").
|
|
162
|
+
*
|
|
163
|
+
* GS2-54 (gap 3) — before redaction, {@link stripLiveModelForConfig} swaps a live `llm` for a
|
|
164
|
+
* `{ type, model }` descriptor so the live model's internals never serialize. This runs on a fresh
|
|
165
|
+
* clone only; the caller's config (and the {@link collectSecretValues} harvest already taken from it)
|
|
166
|
+
* is untouched, so inline secrets inside the model are still scrubbed from the OTHER artifacts.
|
|
167
|
+
*/
|
|
168
|
+
function renderConfig(config, redact, secrets) {
|
|
169
|
+
if (!redact)
|
|
170
|
+
return safeStringify(config);
|
|
171
|
+
try {
|
|
172
|
+
return safeStringify(redactValue(stripLiveModelForConfig(config), secrets));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return REDACTED;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* GS2-47 — render a non-config STRUCTURED artifact (transcript, env, git-state): stringify (never
|
|
180
|
+
* throws), then literal/pattern-redact the text. Structural field-masking is intentionally NOT
|
|
181
|
+
* applied here (config-only, per the brief) so a legitimately `token`/`secret`-named field in tool
|
|
182
|
+
* output is not blanket-masked. {@link redactText} is itself fail-safe.
|
|
183
|
+
*/
|
|
184
|
+
function renderStructured(value, redact, secrets) {
|
|
185
|
+
const raw = safeStringify(value);
|
|
186
|
+
return redact ? redactText(raw, secrets) : raw;
|
|
187
|
+
}
|
|
74
188
|
/**
|
|
75
189
|
* Write one timestamped `/debug-dump` archive under the GLOBAL `~/.gsloth/debug-dumps/<timestamp>/`
|
|
76
190
|
* (via `ensureGlobalGslothDir()` — mirrors how `resolveHistoryDbPath()` builds its path under the
|
|
77
191
|
* same dir — NOT the per-project cwd-relative helper of the same name elsewhere in this codebase).
|
|
78
192
|
* Contains: the full transcript, the resolved config, env/version info, the in-memory debugLog
|
|
79
|
-
* ring buffer, and (best-effort) git repo state.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
193
|
+
* ring buffer, and (best-effort) git repo state.
|
|
194
|
+
*
|
|
195
|
+
* GS2-47 — unless `input.redact === false`, the shared secret-redaction pass
|
|
196
|
+
* ({@link file://./redactSecrets.ts}) is applied to EVERY artifact before it is written: the literal
|
|
197
|
+
* values of secret-named env vars + inline config secrets are substituted everywhere, a tight set of
|
|
198
|
+
* provider-key/auth-header patterns is masked, and the config's sensitive fields are masked in place.
|
|
199
|
+
* On opt-out the archive is raw and the caller surfaces the loud "may contain secrets" warning.
|
|
82
200
|
*/
|
|
83
201
|
export function writeDebugDump(input) {
|
|
84
|
-
const
|
|
202
|
+
const redact = input.redact !== false; // default ON; only an explicit `false` opts out
|
|
203
|
+
const timestamp = debugDumpDirName();
|
|
85
204
|
const archiveDir = resolve(ensureGlobalGslothDir(), 'debug-dumps', timestamp);
|
|
86
205
|
mkdirSync(archiveDir, { recursive: true });
|
|
87
|
-
|
|
88
|
-
|
|
206
|
+
// Technique 1 (load-bearing): the literal secret values to scrub across ALL artifacts, gathered
|
|
207
|
+
// from env (by secret-named var) + the config (apiKeyEnvironmentVariable's target + inline
|
|
208
|
+
// secrets). `env` is the systemUtils accessor (process.env); collection is itself fail-safe.
|
|
209
|
+
const secrets = redact ? collectSecretValues(input.config, env) : [];
|
|
210
|
+
writeFileSync(resolve(archiveDir, 'transcript.json'), renderStructured(input.transcript, redact, secrets), 'utf8');
|
|
211
|
+
writeFileSync(resolve(archiveDir, 'config.json'), renderConfig(input.config, redact, secrets), 'utf8');
|
|
89
212
|
// getSlothVersion() reads the installed package.json via the install dir set at CLI startup
|
|
90
213
|
// (setEntryPoint); guarded so a dump can never fail just because that wasn't wired (e.g. an
|
|
91
214
|
// unusual embedding), falling back to 'unknown'.
|
|
@@ -102,11 +225,11 @@ export function writeDebugDump(input) {
|
|
|
102
225
|
platform: process.platform,
|
|
103
226
|
model: input.modelDisplayName ?? 'unknown',
|
|
104
227
|
};
|
|
105
|
-
writeFileSync(resolve(archiveDir, 'env.json'),
|
|
106
|
-
writeFileSync(resolve(archiveDir, 'debug-log.txt'), getDebugLogBuffer().join('\n'), 'utf8');
|
|
228
|
+
writeFileSync(resolve(archiveDir, 'env.json'), renderStructured(envInfo, redact, secrets), 'utf8');
|
|
229
|
+
writeFileSync(resolve(archiveDir, 'debug-log.txt'), redact ? redactText(getDebugLogBuffer().join('\n'), secrets) : getDebugLogBuffer().join('\n'), 'utf8');
|
|
107
230
|
const gitState = collectGitState(input.cwd ?? process.cwd());
|
|
108
231
|
if (gitState) {
|
|
109
|
-
writeFileSync(resolve(archiveDir, 'git-state.json'),
|
|
232
|
+
writeFileSync(resolve(archiveDir, 'git-state.json'), renderStructured(gitState, redact, secrets), 'utf8');
|
|
110
233
|
}
|
|
111
234
|
return { archiveDir };
|
|
112
235
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debugDump.js","sourceRoot":"","sources":["../../src/utils/debugDump.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"debugDump.js","sourceRoot":"","sources":["../../src/utils/debugDump.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* GS2-47 — a shared, reusable secret-redaction pass for diagnostic output.
|
|
4
|
+
*
|
|
5
|
+
* Built for `/debug-dump` ({@link file://./debugDump.ts}) but deliberately generic and
|
|
6
|
+
* dependency-free — it takes `env` as a PARAMETER rather than reading `process.env`, so the pass is
|
|
7
|
+
* pure, deterministic in tests, and reusable by GS2-48's crash-report handler (which hard-depends on
|
|
8
|
+
* this module). Nothing here reads ambient process state.
|
|
9
|
+
*
|
|
10
|
+
* DESIGN DECISION (settled by the coordinator, GS2-47): **PATTERN-ONLY** redaction — we do NOT scan
|
|
11
|
+
* for high-entropy strings. Rationale: pattern-only is safe to reason about and near-zero
|
|
12
|
+
* false-positive; entropy scanning catches marginally more but produces false-positive redactions
|
|
13
|
+
* the user has to squint through, degrading the dump's debug value. A high-entropy scanner is a
|
|
14
|
+
* DELIBERATELY-DEFERRED option (a config surface for it could be added later if ever warranted) and
|
|
15
|
+
* is intentionally NOT implemented here.
|
|
16
|
+
*
|
|
17
|
+
* Three techniques, in priority order:
|
|
18
|
+
* 1. [load-bearing] Known-secret VALUE substitution — collect the literal values of secret-named
|
|
19
|
+
* env vars + inline config secrets, then substitute every occurrence across ALL artifacts. This
|
|
20
|
+
* catches a leaked key wherever it surfaces (config, env, transcript, log) with no guessing.
|
|
21
|
+
* 2. Provider key/token PATTERNS — a tight, documented, prefix-anchored set of well-known key
|
|
22
|
+
* shapes plus explicit auth-header contexts. We never blanket-redact long alphanumeric strings.
|
|
23
|
+
* 3. Sensitive config-FIELD masking — mask the VALUES of secret-named config keys while preserving
|
|
24
|
+
* structure (the key stays; only the value becomes the marker).
|
|
25
|
+
*
|
|
26
|
+
* Fail-safe throughout: on any internal error the functions redact MORE (return the marker), never
|
|
27
|
+
* less — a redaction hiccup must never cause raw content to be emitted.
|
|
28
|
+
*/
|
|
29
|
+
/** The visible, greppable placeholder substituted for every redacted secret value. */
|
|
30
|
+
export declare const REDACTED = "<redacted>";
|
|
31
|
+
/**
|
|
32
|
+
* Collect the literal secret VALUES to substitute everywhere (technique 1), from two sources:
|
|
33
|
+
* - process-env vars whose NAME matches {@link SECRET_ENV_NAME_RE}, PLUS the specific var(s) named
|
|
34
|
+
* by any `apiKeyEnvironmentVariable` in the config;
|
|
35
|
+
* - non-empty inline secret field values in the config (technique 3's field names), so a key pasted
|
|
36
|
+
* inline is scrubbed wherever it *also* surfaces (transcript, log, env), not only in config.json.
|
|
37
|
+
*
|
|
38
|
+
* `env` is a PARAMETER — never read ambiently — so the pass is pure, reusable (GS2-48) and
|
|
39
|
+
* deterministic in tests. Values shorter than {@link MIN_SECRET_LITERAL_LENGTH} are skipped. The
|
|
40
|
+
* result is returned longest-first so an overlapping-substring secret can't leave a shorter one
|
|
41
|
+
* partially intact. Never throws (a hostile config getter is swallowed — patterns + structural
|
|
42
|
+
* masking still apply).
|
|
43
|
+
*/
|
|
44
|
+
export declare function collectSecretValues(config: unknown, env: Record<string, string | undefined>): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Redact a STRING: substitute every known literal secret value (technique 1, longest-first), then
|
|
47
|
+
* apply the provider key/auth patterns (technique 2). Never throws — on any error it returns the
|
|
48
|
+
* fully-withheld marker (fail safe: redact MORE, never emit the raw text). Applying it twice is
|
|
49
|
+
* safe (re-redacting already-redacted text is a no-op).
|
|
50
|
+
*/
|
|
51
|
+
export declare function redactText(text: string, secrets: readonly string[]): string;
|
|
52
|
+
/**
|
|
53
|
+
* Deep-redact an arbitrary value for serialization: mask the VALUES of secret-named fields
|
|
54
|
+
* (technique 3 — structure preserved: the key stays, only its value becomes the marker) and run
|
|
55
|
+
* {@link redactText} over every string leaf (techniques 1 + 2). Circular refs are broken; functions
|
|
56
|
+
* and bigints are rendered the way `safeStringify` would, so the result is JSON-safe. Pure — never
|
|
57
|
+
* mutates the input. `apiKeyEnvironmentVariable` is intentionally NOT masked (it is a var name).
|
|
58
|
+
*
|
|
59
|
+
* Intended for the CONFIG artifact (where field-name masking is wanted). Non-config artifacts use
|
|
60
|
+
* {@link redactText} over their stringified form instead (literal + pattern only), so a legitimate
|
|
61
|
+
* `token`/`secret`-named field in tool output is not blanket-masked.
|
|
62
|
+
*/
|
|
63
|
+
export declare function redactValue(value: unknown, secrets: readonly string[]): unknown;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* GS2-47 — a shared, reusable secret-redaction pass for diagnostic output.
|
|
4
|
+
*
|
|
5
|
+
* Built for `/debug-dump` ({@link file://./debugDump.ts}) but deliberately generic and
|
|
6
|
+
* dependency-free — it takes `env` as a PARAMETER rather than reading `process.env`, so the pass is
|
|
7
|
+
* pure, deterministic in tests, and reusable by GS2-48's crash-report handler (which hard-depends on
|
|
8
|
+
* this module). Nothing here reads ambient process state.
|
|
9
|
+
*
|
|
10
|
+
* DESIGN DECISION (settled by the coordinator, GS2-47): **PATTERN-ONLY** redaction — we do NOT scan
|
|
11
|
+
* for high-entropy strings. Rationale: pattern-only is safe to reason about and near-zero
|
|
12
|
+
* false-positive; entropy scanning catches marginally more but produces false-positive redactions
|
|
13
|
+
* the user has to squint through, degrading the dump's debug value. A high-entropy scanner is a
|
|
14
|
+
* DELIBERATELY-DEFERRED option (a config surface for it could be added later if ever warranted) and
|
|
15
|
+
* is intentionally NOT implemented here.
|
|
16
|
+
*
|
|
17
|
+
* Three techniques, in priority order:
|
|
18
|
+
* 1. [load-bearing] Known-secret VALUE substitution — collect the literal values of secret-named
|
|
19
|
+
* env vars + inline config secrets, then substitute every occurrence across ALL artifacts. This
|
|
20
|
+
* catches a leaked key wherever it surfaces (config, env, transcript, log) with no guessing.
|
|
21
|
+
* 2. Provider key/token PATTERNS — a tight, documented, prefix-anchored set of well-known key
|
|
22
|
+
* shapes plus explicit auth-header contexts. We never blanket-redact long alphanumeric strings.
|
|
23
|
+
* 3. Sensitive config-FIELD masking — mask the VALUES of secret-named config keys while preserving
|
|
24
|
+
* structure (the key stays; only the value becomes the marker).
|
|
25
|
+
*
|
|
26
|
+
* Fail-safe throughout: on any internal error the functions redact MORE (return the marker), never
|
|
27
|
+
* less — a redaction hiccup must never cause raw content to be emitted.
|
|
28
|
+
*/
|
|
29
|
+
/** The visible, greppable placeholder substituted for every redacted secret value. */
|
|
30
|
+
export const REDACTED = '<redacted>';
|
|
31
|
+
/**
|
|
32
|
+
* Minimum length for a collected VALUE to be treated as a substitutable secret literal (technique
|
|
33
|
+
* 1). Guards against redacting trivial values ("1", "on", a short model name) that happen to be the
|
|
34
|
+
* value of a secret-named env var — substituting those everywhere would gut the dump's debug value
|
|
35
|
+
* with false positives (exactly the trap the pattern-only decision avoids). Short sensitive *config*
|
|
36
|
+
* fields are still masked structurally by key name (technique 3), so nothing sensitive slips through
|
|
37
|
+
* this floor.
|
|
38
|
+
*/
|
|
39
|
+
const MIN_SECRET_LITERAL_LENGTH = 6;
|
|
40
|
+
/** Max recursion depth for the config walks — a guard against pathological/deep object graphs. */
|
|
41
|
+
const MAX_REDACT_DEPTH = 12;
|
|
42
|
+
/**
|
|
43
|
+
* env-var NAME shapes whose VALUE is a secret to substitute everywhere (technique 1): `*_API_KEY`,
|
|
44
|
+
* `*_TOKEN`, `*_SECRET`, `*_KEY`, and anything containing `PASSWORD`. Case-insensitive.
|
|
45
|
+
*/
|
|
46
|
+
const SECRET_ENV_NAME_RE = /(?:_API_KEY|_TOKEN|_SECRET|_KEY)$|PASSWORD/i;
|
|
47
|
+
/**
|
|
48
|
+
* Config-FIELD NAME shapes whose VALUE is masked in place (technique 3), matched case-insensitively
|
|
49
|
+
* at any depth. Mirrors `configCommand.ts`'s `redactConfigForPrint` set for cross-surface
|
|
50
|
+
* consistency. NOTE: `apiKeyEnvironmentVariable` also matches `api?key`, but it holds a var NAME
|
|
51
|
+
* (not a secret) and is what technique 1 reads to find the real key's value — so it is EXCLUDED via
|
|
52
|
+
* {@link NON_SECRET_KEY_NAMES}.
|
|
53
|
+
*/
|
|
54
|
+
const SECRET_KEY_RE = /(api[-_]?key|secret|token|password|passwd|authorization|bearer|credential)/i;
|
|
55
|
+
/** Field names that match {@link SECRET_KEY_RE} but must NOT be masked (they hold a var name). */
|
|
56
|
+
const NON_SECRET_KEY_NAMES = new Set(['apikeyenvironmentvariable']);
|
|
57
|
+
/**
|
|
58
|
+
* Provider key / auth-header PATTERNS (technique 2) — a tight, prefix-anchored set. Each entry is
|
|
59
|
+
* `[regex, replacement]`. Kept deliberately narrow (well-known key prefixes + explicit auth
|
|
60
|
+
* contexts); we do NOT blanket-redact long alphanumeric strings — that IS the entropy trap the
|
|
61
|
+
* pattern-only decision avoids. A long opaque token is only redacted when it sits in an auth context
|
|
62
|
+
* (`Bearer …` / `Authorization: …`), never on its own.
|
|
63
|
+
*/
|
|
64
|
+
const PROVIDER_PATTERNS = [
|
|
65
|
+
// OpenAI (`sk-…`) and Anthropic (`sk-ant-…`, also `sk-`-prefixed) secret keys.
|
|
66
|
+
[/\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}/g, REDACTED],
|
|
67
|
+
// Google API keys (`AIza…`).
|
|
68
|
+
[/\bAIza[0-9A-Za-z_-]{16,}/g, REDACTED],
|
|
69
|
+
// xAI (`xai-…`).
|
|
70
|
+
[/\bxai-[A-Za-z0-9_-]{16,}/g, REDACTED],
|
|
71
|
+
// Groq (`gsk_…`).
|
|
72
|
+
[/\bgsk_[A-Za-z0-9_-]{16,}/g, REDACTED],
|
|
73
|
+
// GS2-54 (gap 2) — GitHub tokens. Classic/scoped PATs share the `gh[oprsu]_` prefix (personal
|
|
74
|
+
// `ghp_`, OAuth `gho_`, user-to-server `ghu_`, server-to-server `ghs_`, refresh `ghr_`); the value
|
|
75
|
+
// is base62 (no `_`). Prefix-anchored + a length floor, same tight style as the provider keys —
|
|
76
|
+
// NOT a blanket long-alphanumeric redaction (that IS the entropy trap avoided).
|
|
77
|
+
[/\bgh[oprsu]_[A-Za-z0-9]{16,}/g, REDACTED],
|
|
78
|
+
// GS2-54 (gap 2) — GitHub fine-grained PATs (`github_pat_…`); the value carries an inner `_`, so
|
|
79
|
+
// its charset includes `_`. Distinct prefix from the classic rule above (`github_` ≠ `gh[oprsu]_`).
|
|
80
|
+
[/\bgithub_pat_[A-Za-z0-9_]{16,}/g, REDACTED],
|
|
81
|
+
// GS2-54 (gap 2) — credentials embedded in a URL (`scheme://user:password@host`). Redact the
|
|
82
|
+
// `user:password` userinfo, KEEP the scheme + host so the artifact stays debuggable. Bounded: the
|
|
83
|
+
// userinfo stops at `@` (and the password never spans `/` or whitespace), so it cannot swallow the
|
|
84
|
+
// rest of the URL/line. A URL without userinfo (`https://host:port/path`) has no `user:pass@` and
|
|
85
|
+
// is left untouched.
|
|
86
|
+
[/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, `$1${REDACTED}@`],
|
|
87
|
+
// GS2-54 (gap 1) — an `Authorization` header value (`Authorization: <scheme?> <credential>` /
|
|
88
|
+
// `"Authorization":"…"`): keep the header name + separator, redact the value REGARDLESS OF SCHEME.
|
|
89
|
+
// Generalized beyond the old fixed `Bearer|Basic|Token|Digest|Negotiate` list so a non-standard
|
|
90
|
+
// scheme (`ApiKey <secret>`, an unknown scheme, `AWS4-HMAC-SHA256 Credential=…`) no longer leaks
|
|
91
|
+
// its token beside the marker. Bounded to <scheme> + ONE credential token (two token runs max,
|
|
92
|
+
// separated by horizontal whitespace only — never a newline), so it can never swallow following
|
|
93
|
+
// prose the way the original `[^"]+` did. An optional value-quote is consumed so JSON
|
|
94
|
+
// `"Authorization":"…"` works too. Runs before the SigV4 and standalone-`Bearer` rules so a normal
|
|
95
|
+
// header collapses to a single marker; a SigV4 header's `Credential=…` is eaten here and its
|
|
96
|
+
// trailing `Signature=…` is mopped up by the next rule.
|
|
97
|
+
[
|
|
98
|
+
/(\bAuthorization["']?\s*[:=]\s*["']?)[A-Za-z0-9._~+/=-]+(?:[ \t]+[A-Za-z0-9._~+/=-]+)?/gi,
|
|
99
|
+
`$1${REDACTED}`,
|
|
100
|
+
],
|
|
101
|
+
// GS2-54 (gap 1) — AWS SigV4 authorization components. The header is comma-separated `key=value`
|
|
102
|
+
// pairs; redact the SENSITIVE `Credential=…` (access-key id) and `Signature=…` values wherever they
|
|
103
|
+
// appear (header value OR a presigned-URL `X-Amz-Credential=…&X-Amz-Signature=…` query), keeping the
|
|
104
|
+
// key name for debuggability. `SignedHeaders=…` is just header names and is intentionally NOT
|
|
105
|
+
// touched. The value charset excludes whitespace/comma/quote/`&`, so it stops at the component
|
|
106
|
+
// boundary and never swallows the next pair or trailing prose.
|
|
107
|
+
[/\b(Credential|Signature)=[A-Za-z0-9._~+/=-]+/gi, `$1=${REDACTED}`],
|
|
108
|
+
// A standalone `Bearer <token>` — keep the scheme word, redact only the credential. A long opaque
|
|
109
|
+
// token is redacted ONLY in this auth context, never on its own (that is the entropy trap avoided).
|
|
110
|
+
[/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi, `$1${REDACTED}`],
|
|
111
|
+
];
|
|
112
|
+
/**
|
|
113
|
+
* Collect the literal secret VALUES to substitute everywhere (technique 1), from two sources:
|
|
114
|
+
* - process-env vars whose NAME matches {@link SECRET_ENV_NAME_RE}, PLUS the specific var(s) named
|
|
115
|
+
* by any `apiKeyEnvironmentVariable` in the config;
|
|
116
|
+
* - non-empty inline secret field values in the config (technique 3's field names), so a key pasted
|
|
117
|
+
* inline is scrubbed wherever it *also* surfaces (transcript, log, env), not only in config.json.
|
|
118
|
+
*
|
|
119
|
+
* `env` is a PARAMETER — never read ambiently — so the pass is pure, reusable (GS2-48) and
|
|
120
|
+
* deterministic in tests. Values shorter than {@link MIN_SECRET_LITERAL_LENGTH} are skipped. The
|
|
121
|
+
* result is returned longest-first so an overlapping-substring secret can't leave a shorter one
|
|
122
|
+
* partially intact. Never throws (a hostile config getter is swallowed — patterns + structural
|
|
123
|
+
* masking still apply).
|
|
124
|
+
*/
|
|
125
|
+
export function collectSecretValues(config, env) {
|
|
126
|
+
const values = new Set();
|
|
127
|
+
const add = (v) => {
|
|
128
|
+
if (typeof v === 'string' && v.length >= MIN_SECRET_LITERAL_LENGTH)
|
|
129
|
+
values.add(v);
|
|
130
|
+
};
|
|
131
|
+
// (a) env vars whose NAME looks secret → their VALUE is a literal to scrub.
|
|
132
|
+
try {
|
|
133
|
+
for (const [name, value] of Object.entries(env ?? {})) {
|
|
134
|
+
if (SECRET_ENV_NAME_RE.test(name))
|
|
135
|
+
add(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// ignore — a broken env object is not fatal to redaction.
|
|
140
|
+
}
|
|
141
|
+
// (b) walk the config for `apiKeyEnvironmentVariable` (→ read that env var's value) and inline
|
|
142
|
+
// secret-named field values.
|
|
143
|
+
const seen = new WeakSet();
|
|
144
|
+
const walk = (node, depth) => {
|
|
145
|
+
if (node === null || typeof node !== 'object' || depth > MAX_REDACT_DEPTH)
|
|
146
|
+
return;
|
|
147
|
+
if (seen.has(node))
|
|
148
|
+
return;
|
|
149
|
+
seen.add(node);
|
|
150
|
+
if (Array.isArray(node)) {
|
|
151
|
+
for (const item of node)
|
|
152
|
+
walk(item, depth + 1);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const [key, value] of Object.entries(node)) {
|
|
156
|
+
if (key.toLowerCase() === 'apikeyenvironmentvariable' && typeof value === 'string') {
|
|
157
|
+
add(env?.[value]); // resolve the NAMED env var's VALUE (the var name itself is not a secret)
|
|
158
|
+
}
|
|
159
|
+
else if (SECRET_KEY_RE.test(key) && !NON_SECRET_KEY_NAMES.has(key.toLowerCase())) {
|
|
160
|
+
add(value);
|
|
161
|
+
}
|
|
162
|
+
walk(value, depth + 1);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
walk(config, 0);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// fail safe: a hostile getter threw — we simply have fewer literals; patterns + structural
|
|
170
|
+
// masking still cover the artifacts. Never rethrow (redaction must not break the dump).
|
|
171
|
+
}
|
|
172
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Redact a STRING: substitute every known literal secret value (technique 1, longest-first), then
|
|
176
|
+
* apply the provider key/auth patterns (technique 2). Never throws — on any error it returns the
|
|
177
|
+
* fully-withheld marker (fail safe: redact MORE, never emit the raw text). Applying it twice is
|
|
178
|
+
* safe (re-redacting already-redacted text is a no-op).
|
|
179
|
+
*/
|
|
180
|
+
export function redactText(text, secrets) {
|
|
181
|
+
try {
|
|
182
|
+
let out = text;
|
|
183
|
+
for (const secret of secrets) {
|
|
184
|
+
if (secret)
|
|
185
|
+
out = out.split(secret).join(REDACTED); // literal — no regex escaping needed
|
|
186
|
+
}
|
|
187
|
+
for (const [re, replacement] of PROVIDER_PATTERNS) {
|
|
188
|
+
out = out.replace(re, replacement);
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return REDACTED; // fail safe
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Deep-redact an arbitrary value for serialization: mask the VALUES of secret-named fields
|
|
198
|
+
* (technique 3 — structure preserved: the key stays, only its value becomes the marker) and run
|
|
199
|
+
* {@link redactText} over every string leaf (techniques 1 + 2). Circular refs are broken; functions
|
|
200
|
+
* and bigints are rendered the way `safeStringify` would, so the result is JSON-safe. Pure — never
|
|
201
|
+
* mutates the input. `apiKeyEnvironmentVariable` is intentionally NOT masked (it is a var name).
|
|
202
|
+
*
|
|
203
|
+
* Intended for the CONFIG artifact (where field-name masking is wanted). Non-config artifacts use
|
|
204
|
+
* {@link redactText} over their stringified form instead (literal + pattern only), so a legitimate
|
|
205
|
+
* `token`/`secret`-named field in tool output is not blanket-masked.
|
|
206
|
+
*/
|
|
207
|
+
export function redactValue(value, secrets) {
|
|
208
|
+
const seen = new WeakSet();
|
|
209
|
+
const walk = (node, depth) => {
|
|
210
|
+
if (typeof node === 'string')
|
|
211
|
+
return redactText(node, secrets);
|
|
212
|
+
if (typeof node === 'function')
|
|
213
|
+
return `[Function: ${node.name || 'anonymous'}]`;
|
|
214
|
+
if (typeof node === 'bigint')
|
|
215
|
+
return node.toString();
|
|
216
|
+
if (node === null || typeof node !== 'object')
|
|
217
|
+
return node;
|
|
218
|
+
if (seen.has(node))
|
|
219
|
+
return '[Circular]';
|
|
220
|
+
if (depth > MAX_REDACT_DEPTH)
|
|
221
|
+
return '[Truncated]';
|
|
222
|
+
seen.add(node);
|
|
223
|
+
if (Array.isArray(node))
|
|
224
|
+
return node.map((item) => walk(item, depth + 1));
|
|
225
|
+
const out = {};
|
|
226
|
+
for (const [key, val] of Object.entries(node)) {
|
|
227
|
+
if (SECRET_KEY_RE.test(key) && !NON_SECRET_KEY_NAMES.has(key.toLowerCase())) {
|
|
228
|
+
out[key] = REDACTED; // mask the value, keep the key — shape is part of the debug signal
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
out[key] = walk(val, depth + 1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
};
|
|
236
|
+
return walk(value, 0);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=redactSecrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactSecrets.js","sourceRoot":"","sources":["../../src/utils/redactSecrets.ts"],"names":[],"mappings":"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"}
|
|
@@ -55,6 +55,98 @@ export declare function appendOsShellNote(systemPrompt: string | undefined): str
|
|
|
55
55
|
* it in the shared path. Returns the note alone when there is no base prompt.
|
|
56
56
|
*/
|
|
57
57
|
export declare function appendCwdNote(systemPrompt: string | undefined, cwd: string): string;
|
|
58
|
+
/** GS2-35 — the configured Git co-author identity (both fields optional; each defaults on its own). */
|
|
59
|
+
export interface CommitCoAuthor {
|
|
60
|
+
name?: string;
|
|
61
|
+
email?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* GS2-35: append the commit co-authoring rule to the composed code-mode system prompt.
|
|
65
|
+
*
|
|
66
|
+
* Gaunt Sloth has **no dedicated git-commit tool** — the agent commits by calling
|
|
67
|
+
* `run_shell_command` with `git commit`, composing the message (including any trailer) itself. Left
|
|
68
|
+
* unguided, models emit `Co-Authored-By: <their own model name>` (e.g. `Claude`, `GPT`, `Gemini`)
|
|
69
|
+
* from trained habit, which is **factually wrong**: the commit was produced by *Gaunt Sloth*, not by
|
|
70
|
+
* the model. This note is the fix at the correct layer — first-party prompt guidance that (a) states
|
|
71
|
+
* the exact trailer to emit and (b) forbids a model-name co-author.
|
|
72
|
+
*
|
|
73
|
+
* The identity is config-driven (`commit.coAuthor` in {@link import('#src/config/types.js').GthConfig}).
|
|
74
|
+
* Each field falls back INDEPENDENTLY to the Gaunt Sloth account
|
|
75
|
+
* ({@link DEFAULT_COMMIT_CO_AUTHOR_NAME} / {@link DEFAULT_COMMIT_CO_AUTHOR_EMAIL}) — so a partial
|
|
76
|
+
* override (name only, or a config that bypassed the loader) still yields a complete trailer, and a
|
|
77
|
+
* fully-absent config yields the default account. Blank/whitespace values are treated as unset.
|
|
78
|
+
*
|
|
79
|
+
* Backend-agnostic: composed through the shared code path so BOTH the lean `GthLangChainAgent` and
|
|
80
|
+
* the deep `GthDeepAgent` inject it (the git-commit capability rides on `run_shell_command`, which
|
|
81
|
+
* both backends expose in code mode). Returns the note alone when there is no base prompt.
|
|
82
|
+
*/
|
|
83
|
+
export declare function appendCommitCoAuthorNote(systemPrompt: string | undefined, coAuthor?: CommitCoAuthor): string;
|
|
84
|
+
/**
|
|
85
|
+
* GS2-34/GS2-53 — the resolved active-model identity, as a STRUCTURED value.
|
|
86
|
+
*
|
|
87
|
+
* `hasProvider` is the authoritative "a real provider half was resolved" signal — carried
|
|
88
|
+
* explicitly rather than inferred from `identity.includes(':')`, because a bare model name can
|
|
89
|
+
* itself contain a colon (e.g. an Ollama/HF tag `gemma3:27b`) and would otherwise be mistaken for a
|
|
90
|
+
* `provider:model` string. It is true iff a non-empty provider (configured `type` OR a non-empty
|
|
91
|
+
* `_llmType()`) formed the leading `provider:` segment; false when `identity` is the bare model.
|
|
92
|
+
*/
|
|
93
|
+
export interface ResolvedModelIdentity {
|
|
94
|
+
/** `provider:model` when a provider resolved, otherwise the bare `model`. */
|
|
95
|
+
identity: string;
|
|
96
|
+
/** True iff a real provider formed the `provider:` segment (never merely a colon in the model). */
|
|
97
|
+
hasProvider: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* GS2-34: resolve the active model identity from the effective config, for injection into the
|
|
101
|
+
* system prompt by {@link appendModelContextNote}.
|
|
102
|
+
*
|
|
103
|
+
* Both halves are read the SAME way the rest of gsloth already surfaces the active model:
|
|
104
|
+
* - MODEL: `config.modelDisplayName` (the string the status line renders — set by the loader from
|
|
105
|
+
* `llm.model`), falling back to the live model's own `model` field.
|
|
106
|
+
* - PROVIDER: the configured `config.modelProviderType` (the raw `llm.type` the loader stashed —
|
|
107
|
+
* `openrouter`/`deepseek`/`xai`/`anthropic`/…) when present, otherwise the live LangChain
|
|
108
|
+
* model's `_llmType()` (the source the AG-UI `/info` endpoint reports). GS2-53 — preferring the
|
|
109
|
+
* configured `type` fixes the OpenAI-compatible shims (openrouter/deepseek/xai all extend
|
|
110
|
+
* ChatOpenAI, so their `_llmType()` reports `openai`): a `type: openrouter` config now injects
|
|
111
|
+
* `openrouter:<model>`, not `openai:<model>`. The `type` is absent for module configs (which
|
|
112
|
+
* hand us an already-built LLM), where we fall back to `_llmType()` unchanged. The MODEL half is
|
|
113
|
+
* always exact.
|
|
114
|
+
*
|
|
115
|
+
* Returns `{ identity: 'provider:model', hasProvider: true }` when both resolve, `{ identity:
|
|
116
|
+
* 'model', hasProvider: false }` when only the model resolves, and `undefined` when the model is
|
|
117
|
+
* unknown (a provider with no model is not a usable identity) — in which case {@link
|
|
118
|
+
* appendModelContextNote} injects nothing, leaving the prompt exactly as before. `hasProvider` is
|
|
119
|
+
* the authoritative provider-present flag (GS2-53), so a bare model whose NAME contains a colon
|
|
120
|
+
* (e.g. `gemma3:27b`) is correctly reported as `hasProvider: false`. `_llmType()` is called
|
|
121
|
+
* defensively (guarded) so a provider whose accessor throws can never break prompt assembly (and is
|
|
122
|
+
* skipped entirely when a configured `type` is present).
|
|
123
|
+
*/
|
|
124
|
+
export declare function resolveModelIdentity(config: {
|
|
125
|
+
llm?: {
|
|
126
|
+
_llmType?: () => string;
|
|
127
|
+
model?: string;
|
|
128
|
+
};
|
|
129
|
+
modelDisplayName?: string;
|
|
130
|
+
modelProviderType?: string;
|
|
131
|
+
} | null | undefined): ResolvedModelIdentity | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* GS2-34: append the active model-identity note to the composed system prompt.
|
|
134
|
+
*
|
|
135
|
+
* The agent otherwise has no reliable knowledge of which `provider:model` is serving it, so it
|
|
136
|
+
* cannot answer "what model are you?" accurately or reason about its own capabilities/limits. This
|
|
137
|
+
* injects a single first-party line naming the resolved identity (see {@link resolveModelIdentity}).
|
|
138
|
+
*
|
|
139
|
+
* Injected in EVERY mode (chat/ask/code/exec), NOT gated to `code` like the cwd/os-shell/commit
|
|
140
|
+
* notes: "which model are you?" can be asked in any session, so the identity must be visible
|
|
141
|
+
* everywhere. Config-gated by `injectModelContext` (default ON): a caller passes an `undefined`
|
|
142
|
+
* `modelIdentity` — because the config opted out (`injectModelContext: false`) or because no model
|
|
143
|
+
* could be resolved — and the base prompt is returned UNCHANGED (no line), preserving the current
|
|
144
|
+
* prompt byte-for-byte.
|
|
145
|
+
*
|
|
146
|
+
* A short capability note from the GS2-6 model catalog is a DEFERRED follow-up (GS2-6 has not
|
|
147
|
+
* landed): this injects the bare `provider:model` identity only.
|
|
148
|
+
*/
|
|
149
|
+
export declare function appendModelContextNote(systemPrompt: string | undefined, modelIdentity: ResolvedModelIdentity | undefined): string | undefined;
|
|
58
150
|
/**
|
|
59
151
|
* EXT-32: hard per-server cap on injected MCP instruction length.
|
|
60
152
|
*
|