@namzu/sdk 5.0.0 → 5.2.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 (181) hide show
  1. package/CHANGELOG.md +463 -0
  2. package/README.md +2 -2
  3. package/dist/agents/__tests__/run-agent-forwarding.test.d.ts +2 -0
  4. package/dist/agents/__tests__/run-agent-forwarding.test.d.ts.map +1 -0
  5. package/dist/agents/__tests__/run-agent-forwarding.test.js +76 -0
  6. package/dist/agents/__tests__/run-agent-forwarding.test.js.map +1 -0
  7. package/dist/agents/__tests__/run-completed-stop-reason.test.d.ts +2 -0
  8. package/dist/agents/__tests__/run-completed-stop-reason.test.d.ts.map +1 -0
  9. package/dist/agents/__tests__/run-completed-stop-reason.test.js +50 -0
  10. package/dist/agents/__tests__/run-completed-stop-reason.test.js.map +1 -0
  11. package/dist/agents/index.d.ts +1 -0
  12. package/dist/agents/index.d.ts.map +1 -1
  13. package/dist/agents/index.js +4 -0
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/agents/runAgent.d.ts +34 -0
  16. package/dist/agents/runAgent.d.ts.map +1 -1
  17. package/dist/agents/runAgent.js +5 -3
  18. package/dist/agents/runAgent.js.map +1 -1
  19. package/dist/config/runtime.d.ts +30 -30
  20. package/dist/constants/tools/__tests__/dangerous-patterns.test.d.ts +2 -0
  21. package/dist/constants/tools/__tests__/dangerous-patterns.test.d.ts.map +1 -0
  22. package/dist/constants/tools/__tests__/dangerous-patterns.test.js +54 -0
  23. package/dist/constants/tools/__tests__/dangerous-patterns.test.js.map +1 -0
  24. package/dist/constants/tools/index.d.ts.map +1 -1
  25. package/dist/constants/tools/index.js +28 -1
  26. package/dist/constants/tools/index.js.map +1 -1
  27. package/dist/contracts/a2a.d.ts +44 -44
  28. package/dist/directory/__tests__/derive.test.d.ts +2 -0
  29. package/dist/directory/__tests__/derive.test.d.ts.map +1 -0
  30. package/dist/directory/__tests__/derive.test.js +111 -0
  31. package/dist/directory/__tests__/derive.test.js.map +1 -0
  32. package/dist/directory/__tests__/load-diagnostics.test.d.ts +2 -0
  33. package/dist/directory/__tests__/load-diagnostics.test.d.ts.map +1 -0
  34. package/dist/directory/__tests__/load-diagnostics.test.js +106 -0
  35. package/dist/directory/__tests__/load-diagnostics.test.js.map +1 -0
  36. package/dist/directory/__tests__/load-directory.test.d.ts +2 -0
  37. package/dist/directory/__tests__/load-directory.test.d.ts.map +1 -0
  38. package/dist/directory/__tests__/load-directory.test.js +196 -0
  39. package/dist/directory/__tests__/load-directory.test.js.map +1 -0
  40. package/dist/directory/__tests__/subagents.test.d.ts +2 -0
  41. package/dist/directory/__tests__/subagents.test.d.ts.map +1 -0
  42. package/dist/directory/__tests__/subagents.test.js +117 -0
  43. package/dist/directory/__tests__/subagents.test.js.map +1 -0
  44. package/dist/directory/__tests__/supervisor-budget.test.d.ts +2 -0
  45. package/dist/directory/__tests__/supervisor-budget.test.d.ts.map +1 -0
  46. package/dist/directory/__tests__/supervisor-budget.test.js +97 -0
  47. package/dist/directory/__tests__/supervisor-budget.test.js.map +1 -0
  48. package/dist/directory/derive-supervisor.d.ts +58 -0
  49. package/dist/directory/derive-supervisor.d.ts.map +1 -0
  50. package/dist/directory/derive-supervisor.js +88 -0
  51. package/dist/directory/derive-supervisor.js.map +1 -0
  52. package/dist/directory/derive.d.ts +12 -0
  53. package/dist/directory/derive.d.ts.map +1 -0
  54. package/dist/directory/derive.js +58 -0
  55. package/dist/directory/derive.js.map +1 -0
  56. package/dist/directory/index.d.ts +7 -0
  57. package/dist/directory/index.d.ts.map +1 -0
  58. package/dist/directory/index.js +5 -0
  59. package/dist/directory/index.js.map +1 -0
  60. package/dist/directory/load.d.ts +14 -0
  61. package/dist/directory/load.d.ts.map +1 -0
  62. package/dist/directory/load.js +488 -0
  63. package/dist/directory/load.js.map +1 -0
  64. package/dist/directory/scan.d.ts +36 -0
  65. package/dist/directory/scan.d.ts.map +1 -0
  66. package/dist/directory/scan.js +136 -0
  67. package/dist/directory/scan.js.map +1 -0
  68. package/dist/directory/types.d.ts +215 -0
  69. package/dist/directory/types.d.ts.map +1 -0
  70. package/dist/directory/types.js +8 -0
  71. package/dist/directory/types.js.map +1 -0
  72. package/dist/plugin/__tests__/path-containment.test.d.ts +2 -0
  73. package/dist/plugin/__tests__/path-containment.test.d.ts.map +1 -0
  74. package/dist/plugin/__tests__/path-containment.test.js +91 -0
  75. package/dist/plugin/__tests__/path-containment.test.js.map +1 -0
  76. package/dist/plugin/lifecycle.d.ts.map +1 -1
  77. package/dist/plugin/lifecycle.js +3 -3
  78. package/dist/plugin/lifecycle.js.map +1 -1
  79. package/dist/plugin/loader.d.ts.map +1 -1
  80. package/dist/plugin/loader.js +12 -2
  81. package/dist/plugin/loader.js.map +1 -1
  82. package/dist/provider/__tests__/model-version.test.d.ts +2 -0
  83. package/dist/provider/__tests__/model-version.test.d.ts.map +1 -0
  84. package/dist/provider/__tests__/model-version.test.js +80 -0
  85. package/dist/provider/__tests__/model-version.test.js.map +1 -0
  86. package/dist/provider/__tests__/strict-schema.test.d.ts +2 -0
  87. package/dist/provider/__tests__/strict-schema.test.d.ts.map +1 -0
  88. package/dist/provider/__tests__/strict-schema.test.js +109 -0
  89. package/dist/provider/__tests__/strict-schema.test.js.map +1 -0
  90. package/dist/provider/__tests__/typed-error-classification.test.d.ts +2 -0
  91. package/dist/provider/__tests__/typed-error-classification.test.d.ts.map +1 -0
  92. package/dist/provider/__tests__/typed-error-classification.test.js +123 -0
  93. package/dist/provider/__tests__/typed-error-classification.test.js.map +1 -0
  94. package/dist/provider/model-version.d.ts +60 -0
  95. package/dist/provider/model-version.d.ts.map +1 -0
  96. package/dist/provider/model-version.js +64 -0
  97. package/dist/provider/model-version.js.map +1 -0
  98. package/dist/provider/retry.d.ts.map +1 -1
  99. package/dist/provider/retry.js +23 -7
  100. package/dist/provider/retry.js.map +1 -1
  101. package/dist/provider/strict-schema.d.ts +49 -0
  102. package/dist/provider/strict-schema.d.ts.map +1 -0
  103. package/dist/provider/strict-schema.js +112 -0
  104. package/dist/provider/strict-schema.js.map +1 -0
  105. package/dist/public-runtime.d.ts +6 -1
  106. package/dist/public-runtime.d.ts.map +1 -1
  107. package/dist/public-runtime.js +21 -1
  108. package/dist/public-runtime.js.map +1 -1
  109. package/dist/public-tools.d.ts +1 -0
  110. package/dist/public-tools.d.ts.map +1 -1
  111. package/dist/public-tools.js +4 -0
  112. package/dist/public-tools.js.map +1 -1
  113. package/dist/public-types.d.ts +2 -0
  114. package/dist/public-types.d.ts.map +1 -1
  115. package/dist/registry/tool/__tests__/strict-registration.test.d.ts +2 -0
  116. package/dist/registry/tool/__tests__/strict-registration.test.d.ts.map +1 -0
  117. package/dist/registry/tool/__tests__/strict-registration.test.js +82 -0
  118. package/dist/registry/tool/__tests__/strict-registration.test.js.map +1 -0
  119. package/dist/registry/tool/execute.d.ts.map +1 -1
  120. package/dist/registry/tool/execute.js +16 -0
  121. package/dist/registry/tool/execute.js.map +1 -1
  122. package/dist/runtime/query/__tests__/stream-recovery.test.js +5 -0
  123. package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
  124. package/dist/runtime/query/result.d.ts.map +1 -1
  125. package/dist/runtime/query/result.js +5 -0
  126. package/dist/runtime/query/result.js.map +1 -1
  127. package/dist/tools/builtins/__tests__/edit-insert-reachable.test.js +7 -1
  128. package/dist/tools/builtins/__tests__/edit-insert-reachable.test.js.map +1 -1
  129. package/dist/tools/builtins/__tests__/edit.test.js +1 -1
  130. package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
  131. package/dist/tools/builtins/edit.d.ts.map +1 -1
  132. package/dist/tools/builtins/edit.js +20 -2
  133. package/dist/tools/builtins/edit.js.map +1 -1
  134. package/dist/types/plugin/index.d.ts +4 -4
  135. package/dist/types/provider/errors.d.ts +0 -17
  136. package/dist/types/provider/errors.d.ts.map +1 -1
  137. package/dist/types/provider/errors.js +60 -0
  138. package/dist/types/provider/errors.js.map +1 -1
  139. package/dist/types/run/events.d.ts +17 -2
  140. package/dist/types/run/events.d.ts.map +1 -1
  141. package/dist/types/run/events.js.map +1 -1
  142. package/dist/types/verification/index.d.ts +6 -6
  143. package/package.json +1 -1
  144. package/src/agents/__tests__/run-agent-forwarding.test.ts +85 -0
  145. package/src/agents/__tests__/run-completed-stop-reason.test.ts +59 -0
  146. package/src/agents/index.ts +8 -0
  147. package/src/agents/runAgent.ts +30 -4
  148. package/src/constants/tools/__tests__/dangerous-patterns.test.ts +62 -0
  149. package/src/constants/tools/index.ts +28 -1
  150. package/src/directory/__tests__/derive.test.ts +135 -0
  151. package/src/directory/__tests__/load-diagnostics.test.ts +126 -0
  152. package/src/directory/__tests__/load-directory.test.ts +243 -0
  153. package/src/directory/__tests__/subagents.test.ts +155 -0
  154. package/src/directory/__tests__/supervisor-budget.test.ts +117 -0
  155. package/src/directory/derive-supervisor.ts +149 -0
  156. package/src/directory/derive.ts +70 -0
  157. package/src/directory/index.ts +27 -0
  158. package/src/directory/load.ts +556 -0
  159. package/src/directory/scan.ts +163 -0
  160. package/src/directory/types.ts +254 -0
  161. package/src/plugin/__tests__/path-containment.test.ts +107 -0
  162. package/src/plugin/lifecycle.ts +3 -3
  163. package/src/plugin/loader.ts +12 -2
  164. package/src/provider/__tests__/model-version.test.ts +93 -0
  165. package/src/provider/__tests__/strict-schema.test.ts +133 -0
  166. package/src/provider/__tests__/typed-error-classification.test.ts +143 -0
  167. package/src/provider/model-version.ts +98 -0
  168. package/src/provider/retry.ts +23 -7
  169. package/src/provider/strict-schema.ts +125 -0
  170. package/src/public-runtime.ts +31 -0
  171. package/src/public-tools.ts +4 -0
  172. package/src/public-types.ts +8 -0
  173. package/src/registry/tool/__tests__/strict-registration.test.ts +115 -0
  174. package/src/registry/tool/execute.ts +16 -0
  175. package/src/runtime/query/__tests__/stream-recovery.test.ts +5 -0
  176. package/src/runtime/query/result.ts +5 -0
  177. package/src/tools/builtins/__tests__/edit-insert-reachable.test.ts +8 -2
  178. package/src/tools/builtins/__tests__/edit.test.ts +1 -1
  179. package/src/tools/builtins/edit.ts +20 -2
  180. package/src/types/provider/errors.ts +71 -0
  181. package/src/types/run/events.ts +15 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,468 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 604a56a: completed is not succeeded — run_completed says why it stopped, and namzu run exits accordingly
8
+
9
+ `run_failed` is emitted from exactly one place in the kernel: the throw path.
10
+ Every other way a run can end badly arrives as `run_completed` — the token
11
+ budget, the timeout, the iteration cap, a cancellation, a rejected plan, a
12
+ refused structured output, and both guardrails.
13
+
14
+ Measured: a `max_iterations` stop reports `status: 'completed'`, and the event
15
+ carried nothing that distinguished it from an answered question.
16
+
17
+ **SDK.** `run_completed` now carries `stopReason`. It is optional and additive,
18
+ so nothing breaks; a consumer that wants to tell "answered" from "ran out of
19
+ budget" no longer has to hold the `Run` alongside the event stream.
20
+
21
+ **CLI — read this before upgrading if you script `namzu run`.** The command
22
+ exited `0` for all of those. The sharp case is the output guardrail: an answer
23
+ that was _refused_ exited `0` with empty text, so
24
+
25
+ ```sh
26
+ namzu run "write the release notes" > notes.md && publish notes.md
27
+ ```
28
+
29
+ published an empty file and reported success. `namzu run` now exits `1` when
30
+ the run did not finish normally, and names the reason on stderr. The text still
31
+ prints — partial output is real output, and a caller who piped it wants what
32
+ there is — but `$?` can now say it is partial.
33
+
34
+ If you have a script that depends on `namzu run` exiting 0 for a truncated run,
35
+ it was depending on not being told. Check `$?` and read the stderr line.
36
+
37
+ Also in the CLI, internally: the `done` agent event's `finishReason?: string`
38
+ had no producer and no reader anywhere in the package, and the name belonged to
39
+ a different concept — a "finish reason" here is `MessageStopReason`, reported
40
+ per model message, not the run-level `StopReason` a caller asks about at the end
41
+ of a turn. Replaced by `stopReason`. The type is not exported from the package
42
+ entry, so this is internal.
43
+
44
+ - f25ebce: a model id's date suffix is no longer read as its minor version
45
+
46
+ Three copies of one regular expression matched Claude model ids — the capability
47
+ table plus two drivers — and all three had the same defect: the minor-version
48
+ group was `(\d+)`, which swallowed the 8-digit date suffix.
49
+
50
+ Measured against the shipped pattern:
51
+
52
+ ```
53
+ claude-sonnet-4-20250514 -> major=4 minor=20250514
54
+ claude-opus-4-1-20250805 -> major=4 minor=1
55
+ ```
56
+
57
+ So a dated id naming no minor version compared as enormously _newer_ than one
58
+ that does, and every capability gate keyed on `minor >= n` inverted for exactly
59
+ those ids. `claude-sonnet-4-20250514` was classified as a 4.7+ model: the driver
60
+ sent it `thinking: {type: 'adaptive'}`, silently discarding a caller's
61
+ `budgetTokens`, and cleared the 4.5 gate that enables strict tool inputs.
62
+
63
+ `parseClaudeModelVersion` and `claudeVersionAtLeast` are now exported from
64
+ `@namzu/sdk` and used by both drivers and the capability table. A real minor
65
+ version is one to three digits; a date is eight, and the group is bounded
66
+ accordingly. An id the parser does not recognise makes `claudeVersionAtLeast`
67
+ return `false` — a capability gate must not open for a name it does not
68
+ understand.
69
+
70
+ The comment above the old parser warned that "a second, subtly different model
71
+ matcher is how two capability decisions drift apart on the same model name."
72
+ There were three.
73
+
74
+ - 5496fb2: the agent-directory loader is part of the SDK
75
+
76
+ It shipped briefly as a separate package. The name was the tell: nothing fit.
77
+ `project` collided with `ProjectId`, the tenancy bucket every run already
78
+ carries, and it described a scope that no longer existed once `channels/` and
79
+ `schedules/` were cut. `agent-dir` was a hyphenated abbreviation, out of family
80
+ with `skills`, `plugin`, `registry`, `sandbox`.
81
+
82
+ A directory reader that needs the kernel to be useful is a function of the
83
+ kernel, not a product beside it. So it is one now:
84
+
85
+ ```ts
86
+ import { loadDirectory, deriveRunOptions, runAgent } from "@namzu/sdk";
87
+
88
+ const { manifest, ok, diagnostics } = await loadDirectory("./agent");
89
+ if (!ok) console.error(diagnostics);
90
+
91
+ const { output } = await runAgent(
92
+ deriveRunOptions(manifest, { provider, prompt: "What is the weather?" })
93
+ );
94
+ ```
95
+
96
+ Nothing about the convention changed — the same `agent.ts`, `instructions.md`,
97
+ `tools/`, `skills/`, `agents/` layout, the same `modules: 'skip'` mode, the same
98
+ diagnostics, the same `deriveSupervisorOptions` for a directory that declares
99
+ delegates. Only the import path and the names.
100
+
101
+ **Nobody has to migrate.** The package was never published — a `@namzu/project`
102
+ install has always 404'd — so there is no consumer to move and no deprecation
103
+ window owed. The rename that would have cost a major after publishing cost
104
+ nothing before it.
105
+
106
+ Renames, if you were following the source: `loadProject` → `loadDirectory`,
107
+ `ProjectManifest` → `DirectoryManifest`, `ProjectConfig` → `DirectoryConfig`,
108
+ `ProjectSlot` → `DirectorySlot`, `ProjectLoadResult` → `DirectoryLoadResult`,
109
+ `ProjectDiagnostic` → `DirectoryDiagnostic`, `LoadProjectOptions` →
110
+ `LoadDirectoryOptions`. `DiagnosticCode` and `DiagnosticSeverity` gained a
111
+ `Directory` prefix as well — bare, in a shared namespace, they read as the
112
+ SDK's own diagnostic vocabulary rather than one loader's.
113
+
114
+ A side effect worth naming: `@namzu/project` was the one package the release
115
+ pipeline could not publish, so every release since `#102` ended red on its
116
+ `E404`. That failure goes with it.
117
+
118
+ - ca64062: runAgent forwards skills and the verification gate
119
+
120
+ `runAgent` built its `drainQuery` call with an `as never` cast. The cast was
121
+ not load-bearing — removing it typechecks clean — but while it was there the
122
+ kernel seam was unchecked in both directions, and two options the kernel
123
+ accepts were never forwarded.
124
+
125
+ **`skills`** is the one with a caller. `@namzu/sdk` reads a whole `skills/`
126
+ directory, puts them on the options, and every one was dropped: the run was
127
+ assembled without them and nothing reported it. If you passed `skills` to
128
+ `runAgent` and wondered why the model behaved as though it had never seen them,
129
+ this is why. No change needed on your side — the field now arrives.
130
+
131
+ **`verificationGate`** is the safety one. The kernel builds a `VerificationGate`
132
+ from it and consults it on every tool call; the front door had no way to supply
133
+ one, so a `runAgent` run was strictly less mediated than a `drainQuery` run. A
134
+ host that hands `runAgent` an agent directory it did not write should now set
135
+ it.
136
+
137
+ Both are optional and default to today's behaviour, so nothing breaks.
138
+
139
+ Three fixes in `@namzu/sdk`, each a check that existed and read the wrong
140
+ thing:
141
+
142
+ - **A tool with no `inputSchema` is refused.** It used to pass `isToolDefinition`
143
+ — which checked only `name` and `execute` — register clean, then die inside
144
+ `toLLMTools()` on `inputSchema._def`, in a `TypeError` naming neither the tool
145
+ file nor the loader. The check is now the four fields `ToolDefinition`
146
+ declares as required, and no more: demanding `defineTool`'s extras would make
147
+ the loader refuse an object the SDK's own published type accepts. A directory
148
+ that previously loaded with `ok: true` and crashed on first use now loads with
149
+ `ok: false` and a `not_a_tool` diagnostic naming the file.
150
+ - **Import failures explain themselves again.** `explainImportFailure` chose its
151
+ hint by matching Node's error code against `err.message`, and Node does not
152
+ put the code in the message — probed: `ERR_MODULE_NOT_FOUND` arrives as
153
+ "Cannot find module …". Every hint in the function was unreachable. It reads
154
+ `err.code` now, and a Node too old for type stripping gets a hint of its own.
155
+ - **`metadata` values are checked.** Typed `Record<string, string>` and admitted
156
+ on `typeof === 'object'` alone, which an array also satisfies and which says
157
+ nothing about the values, so `{ count: 1 }` and `["a"]` both reached a
158
+ consumer that had been promised strings.
159
+
160
+ - 61ca851: a tool whose schema cannot carry the guarantee it asks for is refused at registration
161
+
162
+ The previous release fixed the `edit` tool's schema and added a check in the
163
+ Anthropic driver. That caught the bug, but in the wrong place: per request, in
164
+ one of the **two** drivers that mark tools strict, and only once something
165
+ actually ran.
166
+
167
+ `ToolRegistry` already refused `enforceModelInput` without a
168
+ `modelInputSchema`, and the comment above that check states the principle
169
+ exactly — _"Refusing at registration puts the error where the author can fix it
170
+ rather than at the first request."_ The rule was written down; the new check was
171
+ somewhere else.
172
+
173
+ It is now beside its sibling. One asks whether a model schema **exists**; the
174
+ other asks whether it can **carry the guarantee the tool just requested**. A
175
+ tool that asks for constrained generation and supplies a schema the constrained
176
+ dialect cannot express is wrong at the moment it is declared, whichever model it
177
+ later meets — so it never registers, and can never reach a request.
178
+
179
+ ```
180
+ Tool "edit" is marked for strict input validation, but its model-facing schema
181
+ uses 1 construct(s) the strict subset does not accept…
182
+ edit.properties.insertLine.oneOf — use `anyOf` — for disjoint branches the two are equivalent
183
+ ```
184
+
185
+ This is the only path that matters in practice: the kernel builds its tool list
186
+ with `ToolRegistry.toLLMTools()`, so every tool reaching a driver through the
187
+ normal loop passed the gate.
188
+
189
+ **A tool that never asked for the guarantee is untouched.** Without
190
+ `enforceModelInput` nothing is marked strict, the schema is sent as ordinary
191
+ JSON Schema, and `oneOf` is perfectly legal there. Refusing it would break
192
+ working setups for no reason.
193
+
194
+ `@namzu/http` also marks tools strict and had no check at all — the same bug
195
+ was reachable through it. It now has the driver-level check the Anthropic driver
196
+ already carried. Both remain as a second boundary for a host that hand-builds
197
+ `ChatCompletionParams` and calls a provider directly, bypassing the registry.
198
+
199
+ **If you author a tool with `enforceModelInput: true`,** a schema using `oneOf`,
200
+ `not`, `if`/`then`/`else`, numeric or length bounds, `patternProperties`, or an
201
+ `additionalProperties` other than `false` now throws at registration instead of
202
+ failing the first request that carries it. The message names the path and the
203
+ replacement.
204
+
205
+ - f25ebce: the edit tool's schema could not be sent under strict validation
206
+
207
+ Strict tool input is not "JSON Schema, enforced" — it is a **subset** of JSON
208
+ Schema, and a keyword outside that subset is not degraded. The vendor rejects
209
+ the whole request, so one unexpressible field in one tool takes every other
210
+ tool down with it and the turn dies before producing a token.
211
+
212
+ The `edit` tool declared its integer-or-`"end"` field with `oneOf`, which is
213
+ outside the subset while the equivalent `anyOf` is inside it. Measured against
214
+ the live API:
215
+
216
+ | body | result |
217
+ | ------------------------- | ------------------------------------------------ |
218
+ | `strict: true` + `oneOf` | **400** — `Schema type 'oneOf' is not supported` |
219
+ | `strict: false` + `oneOf` | accepted |
220
+ | `strict: true` + `anyOf` | accepted |
221
+
222
+ The middle row is why nothing caught it. Neither half is wrong on its own — the
223
+ schema is valid JSON Schema, and marking the tool strict is correct policy — so
224
+ no test of either one failed. Only the pairing did, and the pairing had no
225
+ owner. Every agent using the built-in `edit` tool on a model at or above the
226
+ strict gate lost its first tool-carrying turn to a 400.
227
+
228
+ `oneOf` is now `anyOf` (equivalent here — the branches are disjoint), and
229
+ `minimum` is gone from the model-facing schema for the same reason: numeric
230
+ bounds are outside the subset too. The bound is not lost, the execution schema
231
+ still enforces it.
232
+
233
+ **The general fix is the second half.** `assertStrictSchema` and
234
+ `findStrictSchemaViolations` are exported from `@namzu/sdk`, and the driver now
235
+ checks every schema it is about to mark strict — refusing with the exact path
236
+ and the remedy rather than letting the request go and getting back an error
237
+ that names the keyword but not where it lives:
238
+
239
+ ```
240
+ Tool "edit" is marked for strict input validation, but its model-facing schema
241
+ uses 1 construct(s) the strict subset does not accept…
242
+ edit.properties.insertLine.oneOf — use `anyOf` — for disjoint branches the two are equivalent
243
+ ```
244
+
245
+ A test sweeps every built-in tool that asks for strict validation, so the next
246
+ one is caught in the suite rather than in production.
247
+
248
+ - f25ebce: a directory-derived supervisor now has a token budget, a wall clock, and its skills
249
+
250
+ `BaseAgentConfig` declares `tokenBudget` and `timeoutMs` as **required**.
251
+ `deriveSupervisorOptions` supplied them only when `agent.ts` happened to name
252
+ them — the uncommon case — and an `as SupervisorAgentConfig` made that compile.
253
+ The returned object was therefore typed `tokenBudget: number` while holding
254
+ `undefined`.
255
+
256
+ That is not a type-level nicety. `buildLimitConfig` defaults only
257
+ `maxIterations`, so an undefined budget and timeout disable **both** hard stops:
258
+ a supervisor derived from a directory ran with no token cap and no wall clock.
259
+ And the child-spawn guard computes a delegate's allocation from the parent
260
+ budget, so `undefined` became `NaN` — and `NaN <= 0` is `false`, meaning the
261
+ refusal that exists to stop an unfunded child let it through with a `NaN`
262
+ budget.
263
+
264
+ Both now default to the same numbers `runAgent` uses, which are exported as
265
+ `DEFAULT_TOKEN_BUDGET`, `DEFAULT_TIMEOUT_MS` and `DEFAULT_MAX_ITERATIONS` so the
266
+ two front doors cannot drift. Anything `agent.ts` declares still wins, and
267
+ `overrides` still wins over that.
268
+
269
+ The cast is now `satisfies`, so the next missing required field is a compile
270
+ error rather than a run with its limits quietly switched off.
271
+
272
+ Same file, same cast: `skills` were loaded from the project's `skills/`
273
+ directory, put on the manifest, and then left out of the config the supervisor
274
+ actually ran with. `SupervisorAgentConfig` accepts them and the kernel drives
275
+ them; they are now supplied.
276
+
277
+ - c6b8aa8: An agent directory can declare delegates, and `deriveSupervisorOptions` turns them into
278
+ a `SupervisorAgent` configuration.
279
+
280
+ `SupervisorAgent` needs an `agentIds` roster and a manager that can spawn them.
281
+ Nothing led from a directory to either, so a multi-agent system could be
282
+ described on disk and not run.
283
+
284
+ A directory under `agents/` is read by the same loader that read the root — a
285
+ delegate has the same shape as its parent, so this is recursion rather than a
286
+ new concept.
287
+
288
+ ```
289
+ agent/
290
+ ├── instructions.md
291
+ └── agents/
292
+ ├── researcher/ ← its own agent.ts, instructions.md, tools/
293
+ └── writer/
294
+ ```
295
+
296
+ `deriveSupervisorOptions` supplies the roster and leaves the manager to the
297
+ host, the same contract `deriveRunOptions` follows: it converts, it does not
298
+ run. Delegates come back as plans rather than registered agents, because
299
+ registration mutates the host's manager and a function that quietly mutates an
300
+ object it was handed for reference is the surprise this package avoids.
301
+
302
+ A delegate may name its own model and inherits the coordinator's only when it
303
+ does not — a cheap model for a narrow job is the common case, and inheriting
304
+ unconditionally would bill every specialist at the coordinator's rate.
305
+
306
+ **One level only.** A delegate may not declare delegates of its own. How deep a
307
+ system fans out is a topology decision that belongs to whoever composes it, and
308
+ answering it by default is how a directory layout ends up deciding a system's
309
+ shape. It also removes the cycle: `agents/a/agents/b/agents/a` cannot be built
310
+ if the second level is never read.
311
+
312
+ A delegate that fails to load is reported in the parent's diagnostics, prefixed
313
+ with its path, and is not offered in the roster. A caller reading one list
314
+ should not have to walk the tree to find out the run will be short a specialist.
315
+
316
+ ### Patch Changes
317
+
318
+ - f25ebce: the fork-bomb entry in the dangerous-command list could not match a fork bomb
319
+
320
+ `DANGEROUS_PATTERNS` is what the `deny_dangerous_patterns` verification rule
321
+ consults, and what `namzu run`'s own docstring means when it promises that in a
322
+ non-interactive run "the safety gate still hard-denies catastrophic commands".
323
+
324
+ The fork-bomb entry was written `/:(){ :\|:& };:/`. In a regular expression
325
+ `()` is an empty capture group, not two literal parentheses — so that pattern
326
+ described the string `:{ :|:& };:`, which is not valid shell and which nobody
327
+ would ever type. Probed: it returned `false` for `:(){ :|:& };:` and for every
328
+ other spelling of it.
329
+
330
+ The replacement matches on **self-reference** rather than on one literal
331
+ spelling — a fork bomb is a function whose own name appears on both sides of a
332
+ pipe, is backgrounded, and is then invoked. So `bomb(){ bomb|bomb& }; bomb` is
333
+ denied along with the `:` form, while `watch(){ tail -f log | grep E & }` — a
334
+ function that merely contains a pipe and a background job — is not.
335
+
336
+ No test named a fork bomb before this change, which is how it survived. There
337
+ are now sixteen.
338
+
339
+ - c8672ed: The plugin subsystem contains its paths. It had none, and it is the part of
340
+ this SDK that loads third-party code.
341
+
342
+ **A manifest could name any file on disk.** `PluginLifecycleManager` built its
343
+ import path with `join(plugin.rootDir, toolPath)`, and `toolPath` comes out of
344
+ the plugin's own manifest — a file the plugin author writes. A manifest reading
345
+ `"tools": ["../../../../somewhere/evil.js"]` left the plugin directory entirely
346
+ and was imported, which is to say executed, in-process. The same held for
347
+ `hooks`. Both now resolve through `resolveWithinReal`, so a path that escapes
348
+ the plugin root is refused before anything is imported.
349
+
350
+ **Discovery followed symlinks.** `discoverPlugins` used `stat`, which reports
351
+ on a link's _target_, so a symlinked entry pointing anywhere on disk was
352
+ admitted as a plugin directory and its manifest read from there — the directory
353
+ listed was not the directory loaded (CWE-59). It now uses `lstat` and refuses a
354
+ link with a warning naming the path.
355
+
356
+ Found by comparing the plugin loader against `@namzu/sdk`'s scanner, which
357
+ was written this week with both protections. The subsystem that had them was
358
+ the one loading code the repo's own reviewers wrote; the one without them was
359
+ the one loading code from a home directory those reviewers never see.
360
+
361
+ If you ship a plugin whose manifest points outside its own directory, it now
362
+ fails at enable with a message naming the path. Move the file inside the plugin.
363
+
364
+ ## 5.1.0
365
+
366
+ ### Minor Changes
367
+
368
+ - 8dbb98b: Adds `@namzu/project` — a conventional agent directory, read into typed,
369
+ inspectable definitions.
370
+
371
+ ```
372
+ my-agent/agent/
373
+ ├── agent.ts # optional — model, temperature, budgets
374
+ ├── instructions.md # optional — the system prompt
375
+ ├── tools/search.ts # default-exports defineTool(…)
376
+ └── skills/plan-a-trip/SKILL.md
377
+ ```
378
+
379
+ ```ts
380
+ const { manifest, ok, diagnostics } = await loadProject("./agent");
381
+ await runAgent(deriveRunOptions(manifest, { provider, prompt: "go" }));
382
+ ```
383
+
384
+ A **loader, not a runner**, and not in `@namzu/sdk`: the kernel does not
385
+ mandate a directory layout any more than a kernel mandates `/etc/foo.conf`.
386
+ `deriveRunOptions` returns ordinary `RunAgentOptions`, so there is no second
387
+ code path and no behaviour reachable only through the convention.
388
+
389
+ **Importing a directory runs it.** `loadProject` imports every module-backed
390
+ file, in this process, with this process's privileges — a top-level side effect
391
+ in `tools/search.ts` happens during the load. There is no in-process boundary
392
+ that would change that, and `@namzu/sandbox` confines tool execution rather
393
+ than module import. For a directory whose author you are not,
394
+ `modules: 'skip'` imports nothing while still returning the full structural
395
+ truth: every path, the instructions, the skills, duplicate detection. That is
396
+ also the mode a CI gate and a UI file tree want.
397
+
398
+ **TypeScript without a build step.** Files load through `await import()`, so
399
+ `.ts` is handled by Node's own type stripping. Stripping erases types rather
400
+ than transforming code, so `enum`, decorators, parameter properties, runtime
401
+ `namespace`, extensionless relative imports and tsconfig `paths` aliases do not
402
+ work — the README tables each one against what to write instead, and the errors
403
+ name the remedy. A host that needs them passes `importModule` and hands in
404
+ `jiti` or a `tsx`-registered importer: three lines in the host, no bundler in
405
+ this dependency tree.
406
+
407
+ **Nothing fails silently.** A file that cannot load is reported with its path
408
+ and reason, never dropped. Two behaviours worth knowing: a symlink is refused
409
+ rather than followed, because the file that would be imported is not the file
410
+ that was listed; and a timed-out import is `'abandoned'`, not `'failed'`, since
411
+ `import()` cannot be cancelled — the module may still finish, and Node caches
412
+ it, so a later load in the same process can see the same file succeed.
413
+
414
+ `channels/` and `schedules/` are **not** in this version. A trigger of
415
+ `{ id, handler }` cannot express a signed webhook — verification needs the raw
416
+ body, and a handler receiving a parsed one can never check an HMAC — carries no
417
+ idempotency key while webhooks retry and schedules double-fire, and a cron
418
+ field with no timezone story is a declaration nothing drives. Each would be a
419
+ breaking change to a published type; the shape question gets its own pass.
420
+
421
+ `@namzu/sdk` additionally exports `resolveWithin`, `resolveWithinReal` and
422
+ `isWithin`, the containment helpers its own filesystem tools use. They were
423
+ internal while three call sites outside that file needed them.
424
+
425
+ ### Patch Changes
426
+
427
+ - 7ac89da: A driver that classified its own failure was being punished for it, in two
428
+ places, and both shipped in 5.0.0.
429
+
430
+ **`classifyProviderError` never read `kind`.** A `ProviderRequestError` — the
431
+ type first-party drivers throw when they have diagnosed a failure themselves —
432
+ fell through to the status heuristics, where a carefully-determined
433
+ `context_overflow` carrying a 400 became `invalid_request`. Three of the six
434
+ kinds landed wrong that way:
435
+
436
+ | kind | was | now |
437
+ | ------------------ | -------------------------------- | ---------------------------------------- |
438
+ | `context_overflow` | `invalid_request`, not retryable | `context_length_exceeded`, not retryable |
439
+ | `server` | `invalid_request`, not retryable | `server_error`, **retryable** |
440
+ | `network` | `invalid_request`, not retryable | `network`, **retryable** |
441
+
442
+ The overflow case was not cosmetic. The run loop reaches for compaction when it
443
+ sees `context_length_exceeded`, so relief — the one provider failure this
444
+ kernel can actually do something about — was unreachable for exactly the
445
+ drivers that had diagnosed the problem correctly.
446
+
447
+ **`withProviderRetry` rethrew such errors before the retry loop.** Its comment
448
+ justified preserving the driver's classification, which is right; the code also
449
+ skipped retrying, which is a separate decision nobody made. A first-party HTTP
450
+ or OpenRouter driver reporting a 429 as `kind: 'throttle'` got exactly one
451
+ attempt, while the identical failure from a driver that classified nothing got
452
+ the full backoff.
453
+
454
+ Retry is now decided the same way for both, from the classification's
455
+ `retryable`. The original error still escapes to the run boundary, so
456
+ `run.lastProviderError` keeps reporting the driver's own
457
+ `{ kind, status, retryAfterMs }` — wrapping there would have fixed the retry
458
+ and lost the vendor's `kind`, which the existing stream-recovery test caught.
459
+
460
+ **What changes for you.** A 429, a 5xx or a socket failure from
461
+ `@namzu/http` or `@namzu/openrouter` is now retried with backoff instead of
462
+ failing on the first attempt, and a context overflow from those drivers now
463
+ triggers compaction instead of failing the run. If you were relying on a typed
464
+ error failing fast, `retry: { maxRetries: 0 }` on `drainQuery` restores that.
465
+
3
466
  ## 5.0.0
4
467
 
5
468
  ### Major Changes
package/README.md CHANGED
@@ -29,7 +29,7 @@ Namzu is a single-process TypeScript kernel with the following responsibilities:
29
29
  - **Scheduling.** Per-run token, cost, wall-clock, and iteration budgets. Limit checker, task router (cheap model for compaction, expensive for coding), tool tiering (LLM learns to prefer cheaper tools first).
30
30
  - **Signals.** `AbortController` tree spanning parent and children. `cancel(taskId)` and `cancelAll(parentRunId)` propagate. Runs can be paused and resumed, aborted cleanly, and emit lifecycle events for every transition.
31
31
  - **Memory management.** Working memory via structured compaction to a typed `WorkingState`. Long-term memory via an indexed, tag/query/status-searchable store with disk persistence. No vector database required by default.
32
- - **Durability.** Atomic per-iteration checkpoints, automatic emergency core-dump on SIGINT/SIGTERM, separate storage for runs, threads, conversations, activities, memories, and tasks.
32
+ - **Durability.** Atomic per-iteration checkpoints, an opt-in emergency core-dump on SIGINT/SIGTERM (`emergencySave: true` — a library must not seize a host process termination path by default), separate storage for runs, threads, conversations, activities, memories, and tasks.
33
33
  - **IPC.** Native A2A (agent-to-agent) and MCP (Model Context Protocol) — both client and server, one SDK. An internal event bus with circuit breakers, file lock manager, and edit ownership tracking so concurrent agents do not stomp on each other.
34
34
  - **Capability system.** Tools are first-class, typed, permissioned, and progressively disclosed. The LLM does not see the full tool catalog; tools start deferred, get activated on demand, and can be suspended. Each tool declares `readOnly`, `destructive`, `concurrencySafe`, `permissions`, `category`.
35
35
  - **Syscall filtering.** Every tool call goes through a verification gate — allow / deny / ask, with built-in rules for read-only allowlist and dangerous pattern deny-list, plus custom regex rules. This is separate from sandbox isolation; it is the decision layer, the sandbox is the enforcement layer.
@@ -375,7 +375,7 @@ const result = await agent.run(
375
375
  )
376
376
  ```
377
377
 
378
- That is a complete, sandbox-isolated, checkpointed, telemetrized agent run with prompt caching, progressive tool disclosure, structured compaction, and emergency save all wired in by default. Those are not features you enable; they are how the kernel runs. Swap `registerOpenRouter()` for `registerOllama()`, `registerAnthropic()`, `registerBedrock()`, `registerOpenAI()`, `registerLMStudio()`, or `registerHttp()` — the code below the registration line stays identical.
378
+ That is a complete, sandbox-isolated, checkpointed, telemetrized agent run with prompt caching, progressive tool disclosure, and structured compaction all wired in by default those are not features you enable, they are how the kernel runs. Emergency save is the exception and is opt-in: pass `emergencySave: true` when the process owns its run end to end. Swap `registerOpenRouter()` for `registerOllama()`, `registerAnthropic()`, `registerBedrock()`, `registerOpenAI()`, `registerLMStudio()`, or `registerHttp()` — the code below the registration line stays identical.
379
379
 
380
380
  Examples for `PipelineAgent`, `RouterAgent`, and `SupervisorAgent` are in `src/agents/`.
381
381
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run-agent-forwarding.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-agent-forwarding.test.d.ts","sourceRoot":"","sources":["../../../src/agents/__tests__/run-agent-forwarding.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { z } from 'zod';
3
+ import { MockLLMProvider, registerMock } from '../../provider/index.js';
4
+ import { ToolRegistry } from '../../registry/index.js';
5
+ import { runAgent } from '../runAgent.js';
6
+ /**
7
+ * The `drainQuery` call in `runAgent` was written `as never`.
8
+ *
9
+ * That cast was not load-bearing — removing it typechecks clean — but while it
10
+ * was there the kernel seam was unchecked in both directions: a field the
11
+ * kernel accepts and this door forgot to forward produced no error, and neither
12
+ * did a field spelled wrong. Two were already missing when it was removed.
13
+ *
14
+ * `skills` is the one with a caller in this repo. `@namzu/project` reads a
15
+ * whole `skills/` directory, put them on the options, and every one was dropped
16
+ * on the floor — the run was assembled without them and said nothing. These
17
+ * pin the forwarding rather than the cast, because the cast can come back and
18
+ * a test that only asserted its absence would not notice.
19
+ */
20
+ registerMock();
21
+ describe('runAgent forwards what the kernel takes', () => {
22
+ it('puts skills in front of the model', async () => {
23
+ const provider = new MockLLMProvider({ turns: [{ text: 'ok' }] });
24
+ await runAgent({
25
+ provider,
26
+ model: 'mock-model',
27
+ prompt: 'plan something',
28
+ skills: [
29
+ {
30
+ metadata: { name: 'plan-a-trip', description: 'Plan a trip end to end' },
31
+ body: 'Ask for dates first.',
32
+ dirPath: '/tmp/skills/plan-a-trip',
33
+ },
34
+ ],
35
+ });
36
+ // Serialized rather than reached into: the prompt builder decides where a
37
+ // skill lands, and pinning that path here would make this test fail on a
38
+ // refactor that kept the behaviour. What matters is that it arrived.
39
+ expect(JSON.stringify(provider.requests[0])).toContain('plan-a-trip');
40
+ });
41
+ it('forwards the verification gate, so a denied tool does not run', async () => {
42
+ // The first version of this test asserted `run.status === 'completed'`
43
+ // with the gate set. It passed with the forwarding deleted — a run with
44
+ // no gate completes too — so it proved nothing. A gate is only observable
45
+ // through a call it stops, which means the assertion has to be about
46
+ // whether the tool body ran.
47
+ let ran = false;
48
+ const tools = new ToolRegistry();
49
+ tools.register({
50
+ name: 'delete_everything',
51
+ description: 'Deletes everything.',
52
+ inputSchema: z.object({}),
53
+ execute: async () => {
54
+ ran = true;
55
+ return { success: true, output: 'deleted' };
56
+ },
57
+ });
58
+ await runAgent({
59
+ provider: new MockLLMProvider({
60
+ turns: [{ toolCalls: [{ name: 'delete_everything', args: {} }] }, { text: 'done' }],
61
+ }),
62
+ model: 'mock-model',
63
+ prompt: 'clean up',
64
+ tools,
65
+ verificationGate: {
66
+ enabled: true,
67
+ rules: [{ type: 'deny_by_name', toolNames: ['delete_everything'] }],
68
+ allowReadOnlyTools: false,
69
+ denyDangerousPatterns: false,
70
+ logDecisions: false,
71
+ },
72
+ });
73
+ expect(ran).toBe(false);
74
+ });
75
+ });
76
+ //# sourceMappingURL=run-agent-forwarding.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-agent-forwarding.test.js","sourceRoot":"","sources":["../../../src/agents/__tests__/run-agent-forwarding.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;;;;;;;;GAaG;AAEH,YAAY,EAAE,CAAA;AAEd,QAAQ,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACxD,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;QAEjE,MAAM,QAAQ,CAAC;YACd,QAAQ;YACR,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,gBAAgB;YACxB,MAAM,EAAE;gBACP;oBACC,QAAQ,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,wBAAwB,EAAE;oBACxE,IAAI,EAAE,sBAAsB;oBAC5B,OAAO,EAAE,yBAAyB;iBAClC;aACD;SACD,CAAC,CAAA;QAEF,0EAA0E;QAC1E,yEAAyE;QACzE,qEAAqE;QACrE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC9E,uEAAuE;QACvE,wEAAwE;QACxE,0EAA0E;QAC1E,qEAAqE;QACrE,6BAA6B;QAC7B,IAAI,GAAG,GAAG,KAAK,CAAA;QAEf,MAAM,KAAK,GAAG,IAAI,YAAY,EAAE,CAAA;QAChC,KAAK,CAAC,QAAQ,CAAC;YACd,IAAI,EAAE,mBAAmB;YACzB,WAAW,EAAE,qBAAqB;YAClC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,KAAK,IAAI,EAAE;gBACnB,GAAG,GAAG,IAAI,CAAA;gBACV,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;YAC5C,CAAC;SACD,CAAC,CAAA;QAEF,MAAM,QAAQ,CAAC;YACd,QAAQ,EAAE,IAAI,eAAe,CAAC;gBAC7B,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aACnF,CAAC;YACF,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,UAAU;YAClB,KAAK;YACL,gBAAgB,EAAE;gBACjB,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBACnE,kBAAkB,EAAE,KAAK;gBACzB,qBAAqB,EAAE,KAAK;gBAC5B,YAAY,EAAE,KAAK;aACnB;SACD,CAAC,CAAA;QAEF,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxB,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run-completed-stop-reason.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-completed-stop-reason.test.d.ts","sourceRoot":"","sources":["../../../src/agents/__tests__/run-completed-stop-reason.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { MockLLMProvider, registerMock } from '../../provider/index.js';
3
+ import { runAgent } from '../runAgent.js';
4
+ /**
5
+ * `completed` is not `succeeded`.
6
+ *
7
+ * `run_failed` is emitted from exactly one place — the throw path in
8
+ * `result.ts` — so every other way a run can end badly arrives as
9
+ * `run_completed`: the token budget, the timeout, the iteration cap, a
10
+ * cancellation, and a blocking output guardrail. A consumer reading that event
11
+ * as success reported one for a run whose answer was refused, and the CLI did:
12
+ * it mapped `run_completed` to a bare `done` and exited 0.
13
+ *
14
+ * Measured before the fix: a `max_iterations` stop reports
15
+ * `status: 'completed'`, and the event carried nothing to distinguish it.
16
+ */
17
+ registerMock();
18
+ async function eventsOf(options) {
19
+ const events = [];
20
+ await runAgent({ ...options, listener: (e) => void events.push(e) });
21
+ return events;
22
+ }
23
+ function completion(events) {
24
+ return events.find((e) => e.type === 'run_completed');
25
+ }
26
+ describe('run_completed says why the run stopped', () => {
27
+ it('reports end_turn when the model finished its answer', async () => {
28
+ const events = await eventsOf({
29
+ provider: new MockLLMProvider({ turns: [{ text: 'done' }] }),
30
+ model: 'mock-model',
31
+ prompt: 'x',
32
+ });
33
+ expect(completion(events)?.stopReason).toBe('end_turn');
34
+ });
35
+ it('reports max_iterations when the loop was cut short', async () => {
36
+ const events = await eventsOf({
37
+ provider: new MockLLMProvider({ turns: [{ toolCalls: [{ name: 'absent', args: {} }] }] }),
38
+ model: 'mock-model',
39
+ prompt: 'x',
40
+ maxIterations: 1,
41
+ });
42
+ const done = completion(events);
43
+ // The event that a consumer treats as "the run ended" — and the field
44
+ // that stops it being read as "the run succeeded".
45
+ expect(done).toBeDefined();
46
+ expect(done?.stopReason).toBe('max_iterations');
47
+ expect(events.some((e) => e.type === 'run_failed')).toBe(false);
48
+ });
49
+ });
50
+ //# sourceMappingURL=run-completed-stop-reason.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-completed-stop-reason.test.js","sourceRoot":"","sources":["../../../src/agents/__tests__/run-completed-stop-reason.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;;;;;;;GAYG;AAEH,YAAY,EAAE,CAAA;AAEd,KAAK,UAAU,QAAQ,CAAC,OAAuC;IAC9D,MAAM,MAAM,GAAe,EAAE,CAAA;IAC7B,MAAM,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACpE,OAAO,MAAM,CAAA;AACd,CAAC;AAED,SAAS,UAAU,CAAC,MAAkB;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAU,CAAA;AAC/D,CAAC;AAED,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACvD,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;YAC7B,QAAQ,EAAE,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC5D,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,GAAG;SACX,CAAC,CAAA;QAEF,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;YAC7B,QAAQ,EAAE,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YACzF,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,GAAG;YACX,aAAa,EAAE,CAAC;SAChB,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;QAC/B,sEAAsE;QACtE,mDAAmD;QACnD,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;QAC1B,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC/C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -8,5 +8,6 @@ export type { DefineAgentOptions } from './defineAgent.js';
8
8
  export { InvocationLock, ConcurrentInvocationError } from './lock.js';
9
9
  export type { Disposable } from './lock.js';
10
10
  export { runAgent } from './runAgent.js';
11
+ export { DEFAULT_MAX_ITERATIONS, DEFAULT_TIMEOUT_MS, DEFAULT_TOKEN_BUDGET, } from './runAgent.js';
11
12
  export type { AgentIdentity, RunAgentOptions, RunAgentResult } from './runAgent.js';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AACrE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AACrE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAIxC,OAAO,EACN,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,GACpB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}