@krapht/ork 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (51) hide show
  1. package/dist/__build_info__-Bsb1kjgF.mjs +340 -0
  2. package/dist/{store-CM3cERGl.mjs → attach-BHO3X3cf.mjs} +36 -91
  3. package/dist/{attach-E6NNyA3T.mjs → attach-CPespxlQ.mjs} +6 -5
  4. package/dist/bin.mjs +148 -1444
  5. package/dist/brief-za0ozN_E.mjs +58 -0
  6. package/dist/{builtin-files-n6o6oWpB.mjs → builtin-files-2bHf76x-.mjs} +1 -1
  7. package/dist/{chips-CyeB_rhR.mjs → chips-BqcT58qr.mjs} +1 -1
  8. package/dist/{completion-SNNNxnv9.mjs → completion-CQ2UYKuJ.mjs} +52 -11
  9. package/dist/context-BNqDw9z1.mjs +106 -0
  10. package/dist/{find-CjsNYAAf.mjs → find-BYLlszqj.mjs} +1 -1
  11. package/dist/follow-ups-B4GJ3Vzw.mjs +461 -0
  12. package/dist/{format-C60TFpRe.mjs → format-C8MsFj8_.mjs} +30 -16
  13. package/dist/{help-groups-CyfPWlST.mjs → help-groups-CQ98mLjF.mjs} +20 -6
  14. package/dist/{in-a-run-BYjTXaST.mjs → in-a-run-Bp0k0J09.mjs} +1 -1
  15. package/dist/{__build_info__-Da73_nBs.mjs → install-DWtXTq4y.mjs} +3 -227
  16. package/dist/{limits-C5QKZNLT.mjs → limits-B1qzS4cX.mjs} +1 -1
  17. package/dist/{mcp-bcctR-wc.mjs → mcp-BOrVkyhN.mjs} +6 -6
  18. package/dist/{once-DPQu5Q3H.mjs → once-C1fL3GVo.mjs} +4 -4
  19. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  20. package/dist/{output-CnRzrbUM.mjs → output-B1kwo0YU.mjs} +17 -1
  21. package/dist/{pause-BPvlDIvz.mjs → pause-DOMK-R8V.mjs} +4 -4
  22. package/dist/post-BzA6Qobq.mjs +44 -0
  23. package/dist/{relay-C8W0t_zc.mjs → relay-OWuu4fib.mjs} +2 -2
  24. package/dist/run--4-1xAF2.mjs +1451 -0
  25. package/dist/{run-CQIfq918.mjs → run-CiPsW9Kf.mjs} +1 -1
  26. package/dist/send-CnNNW-ot.mjs +105 -0
  27. package/dist/{src-CqRqsb2t.mjs → src-B9zWIVqo.mjs} +226 -44
  28. package/dist/{src-CGJGL1sZ.mjs → src-DQKz2eOH.mjs} +326 -81
  29. package/dist/{src-D93tHZ5B.mjs → src-DcrhgipG.mjs} +107 -47
  30. package/dist/{src-BeR6f70A.mjs → src-Dr_fTSu-.mjs} +5858 -3746
  31. package/dist/store-oKJ-cCf3.mjs +16 -0
  32. package/dist/ui/assets/app-Bdc6OBeZ.css +1 -0
  33. package/dist/ui/assets/app-Drp8Dm9y.js +111 -0
  34. package/dist/ui/assets/{index-CWwm8HTG.js → index-BtwkftKe.js} +2 -2
  35. package/dist/ui/index.html +1 -1
  36. package/dist/{ui-D2Ydofq0.mjs → ui-CzKlsCnm.mjs} +4 -4
  37. package/dist/{usage-6WIowblz.mjs → usage-DHGNk2JL.mjs} +13 -141
  38. package/dist/{context-DHudLA5e.mjs → validate-Bs_gg22g.mjs} +17 -107
  39. package/dist/{verdict-CIy8tAyX.mjs → verdict-CMvPCpWC.mjs} +2 -2
  40. package/dist/{watch-uOU_WeBI.mjs → watch-CCCfw3n7.mjs} +24 -8
  41. package/dist/{workflows-7tQdMnrA.mjs → workflows-CW28ZU-z.mjs} +3 -3
  42. package/dist/{worktrees-ZCII298F.mjs → worktrees-CyE8aF6j.mjs} +3 -5
  43. package/package.json +2 -2
  44. package/dist/clean-Bh4KZgzc.mjs +0 -91
  45. package/dist/decide-DuIHACvH.mjs +0 -50
  46. package/dist/list-BUb5Uc3H.mjs +0 -117
  47. package/dist/remove-BkFWGVYo.mjs +0 -201
  48. package/dist/report-CEN8VoUb.mjs +0 -140
  49. package/dist/send-CaZlGG5a.mjs +0 -163
  50. package/dist/ui/assets/app-DYRC0V4p.js +0 -111
  51. package/dist/ui/assets/app-QoHVi-mb.css +0 -1
@@ -0,0 +1,340 @@
1
+ import { A as isNewer, j as parseVersion, k as compareVersions } from "./src-Dr_fTSu-.mjs";
2
+ import path from "node:path";
3
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ // ../core/src/follow-ups/change.ts
6
+ /** The project's follow-up whose id is, or starts with, `prefix`. */
7
+ const findFollowUp = (rows, project, prefix) => {
8
+ const exact = rows.followUp(project, prefix);
9
+ if (exact !== void 0) return {
10
+ ok: true,
11
+ followUp: exact
12
+ };
13
+ const matches = prefix === "" ? [] : rows.followUps(project).filter((f) => f.id.startsWith(prefix));
14
+ const [only, other] = matches;
15
+ if (only === void 0) return {
16
+ ok: false,
17
+ error: `No follow-up \`${prefix}\` in this project.`
18
+ };
19
+ if (other !== void 0) return {
20
+ ok: false,
21
+ error: `\`${prefix}\` matches ${matches.map((f) => f.id).join(", ")}; give more of the id.`
22
+ };
23
+ return {
24
+ ok: true,
25
+ followUp: only
26
+ };
27
+ };
28
+ /** Which statuses each action starts from, and where it goes. */
29
+ const ACTIONS = {
30
+ done: {
31
+ from: ["waiting", "due"],
32
+ to: () => "done",
33
+ note: "done"
34
+ },
35
+ drop: {
36
+ from: ["waiting", "due"],
37
+ to: () => "dropped",
38
+ note: "dropped"
39
+ },
40
+ reopen: {
41
+ from: ["done", "dropped"],
42
+ to: (f) => f.due === "now" ? "due" : "waiting",
43
+ note: "reopened"
44
+ },
45
+ due: {
46
+ from: ["waiting"],
47
+ to: () => "due",
48
+ note: "marked due"
49
+ }
50
+ };
51
+ /** Why `action` can't be done to `f` now, or `undefined`. */
52
+ const refusal = (f, action) => {
53
+ const rule = ACTIONS[action];
54
+ return rule.from.includes(f.status) ? void 0 : `${f.id} is ${f.status}, so it can't be ${rule.note}.`;
55
+ };
56
+ const check = (f, action) => {
57
+ const error = refusal(f, action);
58
+ return error === void 0 ? { ok: true } : {
59
+ ok: false,
60
+ why: `it is ${f.status}`,
61
+ error
62
+ };
63
+ };
64
+ const postCheck = (f) => {
65
+ const open = f.status === "waiting" || f.status === "due";
66
+ if (f.kind !== "message" || f.draft?.channel !== "github") return {
67
+ ok: false,
68
+ why: "not drafted for the github channel",
69
+ error: "only a message drafted for the github channel is posted; send the others yourself"
70
+ };
71
+ return open ? { ok: true } : {
72
+ ok: false,
73
+ why: `it is ${f.status}`,
74
+ error: `${f.id} is ${f.status}, so it is not posted.`
75
+ };
76
+ };
77
+ const followUpActions = (f) => ({
78
+ done: check(f, "done"),
79
+ drop: check(f, "drop"),
80
+ reopen: check(f, "reopen"),
81
+ due: check(f, "due"),
82
+ post: postCheck(f)
83
+ });
84
+ /** Applies `action` to `followUp`, and keeps it. */
85
+ const changeFollowUp = (rows, followUp, action, now = () => (/* @__PURE__ */ new Date()).toISOString()) => {
86
+ const rule = ACTIONS[action];
87
+ const refused = refusal(followUp, action);
88
+ if (refused !== void 0) return {
89
+ ok: false,
90
+ error: refused
91
+ };
92
+ const { note: _old, ...rest } = followUp;
93
+ const changed = {
94
+ ...rest,
95
+ status: rule.to(followUp),
96
+ note: rule.note,
97
+ updatedAt: now()
98
+ };
99
+ rows.putFollowUp(changed);
100
+ return {
101
+ ok: true,
102
+ followUp: changed
103
+ };
104
+ };
105
+ /**
106
+ * Why a due follow-up is due, read from the note `due.ts` left: its ref merged,
107
+ * its date came, its ref closed unmerged, or a person marked it. `undefined`
108
+ * for one that is not due, or was due from the start.
109
+ */
110
+ const dueCause = (f) => {
111
+ if (f.status !== "due" || f.note === void 0) return void 0;
112
+ if (f.note.includes("closed without merging")) return "closed-unmerged";
113
+ if (f.note.endsWith(" merged")) return "merged";
114
+ if (f.note.startsWith("due on ")) return "overdue";
115
+ return f.note === "marked due" ? "marked" : void 0;
116
+ };
117
+ // ../core/src/remote/session.ts
118
+ const chattable = (type) => type === "agent.leader" || type === "agent.worker";
119
+ /** Reads `node`'s session from a run's events, and every node's last output. */
120
+ const nodeSession = (events, node) => {
121
+ const session = {
122
+ known: false,
123
+ chattable: false,
124
+ running: false
125
+ };
126
+ const outputs = {};
127
+ for (const { event } of events) {
128
+ if (event.type === "run.started") {
129
+ const declared = event.nodes.find((n) => n.id === node);
130
+ if (declared !== void 0) {
131
+ session.known = true;
132
+ session.chattable = chattable(declared.type);
133
+ }
134
+ }
135
+ if (event.type === "node.finished") outputs[event.id] = event.output;
136
+ if (!("id" in event) || event.id !== node) continue;
137
+ if (event.type === "node.started") {
138
+ session.known = true;
139
+ session.chattable = chattable(event.nodeType);
140
+ session.running = true;
141
+ }
142
+ if (event.type === "node.finished") session.running = false;
143
+ if (event.type === "worktree" && event.action === "created") session.checkout = {
144
+ path: event.path,
145
+ ...event.branch === void 0 ? {} : { branch: event.branch },
146
+ own: true,
147
+ owner: node
148
+ };
149
+ if (event.type !== "node.event") continue;
150
+ const inner = event.event;
151
+ if (inner.fork === true) continue;
152
+ if (inner.type === "session.started") {
153
+ session.sessionId = inner.sessionId;
154
+ if (inner.cwd !== void 0) session.cwd = inner.cwd;
155
+ if (inner.driver !== void 0) session.driver = inner.driver;
156
+ if (inner.model !== "") session.model = inner.model;
157
+ }
158
+ if (inner.type === "session.finished" && inner.outcome.sessionId !== "") session.sessionId = inner.outcome.sessionId;
159
+ }
160
+ return {
161
+ session,
162
+ outputs
163
+ };
164
+ };
165
+ /** The dist-tags every install follows, toward and after 1.0.0. */
166
+ const CHANNEL_TAGS = [
167
+ "latest",
168
+ "alpha",
169
+ "beta",
170
+ "rc"
171
+ ];
172
+ /** The dist-tag of snapshots from `main`, followed only by a canary. */
173
+ const CANARY_TAG = "canary";
174
+ const updateCachePath = (home = homedir()) => path.join(home, ".ork", "update-check.json");
175
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
176
+ const text = (record, key) => {
177
+ const value = record[key];
178
+ return typeof value === "string" ? value : void 0;
179
+ };
180
+ const notifiedOf = (value) => {
181
+ if (!isRecord(value)) return void 0;
182
+ const version = text(value, "version");
183
+ const at = text(value, "at");
184
+ return version === void 0 || at === void 0 ? void 0 : {
185
+ version,
186
+ at
187
+ };
188
+ };
189
+ /** The cache, or `null` when there is none or it cannot be read. */
190
+ const readUpdateCache = (file) => {
191
+ let parsed;
192
+ try {
193
+ parsed = JSON.parse(readFileSync(file, "utf8"));
194
+ } catch {
195
+ return null;
196
+ }
197
+ if (!isRecord(parsed)) return null;
198
+ const checkedAt = text(parsed, "checkedAt");
199
+ if (checkedAt === void 0) return null;
200
+ const latest = text(parsed, "latest");
201
+ const notified = notifiedOf(parsed["notified"]);
202
+ return {
203
+ checkedAt,
204
+ ...latest === void 0 ? {} : { latest },
205
+ ...notified === void 0 ? {} : { notified }
206
+ };
207
+ };
208
+ /** Writes the cache whole (a temporary file, then a rename). */
209
+ const writeUpdateCache = (file, cache) => {
210
+ mkdirSync(path.dirname(file), { recursive: true });
211
+ const temporary = `${file}.${process.pid}.tmp`;
212
+ writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}\n`);
213
+ renameSync(temporary, file);
214
+ };
215
+ const age = (iso, now) => {
216
+ const time = Date.parse(iso);
217
+ return Number.isNaN(time) ? Infinity : now.getTime() - time;
218
+ };
219
+ /** Whether it is time to ask the registry again. */
220
+ const checkDue = (cache, now) => cache === null || age(cache.checkedAt, now) >= 864e5;
221
+ /** Whether `version` is a canary snapshot: its pre-release starts `canary`. */
222
+ const isCanary = (version) => parseVersion(version)?.pre[0]?.startsWith(CANARY_TAG) === true;
223
+ /**
224
+ * The newest version, by semver precedence, among the dist-tags `current`
225
+ * follows: the channel tags, and `canary` too when `current` is a canary. A tag
226
+ * whose value doesn't parse is skipped; `null` when none does.
227
+ */
228
+ const newestTagged = (tags, current) => {
229
+ return (current !== void 0 && isCanary(current) ? [...CHANNEL_TAGS, "canary"] : CHANNEL_TAGS).flatMap((tag) => {
230
+ const value = tags[tag];
231
+ const version = typeof value === "string" ? parseVersion(value) : null;
232
+ return typeof value === "string" && version !== null ? [{
233
+ value,
234
+ version
235
+ }] : [];
236
+ }).reduce((best, candidate) => best === null || compareVersions(candidate.version, best.version) > 0 ? candidate : best, null)?.value ?? null;
237
+ };
238
+ /**
239
+ * The newest version on the registry that the running version follows ({@link
240
+ * newestTagged}), or `null` on any failure.
241
+ */
242
+ const fetchLatest = async (name, options = {}) => {
243
+ const registry = (options.registry ?? "https://registry.npmjs.org").replace(/\/+$/u, "");
244
+ const get = options.fetch ?? globalThis.fetch;
245
+ try {
246
+ const response = await get(`${registry}/-/package/${name}/dist-tags`, {
247
+ headers: { accept: "application/json" },
248
+ signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
249
+ });
250
+ if (!response.ok) return null;
251
+ const tags = await response.json();
252
+ return isRecord(tags) ? newestTagged(tags, options.current) : null;
253
+ } catch {
254
+ return null;
255
+ }
256
+ };
257
+ /**
258
+ * Asks the registry and records the answer. A failed check is recorded too, so
259
+ * an offline machine is not asked again until the interval passes; the last
260
+ * known version is kept. `options.current` is the running version.
261
+ */
262
+ const refreshUpdateCache = async (file, name, now, options = {}) => {
263
+ const before = readUpdateCache(file);
264
+ const latest = await fetchLatest(name, options) ?? before?.latest;
265
+ const cache = {
266
+ ...before,
267
+ checkedAt: now.toISOString(),
268
+ ...latest === void 0 ? {} : { latest }
269
+ };
270
+ writeUpdateCache(file, cache);
271
+ return cache;
272
+ };
273
+ /** The newer version the cache knows of, if any. */
274
+ const newerVersion = (cache, current) => cache?.latest !== void 0 && isNewer(cache.latest, current) ? cache.latest : null;
275
+ /**
276
+ * The version to tell the user about now: newer than theirs, and not already
277
+ * told today. `null` when there is nothing to say.
278
+ */
279
+ const pendingNotice = (cache, current, now) => {
280
+ const newer = newerVersion(cache, current);
281
+ if (newer === null) return null;
282
+ const told = cache?.notified;
283
+ return told?.version === newer && age(told.at, now) < 864e5 ? null : newer;
284
+ };
285
+ // ../core/src/update/switches.ts
286
+ /**
287
+ * @file The environment's say over the update check (ADR-038): the variables
288
+ * that turn it off (`ORK_NO_UPDATE_CHECK`, the common `NO_UPDATE_NOTIFIER`,
289
+ * and `CI`), and the registry to ask instead of npm's. The CLI adds its own
290
+ * rules about where a notice may go; the server behind `ork ui` uses only
291
+ * these.
292
+ */
293
+ /** Turns the automatic check and its notice off. */
294
+ const NO_UPDATE_ENV = "ORK_NO_UPDATE_CHECK";
295
+ /** A registry to ask instead of npm's, e.g. a company mirror. */
296
+ const UPDATE_REGISTRY_ENV = "ORK_UPDATE_REGISTRY";
297
+ /** The variables that turn the check off, in the order they are looked at. */
298
+ const UPDATE_SWITCHES = [
299
+ NO_UPDATE_ENV,
300
+ "NO_UPDATE_NOTIFIER",
301
+ "CI"
302
+ ];
303
+ /** Set to anything but empty, `0` or `false`. */
304
+ const isSwitchedOn = (value) => value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
305
+ /** The variable that turns the check off in `env`, or `null` when none does. */
306
+ const updateSwitchOff = (env) => UPDATE_SWITCHES.find((name) => isSwitchedOn(env[name])) ?? null;
307
+ /** The registry `ORK_UPDATE_REGISTRY` names, if it names one. */
308
+ const registryOf = (env) => {
309
+ const registry = env[UPDATE_REGISTRY_ENV];
310
+ return registry === void 0 || registry === "" ? void 0 : registry;
311
+ };
312
+ /** The host of a registry that isn't an http(s) URL, past any `user:pass@`. */
313
+ const hostOf = (registry) => registry.replace(/^[a-z][\w+.-]*:\/\//iu, "").replace(/^[^/?#]*@/u, "").split(/[/?#]/u)[0] ?? "";
314
+ /**
315
+ * The registry as it may be shown (on a page, in a message): an http(s) URL
316
+ * without its user name and password, which a mirror's URL may carry; anything
317
+ * else only as its host.
318
+ */
319
+ const registryShown = (registry) => {
320
+ try {
321
+ const url = new URL(registry);
322
+ if (url.protocol === "http:" || url.protocol === "https:") {
323
+ if (url.username === "" && url.password === "") return registry;
324
+ url.username = "";
325
+ url.password = "";
326
+ return url.href;
327
+ }
328
+ } catch {}
329
+ return hostOf(registry);
330
+ };
331
+ // src/__build_info__.ts
332
+ /**
333
+ * @file The published package's name and version, bound to
334
+ * `packages/cli/package.json`. Do not edit by hand: `pnpm changesets:version`
335
+ * and `pnpm build` rewrite it (`tools/release/build-info.ts`), and a test
336
+ * fails when it is stale.
337
+ */
338
+ const ORK_NAME = "@krapht/ork";
339
+ const ORK_VERSION = "1.0.0-alpha.15";
340
+ export { dueCause as _, registryShown as a, fetchLatest as c, readUpdateCache as d, refreshUpdateCache as f, changeFollowUp as g, nodeSession as h, registryOf as i, newerVersion as l, writeUpdateCache as m, ORK_VERSION as n, updateSwitchOff as o, updateCachePath as p, isSwitchedOn as r, checkDue as s, ORK_NAME as t, pendingNotice as u, findFollowUp as v, followUpActions as y };
@@ -1,8 +1,7 @@
1
- import { $ as followEvents, C as isNodeEvent, Ft as slotWaitLine, On as maskSecrets, _t as liveStatus, in as END_KINDS, nn as outcomeEnd, tn as endOf, w as openStore } from "./src-BeR6f70A.mjs";
2
- import { n as pendingDecisions, t as answerDecision } from "./decide-DuIHACvH.mjs";
3
- import { E as readKeys, S as cut, T as wrap, _ as withdrawn, a as style, c as confirmOrStop, d as apply, f as bold, g as readLine, h as interrupt, i as plain, m as createRegion, n as cost, p as paint, v as canKey, w as widthOf, x as termOf, y as plainEnv } from "./output-CnRzrbUM.mjs";
4
- import { n as chip, r as nodeChip, t as CHIP_LABELS } from "./chips-CyeB_rhR.mjs";
5
- import { a as createView, i as formatBusEvent, n as nodeVerdict, o as styler, r as firstLine, t as hasConflict } from "./verdict-CIy8tAyX.mjs";
1
+ import { Bn as outcomeEnd, H as isNodeEvent, Hn as END_KINDS, Lt as lockWaitLine, Ut as slotWaitLine, _t as followEvents, ct as followRunTree, fr as maskSecrets, lt as answerDecision, ut as pendingDecisions, zn as endOf } from "./src-Dr_fTSu-.mjs";
2
+ import { C as termOf, D as wrap, E as widthOf, O as readKeys, _ as interrupt, a as plain, b as canKey, g as createRegion, h as paint, m as bold, n as cost, p as apply, r as costOf, s as style, u as confirmOrStop, v as readLine, w as cut, x as plainEnv, y as withdrawn } from "./output-B1kwo0YU.mjs";
3
+ import { n as chip, r as nodeChip, t as CHIP_LABELS } from "./chips-BqcT58qr.mjs";
4
+ import { a as createView, i as formatBusEvent, n as nodeVerdict, o as styler, r as firstLine, t as hasConflict } from "./verdict-CMvPCpWC.mjs";
6
5
  import { createInterface } from "node:readline/promises";
7
6
  // ../core/src/control/queue.ts
8
7
  const PERMISSION_OPTIONS = [{
@@ -219,66 +218,6 @@ const createDecisionQueue = (options) => {
219
218
  }
220
219
  };
221
220
  };
222
- // ../core/src/remote/follow-tree.ts
223
- /**
224
- * Calls `onEvent` for each event of `root` and of the runs it called, once
225
- * each. A called run's stored events come right after the `call.started` that
226
- * started it; live events as they arrive. Ends when the root ends or `signal`
227
- * aborts.
228
- */
229
- const followRunTree = async (store, root, onEvent, options = {}) => {
230
- /** Every run followed, with its call path, in the order found. */
231
- const paths = /* @__PURE__ */ new Map([[root, []]]);
232
- /** Each run's last event seen, for the catch-up once the root ends. */
233
- const seqs = /* @__PURE__ */ new Map();
234
- const followers = [];
235
- /** Ends the called runs' following once the root's has ended. */
236
- const halt = new AbortController();
237
- const signal = options.signal === void 0 ? halt.signal : AbortSignal.any([options.signal, halt.signal]);
238
- const handle = (run, path) => (stored) => {
239
- seqs.set(run, stored.seq);
240
- onEvent({
241
- run,
242
- path,
243
- stored
244
- });
245
- const { event } = stored;
246
- if (event.type === "call.started" && !paths.has(event.run)) follow(event.run, [...path, event.node]);
247
- };
248
- const follow = (run, path) => {
249
- paths.set(run, path);
250
- followers.push(followEvents(store, run, handle(run, path), {
251
- ...options,
252
- follow: true,
253
- after: seqs.get(run) ?? 0,
254
- signal
255
- }));
256
- };
257
- /** Waits for every follower, including those started while waiting. */
258
- const settle = async () => {
259
- while (followers.length > 0) await Promise.all(followers.splice(0));
260
- };
261
- const end = await followEvents(store, root, handle(root, []), {
262
- ...options,
263
- follow: true
264
- });
265
- halt.abort();
266
- await settle();
267
- if (end !== "aborted") {
268
- for (const [run, path] of [...paths].slice(1)) followers.push(followEvents(store, run, handle(run, path), {
269
- follow: false,
270
- after: seqs.get(run) ?? 0
271
- }));
272
- await settle();
273
- }
274
- return {
275
- end,
276
- stillRunning: [...paths.keys()].slice(1).filter((id) => {
277
- const [run] = store.lookup(id);
278
- return run !== void 0 && liveStatus(run) === "running";
279
- })
280
- };
281
- };
282
221
  // src/human-card.ts
283
222
  /** Lines of context a card shows before `d` (or `--verbose`) shows the rest. */
284
223
  const CONTEXT_LINES = 6;
@@ -830,8 +769,9 @@ const createTerminalHuman = (io) => {
830
769
  /**
831
770
  * @file How a node's end reads on the terminal (specs/runtime/session-ends.md):
832
771
  * its kind's label and Ork's own message, a note for a kind that may pass,
833
- * the setup hint for an account or provider end, and the scrubbed raw detail
834
- * only when asked for. Never the provider's own words first.
772
+ * the setup hint for an account or provider end, the last line of a failed
773
+ * tool's own stderr, and the scrubbed raw detail only when asked for. Never
774
+ * the provider's own words first.
835
775
  */
836
776
  /** What a transient kind says in place of its hint on the log line. */
837
777
  const MAY_PASS = "(may pass; ork resume retries)";
@@ -840,11 +780,17 @@ const endHeadline = (end) => {
840
780
  const { label, transient } = END_KINDS[end.kind];
841
781
  return `${label}: ${end.message}${transient ? ` ${MAY_PASS}` : ""}`;
842
782
  };
783
+ /** The longest a script's last stderr line is shown on the log line. */
784
+ const LAST_LINE_LIMIT = 200;
785
+ /** The last non-empty line of a scrubbed detail, cut; "" when there is none. */
786
+ const lastLine = (detail) => (detail ?? "").split("\n").map((line) => line.trim()).findLast((line) => line !== "")?.slice(0, LAST_LINE_LIMIT) ?? "";
843
787
  /**
844
788
  * The lines under a headline: the hint for an account or provider end (what to
845
789
  * set up) or whenever `full` (`--verbose`, one node's log), and the detail only
846
790
  * when `full`. A transient kind's hint is its retry wording, which the headline
847
- * already says, so it is not printed twice.
791
+ * already says, so it is not printed twice. A failed tool (`command_failed`)
792
+ * also shows the last line of its detail without `full`: a script's stderr is
793
+ * the workflow's own message, not the provider's.
848
794
  */
849
795
  const endLines = (end, full, term = createView().term) => {
850
796
  const style = styler(term);
@@ -853,6 +799,10 @@ const endLines = (end, full, term = createView().term) => {
853
799
  const lines = [];
854
800
  if (hint !== "" && !transient && (full || setup)) lines.push(` ${style("dim", hint)}`);
855
801
  if (full && end.detail !== void 0) lines.push(style("dim", " details:"), ...end.detail.split("\n").map((line) => style("dim", ` ${line}`)));
802
+ else if (end.kind === "command_failed") {
803
+ const last = lastLine(end.detail);
804
+ if (last !== "") lines.push(` ${style("dim", last)}`);
805
+ }
856
806
  return lines;
857
807
  };
858
808
  /**
@@ -869,6 +819,21 @@ const formatFinished = (event, tag, verbose, term = createView().term, rest = ta
869
819
  if (!(!event.ok && end.kind !== "reported_failure" && (event.end !== void 0 || end.kind !== "unknown"))) return [`${tag} ${mark} ${nodeVerdict(event.output, event.ok, event.error)}${spent}`];
870
820
  return [`${tag} ${mark} ${endHeadline(end)}${spent}`, ...endLines(end, verbose, term).map((line) => `${rest} ${line}`)];
871
821
  };
822
+ // src/run/format-machine.ts
823
+ /**
824
+ * @file How a node's waits for the machine read in `ork logs`: a slot for a
825
+ * heavy command (ADR-080) and a lock (RFC-016), each naming who holds it.
826
+ */
827
+ /** Whether `event` is a slot's or a lock's. */
828
+ const isMachineEvent = (event) => event.type.startsWith("slot.") || event.type.startsWith("lock.");
829
+ /** A slot or lock event's line, after its node's tag. */
830
+ const machineLine = (event, style) => {
831
+ if (event.type === "slot.waiting") return style("yellow", `‖ ${slotWaitLine(event.holders)}`);
832
+ if (event.type === "slot.taken") return style("dim", "▸ got a check slot");
833
+ if (event.type === "lock.waiting") return style("yellow", `‖ ${lockWaitLine(event.lock, event.holders)}`);
834
+ if (event.type === "lock.taken") return style("dim", `▸ holds ${event.lock}`);
835
+ return style("dim", `▸ let ${event.lock} go`);
836
+ };
872
837
  // src/run/format-outcome.ts
873
838
  /**
874
839
  * @file How a finished session reads on the terminal: its report or its end,
@@ -972,7 +937,7 @@ const formatRunResult = (result, term = createView().term) => {
972
937
  const ran = result.nodes.filter((n) => n.runs > 0).length;
973
938
  const skipped = result.skipped ?? result.nodes.length - ran;
974
939
  const handled = result.failuresHandled ?? 0;
975
- const lines = ["", `${mark} run ${result.runId} ${result.status.replace("_", " ")} ${style("dim", `· ${cost(result.costUsd)} · ${String(ran)} ran, ${String(skipped)} skipped${handled === 0 ? "" : `, ${failuresHandled(handled)}`}`)}`];
940
+ const lines = ["", `${mark} run ${result.runId} ${result.status.replace("_", " ")} ${style("dim", `· ${costOf(result.costUsd, result.partial, result.tokens)} · ${String(ran)} ran, ${String(skipped)} skipped${handled === 0 ? "" : `, ${failuresHandled(handled)}`}`)}`];
976
941
  for (const kept of result.worktrees) lines.push(` ${kept.id}: ${kept.branch ?? "detached"} ${style("dim", `at ${kept.path} (${kept.reason})`)}`);
977
942
  for (const reason of result.reasons) lines.push(` ${style("red", reason)}`);
978
943
  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`);
@@ -1025,11 +990,6 @@ const toolLines = (name, input, view, room, verbose, node) => {
1025
990
  return [`${styledHead}${style("dim", cut(first.trim(), Math.max(1, room - headWidth - more.length)))}${style("dim", more)}`];
1026
991
  };
1027
992
  // src/run/format.ts
1028
- /**
1029
- * @file How runs read on the terminal: session events and run events with node
1030
- * chips or prefixes; the node result is `format-outcome.ts`'s, a tool call
1031
- * `format-tool.ts`'s and the run summary `format-result.ts`'s.
1032
- */
1033
993
  /** A line's 2-column indent, after any style codes that open it. */
1034
994
  const INDENT = /^((?:\u001B\[[0-9;]*m)*) {2}/u;
1035
995
  /** Who answered a decision, as the log says it. */
@@ -1126,8 +1086,7 @@ const formatRunEvent = (event, width, tools, terminal = false, verbose = false,
1126
1086
  return [`${tag(event.id)} ${style("yellow", `‖ paused: ${at}`)}`];
1127
1087
  }
1128
1088
  if (event.type === "node.released") return [`${tag(event.id)} ${style("dim", "▸ continues")}`];
1129
- if (event.type === "slot.waiting") return [`${tag(event.id)} ${style("yellow", `‖ ${slotWaitLine(event.holders)}`)}`];
1130
- if (event.type === "slot.taken") return [`${tag(event.id)} ${style("dim", "▸ got a check slot")}`];
1089
+ if (isMachineEvent(event)) return [`${tag(event.id)} ${machineLine(event, style)}`];
1131
1090
  if (event.type === "verify.skipped") return [`${tag(event.id)} ${style("dim", `✓ ${skippedLine(event)}`)}`];
1132
1091
  if (event.type === "each.listed") {
1133
1092
  const round = event.round > 1 ? ` (round ${String(event.round)})` : "";
@@ -1348,18 +1307,4 @@ const attachRun = async (store, root, out, options = {}) => {
1348
1307
  exitCode: end === "aborted" || seen.succeeded ? 0 : 1
1349
1308
  };
1350
1309
  };
1351
- // src/store.ts
1352
- /**
1353
- * @file Opening the user's store (`~/.ork/state.db`) for the length of one
1354
- * command, and closing it after, whatever happens.
1355
- */
1356
- /** Runs `body` with the user's store open, and closes it after. */
1357
- const withStore = async (body) => {
1358
- const store = openStore();
1359
- try {
1360
- await body(store);
1361
- } finally {
1362
- store.close();
1363
- }
1364
- };
1365
- export { unknownNode as a, callsShown as c, createDecisionQueue as d, followRun as i, createTerminalHuman as l, attachRun as n, formatEvent as o, askStored as r, formatRunEvent as s, withStore as t, readText as u };
1310
+ export { formatEvent as a, createTerminalHuman as c, unknownNode as i, readText as l, askStored as n, formatRunEvent as o, followRun as r, callsShown as s, attachRun as t, createDecisionQueue as u };
@@ -1,8 +1,9 @@
1
- import "./src-BeR6f70A.mjs";
2
- import { l as createTerminalHuman, n as attachRun, t as withStore } from "./store-CM3cERGl.mjs";
3
- import { t as findRun } from "./find-CjsNYAAf.mjs";
4
- import { a as style, o as write, s as writeErr } from "./output-CnRzrbUM.mjs";
5
- import { n as attachRefusal } from "./in-a-run-BYjTXaST.mjs";
1
+ import "./src-Dr_fTSu-.mjs";
2
+ import { c as createTerminalHuman, t as attachRun } from "./attach-BHO3X3cf.mjs";
3
+ import { t as findRun } from "./find-BYLlszqj.mjs";
4
+ import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
5
+ import { n as attachRefusal } from "./in-a-run-Bp0k0J09.mjs";
6
+ import { t as withStore } from "./store-oKJ-cCf3.mjs";
6
7
  import { defineCommand } from "citty";
7
8
  // src/commands/attach.ts
8
9
  /**