@namzu/sdk 62.11.0 → 62.13.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 +29 -0
- package/dist/connector/index.d.ts +1 -1
- 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/client.d.ts +96 -0
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +488 -13
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +1 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +1 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/tasks.d.ts +40 -0
- package/dist/connector/mcp/tasks.d.ts.map +1 -0
- package/dist/connector/mcp/tasks.js +55 -0
- package/dist/connector/mcp/tasks.js.map +1 -0
- package/dist/constants/mcp/index.d.ts +1 -1
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +5 -0
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/eval/bootstrap.d.ts +122 -0
- package/dist/eval/bootstrap.d.ts.map +1 -0
- package/dist/eval/bootstrap.js +227 -0
- package/dist/eval/bootstrap.js.map +1 -0
- package/dist/eval/compare.d.ts +109 -0
- package/dist/eval/compare.d.ts.map +1 -0
- package/dist/eval/compare.js +324 -0
- package/dist/eval/compare.js.map +1 -0
- package/dist/eval/disclosure.d.ts +70 -0
- package/dist/eval/disclosure.d.ts.map +1 -0
- package/dist/eval/disclosure.js +64 -0
- package/dist/eval/disclosure.js.map +1 -0
- package/dist/eval/experiment.d.ts +23 -0
- package/dist/eval/experiment.d.ts.map +1 -1
- package/dist/eval/experiment.js +87 -15
- package/dist/eval/experiment.js.map +1 -1
- package/dist/eval/from-turn.d.ts.map +1 -1
- package/dist/eval/from-turn.js +5 -0
- package/dist/eval/from-turn.js.map +1 -1
- package/dist/eval/index.d.ts +13 -2
- package/dist/eval/index.d.ts.map +1 -1
- package/dist/eval/index.js +6 -1
- package/dist/eval/index.js.map +1 -1
- package/dist/eval/report-stats.d.ts +77 -0
- package/dist/eval/report-stats.d.ts.map +1 -0
- package/dist/eval/report-stats.js +184 -0
- package/dist/eval/report-stats.js.map +1 -0
- package/dist/eval/run-options.d.ts +30 -0
- package/dist/eval/run-options.d.ts.map +1 -0
- package/dist/eval/run-options.js +20 -0
- package/dist/eval/run-options.js.map +1 -0
- package/dist/eval/run-summary.d.ts +36 -0
- package/dist/eval/run-summary.d.ts.map +1 -0
- package/dist/eval/run-summary.js +57 -0
- package/dist/eval/run-summary.js.map +1 -0
- package/dist/eval/scorers.d.ts +32 -0
- package/dist/eval/scorers.d.ts.map +1 -1
- package/dist/eval/scorers.js +77 -0
- package/dist/eval/scorers.js.map +1 -1
- package/dist/eval/types.d.ts +74 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts +3 -0
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +20 -5
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/public-runtime.d.ts +5 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +7 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/registry/command/kernel-commands.d.ts +3 -0
- package/dist/registry/command/kernel-commands.d.ts.map +1 -1
- package/dist/registry/command/kernel-commands.js +76 -5
- package/dist/registry/command/kernel-commands.js.map +1 -1
- package/dist/runtime/query/abandon-turn.d.ts.map +1 -1
- package/dist/runtime/query/abandon-turn.js +2 -2
- package/dist/runtime/query/abandon-turn.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +15 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +18 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +80 -1
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +18 -1
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
- package/dist/runtime/query/prelude-lease.js +2 -2
- package/dist/runtime/query/prelude-lease.js.map +1 -1
- package/dist/runtime/query/turn/answer-claims.d.ts +52 -0
- package/dist/runtime/query/turn/answer-claims.d.ts.map +1 -0
- package/dist/runtime/query/turn/answer-claims.js +348 -0
- package/dist/runtime/query/turn/answer-claims.js.map +1 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts +7 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.js +13 -0
- package/dist/runtime/query/turn/outcome-ledger.js.map +1 -1
- package/dist/store/goal/index.d.ts +3 -1
- package/dist/store/goal/index.d.ts.map +1 -1
- package/dist/store/goal/index.js +6 -0
- package/dist/store/goal/index.js.map +1 -1
- package/dist/store/session-log/index.d.ts +1 -0
- package/dist/store/session-log/index.d.ts.map +1 -1
- package/dist/store/session-log/index.js +1 -0
- package/dist/store/session-log/index.js.map +1 -1
- package/dist/store/session-log/turn-holder.d.ts +26 -0
- package/dist/store/session-log/turn-holder.d.ts.map +1 -0
- package/dist/store/session-log/turn-holder.js +79 -0
- package/dist/store/session-log/turn-holder.js.map +1 -0
- package/dist/telemetry/runtime-accessors.d.ts +14 -0
- package/dist/telemetry/runtime-accessors.d.ts.map +1 -1
- package/dist/telemetry/runtime-accessors.js +32 -0
- package/dist/telemetry/runtime-accessors.js.map +1 -1
- package/dist/tools/goal/confirmation.d.ts +154 -0
- package/dist/tools/goal/confirmation.d.ts.map +1 -0
- package/dist/tools/goal/confirmation.js +290 -0
- package/dist/tools/goal/confirmation.js.map +1 -0
- package/dist/tools/goal/index.d.ts +4 -1
- package/dist/tools/goal/index.d.ts.map +1 -1
- package/dist/tools/goal/index.js +27 -2
- package/dist/tools/goal/index.js.map +1 -1
- package/dist/tools/hand-back/index.d.ts +48 -0
- package/dist/tools/hand-back/index.d.ts.map +1 -0
- package/dist/tools/hand-back/index.js +129 -0
- package/dist/tools/hand-back/index.js.map +1 -0
- package/dist/tools/schedules/schedule-tool.d.ts.map +1 -1
- package/dist/tools/schedules/schedule-tool.js +15 -1
- package/dist/tools/schedules/schedule-tool.js.map +1 -1
- package/dist/tools/schedules/types.d.ts +10 -0
- package/dist/tools/schedules/types.d.ts.map +1 -1
- package/dist/types/connector/mcp.d.ts +18 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/goal/index.d.ts +12 -0
- package/dist/types/goal/index.d.ts.map +1 -1
- package/dist/types/session/config.d.ts +8 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/hand-back.d.ts +29 -0
- package/dist/types/session/hand-back.d.ts.map +1 -0
- package/dist/types/session/hand-back.js +11 -0
- package/dist/types/session/hand-back.js.map +1 -0
- package/dist/types/session/outcome.d.ts +37 -0
- package/dist/types/session/outcome.d.ts.map +1 -1
- package/dist/types/session/outcome.js.map +1 -1
- package/dist/types/session/turn.d.ts +19 -1
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/package.json +1 -1
- package/src/connector/index.ts +1 -0
- package/src/connector/mcp/client.ts +531 -14
- package/src/connector/mcp/index.ts +1 -0
- package/src/connector/mcp/tasks.ts +81 -0
- package/src/constants/mcp/index.ts +6 -1
- package/src/eval/bootstrap.ts +314 -0
- package/src/eval/compare.ts +473 -0
- package/src/eval/disclosure.ts +136 -0
- package/src/eval/experiment.ts +127 -15
- package/src/eval/from-turn.ts +5 -0
- package/src/eval/index.ts +51 -0
- package/src/eval/report-stats.ts +257 -0
- package/src/eval/run-options.ts +49 -0
- package/src/eval/run-summary.ts +104 -0
- package/src/eval/scorers.ts +83 -0
- package/src/eval/types.ts +76 -0
- package/src/manager/session/turn-recorder.ts +25 -4
- package/src/public-runtime.ts +53 -0
- package/src/public-types.ts +9 -0
- package/src/registry/command/kernel-commands.ts +94 -6
- package/src/runtime/query/abandon-turn.ts +2 -1
- package/src/runtime/query/index.ts +18 -0
- package/src/runtime/query/iteration/index.ts +85 -1
- package/src/runtime/query/iteration/phases/tool-review.ts +24 -1
- package/src/runtime/query/prelude-lease.ts +7 -2
- package/src/runtime/query/turn/answer-claims.ts +413 -0
- package/src/runtime/query/turn/outcome-ledger.ts +12 -0
- package/src/store/goal/index.ts +11 -0
- package/src/store/session-log/index.ts +8 -0
- package/src/store/session-log/turn-holder.ts +95 -0
- package/src/telemetry/runtime-accessors.ts +29 -0
- package/src/tools/goal/confirmation.ts +375 -0
- package/src/tools/goal/index.ts +37 -1
- package/src/tools/hand-back/index.ts +150 -0
- package/src/tools/schedules/schedule-tool.ts +19 -1
- package/src/tools/schedules/types.ts +10 -0
- package/src/types/connector/mcp.ts +18 -0
- package/src/types/goal/index.ts +13 -0
- package/src/types/session/config.ts +8 -0
- package/src/types/session/hand-back.ts +31 -0
- package/src/types/session/outcome.ts +42 -0
- 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. */
|
|
@@ -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
|
*
|
package/src/types/goal/index.ts
CHANGED
|
@@ -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 {
|
|
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. */
|