@krapht/ork 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (36) hide show
  1. package/dist/{__build_info__-1i1IwLg7.mjs → __build_info__-CxH89h1r.mjs} +2 -2
  2. package/dist/bin.mjs +54 -224
  3. package/dist/{clean-B1zPxq6A.mjs → clean-BbrkMQQi.mjs} +1 -1
  4. package/dist/{completion-BY-frez8.mjs → completion-BeS2NCgO.mjs} +5 -5
  5. package/dist/{context-CD5TCD1D.mjs → context-CHV6aTs7.mjs} +3 -3
  6. package/dist/{decide-CBhYcjwI.mjs → decide-Cnt7o3ya.mjs} +3 -3
  7. package/dist/{find-BgZjVKhv.mjs → find-Dy2H5IvT.mjs} +1 -1
  8. package/dist/{format-Creyq4U3.mjs → format-OL0E1AjA.mjs} +223 -17
  9. package/dist/{in-a-run-CsSBQQ2q.mjs → in-a-run-emTc-kZI.mjs} +1 -1
  10. package/dist/{limits-D_HxH0CK.mjs → limits-TxTnWxix.mjs} +1 -1
  11. package/dist/{list-CzzpVEpM.mjs → list-P5CfcoWx.mjs} +1 -1
  12. package/dist/{mcp-DYmQt372.mjs → mcp-qUwFT9yi.mjs} +6 -6
  13. package/dist/{output-CuivUcDa.mjs → output-CjOPL8W0.mjs} +9 -6
  14. package/dist/{pause-W6MzQqg8.mjs → pause-CBSAxtTp.mjs} +4 -4
  15. package/dist/{relay-CsdoZUXJ.mjs → relay-UgK1Cp9a.mjs} +2 -2
  16. package/dist/{remove-DLfDjBdF.mjs → remove-ccycC3vI.mjs} +3 -3
  17. package/dist/{send-Bw2FI_Sv.mjs → send-B39-Xeo7.mjs} +3 -3
  18. package/dist/{src-CrYCkU9J.mjs → src-5-sCA7cY.mjs} +50 -19
  19. package/dist/{src-Bc9yaAiZ.mjs → src-8nec4uoe.mjs} +14 -7
  20. package/dist/{src-BRB5EQyC.mjs → src-DG4dyBqm.mjs} +61 -20
  21. package/dist/{src-CsjumY1t.mjs → src-jyCJE1kd.mjs} +26 -12
  22. package/dist/ui/THIRD_PARTY_LICENSES.txt +594 -0
  23. package/dist/ui/assets/app-C1DAgdU2.js +75 -0
  24. package/dist/ui/assets/app-Dt_tjFj6.css +1 -0
  25. package/dist/ui/assets/{index-UEC5ZLYw.js → index-BZKXPhht.js} +3 -3
  26. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  27. package/dist/ui/index.html +2 -2
  28. package/dist/{ui-wX050aH1.mjs → ui-DmJWLgxL.mjs} +4 -4
  29. package/dist/{usage-C96RDH-G.mjs → usage-Bh1lVioA.mjs} +5 -5
  30. package/dist/{watch-vdRobJil.mjs → watch-C_vcsn_g.mjs} +6 -6
  31. package/dist/{workflows-B28q8zMY.mjs → workflows-lpexsAYt.mjs} +3 -3
  32. package/dist/{worktrees-CKDRkl6b.mjs → worktrees-D4bVVKfY.mjs} +10 -4
  33. package/package.json +1 -1
  34. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  35. package/dist/ui/assets/app-m9Gfrg_S.js +0 -67
  36. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
@@ -1,6 +1,211 @@
1
- import { ht as liveStatus } from "./src-BRB5EQyC.mjs";
2
- import { i as style, n as cost } from "./output-CuivUcDa.mjs";
1
+ import { cn as maskSecrets, ht as liveStatus } from "./src-DG4dyBqm.mjs";
2
+ import { i as style, n as cost } from "./output-CjOPL8W0.mjs";
3
3
  import { createInterface } from "node:readline/promises";
4
+ // ../core/src/control/queue.ts
5
+ const PERMISSION_OPTIONS = [{
6
+ id: "allow",
7
+ label: "Allow"
8
+ }, {
9
+ id: "deny",
10
+ label: "Deny"
11
+ }];
12
+ const DENIED = "The user did not allow this tool.";
13
+ /** The deny message a session gets, with the person's words when given. */
14
+ const deniedMessage = (note) => note === void 0 ? DENIED : `${DENIED} They said: ${note}`;
15
+ /** The input fields that say what a tool will do, the first one present. */
16
+ const TOOL_INPUT_KEYS = [
17
+ "command",
18
+ "file_path",
19
+ "path",
20
+ "url",
21
+ "pattern"
22
+ ];
23
+ /** A tool's input for the decision's context: as written, cut at `max`. */
24
+ const inputContext = (input, max = 4e3) => {
25
+ const key = TOOL_INPUT_KEYS.find((k) => typeof input[k] === "string");
26
+ const text = key === void 0 ? JSON.stringify(input, void 0, 2) : String(input[key]);
27
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
28
+ };
29
+ const createDecisionQueue = (options) => {
30
+ const { store, runId, emit } = options;
31
+ let count = 0;
32
+ /** Opens a decision and settles it with the first answer. */
33
+ const decide = (open, local, withdrawn) => {
34
+ count += 1;
35
+ const id = `${runId}-${String(count)}`;
36
+ const record = store.openDecision({
37
+ ...open,
38
+ id,
39
+ runId
40
+ });
41
+ const event = {
42
+ type: "decision.opened",
43
+ node: record.node,
44
+ decision: id,
45
+ kind: record.kind,
46
+ question: record.question,
47
+ options: record.options
48
+ };
49
+ if (record.recommended !== void 0) event.recommended = record.recommended;
50
+ if (record.owner !== void 0) event.owner = record.owner;
51
+ emit(event);
52
+ const { promise, resolve, reject } = Promise.withResolvers();
53
+ const settled = new AbortController();
54
+ /** `note` is the stored one: masked, and absent when blank. */
55
+ const finish = (answer, by, note) => {
56
+ if (settled.signal.aborted) return;
57
+ settled.abort();
58
+ const answered = {
59
+ type: "decision.answered",
60
+ node: open.node,
61
+ decision: id,
62
+ answer,
63
+ by
64
+ };
65
+ if (note !== void 0) answered.note = note;
66
+ emit(answered);
67
+ resolve(note === void 0 ? {
68
+ answer,
69
+ by
70
+ } : {
71
+ answer,
72
+ by,
73
+ note
74
+ });
75
+ };
76
+ const withdraw = () => {
77
+ if (settled.signal.aborted) return;
78
+ settled.abort();
79
+ store.withdrawDecision(id);
80
+ emit({
81
+ type: "decision.withdrawn",
82
+ node: open.node,
83
+ decision: id
84
+ });
85
+ reject(/* @__PURE__ */ new Error("the question was withdrawn"));
86
+ };
87
+ if (withdrawn?.aborted === true) withdraw();
88
+ withdrawn?.addEventListener("abort", withdraw, { once: true });
89
+ const fromStore = async () => {
90
+ try {
91
+ const closed = await options.waitFor(id, settled.signal);
92
+ if (closed.status === "answered" && closed.answer !== void 0) finish(closed.answer, closed.answeredBy ?? "decide", closed.note);
93
+ } catch {}
94
+ };
95
+ const fromTerminal = async (ask) => {
96
+ try {
97
+ const { answer, note } = await ask(settled.signal);
98
+ if (store.answerDecision(id, answer, "terminal", note)) finish(answer, "terminal", store.decision(id)?.note);
99
+ } catch {}
100
+ };
101
+ fromStore();
102
+ if (local !== void 0) fromTerminal(local);
103
+ return promise;
104
+ };
105
+ return {
106
+ ask: (question) => {
107
+ const open = {
108
+ node: question.nodeId,
109
+ kind: "question",
110
+ question: question.question,
111
+ options: question.options
112
+ };
113
+ if (question.recommended !== void 0) open.recommended = question.recommended;
114
+ if (question.context !== void 0) open.context = question.context;
115
+ if (question.document !== void 0) open.document = question.document;
116
+ if (question.owner !== void 0) open.owner = question.owner;
117
+ const { local } = options;
118
+ return decide(open, local === void 0 ? void 0 : (signal) => {
119
+ let note;
120
+ return local.ask({
121
+ ...question,
122
+ question: maskSecrets(question.question),
123
+ ...question.context === void 0 ? {} : { context: maskSecrets(question.context) },
124
+ ...question.document === void 0 ? {} : { document: maskSecrets(question.document) },
125
+ signal,
126
+ onNote: (given) => {
127
+ note = given;
128
+ }
129
+ }).then((answer) => note === void 0 ? { answer } : {
130
+ answer,
131
+ note
132
+ });
133
+ }, question.signal).then(({ answer, note }) => {
134
+ if (note !== void 0) question.onNote?.(note);
135
+ return answer;
136
+ });
137
+ },
138
+ settled: (question) => {
139
+ count += 1;
140
+ const id = `${runId}-${String(count)}`;
141
+ const { owner } = question;
142
+ const record = store.openDecision({
143
+ id,
144
+ runId,
145
+ node: question.nodeId,
146
+ kind: "question",
147
+ question: question.question,
148
+ options: question.options,
149
+ owner,
150
+ ...question.recommended === void 0 ? {} : { recommended: question.recommended },
151
+ ...question.context === void 0 ? {} : { context: question.context }
152
+ });
153
+ emit({
154
+ type: "decision.opened",
155
+ node: record.node,
156
+ decision: id,
157
+ kind: "question",
158
+ question: record.question,
159
+ options: record.options,
160
+ owner,
161
+ ...record.recommended === void 0 ? {} : { recommended: record.recommended }
162
+ });
163
+ store.answerDecision(id, question.answer, "owner", question.note);
164
+ const note = store.decision(id)?.note;
165
+ emit({
166
+ type: "decision.answered",
167
+ node: record.node,
168
+ decision: id,
169
+ answer: question.answer,
170
+ by: "owner",
171
+ owner,
172
+ ...note === void 0 ? {} : { note }
173
+ });
174
+ },
175
+ permit: async (request) => {
176
+ const { local } = options;
177
+ let localMessage = DENIED;
178
+ const { answer, note } = await decide({
179
+ node: request.nodeId,
180
+ kind: "permission",
181
+ question: `Allow ${request.toolName}?`,
182
+ options: PERMISSION_OPTIONS,
183
+ context: inputContext(request.input)
184
+ }, local === void 0 ? void 0 : (signal) => {
185
+ let said;
186
+ return local.permit({
187
+ ...request,
188
+ signal,
189
+ onNote: (given) => {
190
+ said = given;
191
+ }
192
+ }).then((result) => {
193
+ if (!result.allow) localMessage = result.message;
194
+ const choice = result.allow ? "allow" : "deny";
195
+ return said === void 0 ? { answer: choice } : {
196
+ answer: choice,
197
+ note: said
198
+ };
199
+ });
200
+ });
201
+ if (note !== void 0) request.onNote?.(note);
202
+ return answer === "allow" ? { allow: true } : {
203
+ allow: false,
204
+ message: note === void 0 ? localMessage : deniedMessage(note)
205
+ };
206
+ }
207
+ };
208
+ };
4
209
  // src/human.ts
5
210
  /**
6
211
  * @file The terminal `HumanChannel`: answers `ask_user` questions, gates and
@@ -10,18 +215,14 @@ import { createInterface } from "node:readline/promises";
10
215
  * answerer, racing `ork decide`.
11
216
  */
12
217
  const PREVIEW = 160;
218
+ /** A document longer than this is offered (`d`) rather than printed. */
219
+ const SHORT_DOCUMENT_LINES = 40;
13
220
  const NO_ONE_TO_PERMIT = "No one is at the terminal to approve this tool, so it is not allowed in this run.";
14
221
  const NOTE_PROMPT = " Note for the run (optional, Enter to skip): ";
15
222
  const NO_ONE_TO_ASK = "no one (nobody is at the terminal; use your best judgment and say so in your report)";
16
223
  /** One line that shows what a tool is about to do. */
17
224
  const previewInput = (toolName, input) => {
18
- const key = [
19
- "command",
20
- "file_path",
21
- "path",
22
- "url",
23
- "pattern"
24
- ].find((k) => typeof input[k] === "string");
225
+ const key = TOOL_INPUT_KEYS.find((k) => typeof input[k] === "string");
25
226
  const line = (key === void 0 ? JSON.stringify(input) : `${key}: ${String(input[key])}`).replaceAll(/\s+/gu, " ").trim();
26
227
  return line.length > PREVIEW ? `${toolName} ${line.slice(0, 159)}…` : `${toolName} ${line}`;
27
228
  };
@@ -63,11 +264,13 @@ const createTerminalHuman = (io) => {
63
264
  const note = (await prompt(NOTE_PROMPT, signal)).trim();
64
265
  if (note !== "") onNote(note);
65
266
  };
66
- const choose = async (hint, options, recommended, signal) => {
67
- const picked = pickOption(await prompt(` Choose${hint}: `, signal), options, recommended);
267
+ const choose = async (hint, options, recommended, signal, document) => {
268
+ const answer = await prompt(` Choose${hint}: `, signal);
269
+ const picked = pickOption(answer, options, recommended);
68
270
  if (picked !== void 0) return picked;
69
- say(style("dim", " Type an option's number or id."));
70
- return choose(hint, options, recommended, signal);
271
+ if (document !== void 0 && answer.trim() === "d") say(document);
272
+ else say(style("dim", " Type an option's number or id."));
273
+ return choose(hint, options, recommended, signal, document);
71
274
  };
72
275
  const oneAtATime = (work) => {
73
276
  const result = turn.then(work);
@@ -93,10 +296,12 @@ const createTerminalHuman = (io) => {
93
296
  message: "The user did not allow this tool."
94
297
  };
95
298
  }),
96
- ask: ({ nodeId, question, options, recommended, context, signal, onNote }) => oneAtATime(async () => {
299
+ ask: ({ nodeId, question, options, recommended, context, document, signal, onNote }) => oneAtATime(async () => {
97
300
  if (signal?.aborted === true) throw new Error("the question was withdrawn");
301
+ const documentLines = document?.trimEnd().split("\n").length ?? 0;
302
+ if (document !== void 0 && documentLines <= SHORT_DOCUMENT_LINES) say(document);
98
303
  say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} asks: ${question}`);
99
- if (context !== void 0) say(style("dim", ` ${context}`));
304
+ if (context !== void 0) say(style("dim", ` ${context.replaceAll("\n", "\n ")}`));
100
305
  for (const [index, option] of options.entries()) say(optionLine(option, index, recommended));
101
306
  if (!io.interactive) {
102
307
  const answer = recommended ?? NO_ONE_TO_ASK;
@@ -105,7 +310,8 @@ const createTerminalHuman = (io) => {
105
310
  }
106
311
  const fallback = options.findIndex((o) => o.id === recommended);
107
312
  const hint = fallback === -1 ? "" : ` [${String(fallback + 1)}]`;
108
- const picked = await choose(hint, options, recommended, signal);
313
+ if (documentLines > SHORT_DOCUMENT_LINES) say(` Document: ${String(documentLines)} lines. Type d to read it.`);
314
+ const picked = await choose(hint, options, recommended, signal, document);
109
315
  await takeNote(onNote, signal);
110
316
  return picked;
111
317
  }),
@@ -424,4 +630,4 @@ const formatTree = (roots) => {
424
630
  return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${usage}`;
425
631
  });
426
632
  };
427
- export { formatRunEvent as a, formatEvent as i, formatRuns as n, createTerminalHuman as o, formatTree as r, ago as t };
633
+ export { formatRunEvent as a, formatEvent as i, formatRuns as n, createTerminalHuman as o, formatTree as r, createDecisionQueue as s, ago as t };
@@ -1,4 +1,4 @@
1
- import { p as runMarkerIn } from "./src-Bc9yaAiZ.mjs";
1
+ import { p as runMarkerIn } from "./src-8nec4uoe.mjs";
2
2
  // src/commands/in-a-run.ts
3
3
  /**
4
4
  * @file The commands that act as the user refuse a program or an agent in a run
@@ -1,4 +1,4 @@
1
- import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-BRB5EQyC.mjs";
1
+ import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-DG4dyBqm.mjs";
2
2
  // src/run/limits.ts
3
3
  /**
4
4
  * @file The limits across runs that apply here (RFC-007): the user's settings
@@ -1,4 +1,4 @@
1
- import { A as gitOk, j as lines, k as git } from "./src-BRB5EQyC.mjs";
1
+ import { A as gitOk, j as lines, k as git } from "./src-DG4dyBqm.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  // ../core/src/worktree/list.ts
4
4
  /**
@@ -1,8 +1,8 @@
1
- import { h as isNewer } from "./src-BRB5EQyC.mjs";
2
- import { n as ORK_VERSION } from "./__build_info__-1i1IwLg7.mjs";
3
- import { a as openMcp, c as refused, o as tool, s as answer } from "./src-CsjumY1t.mjs";
4
- import { p as runMarkerIn } from "./src-Bc9yaAiZ.mjs";
5
- import { n as explainNode } from "./context-CD5TCD1D.mjs";
1
+ import { h as isNewer } from "./src-DG4dyBqm.mjs";
2
+ import { n as ORK_VERSION } from "./__build_info__-CxH89h1r.mjs";
3
+ import { a as openMcp, c as refused, o as tool, s as answer } from "./src-jyCJE1kd.mjs";
4
+ import { p as runMarkerIn } from "./src-8nec4uoe.mjs";
5
+ import { n as explainNode } from "./context-CHV6aTs7.mjs";
6
6
  import { z } from "zod";
7
7
  import { defineCommand } from "citty";
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -43,7 +43,7 @@ const orkMcpServer = (options = {}) => {
43
43
  ...options.script === void 0 ? {} : { script: options.script }
44
44
  });
45
45
  const all = [...opened.tools, explainTool(opened, options.home)];
46
- const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.4") ? tooOld(all, options.expects) : all;
46
+ const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.6") ? tooOld(all, options.expects) : all;
47
47
  const server = new McpServer({
48
48
  name: "ork",
49
49
  version: ORK_VERSION
@@ -1,4 +1,4 @@
1
- import { $ as text, $t as maskReplacer, Ht as FEEDBACK_SEVERITIES, Q as inTransaction, Ut as FEEDBACK_TARGETS, Vt as FEEDBACK_KINDS, X as usageQueries, Y as backfillUsage, Z as eventOf, en as maskSecrets, yn as PROJECT_MODES, yt as ownProcessStart } from "./src-BRB5EQyC.mjs";
1
+ import { $ as text, Ht as FEEDBACK_SEVERITIES, Q as inTransaction, Ut as FEEDBACK_TARGETS, Vt as FEEDBACK_KINDS, X as usageQueries, Y as backfillUsage, Z as eventOf, cn as maskSecrets, sn as maskReplacer, yn as PROJECT_MODES, yt as ownProcessStart } from "./src-DG4dyBqm.mjs";
2
2
  import path from "node:path";
3
3
  import { mkdirSync } from "node:fs";
4
4
  import { homedir } from "node:os";
@@ -70,6 +70,7 @@ const decisionOf = (row) => {
70
70
  const optional = {
71
71
  recommended: text(row, "recommended"),
72
72
  context: text(row, "context"),
73
+ document: text(row, "document"),
73
74
  answer: text(row, "answer"),
74
75
  answeredAt: text(row, "answered_at"),
75
76
  note: text(row, "note"),
@@ -81,8 +82,8 @@ const decisionOf = (row) => {
81
82
  return record;
82
83
  };
83
84
  const decisionQueries = (db) => {
84
- const insert = db.prepare(`INSERT INTO decisions (id, run_id, node, kind, question, options, recommended, context, owner, opened_at, status)
85
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open')`);
85
+ const insert = db.prepare(`INSERT INTO decisions (id, run_id, node, kind, question, options, recommended, context, document, owner, opened_at, status)
86
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open')`);
86
87
  const answer = db.prepare(`UPDATE decisions SET status = 'answered', answer = ?, answered_by = ?, answered_at = ?, note = ?
87
88
  WHERE id = ? AND status = 'open'`);
88
89
  const withdraw = db.prepare("UPDATE decisions SET status = 'withdrawn', answered_at = ? WHERE id = ? AND status = 'open'");
@@ -102,9 +103,10 @@ const decisionQueries = (db) => {
102
103
  const masked = {
103
104
  ...decision,
104
105
  question: maskSecrets(decision.question),
105
- ...decision.context === void 0 ? {} : { context: maskSecrets(decision.context) }
106
+ ...decision.context === void 0 ? {} : { context: maskSecrets(decision.context) },
107
+ ...decision.document === void 0 ? {} : { document: maskSecrets(decision.document) }
106
108
  };
107
- insert.run(masked.id, masked.runId, masked.node, masked.kind, masked.question, JSON.stringify(masked.options), masked.recommended ?? null, masked.context ?? null, masked.owner ?? null, openedAt);
109
+ insert.run(masked.id, masked.runId, masked.node, masked.kind, masked.question, JSON.stringify(masked.options), masked.recommended ?? null, masked.context ?? null, masked.document ?? null, masked.owner ?? null, openedAt);
108
110
  return {
109
111
  ...masked,
110
112
  openedAt,
@@ -731,7 +733,8 @@ const MIGRATIONS = [
731
733
  `ALTER TABLE runs ADD COLUMN skipped INTEGER;
732
734
  ALTER TABLE runs ADD COLUMN failures_handled INTEGER;`,
733
735
  `ALTER TABLE decisions ADD COLUMN note TEXT;`,
734
- `ALTER TABLE decisions ADD COLUMN owner TEXT;`
736
+ `ALTER TABLE decisions ADD COLUMN owner TEXT;`,
737
+ `ALTER TABLE decisions ADD COLUMN document TEXT;`
735
738
  ];
736
739
  const versionOf = (db) => Number(db.prepare("PRAGMA user_version").get()?.["user_version"] ?? 0);
737
740
  /**
@@ -1,7 +1,7 @@
1
- import { G as requestContinue, K as requestPause } from "./src-BRB5EQyC.mjs";
2
- import { t as findRun } from "./find-BgZjVKhv.mjs";
3
- import { a as write, c as openStore, i as style, o as writeErr } from "./output-CuivUcDa.mjs";
4
- import { r as continueRefusal } from "./in-a-run-CsSBQQ2q.mjs";
1
+ import { G as requestContinue, K as requestPause } from "./src-DG4dyBqm.mjs";
2
+ import { t as findRun } from "./find-Dy2H5IvT.mjs";
3
+ import { a as write, c as openStore, i as style, o as writeErr } from "./output-CjOPL8W0.mjs";
4
+ import { r as continueRefusal } from "./in-a-run-emTc-kZI.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  // src/commands/pause.ts
7
7
  /**
@@ -1,5 +1,5 @@
1
- import { B as linkNodes, L as addressOf, U as unlinkNodes } from "./src-BRB5EQyC.mjs";
2
- import { a as write, c as openStore, i as style, o as writeErr } from "./output-CuivUcDa.mjs";
1
+ import { B as linkNodes, L as addressOf, U as unlinkNodes } from "./src-DG4dyBqm.mjs";
2
+ import { a as write, c as openStore, i as style, o as writeErr } from "./output-CjOPL8W0.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  // src/commands/relay.ts
5
5
  /**
@@ -1,6 +1,6 @@
1
- import { ht as liveStatus, j as lines, k as git } from "./src-BRB5EQyC.mjs";
2
- import { n as listOrkWorktrees, t as createdWorktrees } from "./list-CzzpVEpM.mjs";
3
- import { i as keyOf, n as applyStep, r as cleanStep, s as stepPrunes } from "./clean-B1zPxq6A.mjs";
1
+ import { ht as liveStatus, j as lines, k as git } from "./src-DG4dyBqm.mjs";
2
+ import { n as listOrkWorktrees, t as createdWorktrees } from "./list-P5CfcoWx.mjs";
3
+ import { i as keyOf, n as applyStep, r as cleanStep, s as stepPrunes } from "./clean-BbrkMQQi.mjs";
4
4
  // ../core/src/worktree/words.ts
5
5
  /**
6
6
  * @file The plain words for what `ork worktrees` shows, in one place, so the
@@ -1,6 +1,6 @@
1
- import { cn as wakeRun, ht as liveStatus, kt as scriptContactsOf, on as socketPath } from "./src-BRB5EQyC.mjs";
2
- import { c as openStore, o as writeErr } from "./output-CuivUcDa.mjs";
3
- import { u as inheritedScrubber } from "./src-Bc9yaAiZ.mjs";
1
+ import { ht as liveStatus, in as socketPath, kt as scriptContactsOf, on as wakeRun } from "./src-DG4dyBqm.mjs";
2
+ import { c as openStore, o as writeErr } from "./output-CjOPL8W0.mjs";
3
+ import { u as inheritedScrubber } from "./src-8nec4uoe.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  // ../core/src/remote/send.ts
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { Ht as FEEDBACK_SEVERITIES, Ut as FEEDBACK_TARGETS, Vt as FEEDBACK_KINDS, pt as parseReport } from "./src-BRB5EQyC.mjs";
1
+ import { Ht as FEEDBACK_SEVERITIES, Ut as FEEDBACK_TARGETS, Vt as FEEDBACK_KINDS, pt as parseReport } from "./src-DG4dyBqm.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createInterface } from "node:readline";
@@ -336,20 +336,45 @@ const text = (value) => ({ content: [{
336
336
  type: "text",
337
337
  text: value
338
338
  }] });
339
- const defineTool = (definition) => {
340
- const schema = z.object(definition.shape);
341
- return {
342
- name: definition.name,
343
- bind: (context) => definition.available?.(context) ?? true ? {
339
+ const issueAt = (issue) => {
340
+ const where = z.core.toDotPath(issue.path);
341
+ return where === "" ? "" : `${where}: `;
342
+ };
343
+ /** Whether `issue` is about a field of the object `parent` is about. */
344
+ const isFieldOf = (issue, parent) => issue.path.length === parent.path.length + 1 && parent.path.every((key, index) => key === issue.path[index]);
345
+ const expectedField = (issue) => `; the field is \`${String(issue.path.at(-1))}\`${issue.code === "invalid_value" ? `, one of: ${issue.values.join(", ")}` : ""}`;
346
+ /**
347
+ * A failed parse as text a model can act on. A stray key is reported with the
348
+ * fields its object lacks, read from the schema's own issues: a wrong name is
349
+ * otherwise only a "missing" error that hides what was sent.
350
+ */
351
+ const readableIssues = (issues) => {
352
+ const strays = issues.filter((issue) => issue.code === "unrecognized_keys");
353
+ const lacking = issues.filter((issue) => issue.input === void 0 && strays.some((stray) => isFieldOf(issue, stray)));
354
+ return [...strays.map((stray) => `${issueAt(stray)}unknown field ${stray.keys.map((key) => `\`${key}\``).join(", ")}${lacking.filter((issue) => isFieldOf(issue, stray)).map((issue) => expectedField(issue)).join("")}`), ...issues.filter((issue) => issue.code !== "unrecognized_keys" && !lacking.includes(issue)).map((issue) => `${issueAt(issue)}${issue.message}`)].join("\n");
355
+ };
356
+ const defineTool = (definition) => ({
357
+ name: definition.name,
358
+ bind: (context) => {
359
+ if (!(definition.available?.(context) ?? true)) return null;
360
+ const shape = typeof definition.shape === "function" ? definition.shape(context) : definition.shape;
361
+ const schema = z.object(shape);
362
+ return {
344
363
  name: definition.name,
345
364
  description: definition.description,
346
- shape: definition.shape,
365
+ shape,
347
366
  schema,
348
367
  preapproved: definition.preapproved ?? true,
349
- call: (args) => definition.run(context, schema.parse(args))
350
- } : null
351
- };
352
- };
368
+ call: (args) => {
369
+ const parsed = schema.safeParse(args, { reportInput: true });
370
+ return parsed.success ? definition.run(context, parsed.data) : Promise.resolve({
371
+ ...text(readableIssues(parsed.error.issues)),
372
+ isError: true
373
+ });
374
+ }
375
+ };
376
+ }
377
+ });
353
378
  // ../runtime-claude/src/mcp-http.ts
354
379
  /**
355
380
  * @file A minimal MCP server over the streamable HTTP transport, for the
@@ -621,10 +646,13 @@ const delegationText = (delegation) => text(JSON.stringify({
621
646
  const delegateTool = defineTool({
622
647
  name: "delegate",
623
648
  description: "Hand tasks to nodes you own. Tasks in one call run in parallel; the call returns every child's report when all are done. Ork merges a passing child's branch into yours.",
624
- shape: { tasks: z.array(z.object({
625
- child: z.string().describe("The id of a node you own."),
626
- brief: BRIEF
627
- })).min(1) },
649
+ shape: ({ ports }) => {
650
+ const ids = ports.ownership?.children.map((child) => child.id) ?? [];
651
+ return { tasks: z.array(z.strictObject({
652
+ child: z.enum(ids).describe(`The id of a node you own: ${ids.join(", ")}.`),
653
+ brief: BRIEF
654
+ })).min(1) };
655
+ },
628
656
  available: ({ ports }) => (ports.ownership?.children.length ?? 0) > 0,
629
657
  run: async ({ ports }, args) => {
630
658
  if (ports.ownership === void 0) return text("You own no nodes.");
@@ -634,10 +662,13 @@ const delegateTool = defineTool({
634
662
  const spawnWorkerTool = defineTool({
635
663
  name: "spawn_worker",
636
664
  description: "Start new instances of a child worker for work that splits into independent parts. Each runs in its own worktree; the call returns their reports when all are done.",
637
- shape: { tasks: z.array(z.object({
638
- template: z.string().describe("The id of a child worker you own."),
639
- brief: BRIEF
640
- })).min(1) },
665
+ shape: ({ ports }) => {
666
+ const ids = ports.ownership?.templates ?? [];
667
+ return { tasks: z.array(z.strictObject({
668
+ template: z.enum(ids).describe(`The id of a child worker you own: ${ids.join(", ")}.`),
669
+ brief: BRIEF
670
+ })).min(1) };
671
+ },
641
672
  available: ({ ports }) => ports.ownership !== void 0 && ports.ownership.templates.length > 0 && ports.ownership.workersLeft() > 0,
642
673
  run: async ({ ports }, args) => {
643
674
  if (ports.ownership === void 0) return text("You own no workers.");
@@ -1,4 +1,4 @@
1
- import { $n as isToolNode, At as runScope, Bt as serversIn, F as declaredProblem, Mt as envReadsOf, Ot as heldTimeout, Pt as declaredOutputOf, Rt as MCP_FILE, Wn as markValue, Zn as beforeOf, ar as integrationFor, er as programsRunBy, ft as sessionDriver, ir as TOOL_PREFIX, jt as calledWorkflows, mt as reportSchemaFor, nn as parseTemplate, nr as timeoutMs, rn as renderTemplate, rr as INTEGRATIONS, s as planReason, tn as evaluate, ut as planNodeSession, zt as mcpNotText } from "./src-BRB5EQyC.mjs";
1
+ import { $n as isToolNode, $t as evaluate, At as runScope, Bt as serversIn, F as declaredProblem, Mt as envReadsOf, Ot as heldTimeout, Pt as declaredOutputOf, Rt as MCP_FILE, Wn as markValue, Zn as beforeOf, ar as integrationFor, en as parseTemplate, er as programsRunBy, ft as sessionDriver, ir as TOOL_PREFIX, jt as calledWorkflows, mt as reportSchemaFor, nr as timeoutMs, rr as INTEGRATIONS, s as planReason, tn as renderTemplate, ut as planNodeSession, zt as mcpNotText } from "./src-DG4dyBqm.mjs";
2
2
  import path from "node:path";
3
3
  import { accessSync, constants, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { spawn } from "node:child_process";
@@ -1689,10 +1689,11 @@ const claudeExecutor = (options) => async (context) => {
1689
1689
  };
1690
1690
  // ../nodes/src/gate.ts
1691
1691
  /**
1692
- * @file The `human.gate` node type: asks the human the rendered question,
1693
- * outputs their choice with the note they gave, and answers with the
1694
- * `default` when the `timeout` passes. The timeout counts only while the run
1695
- * and the gate are not paused (ADR-041).
1692
+ * @file The `human.gate` node type: asks the human the rendered question, with
1693
+ * its document if it has one, both in readable text (no raw JSON), outputs
1694
+ * their choice with the note they gave, and answers with the `default` when
1695
+ * the `timeout` passes. The timeout counts only while the run and the gate
1696
+ * are not paused (ADR-041).
1696
1697
  */
1697
1698
  /** A promise that never settles: the losing side of a race. */
1698
1699
  const NEVER = new Promise(() => {});
@@ -1714,8 +1715,13 @@ const gateExecutor = async (context) => {
1714
1715
  output: {},
1715
1716
  error: `${context.id} is not a gate`
1716
1717
  };
1717
- const template = parseTemplate(node.question);
1718
- const question = template.ok ? renderTemplate(template.parts, runScope(context.inputs, context.outputs, context.runId)) : node.question;
1718
+ const scope = runScope(context.inputs, context.outputs, context.runId);
1719
+ const render = (text) => {
1720
+ const template = parseTemplate(text);
1721
+ return template.ok ? renderTemplate(template.parts, scope, { readable: true }) : text;
1722
+ };
1723
+ const question = render(node.question);
1724
+ const document = render(node.document ?? "");
1719
1725
  const options = node.options.map((o) => choiceOf(o));
1720
1726
  const withdraw = new AbortController();
1721
1727
  context.signal.addEventListener("abort", () => {
@@ -1732,6 +1738,7 @@ const gateExecutor = async (context) => {
1732
1738
  }
1733
1739
  };
1734
1740
  if (node.recommended !== void 0) asked.recommended = node.recommended;
1741
+ if (document.trim() !== "") asked.document = document;
1735
1742
  const answer = context.human.ask(asked).then((choice) => ({
1736
1743
  choice,
1737
1744
  answeredBy: "user",