@namzu/sdk 57.1.0 → 58.1.0

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 (254) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/file-definitions.d.ts +35 -4
  6. package/dist/agents/file-definitions.d.ts.map +1 -1
  7. package/dist/agents/file-definitions.js +98 -8
  8. package/dist/agents/file-definitions.js.map +1 -1
  9. package/dist/authorization/protected-paths.d.ts +9 -0
  10. package/dist/authorization/protected-paths.d.ts.map +1 -1
  11. package/dist/authorization/protected-paths.js +29 -3
  12. package/dist/authorization/protected-paths.js.map +1 -1
  13. package/dist/compaction/context-window.d.ts +1 -1
  14. package/dist/compaction/context-window.d.ts.map +1 -1
  15. package/dist/compaction/context-window.js +8 -1
  16. package/dist/compaction/context-window.js.map +1 -1
  17. package/dist/compaction/plan.d.ts.map +1 -1
  18. package/dist/compaction/plan.js +2 -2
  19. package/dist/compaction/plan.js.map +1 -1
  20. package/dist/config/runtime.d.ts +10 -0
  21. package/dist/config/runtime.d.ts.map +1 -1
  22. package/dist/config/runtime.js +2 -0
  23. package/dist/config/runtime.js.map +1 -1
  24. package/dist/connector/mcp/client.d.ts +8 -0
  25. package/dist/connector/mcp/client.d.ts.map +1 -1
  26. package/dist/connector/mcp/client.js +75 -2
  27. package/dist/connector/mcp/client.js.map +1 -1
  28. package/dist/constants/mcp/index.d.ts +2 -0
  29. package/dist/constants/mcp/index.d.ts.map +1 -1
  30. package/dist/constants/mcp/index.js +2 -0
  31. package/dist/constants/mcp/index.js.map +1 -1
  32. package/dist/constants/plugin/index.d.ts +2 -0
  33. package/dist/constants/plugin/index.d.ts.map +1 -1
  34. package/dist/constants/plugin/index.js +2 -0
  35. package/dist/constants/plugin/index.js.map +1 -1
  36. package/dist/manager/agent/lifecycle.d.ts +1 -1
  37. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  38. package/dist/manager/agent/lifecycle.js +25 -4
  39. package/dist/manager/agent/lifecycle.js.map +1 -1
  40. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  41. package/dist/manager/session/turn-recorder.js +2 -1
  42. package/dist/manager/session/turn-recorder.js.map +1 -1
  43. package/dist/persona/assembler.d.ts.map +1 -1
  44. package/dist/persona/assembler.js +2 -2
  45. package/dist/persona/assembler.js.map +1 -1
  46. package/dist/plugin/index.d.ts +1 -1
  47. package/dist/plugin/index.d.ts.map +1 -1
  48. package/dist/plugin/lifecycle.d.ts +8 -1
  49. package/dist/plugin/lifecycle.d.ts.map +1 -1
  50. package/dist/plugin/lifecycle.js +9 -2
  51. package/dist/plugin/lifecycle.js.map +1 -1
  52. package/dist/plugin/shell-hook-http.d.ts +39 -0
  53. package/dist/plugin/shell-hook-http.d.ts.map +1 -0
  54. package/dist/plugin/shell-hook-http.js +163 -0
  55. package/dist/plugin/shell-hook-http.js.map +1 -0
  56. package/dist/plugin/shell-hook.d.ts +93 -11
  57. package/dist/plugin/shell-hook.d.ts.map +1 -1
  58. package/dist/plugin/shell-hook.js +223 -39
  59. package/dist/plugin/shell-hook.js.map +1 -1
  60. package/dist/public-runtime.d.ts +2 -2
  61. package/dist/public-runtime.d.ts.map +1 -1
  62. package/dist/public-runtime.js +2 -2
  63. package/dist/public-runtime.js.map +1 -1
  64. package/dist/public-tools.d.ts +1 -0
  65. package/dist/public-tools.d.ts.map +1 -1
  66. package/dist/public-tools.js +2 -0
  67. package/dist/public-tools.js.map +1 -1
  68. package/dist/public-types.d.ts +1 -1
  69. package/dist/public-types.d.ts.map +1 -1
  70. package/dist/runtime/query/executor.d.ts +1 -0
  71. package/dist/runtime/query/executor.d.ts.map +1 -1
  72. package/dist/runtime/query/executor.js +34 -9
  73. package/dist/runtime/query/executor.js.map +1 -1
  74. package/dist/runtime/query/index.d.ts +2 -0
  75. package/dist/runtime/query/index.d.ts.map +1 -1
  76. package/dist/runtime/query/index.js +25 -1
  77. package/dist/runtime/query/index.js.map +1 -1
  78. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  79. package/dist/runtime/query/iteration/index.js +47 -10
  80. package/dist/runtime/query/iteration/index.js.map +1 -1
  81. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +18 -8
  82. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
  83. package/dist/runtime/query/iteration/phases/compaction-breaker.js +33 -9
  84. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
  85. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  86. package/dist/runtime/query/iteration/phases/compaction.js +7 -5
  87. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  88. package/dist/runtime/query/iteration/phases/context.d.ts +2 -0
  89. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  90. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  91. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  92. package/dist/runtime/query/iteration/phases/tool-review.js +70 -3
  93. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  94. package/dist/runtime/query/path-escalation.d.ts +2 -0
  95. package/dist/runtime/query/path-escalation.d.ts.map +1 -1
  96. package/dist/runtime/query/path-escalation.js +7 -4
  97. package/dist/runtime/query/path-escalation.js.map +1 -1
  98. package/dist/runtime/query/plugin-hooks.js +2 -2
  99. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  100. package/dist/runtime/query/prepare-turn.d.ts +2 -0
  101. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  102. package/dist/runtime/query/prepare-turn.js +25 -0
  103. package/dist/runtime/query/prepare-turn.js.map +1 -1
  104. package/dist/runtime/query/review-policy.d.ts +3 -2
  105. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  106. package/dist/runtime/query/review-policy.js +32 -13
  107. package/dist/runtime/query/review-policy.js.map +1 -1
  108. package/dist/session/retention/archive.js +1 -1
  109. package/dist/session/retention/archive.js.map +1 -1
  110. package/dist/session/workspace/driver.d.ts +6 -0
  111. package/dist/session/workspace/driver.d.ts.map +1 -1
  112. package/dist/session/workspace/git-worktree.d.ts.map +1 -1
  113. package/dist/session/workspace/git-worktree.js +28 -0
  114. package/dist/session/workspace/git-worktree.js.map +1 -1
  115. package/dist/skills/index.d.ts +1 -1
  116. package/dist/skills/index.d.ts.map +1 -1
  117. package/dist/skills/index.js +1 -1
  118. package/dist/skills/index.js.map +1 -1
  119. package/dist/skills/loader.d.ts +7 -0
  120. package/dist/skills/loader.d.ts.map +1 -1
  121. package/dist/skills/loader.js +53 -0
  122. package/dist/skills/loader.js.map +1 -1
  123. package/dist/skills/registry.d.ts +1 -1
  124. package/dist/skills/registry.d.ts.map +1 -1
  125. package/dist/skills/registry.js +2 -1
  126. package/dist/skills/registry.js.map +1 -1
  127. package/dist/tools/builtins/bash-cwd.d.ts +52 -0
  128. package/dist/tools/builtins/bash-cwd.d.ts.map +1 -0
  129. package/dist/tools/builtins/bash-cwd.js +127 -0
  130. package/dist/tools/builtins/bash-cwd.js.map +1 -0
  131. package/dist/tools/builtins/bash.d.ts.map +1 -1
  132. package/dist/tools/builtins/bash.js +41 -6
  133. package/dist/tools/builtins/bash.js.map +1 -1
  134. package/dist/tools/builtins/bounded-file-read.d.ts +10 -0
  135. package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -0
  136. package/dist/tools/builtins/bounded-file-read.js +67 -0
  137. package/dist/tools/builtins/bounded-file-read.js.map +1 -0
  138. package/dist/tools/builtins/grep.d.ts +6 -1
  139. package/dist/tools/builtins/grep.d.ts.map +1 -1
  140. package/dist/tools/builtins/grep.js +187 -24
  141. package/dist/tools/builtins/grep.js.map +1 -1
  142. package/dist/tools/builtins/read-file.d.ts +1 -0
  143. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  144. package/dist/tools/builtins/read-file.js +113 -28
  145. package/dist/tools/builtins/read-file.js.map +1 -1
  146. package/dist/tools/builtins/read-media.d.ts +68 -0
  147. package/dist/tools/builtins/read-media.d.ts.map +1 -0
  148. package/dist/tools/builtins/read-media.js +397 -0
  149. package/dist/tools/builtins/read-media.js.map +1 -0
  150. package/dist/tools/builtins/search-tools.js +1 -1
  151. package/dist/tools/builtins/search-tools.js.map +1 -1
  152. package/dist/tools/builtins/skill.d.ts +4 -2
  153. package/dist/tools/builtins/skill.d.ts.map +1 -1
  154. package/dist/tools/builtins/skill.js +61 -2
  155. package/dist/tools/builtins/skill.js.map +1 -1
  156. package/dist/tools/builtins/sleep-guard.d.ts +15 -0
  157. package/dist/tools/builtins/sleep-guard.d.ts.map +1 -0
  158. package/dist/tools/builtins/sleep-guard.js +33 -0
  159. package/dist/tools/builtins/sleep-guard.js.map +1 -0
  160. package/dist/tools/builtins/view-image.d.ts +23 -1
  161. package/dist/tools/builtins/view-image.d.ts.map +1 -1
  162. package/dist/tools/builtins/view-image.js +66 -58
  163. package/dist/tools/builtins/view-image.js.map +1 -1
  164. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  165. package/dist/tools/file-read-tracker.js +31 -0
  166. package/dist/tools/file-read-tracker.js.map +1 -1
  167. package/dist/toolsets/manager.js +3 -3
  168. package/dist/toolsets/manager.js.map +1 -1
  169. package/dist/types/agent/query.d.ts +2 -0
  170. package/dist/types/agent/query.d.ts.map +1 -1
  171. package/dist/types/agent/task.d.ts +6 -2
  172. package/dist/types/agent/task.d.ts.map +1 -1
  173. package/dist/types/agent/task.js.map +1 -1
  174. package/dist/types/connector/mcp.d.ts +46 -0
  175. package/dist/types/connector/mcp.d.ts.map +1 -1
  176. package/dist/types/hitl/index.d.ts +1 -1
  177. package/dist/types/plugin/index.d.ts +72 -5
  178. package/dist/types/plugin/index.d.ts.map +1 -1
  179. package/dist/types/plugin/index.js +12 -1
  180. package/dist/types/plugin/index.js.map +1 -1
  181. package/dist/types/session/sub-session.d.ts +6 -2
  182. package/dist/types/session/sub-session.d.ts.map +1 -1
  183. package/dist/types/skills/index.d.ts +36 -0
  184. package/dist/types/skills/index.d.ts.map +1 -1
  185. package/dist/types/skills/index.js +9 -0
  186. package/dist/types/skills/index.js.map +1 -1
  187. package/dist/types/tool/index.d.ts +22 -0
  188. package/dist/types/tool/index.d.ts.map +1 -1
  189. package/dist/types/tool/index.js.map +1 -1
  190. package/dist/types/workspace/ref.d.ts +2 -0
  191. package/dist/types/workspace/ref.d.ts.map +1 -1
  192. package/dist/utils/frontmatter.d.ts +12 -0
  193. package/dist/utils/frontmatter.d.ts.map +1 -1
  194. package/dist/utils/frontmatter.js +6 -0
  195. package/dist/utils/frontmatter.js.map +1 -1
  196. package/package.json +1 -1
  197. package/src/agents/QueryAgent.ts +2 -0
  198. package/src/agents/file-definitions.ts +126 -6
  199. package/src/authorization/protected-paths.ts +30 -2
  200. package/src/compaction/context-window.ts +10 -0
  201. package/src/compaction/plan.ts +12 -2
  202. package/src/config/runtime.ts +2 -0
  203. package/src/connector/mcp/client.ts +87 -1
  204. package/src/constants/mcp/index.ts +3 -0
  205. package/src/constants/plugin/index.ts +2 -0
  206. package/src/manager/agent/lifecycle.ts +24 -5
  207. package/src/manager/session/turn-recorder.ts +2 -1
  208. package/src/persona/assembler.ts +2 -2
  209. package/src/plugin/index.ts +5 -0
  210. package/src/plugin/lifecycle.ts +16 -2
  211. package/src/plugin/shell-hook-http.ts +181 -0
  212. package/src/plugin/shell-hook.ts +386 -89
  213. package/src/public-runtime.ts +2 -0
  214. package/src/public-tools.ts +2 -0
  215. package/src/public-types.ts +5 -0
  216. package/src/runtime/query/executor.ts +40 -10
  217. package/src/runtime/query/index.ts +33 -0
  218. package/src/runtime/query/iteration/index.ts +51 -11
  219. package/src/runtime/query/iteration/phases/compaction-breaker.ts +38 -9
  220. package/src/runtime/query/iteration/phases/compaction.ts +27 -5
  221. package/src/runtime/query/iteration/phases/context.ts +2 -0
  222. package/src/runtime/query/iteration/phases/tool-review.ts +74 -3
  223. package/src/runtime/query/path-escalation.ts +11 -5
  224. package/src/runtime/query/plugin-hooks.ts +2 -2
  225. package/src/runtime/query/prepare-turn.ts +33 -0
  226. package/src/runtime/query/review-policy.ts +45 -16
  227. package/src/session/retention/archive.ts +1 -1
  228. package/src/session/workspace/driver.ts +6 -0
  229. package/src/session/workspace/git-worktree.ts +31 -0
  230. package/src/skills/index.ts +6 -1
  231. package/src/skills/loader.ts +54 -0
  232. package/src/skills/registry.ts +7 -6
  233. package/src/tools/builtins/bash-cwd.ts +156 -0
  234. package/src/tools/builtins/bash.ts +79 -34
  235. package/src/tools/builtins/bounded-file-read.ts +80 -0
  236. package/src/tools/builtins/grep.ts +198 -28
  237. package/src/tools/builtins/read-file.ts +151 -30
  238. package/src/tools/builtins/read-media.ts +483 -0
  239. package/src/tools/builtins/search-tools.ts +1 -1
  240. package/src/tools/builtins/skill.ts +91 -2
  241. package/src/tools/builtins/sleep-guard.ts +31 -0
  242. package/src/tools/builtins/view-image.ts +74 -62
  243. package/src/tools/file-read-tracker.ts +30 -0
  244. package/src/toolsets/manager.ts +3 -3
  245. package/src/types/agent/query.ts +2 -0
  246. package/src/types/agent/task.ts +6 -2
  247. package/src/types/connector/mcp.ts +48 -0
  248. package/src/types/hitl/index.ts +1 -1
  249. package/src/types/plugin/index.ts +102 -16
  250. package/src/types/session/sub-session.ts +6 -2
  251. package/src/types/skills/index.ts +48 -0
  252. package/src/types/tool/index.ts +22 -0
  253. package/src/types/workspace/ref.ts +2 -0
  254. package/src/utils/frontmatter.ts +21 -0
@@ -3,14 +3,14 @@ import { open } from 'node:fs/promises'
3
3
  import { inflateSync } from 'node:zlib'
4
4
  import { z } from 'zod'
5
5
  import { admitMcpImageBatch } from '../../connector/mcp/image-admission.js'
6
- import type { ToolContext, ToolDefinition } from '../../types/tool/index.js'
6
+ import type { ToolContext, ToolDefinition, ToolResult } from '../../types/tool/index.js'
7
7
  import { defineTool } from '../defineTool.js'
8
8
  import { resolveWithinAnyReal, toolRoots } from '../paths.js'
9
9
  import { STANDARD_SCREENSHOT_LIMITS, fitPng, screenshotTargetSize } from './computer-use-image.js'
10
10
 
11
11
  // 16 MiB becomes less than the runtime's 24 MiB encoded rich-content budget.
12
12
  // Admission is per artifact; the runtime still budgets accumulated images.
13
- const MAX_BYTES = 16 * 1024 * 1024
13
+ export const MAX_BYTES = 16 * 1024 * 1024
14
14
  const READ_CHUNK_BYTES = 1024 * 1024
15
15
  const MAX_EDGE = 16_384
16
16
  const MAX_PIXELS = 16_000_000
@@ -55,7 +55,7 @@ async function readBounded(
55
55
  }
56
56
  }
57
57
 
58
- async function readArtifact(
58
+ export async function readArtifact(
59
59
  path: string,
60
60
  context: ToolContext,
61
61
  ): Promise<{ path: string; bytes: Buffer; sandboxed: boolean }> {
@@ -168,7 +168,7 @@ function webpDimensions(bytes: Buffer): Pick<Raster, 'width' | 'height'> {
168
168
  return dimensions ?? malformed()
169
169
  }
170
170
 
171
- function describeRaster(bytes: Buffer): Raster {
171
+ export function describeRaster(bytes: Buffer): Raster {
172
172
  let raster: Raster
173
173
  if (bytes.subarray(0, 8).equals(PNG_SIGNATURE)) {
174
174
  if (bytes.length < 33) malformed()
@@ -313,6 +313,75 @@ async function validatedPng(bytes: Buffer, raster: Raster): Promise<Buffer> {
313
313
  return canonical
314
314
  }
315
315
 
316
+ /**
317
+ * Validate and fit the bytes of a saved image and shape them as a tool result
318
+ * carrying the pixels. `view_image` and an image `read` share this, so both
319
+ * give the model the same checked picture and the same source dimensions.
320
+ */
321
+ export async function inspectImageBytes(
322
+ artifact: { readonly path: string; readonly bytes: Buffer; readonly sandboxed: boolean },
323
+ signal: AbortSignal,
324
+ ): Promise<ToolResult> {
325
+ const source = describeRaster(artifact.bytes)
326
+ if (
327
+ !admitMcpImageBatch([
328
+ {
329
+ type: 'image',
330
+ mimeType: source.mediaType,
331
+ data: artifact.bytes.toString('base64'),
332
+ },
333
+ ])
334
+ )
335
+ malformed()
336
+ signal.throwIfAborted()
337
+ let image = {
338
+ data: artifact.bytes,
339
+ width: source.width,
340
+ height: source.height,
341
+ }
342
+ if (source.mediaType === 'image/png') {
343
+ const png = await validatedPng(artifact.bytes, source)
344
+ image = await fitPng(png, STANDARD_SCREENSHOT_LIMITS)
345
+ } else {
346
+ const target = screenshotTargetSize(source.width, source.height)
347
+ if (target.width !== source.width || target.height !== source.height)
348
+ throw new Error(
349
+ `This ${source.mediaType} image needs resizing. JPEG/WebP decoding and resizing are unavailable here; export a PNG (or a smaller JPEG/WebP) and inspect that file.`,
350
+ )
351
+ }
352
+ signal.throwIfAborted()
353
+ const validation = source.mediaType === 'image/png' ? 'decoded' : 'container'
354
+ const output = `Image artifact: ${artifact.path} (${source.width}x${source.height}, ${source.mediaType}); shown at ${image.width}x${image.height}.`
355
+ return {
356
+ success: true,
357
+ output,
358
+ content: [
359
+ {
360
+ type: 'text',
361
+ text: `${output}\n${validation === 'container' ? 'The complete container and dimensions were validated; compressed JPEG/WebP pixels were not decoded by this tool. ' : ''}Inspect these pixels for visual evidence. This saved artifact is not a current computer_use screenshot; capture the application before GUI input.`,
362
+ },
363
+ {
364
+ type: 'image',
365
+ mediaType: source.mediaType,
366
+ data: image.data.toString('base64'),
367
+ },
368
+ ],
369
+ data: {
370
+ path: artifact.path,
371
+ sandboxed: artifact.sandboxed,
372
+ artifact: true,
373
+ mediaType: source.mediaType,
374
+ sourceWidth: source.width,
375
+ sourceHeight: source.height,
376
+ width: image.width,
377
+ height: image.height,
378
+ sourceBytes: artifact.bytes.length,
379
+ sha256: createHash('sha256').update(artifact.bytes).digest('hex'),
380
+ validation,
381
+ },
382
+ }
383
+ }
384
+
316
385
  /**
317
386
  * Inspect an artifact without granting screen-coordinate authority. An image
318
387
  * file can be a reference, render or export; it is never a computer_use frame.
@@ -343,64 +412,7 @@ export function createViewImageTool(
343
412
  return { success: false, output: message, error: message }
344
413
  }
345
414
  const artifact = await readArtifact(input.path, context)
346
- const source = describeRaster(artifact.bytes)
347
- if (
348
- !admitMcpImageBatch([
349
- {
350
- type: 'image',
351
- mimeType: source.mediaType,
352
- data: artifact.bytes.toString('base64'),
353
- },
354
- ])
355
- )
356
- malformed()
357
- context.abortSignal.throwIfAborted()
358
- let image = {
359
- data: artifact.bytes,
360
- width: source.width,
361
- height: source.height,
362
- }
363
- if (source.mediaType === 'image/png') {
364
- const png = await validatedPng(artifact.bytes, source)
365
- image = await fitPng(png, STANDARD_SCREENSHOT_LIMITS)
366
- } else {
367
- const target = screenshotTargetSize(source.width, source.height)
368
- if (target.width !== source.width || target.height !== source.height)
369
- throw new Error(
370
- `This ${source.mediaType} image needs resizing. JPEG/WebP decoding and resizing are unavailable here; export a PNG (or a smaller JPEG/WebP) and inspect that file.`,
371
- )
372
- }
373
- context.abortSignal.throwIfAborted()
374
- const validation = source.mediaType === 'image/png' ? 'decoded' : 'container'
375
- const output = `Image artifact: ${artifact.path} (${source.width}x${source.height}, ${source.mediaType}); shown at ${image.width}x${image.height}.`
376
- return {
377
- success: true,
378
- output,
379
- content: [
380
- {
381
- type: 'text',
382
- text: `${output}\n${validation === 'container' ? 'The complete container and dimensions were validated; compressed JPEG/WebP pixels were not decoded by this tool. ' : ''}Inspect these pixels for visual evidence. This saved artifact is not a current computer_use screenshot; capture the application before GUI input.`,
383
- },
384
- {
385
- type: 'image',
386
- mediaType: source.mediaType,
387
- data: image.data.toString('base64'),
388
- },
389
- ],
390
- data: {
391
- path: artifact.path,
392
- sandboxed: artifact.sandboxed,
393
- artifact: true,
394
- mediaType: source.mediaType,
395
- sourceWidth: source.width,
396
- sourceHeight: source.height,
397
- width: image.width,
398
- height: image.height,
399
- sourceBytes: artifact.bytes.length,
400
- sha256: createHash('sha256').update(artifact.bytes).digest('hex'),
401
- validation,
402
- },
403
- }
415
+ return inspectImageBytes(artifact, context.abortSignal)
404
416
  },
405
417
  })
406
418
  }
@@ -1,6 +1,10 @@
1
1
  import type { FileReadTracker, ReadWindowEntry } from '../types/tool/index.js'
2
2
  import { fingerprintContent } from './builtins/content-fingerprint.js'
3
3
 
4
+ /** A body larger than this is not kept; the file's change is still seen by fingerprint. */
5
+ const MAX_BODY_CHARS = 512 * 1024
6
+ const MAX_BODIES = 128
7
+
4
8
  /** Host-owned observation ledger. Share only across turns of the same conversation and filesystem. */
5
9
  export function createFileReadTracker(): FileReadTracker {
6
10
  const paths = new Set<string>()
@@ -27,6 +31,21 @@ export function createFileReadTracker(): FileReadTracker {
27
31
  // `editChain` or `readWitness` answers; it says only that what this ledger
28
32
  // holds for the path is known to be behind the disk.
29
33
  const drifted = new Set<string>()
34
+ // The body of each file whose last observation was the whole file, newest
35
+ // last, so a host can show what changed under it. Bounded: a file larger
36
+ // than the cap keeps no body (its change is still detectable by
37
+ // fingerprint), and the oldest bodies go first.
38
+ const bodies = new Map<string, string>()
39
+ function keepBody(key: string, content: string | undefined): void {
40
+ bodies.delete(key)
41
+ if (content === undefined || content.length > MAX_BODY_CHARS) return
42
+ bodies.set(key, content)
43
+ while (bodies.size > MAX_BODIES) {
44
+ const oldest = bodies.keys().next().value
45
+ if (oldest === undefined) break
46
+ bodies.delete(oldest)
47
+ }
48
+ }
30
49
  // One observation, whatever witness the caller goes on to record against it.
31
50
  // A shared function rather than `this.recordRead`, so a destructured method
32
51
  // keeps working.
@@ -41,6 +60,9 @@ export function createFileReadTracker(): FileReadTracker {
41
60
  }
42
61
  if (next !== undefined) fingerprints.set(key, next)
43
62
  else fingerprints.delete(key)
63
+ // A write supplies the whole body; any other observation without a
64
+ // witness may be a window of the file, which proves nothing about the rest.
65
+ keepBody(key, fullWriteCallId ? content : undefined)
44
66
  if (next !== undefined && fullWriteCallId) {
45
67
  writes.set(key, fullWriteCallId)
46
68
  // A full body arrived whole in one call, so the chain is empty
@@ -76,12 +98,19 @@ export function createFileReadTracker(): FileReadTracker {
76
98
  recordRead: observe,
77
99
  recordFullRead(key, content, callId, renderedFingerprint) {
78
100
  observe(key, content)
101
+ keepBody(key, content)
79
102
  // A surviving write witness outranks this one: it names a body the
80
103
  // model composed itself, and the chain machinery can replay onto it.
81
104
  // This is the same observation either way — only the witness differs.
82
105
  if (!writes.has(key)) reads.set(key, { callId, renderedFingerprint })
83
106
  },
84
107
  readWitness: (key) => reads.get(key),
108
+ fullBody: (key) => bodies.get(key),
109
+ fullBodyPaths: () => [...bodies.keys()],
110
+ recordFullBody(key, content) {
111
+ observe(key, content)
112
+ keepBody(key, content)
113
+ },
85
114
  recordEdit(key, content, callId) {
86
115
  // Both conditions are about the PRE-image, not this edit. A chain
87
116
  // replays only if the content this edit ran against is itself
@@ -97,6 +126,7 @@ export function createFileReadTracker(): FileReadTracker {
97
126
  // no chain can be rooted at a rendering to carry it forward.
98
127
  reads.delete(key)
99
128
  fingerprints.set(key, fingerprintContent(content))
129
+ keepBody(key, content)
100
130
  if (!chained) {
101
131
  writes.delete(key)
102
132
  chains.delete(key)
@@ -547,7 +547,7 @@ export class ToolManager {
547
547
 
548
548
  const parts: string[] = []
549
549
  const contractNote = `<tool_runtime_contract>
550
- Executable tool names, descriptions, and JSON input schemas are attached through the runtime tools parameter. Treat that runtime schema as authoritative; this prompt section is a discoverability summary only.
550
+ Executable tool names, descriptions, and JSON input schemas are attached through the runtime tools parameter. Treat that runtime schema as authoritative. This catalogue is a snapshot for discovery, not live availability status: a tool listed under deferred_tools may already be loaded through history. If its schema is visible in the current runtime tools parameter, call it directly without searching again.
551
551
  </tool_runtime_contract>`
552
552
 
553
553
  if (active.length > 0) {
@@ -571,8 +571,8 @@ Executable tool names, descriptions, and JSON input schemas are attached through
571
571
  this.has('search_tools') &&
572
572
  this.availability('search_tools') === 'active' &&
573
573
  (!names || names.includes('search_tools'))
574
- ? 'Use search_tools to load these before use:'
575
- : 'Deferred tools are discoverable but not executable until the runtime activates them:'
574
+ ? "Only when a needed deferred tool's schema is absent from the current runtime tools parameter, use search_tools with its exact name to load it:"
575
+ : 'For a needed deferred tool whose schema is absent, wait for runtime activation before calling it:'
576
576
  parts.push(`<deferred_tools>\n${deferredIntro}\n${entries}\n</deferred_tools>`)
577
577
  }
578
578
 
@@ -18,6 +18,8 @@ import type { WorkingMemoryProvider } from './working-memory.js'
18
18
 
19
19
  export interface QueryAgentConfig extends BaseAgentConfig {
20
20
  systemPrompt?: string
21
+ /** Host lifecycle hooks; forwarded unchanged to the child query. */
22
+ pluginManager?: QueryParams['pluginManager']
21
23
  /** Provider-hosted search for this turn; the selected driver must support it. */
22
24
  webSearch?: { mode: 'live' | 'cached' }
23
25
 
@@ -28,8 +28,12 @@ export type ChildWorkspaceRequest =
28
28
  readonly baseRef?: string
29
29
  /** Relative directory within the checkout where this child starts. */
30
30
  readonly subdirectory?: string
31
- /** The existing SDK behavior is `dispose`; CLI delegates select `retain`. */
32
- readonly retention?: 'dispose' | 'retain'
31
+ /**
32
+ * The existing SDK behavior is `dispose`; `retain` always keeps the
33
+ * checkout; `retain-changed` removes it when the child ended with no
34
+ * uncommitted changes and no new commits, and keeps it otherwise.
35
+ */
36
+ readonly retention?: 'dispose' | 'retain' | 'retain-changed'
33
37
  }
34
38
 
35
39
  export function isTerminalAgentTaskState(state: AgentTaskState): boolean {
@@ -410,6 +410,12 @@ export interface MCPPromptMessage {
410
410
  export interface MCPClientCapabilities {
411
411
  roots?: { listChanged?: boolean }
412
412
  sampling?: Record<string, never>
413
+ /**
414
+ * Declared on a legacy-era `initialize` when the client was built with
415
+ * {@link MCPClientConfig.onElicitation}; an empty object means form mode
416
+ * only. The client sets it, a caller passing it by hand gets no handler.
417
+ */
418
+ elicitation?: Record<string, never>
413
419
  experimental?: Record<string, unknown>
414
420
  }
415
421
 
@@ -451,6 +457,34 @@ export interface MCPInitializeResult {
451
457
  instructions?: string
452
458
  }
453
459
 
460
+ /**
461
+ * A server's request for structured input from the person, form mode.
462
+ *
463
+ * `requestedSchema` is the server's flat object schema, kept as received:
464
+ * the host decides which property shapes it can show.
465
+ */
466
+ export interface MCPElicitationRequest {
467
+ readonly serverName: string
468
+ readonly message: string
469
+ readonly requestedSchema: {
470
+ readonly type?: 'object'
471
+ readonly properties?: Readonly<Record<string, unknown>>
472
+ readonly required?: readonly string[]
473
+ }
474
+ }
475
+
476
+ /**
477
+ * What the person did: `accept` carries the form values, `decline` is an
478
+ * explicit no, `cancel` is a dismissal (closed without choosing).
479
+ */
480
+ export type MCPElicitationResult =
481
+ | {
482
+ readonly action: 'accept'
483
+ readonly content: Readonly<Record<string, string | number | boolean | readonly string[]>>
484
+ }
485
+ | { readonly action: 'decline' }
486
+ | { readonly action: 'cancel' }
487
+
454
488
  export type MCPConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
455
489
 
456
490
  export interface MCPClientConfig {
@@ -466,6 +500,20 @@ export interface MCPClientConfig {
466
500
  transportFactory?: () => MCPTransport
467
501
  capabilities?: MCPClientCapabilities
468
502
  clientInfo?: { name: string; version: string }
503
+ /**
504
+ * Answer a server's `elicitation/create` (form mode) by asking the person.
505
+ *
506
+ * Setting it makes the client declare the `elicitation` capability on a
507
+ * legacy-era handshake. Left out, nothing is declared and a server's
508
+ * elicitation request is refused as an unknown method, exactly as before.
509
+ * `signal` fires when the server withdraws the request or the connection
510
+ * ends; a handler that throws answers `cancel`. URL-mode requests are
511
+ * never passed here: they are refused as invalid params.
512
+ */
513
+ onElicitation?: (
514
+ request: MCPElicitationRequest,
515
+ context: { readonly signal: AbortSignal },
516
+ ) => Promise<MCPElicitationResult>
469
517
  /**
470
518
  * Deadline for a single JSON-RPC round trip. Defaults to
471
519
  * `DEFAULT_MCP_REQUEST_TIMEOUT_MS`.
@@ -249,7 +249,7 @@ export interface ToolCallSummary {
249
249
  explicitReview?: true
250
250
  /**
251
251
  * A rule written to ask a person (a `review` rule), or a hook that asked
252
- * for one, requested this review. Unlike {@link explicitReview} it is not
252
+ * for one, requested this review. Like {@link explicitReview}, it is not
253
253
  * set when an `allow` rule was demoted to review because the call reaches
254
254
  * past its boundary. `bypass` and `classify` keep this question for a
255
255
  * person; they answer the demoted kind themselves.
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import {
3
+ MAX_AGENT_DIRS_PER_PLUGIN,
4
+ MAX_COMMAND_DIRS_PER_PLUGIN,
3
5
  MAX_CONNECTORS_PER_PLUGIN,
4
6
  MAX_HOOKS_PER_PLUGIN,
5
7
  MAX_MCP_SERVERS_PER_PLUGIN,
@@ -100,10 +102,27 @@ export type PluginHookEvent =
100
102
  /** A compaction pass is about to run / has run. Carries `compaction`. */
101
103
  | 'pre_compact'
102
104
  | 'post_compact'
103
- /** A child session's turn ended. Fired after its own `turn_end`; carries `parentSessionId` and `parentTurnId`. */
105
+ /**
106
+ * A child session is starting, before its first prompt. Carries `parentSessionId`,
107
+ * `parentTurnId` and `agentType`. A hook may `annotate` to add context to the
108
+ * child's turn.
109
+ */
110
+ | 'subagent_start'
111
+ /** A child session's turn ended. Fired after its own `turn_end`; carries `parentSessionId`, `parentTurnId` and `agentType`. */
104
112
  | 'subagent_stop'
113
+ /**
114
+ * A child session's turn is starting. Fired right after its own
115
+ * `turn_start`; carries `parentSessionId` and `parentTurnId`. Observational.
116
+ */
117
+ | 'subagent_start'
105
118
  | 'turn_start'
106
119
  | 'turn_end'
120
+ /**
121
+ * A turn ended because the model call or the turn itself failed, not because
122
+ * the model finished. Carries `error` (the message). Observational: the turn
123
+ * has already failed.
124
+ */
125
+ | 'turn_failure'
107
126
  | 'turn_interrupt'
108
127
  | 'pre_tool_use'
109
128
  | 'post_tool_use'
@@ -113,6 +132,19 @@ export type PluginHookEvent =
113
132
  * and `toolResult`.
114
133
  */
115
134
  | 'post_tool_use_failure'
135
+ /**
136
+ * Every tool call of one model step has settled, before the next model
137
+ * request. Fired once per step; carries `toolCalls`. `post_tool_use` fires
138
+ * per call and calls may run side by side, so this is the place to look at
139
+ * the step as a whole. `continue` may carry `halt`.
140
+ */
141
+ | 'post_tool_batch'
142
+ /**
143
+ * A tool call was refused without a person saying No: by a rule, the
144
+ * policy of the current mode, a classifier, or a hook. Carries `toolName`,
145
+ * `toolInput` and `reason`. Observational.
146
+ */
147
+ | 'permission_denied'
116
148
  /**
117
149
  * A person is about to be asked whether a tool call may run. Carries
118
150
  * `toolName` and `toolInput`. A hook may answer before the question is
@@ -151,13 +183,17 @@ export function assertPluginHookEvent(event: PluginHookEvent): void {
151
183
  case 'session_end':
152
184
  case 'pre_compact':
153
185
  case 'post_compact':
186
+ case 'subagent_start':
154
187
  case 'subagent_stop':
155
188
  case 'turn_start':
156
189
  case 'turn_end':
190
+ case 'turn_failure':
157
191
  case 'turn_interrupt':
158
192
  case 'pre_tool_use':
159
193
  case 'post_tool_use':
160
194
  case 'post_tool_use_failure':
195
+ case 'post_tool_batch':
196
+ case 'permission_denied':
161
197
  case 'permission_request':
162
198
  case 'notification':
163
199
  case 'pre_llm_call':
@@ -217,6 +253,16 @@ export interface PluginCompactionInfo {
217
253
  readonly contextWindowTokens: number
218
254
  }
219
255
 
256
+ /** One call in a `post_tool_batch` hook's context. */
257
+ export interface PluginToolBatchCall {
258
+ readonly toolCallId: string
259
+ readonly toolName: string
260
+ readonly toolInput: unknown
261
+ /** The text the model was given for the call, a refusal or an error included. */
262
+ readonly output: string
263
+ readonly isError: boolean
264
+ }
265
+
220
266
  /** What a `notification` hook is shown. */
221
267
  export interface PluginNotificationInfo {
222
268
  /**
@@ -234,10 +280,12 @@ export interface PluginHookContext {
234
280
  readonly event: PluginHookEvent
235
281
  /** The host's session. Always present. */
236
282
  readonly sessionId: SessionId
237
- /** The session that delegated, on `subagent_stop`. */
283
+ /** The session that delegated, on `subagent_start` and `subagent_stop`. */
238
284
  readonly parentSessionId?: SessionId
239
- /** The turn whose tool call spawned the child, on `subagent_stop`. */
285
+ /** The turn whose tool call spawned the child, on `subagent_start` and `subagent_stop`. */
240
286
  readonly parentTurnId?: TurnId
287
+ /** The name of the agent the child runs, on `subagent_start` and `subagent_stop`. */
288
+ readonly agentType?: string
241
289
  /** The operator's prompt, on `user_prompt_submit`. */
242
290
  readonly prompt?: string
243
291
  /** The pass, on `pre_compact` and `post_compact`. */
@@ -245,6 +293,12 @@ export interface PluginHookContext {
245
293
  readonly toolName?: string
246
294
  readonly toolInput?: unknown
247
295
  readonly toolResult?: ToolResult
296
+ /** The calls of one model step with their outcomes, on `post_tool_batch`. */
297
+ readonly toolCalls?: readonly PluginToolBatchCall[]
298
+ /** Why the call was refused, on `permission_denied`. */
299
+ readonly reason?: string
300
+ /** The failure's message, on `turn_failure`. */
301
+ readonly error?: string
248
302
  /** What the host is telling the operator, on `notification`. */
249
303
  readonly notification?: PluginNotificationInfo
250
304
  /**
@@ -406,19 +460,27 @@ export function assertPluginHookResult(result: PluginHookResult): asserts result
406
460
  /** Actions the query can interpret for a particular hook event. */
407
461
  export type PluginHookResultFor<E extends PluginHookEvent> = E extends 'user_prompt_submit'
408
462
  ? Extract<PluginHookResult, { action: 'continue' | 'annotate' | 'skip' | 'error' }>
409
- : E extends 'pre_tool_use'
410
- ? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'modify' | 'error' }>
411
- : E extends 'post_tool_use'
412
- ? Extract<PluginHookResult, { action: 'continue' | 'replace' | 'retry' | 'error' }>
413
- : E extends 'post_tool_use_failure'
414
- ? Extract<PluginHookResult, { action: 'continue' | 'error' }>
415
- : E extends 'permission_request'
416
- ? Extract<PluginHookResult, { action: 'continue' | 'skip' }>
417
- : E extends 'turn_end' | 'subagent_stop' | 'pre_compact'
418
- ? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'error' }>
419
- : E extends 'turn_interrupt' | 'session_start' | 'session_end' | 'notification'
420
- ? Extract<PluginHookResult, { action: 'continue' }>
421
- : Extract<PluginHookResult, { action: 'continue' | 'error' }>
463
+ : E extends 'subagent_start'
464
+ ? Extract<PluginHookResult, { action: 'continue' | 'annotate' | 'error' }>
465
+ : E extends 'pre_tool_use'
466
+ ? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'modify' | 'error' }>
467
+ : E extends 'post_tool_use'
468
+ ? Extract<PluginHookResult, { action: 'continue' | 'replace' | 'retry' | 'error' }>
469
+ : E extends 'post_tool_use_failure'
470
+ ? Extract<PluginHookResult, { action: 'continue' | 'error' }>
471
+ : E extends 'permission_request'
472
+ ? Extract<PluginHookResult, { action: 'continue' | 'skip' }>
473
+ : E extends 'turn_end' | 'subagent_stop' | 'pre_compact'
474
+ ? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'error' }>
475
+ : E extends
476
+ | 'turn_interrupt'
477
+ | 'session_start'
478
+ | 'session_end'
479
+ | 'notification'
480
+ | 'turn_failure'
481
+ | 'permission_denied'
482
+ ? Extract<PluginHookResult, { action: 'continue' }>
483
+ : Extract<PluginHookResult, { action: 'continue' | 'error' }>
422
484
 
423
485
  /** The event and its handler verdict stay correlated, including in hook arrays. */
424
486
  export type PluginHookDefinition<E extends PluginHookEvent = PluginHookEvent> =
@@ -475,6 +537,18 @@ export interface PluginManifest {
475
537
  readonly mcpServers?: readonly PluginMCPServerConfig[]
476
538
  readonly connectors?: readonly string[]
477
539
  readonly personas?: readonly string[]
540
+ /**
541
+ * Folders of markdown prompt files, relative to the plugin folder. Each
542
+ * file is a slash command named `<plugin>__<file name>`. The runtime does
543
+ * not read them: the host that owns slash commands does.
544
+ */
545
+ readonly commands?: readonly string[]
546
+ /**
547
+ * Folders of markdown agent files, relative to the plugin folder. Each
548
+ * file is an agent type named `<plugin>__<name>`. Read by the host that
549
+ * owns delegation, not by the runtime.
550
+ */
551
+ readonly agents?: readonly string[]
478
552
  }
479
553
 
480
554
  export const PluginMCPServerConfigSchema = z
@@ -487,6 +561,16 @@ export const PluginMCPServerConfigSchema = z
487
561
  })
488
562
  .strict()
489
563
 
564
+ /** A folder inside the plugin: relative, no `..` step, no drive or root. */
565
+ const PluginRelativeDirSchema = z
566
+ .string()
567
+ .min(1)
568
+ .refine(
569
+ (value) =>
570
+ !/^([a-zA-Z]:|[\\/])/.test(value) && !value.split(/[\\/]/).some((part) => part === '..'),
571
+ 'must be a folder inside the plugin, with no ".." and no absolute path',
572
+ )
573
+
490
574
  export const PluginManifestSchema = z.object({
491
575
  name: z
492
576
  .string()
@@ -503,6 +587,8 @@ export const PluginManifestSchema = z.object({
503
587
  mcpServers: z.array(PluginMCPServerConfigSchema).max(MAX_MCP_SERVERS_PER_PLUGIN).optional(),
504
588
  connectors: z.array(z.string()).max(MAX_CONNECTORS_PER_PLUGIN).optional(),
505
589
  personas: z.array(z.string()).max(MAX_PERSONAS_PER_PLUGIN).optional(),
590
+ commands: z.array(PluginRelativeDirSchema).max(MAX_COMMAND_DIRS_PER_PLUGIN).optional(),
591
+ agents: z.array(PluginRelativeDirSchema).max(MAX_AGENT_DIRS_PER_PLUGIN).optional(),
506
592
  })
507
593
 
508
594
  // ---------------------------------------------------------------------------
@@ -100,8 +100,12 @@ export interface SubSession {
100
100
  failureMode: FailureMode
101
101
  completionMode: CompletionMode
102
102
  workspaceId: WorkspaceId | null
103
- /** Keep this delegated workspace when the child ends or its session is archived. */
104
- workspaceRetention?: 'retain'
103
+ /**
104
+ * Keep this delegated workspace when the child ends or its session is
105
+ * archived. `retain-changed` keeps it only if the child changed something;
106
+ * a workspace that is still there is therefore one worth keeping.
107
+ */
108
+ workspaceRetention?: 'retain' | 'retain-changed'
105
109
  /**
106
110
  * For interventions, the immutable artifact being addressed. Chains form
107
111
  * a strict acyclic DAG — see session-hierarchy.md §4.5.
@@ -1,3 +1,5 @@
1
+ import type { ReasoningEffort } from '../provider/chat.js'
2
+
1
3
  /**
2
4
  * Who may reach for a skill.
3
5
  *
@@ -37,6 +39,52 @@ export interface SkillMetadata {
37
39
  invocation?: SkillInvocation
38
40
 
39
41
  metadata?: Record<string, string>
42
+
43
+ /**
44
+ * When the model should reach for the skill, in the author's words.
45
+ * Shown to the model next to the description (`when_to_use`); see
46
+ * {@link skillModelDescription}.
47
+ */
48
+ whenToUse?: string
49
+
50
+ /**
51
+ * Where the skill runs. `fork` hands its instructions to a delegated agent
52
+ * and returns that agent's report; absent or `inline` loads them into the
53
+ * current conversation.
54
+ */
55
+ context?: 'inline' | 'fork'
56
+
57
+ /** The delegated agent type a `fork` skill runs as (`agent`). */
58
+ agent?: string
59
+
60
+ /**
61
+ * The model a `fork` skill's delegated agent runs on (`model`). An inline
62
+ * skill runs on the turn's own model; this does not switch it.
63
+ */
64
+ model?: string
65
+
66
+ /**
67
+ * The reasoning effort requested for a `fork` skill's delegated agent
68
+ * (`effort`), even without `model`. The host validates the combination;
69
+ * this does not switch the current turn's effort for an inline skill.
70
+ */
71
+ effort?: ReasoningEffort
72
+
73
+ /** Whether a `fork` skill's agent runs in the background (`background`). */
74
+ background?: boolean
75
+ }
76
+
77
+ /**
78
+ * What the model reads as a skill's description: the description, then the
79
+ * author's `when_to_use` guidance when there is one.
80
+ */
81
+ export function skillModelDescription(metadata: {
82
+ description: string
83
+ whenToUse?: string
84
+ }): string {
85
+ const description = metadata.description.trim()
86
+ const when = metadata.whenToUse?.trim()
87
+ return when ? `${description} - ${when}` : description
40
88
  }
41
89
 
42
90
  /** What a skill that says nothing means. */