@burtson-labs/agent-core 1.6.13

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 (195) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +88 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +52 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mcp/activation.d.ts +60 -0
  8. package/dist/mcp/activation.d.ts.map +1 -0
  9. package/dist/mcp/activation.js +139 -0
  10. package/dist/mcp/activation.js.map +1 -0
  11. package/dist/mcp/clientPool.d.ts +202 -0
  12. package/dist/mcp/clientPool.d.ts.map +1 -0
  13. package/dist/mcp/clientPool.js +469 -0
  14. package/dist/mcp/clientPool.js.map +1 -0
  15. package/dist/mcp/index.d.ts +18 -0
  16. package/dist/mcp/index.d.ts.map +1 -0
  17. package/dist/mcp/index.js +28 -0
  18. package/dist/mcp/index.js.map +1 -0
  19. package/dist/mcp/server.d.ts +43 -0
  20. package/dist/mcp/server.d.ts.map +1 -0
  21. package/dist/mcp/server.js +130 -0
  22. package/dist/mcp/server.js.map +1 -0
  23. package/dist/mcp/toolAdapter.d.ts +57 -0
  24. package/dist/mcp/toolAdapter.d.ts.map +1 -0
  25. package/dist/mcp/toolAdapter.js +223 -0
  26. package/dist/mcp/toolAdapter.js.map +1 -0
  27. package/dist/mcp/types.d.ts +122 -0
  28. package/dist/mcp/types.d.ts.map +1 -0
  29. package/dist/mcp/types.js +15 -0
  30. package/dist/mcp/types.js.map +1 -0
  31. package/dist/providers/deterministic-provider.d.ts +21 -0
  32. package/dist/providers/deterministic-provider.d.ts.map +1 -0
  33. package/dist/providers/deterministic-provider.js +80 -0
  34. package/dist/providers/deterministic-provider.js.map +1 -0
  35. package/dist/providers/provider-client.d.ts +12 -0
  36. package/dist/providers/provider-client.d.ts.map +1 -0
  37. package/dist/providers/provider-client.js +11 -0
  38. package/dist/providers/provider-client.js.map +1 -0
  39. package/dist/runtime/AgentRuntime.d.ts +67 -0
  40. package/dist/runtime/AgentRuntime.d.ts.map +1 -0
  41. package/dist/runtime/AgentRuntime.js +382 -0
  42. package/dist/runtime/AgentRuntime.js.map +1 -0
  43. package/dist/security/secretPatterns.d.ts +76 -0
  44. package/dist/security/secretPatterns.d.ts.map +1 -0
  45. package/dist/security/secretPatterns.js +290 -0
  46. package/dist/security/secretPatterns.js.map +1 -0
  47. package/dist/tools/ask-user-tool.d.ts +19 -0
  48. package/dist/tools/ask-user-tool.d.ts.map +1 -0
  49. package/dist/tools/ask-user-tool.js +148 -0
  50. package/dist/tools/ask-user-tool.js.map +1 -0
  51. package/dist/tools/compactMessages.d.ts +52 -0
  52. package/dist/tools/compactMessages.d.ts.map +1 -0
  53. package/dist/tools/compactMessages.js +158 -0
  54. package/dist/tools/compactMessages.js.map +1 -0
  55. package/dist/tools/core-tools.d.ts +29 -0
  56. package/dist/tools/core-tools.d.ts.map +1 -0
  57. package/dist/tools/core-tools.js +2214 -0
  58. package/dist/tools/core-tools.js.map +1 -0
  59. package/dist/tools/git-tools.d.ts +32 -0
  60. package/dist/tools/git-tools.d.ts.map +1 -0
  61. package/dist/tools/git-tools.js +330 -0
  62. package/dist/tools/git-tools.js.map +1 -0
  63. package/dist/tools/index.d.ts +15 -0
  64. package/dist/tools/index.d.ts.map +1 -0
  65. package/dist/tools/index.js +31 -0
  66. package/dist/tools/index.js.map +1 -0
  67. package/dist/tools/language-adapters.d.ts +48 -0
  68. package/dist/tools/language-adapters.d.ts.map +1 -0
  69. package/dist/tools/language-adapters.js +299 -0
  70. package/dist/tools/language-adapters.js.map +1 -0
  71. package/dist/tools/loop/compactionTrigger.d.ts +47 -0
  72. package/dist/tools/loop/compactionTrigger.d.ts.map +1 -0
  73. package/dist/tools/loop/compactionTrigger.js +32 -0
  74. package/dist/tools/loop/compactionTrigger.js.map +1 -0
  75. package/dist/tools/loop/finalAnswerNudges.d.ts +68 -0
  76. package/dist/tools/loop/finalAnswerNudges.d.ts.map +1 -0
  77. package/dist/tools/loop/finalAnswerNudges.js +87 -0
  78. package/dist/tools/loop/finalAnswerNudges.js.map +1 -0
  79. package/dist/tools/loop/goalAnchor.d.ts +72 -0
  80. package/dist/tools/loop/goalAnchor.d.ts.map +1 -0
  81. package/dist/tools/loop/goalAnchor.js +76 -0
  82. package/dist/tools/loop/goalAnchor.js.map +1 -0
  83. package/dist/tools/loop/llmStream.d.ts +70 -0
  84. package/dist/tools/loop/llmStream.d.ts.map +1 -0
  85. package/dist/tools/loop/llmStream.js +181 -0
  86. package/dist/tools/loop/llmStream.js.map +1 -0
  87. package/dist/tools/loop/parallelExecute.d.ts +57 -0
  88. package/dist/tools/loop/parallelExecute.d.ts.map +1 -0
  89. package/dist/tools/loop/parallelExecute.js +54 -0
  90. package/dist/tools/loop/parallelExecute.js.map +1 -0
  91. package/dist/tools/loop/singleToolExecute.d.ts +71 -0
  92. package/dist/tools/loop/singleToolExecute.d.ts.map +1 -0
  93. package/dist/tools/loop/singleToolExecute.js +139 -0
  94. package/dist/tools/loop/singleToolExecute.js.map +1 -0
  95. package/dist/tools/loop/toolCallNormalize.d.ts +57 -0
  96. package/dist/tools/loop/toolCallNormalize.d.ts.map +1 -0
  97. package/dist/tools/loop/toolCallNormalize.js +99 -0
  98. package/dist/tools/loop/toolCallNormalize.js.map +1 -0
  99. package/dist/tools/loop/turnSetup.d.ts +43 -0
  100. package/dist/tools/loop/turnSetup.d.ts.map +1 -0
  101. package/dist/tools/loop/turnSetup.js +48 -0
  102. package/dist/tools/loop/turnSetup.js.map +1 -0
  103. package/dist/tools/ocr.d.ts +52 -0
  104. package/dist/tools/ocr.d.ts.map +1 -0
  105. package/dist/tools/ocr.js +238 -0
  106. package/dist/tools/ocr.js.map +1 -0
  107. package/dist/tools/post-edit-checks.d.ts +46 -0
  108. package/dist/tools/post-edit-checks.d.ts.map +1 -0
  109. package/dist/tools/post-edit-checks.js +236 -0
  110. package/dist/tools/post-edit-checks.js.map +1 -0
  111. package/dist/tools/skill-loader.d.ts +94 -0
  112. package/dist/tools/skill-loader.d.ts.map +1 -0
  113. package/dist/tools/skill-loader.js +422 -0
  114. package/dist/tools/skill-loader.js.map +1 -0
  115. package/dist/tools/skill-registry.d.ts +44 -0
  116. package/dist/tools/skill-registry.d.ts.map +1 -0
  117. package/dist/tools/skill-registry.js +118 -0
  118. package/dist/tools/skill-registry.js.map +1 -0
  119. package/dist/tools/skill-types.d.ts +38 -0
  120. package/dist/tools/skill-types.d.ts.map +1 -0
  121. package/dist/tools/skill-types.js +10 -0
  122. package/dist/tools/skill-types.js.map +1 -0
  123. package/dist/tools/skills/code-review-skill.d.ts +9 -0
  124. package/dist/tools/skills/code-review-skill.d.ts.map +1 -0
  125. package/dist/tools/skills/code-review-skill.js +66 -0
  126. package/dist/tools/skills/code-review-skill.js.map +1 -0
  127. package/dist/tools/skills/core-skill.d.ts +13 -0
  128. package/dist/tools/skills/core-skill.d.ts.map +1 -0
  129. package/dist/tools/skills/core-skill.js +23 -0
  130. package/dist/tools/skills/core-skill.js.map +1 -0
  131. package/dist/tools/skills/git-skill.d.ts +10 -0
  132. package/dist/tools/skills/git-skill.d.ts.map +1 -0
  133. package/dist/tools/skills/git-skill.js +30 -0
  134. package/dist/tools/skills/git-skill.js.map +1 -0
  135. package/dist/tools/skills/index.d.ts +17 -0
  136. package/dist/tools/skills/index.d.ts.map +1 -0
  137. package/dist/tools/skills/index.js +49 -0
  138. package/dist/tools/skills/index.js.map +1 -0
  139. package/dist/tools/skills/interaction-skill.d.ts +14 -0
  140. package/dist/tools/skills/interaction-skill.d.ts.map +1 -0
  141. package/dist/tools/skills/interaction-skill.js +24 -0
  142. package/dist/tools/skills/interaction-skill.js.map +1 -0
  143. package/dist/tools/skills/mail-search-skill.d.ts +25 -0
  144. package/dist/tools/skills/mail-search-skill.d.ts.map +1 -0
  145. package/dist/tools/skills/mail-search-skill.js +343 -0
  146. package/dist/tools/skills/mail-search-skill.js.map +1 -0
  147. package/dist/tools/skills/plan-skill.d.ts +10 -0
  148. package/dist/tools/skills/plan-skill.d.ts.map +1 -0
  149. package/dist/tools/skills/plan-skill.js +126 -0
  150. package/dist/tools/skills/plan-skill.js.map +1 -0
  151. package/dist/tools/skills/semantic-search-skill.d.ts +22 -0
  152. package/dist/tools/skills/semantic-search-skill.d.ts.map +1 -0
  153. package/dist/tools/skills/semantic-search-skill.js +244 -0
  154. package/dist/tools/skills/semantic-search-skill.js.map +1 -0
  155. package/dist/tools/skills/test-gen-skill.d.ts +9 -0
  156. package/dist/tools/skills/test-gen-skill.d.ts.map +1 -0
  157. package/dist/tools/skills/test-gen-skill.js +123 -0
  158. package/dist/tools/skills/test-gen-skill.js.map +1 -0
  159. package/dist/tools/tool-registry.d.ts +60 -0
  160. package/dist/tools/tool-registry.d.ts.map +1 -0
  161. package/dist/tools/tool-registry.js +200 -0
  162. package/dist/tools/tool-registry.js.map +1 -0
  163. package/dist/tools/tool-types.d.ts +281 -0
  164. package/dist/tools/tool-types.d.ts.map +1 -0
  165. package/dist/tools/tool-types.js +10 -0
  166. package/dist/tools/tool-types.js.map +1 -0
  167. package/dist/tools/tool-use-loop.d.ts +231 -0
  168. package/dist/tools/tool-use-loop.d.ts.map +1 -0
  169. package/dist/tools/tool-use-loop.js +2057 -0
  170. package/dist/tools/tool-use-loop.js.map +1 -0
  171. package/dist/tools/tool-use-parser.d.ts +78 -0
  172. package/dist/tools/tool-use-parser.d.ts.map +1 -0
  173. package/dist/tools/tool-use-parser.js +427 -0
  174. package/dist/tools/tool-use-parser.js.map +1 -0
  175. package/dist/tools/toolAvailabilityDetector.d.ts +48 -0
  176. package/dist/tools/toolAvailabilityDetector.d.ts.map +1 -0
  177. package/dist/tools/toolAvailabilityDetector.js +156 -0
  178. package/dist/tools/toolAvailabilityDetector.js.map +1 -0
  179. package/dist/tools/unified-patch.d.ts +87 -0
  180. package/dist/tools/unified-patch.d.ts.map +1 -0
  181. package/dist/tools/unified-patch.js +217 -0
  182. package/dist/tools/unified-patch.js.map +1 -0
  183. package/dist/types/agent.d.ts +69 -0
  184. package/dist/types/agent.d.ts.map +1 -0
  185. package/dist/types/agent.js +54 -0
  186. package/dist/types/agent.js.map +1 -0
  187. package/dist/types/tasks.d.ts +22 -0
  188. package/dist/types/tasks.d.ts.map +1 -0
  189. package/dist/types/tasks.js +3 -0
  190. package/dist/types/tasks.js.map +1 -0
  191. package/dist/utils/event-emitter.d.ts +13 -0
  192. package/dist/utils/event-emitter.d.ts.map +1 -0
  193. package/dist/utils/event-emitter.js +54 -0
  194. package/dist/utils/event-emitter.js.map +1 -0
  195. package/package.json +33 -0
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ /**
3
+ * Secret-leak protection. Tool output (file reads, command
4
+ * stdout, search results) routinely surfaces API keys, OAuth tokens,
5
+ * and private keys when the user asks the agent to inspect dotfiles,
6
+ * environment variables, or anything else that touches credentials.
7
+ * Without redaction, those tokens land in:
8
+ *
9
+ * 1. The model's context window → the model may echo them in its
10
+ * next response, embed them in a tool call, or hallucinate
11
+ * surrounding prose that quotes them verbatim
12
+ * 2. The user's terminal → secrets sit in the scrollback,
13
+ * visible to over-shoulder readers and any screen-sharing flow
14
+ * 3. The session log on disk → ~/.bandit/sessions/*.jsonl
15
+ * persists raw secrets indefinitely, becoming a target if
16
+ * that file is ever shared (bug reports, etc.)
17
+ *
18
+ * The redactor is the single source of truth for "what looks like a
19
+ * secret." Patterns target high-confidence formats (known prefixes,
20
+ * stable lengths) — we deliberately do NOT redact generic
21
+ * high-entropy strings because false positives are corrosive (the
22
+ * agent loses the ability to reason about real data that happens to
23
+ * look secret-shaped).
24
+ *
25
+ * Each pattern has a `kind` label so the redaction replaces the match
26
+ * with a typed token like `<REDACTED:github-pat>` — preserves enough
27
+ * structure for the model (and the user) to understand what kind of
28
+ * thing was hidden without leaking the value.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.BUILTIN_SECRET_PATTERNS = void 0;
32
+ exports.redactSecrets = redactSecrets;
33
+ exports.redactSecretsString = redactSecretsString;
34
+ /**
35
+ * Built-in secret patterns. Each entry is a high-confidence format with
36
+ * either a recognized prefix or a strict length/charset constraint.
37
+ *
38
+ * Order matters: longer / more-specific patterns must match BEFORE
39
+ * shorter / more-generic ones, otherwise a fine-grained PAT would be
40
+ * partially matched by the classic-PAT pattern. We rely on the regex
41
+ * engine matching in source order via the array iteration below.
42
+ */
43
+ exports.BUILTIN_SECRET_PATTERNS = [
44
+ // ─── GitHub ───────────────────────────────────────────────────────
45
+ // Fine-grained PATs are LONG (82 char body) — listed first so the
46
+ // classic-PAT pattern doesn't truncate them.
47
+ {
48
+ kind: 'github-pat-fine-grained',
49
+ label: 'GitHub fine-grained PAT',
50
+ re: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g
51
+ },
52
+ {
53
+ kind: 'github-pat',
54
+ label: 'GitHub classic PAT',
55
+ re: /\bghp_[A-Za-z0-9]{36,251}\b/g
56
+ },
57
+ {
58
+ kind: 'github-oauth',
59
+ label: 'GitHub OAuth token',
60
+ re: /\bgho_[A-Za-z0-9]{36,251}\b/g
61
+ },
62
+ {
63
+ kind: 'github-server-token',
64
+ label: 'GitHub server-to-server token',
65
+ re: /\bghs_[A-Za-z0-9]{36,251}\b/g
66
+ },
67
+ {
68
+ kind: 'github-user-token',
69
+ label: 'GitHub user-to-server token',
70
+ re: /\bghu_[A-Za-z0-9]{36,251}\b/g
71
+ },
72
+ // ─── Slack ────────────────────────────────────────────────────────
73
+ {
74
+ kind: 'slack-bot-token',
75
+ label: 'Slack bot token',
76
+ re: /\bxoxb-[A-Za-z0-9-]+\b/g
77
+ },
78
+ {
79
+ kind: 'slack-user-token',
80
+ label: 'Slack user token',
81
+ re: /\bxoxp-[A-Za-z0-9-]+\b/g
82
+ },
83
+ {
84
+ kind: 'slack-app-token',
85
+ label: 'Slack app token',
86
+ re: /\bxoxa-[A-Za-z0-9-]+\b/g
87
+ },
88
+ // ─── GitLab ───────────────────────────────────────────────────────
89
+ {
90
+ kind: 'gitlab-pat',
91
+ label: 'GitLab personal access token',
92
+ re: /\bglpat-[A-Za-z0-9_-]{20,}\b/g
93
+ },
94
+ // ─── AWS ──────────────────────────────────────────────────────────
95
+ {
96
+ kind: 'aws-access-key',
97
+ label: 'AWS access key',
98
+ re: /\b(?:AKIA|ASIA|AROA|AGPA|AIPA)[0-9A-Z]{16}\b/g
99
+ },
100
+ // ─── Anthropic / OpenAI / Bandit ──────────────────────────────────
101
+ {
102
+ kind: 'anthropic-key',
103
+ label: 'Anthropic API key',
104
+ re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g
105
+ },
106
+ {
107
+ kind: 'openai-key',
108
+ label: 'OpenAI API key',
109
+ re: /\bsk-(?!ant-)[A-Za-z0-9_-]{20,}\b/g
110
+ },
111
+ {
112
+ kind: 'bandit-api-key',
113
+ label: 'Bandit / Burtson Labs API key',
114
+ re: /\bbai_[A-Za-z0-9]{20,}\b/g
115
+ },
116
+ // ─── Google ───────────────────────────────────────────────────────
117
+ // Google API keys start with "AIza" + 35 chars (39 total).
118
+ {
119
+ kind: 'google-api-key',
120
+ label: 'Google API key',
121
+ re: /\bAIza[A-Za-z0-9_-]{35}\b/g
122
+ },
123
+ // OAuth refresh tokens are "1//" + base64-like body.
124
+ {
125
+ kind: 'google-oauth-refresh',
126
+ label: 'Google OAuth refresh token',
127
+ re: /\b1\/\/[A-Za-z0-9_-]{43,}\b/g
128
+ },
129
+ // ─── Stripe ───────────────────────────────────────────────────────
130
+ {
131
+ kind: 'stripe-secret-key',
132
+ label: 'Stripe secret key',
133
+ re: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g
134
+ },
135
+ // ─── JWTs (generic, three base64-url segments) ────────────────────
136
+ // Any token shaped `eyJ<header>.<payload>.<signature>` — covers JWTs
137
+ // from any issuer. Catches our own AuthApi tokens, Auth0, Google ID
138
+ // tokens, etc. Long enough that the entropy filter doesn't hurt.
139
+ {
140
+ kind: 'jwt',
141
+ label: 'JWT token',
142
+ re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g
143
+ },
144
+ // ─── Private keys (PEM blocks) ────────────────────────────────────
145
+ // PEM-armored RSA / ECDSA / OpenSSH / generic private keys. The
146
+ // multi-line matcher captures the entire BEGIN...END block.
147
+ {
148
+ kind: 'private-key',
149
+ label: 'PEM private key',
150
+ re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |PGP |ENCRYPTED )?PRIVATE KEY-----/g
151
+ },
152
+ // ─── HTTP Authorization headers ───────────────────────────────────
153
+ // Catches "Authorization: Bearer <token>" / Token / Basic shapes that
154
+ // leak through curl outputs, request logs, fetch() debugging, etc.
155
+ // Preserves the header name + scheme so the model can still reason
156
+ // about WHAT auth was attempted, just not the token value.
157
+ //
158
+ // Caps the token character class at non-quote / non-whitespace /
159
+ // non-angle-bracket so this pattern can't re-match an already-redacted
160
+ // placeholder like "<REDACTED:jwt>".
161
+ {
162
+ kind: 'authorization-bearer',
163
+ label: 'HTTP Authorization header token',
164
+ // Backreferences \1 and \3 balance quotes around the key name and
165
+ // the value so both `Authorization: Bearer foo` and the JSON form
166
+ // `"Authorization": "Bearer foo"` match without grabbing stray quotes.
167
+ re: /(["']?)Authorization\1(\s*[:=]\s*)(["']?)(Bearer|Token|Basic)\s+([^\s"'<>]{16,})\3/gi
168
+ },
169
+ // ─── JSON / JS-style camelCase secret fields ──────────────────────
170
+ // The env-secret pattern below only catches SHOUTY_CASE names. JSON
171
+ // config files use camelCase ("apiKey", "accessToken", "clientSecret"),
172
+ // so the env pattern misses them entirely. ~/.bandit/config.json
173
+ // ({"bandit": {"apiKey": "..."}}) was leaking because of exactly this
174
+ // gap (2026-05-26).
175
+ //
176
+ // Matches: any quoted-or-bare camelCase identifier that ends in
177
+ // Key|Token|Secret|Password|Credentials, OR standalone "password" /
178
+ // "passwd" / "secret". Value must be a quoted string of 8+ chars.
179
+ // The value char class excludes <, > so it can't re-match a prior
180
+ // pattern's placeholder.
181
+ //
182
+ // Preserves: opening key quote, key name, closing key quote,
183
+ // separator (`: ` or `=`), opening value quote, closing value quote.
184
+ // Replaces: only the value's content.
185
+ {
186
+ kind: 'json-camelcase-secret',
187
+ label: 'camelCase config secret value',
188
+ re: /(["']?)([a-z][a-zA-Z0-9]*(?:Key|Token|Secret|Password|Credentials?)|password|passwd|secret)\1(\s*[:=]\s*)(["'])([^"'\s<>]{8,})\4/g
189
+ },
190
+ // ─── Generic env-style secret lines ───────────────────────────────
191
+ // Last-resort pattern: capture `*_TOKEN=<value>`, `*_KEY=<value>`,
192
+ // `*_SECRET=<value>`, `*_PASSWORD=<value>` style env lines where
193
+ // the value is non-empty and has at least 8 characters. Replaces
194
+ // only the value, not the variable name (preserves readability).
195
+ //
196
+ // The variable name is either a prefix + underscore + keyword
197
+ // (`STRIPE_SECRET_KEY`, `GITHUB_TOKEN`) OR just the keyword on its
198
+ // own (`PASSWORD=`, `TOKEN=`, `API_KEY=`). The optional non-capturing
199
+ // group `(?:[A-Z][A-Z0-9_]*_)?` handles both cases — the trailing
200
+ // underscore forces a real prefix boundary so `DEBUG=...` (which has
201
+ // no keyword suffix) never matches.
202
+ //
203
+ // Quoted values supported via the back-referenced quote group.
204
+ {
205
+ kind: 'env-secret',
206
+ label: 'env-style secret value',
207
+ re: /\b((?:[A-Z][A-Z0-9_]*_)?(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|CREDENTIALS?|API|AUTH))\s*[:=]\s*(['"]?)([^\s'"]{8,})\2/g
208
+ }
209
+ ];
210
+ /**
211
+ * Apply all built-in patterns to a string. Replaces each match with
212
+ * `<REDACTED:{kind}>`. The replacement is intentionally short so the
213
+ * model can still reason about the surrounding text without burning
214
+ * context on long placeholder tokens.
215
+ *
216
+ * The `env-secret` pattern is special: it preserves the variable name
217
+ * and only redacts the value. So `GITHUB_TOKEN=ghp_abc...` becomes
218
+ * `GITHUB_TOKEN=<REDACTED:env-secret>` — the model can still see WHAT
219
+ * variable was set without seeing its value. For all other patterns
220
+ * the whole match is replaced.
221
+ */
222
+ function redactSecrets(text, patterns = exports.BUILTIN_SECRET_PATTERNS) {
223
+ if (!text || text.length === 0) {
224
+ return { text, redactionCount: 0, kinds: [] };
225
+ }
226
+ let working = text;
227
+ let redactionCount = 0;
228
+ const kinds = [];
229
+ const recordKind = (kind) => {
230
+ if (!kinds.includes(kind))
231
+ kinds.push(kind);
232
+ };
233
+ for (const pattern of patterns) {
234
+ // Reset lastIndex so the global regex starts at 0 every call.
235
+ pattern.re.lastIndex = 0;
236
+ let mutated = false;
237
+ if (pattern.kind === 'env-secret') {
238
+ // Preserve `VAR=` prefix, redact only the value.
239
+ working = working.replace(pattern.re, (_match, varName) => {
240
+ redactionCount++;
241
+ recordKind(pattern.kind);
242
+ mutated = true;
243
+ return `${varName}=<REDACTED:${pattern.kind}>`;
244
+ });
245
+ }
246
+ else if (pattern.kind === 'json-camelcase-secret') {
247
+ // Preserve quoted key + separator + value quotes; redact only the
248
+ // inner value. Groups: (1) keyQuote, (2) keyName, (3) separator,
249
+ // (4) valueQuote, (5) value.
250
+ working = working.replace(pattern.re, (_match, kq, keyName, sep, vq) => {
251
+ redactionCount++;
252
+ recordKind(pattern.kind);
253
+ mutated = true;
254
+ return `${kq}${keyName}${kq}${sep}${vq}<REDACTED:${pattern.kind}>${vq}`;
255
+ });
256
+ }
257
+ else if (pattern.kind === 'authorization-bearer') {
258
+ // Preserve quoted header name + separator + value quotes; redact
259
+ // only the token. Groups: (1) keyQuote, (2) separator, (3) valueQuote,
260
+ // (4) scheme, (5) token.
261
+ working = working.replace(pattern.re, (_match, kq, sep, vq, scheme) => {
262
+ redactionCount++;
263
+ recordKind(pattern.kind);
264
+ mutated = true;
265
+ return `${kq}Authorization${kq}${sep}${vq}${scheme} <REDACTED:${pattern.kind}>${vq}`;
266
+ });
267
+ }
268
+ else {
269
+ working = working.replace(pattern.re, () => {
270
+ redactionCount++;
271
+ recordKind(pattern.kind);
272
+ mutated = true;
273
+ return `<REDACTED:${pattern.kind}>`;
274
+ });
275
+ }
276
+ // Defensive: regex with `g` flag carries state across calls. Reset
277
+ // again to guarantee no leftover lastIndex affects later callers.
278
+ if (mutated)
279
+ pattern.re.lastIndex = 0;
280
+ }
281
+ return { text: working, redactionCount, kinds };
282
+ }
283
+ /**
284
+ * Convenience helper used by callers that don't need the metadata —
285
+ * just want the masked string. Equivalent to redactSecrets(text).text.
286
+ */
287
+ function redactSecretsString(text) {
288
+ return redactSecrets(text).text;
289
+ }
290
+ //# sourceMappingURL=secretPatterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretPatterns.js","sourceRoot":"","sources":["../../src/security/secretPatterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `ask_user` — pose one or more clarifying questions to the user and wait
3
+ * for their answer before continuing.
4
+ *
5
+ * Host-agnostic: the tool only formats the question payload and the answer
6
+ * summary. The actual interactive prompt is rendered by the host via
7
+ * `ToolExecutionContext.requestUserInput` (the CLI's ink form, the
8
+ * extension's webview card). When a host doesn't provide that callback the
9
+ * tool degrades to telling the model to ask in plain text, so it's always
10
+ * safe to register — though hosts typically only register the owning skill
11
+ * when they actually have an interactive surface (see interaction-skill).
12
+ */
13
+ import type { AgentTool, UserInputQuestion } from './tool-types';
14
+ /** Parse the model-supplied `questions` JSON into validated question specs.
15
+ * Tolerant: accepts a single object (not wrapped in an array), string
16
+ * options, and `question`/`text`/`prompt` aliases. Assigns stable ids. */
17
+ export declare function parseAskUserQuestions(rawJson: string): UserInputQuestion[];
18
+ export declare const askUserTool: AgentTool;
19
+ //# sourceMappingURL=ask-user-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-user-tool.d.ts","sourceRoot":"","sources":["../../src/tools/ask-user-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAyBjE;;2EAE2E;AAC3E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA4B1E;AAED,eAAO,MAAM,WAAW,EAAE,SAmFzB,CAAC"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * `ask_user` — pose one or more clarifying questions to the user and wait
4
+ * for their answer before continuing.
5
+ *
6
+ * Host-agnostic: the tool only formats the question payload and the answer
7
+ * summary. The actual interactive prompt is rendered by the host via
8
+ * `ToolExecutionContext.requestUserInput` (the CLI's ink form, the
9
+ * extension's webview card). When a host doesn't provide that callback the
10
+ * tool degrades to telling the model to ask in plain text, so it's always
11
+ * safe to register — though hosts typically only register the owning skill
12
+ * when they actually have an interactive surface (see interaction-skill).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.askUserTool = void 0;
16
+ exports.parseAskUserQuestions = parseAskUserQuestions;
17
+ const QUESTIONS_EXAMPLE = '[{"question":"Which database should we use?","header":"Database","options":' +
18
+ '[{"label":"Postgres","description":"Relational, strong consistency"},' +
19
+ '{"label":"MongoDB","description":"Flexible document store"}],"allowFreeform":true}]';
20
+ function coerceOption(raw) {
21
+ if (typeof raw === 'string')
22
+ return raw.trim() ? { label: raw.trim() } : null;
23
+ if (raw && typeof raw === 'object') {
24
+ const o = raw;
25
+ const label = typeof o.label === 'string' ? o.label
26
+ : typeof o.value === 'string' ? o.value
27
+ : typeof o.text === 'string' ? o.text : '';
28
+ if (!label.trim())
29
+ return null;
30
+ return {
31
+ label: label.trim(),
32
+ description: typeof o.description === 'string' ? o.description : undefined
33
+ };
34
+ }
35
+ return null;
36
+ }
37
+ /** Parse the model-supplied `questions` JSON into validated question specs.
38
+ * Tolerant: accepts a single object (not wrapped in an array), string
39
+ * options, and `question`/`text`/`prompt` aliases. Assigns stable ids. */
40
+ function parseAskUserQuestions(rawJson) {
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(rawJson);
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ const list = Array.isArray(parsed) ? parsed : [parsed];
49
+ const questions = [];
50
+ list.forEach((raw, i) => {
51
+ if (!raw || typeof raw !== 'object')
52
+ return;
53
+ const r = raw;
54
+ const text = typeof r.question === 'string' ? r.question
55
+ : typeof r.text === 'string' ? r.text
56
+ : typeof r.prompt === 'string' ? r.prompt : '';
57
+ if (!text.trim())
58
+ return;
59
+ const options = (Array.isArray(r.options) ? r.options : [])
60
+ .map(coerceOption)
61
+ .filter((o) => o !== null);
62
+ questions.push({
63
+ id: typeof r.id === 'string' && r.id.trim() ? r.id.trim() : `q${i + 1}`,
64
+ question: text.trim(),
65
+ header: typeof r.header === 'string' && r.header.trim() ? r.header.trim() : undefined,
66
+ options: options.length > 0 ? options : undefined,
67
+ allowFreeform: r.allowFreeform === false ? false : true
68
+ });
69
+ });
70
+ return questions;
71
+ }
72
+ exports.askUserTool = {
73
+ name: 'ask_user',
74
+ description: 'Ask the user one or more clarifying questions and WAIT for their answer before continuing. ' +
75
+ 'ALWAYS use this tool when you need a decision or direction from the user — do NOT ask in your prose ' +
76
+ 'response and end the turn. A prose question is passive (the user must start a new turn); this renders ' +
77
+ 'an interactive prompt they answer in one click. ' +
78
+ 'Use only when you are genuinely blocked on a decision that is the user\'s to make and cannot be ' +
79
+ 'resolved from the request, the code, or sensible defaults — not for routine choices you can make ' +
80
+ 'yourself. Provide 1–4 questions, each with 2–4 suggested options (the user can also type their own ' +
81
+ 'answer). When one option is the clear best choice, make it the FIRST option and append ' +
82
+ '" (Recommended)" to its label — it is pre-selected, so the user can accept it with one click. ' +
83
+ 'The tool result contains the user\'s answers; act on them directly without re-asking.',
84
+ parameters: [
85
+ {
86
+ name: 'questions',
87
+ description: 'A JSON array of question objects. Each object: { "question": "the question text", ' +
88
+ '"header": "short tab label", "options": [{ "label": "...", "description": "optional context" }], ' +
89
+ '"allowFreeform": true }. Example: ' + QUESTIONS_EXAMPLE,
90
+ required: true,
91
+ schema: {
92
+ type: 'array',
93
+ items: {
94
+ type: 'object',
95
+ properties: {
96
+ question: { type: 'string', description: 'The question to ask the user.' },
97
+ header: { type: 'string', description: 'A short (≤12 char) label for the question tab.' },
98
+ options: {
99
+ type: 'array',
100
+ description: 'Suggested answers (2–4). If one is the clear best choice, list it first and append " (Recommended)" to its label.',
101
+ items: {
102
+ type: 'object',
103
+ properties: {
104
+ label: { type: 'string', description: 'The option text.' },
105
+ description: { type: 'string', description: 'Optional one-line explanation of the option.' }
106
+ },
107
+ required: ['label']
108
+ }
109
+ },
110
+ allowFreeform: { type: 'boolean', description: 'Allow a typed custom answer (default true).' }
111
+ },
112
+ required: ['question']
113
+ }
114
+ }
115
+ }
116
+ ],
117
+ async execute(params, ctx) {
118
+ if (!ctx.requestUserInput) {
119
+ return {
120
+ output: 'Interactive questions are not available in this environment. Ask your question(s) in plain ' +
121
+ 'text in your normal response and wait for the user to reply.',
122
+ isError: false
123
+ };
124
+ }
125
+ const questions = parseAskUserQuestions(params.questions ?? '');
126
+ if (questions.length === 0) {
127
+ return {
128
+ output: 'ask_user failed: the `questions` parameter must be a JSON array of {question, options} ' +
129
+ 'objects. Example: ' + QUESTIONS_EXAMPLE,
130
+ isError: true
131
+ };
132
+ }
133
+ const res = await ctx.requestUserInput({ questions });
134
+ if (res.cancelled) {
135
+ return {
136
+ output: 'The user dismissed the question(s) without answering. Do not immediately re-ask — proceed ' +
137
+ 'with your best judgment, or briefly explain what you need and let the user respond when ready.',
138
+ isError: false
139
+ };
140
+ }
141
+ const lines = questions.map((q) => {
142
+ const a = res.answers[q.id];
143
+ return `Q: ${q.question}\nA: ${a !== undefined && a !== '' ? a : '(no answer)'}`;
144
+ });
145
+ return { output: 'The user answered:\n\n' + lines.join('\n\n'), isError: false };
146
+ }
147
+ };
148
+ //# sourceMappingURL=ask-user-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-user-tool.js","sourceRoot":"","sources":["../../src/tools/ask-user-tool.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AA8BH,sDA4BC;AAtDD,MAAM,iBAAiB,GACrB,6EAA6E;IAC7E,uEAAuE;IACvE,qFAAqF,CAAC;AAIxF,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9E,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;YACjD,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;gBACvC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;QAC/B,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;YACnB,WAAW,EAAE,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;SAC3E,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;2EAE2E;AAC3E,SAAgB,qBAAqB,CAAC,OAAe;IACnD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO;QAC5C,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ;YACtD,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;gBACrC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO;QACzB,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;aACxD,GAAG,CAAC,YAAY,CAAC;aACjB,MAAM,CAAC,CAAC,CAAC,EAAqB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QAChD,SAAS,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;YACvE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE;YACrB,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS;YACrF,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;YACjD,aAAa,EAAE,CAAC,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;SACxD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,SAAS,CAAC;AACnB,CAAC;AAEY,QAAA,WAAW,GAAc;IACpC,IAAI,EAAE,UAAU;IAChB,WAAW,EACT,6FAA6F;QAC7F,sGAAsG;QACtG,wGAAwG;QACxG,kDAAkD;QAClD,kGAAkG;QAClG,mGAAmG;QACnG,qGAAqG;QACrG,yFAAyF;QACzF,gGAAgG;QAChG,uFAAuF;IACzF,UAAU,EAAE;QACV;YACE,IAAI,EAAE,WAAW;YACjB,WAAW,EACT,oFAAoF;gBACpF,mGAAmG;gBACnG,oCAAoC,GAAG,iBAAiB;YAC1D,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE;gBACN,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+BAA+B,EAAE;wBAC1E,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gDAAgD,EAAE;wBACzF,OAAO,EAAE;4BACP,IAAI,EAAE,OAAO;4BACb,WAAW,EAAE,mHAAmH;4BAChI,KAAK,EAAE;gCACL,IAAI,EAAE,QAAQ;gCACd,UAAU,EAAE;oCACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE;oCAC1D,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8CAA8C,EAAE;iCAC7F;gCACD,QAAQ,EAAE,CAAC,OAAO,CAAC;6BACpB;yBACF;wBACD,aAAa,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,6CAA6C,EAAE;qBAC/F;oBACD,QAAQ,EAAE,CAAC,UAAU,CAAC;iBACvB;aACF;SACF;KACF;IACD,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG;QACvB,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;YAC1B,OAAO;gBACL,MAAM,EACJ,6FAA6F;oBAC7F,8DAA8D;gBAChE,OAAO,EAAE,KAAK;aACf,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,MAAM,EACJ,yFAAyF;oBACzF,oBAAoB,GAAG,iBAAiB;gBAC1C,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;QACtD,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClB,OAAO;gBACL,MAAM,EACJ,4FAA4F;oBAC5F,gGAAgG;gBAClG,OAAO,EAAE,KAAK;aACf,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5B,OAAO,MAAM,CAAC,CAAC,QAAQ,QAAQ,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACnF,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,wBAAwB,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACnF,CAAC;CACF,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Message-history compaction for the tool-use loop.
3
+ *
4
+ * Why this exists:
5
+ * The loop appends every tool result as a `user` message carrying
6
+ * the raw output (read_file content, search_code hits, command
7
+ * stdout). After ~6 iterations on a medium-sized codebase the
8
+ * cumulative tool output is ~12-18k tokens — enough to push past
9
+ * the Ollama num_ctx ceiling on small/medium models, which causes
10
+ * silent front-truncation of the system prompt (the exact failure
11
+ * mode that made `bandit-core:12b-it-qat` appear to "refuse" C#
12
+ * edits until num_ctx was properly configured).
13
+ *
14
+ * Strategy (deterministic, no extra LLM calls):
15
+ * 1. Count tokens with a cheap char/4 heuristic.
16
+ * 2. If the total fits a configurable budget, return as-is.
17
+ * 3. Otherwise, walk the message list from oldest to newest and
18
+ * replace the *body* of earlier tool-result messages with a
19
+ * one-line placeholder ("[read_file api/foo.cs — 412 lines, see
20
+ * earlier]"). We always keep the system prompt, the initial user
21
+ * prompt, the last N tool results in full, and any assistant
22
+ * message that contains a tool call (the model needs to see its
23
+ * own prior reasoning).
24
+ *
25
+ * The compacted messages retain the same `role` / `content` shape so
26
+ * the loop doesn't need to change — it just passes them through chat.
27
+ */
28
+ import type { ToolLoopMessage } from './tool-types';
29
+ export interface CompactionOptions {
30
+ /** Target token budget. Defaults to 12000 (safe for 16k num_ctx). */
31
+ tokenBudget?: number;
32
+ /** Always keep the last N tool-result messages in full. Default 2. */
33
+ keepRecentToolResults?: number;
34
+ /** Character-to-token ratio for heuristic counting. Default 4. */
35
+ charsPerToken?: number;
36
+ }
37
+ export interface CompactionReport {
38
+ compacted: ToolLoopMessage[];
39
+ /** Pre-compaction estimated token count. */
40
+ beforeTokens: number;
41
+ /** Post-compaction estimated token count. */
42
+ afterTokens: number;
43
+ /** How many messages were collapsed to placeholders. */
44
+ messagesCompacted: number;
45
+ }
46
+ /**
47
+ * Main entry point. Returns compacted messages plus a report of what
48
+ * changed. Callers can log the report so agents/CLIs can surface
49
+ * "compacted history — N msgs, saved X tokens" as a status line.
50
+ */
51
+ export declare function compactToolMessages(messages: ToolLoopMessage[], options?: CompactionOptions): CompactionReport;
52
+ //# sourceMappingURL=compactMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compactMessages.d.ts","sourceRoot":"","sources":["../../src/tools/compactMessages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAyCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EAAE,EAC3B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CA0FlB"}