@namzu/sdk 39.0.0 → 41.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 (289) hide show
  1. package/CHANGELOG.md +328 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +8 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +11 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/connector/index.d.ts +2 -2
  9. package/dist/connector/index.d.ts.map +1 -1
  10. package/dist/connector/index.js +1 -1
  11. package/dist/connector/index.js.map +1 -1
  12. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  13. package/dist/connector/mcp/adapter.js +112 -10
  14. package/dist/connector/mcp/adapter.js.map +1 -1
  15. package/dist/connector/mcp/audio-admission.d.ts +17 -0
  16. package/dist/connector/mcp/audio-admission.d.ts.map +1 -0
  17. package/dist/connector/mcp/audio-admission.js +171 -0
  18. package/dist/connector/mcp/audio-admission.js.map +1 -0
  19. package/dist/connector/mcp/client.d.ts +252 -1
  20. package/dist/connector/mcp/client.d.ts.map +1 -1
  21. package/dist/connector/mcp/client.js +611 -39
  22. package/dist/connector/mcp/client.js.map +1 -1
  23. package/dist/connector/mcp/envelope.d.ts +91 -0
  24. package/dist/connector/mcp/envelope.d.ts.map +1 -0
  25. package/dist/connector/mcp/envelope.js +173 -0
  26. package/dist/connector/mcp/envelope.js.map +1 -0
  27. package/dist/connector/mcp/era.d.ts +130 -0
  28. package/dist/connector/mcp/era.d.ts.map +1 -0
  29. package/dist/connector/mcp/era.js +304 -0
  30. package/dist/connector/mcp/era.js.map +1 -0
  31. package/dist/connector/mcp/errors.d.ts +106 -0
  32. package/dist/connector/mcp/errors.d.ts.map +1 -0
  33. package/dist/connector/mcp/errors.js +154 -0
  34. package/dist/connector/mcp/errors.js.map +1 -0
  35. package/dist/connector/mcp/http-sse.d.ts +11 -0
  36. package/dist/connector/mcp/http-sse.d.ts.map +1 -1
  37. package/dist/connector/mcp/http-sse.js +21 -6
  38. package/dist/connector/mcp/http-sse.js.map +1 -1
  39. package/dist/connector/mcp/index.d.ts +7 -0
  40. package/dist/connector/mcp/index.d.ts.map +1 -1
  41. package/dist/connector/mcp/index.js +10 -0
  42. package/dist/connector/mcp/index.js.map +1 -1
  43. package/dist/connector/mcp/streamable-http.d.ts +83 -0
  44. package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
  45. package/dist/connector/mcp/streamable-http.js +177 -11
  46. package/dist/connector/mcp/streamable-http.js.map +1 -1
  47. package/dist/connector/mcp/x-mcp-header.d.ts +56 -0
  48. package/dist/connector/mcp/x-mcp-header.d.ts.map +1 -0
  49. package/dist/connector/mcp/x-mcp-header.js +254 -0
  50. package/dist/connector/mcp/x-mcp-header.js.map +1 -0
  51. package/dist/constants/mcp/index.d.ts +123 -15
  52. package/dist/constants/mcp/index.d.ts.map +1 -1
  53. package/dist/constants/mcp/index.js +135 -16
  54. package/dist/constants/mcp/index.js.map +1 -1
  55. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  56. package/dist/manager/agent/lifecycle.js +23 -0
  57. package/dist/manager/agent/lifecycle.js.map +1 -1
  58. package/dist/manager/run/persistence.d.ts +8 -0
  59. package/dist/manager/run/persistence.d.ts.map +1 -1
  60. package/dist/manager/run/persistence.js +12 -0
  61. package/dist/manager/run/persistence.js.map +1 -1
  62. package/dist/prompt/coding-agent-doctrine.d.ts +20 -0
  63. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  64. package/dist/prompt/coding-agent-doctrine.js +19 -3
  65. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  66. package/dist/prompt/index.d.ts +1 -1
  67. package/dist/prompt/index.d.ts.map +1 -1
  68. package/dist/prompt/index.js +1 -1
  69. package/dist/prompt/index.js.map +1 -1
  70. package/dist/public-runtime.d.ts +6 -4
  71. package/dist/public-runtime.d.ts.map +1 -1
  72. package/dist/public-runtime.js +8 -4
  73. package/dist/public-runtime.js.map +1 -1
  74. package/dist/public-tools.d.ts +11 -0
  75. package/dist/public-tools.d.ts.map +1 -1
  76. package/dist/public-tools.js +14 -0
  77. package/dist/public-tools.js.map +1 -1
  78. package/dist/registry/tool/execute.d.ts.map +1 -1
  79. package/dist/registry/tool/execute.js +2 -3
  80. package/dist/registry/tool/execute.js.map +1 -1
  81. package/dist/registry/tool/portable.d.ts +65 -0
  82. package/dist/registry/tool/portable.d.ts.map +1 -0
  83. package/dist/registry/tool/portable.js +244 -0
  84. package/dist/registry/tool/portable.js.map +1 -0
  85. package/dist/registry/tool/schema.d.ts +32 -5
  86. package/dist/registry/tool/schema.d.ts.map +1 -1
  87. package/dist/registry/tool/schema.js +35 -9
  88. package/dist/registry/tool/schema.js.map +1 -1
  89. package/dist/registry/toolset/catalog.js +8 -8
  90. package/dist/registry/toolset/catalog.js.map +1 -1
  91. package/dist/runtime/jobs/awaited-jobs.d.ts +215 -0
  92. package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -0
  93. package/dist/runtime/jobs/awaited-jobs.js +259 -0
  94. package/dist/runtime/jobs/awaited-jobs.js.map +1 -0
  95. package/dist/runtime/jobs/registry.d.ts +33 -2
  96. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  97. package/dist/runtime/jobs/registry.js +37 -0
  98. package/dist/runtime/jobs/registry.js.map +1 -1
  99. package/dist/runtime/query/executor.d.ts +28 -0
  100. package/dist/runtime/query/executor.d.ts.map +1 -1
  101. package/dist/runtime/query/executor.js +39 -1
  102. package/dist/runtime/query/executor.js.map +1 -1
  103. package/dist/runtime/query/file-evidence-context.d.ts.map +1 -1
  104. package/dist/runtime/query/file-evidence-context.js +159 -43
  105. package/dist/runtime/query/file-evidence-context.js.map +1 -1
  106. package/dist/runtime/query/file-evidence-replay.d.ts +260 -0
  107. package/dist/runtime/query/file-evidence-replay.d.ts.map +1 -0
  108. package/dist/runtime/query/file-evidence-replay.js +647 -0
  109. package/dist/runtime/query/file-evidence-replay.js.map +1 -0
  110. package/dist/runtime/query/file-evidence-seed.d.ts +50 -0
  111. package/dist/runtime/query/file-evidence-seed.d.ts.map +1 -0
  112. package/dist/runtime/query/file-evidence-seed.js +100 -0
  113. package/dist/runtime/query/file-evidence-seed.js.map +1 -0
  114. package/dist/runtime/query/index.d.ts.map +1 -1
  115. package/dist/runtime/query/index.js +94 -2
  116. package/dist/runtime/query/index.js.map +1 -1
  117. package/dist/runtime/query/iteration/index.d.ts +87 -9
  118. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  119. package/dist/runtime/query/iteration/index.js +193 -28
  120. package/dist/runtime/query/iteration/index.js.map +1 -1
  121. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  122. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  124. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/tool-review.js +5 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  127. package/dist/runtime/query/plugin-hooks.d.ts +14 -0
  128. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  129. package/dist/runtime/query/plugin-hooks.js +18 -0
  130. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  131. package/dist/runtime/query/repeat-call.d.ts +17 -4
  132. package/dist/runtime/query/repeat-call.d.ts.map +1 -1
  133. package/dist/runtime/query/repeat-call.js +26 -19
  134. package/dist/runtime/query/repeat-call.js.map +1 -1
  135. package/dist/runtime/query/steering.d.ts +11 -1
  136. package/dist/runtime/query/steering.d.ts.map +1 -1
  137. package/dist/runtime/query/steering.js +12 -1
  138. package/dist/runtime/query/steering.js.map +1 -1
  139. package/dist/runtime/query/tooling.d.ts +2 -0
  140. package/dist/runtime/query/tooling.d.ts.map +1 -1
  141. package/dist/runtime/query/tooling.js +1 -0
  142. package/dist/runtime/query/tooling.js.map +1 -1
  143. package/dist/sandbox/provider/local.d.ts.map +1 -1
  144. package/dist/sandbox/provider/local.js +46 -3
  145. package/dist/sandbox/provider/local.js.map +1 -1
  146. package/dist/scheduler/completion-inbox.d.ts +48 -2
  147. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  148. package/dist/scheduler/completion-inbox.js +102 -10
  149. package/dist/scheduler/completion-inbox.js.map +1 -1
  150. package/dist/scheduler/local.d.ts.map +1 -1
  151. package/dist/scheduler/local.js +8 -0
  152. package/dist/scheduler/local.js.map +1 -1
  153. package/dist/store/run/disk.d.ts +35 -1
  154. package/dist/store/run/disk.d.ts.map +1 -1
  155. package/dist/store/run/disk.js +100 -0
  156. package/dist/store/run/disk.js.map +1 -1
  157. package/dist/tools/builtins/bash.d.ts.map +1 -1
  158. package/dist/tools/builtins/bash.js +4 -10
  159. package/dist/tools/builtins/bash.js.map +1 -1
  160. package/dist/tools/builtins/edit-apply.d.ts +126 -0
  161. package/dist/tools/builtins/edit-apply.d.ts.map +1 -0
  162. package/dist/tools/builtins/edit-apply.js +360 -0
  163. package/dist/tools/builtins/edit-apply.js.map +1 -0
  164. package/dist/tools/builtins/edit.d.ts +143 -1
  165. package/dist/tools/builtins/edit.d.ts.map +1 -1
  166. package/dist/tools/builtins/edit.js +37 -219
  167. package/dist/tools/builtins/edit.js.map +1 -1
  168. package/dist/tools/builtins/index.d.ts +1 -0
  169. package/dist/tools/builtins/index.d.ts.map +1 -1
  170. package/dist/tools/builtins/index.js +9 -3
  171. package/dist/tools/builtins/index.js.map +1 -1
  172. package/dist/tools/builtins/job.js +1 -1
  173. package/dist/tools/builtins/job.js.map +1 -1
  174. package/dist/tools/builtins/read-file.d.ts +2 -2
  175. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  176. package/dist/tools/builtins/read-file.js +50 -65
  177. package/dist/tools/builtins/read-file.js.map +1 -1
  178. package/dist/tools/builtins/read-render.d.ts +56 -0
  179. package/dist/tools/builtins/read-render.d.ts.map +1 -0
  180. package/dist/tools/builtins/read-render.js +73 -0
  181. package/dist/tools/builtins/read-render.js.map +1 -0
  182. package/dist/tools/builtins/wait-for-job-bounds.d.ts +67 -0
  183. package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -0
  184. package/dist/tools/builtins/wait-for-job-bounds.js +108 -0
  185. package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -0
  186. package/dist/tools/builtins/wait-for-job.d.ts +6 -0
  187. package/dist/tools/builtins/wait-for-job.d.ts.map +1 -0
  188. package/dist/tools/builtins/wait-for-job.js +162 -0
  189. package/dist/tools/builtins/wait-for-job.js.map +1 -0
  190. package/dist/tools/builtins/write-file.js +5 -0
  191. package/dist/tools/builtins/write-file.js.map +1 -1
  192. package/dist/tools/coordinator/index.d.ts.map +1 -1
  193. package/dist/tools/coordinator/index.js +1 -7
  194. package/dist/tools/coordinator/index.js.map +1 -1
  195. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  196. package/dist/tools/file-read-tracker.js +88 -10
  197. package/dist/tools/file-read-tracker.js.map +1 -1
  198. package/dist/types/agent/scheduler.d.ts +20 -0
  199. package/dist/types/agent/scheduler.d.ts.map +1 -1
  200. package/dist/types/agent/task.d.ts +20 -0
  201. package/dist/types/agent/task.d.ts.map +1 -1
  202. package/dist/types/connector/mcp.d.ts +205 -0
  203. package/dist/types/connector/mcp.d.ts.map +1 -1
  204. package/dist/types/message/index.d.ts +1 -1
  205. package/dist/types/message/index.d.ts.map +1 -1
  206. package/dist/types/message/index.js +2 -0
  207. package/dist/types/message/index.js.map +1 -1
  208. package/dist/types/run/entity.d.ts +13 -0
  209. package/dist/types/run/entity.d.ts.map +1 -1
  210. package/dist/types/run/events.d.ts +56 -0
  211. package/dist/types/run/events.d.ts.map +1 -1
  212. package/dist/types/run/events.js.map +1 -1
  213. package/dist/types/run/store.d.ts +41 -0
  214. package/dist/types/run/store.d.ts.map +1 -1
  215. package/dist/types/sandbox/index.d.ts +83 -15
  216. package/dist/types/sandbox/index.d.ts.map +1 -1
  217. package/dist/types/sandbox/index.js.map +1 -1
  218. package/dist/types/tool/index.d.ts +109 -0
  219. package/dist/types/tool/index.d.ts.map +1 -1
  220. package/dist/types/tool/index.js.map +1 -1
  221. package/dist/utils/env.d.ts +19 -0
  222. package/dist/utils/env.d.ts.map +1 -0
  223. package/dist/utils/env.js +25 -0
  224. package/dist/utils/env.js.map +1 -0
  225. package/package.json +1 -1
  226. package/src/bridge/a2a/mapper.ts +8 -0
  227. package/src/bridge/sse/mapper.ts +11 -0
  228. package/src/connector/index.ts +27 -0
  229. package/src/connector/mcp/adapter.ts +123 -10
  230. package/src/connector/mcp/audio-admission.ts +173 -0
  231. package/src/connector/mcp/client.ts +694 -45
  232. package/src/connector/mcp/envelope.ts +235 -0
  233. package/src/connector/mcp/era.ts +400 -0
  234. package/src/connector/mcp/errors.ts +171 -0
  235. package/src/connector/mcp/http-sse.ts +23 -6
  236. package/src/connector/mcp/index.ts +37 -0
  237. package/src/connector/mcp/streamable-http.ts +199 -11
  238. package/src/connector/mcp/x-mcp-header.ts +322 -0
  239. package/src/constants/mcp/index.ts +145 -16
  240. package/src/manager/agent/lifecycle.ts +29 -0
  241. package/src/manager/run/persistence.ts +12 -0
  242. package/src/prompt/coding-agent-doctrine.ts +31 -4
  243. package/src/prompt/index.ts +1 -0
  244. package/src/public-runtime.ts +37 -1
  245. package/src/public-tools.ts +18 -0
  246. package/src/registry/tool/execute.ts +2 -4
  247. package/src/registry/tool/portable.ts +264 -0
  248. package/src/registry/tool/schema.ts +38 -8
  249. package/src/registry/toolset/catalog.ts +8 -9
  250. package/src/runtime/jobs/awaited-jobs.ts +271 -0
  251. package/src/runtime/jobs/registry.ts +50 -0
  252. package/src/runtime/query/executor.ts +49 -1
  253. package/src/runtime/query/file-evidence-context.ts +190 -46
  254. package/src/runtime/query/file-evidence-replay.ts +776 -0
  255. package/src/runtime/query/file-evidence-seed.ts +126 -0
  256. package/src/runtime/query/index.ts +104 -2
  257. package/src/runtime/query/iteration/index.ts +202 -28
  258. package/src/runtime/query/iteration/phases/context.ts +10 -0
  259. package/src/runtime/query/iteration/phases/tool-review.ts +4 -0
  260. package/src/runtime/query/plugin-hooks.ts +20 -0
  261. package/src/runtime/query/repeat-call.ts +28 -18
  262. package/src/runtime/query/steering.ts +11 -0
  263. package/src/runtime/query/tooling.ts +3 -0
  264. package/src/sandbox/provider/local.ts +45 -2
  265. package/src/scheduler/completion-inbox.ts +105 -9
  266. package/src/scheduler/local.ts +8 -0
  267. package/src/store/run/disk.ts +108 -0
  268. package/src/tools/builtins/bash.ts +4 -10
  269. package/src/tools/builtins/edit-apply.ts +456 -0
  270. package/src/tools/builtins/edit.ts +39 -270
  271. package/src/tools/builtins/index.ts +9 -3
  272. package/src/tools/builtins/job.ts +1 -1
  273. package/src/tools/builtins/read-file.ts +56 -77
  274. package/src/tools/builtins/read-render.ts +104 -0
  275. package/src/tools/builtins/wait-for-job-bounds.ts +179 -0
  276. package/src/tools/builtins/wait-for-job.ts +184 -0
  277. package/src/tools/builtins/write-file.ts +5 -0
  278. package/src/tools/coordinator/index.ts +1 -7
  279. package/src/tools/file-read-tracker.ts +85 -7
  280. package/src/types/agent/scheduler.ts +21 -0
  281. package/src/types/agent/task.ts +21 -0
  282. package/src/types/connector/mcp.ts +205 -1
  283. package/src/types/message/index.ts +2 -0
  284. package/src/types/run/entity.ts +14 -0
  285. package/src/types/run/events.ts +56 -0
  286. package/src/types/run/store.ts +42 -0
  287. package/src/types/sandbox/index.ts +84 -15
  288. package/src/types/tool/index.ts +104 -0
  289. package/src/utils/env.ts +23 -0
@@ -0,0 +1,322 @@
1
+ import { MCP_PARAM_HEADER_PREFIX } from '../../constants/mcp/index.js'
2
+
3
+ /**
4
+ * The extension property a server writes into a tool parameter's schema to
5
+ * ask that the parameter's value be mirrored into an HTTP request header.
6
+ *
7
+ * Its value is the NAME PORTION of `Mcp-Param-{name}`, so an annotation
8
+ * reading `"Region"` produces `Mcp-Param-Region`. The point of mirroring is
9
+ * that a load balancer or a policy proxy can route and authorise a tool call
10
+ * without parsing JSON-RPC — which is also why a malformed annotation is not
11
+ * a cosmetic problem: a value carrying a line feed would end the field and
12
+ * let a server's own tool definition inject a header namzu never wrote.
13
+ */
14
+ const X_MCP_HEADER = 'x-mcp-header'
15
+
16
+ /**
17
+ * HTTP field-name token syntax — RFC 9110 §5.1 `1*tchar`.
18
+ *
19
+ * Non-emptiness (`+`), the absence of CR, LF and every other control
20
+ * character, and the absence of spaces and separators all fall out of this
21
+ * one expression. They are still checked separately below, in the order the
22
+ * spec lists them, so a refusal names the constraint that was actually
23
+ * broken rather than the most general one that happens to cover it.
24
+ */
25
+ const FIELD_NAME_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
26
+
27
+ /** The JSON Schema keywords whose value is a MAP of schemas, not a schema. */
28
+ const SCHEMA_MAP_KEYWORDS = new Set([
29
+ 'properties',
30
+ 'patternProperties',
31
+ '$defs',
32
+ 'definitions',
33
+ 'dependentSchemas',
34
+ ])
35
+
36
+ /**
37
+ * Keywords holding INSTANCE data rather than sub-schemas.
38
+ *
39
+ * Never descended into. A `default` of `{ "x-mcp-header": "X" }` is a
40
+ * perfectly ordinary example value for an object-typed parameter, and
41
+ * reading it as an annotation would refuse a tool over a string that was
42
+ * never a schema at all.
43
+ */
44
+ const INSTANCE_VALUE_KEYWORDS = new Set(['default', 'examples', 'example', 'const', 'enum'])
45
+
46
+ /** The parameter types a value may be mirrored from. `number` is excluded. */
47
+ const MIRRORABLE_TYPES = new Set(['string', 'boolean', 'integer'])
48
+
49
+ /**
50
+ * One validated `x-mcp-header` annotation: which header to write, which
51
+ * argument to read it from, and how to spell that argument.
52
+ *
53
+ * `path` is the exact chain of `properties` keys leading to the annotated
54
+ * property, which is also the path into a call's `arguments` object. It is
55
+ * produced only by {@link validateMcpHeaderAnnotations}, so a binding cannot
56
+ * exist for a property that was not statically reachable.
57
+ */
58
+ export interface McpParamHeaderBinding {
59
+ /** The full field name, prefix included — `Mcp-Param-Region`. */
60
+ readonly header: string
61
+ /** The chain of `properties` keys, read against the call's `arguments`. */
62
+ readonly path: readonly string[]
63
+ readonly type: 'string' | 'boolean' | 'integer'
64
+ }
65
+
66
+ /**
67
+ * Whether a tool definition may be exposed, and what it asked to mirror.
68
+ *
69
+ * A verdict about the WHOLE tool, not about one annotation: the spec makes a
70
+ * single bad annotation invalidate the tool definition, because a client
71
+ * that mirrored the rest would send a request the server then rejects for
72
+ * headers it cannot explain.
73
+ */
74
+ export type McpHeaderAnnotationVerdict =
75
+ | { readonly ok: true; readonly bindings: readonly McpParamHeaderBinding[] }
76
+ | { readonly ok: false; readonly reason: string }
77
+
78
+ const NO_BINDINGS: McpHeaderAnnotationVerdict = { ok: true, bindings: [] }
79
+
80
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
81
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
82
+ }
83
+
84
+ /** One annotation found anywhere in a schema, and where it was found. */
85
+ interface FoundAnnotation {
86
+ readonly value: unknown
87
+ /** A dotted path through the raw schema, for a refusal a person reads. */
88
+ readonly where: string
89
+ /** The `properties` chain, or `undefined` when it is not reachable by one. */
90
+ readonly path: readonly string[] | undefined
91
+ /** The `type` declared beside the annotation. */
92
+ readonly declaredType: unknown
93
+ }
94
+
95
+ /**
96
+ * Every property reachable from the root through `properties` keys ALONE,
97
+ * mapped to its path.
98
+ *
99
+ * This is the whole of the static-reachability rule, and writing it as its
100
+ * own walk is deliberate. The tempting implementation — resolve `$ref`s and
101
+ * flatten `allOf` first, then walk the result — accepts exactly what the
102
+ * spec forbids: a property reached through a `$ref` looks like an ordinary
103
+ * `properties` child once the reference has been followed. This walk never
104
+ * follows one, so a `$ref`-reached annotation simply is not in this map.
105
+ */
106
+ function reachableByProperties(root: Record<string, unknown>): Map<object, string[]> {
107
+ const reachable = new Map<object, string[]>()
108
+ // A schema is JSON and so is usually a tree, but nothing stops a caller
109
+ // building one with a shared (or cyclic) node in it, and this walk is
110
+ // depth-first.
111
+ const seen = new Set<object>([root])
112
+
113
+ const walk = (node: Record<string, unknown>, path: readonly string[]): void => {
114
+ const properties = node.properties
115
+ if (!isPlainObject(properties)) return
116
+ for (const [key, child] of Object.entries(properties)) {
117
+ if (!isPlainObject(child) || seen.has(child)) continue
118
+ seen.add(child)
119
+ const childPath = [...path, key]
120
+ reachable.set(child, childPath)
121
+ walk(child, childPath)
122
+ }
123
+ }
124
+
125
+ walk(root, [])
126
+ return reachable
127
+ }
128
+
129
+ /**
130
+ * Every `x-mcp-header` in the schema, wherever it sits.
131
+ *
132
+ * Deliberately a whole-schema scan rather than a scan of the reachable
133
+ * properties: an annotation under `items`, inside a `oneOf` branch or in
134
+ * `$defs` is not something to ignore, it is something that invalidates the
135
+ * tool. Finding only the reachable ones would silently admit exactly the
136
+ * definitions the spec says to refuse.
137
+ *
138
+ * The walk knows which keywords hold a MAP of schemas so that a parameter
139
+ * legitimately NAMED `x-mcp-header` is not mistaken for an annotation on the
140
+ * map that holds it, and refuses to descend into keywords holding instance
141
+ * data. Everything else is descended into generically, because a vendor
142
+ * keyword can nest a schema and an annotation buried in one is still
143
+ * unreachable and still invalid.
144
+ */
145
+ function annotationsIn(root: Record<string, unknown>): FoundAnnotation[] {
146
+ const reachable = reachableByProperties(root)
147
+ const found: FoundAnnotation[] = []
148
+ const seen = new Set<object>()
149
+
150
+ const visit = (node: unknown, where: string): void => {
151
+ if (Array.isArray(node)) {
152
+ node.forEach((item, index) => visit(item, `${where}[${index}]`))
153
+ return
154
+ }
155
+ if (!isPlainObject(node) || seen.has(node)) return
156
+ seen.add(node)
157
+
158
+ if (Object.hasOwn(node, X_MCP_HEADER)) {
159
+ found.push({
160
+ value: node[X_MCP_HEADER],
161
+ where: where === '' ? 'the schema root' : where,
162
+ path: reachable.get(node),
163
+ declaredType: node.type,
164
+ })
165
+ }
166
+
167
+ for (const [key, child] of Object.entries(node)) {
168
+ if (key === X_MCP_HEADER || INSTANCE_VALUE_KEYWORDS.has(key)) continue
169
+ const childWhere = where === '' ? key : `${where}.${key}`
170
+ if (SCHEMA_MAP_KEYWORDS.has(key)) {
171
+ if (!isPlainObject(child)) continue
172
+ for (const [name, schema] of Object.entries(child)) visit(schema, `${childWhere}.${name}`)
173
+ continue
174
+ }
175
+ visit(child, childWhere)
176
+ }
177
+ }
178
+
179
+ visit(root, '')
180
+ return found
181
+ }
182
+
183
+ /** A value quoted into a refusal without carrying its control bytes along. */
184
+ function quoted(value: unknown): string {
185
+ return typeof value === 'string' ? JSON.stringify(value) : String(value)
186
+ }
187
+
188
+ function describeType(declared: unknown): string {
189
+ if (declared === undefined) return 'a parameter with no declared `type`'
190
+ if (typeof declared !== 'string') return `a parameter whose \`type\` is ${quoted(declared)}`
191
+ return `a \`${declared}\` parameter`
192
+ }
193
+
194
+ function refuse(reason: string): McpHeaderAnnotationVerdict {
195
+ return { ok: false, reason }
196
+ }
197
+
198
+ /**
199
+ * Decide whether a tool may be exposed, and collect what it asked to mirror.
200
+ *
201
+ * The six constraints, in the spec's own order: non-empty; HTTP field-name
202
+ * token syntax; no control characters; case-insensitively unique across the
203
+ * whole `inputSchema`; applied only to `string`, `boolean` or `integer`
204
+ * (never `number`); and statically reachable through a chain of `properties`
205
+ * keys alone.
206
+ *
207
+ * Returns a REASON rather than throwing, because the caller's job is to drop
208
+ * one tool and keep the rest: a listing where one definition is malformed
209
+ * must still deliver the others, and an exception here would take the whole
210
+ * listing down with it.
211
+ */
212
+ export function validateMcpHeaderAnnotations(inputSchema: unknown): McpHeaderAnnotationVerdict {
213
+ if (!isPlainObject(inputSchema)) return NO_BINDINGS
214
+
215
+ const found = annotationsIn(inputSchema)
216
+ if (found.length === 0) return NO_BINDINGS
217
+
218
+ const bindings: McpParamHeaderBinding[] = []
219
+ /** Lowercased name to the spelling that claimed it first. */
220
+ const claimed = new Map<string, string>()
221
+
222
+ for (const annotation of found) {
223
+ const at = `at ${annotation.where}`
224
+ const name = annotation.value
225
+
226
+ if (typeof name !== 'string') {
227
+ return refuse(`\`x-mcp-header\` ${at} is ${quoted(name)}, which is not a string`)
228
+ }
229
+ if (name.length === 0) {
230
+ return refuse(`\`x-mcp-header\` ${at} is empty`)
231
+ }
232
+ if (/[\r\n]/.test(name)) {
233
+ return refuse(
234
+ `\`x-mcp-header\` ${quoted(name)} ${at} contains a carriage return or line feed`,
235
+ )
236
+ }
237
+ if (!FIELD_NAME_TOKEN.test(name)) {
238
+ return refuse(
239
+ `\`x-mcp-header\` ${quoted(name)} ${at} is not HTTP field-name token syntax (RFC 9110 1*tchar)`,
240
+ )
241
+ }
242
+
243
+ const first = claimed.get(name.toLowerCase())
244
+ if (first !== undefined) {
245
+ return refuse(
246
+ `\`x-mcp-header\` ${quoted(name)} ${at} repeats ${quoted(first)}; header names are compared without regard to case`,
247
+ )
248
+ }
249
+ claimed.set(name.toLowerCase(), name)
250
+
251
+ if (annotation.path === undefined) {
252
+ return refuse(
253
+ `\`x-mcp-header\` ${quoted(name)} ${at} is not statically reachable: the chain from the schema root must consist solely of \`properties\` keys`,
254
+ )
255
+ }
256
+
257
+ const declared = annotation.declaredType
258
+ if (typeof declared !== 'string' || !MIRRORABLE_TYPES.has(declared)) {
259
+ return refuse(
260
+ `\`x-mcp-header\` ${quoted(name)} ${at} annotates ${describeType(declared)}; only \`string\`, \`boolean\` and \`integer\` may be mirrored, and \`number\` is excluded`,
261
+ )
262
+ }
263
+
264
+ bindings.push({
265
+ header: `${MCP_PARAM_HEADER_PREFIX}${name}`,
266
+ path: annotation.path,
267
+ type: declared as McpParamHeaderBinding['type'],
268
+ })
269
+ }
270
+
271
+ return { ok: true, bindings }
272
+ }
273
+
274
+ function readPath(root: unknown, path: readonly string[]): unknown {
275
+ let cursor: unknown = root
276
+ for (const key of path) {
277
+ if (!isPlainObject(cursor)) return undefined
278
+ cursor = cursor[key]
279
+ }
280
+ return cursor
281
+ }
282
+
283
+ /**
284
+ * How one argument value is spelled in a header field, or `undefined` when
285
+ * it is not sent at all.
286
+ *
287
+ * Four reasons a header is omitted, and only the first two are ordinary: the
288
+ * argument is absent, or it is `null` — both of which the spec says to omit
289
+ * rather than send empty. The other two are a server contradicting its own
290
+ * schema (a value whose runtime type is not the declared one) and an integer
291
+ * outside the range the spec bounds these to, ±(2^53−1) — exactly
292
+ * JavaScript's safe-integer range, which is the range in which the number
293
+ * this client parsed is still the number the server sent. Sending either
294
+ * would put a value on the wire that disagrees with the body it mirrors,
295
+ * which is the one thing a conforming server rejects outright.
296
+ */
297
+ function headerValue(type: McpParamHeaderBinding['type'], value: unknown): string | undefined {
298
+ if (value === undefined || value === null) return undefined
299
+ if (type === 'string') return typeof value === 'string' ? value : undefined
300
+ if (type === 'boolean') return typeof value === 'boolean' ? String(value) : undefined
301
+ return typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : undefined
302
+ }
303
+
304
+ /**
305
+ * The `Mcp-Param-*` field values for one call's arguments, unencoded.
306
+ *
307
+ * Returns the values as the BODY carries them; wrapping a value that cannot
308
+ * be written into a header field verbatim is the envelope's job, so the
309
+ * sentinel rule lives in one place for `Mcp-Name` and `Mcp-Param-*` alike
310
+ * rather than being reimplemented here.
311
+ */
312
+ export function mcpParamHeaderValues(
313
+ bindings: readonly McpParamHeaderBinding[],
314
+ args: unknown,
315
+ ): Record<string, string> {
316
+ const headers: Record<string, string> = {}
317
+ for (const binding of bindings) {
318
+ const written = headerValue(binding.type, readPath(args, binding.path))
319
+ if (written !== undefined) headers[binding.header] = written
320
+ }
321
+ return headers
322
+ }
@@ -1,26 +1,65 @@
1
+ import type { McpLegacyVersion, McpModernVersion } from '../../types/connector/mcp.js'
2
+
1
3
  /**
2
- * The protocol version this client ADVERTISES in `initialize`.
4
+ * The MCP spec revisions this client speaks WITHOUT the `initialize`
5
+ * handshake, newest first.
3
6
  *
4
- * Deliberately the version namzu actually implements, not the newest one
5
- * published. Advertising a version whose requirements are unimplemented is
6
- * worse than advertising an older one honestly: the server tailors its
7
- * behavior to what the client claims, and the mismatch surfaces later as
8
- * a malformed exchange rather than a clean negotiation failure. Raising
9
- * this is a conformance task, not a constant edit.
7
+ * The modern era removes that handshake entirely: a connection is resolved
8
+ * by probing with `server/discover`, and every request afterwards carries
9
+ * its own protocol version, client capabilities and client info in `_meta`
10
+ * instead of inheriting them from a session. `connect()` tries the newest
11
+ * entry here FIRST and falls back to the legacy handshake below only when
12
+ * the probe says the peer does not speak it.
10
13
  */
11
- export const MCP_PROTOCOL_VERSION = '2024-11-05'
14
+ export const MCP_MODERN_VERSIONS: readonly McpModernVersion[] = ['2026-07-28']
12
15
 
13
16
  /**
14
- * Versions this client can speak if a server negotiates down (or up) to
15
- * one of them, newest first.
17
+ * Protocol versions this client offers and accepts through the legacy
18
+ * `initialize` handshake, newest first.
19
+ *
20
+ * `connect()` offers `MCP_LEGACY_VERSIONS[0]` — the newest one — in a
21
+ * single `initialize` request. A server is free to answer with any version
22
+ * in `MCP_SUPPORTED_PROTOCOL_VERSIONS` below, not only the one offered:
23
+ * that is how the handshake is specified. namzu used to advertise and
24
+ * accept only `2024-11-05`, which refused the overwhelming majority of
25
+ * servers deployed since — every one that negotiated down to 2025-03-26,
26
+ * 2025-06-18 or 2025-11-25 instead of all the way to the oldest revision.
16
27
  *
17
- * A server is free to answer `initialize` with a version other than the one
18
- * the client asked for — that is how the handshake is specified. namzu used
19
- * to ignore the answer entirely and carry on regardless, so a server
20
- * responding with a version we cannot speak looked exactly like a
21
- * successful connection until something downstream broke oddly.
28
+ * This is deliberately a single round trip, never a per-version waterfall:
29
+ * the spec's own backward-compatibility algorithm offers one version and
30
+ * honors whatever the server answers
31
+ * (https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle).
32
+ * A three-step retry loop is three times the latency for a code path no
33
+ * server expects, and `protocol-negotiation.test.ts` counts `initialize`
34
+ * frames so nobody reintroduces one.
22
35
  */
23
- export const MCP_SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = ['2024-11-05']
36
+ export const MCP_LEGACY_VERSIONS: readonly McpLegacyVersion[] = [
37
+ '2025-11-25',
38
+ '2025-06-18',
39
+ '2025-03-26',
40
+ '2024-11-05',
41
+ ]
42
+
43
+ /**
44
+ * Every protocol version this client can speak, in any capacity — offered
45
+ * in `initialize`, or merely a value it will not refuse if a server
46
+ * answers with it.
47
+ *
48
+ * The single authority for "can we speak this?": a server negotiating to
49
+ * a version outside this list is refused rather than let through to break
50
+ * confusingly downstream. Advertising or accepting a version whose
51
+ * requirements are unimplemented is worse than being honest about a
52
+ * narrower set — the server tailors its behavior to what the client
53
+ * claims, and the mismatch would otherwise surface later as a malformed
54
+ * exchange instead of a clean negotiation failure. That reasoning is why
55
+ * this list once held a single entry; it now argues for the broadened set
56
+ * above, not against it, because every version listed here is one namzu
57
+ * has actually verified it can carry a legacy `initialize` handshake for.
58
+ */
59
+ export const MCP_SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = [
60
+ ...MCP_MODERN_VERSIONS,
61
+ ...MCP_LEGACY_VERSIONS,
62
+ ]
24
63
 
25
64
  /**
26
65
  * Default deadline for one MCP JSON-RPC round trip.
@@ -33,3 +72,93 @@ export const DEFAULT_MCP_REQUEST_TIMEOUT_MS = 30_000
33
72
 
34
73
  /** JSON-RPC 2.0 reserved code for an unimplemented method. */
35
74
  export const JSON_RPC_METHOD_NOT_FOUND = -32601
75
+
76
+ /**
77
+ * JSON-RPC codes a server may use to say "that resource does not exist".
78
+ *
79
+ * The current spec's resource-read error path uses the application-defined
80
+ * `-32002`, but a server on an older version may still answer with the
81
+ * generic `-32602` ("Invalid params") for the same condition — and the spec
82
+ * explicitly says a client SHOULD keep accepting it. Both are listed so a
83
+ * caller distinguishing "not found" from "actually broken" does not have to
84
+ * special-case the older code itself.
85
+ */
86
+ export const RESOURCE_NOT_FOUND_CODES: readonly number[] = [-32602, -32002]
87
+
88
+ /**
89
+ * The method a modern server answers with its `DiscoverResult`.
90
+ *
91
+ * Mandatory for a 2026-07-28 server and optional for a client, which is
92
+ * exactly what makes it a usable era probe: a server that answers it speaks
93
+ * the modern protocol, and a legacy server answers it with an
94
+ * implementation-defined error — commonly `-32601` or `-32602` — or not at
95
+ * all.
96
+ */
97
+ export const MCP_DISCOVER_METHOD = 'server/discover'
98
+
99
+ /**
100
+ * How long an era probe waits for an answer before deciding the peer is
101
+ * legacy.
102
+ *
103
+ * A heuristic with no good universal value, which is why it is
104
+ * configurable per client (`MCPClientConfig.eraProbeTimeoutMs`). Too short
105
+ * and a slow-starting server is misclassified as legacy; too long and every
106
+ * legacy server pays the wait on every connect. Two seconds is long enough
107
+ * for a process that has already spawned to answer one request and short
108
+ * enough to sit comfortably inside an operator-facing connect deadline —
109
+ * and the era cache means a given origin or command pays it once, not once
110
+ * per connection.
111
+ */
112
+ export const DEFAULT_MCP_ERA_PROBE_TIMEOUT_MS = 2_000
113
+
114
+ /**
115
+ * The HTTP statuses a legacy origin answers a modern request with.
116
+ *
117
+ * A status alone is NOT the fallback signal: a modern server answers an
118
+ * unknown method with `404` and a JSON-RPC `-32601` body specifically so a
119
+ * client can tell it apart from the `404` of a server that has never heard
120
+ * of the modern protocol. The body decides; this list only says which
121
+ * responses are worth reading a body from.
122
+ */
123
+ export const MCP_MODERN_HTTP_FALLBACK_STATUSES: readonly number[] = [400, 404, 405]
124
+
125
+ /**
126
+ * The reserved `_meta` keys a modern request and reply carry.
127
+ *
128
+ * `protocolVersion` and `clientCapabilities` are REQUIRED on every modern
129
+ * request; `clientInfo` is a SHOULD. `serverInfo` travels the other way —
130
+ * it is how a `DiscoverResult` names the peer, and is what `connect()`
131
+ * reads to synthesise the `MCPInitializeResult` a host still expects.
132
+ */
133
+ export const MCP_META_PROTOCOL_VERSION = 'io.modelcontextprotocol/protocolVersion'
134
+ export const MCP_META_CLIENT_CAPABILITIES = 'io.modelcontextprotocol/clientCapabilities'
135
+ export const MCP_META_CLIENT_INFO = 'io.modelcontextprotocol/clientInfo'
136
+ export const MCP_META_SERVER_INFO = 'io.modelcontextprotocol/serverInfo'
137
+
138
+ /**
139
+ * The request headers a modern Streamable HTTP request mirrors its body
140
+ * with, so an intermediary can route and authorise a call without parsing
141
+ * JSON-RPC.
142
+ *
143
+ * `MCP_PROTOCOL_VERSION_HEADER` is shared with the later legacy revisions,
144
+ * where it means the version negotiated by `initialize` rather than the one
145
+ * carried in this request's own `_meta`.
146
+ */
147
+ export const MCP_PROTOCOL_VERSION_HEADER = 'MCP-Protocol-Version'
148
+ export const MCP_METHOD_HEADER = 'Mcp-Method'
149
+ export const MCP_NAME_HEADER = 'Mcp-Name'
150
+ export const MCP_PARAM_HEADER_PREFIX = 'Mcp-Param-'
151
+
152
+ /**
153
+ * The methods whose target is named in the `Mcp-Name` header, and the
154
+ * parameter each one's name is read from.
155
+ *
156
+ * Required for compliance on Streamable HTTP: an intermediary that can see
157
+ * WHICH tool is being called without reading the body is the whole point of
158
+ * mirroring it into a header.
159
+ */
160
+ export const MCP_NAME_HEADER_METHODS: Readonly<Record<string, 'name' | 'uri'>> = {
161
+ 'tools/call': 'name',
162
+ 'prompts/get': 'name',
163
+ 'resources/read': 'uri',
164
+ }
@@ -254,6 +254,7 @@ export class AgentManager {
254
254
  depth: context.depth,
255
255
  ...(options.planId ? { planId: options.planId } : {}),
256
256
  ...(options.planStepId ? { planStepId: options.planStepId } : {}),
257
+ ...displayLabels(options),
257
258
  })
258
259
  entry.ready = true
259
260
  } catch (error) {
@@ -548,6 +549,7 @@ export class AgentManager {
548
549
  depth: context.depth,
549
550
  ...(options.planId ? { planId: options.planId } : {}),
550
551
  ...(options.planStepId ? { planStepId: options.planStepId } : {}),
552
+ ...displayLabels(options),
551
553
  })
552
554
 
553
555
  const lineage: Lineage = {
@@ -1588,6 +1590,33 @@ export class AgentManager {
1588
1590
  }
1589
1591
  }
1590
1592
 
1593
+ /**
1594
+ * The display grouping a host supplied at spawn, in the shape `agent_pending`
1595
+ * carries it.
1596
+ *
1597
+ * Present only when named. A blank workflow spread onto the event would make a
1598
+ * host that grouped nothing indistinguishable from one that grouped everything
1599
+ * under an empty label, and the two are different answers to a consumer
1600
+ * rebuilding the operator's view.
1601
+ *
1602
+ * Nothing in this file reads the values back: they are display annotations
1603
+ * only; they do not create dependencies, barriers, or serial execution.
1604
+ * Admission, ordering and capacity are decided above without consulting them.
1605
+ */
1606
+ function displayLabels(options: DelegationDisplayLabels): DelegationDisplayLabels {
1607
+ return {
1608
+ ...(options.workflow ? { workflow: options.workflow } : {}),
1609
+ ...(options.phase ? { phase: options.phase } : {}),
1610
+ ...(options.phaseDetail ? { phaseDetail: options.phaseDetail } : {}),
1611
+ ...(options.phaseOrder !== undefined ? { phaseOrder: options.phaseOrder } : {}),
1612
+ }
1613
+ }
1614
+
1615
+ type DelegationDisplayLabels = Pick<
1616
+ SendMessageOptions,
1617
+ 'workflow' | 'phase' | 'phaseDetail' | 'phaseOrder'
1618
+ >
1619
+
1591
1620
  /**
1592
1621
  * Maps a {@link BaseAgentResult} to {@link SessionSummaryOutcome}. Phase 6
1593
1622
  * INTERPRETATION: `completed` → `succeeded`; any other status → `failed`.
@@ -489,6 +489,18 @@ export class RunPersistence {
489
489
  this.run.abandonedTaskIds = [...taskIds]
490
490
  }
491
491
 
492
+ /**
493
+ * Name the awaited background jobs this run ended without waiting for.
494
+ *
495
+ * See {@link Run.abandonedJobIds}. The same rule as the task list above:
496
+ * recording, never stopping — what happens to a job is decided by whoever
497
+ * owns it, not by the run that gave up waiting.
498
+ */
499
+ setAbandonedJobIds(jobIds: readonly string[]): void {
500
+ if (jobIds.length === 0) return
501
+ this.run.abandonedJobIds = [...jobIds]
502
+ }
503
+
492
504
  setSteps(steps: readonly StepResult[]): void {
493
505
  this.run.steps = steps
494
506
  }
@@ -94,6 +94,17 @@ You are in plan mode. Read, search and think; do not change anything. \`read\`,
94
94
 
95
95
  When you have understood the task, reply with the plan: what you would change, in which files, in what order, and what you would verify. Be concrete — name files and functions — and short enough to read in one screen. Then stop and wait; the user will leave plan mode to have the plan carried out, and that switch is their approval.`
96
96
 
97
+ /**
98
+ * Appended after {@link CODING_AGENT_DELEGATION_DOCTRINE} only for a host
99
+ * session that has an orchestration mode turned on. See
100
+ * {@link CodingAgentDoctrineOptions.orchestrate}: this text is a stronger
101
+ * restatement of the existing delegation rule, not a new capability — it
102
+ * creates no roster, starts no work by itself, and names no tool the base
103
+ * doctrine did not already name.
104
+ */
105
+ export const CODING_AGENT_ORCHESTRATE_DOCTRINE = `### Orchestrate mode
106
+ This session has orchestrate mode on: treat delegation through \`Agent\` as the default for substantive work, not the exception. Before doing multi-step work yourself, ask whether an independent piece of it — a lookup, a draft, a check — could run as its own delegation, and prefer delegating it when the answer is yes. This changes only how eagerly you reach for \`Agent\` on work you would otherwise do inline; it does not mount a roster or start any delegation by itself.`
107
+
97
108
  export const CODING_AGENT_DOCTRINE_CONTRIBUTION_ID = 'namzu.coding-agent-doctrine'
98
109
 
99
110
  export interface CodingAgentDoctrineOptions {
@@ -103,6 +114,17 @@ export interface CodingAgentDoctrineOptions {
103
114
  * `task_create`, passes `false`.
104
115
  */
105
116
  delegation?: boolean
117
+ /**
118
+ * Append {@link CODING_AGENT_ORCHESTRATE_DOCTRINE} after the delegation
119
+ * rules, for a host session whose operator turned on an orchestration
120
+ * mode. Default `false`, and ignored when `delegation` is `false` — a
121
+ * sub-agent prompt never carries delegation guidance of any strength.
122
+ * Display/prompt-only: it creates no scheduling, no roster and no kernel
123
+ * behaviour change, the same guarantee the `workflow`/`phase` labels on
124
+ * `agent_pending` make. With this left at its default the rendered text
125
+ * is byte-identical to before this option existed.
126
+ */
127
+ orchestrate?: boolean
106
128
  }
107
129
 
108
130
  /**
@@ -114,10 +136,15 @@ export interface CodingAgentDoctrineOptions {
114
136
  export function codingAgentDoctrineContribution(
115
137
  options: CodingAgentDoctrineOptions = {},
116
138
  ): PromptContribution {
117
- const text =
118
- options.delegation === false
119
- ? CODING_AGENT_WORKING_DOCTRINE
120
- : `${CODING_AGENT_WORKING_DOCTRINE}\n\n${CODING_AGENT_DELEGATION_DOCTRINE}`
139
+ const text = [
140
+ CODING_AGENT_WORKING_DOCTRINE,
141
+ options.delegation === false ? undefined : CODING_AGENT_DELEGATION_DOCTRINE,
142
+ options.delegation !== false && options.orchestrate
143
+ ? CODING_AGENT_ORCHESTRATE_DOCTRINE
144
+ : undefined,
145
+ ]
146
+ .filter((part): part is string => Boolean(part))
147
+ .join('\n\n')
121
148
  return {
122
149
  id: CODING_AGENT_DOCTRINE_CONTRIBUTION_ID,
123
150
  placement: 'static',
@@ -16,6 +16,7 @@ export type {
16
16
  export {
17
17
  CODING_AGENT_DELEGATION_DOCTRINE,
18
18
  CODING_AGENT_DOCTRINE_CONTRIBUTION_ID,
19
+ CODING_AGENT_ORCHESTRATE_DOCTRINE,
19
20
  CODING_AGENT_WORKING_DOCTRINE,
20
21
  PLAN_MODE_DOCTRINE,
21
22
  codingAgentDoctrineContribution,