@namzu/sdk 26.0.0 → 26.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 (33) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/connector/mcp/index.d.ts +1 -0
  3. package/dist/connector/mcp/index.d.ts.map +1 -1
  4. package/dist/connector/mcp/index.js +3 -0
  5. package/dist/connector/mcp/index.js.map +1 -1
  6. package/dist/connector/mcp/server-stdio.d.ts +62 -0
  7. package/dist/connector/mcp/server-stdio.d.ts.map +1 -0
  8. package/dist/connector/mcp/server-stdio.js +121 -0
  9. package/dist/connector/mcp/server-stdio.js.map +1 -0
  10. package/dist/session/workspace/git-worktree.d.ts +15 -0
  11. package/dist/session/workspace/git-worktree.d.ts.map +1 -1
  12. package/dist/session/workspace/git-worktree.js +55 -1
  13. package/dist/session/workspace/git-worktree.js.map +1 -1
  14. package/dist/tools/builtins/bash.d.ts.map +1 -1
  15. package/dist/tools/builtins/bash.js +35 -0
  16. package/dist/tools/builtins/bash.js.map +1 -1
  17. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  18. package/dist/tools/coordinator/agent.js +12 -0
  19. package/dist/tools/coordinator/agent.js.map +1 -1
  20. package/dist/tools/coordinator/index.d.ts.map +1 -1
  21. package/dist/tools/coordinator/index.js +134 -6
  22. package/dist/tools/coordinator/index.js.map +1 -1
  23. package/dist/types/sandbox/index.d.ts +23 -0
  24. package/dist/types/sandbox/index.d.ts.map +1 -1
  25. package/dist/types/sandbox/index.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/connector/mcp/index.ts +3 -0
  28. package/src/connector/mcp/server-stdio.ts +137 -0
  29. package/src/session/workspace/git-worktree.ts +55 -1
  30. package/src/tools/builtins/bash.ts +34 -0
  31. package/src/tools/coordinator/agent.ts +12 -0
  32. package/src/tools/coordinator/index.ts +144 -8
  33. package/src/types/sandbox/index.ts +23 -0
@@ -7,6 +7,7 @@ import { defineTool } from '../defineTool.js'
7
7
  import { wrapUntrusted } from '../untrusted-envelope.js'
8
8
  import { failureLabel, taskSucceeded } from './outcome.js'
9
9
 
10
+ import { DELEGATION_TIMEOUT_MS } from './index.js'
10
11
  import type { TaskLaunchedCallback } from './index.js'
11
12
 
12
13
  /**
@@ -107,6 +108,17 @@ export function buildAgentTool(opts: AgentToolOptions): ToolDefinition {
107
108
  readOnly: false,
108
109
  destructive: false,
109
110
  concurrencySafe: true,
111
+ // Declaring nothing here does not mean "no deadline"; it means the
112
+ // executor's 120-second default, which is a bound for a tool call and
113
+ // absurd for a whole agent run. `create_task` in the sibling module
114
+ // carries the same reasoning and the same hour, and the measurement
115
+ // behind that number is in its docblock: three delegated children took
116
+ // 4m21s, 5m58s and 8m04s, and all three parents gave up at 120s.
117
+ //
118
+ // This surface did not get that fix when its twin did, and the file's
119
+ // own note above records the pair doing exactly this before. The two
120
+ // tools are twins; a bound applied to one of them is not applied.
121
+ timeoutMs: DELEGATION_TIMEOUT_MS,
110
122
  ...(opts.terminal !== undefined ? { terminal: opts.terminal } : {}),
111
123
  async execute({ description, prompt, subagent_type }, context) {
112
124
  // With a single registered subagent the type is optional — default to
@@ -157,6 +157,73 @@ const askUserQuestionModelInputSchema: Record<string, unknown> = {
157
157
  additionalProperties: false,
158
158
  }
159
159
 
160
+ /** One well-formed tag token: `<step>`, `</step>`, `<a href="…">`, `<br/>`. */
161
+ const TAG_TOKEN = /<\/?[A-Za-z][\w-]*(?:\s[^<>]*)?\/?>/g
162
+ const DESCRIPTION_BLOCK = /<description>([\s\S]*?)<\/description>/gi
163
+
164
+ /**
165
+ * Remove every tag token, including the ones removing a tag creates.
166
+ *
167
+ * One pass is not enough and the reason is not obvious: deleting an inner
168
+ * tag can splice its neighbours into a new one. `<<step>step>` loses the
169
+ * inner `<step>` and the halves close up into `<step>` again, so a line
170
+ * that is nothing but markup comes back non-empty and is offered to a
171
+ * human as a step to approve — which is the exact outcome this whole path
172
+ * exists to prevent.
173
+ *
174
+ * Repeating to a fixed point terminates: every pass that changes the
175
+ * string removes at least one token and so strictly shortens it.
176
+ *
177
+ * Only ever used to ANSWER "is there anything here besides markup". The
178
+ * result is never shown to anyone, so this is a test rather than a
179
+ * sanitiser, and it does not have to defend against every way a tag can
180
+ * be spelled.
181
+ */
182
+ function withoutTags(text: string): string {
183
+ let current = text
184
+ for (;;) {
185
+ const next = current.replace(TAG_TOKEN, '')
186
+ if (next === current) return current
187
+ current = next
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Peel tag wrappers off the ENDS of one line, and nowhere else — a step
193
+ * that legitimately says "wrap it in a <div>" keeps its sentence.
194
+ */
195
+ function unwrapStepLine(line: string): string {
196
+ let text = line.trim()
197
+ for (;;) {
198
+ const next = text
199
+ .replace(/^<[A-Za-z][\w-]*(?:\s[^<>]*)?>\s*/, '')
200
+ .replace(/\s*<\/[A-Za-z][\w-]*>$/, '')
201
+ .trim()
202
+ if (next === text) break
203
+ text = next
204
+ }
205
+ return text
206
+ }
207
+
208
+ /**
209
+ * A step list the model serialized instead of building.
210
+ *
211
+ * The line-splitting fallback below is the general case, and it had one
212
+ * shape badly wrong. A model that serializes this array tends to reach for
213
+ * MARKUP, not for prose:
214
+ *
215
+ * <steps>
216
+ * <step>
217
+ * <description>Convert the document to Word</description>
218
+ * </step>
219
+ * </steps>
220
+ *
221
+ * Split on newlines, that is seven "steps", five of which are tags. A host
222
+ * then numbered them in an approval card and asked a person to approve
223
+ * `</steps>` — reported from a real run. The descriptions the model named
224
+ * are right there, so read them; fall back to lines only when there are
225
+ * none, and drop the lines that carry no words at all.
226
+ */
160
227
  function normalizeApprovePlanSteps(value: unknown): unknown {
161
228
  if (typeof value !== 'string') return value
162
229
 
@@ -171,19 +238,84 @@ function normalizeApprovePlanSteps(value: unknown): unknown {
171
238
  }
172
239
  }
173
240
 
241
+ const described = [...trimmed.matchAll(DESCRIPTION_BLOCK)]
242
+ .map((match) => (match[1] ?? '').trim())
243
+ .filter(Boolean)
244
+ if (described.length > 0) {
245
+ return described.map((description) => ({ description }))
246
+ }
247
+
174
248
  const lines = trimmed
175
249
  .split(/\r?\n+/)
176
250
  .map((line) =>
177
- line
178
- .trim()
179
- .replace(/^(?:[-*•]|\d+[.)])\s*/, '')
180
- .trim(),
251
+ unwrapStepLine(
252
+ line
253
+ .trim()
254
+ .replace(/^(?:[-*•]|\d+[.)])\s*/, '')
255
+ .trim(),
256
+ ),
181
257
  )
182
- .filter(Boolean)
258
+ .filter((line) => line.length > 0 && withoutTags(line).trim().length > 0)
259
+
260
+ // Every line was markup: there is no plan in this string, and inventing
261
+ // one step reading `<steps>` is worse than saying so.
262
+ if (lines.length === 0) {
263
+ return withoutTags(unwrapStepLine(trimmed)).trim()
264
+ ? [{ description: unwrapStepLine(trimmed) }]
265
+ : []
266
+ }
183
267
 
184
- return (lines.length ? lines : [trimmed]).map((description) => ({
185
- description,
186
- }))
268
+ return lines.map((description) => ({ description }))
269
+ }
270
+
271
+ /**
272
+ * The single closed shape a capable provider constrains this call to —
273
+ * the same instrument `ask_user_question` carries, for the same failure.
274
+ *
275
+ * `steps` arriving as a STRING is what everything above exists to survive,
276
+ * and surviving it is not the same as preventing it: the normalizer can
277
+ * only guess at a structure the model already threw away. Advertising the
278
+ * closed shape turns the guess into a refusal at generation time.
279
+ */
280
+ const approvePlanModelInputSchema: Record<string, unknown> = {
281
+ type: 'object',
282
+ properties: {
283
+ title: {
284
+ type: 'string',
285
+ description: 'Short title for the plan (e.g. "TypeScript Security & Performance Review").',
286
+ },
287
+ summary: {
288
+ type: 'string',
289
+ description: '1-3 sentence summary of what you plan to do.',
290
+ },
291
+ steps: {
292
+ type: 'array',
293
+ description:
294
+ 'A JSON array of ordered step objects. Never a string, and never markup — no <step> or <description> tags.',
295
+ items: {
296
+ type: 'object',
297
+ properties: {
298
+ description: {
299
+ type: 'string',
300
+ description: 'What this step does, as one plain sentence a person can read.',
301
+ },
302
+ agent_id: {
303
+ type: 'string',
304
+ description: 'Which agent handles this; omit for steps you carry out yourself.',
305
+ },
306
+ depends_on: {
307
+ type: 'array',
308
+ items: { type: 'string' },
309
+ description: 'Descriptions of the steps that must finish before this one.',
310
+ },
311
+ },
312
+ required: ['description'],
313
+ additionalProperties: false,
314
+ },
315
+ },
316
+ },
317
+ required: ['title', 'summary', 'steps'],
318
+ additionalProperties: false,
187
319
  }
188
320
 
189
321
  /**
@@ -1013,6 +1145,10 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
1013
1145
  .preprocess(normalizeApprovePlanSteps, z.array(approvePlanStepSchema))
1014
1146
  .describe('Ordered list of planned steps'),
1015
1147
  }),
1148
+ modelInputSchema: structuredClone(approvePlanModelInputSchema),
1149
+ enforceModelInput: true,
1150
+ validationErrorHint:
1151
+ 'Required shape: {"title":"…","summary":"…","steps":[{"description":"One plain sentence"}]}. "steps" must be a JSON array of objects — never a string, and never markup such as <step> or <description>.',
1016
1152
  category: 'custom',
1017
1153
  permissions: [],
1018
1154
  readOnly: true,
@@ -103,6 +103,29 @@ export interface SandboxExecOptions {
103
103
  readonly timeout?: number
104
104
  readonly env?: Record<string, string>
105
105
  readonly cwd?: string
106
+ /**
107
+ * Called as output arrives, before the command has finished.
108
+ *
109
+ * Every container-tier worker already streams its output a chunk at a
110
+ * time — the wire carries `stdout_delta` and `stderr_delta` events —
111
+ * and every backend concatenated them into a string and returned that
112
+ * when the process exited. So a command that takes eight minutes said
113
+ * nothing for eight minutes, on a transport that had been reporting
114
+ * the whole time.
115
+ *
116
+ * Additive and optional: a backend that cannot stream simply never
117
+ * calls it, and `SandboxExecResult.stdout` still carries the complete
118
+ * output either way. A caller that wants only the result ignores this
119
+ * and behaves exactly as before.
120
+ *
121
+ * The callback must not throw and must not be awaited — it is on the
122
+ * read path of a running process, so a slow or failing consumer would
123
+ * otherwise become a slow or failing command.
124
+ */
125
+ readonly onOutput?: (chunk: {
126
+ readonly stream: 'stdout' | 'stderr'
127
+ readonly data: string
128
+ }) => void
106
129
  /**
107
130
  * Cancellation for the command. A backend that honours it kills the
108
131
  * process; one that does not simply ignores it, so this is additive.