@namzu/sdk 32.0.0 → 33.0.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 (260) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/dist/authorization/gate.d.ts +1 -1
  3. package/dist/authorization/gate.js +1 -1
  4. package/dist/connector/index.d.ts +1 -1
  5. package/dist/connector/index.d.ts.map +1 -1
  6. package/dist/connector/index.js +1 -1
  7. package/dist/connector/index.js.map +1 -1
  8. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  9. package/dist/connector/mcp/adapter.js +71 -16
  10. package/dist/connector/mcp/adapter.js.map +1 -1
  11. package/dist/connector/mcp/client.d.ts.map +1 -1
  12. package/dist/connector/mcp/client.js +3 -2
  13. package/dist/connector/mcp/client.js.map +1 -1
  14. package/dist/connector/mcp/http-redirect.d.ts +17 -0
  15. package/dist/connector/mcp/http-redirect.d.ts.map +1 -0
  16. package/dist/connector/mcp/http-redirect.js +31 -0
  17. package/dist/connector/mcp/http-redirect.js.map +1 -0
  18. package/dist/connector/mcp/http-sse.d.ts.map +1 -1
  19. package/dist/connector/mcp/http-sse.js +5 -0
  20. package/dist/connector/mcp/http-sse.js.map +1 -1
  21. package/dist/connector/mcp/image-admission.d.ts +16 -0
  22. package/dist/connector/mcp/image-admission.d.ts.map +1 -0
  23. package/dist/connector/mcp/image-admission.js +288 -0
  24. package/dist/connector/mcp/image-admission.js.map +1 -0
  25. package/dist/connector/mcp/stdio.d.ts +13 -0
  26. package/dist/connector/mcp/stdio.d.ts.map +1 -1
  27. package/dist/connector/mcp/stdio.js +66 -23
  28. package/dist/connector/mcp/stdio.js.map +1 -1
  29. package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
  30. package/dist/connector/mcp/streamable-http.js +3 -0
  31. package/dist/connector/mcp/streamable-http.js.map +1 -1
  32. package/dist/constants/tools/index.js +1 -1
  33. package/dist/constants/tools/index.js.map +1 -1
  34. package/dist/execution/base.d.ts +13 -0
  35. package/dist/execution/base.d.ts.map +1 -1
  36. package/dist/execution/base.js +103 -8
  37. package/dist/execution/base.js.map +1 -1
  38. package/dist/execution/errors.d.ts +12 -0
  39. package/dist/execution/errors.d.ts.map +1 -0
  40. package/dist/execution/errors.js +21 -0
  41. package/dist/execution/errors.js.map +1 -0
  42. package/dist/execution/factory.d.ts.map +1 -1
  43. package/dist/execution/factory.js +1 -0
  44. package/dist/execution/factory.js.map +1 -1
  45. package/dist/execution/hybrid.d.ts +5 -0
  46. package/dist/execution/hybrid.d.ts.map +1 -1
  47. package/dist/execution/hybrid.js +36 -16
  48. package/dist/execution/hybrid.js.map +1 -1
  49. package/dist/execution/index.d.ts +1 -0
  50. package/dist/execution/index.d.ts.map +1 -1
  51. package/dist/execution/index.js +1 -0
  52. package/dist/execution/index.js.map +1 -1
  53. package/dist/execution/local.d.ts +13 -0
  54. package/dist/execution/local.d.ts.map +1 -1
  55. package/dist/execution/local.js +145 -19
  56. package/dist/execution/local.js.map +1 -1
  57. package/dist/execution/output.d.ts +27 -0
  58. package/dist/execution/output.d.ts.map +1 -0
  59. package/dist/execution/output.js +105 -0
  60. package/dist/execution/output.js.map +1 -0
  61. package/dist/execution/remote.d.ts +26 -2
  62. package/dist/execution/remote.d.ts.map +1 -1
  63. package/dist/execution/remote.js +86 -15
  64. package/dist/execution/remote.js.map +1 -1
  65. package/dist/plugin/lifecycle.d.ts.map +1 -1
  66. package/dist/plugin/lifecycle.js +9 -3
  67. package/dist/plugin/lifecycle.js.map +1 -1
  68. package/dist/process/kill-tree.d.ts +11 -13
  69. package/dist/process/kill-tree.d.ts.map +1 -1
  70. package/dist/process/kill-tree.js +11 -13
  71. package/dist/process/kill-tree.js.map +1 -1
  72. package/dist/provider/fallback.d.ts.map +1 -1
  73. package/dist/provider/fallback.js +31 -0
  74. package/dist/provider/fallback.js.map +1 -1
  75. package/dist/provider/idle-timeout.d.ts.map +1 -1
  76. package/dist/provider/idle-timeout.js +5 -0
  77. package/dist/provider/idle-timeout.js.map +1 -1
  78. package/dist/provider/retry.d.ts.map +1 -1
  79. package/dist/provider/retry.js +5 -0
  80. package/dist/provider/retry.js.map +1 -1
  81. package/dist/public-runtime.d.ts +2 -2
  82. package/dist/public-runtime.d.ts.map +1 -1
  83. package/dist/public-runtime.js +2 -2
  84. package/dist/public-runtime.js.map +1 -1
  85. package/dist/registry/tool/execute.d.ts +7 -1
  86. package/dist/registry/tool/execute.d.ts.map +1 -1
  87. package/dist/registry/tool/execute.js +201 -54
  88. package/dist/registry/tool/execute.js.map +1 -1
  89. package/dist/run/command-gate.d.ts.map +1 -1
  90. package/dist/run/command-gate.js +11 -0
  91. package/dist/run/command-gate.js.map +1 -1
  92. package/dist/run/workspace-fingerprint.d.ts +4 -4
  93. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  94. package/dist/run/workspace-fingerprint.js +6 -4
  95. package/dist/run/workspace-fingerprint.js.map +1 -1
  96. package/dist/runtime/bidi/index.d.ts +1 -1
  97. package/dist/runtime/bidi/index.d.ts.map +1 -1
  98. package/dist/runtime/bidi/index.js +1 -1
  99. package/dist/runtime/bidi/index.js.map +1 -1
  100. package/dist/runtime/bidi/session.d.ts +19 -0
  101. package/dist/runtime/bidi/session.d.ts.map +1 -1
  102. package/dist/runtime/bidi/session.js +150 -26
  103. package/dist/runtime/bidi/session.js.map +1 -1
  104. package/dist/runtime/query/executor.d.ts +43 -1
  105. package/dist/runtime/query/executor.d.ts.map +1 -1
  106. package/dist/runtime/query/executor.js +626 -161
  107. package/dist/runtime/query/executor.js.map +1 -1
  108. package/dist/runtime/query/index.d.ts.map +1 -1
  109. package/dist/runtime/query/index.js +46 -4
  110. package/dist/runtime/query/index.js.map +1 -1
  111. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  112. package/dist/runtime/query/iteration/phases/tool-review.js +84 -22
  113. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  114. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  115. package/dist/runtime/query/request-rich-content.js +33 -13
  116. package/dist/runtime/query/request-rich-content.js.map +1 -1
  117. package/dist/runtime/query/resume-pending.d.ts +5 -1
  118. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  119. package/dist/runtime/query/resume-pending.js +66 -1
  120. package/dist/runtime/query/resume-pending.js.map +1 -1
  121. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  122. package/dist/runtime/query/tool-output-budget.js +45 -16
  123. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  124. package/dist/runtime/query/tooling.d.ts +6 -0
  125. package/dist/runtime/query/tooling.d.ts.map +1 -1
  126. package/dist/runtime/query/tooling.js +4 -0
  127. package/dist/runtime/query/tooling.js.map +1 -1
  128. package/dist/sandbox/provider/local.d.ts.map +1 -1
  129. package/dist/sandbox/provider/local.js +193 -41
  130. package/dist/sandbox/provider/local.js.map +1 -1
  131. package/dist/scheduler/delegating.d.ts +2 -1
  132. package/dist/scheduler/delegating.d.ts.map +1 -1
  133. package/dist/scheduler/delegating.js +6 -5
  134. package/dist/scheduler/delegating.js.map +1 -1
  135. package/dist/scheduler/local.d.ts +2 -1
  136. package/dist/scheduler/local.d.ts.map +1 -1
  137. package/dist/scheduler/local.js +2 -2
  138. package/dist/scheduler/local.js.map +1 -1
  139. package/dist/session/workspace/driver.d.ts +12 -4
  140. package/dist/session/workspace/driver.d.ts.map +1 -1
  141. package/dist/session/workspace/git-worktree.d.ts +17 -3
  142. package/dist/session/workspace/git-worktree.d.ts.map +1 -1
  143. package/dist/session/workspace/git-worktree.js +153 -34
  144. package/dist/session/workspace/git-worktree.js.map +1 -1
  145. package/dist/skills/registry.d.ts +15 -0
  146. package/dist/skills/registry.d.ts.map +1 -1
  147. package/dist/skills/registry.js +33 -0
  148. package/dist/skills/registry.js.map +1 -1
  149. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  150. package/dist/tools/builtins/computer-use.js +39 -2
  151. package/dist/tools/builtins/computer-use.js.map +1 -1
  152. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  153. package/dist/tools/builtins/run-code.js +9 -11
  154. package/dist/tools/builtins/run-code.js.map +1 -1
  155. package/dist/tools/builtins/skill.d.ts +2 -1
  156. package/dist/tools/builtins/skill.d.ts.map +1 -1
  157. package/dist/tools/builtins/skill.js +250 -6
  158. package/dist/tools/builtins/skill.js.map +1 -1
  159. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  160. package/dist/tools/coordinator/agent.js +100 -30
  161. package/dist/tools/coordinator/agent.js.map +1 -1
  162. package/dist/types/agent/manager.d.ts +1 -1
  163. package/dist/types/agent/manager.d.ts.map +1 -1
  164. package/dist/types/agent/scheduler.d.ts +10 -1
  165. package/dist/types/agent/scheduler.d.ts.map +1 -1
  166. package/dist/types/authorization/index.d.ts +12 -12
  167. package/dist/types/bidi/index.d.ts +19 -2
  168. package/dist/types/bidi/index.d.ts.map +1 -1
  169. package/dist/types/computer-use/index.d.ts +18 -0
  170. package/dist/types/computer-use/index.d.ts.map +1 -1
  171. package/dist/types/connector/execution-context.d.ts +3 -1
  172. package/dist/types/connector/execution-context.d.ts.map +1 -1
  173. package/dist/types/execution/index.d.ts +43 -1
  174. package/dist/types/execution/index.d.ts.map +1 -1
  175. package/dist/types/hitl/index.d.ts +11 -0
  176. package/dist/types/hitl/index.d.ts.map +1 -1
  177. package/dist/types/hitl/index.js.map +1 -1
  178. package/dist/types/message/index.d.ts +1 -1
  179. package/dist/types/message/index.d.ts.map +1 -1
  180. package/dist/types/message/index.js +2 -1
  181. package/dist/types/message/index.js.map +1 -1
  182. package/dist/types/plugin/index.d.ts +14 -1
  183. package/dist/types/plugin/index.d.ts.map +1 -1
  184. package/dist/types/plugin/index.js +1 -0
  185. package/dist/types/plugin/index.js.map +1 -1
  186. package/dist/types/provider/interface.d.ts +14 -0
  187. package/dist/types/provider/interface.d.ts.map +1 -1
  188. package/dist/types/sandbox/index.d.ts +15 -10
  189. package/dist/types/sandbox/index.d.ts.map +1 -1
  190. package/dist/types/sandbox/index.js.map +1 -1
  191. package/dist/types/tool/index.d.ts +77 -6
  192. package/dist/types/tool/index.d.ts.map +1 -1
  193. package/dist/types/tool/index.js.map +1 -1
  194. package/dist/types/workspace/ref.d.ts +5 -0
  195. package/dist/types/workspace/ref.d.ts.map +1 -1
  196. package/dist/utils/abort.d.ts +8 -0
  197. package/dist/utils/abort.d.ts.map +1 -1
  198. package/dist/utils/abort.js +16 -0
  199. package/dist/utils/abort.js.map +1 -1
  200. package/package.json +4 -4
  201. package/src/authorization/gate.ts +1 -1
  202. package/src/connector/index.ts +2 -0
  203. package/src/connector/mcp/adapter.ts +82 -21
  204. package/src/connector/mcp/client.ts +2 -1
  205. package/src/connector/mcp/http-redirect.ts +32 -0
  206. package/src/connector/mcp/http-sse.ts +5 -0
  207. package/src/connector/mcp/image-admission.ts +272 -0
  208. package/src/connector/mcp/stdio.ts +63 -21
  209. package/src/connector/mcp/streamable-http.ts +3 -0
  210. package/src/constants/tools/index.ts +1 -1
  211. package/src/execution/base.ts +124 -7
  212. package/src/execution/errors.ts +24 -0
  213. package/src/execution/factory.ts +1 -0
  214. package/src/execution/hybrid.ts +47 -16
  215. package/src/execution/index.ts +1 -0
  216. package/src/execution/local.ts +164 -19
  217. package/src/execution/output.ts +104 -0
  218. package/src/execution/remote.ts +122 -17
  219. package/src/plugin/lifecycle.ts +9 -3
  220. package/src/process/kill-tree.ts +11 -13
  221. package/src/provider/fallback.ts +39 -0
  222. package/src/provider/idle-timeout.ts +8 -0
  223. package/src/provider/retry.ts +8 -0
  224. package/src/public-runtime.ts +7 -1
  225. package/src/registry/tool/execute.ts +230 -64
  226. package/src/run/command-gate.ts +12 -0
  227. package/src/run/workspace-fingerprint.ts +5 -4
  228. package/src/runtime/bidi/index.ts +1 -1
  229. package/src/runtime/bidi/session.ts +177 -28
  230. package/src/runtime/query/executor.ts +769 -163
  231. package/src/runtime/query/index.ts +53 -6
  232. package/src/runtime/query/iteration/phases/tool-review.ts +87 -23
  233. package/src/runtime/query/request-rich-content.ts +39 -14
  234. package/src/runtime/query/resume-pending.ts +83 -1
  235. package/src/runtime/query/tool-output-budget.ts +52 -17
  236. package/src/runtime/query/tooling.ts +10 -0
  237. package/src/sandbox/provider/local.ts +213 -45
  238. package/src/scheduler/delegating.ts +6 -5
  239. package/src/scheduler/local.ts +3 -2
  240. package/src/session/workspace/driver.ts +12 -4
  241. package/src/session/workspace/git-worktree.ts +173 -34
  242. package/src/skills/registry.ts +47 -0
  243. package/src/tools/builtins/computer-use.ts +45 -2
  244. package/src/tools/builtins/run-code.ts +9 -11
  245. package/src/tools/builtins/skill.ts +325 -7
  246. package/src/tools/coordinator/agent.ts +108 -33
  247. package/src/types/agent/manager.ts +1 -1
  248. package/src/types/agent/scheduler.ts +10 -1
  249. package/src/types/bidi/index.ts +19 -2
  250. package/src/types/computer-use/index.ts +19 -0
  251. package/src/types/connector/execution-context.ts +3 -0
  252. package/src/types/execution/index.ts +46 -1
  253. package/src/types/hitl/index.ts +11 -0
  254. package/src/types/message/index.ts +3 -2
  255. package/src/types/plugin/index.ts +15 -0
  256. package/src/types/provider/interface.ts +18 -0
  257. package/src/types/sandbox/index.ts +15 -10
  258. package/src/types/tool/index.ts +85 -6
  259. package/src/types/workspace/ref.ts +5 -0
  260. package/src/utils/abort.ts +18 -0
@@ -7,10 +7,12 @@ import { isTrustedReadOnly } from '../../tools/trusted-read-only.js'
7
7
  import type { ToolResultGuardrailSpec } from '../../types/guardrail/index.js'
8
8
  import type {
9
9
  LLMToolSchema,
10
+ PreparedToolExecution,
10
11
  ToolAvailability,
11
12
  ToolContext,
12
13
  ToolDefinition,
13
14
  ToolExecutionResult,
15
+ ToolPreparationResult,
14
16
  ToolRegistryConfig,
15
17
  ToolTierConfig,
16
18
  } from '../../types/tool/index.js'
@@ -87,6 +89,82 @@ export function assertToolName(name: string): void {
87
89
  )
88
90
  }
89
91
 
92
+ function clonePreparedInput(value: unknown, freeze: boolean, path = '$'): unknown {
93
+ const active = new WeakSet<object>()
94
+ const copies = new WeakMap<object, object>()
95
+
96
+ const clone = (candidate: unknown, at: string): unknown => {
97
+ if (candidate === null || typeof candidate === 'string' || typeof candidate === 'boolean') {
98
+ return candidate
99
+ }
100
+ if (typeof candidate === 'number') {
101
+ if (!Number.isFinite(candidate) || Object.is(candidate, -0)) {
102
+ throw new TypeError(`${at} must be a finite JSON number`)
103
+ }
104
+ return candidate
105
+ }
106
+ if (typeof candidate !== 'object') {
107
+ throw new TypeError(`${at} must be a JSON value, not ${typeof candidate}`)
108
+ }
109
+ if (active.has(candidate)) throw new TypeError(`${at} contains a cycle`)
110
+ const existing = copies.get(candidate)
111
+ if (existing) return existing
112
+
113
+ active.add(candidate)
114
+ try {
115
+ if (Array.isArray(candidate)) {
116
+ const extra = Reflect.ownKeys(candidate).filter(
117
+ (key) => key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key)),
118
+ )
119
+ if (extra.length > 0) throw new TypeError(`${at} has non-JSON array properties`)
120
+ const result: unknown[] = new Array(candidate.length)
121
+ copies.set(candidate, result)
122
+ for (let index = 0; index < candidate.length; index++) {
123
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index))
124
+ if (!descriptor) throw new TypeError(`${at}[${index}] is a sparse array hole`)
125
+ if (!descriptor.enumerable || !('value' in descriptor)) {
126
+ throw new TypeError(`${at}[${index}] must be an enumerable data property`)
127
+ }
128
+ result[index] = clone(descriptor.value, `${at}[${index}]`)
129
+ }
130
+ if (freeze) Object.freeze(result)
131
+ return result
132
+ }
133
+
134
+ const prototype = Object.getPrototypeOf(candidate)
135
+ if (prototype !== Object.prototype && prototype !== null) {
136
+ throw new TypeError(`${at} must be a plain JSON object`)
137
+ }
138
+ // JSON does not preserve prototypes. Canonicalize both admitted plain
139
+ // shapes to an ordinary object so a disk checkpoint round-trip compares
140
+ // equal to a fresh preparation of the same semantic value. Properties
141
+ // are still defined explicitly, so a literal "__proto__" key remains
142
+ // data rather than changing this object's prototype.
143
+ const result: Record<string, unknown> = {}
144
+ copies.set(candidate, result)
145
+ for (const key of Reflect.ownKeys(candidate)) {
146
+ if (typeof key !== 'string') throw new TypeError(`${at} has a symbol property`)
147
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key)
148
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
149
+ throw new TypeError(`${at}.${key} must be an enumerable data property`)
150
+ }
151
+ Object.defineProperty(result, key, {
152
+ value: clone(descriptor.value, `${at}.${key}`),
153
+ enumerable: true,
154
+ writable: !freeze,
155
+ configurable: !freeze,
156
+ })
157
+ }
158
+ if (freeze) Object.freeze(result)
159
+ return result
160
+ } finally {
161
+ active.delete(candidate)
162
+ }
163
+ }
164
+
165
+ return clone(value, path)
166
+ }
167
+
90
168
  /**
91
169
  * Two sources contributed the same tool name and neither may take it.
92
170
  *
@@ -136,9 +214,17 @@ export class ToolRegistry extends ManagedRegistry<ToolDefinition> {
136
214
  private availability: Map<string, ToolAvailability> = new Map()
137
215
  private tierConfig?: ToolTierConfig
138
216
  private resultGuardrails?: readonly ToolResultGuardrailSpec[]
217
+ private readonly preparations = new WeakMap<
218
+ PreparedToolExecution,
219
+ { readonly tool: ToolDefinition; readonly input: unknown }
220
+ >()
139
221
 
140
222
  constructor(config?: ToolRegistryConfig) {
141
- super({ componentName: 'ToolRegistry', idField: 'name', logger: config?.logger })
223
+ super({
224
+ componentName: 'ToolRegistry',
225
+ idField: 'name',
226
+ logger: config?.logger,
227
+ })
142
228
  this.tierConfig = config?.tierConfig
143
229
  this.resultGuardrails = config?.resultGuardrails
144
230
  }
@@ -401,10 +487,106 @@ Executable tool names, descriptions, and JSON input schemas are attached through
401
487
  return this.getByAvailability(['active'], toolNames)
402
488
  }
403
489
 
490
+ prepareExecution(toolName: string, rawInput: unknown): ToolPreparationResult {
491
+ const tool = this.getOrThrow(toolName)
492
+ const parseResult = tool.inputSchema.safeParse(rawInput)
493
+ if (!parseResult.success) {
494
+ return {
495
+ success: false,
496
+ result: this.validationFailure(tool, rawInput, parseResult.error),
497
+ }
498
+ }
499
+
500
+ let retainedInput: unknown
501
+ let reviewInput: unknown
502
+ try {
503
+ retainedInput = clonePreparedInput(parseResult.data, false)
504
+ reviewInput = clonePreparedInput(retainedInput, true)
505
+ } catch (err) {
506
+ const message = `Tool "${toolName}" produced an input that cannot be safely prepared for review and execution: ${toErrorMessage(err)}`
507
+ this.log.error('Prepared tool input could not be detached for review and execution', {
508
+ 'namzu.tool.name': toolName,
509
+ 'exception.message': toErrorMessage(err),
510
+ })
511
+ return {
512
+ success: false,
513
+ result: { success: false, output: '', error: message },
514
+ }
515
+ }
516
+
517
+ const prepared = Object.freeze({ toolName, input: reviewInput })
518
+ this.preparations.set(prepared, { tool, input: retainedInput })
519
+ return { success: true, prepared }
520
+ }
521
+
522
+ async executePrepared(
523
+ prepared: PreparedToolExecution,
524
+ context: ToolContext,
525
+ ): Promise<ToolExecutionResult> {
526
+ const retained = this.preparations.get(prepared)
527
+ if (!retained) {
528
+ return {
529
+ success: false,
530
+ output: '',
531
+ error: `Tool "${prepared.toolName}" preparation is not owned by this registry or is no longer valid.`,
532
+ }
533
+ }
534
+ if (this.get(prepared.toolName) !== retained.tool) {
535
+ return {
536
+ success: false,
537
+ output: '',
538
+ error: `Tool "${prepared.toolName}" changed after its input was reviewed; prepare the call again.`,
539
+ }
540
+ }
541
+ return this.executeRetained(prepared.toolName, retained.tool, retained.input, context)
542
+ }
543
+
404
544
  async execute(
405
545
  toolName: string,
406
546
  rawInput: unknown,
407
547
  context: ToolContext,
548
+ ): Promise<ToolExecutionResult> {
549
+ let preparation: ToolPreparationResult
550
+ try {
551
+ preparation = this.prepareExecution(toolName, rawInput)
552
+ } catch (err) {
553
+ return this.rejectPreparationWithClosedSpan(toolName, context, err)
554
+ }
555
+ if (!preparation.success) return preparation.result
556
+ return this.executePrepared(preparation.prepared, context)
557
+ }
558
+
559
+ private rejectPreparationWithClosedSpan(
560
+ toolName: string,
561
+ context: ToolContext,
562
+ err: unknown,
563
+ ): Promise<never> {
564
+ const tracer = getTracer()
565
+ const parentCtx = context.parentSpan
566
+ ? trace.setSpan(otelContext.active(), context.parentSpan)
567
+ : otelContext.active()
568
+ return tracer.startActiveSpan(toolSpanName(toolName), {}, parentCtx, async (span) => {
569
+ try {
570
+ span.setAttributes({
571
+ [GENAI.TOOL_NAME]: toolName,
572
+ [GENAI.TOOL_TYPE]: 'function',
573
+ ...(context.toolUseId !== undefined ? { [GENAI.TOOL_CALL_ID]: context.toolUseId } : {}),
574
+ [NAMZU.TOOL_SUCCESS]: false,
575
+ [NAMZU.TOOL_ERROR]: toErrorMessage(err),
576
+ })
577
+ span.setStatus({ code: SpanStatusCode.ERROR, message: toErrorMessage(err) })
578
+ throw err
579
+ } finally {
580
+ span.end()
581
+ }
582
+ })
583
+ }
584
+
585
+ private async executeRetained(
586
+ toolName: string,
587
+ tool: ToolDefinition,
588
+ finalInput: unknown,
589
+ context: ToolContext,
408
590
  ): Promise<ToolExecutionResult> {
409
591
  const tracer = getTracer()
410
592
 
@@ -437,8 +619,6 @@ Executable tool names, descriptions, and JSON input schemas are attached through
437
619
  ...(context.toolUseId !== undefined ? { [GENAI.TOOL_CALL_ID]: context.toolUseId } : {}),
438
620
  })
439
621
 
440
- const tool = this.getOrThrow(toolName)
441
-
442
622
  const availability = this.getAvailability(toolName)
443
623
  if (availability !== 'active') {
444
624
  const msg = `Tool "${toolName}" is ${availability} and cannot be executed`
@@ -492,7 +672,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
492
672
 
493
673
  const mode = context.permissionContext?.mode ?? 'auto'
494
674
  if (mode === 'plan') {
495
- const isReadOnly = isTrustedReadOnly(tool, rawInput)
675
+ const isReadOnly = isTrustedReadOnly(tool, finalInput)
496
676
  if (!isReadOnly) {
497
677
  const msg = `plan mode: non-read-only tool "${toolName}" blocked`
498
678
  span.setAttributes({
@@ -510,63 +690,6 @@ Executable tool names, descriptions, and JSON input schemas are attached through
510
690
  }
511
691
  }
512
692
 
513
- const parseResult = tool.inputSchema.safeParse(rawInput)
514
- if (!parseResult.success) {
515
- const errorMessage = parseResult.error.issues
516
- .map((i) => `${i.path.join('.')}: ${i.message}`)
517
- .join('; ')
518
-
519
- // Distinguish "model sent an empty/no-arg call" from
520
- // "model sent partial args" — the first is most often a
521
- // streaming hiccup or a definition-test ping (a provider
522
- // occasionally pings tool surfaces with `{}` while the
523
- // schema is still loading), the second is a genuine
524
- // programming mistake by the model. The model self-
525
- // corrects MUCH more reliably when the error tells it
526
- // (a) which fields are required, (b) their types, and
527
- // (c) a minimal example call. Without these hints the
528
- // downstream UI just shows a red "Failed" row and the
529
- // model rarely retries with the right args.
530
- const isEmptyInput =
531
- rawInput === null ||
532
- rawInput === undefined ||
533
- (typeof rawInput === 'object' &&
534
- !Array.isArray(rawInput) &&
535
- Object.keys(rawInput as Record<string, unknown>).length === 0)
536
-
537
- const requiredHint = describeRequiredInput(tool.inputSchema)
538
- // A conditional schema's required shape cannot be reconstructed
539
- // from JSON Schema's top-level `required`, so the author gets to
540
- // say what a valid retry looks like.
541
- const recoveryHint = tool.validationErrorHint?.trim()
542
- ? ` ${tool.validationErrorHint.trim()}`
543
- : ''
544
-
545
- const enrichedMessage = isEmptyInput
546
- ? `Tool "${toolName}" was called with no arguments. ${requiredHint}${recoveryHint} Retry the call with the required parameters populated.`
547
- : `Validation failed for "${toolName}": ${errorMessage}. ${requiredHint}${recoveryHint}`
548
-
549
- this.log.error('Tool input validation failed', {
550
- 'namzu.tool.name': toolName,
551
- 'namzu.registry.errors': errorMessage,
552
- 'namzu.registry.empty': isEmptyInput,
553
- })
554
-
555
- span.setAttributes({
556
- [NAMZU.TOOL_SUCCESS]: false,
557
- [NAMZU.TOOL_ERROR]: `Validation: ${errorMessage}`,
558
- })
559
- span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage })
560
-
561
- return {
562
- success: false,
563
- output: '',
564
- error: enrichedMessage,
565
- }
566
- }
567
-
568
- const finalInput = parseResult.data
569
-
570
693
  try {
571
694
  this.log.debug('Executing tool', { 'namzu.tool.name': toolName })
572
695
  const startedAt = Date.now()
@@ -604,7 +727,10 @@ Executable tool names, descriptions, and JSON input schemas are attached through
604
727
  )
605
728
  if (!result.success && result.error) {
606
729
  span.setAttribute(NAMZU.TOOL_ERROR, result.error)
607
- span.setStatus({ code: SpanStatusCode.ERROR, message: result.error })
730
+ span.setStatus({
731
+ code: SpanStatusCode.ERROR,
732
+ message: result.error,
733
+ })
608
734
  } else {
609
735
  span.setStatus({ code: SpanStatusCode.OK })
610
736
  }
@@ -622,7 +748,10 @@ Executable tool names, descriptions, and JSON input schemas are attached through
622
748
  [NAMZU.TOOL_SUCCESS]: false,
623
749
  [NAMZU.TOOL_ERROR]: err.message,
624
750
  })
625
- span.setStatus({ code: SpanStatusCode.ERROR, message: err.message })
751
+ span.setStatus({
752
+ code: SpanStatusCode.ERROR,
753
+ message: err.message,
754
+ })
626
755
  throw err
627
756
  }
628
757
  const errorMessage = toErrorMessage(err)
@@ -635,7 +764,10 @@ Executable tool names, descriptions, and JSON input schemas are attached through
635
764
  [NAMZU.TOOL_SUCCESS]: false,
636
765
  [NAMZU.TOOL_ERROR]: errorMessage,
637
766
  })
638
- span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage })
767
+ span.setStatus({
768
+ code: SpanStatusCode.ERROR,
769
+ message: errorMessage,
770
+ })
639
771
  span.recordException(err instanceof Error ? err : new Error(errorMessage))
640
772
 
641
773
  return {
@@ -655,6 +787,40 @@ Executable tool names, descriptions, and JSON input schemas are attached through
655
787
  })
656
788
  }
657
789
 
790
+ private validationFailure(
791
+ tool: ToolDefinition,
792
+ rawInput: unknown,
793
+ error: {
794
+ readonly issues: readonly {
795
+ readonly path: readonly PropertyKey[]
796
+ readonly message: string
797
+ }[]
798
+ },
799
+ ): ToolExecutionResult {
800
+ const errorMessage = error.issues
801
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
802
+ .join('; ')
803
+ const isEmptyInput =
804
+ rawInput === null ||
805
+ rawInput === undefined ||
806
+ (typeof rawInput === 'object' &&
807
+ !Array.isArray(rawInput) &&
808
+ Object.keys(rawInput as Record<string, unknown>).length === 0)
809
+ const requiredHint = describeRequiredInput(tool.inputSchema)
810
+ const recoveryHint = tool.validationErrorHint?.trim()
811
+ ? ` ${tool.validationErrorHint.trim()}`
812
+ : ''
813
+ const enrichedMessage = isEmptyInput
814
+ ? `Tool "${tool.name}" was called with no arguments. ${requiredHint}${recoveryHint} Retry the call with the required parameters populated.`
815
+ : `Validation failed for "${tool.name}": ${errorMessage}. ${requiredHint}${recoveryHint}`
816
+ this.log.error('Tool input validation failed', {
817
+ 'namzu.tool.name': tool.name,
818
+ 'namzu.registry.errors': errorMessage,
819
+ 'namzu.registry.empty': isEmptyInput,
820
+ })
821
+ return { success: false, output: '', error: enrichedMessage }
822
+ }
823
+
658
824
  private getByAvailability(states: ToolAvailability[], filter?: string[]): ToolDefinition[] {
659
825
  const candidates = filter ? filter.map((n) => this.getOrThrow(n)) : this.getAll()
660
826
  return candidates.filter((t) => states.includes(this.getAvailability(t.name)))
@@ -130,6 +130,17 @@ function failureFeedback(
130
130
  maxOutputChars: number,
131
131
  ): string {
132
132
  const output = clipOutput(`${result.stdout}\n${result.stderr}`, maxOutputChars)
133
+ const truncatedStreams = [
134
+ result.stdoutTruncated === true ? 'stdout' : undefined,
135
+ result.stderrTruncated === true ? 'stderr' : undefined,
136
+ ].filter((stream): stream is string => stream !== undefined)
137
+ const truncationWarning =
138
+ truncatedStreams.length === 0
139
+ ? []
140
+ : [
141
+ '',
142
+ `Retained output is incomplete: ${truncatedStreams.join(' and ')} ${truncatedStreams.length === 1 ? 'was' : 'were'} truncated by the execution context. Re-run with a narrower command or filter to recover the missing diagnostics.`,
143
+ ]
133
144
  return [
134
145
  `The answer was not accepted: \`${command}\` failed (attempt ${attempt}, exit ${result.exitCode}).`,
135
146
  '',
@@ -137,6 +148,7 @@ function failureFeedback(
137
148
  '```',
138
149
  output || '(no output)',
139
150
  '```',
151
+ ...truncationWarning,
140
152
  '',
141
153
  'Fix the cause and then finish. Do not restate the failure back to me; change the code so the command passes.',
142
154
  ].join('\n')
@@ -41,10 +41,10 @@
41
41
  * told to edit something it had already edited.
42
42
  *
43
43
  * So: a non-zero exit from any git invocation, a repository with no commits,
44
- * a timeout, or output past the size cap all produce `null` rather than a
45
- * partial hash. A truncated diff that hashed successfully would be the worst
46
- * outcome available here, because two different workspaces truncated at the
47
- * same point collide.
44
+ * a timeout, an executor-reported truncated stream, or output past the size
45
+ * cap all produce `null` rather than a partial hash. A truncated diff that
46
+ * hashed successfully would be the worst outcome available here, because two
47
+ * different workspaces truncated at the same point collide.
48
48
  */
49
49
 
50
50
  import { createHash } from 'node:crypto'
@@ -156,6 +156,7 @@ export async function fingerprintWorkspace(
156
156
  // repository" and "no commits yet". All three mean the same thing to
157
157
  // this function: it has no basis for a comparison.
158
158
  if (result.exitCode !== 0) return null
159
+ if (result.stdoutTruncated === true || result.stderrTruncated === true) return null
159
160
  if (Buffer.byteLength(result.stdout, 'utf8') > maxBytes) return null
160
161
  return result.stdout
161
162
  }
@@ -1,4 +1,4 @@
1
1
  export { createMockBidiProvider } from './mock.js'
2
2
  export type { MockBidiScript, MockBidiSession } from './mock.js'
3
- export { startBidiRun } from './session.js'
3
+ export { BidiSessionCloseTimeoutError, startBidiRun } from './session.js'
4
4
  export type { BidiRun, BidiRunParams } from './session.js'