@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12

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 (247) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
  3. package/dist/cli.js +10775 -10762
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/capability/mcp.d.ts +11 -0
  6. package/dist/types/capability/skill.d.ts +6 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/custom-models.d.ts +31 -0
  14. package/dist/types/config/model-config-values.d.ts +19 -0
  15. package/dist/types/config/model-patch.d.ts +93 -0
  16. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  17. package/dist/types/config/model-registry.d.ts +9 -52
  18. package/dist/types/config/settings-schema.d.ts +1 -34
  19. package/dist/types/config/settings.d.ts +5 -3
  20. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  21. package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
  22. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  23. package/dist/types/discovery/contained-path.d.ts +33 -0
  24. package/dist/types/discovery/index.d.ts +1 -0
  25. package/dist/types/eval/executor-base.d.ts +8 -2
  26. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  27. package/dist/types/export/share.d.ts +1 -1
  28. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +7 -0
  30. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -2
  32. package/dist/types/extensibility/skills.d.ts +5 -0
  33. package/dist/types/lsp/diagnostics.d.ts +96 -0
  34. package/dist/types/lsp/index.d.ts +7 -128
  35. package/dist/types/lsp/servers.d.ts +72 -0
  36. package/dist/types/lsp/tool.d.ts +42 -0
  37. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  38. package/dist/types/lsp/writethrough.d.ts +33 -0
  39. package/dist/types/mcp/transports/header-policy.d.ts +46 -0
  40. package/dist/types/mcp/types.d.ts +15 -0
  41. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  42. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  43. package/dist/types/modes/components/agent-hub.d.ts +2 -0
  44. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  45. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  49. package/dist/types/modes/theme/color.d.ts +19 -0
  50. package/dist/types/modes/theme/loader.d.ts +19 -0
  51. package/dist/types/modes/theme/schema.d.ts +175 -0
  52. package/dist/types/modes/theme/symbols.d.ts +28 -0
  53. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  54. package/dist/types/modes/theme/theme.d.ts +9 -280
  55. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  56. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  57. package/dist/types/registry/agent-registry.d.ts +1 -3
  58. package/dist/types/secrets/index.d.ts +26 -2
  59. package/dist/types/secrets/message-transform.d.ts +40 -0
  60. package/dist/types/secrets/obfuscator.d.ts +0 -108
  61. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  62. package/dist/types/secrets/placeholder.d.ts +94 -0
  63. package/dist/types/secrets/replacement.d.ts +82 -0
  64. package/dist/types/session/agent-session-events.d.ts +2 -2
  65. package/dist/types/session/agent-session-types.d.ts +6 -0
  66. package/dist/types/session/agent-session.d.ts +2 -2
  67. package/dist/types/session/messages.d.ts +1 -0
  68. package/dist/types/session/prewalk.d.ts +3 -1
  69. package/dist/types/session/session-handoff.d.ts +3 -3
  70. package/dist/types/session/session-manager.d.ts +32 -0
  71. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  72. package/dist/types/session/session-tools.d.ts +8 -0
  73. package/dist/types/session/turn-recovery.d.ts +9 -4
  74. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  75. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  76. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  77. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  78. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  79. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  80. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  81. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  82. package/dist/types/task/index.d.ts +2 -0
  83. package/dist/types/tools/gh-common.d.ts +69 -0
  84. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  85. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  86. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  87. package/dist/types/tools/gh-search.d.ts +61 -0
  88. package/dist/types/tools/gh-types.d.ts +340 -0
  89. package/dist/types/tools/gh-view.d.ts +88 -0
  90. package/dist/types/tools/gh.d.ts +4 -201
  91. package/dist/types/tools/read-archive.d.ts +16 -0
  92. package/dist/types/tools/read-format.d.ts +104 -0
  93. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  94. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  95. package/dist/types/tools/read-renderer.d.ts +24 -0
  96. package/dist/types/tools/read-selector.d.ts +27 -0
  97. package/dist/types/tools/read-sqlite.d.ts +16 -0
  98. package/dist/types/tools/read-summary.d.ts +19 -0
  99. package/dist/types/tools/read.d.ts +1 -23
  100. package/dist/types/tools/shell-tokenize.d.ts +18 -1
  101. package/dist/types/utils/changelog.d.ts +0 -5
  102. package/package.json +13 -13
  103. package/scripts/legacy-pi-virtual-module.ts +4 -1
  104. package/src/advisor/runtime.ts +12 -7
  105. package/src/capability/mcp.ts +11 -0
  106. package/src/capability/skill.ts +6 -0
  107. package/src/cli/command-help.ts +4 -0
  108. package/src/cli/gallery-cli.ts +1 -1
  109. package/src/cli-commands.ts +5 -0
  110. package/src/commands/commit.ts +16 -3
  111. package/src/commands/share.ts +71 -0
  112. package/src/commit/agentic/index.ts +39 -21
  113. package/src/commit/execute.ts +56 -0
  114. package/src/commit/index.ts +2 -1
  115. package/src/commit/pipeline.ts +20 -7
  116. package/src/config/custom-models.ts +188 -0
  117. package/src/config/model-config-values.ts +128 -0
  118. package/src/config/model-discovery.ts +1 -1
  119. package/src/config/model-patch.ts +252 -0
  120. package/src/config/model-provider-discovery.ts +132 -0
  121. package/src/config/model-registry.ts +84 -704
  122. package/src/config/settings-schema.ts +2 -33
  123. package/src/config/settings.ts +53 -3
  124. package/src/debug/raw-sse-buffer.ts +20 -0
  125. package/src/discovery/agent-plugin-format.ts +551 -0
  126. package/src/discovery/agent-plugins.ts +341 -0
  127. package/src/discovery/claude-plugins.ts +21 -5
  128. package/src/discovery/contained-path.ts +75 -0
  129. package/src/discovery/helpers.ts +23 -9
  130. package/src/discovery/index.ts +1 -0
  131. package/src/discovery/omp-plugins.ts +20 -7
  132. package/src/edit/hashline/diff.ts +5 -1
  133. package/src/eval/executor-base.ts +39 -6
  134. package/src/eval/jl/executor.ts +82 -371
  135. package/src/eval/kernel-session-registry.ts +398 -0
  136. package/src/eval/py/executor.ts +53 -261
  137. package/src/eval/rb/executor.ts +36 -279
  138. package/src/exec/non-interactive-env.ts +1 -0
  139. package/src/export/share.ts +2 -1
  140. package/src/extensibility/custom-tools/types.ts +2 -2
  141. package/src/extensibility/extensions/loader.ts +78 -14
  142. package/src/extensibility/extensions/runner.ts +6 -0
  143. package/src/extensibility/extensions/types.ts +12 -0
  144. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  145. package/src/extensibility/shared-events.ts +3 -2
  146. package/src/extensibility/skills.ts +9 -0
  147. package/src/internal-urls/skill-protocol.ts +14 -0
  148. package/src/internal-urls/vault-protocol.ts +2 -2
  149. package/src/launch/client.ts +11 -1
  150. package/src/launch/protocol.ts +7 -2
  151. package/src/lsp/diagnostics.ts +516 -0
  152. package/src/lsp/index.ts +20 -2819
  153. package/src/lsp/servers.ts +296 -0
  154. package/src/lsp/tool.ts +1352 -0
  155. package/src/lsp/workspace-diagnostics.ts +170 -0
  156. package/src/lsp/writethrough.ts +561 -0
  157. package/src/main.ts +3 -2
  158. package/src/mcp/config.ts +3 -0
  159. package/src/mcp/manager.ts +12 -9
  160. package/src/mcp/transports/header-policy.ts +95 -0
  161. package/src/mcp/transports/http.ts +35 -52
  162. package/src/mcp/transports/sse.ts +20 -27
  163. package/src/mcp/types.ts +15 -0
  164. package/src/modes/acp/acp-agent.ts +15 -4
  165. package/src/modes/components/agent-dashboard.ts +2 -0
  166. package/src/modes/components/agent-hub-projection.ts +2 -2
  167. package/src/modes/components/agent-hub-renderer.ts +3 -7
  168. package/src/modes/components/agent-hub.ts +5 -0
  169. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  170. package/src/modes/components/chat-transcript-builder.ts +3 -0
  171. package/src/modes/components/custom-editor.ts +0 -9
  172. package/src/modes/components/status-line/component.ts +1 -0
  173. package/src/modes/components/status-line/segments.ts +6 -4
  174. package/src/modes/components/status-line/types.ts +2 -0
  175. package/src/modes/components/tool-execution.ts +13 -16
  176. package/src/modes/components/tree-selector.ts +62 -3
  177. package/src/modes/controllers/command-controller.ts +8 -2
  178. package/src/modes/controllers/event-controller.ts +15 -15
  179. package/src/modes/controllers/input-controller.ts +20 -2
  180. package/src/modes/controllers/selector-controller.ts +1 -0
  181. package/src/modes/theme/color.ts +133 -0
  182. package/src/modes/theme/loader.ts +178 -0
  183. package/src/modes/theme/schema.ts +263 -0
  184. package/src/modes/theme/symbols.ts +1000 -0
  185. package/src/modes/theme/theme-class.ts +611 -0
  186. package/src/modes/theme/theme.ts +27 -2453
  187. package/src/modes/theme/tui-adapters.ts +279 -0
  188. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  189. package/src/modes/utils/ui-helpers.ts +1 -0
  190. package/src/registry/agent-registry.ts +2 -2
  191. package/src/sdk.ts +6 -49
  192. package/src/secrets/index.ts +51 -6
  193. package/src/secrets/message-transform.ts +287 -0
  194. package/src/secrets/obfuscator.ts +39 -1303
  195. package/src/secrets/placeholder-scan.ts +506 -0
  196. package/src/secrets/placeholder.ts +309 -0
  197. package/src/secrets/replacement.ts +216 -0
  198. package/src/session/agent-session-events.ts +2 -2
  199. package/src/session/agent-session-types.ts +6 -0
  200. package/src/session/agent-session.ts +146 -21
  201. package/src/session/indexed-session-storage.ts +7 -2
  202. package/src/session/messages.ts +1 -0
  203. package/src/session/prewalk.ts +57 -17
  204. package/src/session/session-advisors.ts +32 -34
  205. package/src/session/session-context.ts +3 -5
  206. package/src/session/session-handoff.ts +39 -16
  207. package/src/session/session-manager.ts +67 -3
  208. package/src/session/session-provider-boundary.ts +3 -6
  209. package/src/session/session-tools.ts +37 -3
  210. package/src/session/todo-tracker.ts +11 -1
  211. package/src/session/turn-recovery.ts +115 -84
  212. package/src/slash-commands/builtin-collaboration.ts +504 -0
  213. package/src/slash-commands/builtin-completions.ts +291 -0
  214. package/src/slash-commands/builtin-control.ts +78 -0
  215. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  216. package/src/slash-commands/builtin-marketplace.ts +567 -0
  217. package/src/slash-commands/builtin-modes.ts +518 -0
  218. package/src/slash-commands/builtin-registry.ts +21 -2996
  219. package/src/slash-commands/builtin-session.ts +592 -0
  220. package/src/task/executor.ts +17 -14
  221. package/src/task/index.ts +21 -6
  222. package/src/tools/bash-skill-urls.ts +15 -0
  223. package/src/tools/bash.ts +10 -11
  224. package/src/tools/debug.ts +1 -1
  225. package/src/tools/gh-common.ts +288 -0
  226. package/src/tools/gh-pr-checkout.ts +679 -0
  227. package/src/tools/gh-pr-diff.ts +473 -0
  228. package/src/tools/gh-run-watch.ts +1015 -0
  229. package/src/tools/gh-search.ts +500 -0
  230. package/src/tools/gh-types.ts +379 -0
  231. package/src/tools/gh-view.ts +612 -0
  232. package/src/tools/gh.ts +109 -3821
  233. package/src/tools/jtd-to-json-schema.ts +123 -21
  234. package/src/tools/read-archive.ts +209 -0
  235. package/src/tools/read-format.ts +593 -0
  236. package/src/tools/read-path-resolution.ts +36 -0
  237. package/src/tools/read-pdf-images.ts +250 -0
  238. package/src/tools/read-renderer.ts +289 -0
  239. package/src/tools/read-selector.ts +84 -0
  240. package/src/tools/read-sqlite.ts +215 -0
  241. package/src/tools/read-summary.ts +199 -0
  242. package/src/tools/read.ts +82 -1824
  243. package/src/tools/shell-tokenize.ts +99 -1
  244. package/src/utils/changelog.ts +1 -1
  245. package/src/utils/title-generator.ts +3 -1
  246. package/src/web/search/providers/exa.ts +1 -1
  247. package/src/web/search/providers/zai.ts +12 -3
@@ -8,6 +8,7 @@
8
8
  * @see https://datatracker.ietf.org/doc/html/rfc8927
9
9
  */
10
10
 
11
+ import { isRecord } from "@oh-my-pi/pi-utils";
11
12
  import type { JTDPrimitive } from "./jtd-utils.js";
12
13
  import {
13
14
  isJTDDiscriminator,
@@ -134,6 +135,18 @@ function convertSchema(schema: unknown): unknown {
134
135
  return {};
135
136
  }
136
137
 
138
+ const jtdOnlyPrimitiveTypes: Record<string, true> = {
139
+ timestamp: true,
140
+ float32: true,
141
+ float64: true,
142
+ int8: true,
143
+ uint8: true,
144
+ int16: true,
145
+ uint16: true,
146
+ int32: true,
147
+ uint32: true,
148
+ };
149
+
137
150
  /**
138
151
  * Detect if a schema is JTD format (vs JSON Schema).
139
152
  *
@@ -155,11 +168,8 @@ export function isJTDSchema(schema: unknown): boolean {
155
168
  if ("ref" in obj) return true;
156
169
 
157
170
  // JTD type primitives (JSON Schema doesn't have int32, float64, etc.)
158
- if ("type" in obj) {
159
- const jtdPrimitives = ["timestamp", "float32", "float64", "int8", "uint8", "int16", "uint16", "int32", "uint32"];
160
- if (jtdPrimitives.includes(obj.type as string)) {
161
- return true;
162
- }
171
+ if (typeof obj.type === "string" && Object.hasOwn(jtdOnlyPrimitiveTypes, obj.type)) {
172
+ return true;
163
173
  }
164
174
 
165
175
  // JTD properties form without type: "object" (JSON Schema requires it)
@@ -170,36 +180,128 @@ export function isJTDSchema(schema: unknown): boolean {
170
180
  return false;
171
181
  }
172
182
 
173
- function normalizeMixedSchemaNode(schema: unknown): unknown {
174
- if (schema === null || typeof schema !== "object") {
175
- return schema;
183
+ function isUnambiguousJTDSchema(schema: unknown): boolean {
184
+ if (!isRecord(schema)) return false;
185
+
186
+ if (isRecord(schema.elements) || isRecord(schema.values) || isRecord(schema.optionalProperties)) {
187
+ return true;
188
+ }
189
+ if (typeof schema.ref === "string") return true;
190
+ if (typeof schema.type === "string" && Object.hasOwn(jtdOnlyPrimitiveTypes, schema.type)) {
191
+ return true;
192
+ }
193
+ if (typeof schema.discriminator !== "string" || !isRecord(schema.mapping)) {
194
+ return false;
176
195
  }
177
196
 
178
- if (Array.isArray(schema)) {
179
- return schema.map(item => normalizeMixedSchemaNode(item));
197
+ for (const key in schema.mapping) {
198
+ if (!Object.hasOwn(schema.mapping, key)) continue;
199
+ const mapping = schema.mapping[key];
200
+ if (!isRecord(mapping)) return false;
201
+
202
+ let hasSchemaProperties = false;
203
+ if (Object.hasOwn(mapping, "properties")) {
204
+ if (!isRecord(mapping.properties)) return false;
205
+ hasSchemaProperties = true;
206
+ }
207
+ if (Object.hasOwn(mapping, "optionalProperties")) {
208
+ if (!isRecord(mapping.optionalProperties)) return false;
209
+ hasSchemaProperties = true;
210
+ }
211
+ if (!hasSchemaProperties) return false;
180
212
  }
181
213
 
182
- if (isJTDSchema(schema)) {
183
- // `convertSchema` is itself fully recursive and emits pure JSON Schema, so
184
- // re-walking the result with `normalizeMixedSchemaNode` is unnecessary and
185
- // unsafe: it would treat user-named properties whose keys happen to be JTD
186
- // keywords (e.g. `ref`, `elements`) as nested JTD forms (#1345).
187
- return convertSchema(schema);
214
+ return true;
215
+ }
216
+
217
+ function normalizeJsonSchemaArray(value: unknown): unknown {
218
+ if (!Array.isArray(value)) return value;
219
+
220
+ let normalized: unknown[] | undefined;
221
+ for (let index = 0; index < value.length; index++) {
222
+ const item = value[index];
223
+ const converted = normalizeJsonSchemaNode(item);
224
+ if (converted === item) continue;
225
+ normalized ??= value.slice();
226
+ normalized[index] = converted;
227
+ }
228
+ return normalized ?? value;
229
+ }
230
+
231
+ function normalizeJsonSchemaMap(value: unknown): unknown {
232
+ if (!isRecord(value)) return value;
233
+
234
+ let normalized: Record<string, unknown> | undefined;
235
+ for (const key in value) {
236
+ if (!Object.hasOwn(value, key)) continue;
237
+ const item = value[key];
238
+ const converted = normalizeJsonSchemaNode(item);
239
+ if (converted === item) continue;
240
+ normalized ??= { ...value };
241
+ normalized[key] = converted;
188
242
  }
243
+ return normalized ?? value;
244
+ }
189
245
 
190
- const normalized: Record<string, unknown> = {};
191
- for (const [key, value] of Object.entries(schema)) {
192
- normalized[key] = normalizeMixedSchemaNode(value);
246
+ function normalizeJsonSchemaNode(schema: unknown): unknown {
247
+ if (!isRecord(schema)) return schema;
248
+ if (isUnambiguousJTDSchema(schema)) return convertSchema(schema);
249
+
250
+ let normalized: Record<string, unknown> | undefined;
251
+ for (const key in schema) {
252
+ if (!Object.hasOwn(schema, key)) continue;
253
+
254
+ const value = schema[key];
255
+ let converted: unknown;
256
+ switch (key) {
257
+ case "not":
258
+ case "if":
259
+ case "then":
260
+ case "else":
261
+ case "items":
262
+ case "contains":
263
+ case "propertyNames":
264
+ case "additionalProperties":
265
+ case "unevaluatedProperties":
266
+ case "unevaluatedItems":
267
+ case "contentSchema":
268
+ converted = normalizeJsonSchemaNode(value);
269
+ break;
270
+ case "allOf":
271
+ case "anyOf":
272
+ case "oneOf":
273
+ case "prefixItems":
274
+ converted = normalizeJsonSchemaArray(value);
275
+ break;
276
+ case "properties":
277
+ case "patternProperties":
278
+ case "$defs":
279
+ case "definitions":
280
+ case "dependentSchemas":
281
+ converted = normalizeJsonSchemaMap(value);
282
+ break;
283
+ default:
284
+ continue;
285
+ }
286
+
287
+ if (converted === value) continue;
288
+ normalized ??= { ...schema };
289
+ normalized[key] = converted;
193
290
  }
194
291
 
195
- return normalized;
292
+ return normalized ?? schema;
196
293
  }
294
+
197
295
  /**
198
296
  * Convert JTD schema to JSON Schema.
199
297
  * If already JSON Schema, returns as-is.
200
298
  */
201
299
  export function jtdToJsonSchema(schema: unknown): unknown {
202
- return normalizeMixedSchemaNode(schema);
300
+ if (isJTDSchema(schema)) {
301
+ // convertSchema is recursive; re-walking its JSON Schema output caused #1345.
302
+ return convertSchema(schema);
303
+ }
304
+ return normalizeJsonSchemaNode(schema);
203
305
  }
204
306
 
205
307
  /**
@@ -0,0 +1,209 @@
1
+ import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
+ import type { TextContent } from "@oh-my-pi/pi-ai";
3
+ import type { ToolSession } from "../sdk";
4
+ import { truncateHead } from "../session/streaming-output";
5
+ import { type ArchiveReader, formatArchiveEntryLines, openArchive, parseArchivePathCandidates } from "../utils/zip";
6
+ import { applyListLimit } from "./list-limit";
7
+ import { resolveReadPath } from "./path-utils";
8
+ import type { ReadToolDetails } from "./read";
9
+ import {
10
+ buildInMemoryMultiRangeResult,
11
+ buildInMemoryTextResult,
12
+ decodeUtf8Text,
13
+ markMarkdownContentType,
14
+ prependSuffixResolutionNotice,
15
+ } from "./read-format";
16
+ import {
17
+ findSuffixMatchCached,
18
+ isNotFoundError,
19
+ isRemoteMountPath,
20
+ type SuffixMatchCache,
21
+ } from "./read-path-resolution";
22
+ import { isMultiRange, isRawSelector, type ParsedSelector, parseSel, selToOffsetLimit } from "./read-selector";
23
+ import { formatBytes } from "./render-utils";
24
+ import { ToolError, throwIfAborted } from "./tool-errors";
25
+ import { toolResult } from "./tool-result";
26
+
27
+ interface ResolvedArchiveReadPath {
28
+ absolutePath: string;
29
+ archiveSubPath: string;
30
+ suffixResolution?: { from: string; to: string };
31
+ }
32
+ export async function resolveArchiveReadPath(
33
+ session: ToolSession,
34
+ readPath: string,
35
+ suffixCache: SuffixMatchCache,
36
+ signal?: AbortSignal,
37
+ ): Promise<ResolvedArchiveReadPath | null> {
38
+ const candidates = parseArchivePathCandidates(readPath);
39
+ for (const candidate of candidates) {
40
+ let absolutePath = resolveReadPath(candidate.archivePath, session.cwd);
41
+ let suffixResolution: { from: string; to: string } | undefined;
42
+
43
+ try {
44
+ const stat = await Bun.file(absolutePath).stat();
45
+ if (stat.isDirectory()) continue;
46
+ return {
47
+ absolutePath,
48
+ archiveSubPath: candidate.archivePath === readPath ? "" : candidate.subPath,
49
+ suffixResolution,
50
+ };
51
+ } catch (error) {
52
+ if (!isNotFoundError(error) || isRemoteMountPath(absolutePath)) continue;
53
+
54
+ const suffixMatch = await findSuffixMatchCached(session, suffixCache, candidate.archivePath, signal);
55
+ if (!suffixMatch) continue;
56
+
57
+ try {
58
+ const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
59
+ if (retryStat.isDirectory()) continue;
60
+
61
+ absolutePath = suffixMatch.absolutePath;
62
+ suffixResolution = { from: candidate.archivePath, to: suffixMatch.displayPath };
63
+ return {
64
+ absolutePath,
65
+ archiveSubPath: candidate.archivePath === readPath ? "" : candidate.subPath,
66
+ suffixResolution,
67
+ };
68
+ } catch (retryError) {
69
+ if (!isNotFoundError(retryError)) {
70
+ throw retryError;
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ return null;
77
+ }
78
+ async function readArchiveDirectory(
79
+ archive: ArchiveReader,
80
+ archivePath: string,
81
+ subPath: string,
82
+ offset: number | undefined,
83
+ limit: number | undefined,
84
+ details: ReadToolDetails,
85
+ signal?: AbortSignal,
86
+ ): Promise<AgentToolResult<ReadToolDetails>> {
87
+ const DEFAULT_LIMIT = 500;
88
+ const effectiveLimit = limit ?? DEFAULT_LIMIT;
89
+ const allEntries = archive.listDirectory(subPath);
90
+ // `offset` is 1-indexed (line-selector semantics): `a.zip:dir:50` starts
91
+ // the listing at the 50th entry instead of being silently ignored.
92
+ const entries = offset !== undefined && offset > 1 ? allEntries.slice(offset - 1) : allEntries;
93
+
94
+ const listLimit = applyListLimit(entries, { limit: effectiveLimit });
95
+ const limitedEntries = listLimit.items;
96
+ const limitMeta = listLimit.meta;
97
+
98
+ for (let index = 0; index < limitedEntries.length; index++) {
99
+ throwIfAborted(signal);
100
+ }
101
+ const results = formatArchiveEntryLines(limitedEntries);
102
+
103
+ const output = results.length > 0 ? results.join("\n") : "(empty archive directory)";
104
+ const text = prependSuffixResolutionNotice(output, details.suffixResolution);
105
+ const truncation = truncateHead(text, { maxLines: Number.MAX_SAFE_INTEGER });
106
+ const directoryDetails: ReadToolDetails = { ...details, isDirectory: true };
107
+ const resultBuilder = toolResult<ReadToolDetails>(directoryDetails).text(truncation.content);
108
+ resultBuilder.sourcePath(archivePath).limits({ resultLimit: limitMeta.resultLimit?.reached });
109
+ if (truncation.truncated) {
110
+ directoryDetails.truncation = truncation;
111
+ resultBuilder.truncation(truncation, { direction: "head" });
112
+ }
113
+ return resultBuilder.done();
114
+ }
115
+
116
+ export async function readArchive(
117
+ session: ToolSession,
118
+ readPath: string,
119
+ parsedSel: ParsedSelector,
120
+ resolvedArchivePath: ResolvedArchiveReadPath,
121
+ signal?: AbortSignal,
122
+ ): Promise<AgentToolResult<ReadToolDetails>> {
123
+ throwIfAborted(signal);
124
+ const archive = await openArchive(resolvedArchivePath.absolutePath);
125
+ throwIfAborted(signal);
126
+
127
+ const details: ReadToolDetails = markMarkdownContentType(
128
+ session,
129
+ {
130
+ resolvedPath: resolvedArchivePath.absolutePath,
131
+ suffixResolution: resolvedArchivePath.suffixResolution,
132
+ },
133
+ resolvedArchivePath.archiveSubPath,
134
+ );
135
+
136
+ let archiveSubPath = resolvedArchivePath.archiveSubPath;
137
+ let sel = parsedSel;
138
+ let node = archive.getNode(archiveSubPath);
139
+ if (!node && archiveSubPath) {
140
+ // `archive.zip:500` / `archive.zip:raw`: the whole subPath is a
141
+ // selector on the archive root, not a member name. Member names take
142
+ // precedence (getNode above); fall back to root + selector.
143
+ const wholeSel = parseSel(archiveSubPath);
144
+ if (wholeSel.kind !== "none") {
145
+ node = archive.getNode("");
146
+ archiveSubPath = "";
147
+ sel = wholeSel;
148
+ }
149
+ }
150
+ if (!node) {
151
+ throw new ToolError(`Path '${readPath}' not found inside archive`);
152
+ }
153
+
154
+ if (node.isDirectory) {
155
+ if (isMultiRange(sel)) {
156
+ throw new ToolError("Multi-range line selectors are not supported for archive directory listings.");
157
+ }
158
+ const { offset, limit } = selToOffsetLimit(sel);
159
+ return readArchiveDirectory(
160
+ archive,
161
+ resolvedArchivePath.absolutePath,
162
+ archiveSubPath,
163
+ offset,
164
+ limit,
165
+ details,
166
+ signal,
167
+ );
168
+ }
169
+
170
+ const entry = await archive.readFile(archiveSubPath);
171
+ const text = decodeUtf8Text(entry.bytes);
172
+ if (text === null) {
173
+ return toolResult<ReadToolDetails>(details)
174
+ .text(
175
+ prependSuffixResolutionNotice(
176
+ `[Cannot read binary archive entry '${entry.path}' (${formatBytes(entry.size)})]`,
177
+ resolvedArchivePath.suffixResolution,
178
+ ),
179
+ )
180
+ .sourcePath(resolvedArchivePath.absolutePath)
181
+ .done();
182
+ }
183
+
184
+ // Archive members are immutable: there is no edit path for bytes inside
185
+ // an archive, and a hashline tag keyed to the archive file would invite
186
+ // (and fail) edits while clobbering sibling members' snapshots.
187
+ const raw = isRawSelector(sel);
188
+ const result =
189
+ isMultiRange(sel) && sel.kind === "lines"
190
+ ? buildInMemoryMultiRangeResult(session, text, sel.ranges, {
191
+ details,
192
+ sourcePath: resolvedArchivePath.absolutePath,
193
+ entityLabel: "archive entry",
194
+ raw,
195
+ immutable: true,
196
+ })
197
+ : buildInMemoryTextResult(session, text, selToOffsetLimit(sel).offset, selToOffsetLimit(sel).limit, {
198
+ details,
199
+ sourcePath: resolvedArchivePath.absolutePath,
200
+ entityLabel: "archive entry",
201
+ raw,
202
+ immutable: true,
203
+ });
204
+ const firstText = result.content.find((content): content is TextContent => content.type === "text");
205
+ if (firstText) {
206
+ firstText.text = prependSuffixResolutionNotice(firstText.text, resolvedArchivePath.suffixResolution);
207
+ }
208
+ return result;
209
+ }