@namzu/sdk 62.12.0 → 62.14.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 (248) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/agents/file-definitions.d.ts +7 -0
  3. package/dist/agents/file-definitions.d.ts.map +1 -1
  4. package/dist/agents/file-definitions.js +20 -0
  5. package/dist/agents/file-definitions.js.map +1 -1
  6. package/dist/agents/index.d.ts +1 -1
  7. package/dist/agents/index.d.ts.map +1 -1
  8. package/dist/agents/index.js +1 -1
  9. package/dist/agents/index.js.map +1 -1
  10. package/dist/bridge/acp/server.d.ts +8 -1
  11. package/dist/bridge/acp/server.d.ts.map +1 -1
  12. package/dist/bridge/acp/server.js +12 -0
  13. package/dist/bridge/acp/server.js.map +1 -1
  14. package/dist/connector/index.d.ts +1 -1
  15. package/dist/connector/index.d.ts.map +1 -1
  16. package/dist/connector/index.js +1 -1
  17. package/dist/connector/index.js.map +1 -1
  18. package/dist/connector/mcp/client.d.ts +96 -0
  19. package/dist/connector/mcp/client.d.ts.map +1 -1
  20. package/dist/connector/mcp/client.js +488 -13
  21. package/dist/connector/mcp/client.js.map +1 -1
  22. package/dist/connector/mcp/index.d.ts +1 -0
  23. package/dist/connector/mcp/index.d.ts.map +1 -1
  24. package/dist/connector/mcp/index.js +1 -0
  25. package/dist/connector/mcp/index.js.map +1 -1
  26. package/dist/connector/mcp/tasks.d.ts +40 -0
  27. package/dist/connector/mcp/tasks.d.ts.map +1 -0
  28. package/dist/connector/mcp/tasks.js +55 -0
  29. package/dist/connector/mcp/tasks.js.map +1 -0
  30. package/dist/constants/data-epoch.d.ts +18 -0
  31. package/dist/constants/data-epoch.d.ts.map +1 -0
  32. package/dist/constants/data-epoch.js +18 -0
  33. package/dist/constants/data-epoch.js.map +1 -0
  34. package/dist/constants/index.d.ts +1 -0
  35. package/dist/constants/index.d.ts.map +1 -1
  36. package/dist/constants/index.js +1 -0
  37. package/dist/constants/index.js.map +1 -1
  38. package/dist/constants/mcp/index.d.ts +1 -1
  39. package/dist/constants/mcp/index.d.ts.map +1 -1
  40. package/dist/constants/mcp/index.js +5 -0
  41. package/dist/constants/mcp/index.js.map +1 -1
  42. package/dist/constants/telemetry/index.d.ts +12 -0
  43. package/dist/constants/telemetry/index.d.ts.map +1 -1
  44. package/dist/constants/telemetry/index.js +12 -0
  45. package/dist/constants/telemetry/index.js.map +1 -1
  46. package/dist/eval/bootstrap.d.ts +122 -0
  47. package/dist/eval/bootstrap.d.ts.map +1 -0
  48. package/dist/eval/bootstrap.js +227 -0
  49. package/dist/eval/bootstrap.js.map +1 -0
  50. package/dist/eval/compare.d.ts +109 -0
  51. package/dist/eval/compare.d.ts.map +1 -0
  52. package/dist/eval/compare.js +324 -0
  53. package/dist/eval/compare.js.map +1 -0
  54. package/dist/eval/disclosure.d.ts +70 -0
  55. package/dist/eval/disclosure.d.ts.map +1 -0
  56. package/dist/eval/disclosure.js +64 -0
  57. package/dist/eval/disclosure.js.map +1 -0
  58. package/dist/eval/experiment.d.ts +23 -0
  59. package/dist/eval/experiment.d.ts.map +1 -1
  60. package/dist/eval/experiment.js +87 -15
  61. package/dist/eval/experiment.js.map +1 -1
  62. package/dist/eval/from-turn.d.ts.map +1 -1
  63. package/dist/eval/from-turn.js +5 -0
  64. package/dist/eval/from-turn.js.map +1 -1
  65. package/dist/eval/index.d.ts +13 -2
  66. package/dist/eval/index.d.ts.map +1 -1
  67. package/dist/eval/index.js +6 -1
  68. package/dist/eval/index.js.map +1 -1
  69. package/dist/eval/report-stats.d.ts +77 -0
  70. package/dist/eval/report-stats.d.ts.map +1 -0
  71. package/dist/eval/report-stats.js +184 -0
  72. package/dist/eval/report-stats.js.map +1 -0
  73. package/dist/eval/run-options.d.ts +30 -0
  74. package/dist/eval/run-options.d.ts.map +1 -0
  75. package/dist/eval/run-options.js +20 -0
  76. package/dist/eval/run-options.js.map +1 -0
  77. package/dist/eval/run-summary.d.ts +36 -0
  78. package/dist/eval/run-summary.d.ts.map +1 -0
  79. package/dist/eval/run-summary.js +57 -0
  80. package/dist/eval/run-summary.js.map +1 -0
  81. package/dist/eval/scorers.d.ts +32 -0
  82. package/dist/eval/scorers.d.ts.map +1 -1
  83. package/dist/eval/scorers.js +77 -0
  84. package/dist/eval/scorers.js.map +1 -1
  85. package/dist/eval/types.d.ts +74 -0
  86. package/dist/eval/types.d.ts.map +1 -1
  87. package/dist/manager/session/turn-recorder.d.ts +3 -0
  88. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  89. package/dist/manager/session/turn-recorder.js +18 -2
  90. package/dist/manager/session/turn-recorder.js.map +1 -1
  91. package/dist/plugin/index.d.ts +1 -1
  92. package/dist/plugin/index.d.ts.map +1 -1
  93. package/dist/plugin/index.js +1 -1
  94. package/dist/plugin/index.js.map +1 -1
  95. package/dist/plugin/loader.d.ts +11 -0
  96. package/dist/plugin/loader.d.ts.map +1 -1
  97. package/dist/plugin/loader.js +15 -2
  98. package/dist/plugin/loader.js.map +1 -1
  99. package/dist/public-runtime.d.ts +10 -6
  100. package/dist/public-runtime.d.ts.map +1 -1
  101. package/dist/public-runtime.js +13 -5
  102. package/dist/public-runtime.js.map +1 -1
  103. package/dist/public-types.d.ts +2 -0
  104. package/dist/public-types.d.ts.map +1 -1
  105. package/dist/registry/command/kernel-commands.d.ts +3 -0
  106. package/dist/registry/command/kernel-commands.d.ts.map +1 -1
  107. package/dist/registry/command/kernel-commands.js +76 -5
  108. package/dist/registry/command/kernel-commands.js.map +1 -1
  109. package/dist/runtime/query/cancelled-before-start.d.ts.map +1 -1
  110. package/dist/runtime/query/cancelled-before-start.js +2 -0
  111. package/dist/runtime/query/cancelled-before-start.js.map +1 -1
  112. package/dist/runtime/query/index.d.ts.map +1 -1
  113. package/dist/runtime/query/index.js +17 -0
  114. package/dist/runtime/query/index.js.map +1 -1
  115. package/dist/runtime/query/iteration/index.d.ts +18 -1
  116. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  117. package/dist/runtime/query/iteration/index.js +80 -1
  118. package/dist/runtime/query/iteration/index.js.map +1 -1
  119. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  120. package/dist/runtime/query/iteration/phases/tool-review.js +18 -1
  121. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  122. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  123. package/dist/runtime/query/iteration/stream-turn.js +5 -0
  124. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  125. package/dist/runtime/query/result.d.ts.map +1 -1
  126. package/dist/runtime/query/result.js +2 -0
  127. package/dist/runtime/query/result.js.map +1 -1
  128. package/dist/runtime/query/turn/answer-claims.d.ts +52 -0
  129. package/dist/runtime/query/turn/answer-claims.d.ts.map +1 -0
  130. package/dist/runtime/query/turn/answer-claims.js +348 -0
  131. package/dist/runtime/query/turn/answer-claims.js.map +1 -0
  132. package/dist/runtime/query/turn/outcome-ledger.d.ts +7 -0
  133. package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -1
  134. package/dist/runtime/query/turn/outcome-ledger.js +13 -0
  135. package/dist/runtime/query/turn/outcome-ledger.js.map +1 -1
  136. package/dist/skills/index.d.ts +1 -1
  137. package/dist/skills/index.d.ts.map +1 -1
  138. package/dist/skills/index.js +1 -1
  139. package/dist/skills/index.js.map +1 -1
  140. package/dist/skills/loader.d.ts +15 -1
  141. package/dist/skills/loader.d.ts.map +1 -1
  142. package/dist/skills/loader.js +17 -3
  143. package/dist/skills/loader.js.map +1 -1
  144. package/dist/store/goal/index.d.ts +3 -1
  145. package/dist/store/goal/index.d.ts.map +1 -1
  146. package/dist/store/goal/index.js +6 -0
  147. package/dist/store/goal/index.js.map +1 -1
  148. package/dist/telemetry/genai.d.ts +98 -0
  149. package/dist/telemetry/genai.d.ts.map +1 -0
  150. package/dist/telemetry/genai.js +189 -0
  151. package/dist/telemetry/genai.js.map +1 -0
  152. package/dist/telemetry/runtime-accessors.d.ts +19 -0
  153. package/dist/telemetry/runtime-accessors.d.ts.map +1 -1
  154. package/dist/telemetry/runtime-accessors.js +43 -1
  155. package/dist/telemetry/runtime-accessors.js.map +1 -1
  156. package/dist/tools/goal/confirmation.d.ts +154 -0
  157. package/dist/tools/goal/confirmation.d.ts.map +1 -0
  158. package/dist/tools/goal/confirmation.js +290 -0
  159. package/dist/tools/goal/confirmation.js.map +1 -0
  160. package/dist/tools/goal/index.d.ts +4 -1
  161. package/dist/tools/goal/index.d.ts.map +1 -1
  162. package/dist/tools/goal/index.js +27 -2
  163. package/dist/tools/goal/index.js.map +1 -1
  164. package/dist/tools/hand-back/index.d.ts +48 -0
  165. package/dist/tools/hand-back/index.d.ts.map +1 -0
  166. package/dist/tools/hand-back/index.js +129 -0
  167. package/dist/tools/hand-back/index.js.map +1 -0
  168. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -1
  169. package/dist/tools/schedules/schedule-tool.js +15 -1
  170. package/dist/tools/schedules/schedule-tool.js.map +1 -1
  171. package/dist/tools/schedules/types.d.ts +10 -0
  172. package/dist/tools/schedules/types.d.ts.map +1 -1
  173. package/dist/toolsets/manager.d.ts.map +1 -1
  174. package/dist/toolsets/manager.js +12 -0
  175. package/dist/toolsets/manager.js.map +1 -1
  176. package/dist/types/acp/index.d.ts +18 -0
  177. package/dist/types/acp/index.d.ts.map +1 -1
  178. package/dist/types/connector/mcp.d.ts +18 -0
  179. package/dist/types/connector/mcp.d.ts.map +1 -1
  180. package/dist/types/goal/index.d.ts +12 -0
  181. package/dist/types/goal/index.d.ts.map +1 -1
  182. package/dist/types/session/config.d.ts +8 -0
  183. package/dist/types/session/config.d.ts.map +1 -1
  184. package/dist/types/session/hand-back.d.ts +29 -0
  185. package/dist/types/session/hand-back.d.ts.map +1 -0
  186. package/dist/types/session/hand-back.js +11 -0
  187. package/dist/types/session/hand-back.js.map +1 -0
  188. package/dist/types/session/outcome.d.ts +37 -0
  189. package/dist/types/session/outcome.d.ts.map +1 -1
  190. package/dist/types/session/outcome.js.map +1 -1
  191. package/dist/types/session/turn.d.ts +19 -1
  192. package/dist/types/session/turn.d.ts.map +1 -1
  193. package/dist/types/session/turn.js.map +1 -1
  194. package/package.json +1 -1
  195. package/src/agents/file-definitions.ts +21 -0
  196. package/src/agents/index.ts +1 -0
  197. package/src/bridge/acp/server.ts +20 -0
  198. package/src/connector/index.ts +1 -0
  199. package/src/connector/mcp/client.ts +531 -14
  200. package/src/connector/mcp/index.ts +1 -0
  201. package/src/connector/mcp/tasks.ts +81 -0
  202. package/src/constants/data-epoch.ts +17 -0
  203. package/src/constants/index.ts +1 -0
  204. package/src/constants/mcp/index.ts +6 -1
  205. package/src/constants/telemetry/index.ts +13 -0
  206. package/src/eval/bootstrap.ts +314 -0
  207. package/src/eval/compare.ts +473 -0
  208. package/src/eval/disclosure.ts +136 -0
  209. package/src/eval/experiment.ts +127 -15
  210. package/src/eval/from-turn.ts +5 -0
  211. package/src/eval/index.ts +51 -0
  212. package/src/eval/report-stats.ts +257 -0
  213. package/src/eval/run-options.ts +49 -0
  214. package/src/eval/run-summary.ts +104 -0
  215. package/src/eval/scorers.ts +83 -0
  216. package/src/eval/types.ts +76 -0
  217. package/src/manager/session/turn-recorder.ts +23 -2
  218. package/src/plugin/index.ts +7 -1
  219. package/src/plugin/loader.ts +16 -2
  220. package/src/public-runtime.ts +70 -0
  221. package/src/public-types.ts +9 -0
  222. package/src/registry/command/kernel-commands.ts +94 -6
  223. package/src/runtime/query/cancelled-before-start.ts +2 -0
  224. package/src/runtime/query/index.ts +20 -0
  225. package/src/runtime/query/iteration/index.ts +85 -1
  226. package/src/runtime/query/iteration/phases/tool-review.ts +24 -1
  227. package/src/runtime/query/iteration/stream-turn.ts +10 -0
  228. package/src/runtime/query/result.ts +2 -0
  229. package/src/runtime/query/turn/answer-claims.ts +413 -0
  230. package/src/runtime/query/turn/outcome-ledger.ts +12 -0
  231. package/src/skills/index.ts +1 -0
  232. package/src/skills/loader.ts +21 -3
  233. package/src/store/goal/index.ts +11 -0
  234. package/src/telemetry/genai.ts +202 -0
  235. package/src/telemetry/runtime-accessors.ts +40 -1
  236. package/src/tools/goal/confirmation.ts +375 -0
  237. package/src/tools/goal/index.ts +37 -1
  238. package/src/tools/hand-back/index.ts +150 -0
  239. package/src/tools/schedules/schedule-tool.ts +19 -1
  240. package/src/tools/schedules/types.ts +10 -0
  241. package/src/toolsets/manager.ts +22 -0
  242. package/src/types/acp/index.ts +20 -0
  243. package/src/types/connector/mcp.ts +18 -0
  244. package/src/types/goal/index.ts +13 -0
  245. package/src/types/session/config.ts +8 -0
  246. package/src/types/session/hand-back.ts +31 -0
  247. package/src/types/session/outcome.ts +42 -0
  248. package/src/types/session/turn.ts +19 -1
@@ -0,0 +1,150 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { HandBack, HandBackKind } from '../../types/session/hand-back.js'
4
+ import type { ToolDefinition } from '../../types/tool/index.js'
5
+ import { defineTool } from '../defineTool.js'
6
+
7
+ /** The name the model calls. */
8
+ export const HAND_BACK_TOOL_NAME = 'hand_back_task'
9
+
10
+ /** Each of the three sentences is cut to this many characters when it is kept. */
11
+ export const HAND_BACK_MAX_CHARS = 800
12
+
13
+ /** What the model is told when the setting is on. One place, added to the static prompt. */
14
+ export const HAND_BACK_GUIDANCE = `## Handing a task back
15
+ If the task cannot be done as specified, call \`${HAND_BACK_TOOL_NAME}\` instead of forcing a result: it ends the turn and tells a person what blocks you, what you tried and what they must decide or provide. Never weaken, skip, delete or special-case a check to make it pass; if the request and its checks cannot both hold, hand it back. Hand back only what you could not finish.`
16
+
17
+ /**
18
+ * What the tool answers when the hand-back was accepted. A saved conversation
19
+ * holds this receipt beside the call, so a reader can tell an accepted call
20
+ * (the turn ended there) from one that was refused.
21
+ */
22
+ export const HAND_BACK_RECEIPT = 'Handed back to a person.'
23
+
24
+ /** The id of the prompt contribution that carries {@link HAND_BACK_GUIDANCE}. */
25
+ export const HAND_BACK_GUIDANCE_CONTRIBUTION_ID = 'namzu.hand-back'
26
+
27
+ const text = z.string().trim().min(1)
28
+
29
+ export const HandBackInputSchema = z.object({
30
+ kind: z
31
+ .enum(['conflict', 'missing', 'decision'])
32
+ .describe(
33
+ 'conflict: request and checks disagree. missing: something needed is missing or not allowed. decision: the person must decide.',
34
+ ),
35
+ blocker: text.describe('What blocks you.'),
36
+ tried: text.describe('What you tried.'),
37
+ needed: text.describe('What a person has to decide or provide.'),
38
+ })
39
+
40
+ /** Control, invisible-format and bidirectional code points: none of them is text a person reads. */
41
+ const UNSHOWN: readonly (readonly [number, number])[] = [
42
+ [0x0000, 0x001f],
43
+ [0x007f, 0x009f],
44
+ [0x00ad, 0x00ad],
45
+ [0x061c, 0x061c],
46
+ [0x180e, 0x180e],
47
+ [0x200b, 0x200f],
48
+ [0x2028, 0x202e],
49
+ [0x2060, 0x206f],
50
+ [0xfeff, 0xfeff],
51
+ [0xfff9, 0xfffb],
52
+ ]
53
+
54
+ /** Whitespace-like controls become a space; every other unshown code point is dropped. */
55
+ const cleaned = (value: string): string => {
56
+ let out = ''
57
+ for (const ch of value) {
58
+ const code = ch.codePointAt(0) ?? 0
59
+ if (code === 0x09 || code === 0x0a || code === 0x0d) out += ' '
60
+ else if (!UNSHOWN.some(([lo, hi]) => code >= lo && code <= hi)) out += ch
61
+ }
62
+ return out
63
+ }
64
+
65
+ const clip = (value: string): string => {
66
+ const flat = cleaned(value).replace(/\s+/g, ' ').trim()
67
+ return flat.length > HAND_BACK_MAX_CHARS ? `${flat.slice(0, HAND_BACK_MAX_CHARS - 1)}…` : flat
68
+ }
69
+
70
+ /**
71
+ * The hand-back a tool call's input holds, or `undefined` when it is not one.
72
+ * Never throws; text is stripped of control and bidirectional characters, whitespace-folded and clipped.
73
+ */
74
+ export function parseHandBack(input: unknown): HandBack | undefined {
75
+ let value = input
76
+ if (typeof value === 'string') {
77
+ try {
78
+ value = JSON.parse(value)
79
+ } catch {
80
+ return undefined
81
+ }
82
+ }
83
+ const parsed = HandBackInputSchema.safeParse(value)
84
+ if (!parsed.success) return undefined
85
+ const { kind, blocker, tried, needed } = parsed.data
86
+ return { kind, blocker: clip(blocker), tried: clip(tried), needed: clip(needed) }
87
+ }
88
+
89
+ const KIND_SENTENCE: Record<HandBackKind, string> = {
90
+ conflict: 'the request conflicts with itself or with its checks',
91
+ missing: 'something needed is missing or not allowed',
92
+ decision: 'a decision is needed that belongs to a person',
93
+ }
94
+
95
+ /** The kernel's own sentence for a kind; the model's words are never part of it. */
96
+ export function handBackKindSentence(kind: HandBackKind): string {
97
+ return KIND_SENTENCE[kind] ?? KIND_SENTENCE.decision
98
+ }
99
+
100
+ /** The closing line in the kernel's words: `The model handed the task back: <kind>.` */
101
+ export function describeHandBack(handBack: HandBack): string {
102
+ return `The model handed the task back to a person: ${handBackKindSentence(handBack.kind)}.`
103
+ }
104
+
105
+ /**
106
+ * The model's three sentences, labelled as the model's, one per line. The
107
+ * labels are the kernel's; every value after a label is the model's own text.
108
+ */
109
+ export function handBackModelLines(handBack: HandBack): readonly string[] {
110
+ return [
111
+ `The model says it is blocked by: ${handBack.blocker}`,
112
+ `It tried: ${handBack.tried}`,
113
+ `It needs a person to: ${handBack.needed}`,
114
+ ]
115
+ }
116
+
117
+ /** The one-line reason a goal or a scheduled run records. */
118
+ export function handBackReason(handBack: HandBack): string {
119
+ return `${describeHandBack(handBack).replace(/\.$/, '')}. The model said: ${handBack.blocker}`
120
+ }
121
+
122
+ /** What a refused sibling call is told in the step that handed the task back. */
123
+ export const HAND_BACK_SIBLING_REFUSAL = `Not run: this step handed the task back to a person with ${HAND_BACK_TOOL_NAME}, and the turn ends there.`
124
+
125
+ /** The tool. Offered only when `TurnConfig.handBack` is on. */
126
+ export function buildHandBackTool(): ToolDefinition {
127
+ return defineTool({
128
+ name: HAND_BACK_TOOL_NAME,
129
+ description:
130
+ 'Hand the task back to a person when it cannot be done as specified. Ends the turn: nothing else runs after it.',
131
+ inputSchema: HandBackInputSchema,
132
+ category: 'custom',
133
+ permissions: [],
134
+ readOnly: true,
135
+ destructive: false,
136
+ concurrencySafe: false,
137
+ async execute(input) {
138
+ const handBack = parseHandBack(input)
139
+ if (!handBack)
140
+ throw new TypeError(
141
+ 'The hand-back needs a kind, a blocker, what was tried and what is needed.',
142
+ )
143
+ return {
144
+ success: true,
145
+ output: `${HAND_BACK_RECEIPT} The turn ends now; nothing more will run and no further answer is needed.`,
146
+ data: handBack,
147
+ }
148
+ },
149
+ })
150
+ }
@@ -65,6 +65,12 @@ const inputSchema = z.object({
65
65
  .describe(
66
66
  'create: required when kind is script or script+agent. For script+agent this is the wake-gate: its stdout must be exactly one JSON line, {"wake": boolean, "context": string}.',
67
67
  ),
68
+ gate: z
69
+ .unknown()
70
+ .optional()
71
+ .describe(
72
+ 'Never set this. A gate is a command that runs unattended before every run, so only the operator can set or change one (namzu schedule add/edit --gate, or the job file). Giving it refuses the call. A job that already has a gate keeps it when you update the job.',
73
+ ),
68
74
  prompt: z
69
75
  .string()
70
76
  .min(1)
@@ -177,6 +183,9 @@ const inputSchema = z.object({
177
183
 
178
184
  type Input = z.infer<typeof inputSchema>
179
185
 
186
+ const GATE_REFUSAL =
187
+ 'This tool cannot set or change a gate. A gate is a command that runs unattended before every run of the job, so it must come from the person: they set it with `namzu schedule add --gate` or `namzu schedule edit --gate`, or in the job’s file. Leave gate out; a job that already has one keeps it when you update the job.'
188
+
180
189
  function refuse(error: string): ToolResult {
181
190
  return { success: false, output: '', error }
182
191
  }
@@ -297,6 +306,7 @@ async function create(
297
306
  input: Input,
298
307
  signal: AbortSignal | undefined,
299
308
  ): Promise<ToolResult> {
309
+ if (input.gate !== undefined) return refuse(GATE_REFUSAL)
300
310
  const runKind = input.kind ?? 'agent'
301
311
  const required: string[] = (['name', 'when', 'permissions'] as const).filter(
302
312
  (k) => input[k] === undefined,
@@ -427,6 +437,7 @@ async function update(
427
437
  'This host cannot change a scheduled job. Ask the operator to change it themselves; do not delete and create it again, which loses its history.',
428
438
  )
429
439
  }
440
+ if (input.gate !== undefined) return refuse(GATE_REFUSAL)
430
441
  if (!input.job) return refuse("update needs job (the job's name) and the fields to change.")
431
442
  if (input.name !== undefined) {
432
443
  return refuse(
@@ -496,6 +507,13 @@ async function update(
496
507
  ) {
497
508
  return refuse(NETWORK_WITH_HOST_SHELL)
498
509
  }
510
+ if (
511
+ proposal.preview.gate !== undefined &&
512
+ proposal.preview.execution === 'host' &&
513
+ (proposal.preview.networkGrantAccess ?? proposal.preview.networkAccess)
514
+ ) {
515
+ return refuse(NETWORK_WITH_HOST_SHELL)
516
+ }
499
517
  let confirmed = false
500
518
  try {
501
519
  confirmed = await host.confirmUpdate(
@@ -587,7 +605,7 @@ export function buildScheduleTools(host: ScheduleToolHost): ToolDefinition[] {
587
605
  defineTool({
588
606
  name: SCHEDULE_TOOL_NAME,
589
607
  description:
590
- 'Manage durable jobs that run later, including while Namzu is closed when its scheduler is installed. For repetition in this open conversation while the TUI is open, use session_loop (/loop); a loop is not a background service. Use schedule only when the user asks for durable scheduled work. create, update, resume and delete ask the operator; pause does not. A job needs name, when and explicit permissions. agent and script+agent need prompt; script and script+agent need script. For a pure script, use permissions {rules:{},unmatched:\'deny\'} without a preset unless you intend deny rules: read-only denies bash and blocks every script. A pure script has no prompt, model, agent budget, browser grant or run session; set script.timeoutMs to limit it. A pure script may opt into script.report:\'json-v1\': one JSON stdout line says quiet or changed with a summary and optional scheduler-owned nextState. For a project-free pure script set workspace:\'none\' and omit folder; its private scheduler workspace is not a sandbox, and its shell still has host authority. For project work, use the actual existing project folder; home, root and NAMZU_HOME are invalid. Do not create a visible folder merely to satisfy validation. The requesting conversation can receive important results separately from the run workspace. To change a job, update it with job and only the fields that change; do not delete and recreate it. Leave tz, execution, budget and headed unset unless requested: defaults are the operator\'s. Scheduled runs cannot ask questions. Example, a weekday-morning agent job: {"action":"create","name":"daily-report","kind":"agent","when":"0 9 * * 1-5","prompt":"Summarise yesterday\'s changes","permissions":{"preset":"read-only","unmatched":"deny"}}.',
608
+ 'Manage durable jobs that run later, including while Namzu is closed when its scheduler is installed. For repetition in this open conversation while the TUI is open, use session_loop (/loop); a loop is not a background service. Use schedule only when the user asks for durable scheduled work. create, update, resume and delete ask the operator; pause does not. A job needs name, when and explicit permissions. agent and script+agent need prompt; script and script+agent need script. For a pure script, use permissions {rules:{},unmatched:\'deny\'} without a preset unless you intend deny rules: read-only denies bash and blocks every script. A pure script has no prompt, model, agent budget, browser grant or run session; set script.timeoutMs to limit it. A pure script may opt into script.report:\'json-v1\': one JSON stdout line says quiet or changed with a summary and optional scheduler-owned nextState. For a project-free pure script set workspace:\'none\' and omit folder; its private scheduler workspace is not a sandbox, and its shell still has host authority. For project work, use the actual existing project folder; home, root and NAMZU_HOME are invalid. Do not create a visible folder merely to satisfy validation. The requesting conversation can receive important results separately from the run workspace. To change a job, update it with job and only the fields that change; do not delete and recreate it. A gate (a command the operator runs before every run to decide whether to wake the model) is the operator\'s alone: this tool cannot set or change one, and a job that has one keeps it. Leave tz, execution, budget and headed unset unless requested: defaults are the operator\'s. Scheduled runs cannot ask questions. Example, a weekday-morning agent job: {"action":"create","name":"daily-report","kind":"agent","when":"0 9 * * 1-5","prompt":"Summarise yesterday\'s changes","permissions":{"preset":"read-only","unmatched":"deny"}}.',
591
609
  inputSchema,
592
610
  category: 'custom',
593
611
  permissions: [],
@@ -125,6 +125,16 @@ export interface ScheduleJobPreview {
125
125
  readonly timeoutMs: number
126
126
  readonly report?: 'json-v1'
127
127
  }
128
+ /**
129
+ * Present when the job has a gate: the exact command the operator wrote,
130
+ * run before every run, which decides whether the model is called. Shown so
131
+ * a change to the job is confirmed knowing it. The tool never sets one.
132
+ */
133
+ readonly gate?: {
134
+ readonly command: string
135
+ readonly shell: 'bash' | 'sh'
136
+ readonly timeoutMs: number
137
+ }
128
138
  /** The schedule in words, with its zone. */
129
139
  readonly schedule: string
130
140
  /** Whether a completed run sends a Namzu desktop notice; absent for older hosts. */
@@ -6,6 +6,13 @@ import { assertToolName, describeWithOutput, toolDiscoveryHint } from '../regist
6
6
  import { renderToolSchema, toolWireSchema } from '../registry/tool/schema.js'
7
7
  import { ToolResultHalted, screenToolResult } from '../registry/tool/screen.js'
8
8
  import { GENAI, NAMZU, toolSpanName } from '../telemetry/attributes.js'
9
+ import {
10
+ ERROR_TYPE_OTHER,
11
+ PERMISSION_DENIED,
12
+ errorTypeAttributes,
13
+ genAiErrorType,
14
+ toolCallAttributes,
15
+ } from '../telemetry/genai.js'
9
16
  import { recordToolCall } from '../telemetry/metrics.js'
10
17
  import { getTracer } from '../telemetry/runtime-accessors.js'
11
18
  import { failureFactsFromError } from '../tools/failure-facts.js'
@@ -804,6 +811,8 @@ Executable tool names, descriptions, and JSON input schemas are attached through
804
811
  [GENAI.TOOL_TYPE]: 'function',
805
812
  ...(context.toolUseId !== undefined ? { [GENAI.TOOL_CALL_ID]: context.toolUseId } : {}),
806
813
  ...toolSpanIdentity(context),
814
+ ...toolCallAttributes(),
815
+ ...errorTypeAttributes(genAiErrorType(err)),
807
816
  [NAMZU.TOOL_SUCCESS]: false,
808
817
  [NAMZU.TOOL_ERROR]: toErrorMessage(err),
809
818
  })
@@ -841,6 +850,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
841
850
  [GENAI.TOOL_TYPE]: 'function',
842
851
  ...(context.toolUseId !== undefined ? { [GENAI.TOOL_CALL_ID]: context.toolUseId } : {}),
843
852
  ...toolSpanIdentity(context),
853
+ ...toolCallAttributes(),
844
854
  })
845
855
 
846
856
  // The permitted `this.getAvailability` substitution: derived,
@@ -852,6 +862,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
852
862
  span.setAttributes({
853
863
  [NAMZU.TOOL_SUCCESS]: false,
854
864
  [NAMZU.TOOL_ERROR]: msg,
865
+ ...errorTypeAttributes(ERROR_TYPE_OTHER),
855
866
  })
856
867
  span.setStatus({ code: SpanStatusCode.ERROR, message: msg })
857
868
  return {
@@ -871,6 +882,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
871
882
  span.setAttributes({
872
883
  [NAMZU.TOOL_SUCCESS]: false,
873
884
  [NAMZU.TOOL_ERROR]: msg,
885
+ ...errorTypeAttributes(PERMISSION_DENIED),
874
886
  })
875
887
  span.setStatus({ code: SpanStatusCode.ERROR, message: msg })
876
888
  return {
@@ -889,6 +901,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
889
901
  span.setAttributes({
890
902
  [NAMZU.TOOL_SUCCESS]: false,
891
903
  [NAMZU.TOOL_ERROR]: msg,
904
+ ...errorTypeAttributes(PERMISSION_DENIED),
892
905
  })
893
906
  span.setStatus({ code: SpanStatusCode.ERROR, message: msg })
894
907
  return {
@@ -932,6 +945,13 @@ Executable tool names, descriptions, and JSON input schemas are attached through
932
945
  )
933
946
  if (!result.success && result.error) {
934
947
  span.setAttribute(NAMZU.TOOL_ERROR, result.error)
948
+ span.setAttributes(
949
+ errorTypeAttributes(
950
+ 'permissionDenied' in result && result.permissionDenied
951
+ ? PERMISSION_DENIED
952
+ : ERROR_TYPE_OTHER,
953
+ ),
954
+ )
935
955
  span.setStatus({
936
956
  code: SpanStatusCode.ERROR,
937
957
  message: result.error,
@@ -946,6 +966,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
946
966
  span.setAttributes({
947
967
  [NAMZU.TOOL_SUCCESS]: false,
948
968
  [NAMZU.TOOL_ERROR]: err.message,
969
+ ...errorTypeAttributes(genAiErrorType(err)),
949
970
  })
950
971
  span.setStatus({
951
972
  code: SpanStatusCode.ERROR,
@@ -962,6 +983,7 @@ Executable tool names, descriptions, and JSON input schemas are attached through
962
983
  span.setAttributes({
963
984
  [NAMZU.TOOL_SUCCESS]: false,
964
985
  [NAMZU.TOOL_ERROR]: errorMessage,
986
+ ...errorTypeAttributes(genAiErrorType(err)),
965
987
  })
966
988
  span.setStatus({
967
989
  code: SpanStatusCode.ERROR,
@@ -38,7 +38,27 @@ export interface AcpInitializeParams extends AcpClientCapabilities {
38
38
  readonly clientInfo?: { readonly name?: string; readonly version?: string }
39
39
  }
40
40
 
41
+ /**
42
+ * What a host says about the engine it runs, in `initialize`. A reader that
43
+ * does not know this object ignores it.
44
+ */
45
+ export interface AcpKernelFacts {
46
+ /** The engine's own version (this package's). */
47
+ readonly engine: string
48
+ /** The data epoch of the on-disk formats this engine writes. */
49
+ readonly dataEpoch: number
50
+ /** The version of the application that hosts the engine, when that application says one. */
51
+ readonly commandLine?: string
52
+ /** The contract number an installed client application checks, when the host was started for one. */
53
+ readonly desktopContract?: number
54
+ }
55
+
56
+ /** The two facts a host application adds to `AcpKernelFacts`; the engine states the rest itself. */
57
+ export type AcpHostKernelFacts = Pick<AcpKernelFacts, 'commandLine' | 'desktopContract'>
58
+
41
59
  export interface AcpInitializeResult {
60
+ /** The engine and data facts a client may use to decide whether this host is safe for it. */
61
+ readonly kernel?: AcpKernelFacts
42
62
  /** True only when the host gateway opts into inline user attachment delivery. */
43
63
  readonly promptAttachments?: boolean
44
64
  /** True only when this host applies captured effort and review settings per prompt. */
@@ -416,6 +416,12 @@ export interface MCPClientCapabilities {
416
416
  * only. The client sets it, a caller passing it by hand gets no handler.
417
417
  */
418
418
  elicitation?: Record<string, never>
419
+ /**
420
+ * Extensions beyond the core protocol, by identifier (2026-07-28). The
421
+ * client adds `io.modelcontextprotocol/tasks` itself when the server
422
+ * advertises it and {@link MCPClientConfig.tasks} is not `false`.
423
+ */
424
+ extensions?: Record<string, Record<string, unknown>>
419
425
  experimental?: Record<string, unknown>
420
426
  }
421
427
 
@@ -424,6 +430,8 @@ export interface MCPServerCapabilities {
424
430
  resources?: { subscribe?: boolean; listChanged?: boolean }
425
431
  prompts?: { listChanged?: boolean }
426
432
  logging?: Record<string, never>
433
+ /** Extensions the server offers, by identifier (2026-07-28 `server/discover`). */
434
+ extensions?: Record<string, Record<string, unknown>>
427
435
  experimental?: Record<string, unknown>
428
436
  }
429
437
 
@@ -500,6 +508,16 @@ export interface MCPClientConfig {
500
508
  transportFactory?: () => MCPTransport
501
509
  capabilities?: MCPClientCapabilities
502
510
  clientInfo?: { name: string; version: string }
511
+ /**
512
+ * Take part in the tasks extension (2026-07-28). Default `true`.
513
+ *
514
+ * When the server advertises `io.modelcontextprotocol/tasks`, the client
515
+ * declares it on each request, and a `tools/call` the server turns into a
516
+ * task is waited on until it ends: `callTool()` resolves with the task's
517
+ * result exactly as if the server had answered at once. `false` declares
518
+ * nothing, so a server never returns a task to this client.
519
+ */
520
+ tasks?: boolean
503
521
  /**
504
522
  * Answer a server's `elicitation/create` (form mode) by asking the person.
505
523
  *
@@ -3,6 +3,13 @@ import type { GoalId, SessionId, TenantId } from '../ids/index.js'
3
3
  /** Durable lifecycle of one same-session completion goal. */
4
4
  export type GoalPhase = 'active' | 'paused' | 'blocked' | 'complete'
5
5
 
6
+ /**
7
+ * Who says a goal is done. `model`: the model's own report ends the goal, as it
8
+ * always has. `record`: the kernel reads the record of the work done for the
9
+ * goal first, and the goal ends only as that record allows.
10
+ */
11
+ export type GoalConfirmedBy = 'model' | 'record'
12
+
6
13
  /** Machine-routable and human-readable reason that a goal stopped. */
7
14
  export interface GoalBlockReason {
8
15
  /** Stable lower-kebab-case policy code. */
@@ -33,6 +40,12 @@ export interface SessionGoal extends GoalRef {
33
40
  readonly maxGoalRounds: number
34
41
  /** Durable admissions, not a claim that the provider began or completed. */
35
42
  readonly roundsAdmitted: number
43
+ /**
44
+ * What this goal was created to be confirmed by. Absent means the host's own
45
+ * setting decides, which is the case for every goal created before this
46
+ * field existed.
47
+ */
48
+ readonly confirmedBy?: GoalConfirmedBy
36
49
  /** Present exactly while {@link phase} is `blocked`. */
37
50
  readonly blockedReason?: GoalBlockReason
38
51
  readonly createdAt: number
@@ -106,6 +106,14 @@ export interface TurnConfig {
106
106
  * Off when absent, so an embedding host sees no change.
107
107
  */
108
108
  completionAudit?: boolean
109
+ /**
110
+ * Let the model hand the task back to a person (`hand_back_task`) instead of
111
+ * forcing a result it cannot honestly reach. On: the tool is offered, a short
112
+ * paragraph of guidance joins the static system prompt, and a call to the tool
113
+ * ends the turn with nothing else run (`Turn.handedBack`). Off or absent: the
114
+ * tool is absent and the prompt is byte for byte what it was.
115
+ */
116
+ handBack?: boolean
109
117
  /**
110
118
  * How many times in a row `turn_end` / `subagent_stop` hooks may refuse to
111
119
  * let the turn end (answer `skip`) before the turn stops honouring them. At
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A turn the model handed back to a person (`docs/sdk/hand-back.md`).
3
+ *
4
+ * The model calls `hand_back_task` when the task cannot be finished as
5
+ * specified and the honest thing is to stop and say so. The turn ends there:
6
+ * no further model call, nothing else runs. The fact is kept beside the
7
+ * turn's outcome verdict, not inside it: a turn can have changed files and
8
+ * still have been handed back.
9
+ */
10
+
11
+ /** Why the model stopped; a small closed set so a script can branch on it. */
12
+ export type HandBackKind =
13
+ /** The request conflicts with itself, or with the checks that decide whether it is done. */
14
+ | 'conflict'
15
+ /** Something needed is missing or not allowed: an input, a file, a credential, a permission. */
16
+ | 'missing'
17
+ /** A decision is needed that belongs to a person. */
18
+ | 'decision'
19
+
20
+ export const HAND_BACK_KINDS: readonly HandBackKind[] = ['conflict', 'missing', 'decision']
21
+
22
+ /** What the model said when it handed the task back. The words are the model's. */
23
+ export interface HandBack {
24
+ readonly kind: HandBackKind
25
+ /** What blocks it. */
26
+ readonly blocker: string
27
+ /** What it tried. */
28
+ readonly tried: string
29
+ /** What a person has to decide or provide. */
30
+ readonly needed: string
31
+ }
@@ -74,6 +74,35 @@ export interface TurnOutcome {
74
74
  readonly checksTotal?: number
75
75
  }
76
76
 
77
+ /** What a plain claim in an answer was about. */
78
+ export type AnswerClaimKind = 'tests' | 'build' | 'lint' | 'typecheck' | 'checks'
79
+
80
+ /** What the turn's record held that the claim did not match. */
81
+ export type AnswerClaimRecord =
82
+ /** A check's latest run failed. */
83
+ | 'check-failed'
84
+ /** Files changed and no check ran. */
85
+ | 'unchecked'
86
+ /** Files changed and no check established a pass, or its result could not be read. */
87
+ | 'unreadable'
88
+
89
+ /**
90
+ * An answer said a check passed and the same turn's record says otherwise.
91
+ *
92
+ * Recorded, not acted on: the model is not told and nothing blocks. See
93
+ * `compareAnswerToOutcome`.
94
+ */
95
+ export interface AnswerClaimMismatch {
96
+ readonly claim: AnswerClaimKind
97
+ /** The claim in the kernel's words, for example `the tests pass`. */
98
+ readonly said: string
99
+ /** The clause of the answer it was found in, whitespace folded and clipped. */
100
+ readonly quote: string
101
+ readonly record: AnswerClaimRecord
102
+ /** What the record holds, in the kernel's words, for example `pnpm test failed`. */
103
+ readonly recordSays: string
104
+ }
105
+
77
106
  /** Host hooks for the turn's outcome record. */
78
107
  export interface TurnOutcomeConfig {
79
108
  /**
@@ -88,6 +117,19 @@ export interface TurnOutcomeConfig {
88
117
  readonly isCheck?: (command: string) => boolean
89
118
  /** Deadline per detector call. A detector that is late is `null`. Default 3000. */
90
119
  readonly detectTimeoutMs?: number
120
+ /**
121
+ * Asked when the model has finished its answer with no tool call left and
122
+ * the turn would end. `read` gives the turn's record as it stands, with the
123
+ * workspace compared once; it is not computed unless called. A returned
124
+ * sentence goes to the model as a runtime note and the turn goes on; nothing
125
+ * returned lets the turn end. A turn that is being stopped by a limit, a
126
+ * cancellation or a forced final answer is not asked, so a limit always wins
127
+ * over this hook. At most 16 notes are honoured in one turn. A hook that
128
+ * throws is read as nothing returned.
129
+ */
130
+ readonly beforeEnd?: (
131
+ read: () => Promise<TurnOutcome | undefined>,
132
+ ) => string | undefined | Promise<string | undefined>
91
133
  }
92
134
 
93
135
  /** Bounds on a recorded outcome. */
@@ -11,7 +11,8 @@ import type {
11
11
  import type { Message } from '../message/index.js'
12
12
  import type { ProviderErrorInfo } from '../provider/index.js'
13
13
  import type { TurnConfig } from './config.js'
14
- import type { TurnOutcome } from './outcome.js'
14
+ import type { HandBack } from './hand-back.js'
15
+ import type { AnswerClaimMismatch, TurnOutcome } from './outcome.js'
15
16
  import type { StepResult } from './step.js'
16
17
  import type { StopReason } from './stop-reason.js'
17
18
 
@@ -166,6 +167,21 @@ export interface LiveTurnSettlement extends TurnSettlement {
166
167
  * did not keep the record.
167
168
  */
168
169
  readonly outcome?: TurnOutcome
170
+ /**
171
+ * A plain claim in the answer that this turn's outcome record contradicts.
172
+ * Shadow only: the model is not told and nothing blocks. In memory only,
173
+ * like {@link LiveTurnSettlement.outcome}; the session log does not keep it.
174
+ * Absent when the answer made no such claim, which is the usual case.
175
+ */
176
+ readonly claimMismatch?: AnswerClaimMismatch
177
+ /**
178
+ * The model handed the task back to a person (`hand_back_task`) and the turn
179
+ * ended there. A fact beside {@link LiveTurnSettlement.outcome}, not a field of
180
+ * it: a turn can have changed files and been handed back. In memory only; the
181
+ * session log keeps no record of it, since the call and its words are already in
182
+ * the saved conversation and a reopened one reads them from there.
183
+ */
184
+ readonly handedBack?: HandBack
169
185
  }
170
186
 
171
187
  /** Metadata of one turn: who ran it, with what, and where it sits in the session tree. */
@@ -235,6 +251,8 @@ export interface Turn {
235
251
  abandonedJobIds?: readonly string[]
236
252
  /** What the turn changed and what checked it; see {@link LiveTurnSettlement.outcome}. */
237
253
  outcome?: TurnOutcome
254
+ /** Set when the model handed the task back; see {@link LiveTurnSettlement.handedBack}. */
255
+ handedBack?: HandBack
238
256
  /** Present on a child session's turn. */
239
257
  parentSessionId?: SessionId
240
258
  /** Present on a child session's turn. */