@namzu/sdk 40.0.0 → 42.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.
- package/CHANGELOG.md +236 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +3 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +11 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/runAgent.d.ts +14 -0
- package/dist/agents/runAgent.d.ts.map +1 -1
- package/dist/agents/runAgent.js +3 -0
- package/dist/agents/runAgent.js.map +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +8 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +11 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/connector/index.d.ts +2 -2
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/adapter.d.ts.map +1 -1
- package/dist/connector/mcp/adapter.js +92 -4
- package/dist/connector/mcp/adapter.js.map +1 -1
- package/dist/connector/mcp/audio-admission.d.ts +17 -0
- package/dist/connector/mcp/audio-admission.d.ts.map +1 -0
- package/dist/connector/mcp/audio-admission.js +171 -0
- package/dist/connector/mcp/audio-admission.js.map +1 -0
- package/dist/connector/mcp/client.d.ts +252 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +611 -39
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/envelope.d.ts +91 -0
- package/dist/connector/mcp/envelope.d.ts.map +1 -0
- package/dist/connector/mcp/envelope.js +173 -0
- package/dist/connector/mcp/envelope.js.map +1 -0
- package/dist/connector/mcp/era.d.ts +130 -0
- package/dist/connector/mcp/era.d.ts.map +1 -0
- package/dist/connector/mcp/era.js +304 -0
- package/dist/connector/mcp/era.js.map +1 -0
- package/dist/connector/mcp/errors.d.ts +106 -0
- package/dist/connector/mcp/errors.d.ts.map +1 -0
- package/dist/connector/mcp/errors.js +154 -0
- package/dist/connector/mcp/errors.js.map +1 -0
- package/dist/connector/mcp/http-sse.d.ts +11 -0
- package/dist/connector/mcp/http-sse.d.ts.map +1 -1
- package/dist/connector/mcp/http-sse.js +21 -6
- package/dist/connector/mcp/http-sse.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +7 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +10 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts +83 -0
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
- package/dist/connector/mcp/streamable-http.js +177 -11
- package/dist/connector/mcp/streamable-http.js.map +1 -1
- package/dist/connector/mcp/x-mcp-header.d.ts +56 -0
- package/dist/connector/mcp/x-mcp-header.d.ts.map +1 -0
- package/dist/connector/mcp/x-mcp-header.js +254 -0
- package/dist/connector/mcp/x-mcp-header.js.map +1 -0
- package/dist/constants/mcp/index.d.ts +123 -15
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +135 -16
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +43 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +20 -0
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +19 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/prompt/index.d.ts +1 -1
- package/dist/prompt/index.d.ts.map +1 -1
- package/dist/prompt/index.js +1 -1
- package/dist/prompt/index.js.map +1 -1
- package/dist/public-runtime.d.ts +7 -4
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +16 -4
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -1
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +4 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +10 -1
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/runtime/bidi/session.d.ts +11 -0
- package/dist/runtime/bidi/session.d.ts.map +1 -1
- package/dist/runtime/bidi/session.js +2 -0
- package/dist/runtime/bidi/session.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +6 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +6 -0
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/guardrail-presets.d.ts +187 -1
- package/dist/runtime/query/guardrail-presets.d.ts.map +1 -1
- package/dist/runtime/query/guardrail-presets.js +298 -0
- package/dist/runtime/query/guardrail-presets.js.map +1 -1
- package/dist/runtime/query/index.d.ts +14 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +3 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +3 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/sandbox/provider/local.d.ts.map +1 -1
- package/dist/sandbox/provider/local.js +46 -3
- package/dist/sandbox/provider/local.js.map +1 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +8 -0
- package/dist/scheduler/local.js.map +1 -1
- package/dist/store/run/disk.d.ts +35 -1
- package/dist/store/run/disk.d.ts.map +1 -1
- package/dist/store/run/disk.js +100 -0
- package/dist/store/run/disk.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +17 -2
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +17 -3
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/untrusted-envelope.d.ts +35 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +91 -3
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/types/agent/base.d.ts +23 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +20 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +39 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/connector/mcp.d.ts +205 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +56 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/store.d.ts +41 -0
- package/dist/types/run/store.d.ts.map +1 -1
- package/dist/types/sandbox/index.d.ts +68 -1
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +19 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +3 -0
- package/src/agents/SupervisorAgent.ts +11 -0
- package/src/agents/runAgent.ts +18 -0
- package/src/bridge/a2a/mapper.ts +8 -0
- package/src/bridge/sse/mapper.ts +11 -0
- package/src/connector/index.ts +27 -0
- package/src/connector/mcp/adapter.ts +103 -4
- package/src/connector/mcp/audio-admission.ts +173 -0
- package/src/connector/mcp/client.ts +694 -45
- package/src/connector/mcp/envelope.ts +235 -0
- package/src/connector/mcp/era.ts +400 -0
- package/src/connector/mcp/errors.ts +171 -0
- package/src/connector/mcp/http-sse.ts +23 -6
- package/src/connector/mcp/index.ts +37 -0
- package/src/connector/mcp/streamable-http.ts +199 -11
- package/src/connector/mcp/x-mcp-header.ts +322 -0
- package/src/constants/mcp/index.ts +145 -16
- package/src/manager/agent/lifecycle.ts +51 -0
- package/src/prompt/coding-agent-doctrine.ts +31 -4
- package/src/prompt/index.ts +1 -0
- package/src/public-runtime.ts +42 -0
- package/src/public-tools.ts +8 -2
- package/src/registry/tool/execute.ts +9 -1
- package/src/runtime/bidi/session.ts +13 -0
- package/src/runtime/query/executor.ts +13 -0
- package/src/runtime/query/guardrail-presets.ts +356 -0
- package/src/runtime/query/index.ts +17 -0
- package/src/runtime/query/tooling.ts +5 -0
- package/src/sandbox/provider/local.ts +45 -2
- package/src/scheduler/local.ts +8 -0
- package/src/store/run/disk.ts +108 -0
- package/src/tools/coordinator/agent.ts +17 -2
- package/src/tools/coordinator/index.ts +17 -3
- package/src/tools/untrusted-envelope.ts +94 -3
- package/src/types/agent/base.ts +24 -0
- package/src/types/agent/scheduler.ts +21 -0
- package/src/types/agent/task.ts +41 -0
- package/src/types/connector/mcp.ts +205 -1
- package/src/types/run/events.ts +56 -0
- package/src/types/run/store.ts +42 -0
- package/src/types/sandbox/index.ts +69 -1
- package/src/types/tool/index.ts +20 -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
|
|
4
|
+
* The MCP spec revisions this client speaks WITHOUT the `initialize`
|
|
5
|
+
* handshake, newest first.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
14
|
+
export const MCP_MODERN_VERSIONS: readonly McpModernVersion[] = ['2026-07-28']
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
*
|
|
18
|
-
* the
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
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 = {
|
|
@@ -674,6 +676,28 @@ export class AgentManager {
|
|
|
674
676
|
}
|
|
675
677
|
}
|
|
676
678
|
|
|
679
|
+
// The screens in force for the parent run, stamped onto the child
|
|
680
|
+
// the way the trace parent and the review handler are — and after
|
|
681
|
+
// both branches, because the bare-config branch builds its whole
|
|
682
|
+
// config by hand and would otherwise omit this one field.
|
|
683
|
+
//
|
|
684
|
+
// A child run builds its own executor, which installs
|
|
685
|
+
// `DEFAULT_TOOL_RESULT_GUARDRAILS` when nothing said otherwise — so
|
|
686
|
+
// without this a parent that turned the screens off, or exempted a
|
|
687
|
+
// tool it knows, had that decision revert the moment it delegated,
|
|
688
|
+
// in the half of its work it does not watch. `BaseAgentConfig`'s
|
|
689
|
+
// own docblock has claimed this inheritance since the screens
|
|
690
|
+
// shipped; until this existed it was a claim the kernel did not
|
|
691
|
+
// implement. An explicit `configOverrides` still wins, so a host
|
|
692
|
+
// can hand one child a different set — which is also why the test
|
|
693
|
+
// is against `undefined` rather than truthiness: `[]` is a
|
|
694
|
+
// decision ("no screens"), not an absence.
|
|
695
|
+
const inheritedScreens =
|
|
696
|
+
options.configOverrides?.toolResultGuardrails ?? context.toolResultGuardrails
|
|
697
|
+
if (inheritedScreens !== undefined) {
|
|
698
|
+
childConfig.toolResultGuardrails = inheritedScreens
|
|
699
|
+
}
|
|
700
|
+
|
|
677
701
|
// Lineage is assigned by the spawning manager, not proposed by the
|
|
678
702
|
// child definition. A fixed configBuilder can ignore its inputs and
|
|
679
703
|
// configOverrides is caller-authored; neither may turn a child back
|
|
@@ -1588,6 +1612,33 @@ export class AgentManager {
|
|
|
1588
1612
|
}
|
|
1589
1613
|
}
|
|
1590
1614
|
|
|
1615
|
+
/**
|
|
1616
|
+
* The display grouping a host supplied at spawn, in the shape `agent_pending`
|
|
1617
|
+
* carries it.
|
|
1618
|
+
*
|
|
1619
|
+
* Present only when named. A blank workflow spread onto the event would make a
|
|
1620
|
+
* host that grouped nothing indistinguishable from one that grouped everything
|
|
1621
|
+
* under an empty label, and the two are different answers to a consumer
|
|
1622
|
+
* rebuilding the operator's view.
|
|
1623
|
+
*
|
|
1624
|
+
* Nothing in this file reads the values back: they are display annotations
|
|
1625
|
+
* only; they do not create dependencies, barriers, or serial execution.
|
|
1626
|
+
* Admission, ordering and capacity are decided above without consulting them.
|
|
1627
|
+
*/
|
|
1628
|
+
function displayLabels(options: DelegationDisplayLabels): DelegationDisplayLabels {
|
|
1629
|
+
return {
|
|
1630
|
+
...(options.workflow ? { workflow: options.workflow } : {}),
|
|
1631
|
+
...(options.phase ? { phase: options.phase } : {}),
|
|
1632
|
+
...(options.phaseDetail ? { phaseDetail: options.phaseDetail } : {}),
|
|
1633
|
+
...(options.phaseOrder !== undefined ? { phaseOrder: options.phaseOrder } : {}),
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
type DelegationDisplayLabels = Pick<
|
|
1638
|
+
SendMessageOptions,
|
|
1639
|
+
'workflow' | 'phase' | 'phaseDetail' | 'phaseOrder'
|
|
1640
|
+
>
|
|
1641
|
+
|
|
1591
1642
|
/**
|
|
1592
1643
|
* Maps a {@link BaseAgentResult} to {@link SessionSummaryOutcome}. Phase 6
|
|
1593
1644
|
* INTERPRETATION: `completed` → `succeeded`; any other status → `failed`.
|
|
@@ -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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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',
|
package/src/prompt/index.ts
CHANGED