@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.20

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 (71) hide show
  1. package/README.md +12 -7
  2. package/dist/__build_info__-D6mXC4cD.mjs +228 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/argument-error-CihH46Le.mjs +27 -0
  5. package/dist/attach-C1OnxyMh.mjs +92 -0
  6. package/dist/attach-CEGbRg1D.mjs +1230 -0
  7. package/dist/bin.mjs +1312 -2390
  8. package/dist/brief-DH66xmj8.mjs +115 -0
  9. package/dist/{decide-CGyx9rv7.mjs → builtin-files-Cf-NZDqM.mjs} +3 -51
  10. package/dist/chips-BqcT58qr.mjs +43 -0
  11. package/dist/command-tree-CEkzWNy6.mjs +63 -0
  12. package/dist/{completion-CSU5vkEt.mjs → completion-DS0gCZRm.mjs} +200 -30
  13. package/dist/context-CGPh12HV.mjs +106 -0
  14. package/dist/detached-Dakw5RZj.mjs +38 -0
  15. package/dist/extra-arguments-BxQMXARO.mjs +13 -0
  16. package/dist/{find-D6Gj1sxg.mjs → find-BTikxc54.mjs} +2 -2
  17. package/dist/flags-C8yCq0j5.mjs +145 -0
  18. package/dist/follow-ups-CtlhHbwe.mjs +563 -0
  19. package/dist/format-BB4E-T6x.mjs +249 -0
  20. package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BpRgBSTh.mjs} +42 -76
  21. package/dist/in-a-run-CbnKjO7t.mjs +27 -0
  22. package/dist/install-CWcpX5g4.mjs +211 -0
  23. package/dist/{limits-B1-DYc3d.mjs → limits-VQqsBoQk.mjs} +2 -2
  24. package/dist/{mcp-DE_EmyVm.mjs → mcp-wGfkMCVF.mjs} +6 -6
  25. package/dist/once-BHxSdZz_.mjs +112 -0
  26. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  27. package/dist/output-B1kwo0YU.mjs +439 -0
  28. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  29. package/dist/{pause-CL6N_Xrj.mjs → pause-C_Wxh-KM.mjs} +7 -4
  30. package/dist/print-DNYzq4Rr.mjs +19 -0
  31. package/dist/project-records-XY3ku95x.mjs +233 -0
  32. package/dist/refusals-DQQX3AIb.mjs +116 -0
  33. package/dist/{relay-0SD0erwf.mjs → relay-BA6tsnpc.mjs} +2 -2
  34. package/dist/run-Bvvg-fiF.mjs +234 -0
  35. package/dist/run-DBUAq-Uu.mjs +1452 -0
  36. package/dist/send-nrKtak5F.mjs +105 -0
  37. package/dist/{src-Dujmvv4s.mjs → src-8cOPOgrT.mjs} +12523 -5507
  38. package/dist/{src-BbyEoMn4.mjs → src-CmEvHdju.mjs} +797 -233
  39. package/dist/src-D4JSymhK.mjs +3104 -0
  40. package/dist/{src-DOcK8Jfp.mjs → src-I_RyKu6_.mjs} +1022 -590
  41. package/dist/store-CDpk8Wtz.mjs +16 -0
  42. package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
  43. package/dist/ui/assets/app--FijEXiJ.css +1 -0
  44. package/dist/ui/assets/app-BowhjEAe.js +112 -0
  45. package/dist/ui/assets/{index-9dFha9Qn.js → index-CrMa2oHB.js} +3 -3
  46. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  47. package/dist/ui/index.html +2 -2
  48. package/dist/{ui-CTmO2Xzu.mjs → ui-DY3Dnpho.mjs} +54 -25
  49. package/dist/usage-1VwSg_Qt.mjs +257 -0
  50. package/dist/validate-BxayjjkV.mjs +632 -0
  51. package/dist/verdict-CMvPCpWC.mjs +84 -0
  52. package/dist/{watch-CtbgDAcz.mjs → watch-CT0EtLPZ.mjs} +45 -17
  53. package/dist/{workflows-D1_WVukk.mjs → workflows-DHHi-APg.mjs} +30 -5
  54. package/dist/worktrees-CZvbHzbN.mjs +302 -0
  55. package/package.json +10 -8
  56. package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
  57. package/dist/clean-DaHjs__2.mjs +0 -88
  58. package/dist/context-DT8MfEIR.mjs +0 -274
  59. package/dist/format-Dnfv8zMT.mjs +0 -427
  60. package/dist/in-a-run-pLAZeNys.mjs +0 -22
  61. package/dist/list-BrH8KRSJ.mjs +0 -110
  62. package/dist/output-BLQWD6MJ.mjs +0 -950
  63. package/dist/remove-BeGdqroy.mjs +0 -194
  64. package/dist/report-DK4LGqwF.mjs +0 -129
  65. package/dist/send-C8tunWhv.mjs +0 -163
  66. package/dist/src-CCUKRX51.mjs +0 -1204
  67. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  68. package/dist/ui/assets/app-T7wVAhgx.js +0 -67
  69. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
  70. package/dist/usage-DYUmP0i4.mjs +0 -419
  71. package/dist/worktrees-DdMYoNOC.mjs +0 -212
@@ -1,427 +0,0 @@
1
- import { ht as liveStatus } from "./src-Dujmvv4s.mjs";
2
- import { i as style, n as cost } from "./output-BLQWD6MJ.mjs";
3
- import { createInterface } from "node:readline/promises";
4
- // src/human.ts
5
- /**
6
- * @file The terminal `HumanChannel`: answers `ask_user` questions, gates and
7
- * permission prompts on the terminal, one at a time, and denies or defers
8
- * when no one is there. After a choice it offers one optional line for a
9
- * note, which Enter skips. In a run it is the decision queue's local
10
- * answerer, racing `ork decide`.
11
- */
12
- const PREVIEW = 160;
13
- const NO_ONE_TO_PERMIT = "No one is at the terminal to approve this tool, so it is not allowed in this run.";
14
- const NOTE_PROMPT = " Note for the run (optional, Enter to skip): ";
15
- const NO_ONE_TO_ASK = "no one (nobody is at the terminal; use your best judgment and say so in your report)";
16
- /** One line that shows what a tool is about to do. */
17
- 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");
25
- const line = (key === void 0 ? JSON.stringify(input) : `${key}: ${String(input[key])}`).replaceAll(/\s+/gu, " ").trim();
26
- return line.length > PREVIEW ? `${toolName} ${line.slice(0, 159)}…` : `${toolName} ${line}`;
27
- };
28
- /** The option an answer names: its number, or its id. */
29
- const pickOption = (answer, options, recommended) => {
30
- const value = answer.trim();
31
- if (value === "") return recommended;
32
- const index = Number(value);
33
- if (Number.isInteger(index) && index >= 1 && index <= options.length) return options[index - 1]?.id;
34
- return options.find((o) => o.id === value)?.id;
35
- };
36
- const optionLine = (option, index, recommended) => {
37
- const label = option.label === void 0 ? "" : ` ${option.label}`;
38
- const description = option.description === void 0 ? "" : style("dim", ` - ${option.description}`);
39
- const mark = option.id === recommended ? style("green", " (recommended)") : "";
40
- return ` ${String(index + 1)}) ${style("bold", option.id)}${label}${description}${mark}`;
41
- };
42
- /**
43
- * Answers permission prompts and `ask_user` questions on the terminal, one at a
44
- * time. Without a terminal it denies tools and leaves questions to the node. A
45
- * withdrawn prompt (answered elsewhere) rejects, which frees the turn.
46
- */
47
- const createTerminalHuman = (io) => {
48
- let rl;
49
- let turn = Promise.resolve();
50
- const say = (text) => {
51
- io.output.write(`${text}\n`);
52
- };
53
- const prompt = (text, signal) => {
54
- rl ??= createInterface({
55
- input: io.input,
56
- output: io.output
57
- });
58
- return signal === void 0 ? rl.question(text) : rl.question(text, { signal });
59
- };
60
- /** The optional line after a choice, when the asker takes a note. */
61
- const takeNote = async (onNote, signal) => {
62
- if (onNote === void 0) return;
63
- const note = (await prompt(NOTE_PROMPT, signal)).trim();
64
- if (note !== "") onNote(note);
65
- };
66
- const choose = async (hint, options, recommended, signal) => {
67
- const picked = pickOption(await prompt(` Choose${hint}: `, signal), options, recommended);
68
- if (picked !== void 0) return picked;
69
- say(style("dim", " Type an option's number or id."));
70
- return choose(hint, options, recommended, signal);
71
- };
72
- const oneAtATime = (work) => {
73
- const result = turn.then(work);
74
- turn = result.catch(() => null);
75
- return result;
76
- };
77
- return {
78
- permit: ({ nodeId, toolName, input, signal, onNote }) => oneAtATime(async () => {
79
- if (signal?.aborted === true) throw new Error("the prompt was withdrawn");
80
- say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} wants to run ${previewInput(toolName, input)}`);
81
- if (!io.interactive) {
82
- say(style("dim", ` denied: ${NO_ONE_TO_PERMIT}`));
83
- return {
84
- allow: false,
85
- message: NO_ONE_TO_PERMIT
86
- };
87
- }
88
- const answer = (await prompt(" Allow? [y/N] ", signal)).trim().toLowerCase();
89
- if (answer === "y" || answer === "yes") return { allow: true };
90
- await takeNote(onNote, signal);
91
- return {
92
- allow: false,
93
- message: "The user did not allow this tool."
94
- };
95
- }),
96
- ask: ({ nodeId, question, options, recommended, context, signal, onNote }) => oneAtATime(async () => {
97
- if (signal?.aborted === true) throw new Error("the question was withdrawn");
98
- say(`${style(["yellow", "bold"], "?")} ${style("bold", nodeId)} asks: ${question}`);
99
- if (context !== void 0) say(style("dim", ` ${context}`));
100
- for (const [index, option] of options.entries()) say(optionLine(option, index, recommended));
101
- if (!io.interactive) {
102
- const answer = recommended ?? NO_ONE_TO_ASK;
103
- say(style("dim", ` no terminal; answered ${answer}`));
104
- return answer;
105
- }
106
- const fallback = options.findIndex((o) => o.id === recommended);
107
- const hint = fallback === -1 ? "" : ` [${String(fallback + 1)}]`;
108
- const picked = await choose(hint, options, recommended, signal);
109
- await takeNote(onNote, signal);
110
- return picked;
111
- }),
112
- close: () => {
113
- rl?.close();
114
- }
115
- };
116
- };
117
- // src/run/format-bus.ts
118
- /** The first line of a message body, cut to fit one terminal line. */
119
- const firstLine = (body) => {
120
- const [line = ""] = body.trim().split("\n");
121
- return line.length > 100 ? `${line.slice(0, 99)}…` : line;
122
- };
123
- /** Messages, owner questions and claims, without the node prefix. */
124
- const formatBusEvent = (event) => {
125
- if (event.type === "claim.taken") return [style("dim", `⚑ claimed ${event.resource}`)];
126
- if (event.type === "claim.refused") {
127
- const held = event.held === event.resource ? "" : ` (it holds ${event.held})`;
128
- return [style("yellow", `⚑ ${event.resource} is ${event.holder}'s${held}`)];
129
- }
130
- if (event.type === "claim.released") return [style("dim", `⚑ released ${event.resource}`)];
131
- if (event.type === "message.sent") {
132
- const how = event.via === void 0 ? "" : ` (${event.via})`;
133
- return [style("dim", `✉ to ${event.to}${how}: ${firstLine(event.body)}`)];
134
- }
135
- if (event.type === "message.received") {
136
- const how = event.via === void 0 ? "" : ` (${event.via})`;
137
- return [style("dim", `✉ from ${event.from}${how}: ${firstLine(event.body)}`)];
138
- }
139
- if (event.type === "call.started") return [style("dim", `↳ started ${event.workflow} as run ${event.run}`)];
140
- if (event.type === "link.notice") return [style("dim", `↔ ${event.text}`)];
141
- if (event.type === "message.read") return [];
142
- if (event.type === "owner.asked") return [`${style(["yellow", "bold"], "?")} asked as the owner of ${event.about}: ${event.text} ${style("dim", `(${event.question})`)}`];
143
- if (event.type === "owner.answered") return [style("dim", `→ ${event.answer} (owner)`)];
144
- return [style("dim", "→ its session ended first; asking you")];
145
- };
146
- // src/run/format-result.ts
147
- const RUN_MARK = {
148
- succeeded: style("green", "✓"),
149
- failed: style("red", "✗"),
150
- budget_exhausted: style("yellow", "!"),
151
- quota_exhausted: style("yellow", "!"),
152
- timed_out: style("yellow", "!"),
153
- interrupted: style("yellow", "!")
154
- };
155
- /** `1 failure handled`, `2 failures handled`. */
156
- const failuresHandled = (count) => `${String(count)} ${count === 1 ? "failure" : "failures"} handled`;
157
- /**
158
- * The run summary: status, cost, what ran, what was skipped and which failures
159
- * an edge handled, branches to look at, why, and mail to nodes that never ran.
160
- */
161
- const formatRunResult = (result) => {
162
- const ran = result.nodes.filter((n) => n.runs > 0).length;
163
- const skipped = result.skipped ?? result.nodes.length - ran;
164
- const handled = result.failuresHandled ?? 0;
165
- const lines = ["", `${RUN_MARK[result.status]} run ${result.runId} ${result.status.replace("_", " ")} ${style("dim", `· ${cost(result.costUsd)} · ${String(ran)} ran, ${String(skipped)} skipped${handled === 0 ? "" : `, ${failuresHandled(handled)}`}`)}`];
166
- for (const kept of result.worktrees) lines.push(` ${kept.id}: ${kept.branch ?? "detached"} ${style("dim", `at ${kept.path} (${kept.reason})`)}`);
167
- for (const reason of result.reasons) lines.push(` ${style("red", reason)}`);
168
- for (const { node, messages } of result.unread ?? []) lines.push(` ${style(["yellow", "bold"], "warning")} ${node} never ran, so ${messages === 1 ? "1 message" : `${String(messages)} messages`} to it went unread`);
169
- return lines;
170
- };
171
- // src/run/format.ts
172
- /** Who answered a decision, as the log says it. */
173
- const ANSWERED_BY = {
174
- terminal: "terminal",
175
- decide: "ork decide",
176
- ui: "ork ui",
177
- owner: "its owner"
178
- };
179
- const STATUS_MARK = {
180
- pass: style("green", "✓"),
181
- done: style("green", "✓"),
182
- changes_requested: style("yellow", "!"),
183
- fail: style("red", "✗")
184
- };
185
- const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
186
- const indent = (text) => text.trimEnd().split("\n").map((line) => ` ${line}`);
187
- /** The usage line: cost, tokens, turns and time. */
188
- const formatUsage = ({ usage }) => style("dim", [
189
- cost(usage.costUsd),
190
- `${String(usage.inputTokens)} in / ${String(usage.outputTokens)} out (${String(usage.cacheReadTokens)} cached)`,
191
- `${String(usage.turns)} turns`,
192
- seconds(usage.durationMs)
193
- ].join(" · "));
194
- /** How a finished session reads on the terminal. */
195
- const formatOutcome = (nodeId, outcome) => {
196
- const { report } = outcome;
197
- if (report === null) return [
198
- `${style("red", "✗")} ${style("bold", nodeId)} ended without a report (${outcome.subtype})`,
199
- ...outcome.errors.map((e) => ` ${style("red", e)}`),
200
- ` ${formatUsage(outcome)}`
201
- ];
202
- const lines = [`${STATUS_MARK[report.status] ?? "-"} ${style("bold", nodeId)} ${report.status}: ${report.summary}`];
203
- if (report.branch !== void 0) lines.push(` branch: ${report.branch}`);
204
- for (const [name, file] of Object.entries(report.artifacts)) lines.push(` ${name}: ${file}`);
205
- for (const [name, value] of Object.entries(report.fields)) lines.push(` ${name}: ${JSON.stringify(value)}`);
206
- lines.push(` ${formatUsage(outcome)}`);
207
- return lines;
208
- };
209
- /** The transcript lines for one event; tool names come from `tools`. */
210
- const formatEvent = (nodeId, event, tools) => {
211
- if (event.type === "session.started") {
212
- const fork = event.fork === true ? ", a fork to answer a question" : "";
213
- return [style("dim", `▸ ${nodeId} (${event.model}, session ${event.sessionId}${fork})`)];
214
- }
215
- if (event.type === "assistant.text") return indent(event.text);
216
- if (event.type === "tool.called") {
217
- tools.set(event.id, event.name);
218
- return [style("dim", ` · ${previewInput(event.name, event.input)}`)];
219
- }
220
- if (event.type === "tool.result") return event.isError ? [style("red", ` ✗ ${tools.get(event.id) ?? "tool"} failed`)] : [];
221
- if (event.type === "plan.limit") return event.status === "allowed" ? [] : [style("yellow", ` ! plan: ${event.window.replaceAll("_", " ")} limit ${event.status === "rejected" ? "reached" : "is close"}`)];
222
- return formatOutcome(nodeId, event.outcome);
223
- };
224
- /** A one-line verdict for a finished node, by what its type outputs. */
225
- const nodeVerdict = (output) => {
226
- const { status, summary, exitCode, choice, answeredBy, count } = output;
227
- if (typeof status === "string") return typeof summary === "string" && summary !== "" ? `${status}: ${summary}` : status;
228
- if (typeof exitCode === "number") return `exit ${String(exitCode)}`;
229
- if (typeof choice === "string") return `${choice} (${String(answeredBy)})`;
230
- if (typeof count === "number") return `${String(count)} ran`;
231
- return "done";
232
- };
233
- /** Transcript lines for one run event, prefixed with the node they are from. */
234
- const formatRunEvent = (event, width, tools) => {
235
- const tag = (id) => style("cyan", `${id.padEnd(width)} │`);
236
- if (event.type === "run.started") return [style("dim", `▸ run ${event.runId} of ${event.workflow} (starts at ${event.entries.join(", ")})`), ...event.settings?.trigger === void 0 ? [] : [style("dim", ` started by trigger ${event.settings.trigger}; untrusted inputs: ${(event.settings.untrusted ?? []).join(", ") || "none"}`)]];
237
- if (event.type === "node.started") {
238
- const round = event.round > 1 ? ` (round ${String(event.round)})` : "";
239
- const owner = event.owner === void 0 ? "" : `, for ${event.owner}`;
240
- return [`${tag(event.id)} ${style("dim", `${event.nodeType}${round}${owner}`)}`];
241
- }
242
- if (event.type === "node.event") {
243
- const inner = event.event;
244
- if (inner.type === "session.finished") return [];
245
- const fork = inner.fork === true ? style("dim", "fork · ") : "";
246
- if (inner.type === "session.started") {
247
- const why = inner.fork === true ? ", to answer a question" : "";
248
- return [`${tag(event.id)} ${fork}${style("dim", `${inner.model}, session ${inner.sessionId}${why}`)}`];
249
- }
250
- return formatEvent(event.id, event.event, tools).map((line) => `${tag(event.id)} ${fork}${line.replace(/^ {2}/u, "")}`);
251
- }
252
- if (event.type === "node.finished") {
253
- const mark = event.ok ? style("green", "✓") : style("red", "✗");
254
- const spent = event.usage === void 0 ? "" : style("dim", ` · ${cost(event.usage.costUsd)}`);
255
- return [`${tag(event.id)} ${mark} ${nodeVerdict(event.output)}${spent}`];
256
- }
257
- if (event.type === "node.skipped") return [`${tag(event.id)} ${style("dim", "- skipped")}`];
258
- if (event.type === "loop.exhausted") return [`${style(["yellow", "bold"], "!")} the loop ${event.from} -> ${event.to} used all ${String(event.maxRounds)} rounds`];
259
- if (event.type === "watch.started") return [`${tag(event.id)} ${style("dim", `watching until ${event.until}, every ${event.every}`)}`];
260
- if (event.type === "watch.changed") return [`${tag(event.id)} ${style("dim", `look ${String(event.looks)}:`)} ${event.said}`];
261
- if (event.type === "watch.ended") return [`${tag(event.id)} ${style("dim", `${event.outcome} after ${String(event.looks)} looks`)}`];
262
- if (event.type === "edge.taken") return [];
263
- if (event.type === "worktree") {
264
- const detail = event.detail === void 0 ? "" : ` (${event.detail})`;
265
- const line = `${event.action} ${event.branch ?? event.path}${detail}`;
266
- return [`${tag(event.id)} ${event.action === "conflict" ? style("yellow", `! ${line}`) : style("dim", `⎇ ${line}`)}`];
267
- }
268
- if (event.type === "decision.opened") {
269
- const what = event.kind === "permission" ? "asks to" : "asks:";
270
- const question = event.kind === "permission" ? event.question.replace(/^Allow /u, "run ").replace(/\?$/u, "") : event.question;
271
- const where = event.owner === void 0 ? `decision ${event.decision}; ork decide` : `decision ${event.decision}, via ${event.owner}`;
272
- return [`${tag(event.node)} ${style(["yellow", "bold"], "?")} ${what} ${question} ${style("dim", `(${where})`)}`];
273
- }
274
- if (event.type === "decision.answered") {
275
- const by = event.by === "owner" && event.owner !== void 0 ? event.owner : ANSWERED_BY[event.by];
276
- const said = event.note === void 0 ? "" : ` · said: ${event.note.replaceAll(/\s+/gu, " ")}`;
277
- return [`${tag(event.node)} ${style("dim", `→ ${event.answer} (${by})${said}`)}`];
278
- }
279
- if (event.type === "decision.withdrawn") return [`${tag(event.node)} ${style("dim", "→ question withdrawn")}`];
280
- if (event.type === "run.command") {
281
- const node = event.node === void 0 ? "" : ` --node ${event.node}`;
282
- return [style(["yellow", "bold"], `! ork ${event.command}${node} received`)];
283
- }
284
- if (event.type === "node.held") {
285
- const at = event.at === "tool" ? "its tool call waits for ork continue" : "it starts after ork continue";
286
- return [`${tag(event.id)} ${style("yellow", `‖ paused: ${at}`)}`];
287
- }
288
- if (event.type === "node.released") return [`${tag(event.id)} ${style("dim", "▸ continues")}`];
289
- if (event.type === "run.finished") return formatRunResult(event.result);
290
- if (event.type === "run.resumed") {
291
- const [one] = event.kept;
292
- const kept = one === void 0 ? "every node runs again" : event.kept.length === 1 ? `${one} keeps its result` : `${event.kept.join(", ")} keep their results`;
293
- return ["", style("dim", `▸ resumed: ${kept}`)];
294
- }
295
- if (event.type === "feedback.recorded") {
296
- const { target, kind, title } = event.feedback;
297
- const line = style("dim", `✎ feedback (${target}, ${kind}): ${title}`);
298
- return [event.node === void 0 ? line : `${tag(event.node)} ${line}`];
299
- }
300
- if (event.type === "user.message") {
301
- const note = event.delivered === "queued" ? " (for its next session)" : "";
302
- return [`${tag(event.node)} ${style(["magenta", "bold"], "you:")} ${firstLine(event.body)}${style("dim", note)}`];
303
- }
304
- return formatBusEvent(event).map((line) => `${tag(event.node)} ${line}`);
305
- };
306
- // src/runs/format.ts
307
- /**
308
- * @file How stored runs read on the terminal: the `ork ps` table and the `ork
309
- * tree` ownership tree with each node's state, tokens and cost, and what is
310
- * paused (ADR-041).
311
- */
312
- const STATUS_STYLE = {
313
- running: "cyan",
314
- paused: "yellow",
315
- succeeded: "green",
316
- failed: "red",
317
- crashed: "red",
318
- budget_exhausted: "yellow",
319
- quota_exhausted: "yellow",
320
- timed_out: "yellow",
321
- interrupted: "yellow"
322
- };
323
- /** `just now`, `5m ago`, `3h ago`, `2d ago`, then the date. */
324
- const ago = (iso, now = Date.now()) => {
325
- const minutes = Math.floor((now - Date.parse(iso)) / 6e4);
326
- if (minutes < 1) return "just now";
327
- if (minutes < 60) return `${String(minutes)}m ago`;
328
- if (minutes < 1440) return `${String(Math.floor(minutes / 60))}h ago`;
329
- if (minutes < 10080) return `${String(Math.floor(minutes / 1440))}d ago`;
330
- return iso.slice(0, 10);
331
- };
332
- /** `42s`, `3m12s`, `1h05m`. */
333
- const duration = (ms) => {
334
- const s = Math.max(0, Math.round(ms / 1e3));
335
- if (s < 60) return `${String(s)}s`;
336
- if (s < 3600) return `${String(Math.floor(s / 60))}m${String(s % 60).padStart(2, "0")}s`;
337
- return `${String(Math.floor(s / 3600))}h${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}m`;
338
- };
339
- /**
340
- * The `ork ps` table; `withProject` adds each run's project. A called run's
341
- * cost is dimmed and says its caller's, whose cost already counts it.
342
- */
343
- const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus) => {
344
- if (runs.length === 0) return ["No runs."];
345
- const rows = runs.map((run) => {
346
- const status = statusOf(run);
347
- const end = run.finishedAt === void 0 ? now : Date.parse(run.finishedAt);
348
- return {
349
- id: run.id,
350
- workflow: run.workflow,
351
- status,
352
- when: `${ago(run.startedAt, now)} · ${duration(end - Date.parse(run.startedAt))}`,
353
- cost: run.parent === void 0 ? cost(run.costUsd) : `${cost(run.costUsd)} in ${run.parent}`,
354
- called: run.parent !== void 0,
355
- project: run.project
356
- };
357
- });
358
- const width = (pick, title) => Math.max(title.length, ...rows.map((r) => pick(r).length));
359
- const w = {
360
- id: width((r) => r.id, "RUN"),
361
- workflow: width((r) => r.workflow, "WORKFLOW"),
362
- status: width((r) => r.status, "STATUS"),
363
- when: width((r) => r.when, "STARTED"),
364
- cost: width((r) => r.cost, "COST")
365
- };
366
- const header = [
367
- "RUN".padEnd(w.id),
368
- "WORKFLOW".padEnd(w.workflow),
369
- "STATUS".padEnd(w.status),
370
- "STARTED".padEnd(w.when),
371
- "COST".padStart(w.cost),
372
- ...withProject ? ["PROJECT"] : []
373
- ].join(" ");
374
- return [style("dim", header), ...rows.map((r) => [
375
- style("bold", r.id.padEnd(w.id)),
376
- r.workflow.padEnd(w.workflow),
377
- style(STATUS_STYLE[r.status] ?? "reset", r.status.padEnd(w.status)),
378
- style("dim", r.when.padEnd(w.when)),
379
- r.called ? style("dim", r.cost.padStart(w.cost)) : r.cost.padStart(w.cost),
380
- ...withProject ? [style("dim", r.project)] : []
381
- ].join(" "))];
382
- };
383
- const STATE_MARK = {
384
- waiting: style("dim", "·"),
385
- running: style("cyan", "●"),
386
- ok: style("green", "✓"),
387
- failed: style("red", "✗"),
388
- skipped: style("dim", "-")
389
- };
390
- /** `950`, `12.3k`, `1.2M`. */
391
- const count = (n) => {
392
- if (n < 1e3) return String(n);
393
- if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
394
- return `${(n / 1e6).toFixed(1)}M`;
395
- };
396
- const stateText = (node) => {
397
- if (node.state === "running") return node.runs > 1 ? `running (round ${String(node.runs)})` : "running";
398
- if (node.state === "ok" || node.state === "failed") {
399
- const verdict = nodeVerdict(node.output ?? {});
400
- const line = verdict.length > 60 ? `${verdict.slice(0, 59)}…` : verdict;
401
- return node.runs > 1 ? `${line} (${String(node.runs)} runs)` : line;
402
- }
403
- return node.state;
404
- };
405
- /** The ownership tree, one line per node, children under their owner. */
406
- const formatTree = (roots) => {
407
- const lines = [];
408
- const walk = (node, lead, branch) => {
409
- lines.push({
410
- label: `${lead}${branch}${node.id}`,
411
- node
412
- });
413
- const next = lead + (branch === "├─ " ? "│ " : branch === "" ? "" : " ");
414
- node.children.forEach((child, index) => {
415
- walk(child, next, index === node.children.length - 1 ? "└─ " : "├─ ");
416
- });
417
- };
418
- for (const root of roots) walk(root, "", "");
419
- const label = Math.max(...lines.map((l) => l.label.length));
420
- const type = Math.max(...lines.map((l) => l.node.type.length));
421
- return lines.map(({ label: text, node }) => {
422
- const usage = node.inputTokens + node.outputTokens === 0 ? "" : style("dim", ` ${count(node.inputTokens)} in / ${count(node.outputTokens)} out · ${cost(node.costUsd)}`);
423
- const state = node.held === true ? `${style("yellow", "‖")} paused` : `${STATE_MARK[node.state]} ${stateText(node)}`;
424
- return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${usage}`;
425
- });
426
- };
427
- export { formatRunEvent as a, formatEvent as i, formatRuns as n, createTerminalHuman as o, formatTree as r, ago as t };
@@ -1,22 +0,0 @@
1
- import { p as runMarkerIn } from "./src-DOcK8Jfp.mjs";
2
- // src/commands/in-a-run.ts
3
- /**
4
- * @file The commands that act as the user refuse a program or an agent in a run
5
- * (ADR-047). The refusal names the variable it saw, for someone at their own
6
- * terminal who inherited it. It stops accidents; it is not a boundary.
7
- */
8
- const refusal = (why, env) => {
9
- const marker = runMarkerIn(env);
10
- return marker === null ? null : `${why} ${marker} is set here. If this is your own terminal, it inherited that from a program in a run (a tmux server started by one, say); open a shell without it.`;
11
- };
12
- /** Why `ork watch --accept` refuses here, or `null` when it may run. */
13
- const acceptRefusal = (env = process.env) => refusal("`ork watch --accept` approves code to run with nobody there, so a program or an agent in a run cannot use it.", env);
14
- /** Why `ork chat` refuses here, or `null` when it may run. */
15
- const chatRefusal = (env = process.env) => refusal("`ork chat` speaks as you, so a program or an agent in a run cannot use it. A program writes to a session with `ork send`.", env);
16
- /** Why `ork decide` will not answer here, or `null` when it may. */
17
- const decideRefusal = (env = process.env) => refusal("`ork decide` answers as you, so a program or an agent in a run cannot answer with it; `ork decide --list` still shows what is open.", env);
18
- /** Why `ork continue` refuses here, or `null` when it may run. */
19
- const continueRefusal = (env = process.env) => refusal("`ork continue` undoes a pause you set, so a program or an agent in a run cannot use it.", env);
20
- /** Whether `ork ui` may print its sign-in link, which acts as the user. */
21
- const mayShowSignIn = (env = process.env) => runMarkerIn(env) === null;
22
- export { mayShowSignIn as a, decideRefusal as i, chatRefusal as n, continueRefusal as r, acceptRefusal as t };
@@ -1,110 +0,0 @@
1
- import { A as gitOk, j as lines, k as git } from "./src-Dujmvv4s.mjs";
2
- import { existsSync } from "node:fs";
3
- // ../core/src/worktree/list.ts
4
- /**
5
- * @file What `ork worktrees` shows: every worktree and branch Ork's runs
6
- * recorded creating, whatever the project's git rules named them, and any
7
- * `ork/*` branch besides, with their state read from git itself. What clean
8
- * and remove do with them is clean.ts.
9
- */
10
- /**
11
- * The worktrees the project's runs recorded creating, oldest run first; one
12
- * entry per branch or folder, from the latest run that created it.
13
- */
14
- const createdWorktrees = (store, project) => {
15
- const byName = /* @__PURE__ */ new Map();
16
- for (const run of store.runs({ project }).toReversed()) for (const { event } of store.events(run.id)) {
17
- if (event.type !== "worktree" || event.action !== "created") continue;
18
- byName.set(event.branch ?? event.path, {
19
- runId: run.id,
20
- nodeId: event.id,
21
- path: event.path,
22
- ...event.branch === void 0 ? {} : { branch: event.branch }
23
- });
24
- }
25
- return [...byName.values()];
26
- };
27
- const parsePorcelain = (text) => text.split("\n\n").map((block) => lines(block)).map((fields) => {
28
- const value = (key) => fields.find((f) => f.startsWith(`${key} `))?.slice(key.length + 1);
29
- return {
30
- path: value("worktree") ?? "",
31
- branch: (value("branch") ?? "").replace(/^refs\/heads\//u, ""),
32
- prunable: fields.some((f) => f.startsWith("prunable"))
33
- };
34
- }).filter((entry) => entry.path !== "");
35
- /**
36
- * The `ork/<run>/<node>` branches of runs that recorded nothing; with no path
37
- * recorded, the worktree is whichever has the branch checked out.
38
- */
39
- const prefixed = (branch) => {
40
- const [, runId = "", ...rest] = branch.split("/");
41
- return {
42
- runId,
43
- nodeId: rest.join("/"),
44
- path: "",
45
- branch
46
- };
47
- };
48
- /** How many commits a detached worktree holds that no branch, tag or remote has. */
49
- const onNoBranch = async (entry) => entry === void 0 ? "0" : (await git(entry.path, [
50
- "rev-list",
51
- "--count",
52
- "HEAD",
53
- "--not",
54
- "--branches",
55
- "--tags",
56
- "--remotes"
57
- ])).stdout;
58
- /** One created worktree or branch, with its state read from git. */
59
- const stateOf = async (root, entries, item) => {
60
- const { branch } = item;
61
- const entry = branch === void 0 || item.path !== "" ? entries.find((e) => e.path === item.path) : entries.find((e) => e.branch === branch);
62
- const holder = branch === void 0 ? void 0 : entries.find((e) => e.branch === branch && e.path !== entry?.path);
63
- const missing = entry !== void 0 && (entry.prunable || !existsSync(entry.path));
64
- const status = entry === void 0 || missing ? "" : (await git(entry.path, ["status", "--porcelain"])).stdout;
65
- const checkedOut = branch === void 0 && entry !== void 0 && entry.branch !== "" ? entry.branch : void 0;
66
- const on = branch ?? checkedOut;
67
- const counted = on === void 0 ? await onNoBranch(entry === void 0 || missing ? void 0 : entry) : (await git(root, [
68
- "rev-list",
69
- "--count",
70
- `HEAD..${on}`
71
- ])).stdout;
72
- const ahead = Number(counted);
73
- const changes = lines(status).length;
74
- return {
75
- ...branch === void 0 ? {} : { branch },
76
- ...checkedOut === void 0 ? {} : { checkedOut },
77
- runId: item.runId,
78
- nodeId: item.nodeId,
79
- path: entry?.path ?? null,
80
- missing,
81
- dirty: changes > 0,
82
- changes,
83
- ahead: Number.isNaN(ahead) ? 0 : ahead,
84
- merged: on !== void 0 && ahead === 0,
85
- inMainCheckout: entry?.path === root || holder?.path === root,
86
- ...holder === void 0 || holder.path === root ? {} : { heldBy: holder.path }
87
- };
88
- };
89
- /**
90
- * Every worktree and branch Ork's runs created in the repository at `root`,
91
- * with its state: those the runs recorded (`created`, whatever the project's
92
- * git rules named them), and any `ork/` branch besides. A recorded one that git
93
- * no longer has is left out.
94
- */
95
- const listOrkWorktrees = async (root, created = []) => {
96
- const entries = parsePorcelain(await gitOk(root, [
97
- "worktree",
98
- "list",
99
- "--porcelain"
100
- ]));
101
- const branches = lines(await gitOk(root, [
102
- "for-each-ref",
103
- "--format=%(refname:short)",
104
- "refs/heads/"
105
- ]));
106
- const recordedBranches = new Set(created.flatMap((c) => c.branch ?? []));
107
- const items = [...created.filter((c) => c.branch === void 0 ? entries.some((e) => e.path === c.path) : branches.includes(c.branch)), ...branches.filter((b) => b.startsWith("ork/") && !recordedBranches.has(b)).map((b) => prefixed(b))];
108
- return Promise.all(items.map((item) => stateOf(root, entries, item)));
109
- };
110
- export { listOrkWorktrees as n, createdWorktrees as t };