@krapht/ork 1.0.0-alpha.14 → 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 (42) hide show
  1. package/dist/__build_info__-Bsb1kjgF.mjs +340 -0
  2. package/dist/{store-CleF7KeS.mjs → attach-BHO3X3cf.mjs} +16 -19
  3. package/dist/{attach-CdtGX9se.mjs → attach-CPespxlQ.mjs} +5 -4
  4. package/dist/bin.mjs +64 -1339
  5. package/dist/brief-za0ozN_E.mjs +58 -0
  6. package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-2bHf76x-.mjs} +1 -1
  7. package/dist/{completion--ywde-56.mjs → completion-CQ2UYKuJ.mjs} +5 -5
  8. package/dist/context-BNqDw9z1.mjs +106 -0
  9. package/dist/{find-BVX-bL4u.mjs → find-BYLlszqj.mjs} +1 -1
  10. package/dist/follow-ups-B4GJ3Vzw.mjs +461 -0
  11. package/dist/{format-CvTE5CZQ.mjs → format-C8MsFj8_.mjs} +19 -5
  12. package/dist/{help-groups-CpR2UzSn.mjs → help-groups-CQ98mLjF.mjs} +4 -0
  13. package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-Bp0k0J09.mjs} +1 -1
  14. package/dist/{__build_info__-B82-dGJY.mjs → install-DWtXTq4y.mjs} +3 -227
  15. package/dist/{limits-Dhe--wmX.mjs → limits-B1qzS4cX.mjs} +1 -1
  16. package/dist/{mcp-CsmboyXv.mjs → mcp-BOrVkyhN.mjs} +6 -6
  17. package/dist/{once-CrwI-ZdN.mjs → once-C1fL3GVo.mjs} +3 -3
  18. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  19. package/dist/{pause-DssrYcda.mjs → pause-DOMK-R8V.mjs} +3 -3
  20. package/dist/post-BzA6Qobq.mjs +44 -0
  21. package/dist/{relay-DASRgsTQ.mjs → relay-OWuu4fib.mjs} +1 -1
  22. package/dist/run--4-1xAF2.mjs +1451 -0
  23. package/dist/{run-D8divGCs.mjs → run-CiPsW9Kf.mjs} +1 -1
  24. package/dist/{send-CXv8bx0i.mjs → send-CnNNW-ot.mjs} +2 -2
  25. package/dist/{src-njdtejgZ.mjs → src-B9zWIVqo.mjs} +129 -12
  26. package/dist/{src-8Mul0695.mjs → src-DQKz2eOH.mjs} +322 -75
  27. package/dist/{src-C7z_6fhn.mjs → src-DcrhgipG.mjs} +17 -10
  28. package/dist/{src-tsuZRu0H.mjs → src-Dr_fTSu-.mjs} +4832 -4155
  29. package/dist/store-oKJ-cCf3.mjs +16 -0
  30. package/dist/ui/assets/app-Bdc6OBeZ.css +1 -0
  31. package/dist/ui/assets/app-Drp8Dm9y.js +111 -0
  32. package/dist/ui/assets/{index-D-_uPI2v.js → index-BtwkftKe.js} +2 -2
  33. package/dist/ui/index.html +1 -1
  34. package/dist/{ui-CNO1M0qT.mjs → ui-CzKlsCnm.mjs} +3 -3
  35. package/dist/{usage-BVXi0iFY.mjs → usage-DHGNk2JL.mjs} +3 -3
  36. package/dist/{context-Y_I4Mofq.mjs → validate-Bs_gg22g.mjs} +4 -104
  37. package/dist/{watch-DVNIciHp.mjs → watch-CCCfw3n7.mjs} +19 -5
  38. package/dist/{workflows-B30QkwTk.mjs → workflows-CW28ZU-z.mjs} +2 -2
  39. package/dist/{worktrees-DD4_TpkH.mjs → worktrees-CyE8aF6j.mjs} +1 -1
  40. package/package.json +1 -1
  41. package/dist/ui/assets/app-D_nAPnHM.css +0 -1
  42. package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
@@ -1,4 +1,4 @@
1
- import { Kt as runStatus, Ut as liveStatus, Wt as lockWaitsOf, Zt as isItem, nn as lockWaitLine, qt as slotWaitsOf } from "./src-tsuZRu0H.mjs";
1
+ import { Cn as liveStatus, Dn as slotWaitsOf, En as runStatus, Lt as lockWaitLine, jn as isItem, wn as lockWaitsOf } from "./src-Dr_fTSu-.mjs";
2
2
  import { C as termOf, E as widthOf, n as cost, o as shortCount, r as costOf, s as style, w as cut } from "./output-B1kwo0YU.mjs";
3
3
  import { i as statusChip, n as chip } from "./chips-BqcT58qr.mjs";
4
4
  import { n as nodeVerdict } from "./verdict-CMvPCpWC.mjs";
@@ -180,8 +180,12 @@ const stateText = (node) => {
180
180
  }
181
181
  return node.state;
182
182
  };
183
- /** The ownership tree, one line per node, children under their owner. */
184
- const formatTree = (roots, now = Date.now()) => {
183
+ /**
184
+ * The ownership tree, one line per node, children under their owner. A node in
185
+ * `waits` (a live run's `lockWaitsOf`) reads `waiting for <lock>`, an item
186
+ * too.
187
+ */
188
+ const formatTree = (roots, now = Date.now(), waits = []) => {
185
189
  const lines = [];
186
190
  const walk = (node, lead, branch) => {
187
191
  lines.push({
@@ -198,9 +202,19 @@ const formatTree = (roots, now = Date.now()) => {
198
202
  const type = Math.max(...lines.map((l) => l.node.type.length));
199
203
  return lines.map(({ label: text, node }) => {
200
204
  const usage = node.inputTokens + node.outputTokens === 0 ? "" : style("dim", ` ${count(node.inputTokens)} in / ${count(node.outputTokens)} out · ${cost(node.costUsd)}`);
201
- const state = node.held === true ? `${style("yellow", "‖")} paused` : `${STATE_MARK[node.state]()} ${stateText(node)}`;
205
+ const lock = waits.find((wait) => wait.id === node.id)?.lock;
206
+ const state = node.held === true ? `${style("yellow", "‖")} paused` : lock === void 0 ? `${STATE_MARK[node.state]()} ${stateText(node)}` : `${STATE_MARK.waiting()} waiting for ${lock}`;
202
207
  const spent = isItem(node) && node.startedAt !== void 0 ? style("dim", ` ${duration((node.finishedAt === void 0 ? now : Date.parse(node.finishedAt)) - Date.parse(node.startedAt))}`) : "";
203
208
  return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
204
209
  });
205
210
  };
206
- export { header as a, listedStatus as i, formatRuns as n, table as o, formatTree as r, ago as t };
211
+ /**
212
+ * What `ork tree` prints: the run's header (its status as `ork ps` reads it)
213
+ * and the tree. A finished run's lock waits are stale, so only a live run's
214
+ * show; `events` are the run's, `roots` its nested tree.
215
+ */
216
+ const formatTreeView = (run, events, roots, now = Date.now()) => {
217
+ const waits = runStatus(run, events) === "running" ? lockWaitsOf(events) : [];
218
+ return [`${style("bold", `run ${run.id}`)} ${dim(`of ${run.workflow} · ${listedStatus(run, events)} · ${cost(run.costUsd)}`)}`, ...formatTree(roots, now, waits)];
219
+ };
220
+ export { header as a, listedStatus as i, formatRuns as n, table as o, formatTreeView as r, ago as t };
@@ -284,6 +284,10 @@ const HELP_GROUPS = [
284
284
  name: "feedback",
285
285
  short: "What got in the way in your runs"
286
286
  },
287
+ {
288
+ name: "follow-ups",
289
+ short: "What shipped changes left for later"
290
+ },
287
291
  {
288
292
  name: "worktrees",
289
293
  short: "Track and clean up Ork's worktrees"
@@ -1,4 +1,4 @@
1
- import { p as runMarkerIn } from "./src-C7z_6fhn.mjs";
1
+ import { p as runMarkerIn } from "./src-DcrhgipG.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,7 +1,6 @@
1
- import { $n as maskSecrets, A as isNewer, Cr as guardedBase, Mn as FEEDBACK_KINDS, Nn as FEEDBACK_SEVERITIES, Sr as estimateTokens, Tr as writePlainFile, Un as targetOf, Vn as fingerprint, j as parseVersion, k as compareVersions, mr as placeFor, pr as placeFiles, wr as plainPathInside, xr as LESSON_CAP_TOKENS, yr as inside, zn as publishPost } from "./src-tsuZRu0H.mjs";
1
+ import { Dr as placeFor, Er as placeFiles, Fr as estimateTokens, Gn as FEEDBACK_SEVERITIES, Ir as guardedBase, Lr as plainPathInside, Mr as inside, Pr as LESSON_CAP_TOKENS, Rr as writePlainFile, Wn as FEEDBACK_KINDS, Zn as publishPost, er as fingerprint, fr as maskSecrets, nr as targetOf } from "./src-Dr_fTSu-.mjs";
2
2
  import path from "node:path";
3
- import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
4
- import { homedir } from "node:os";
3
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
5
4
  import { spawn } from "node:child_process";
6
5
  // ../core/src/feedback/actions.ts
7
6
  const ALLOWED = { ok: true };
@@ -363,174 +362,6 @@ const previewPost = (store, item, ask) => {
363
362
  })
364
363
  };
365
364
  };
366
- // ../core/src/remote/session.ts
367
- const chattable = (type) => type === "agent.leader" || type === "agent.worker";
368
- /** Reads `node`'s session from a run's events, and every node's last output. */
369
- const nodeSession = (events, node) => {
370
- const session = {
371
- known: false,
372
- chattable: false,
373
- running: false
374
- };
375
- const outputs = {};
376
- for (const { event } of events) {
377
- if (event.type === "run.started") {
378
- const declared = event.nodes.find((n) => n.id === node);
379
- if (declared !== void 0) {
380
- session.known = true;
381
- session.chattable = chattable(declared.type);
382
- }
383
- }
384
- if (event.type === "node.finished") outputs[event.id] = event.output;
385
- if (!("id" in event) || event.id !== node) continue;
386
- if (event.type === "node.started") {
387
- session.known = true;
388
- session.chattable = chattable(event.nodeType);
389
- session.running = true;
390
- }
391
- if (event.type === "node.finished") session.running = false;
392
- if (event.type === "worktree" && event.action === "created") session.checkout = {
393
- path: event.path,
394
- ...event.branch === void 0 ? {} : { branch: event.branch },
395
- own: true,
396
- owner: node
397
- };
398
- if (event.type !== "node.event") continue;
399
- const inner = event.event;
400
- if (inner.fork === true) continue;
401
- if (inner.type === "session.started") {
402
- session.sessionId = inner.sessionId;
403
- if (inner.cwd !== void 0) session.cwd = inner.cwd;
404
- if (inner.driver !== void 0) session.driver = inner.driver;
405
- if (inner.model !== "") session.model = inner.model;
406
- }
407
- if (inner.type === "session.finished" && inner.outcome.sessionId !== "") session.sessionId = inner.outcome.sessionId;
408
- }
409
- return {
410
- session,
411
- outputs
412
- };
413
- };
414
- /** The dist-tags every install follows, toward and after 1.0.0. */
415
- const CHANNEL_TAGS = [
416
- "latest",
417
- "alpha",
418
- "beta",
419
- "rc"
420
- ];
421
- /** The dist-tag of snapshots from `main`, followed only by a canary. */
422
- const CANARY_TAG = "canary";
423
- const updateCachePath = (home = homedir()) => path.join(home, ".ork", "update-check.json");
424
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
425
- const text = (record, key) => {
426
- const value = record[key];
427
- return typeof value === "string" ? value : void 0;
428
- };
429
- const notifiedOf = (value) => {
430
- if (!isRecord(value)) return void 0;
431
- const version = text(value, "version");
432
- const at = text(value, "at");
433
- return version === void 0 || at === void 0 ? void 0 : {
434
- version,
435
- at
436
- };
437
- };
438
- /** The cache, or `null` when there is none or it cannot be read. */
439
- const readUpdateCache = (file) => {
440
- let parsed;
441
- try {
442
- parsed = JSON.parse(readFileSync(file, "utf8"));
443
- } catch {
444
- return null;
445
- }
446
- if (!isRecord(parsed)) return null;
447
- const checkedAt = text(parsed, "checkedAt");
448
- if (checkedAt === void 0) return null;
449
- const latest = text(parsed, "latest");
450
- const notified = notifiedOf(parsed["notified"]);
451
- return {
452
- checkedAt,
453
- ...latest === void 0 ? {} : { latest },
454
- ...notified === void 0 ? {} : { notified }
455
- };
456
- };
457
- /** Writes the cache whole (a temporary file, then a rename). */
458
- const writeUpdateCache = (file, cache) => {
459
- mkdirSync(path.dirname(file), { recursive: true });
460
- const temporary = `${file}.${process.pid}.tmp`;
461
- writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}\n`);
462
- renameSync(temporary, file);
463
- };
464
- const age = (iso, now) => {
465
- const time = Date.parse(iso);
466
- return Number.isNaN(time) ? Infinity : now.getTime() - time;
467
- };
468
- /** Whether it is time to ask the registry again. */
469
- const checkDue = (cache, now) => cache === null || age(cache.checkedAt, now) >= 864e5;
470
- /** Whether `version` is a canary snapshot: its pre-release starts `canary`. */
471
- const isCanary = (version) => parseVersion(version)?.pre[0]?.startsWith(CANARY_TAG) === true;
472
- /**
473
- * The newest version, by semver precedence, among the dist-tags `current`
474
- * follows: the channel tags, and `canary` too when `current` is a canary. A tag
475
- * whose value doesn't parse is skipped; `null` when none does.
476
- */
477
- const newestTagged = (tags, current) => {
478
- return (current !== void 0 && isCanary(current) ? [...CHANNEL_TAGS, "canary"] : CHANNEL_TAGS).flatMap((tag) => {
479
- const value = tags[tag];
480
- const version = typeof value === "string" ? parseVersion(value) : null;
481
- return typeof value === "string" && version !== null ? [{
482
- value,
483
- version
484
- }] : [];
485
- }).reduce((best, candidate) => best === null || compareVersions(candidate.version, best.version) > 0 ? candidate : best, null)?.value ?? null;
486
- };
487
- /**
488
- * The newest version on the registry that the running version follows ({@link
489
- * newestTagged}), or `null` on any failure.
490
- */
491
- const fetchLatest = async (name, options = {}) => {
492
- const registry = (options.registry ?? "https://registry.npmjs.org").replace(/\/+$/u, "");
493
- const get = options.fetch ?? globalThis.fetch;
494
- try {
495
- const response = await get(`${registry}/-/package/${name}/dist-tags`, {
496
- headers: { accept: "application/json" },
497
- signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
498
- });
499
- if (!response.ok) return null;
500
- const tags = await response.json();
501
- return isRecord(tags) ? newestTagged(tags, options.current) : null;
502
- } catch {
503
- return null;
504
- }
505
- };
506
- /**
507
- * Asks the registry and records the answer. A failed check is recorded too, so
508
- * an offline machine is not asked again until the interval passes; the last
509
- * known version is kept. `options.current` is the running version.
510
- */
511
- const refreshUpdateCache = async (file, name, now, options = {}) => {
512
- const before = readUpdateCache(file);
513
- const latest = await fetchLatest(name, options) ?? before?.latest;
514
- const cache = {
515
- ...before,
516
- checkedAt: now.toISOString(),
517
- ...latest === void 0 ? {} : { latest }
518
- };
519
- writeUpdateCache(file, cache);
520
- return cache;
521
- };
522
- /** The newer version the cache knows of, if any. */
523
- const newerVersion = (cache, current) => cache?.latest !== void 0 && isNewer(cache.latest, current) ? cache.latest : null;
524
- /**
525
- * The version to tell the user about now: newer than theirs, and not already
526
- * told today. `null` when there is nothing to say.
527
- */
528
- const pendingNotice = (cache, current, now) => {
529
- const newer = newerVersion(cache, current);
530
- if (newer === null) return null;
531
- const told = cache?.notified;
532
- return told?.version === newer && age(told.at, now) < 864e5 ? null : newer;
533
- };
534
365
  /**
535
366
  * Runs the command; resolves its exit code. Without `onOutput` it writes on the
536
367
  * terminal.
@@ -558,59 +389,4 @@ const runInstall = (argv, onOutput) => new Promise((resolve) => {
558
389
  resolve(code ?? 1);
559
390
  });
560
391
  });
561
- // ../core/src/update/switches.ts
562
- /**
563
- * @file The environment's say over the update check (ADR-038): the variables
564
- * that turn it off (`ORK_NO_UPDATE_CHECK`, the common `NO_UPDATE_NOTIFIER`,
565
- * and `CI`), and the registry to ask instead of npm's. The CLI adds its own
566
- * rules about where a notice may go; the server behind `ork ui` uses only
567
- * these.
568
- */
569
- /** Turns the automatic check and its notice off. */
570
- const NO_UPDATE_ENV = "ORK_NO_UPDATE_CHECK";
571
- /** A registry to ask instead of npm's, e.g. a company mirror. */
572
- const UPDATE_REGISTRY_ENV = "ORK_UPDATE_REGISTRY";
573
- /** The variables that turn the check off, in the order they are looked at. */
574
- const UPDATE_SWITCHES = [
575
- NO_UPDATE_ENV,
576
- "NO_UPDATE_NOTIFIER",
577
- "CI"
578
- ];
579
- /** Set to anything but empty, `0` or `false`. */
580
- const isSwitchedOn = (value) => value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
581
- /** The variable that turns the check off in `env`, or `null` when none does. */
582
- const updateSwitchOff = (env) => UPDATE_SWITCHES.find((name) => isSwitchedOn(env[name])) ?? null;
583
- /** The registry `ORK_UPDATE_REGISTRY` names, if it names one. */
584
- const registryOf = (env) => {
585
- const registry = env[UPDATE_REGISTRY_ENV];
586
- return registry === void 0 || registry === "" ? void 0 : registry;
587
- };
588
- /** The host of a registry that isn't an http(s) URL, past any `user:pass@`. */
589
- const hostOf = (registry) => registry.replace(/^[a-z][\w+.-]*:\/\//iu, "").replace(/^[^/?#]*@/u, "").split(/[/?#]/u)[0] ?? "";
590
- /**
591
- * The registry as it may be shown (on a page, in a message): an http(s) URL
592
- * without its user name and password, which a mirror's URL may carry; anything
593
- * else only as its host.
594
- */
595
- const registryShown = (registry) => {
596
- try {
597
- const url = new URL(registry);
598
- if (url.protocol === "http:" || url.protocol === "https:") {
599
- if (url.username === "" && url.password === "") return registry;
600
- url.username = "";
601
- url.password = "";
602
- return url.href;
603
- }
604
- } catch {}
605
- return hostOf(registry);
606
- };
607
- // src/__build_info__.ts
608
- /**
609
- * @file The published package's name and version, bound to
610
- * `packages/cli/package.json`. Do not edit by hand: `pnpm changesets:version`
611
- * and `pnpm build` rewrite it (`tools/release/build-info.ts`), and a test
612
- * fails when it is stale.
613
- */
614
- const ORK_NAME = "@krapht/ork";
615
- const ORK_VERSION = "1.0.0-alpha.14";
616
- export { feedbackActions as C, placeOf as S, previewPost as _, registryShown as a, acceptItem as b, checkDue as c, pendingNotice as d, readUpdateCache as f, nodeSession as g, writeUpdateCache as h, registryOf as i, fetchLatest as l, updateCachePath as m, ORK_VERSION as n, updateSwitchOff as o, refreshUpdateCache as p, isSwitchedOn as r, runInstall as s, ORK_NAME as t, newerVersion as u, checkNote as v, openFixRun as w, lessonPlaceOf as x, recordNote as y };
392
+ export { acceptItem as a, feedbackActions as c, recordNote as i, openFixRun as l, previewPost as n, lessonPlaceOf as o, checkNote as r, placeOf as s, runInstall as t };
@@ -1,4 +1,4 @@
1
- import { C as capsHere, Dt as limitsNow, Et as NOT_STARTED, Ot as limitsReport } from "./src-tsuZRu0H.mjs";
1
+ import { C as capsHere, bt as limitsReport, vt as NOT_STARTED, yt as limitsNow } from "./src-Dr_fTSu-.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,8 +1,8 @@
1
- import { A as isNewer } from "./src-tsuZRu0H.mjs";
2
- import { n as ORK_VERSION } from "./__build_info__-B82-dGJY.mjs";
3
- import { p as runMarkerIn } from "./src-C7z_6fhn.mjs";
4
- import { n as explainNode } from "./context-Y_I4Mofq.mjs";
5
- import { a as openMcp, c as refused, o as tool, s as answer } from "./src-8Mul0695.mjs";
1
+ import { A as isNewer } from "./src-Dr_fTSu-.mjs";
2
+ import { n as ORK_VERSION } from "./__build_info__-Bsb1kjgF.mjs";
3
+ import { p as runMarkerIn } from "./src-DcrhgipG.mjs";
4
+ import { n as explainNode } from "./context-BNqDw9z1.mjs";
5
+ import { a as openMcp, c as refused, o as tool, s as answer } from "./src-DQKz2eOH.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.14") ? tooOld(all, options.expects) : all;
46
+ const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.15") ? tooOld(all, options.expects) : all;
47
47
  const server = new McpServer({
48
48
  name: "ork",
49
49
  version: ORK_VERSION
@@ -1,7 +1,7 @@
1
- import "./src-tsuZRu0H.mjs";
2
- import { t as runOnce } from "./run-D8divGCs.mjs";
1
+ import "./src-Dr_fTSu-.mjs";
2
+ import { t as runOnce } from "./run-CiPsW9Kf.mjs";
3
3
  import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
4
- import { t as ago } from "./format-CvTE5CZQ.mjs";
4
+ import { t as ago } from "./format-C8MsFj8_.mjs";
5
5
  import { spawn } from "node:child_process";
6
6
  import { defineCommand, parseArgs } from "citty";
7
7
  // src/commands/once.ts
@@ -0,0 +1,58 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ // ../core/src/machine/orphan-guard.ts
3
+ /**
4
+ * @file The orphan guard: a node's processes end with `ork`, whatever ends
5
+ * `ork` (SIGKILL, the OOM killer, a crash exit). A child started in its own
6
+ * process group (`detached: true`) under `sh` also starts a small watcher in
7
+ * that group, which reads a pipe that only `ork` holds open. `ork` never
8
+ * writes to it, so when `ork` dies the read sees EOF, and the watcher sends
9
+ * its own group (`kill 0`, never another's) SIGTERM, then SIGKILL after
10
+ * {@link KILL_AFTER_MS}. A line on the pipe instead lets the watcher exit
11
+ * quietly, and so does killing it, which is how a node that ends normally
12
+ * leaves its leftover background processes alone. POSIX only. Two uses:
13
+ *
14
+ * - A shell that outlives the program (the tool runner's timing wrapper):
15
+ * `${orphanGuard(4)} w=$!; "$0" "$@" 4<&-; s=$?; kill -s KILL $w`, then
16
+ * spawn with a `"pipe"` at `stdio[4]` and call
17
+ * `holdGuard(child.stdio[4])`.
18
+ * - A shell that `exec`s the program (a session, so its pid stays the
19
+ * driver's): `${orphanGuard(3)} exec 3<&-; exec "$0" "$@"` (a bare `exec`
20
+ * closes the pipe: macOS's `sh` keeps a copy of one an `exec` command
21
+ * closes, which the driver would inherit), with a `"pipe"` at `stdio[3]`;
22
+ * nothing is left to kill the watcher, so call the function `holdGuard`
23
+ * returns once the driver has exited (on `exit`: the pipe holds `close`).
24
+ */
25
+ /** How long the group gets to end after SIGTERM before SIGKILL. */
26
+ const KILL_AFTER_MS = 2e3;
27
+ /** The file descriptors a redirect can name in every POSIX shell. */
28
+ const SHELL_FDS = [
29
+ 3,
30
+ 4,
31
+ 5,
32
+ 6,
33
+ 7,
34
+ 8,
35
+ 9
36
+ ];
37
+ /**
38
+ * Shell text that starts the watcher in the background on pipe `fd` (3 to 9);
39
+ * it ends with `&`, so `$!` after it is the watcher's pid. The watcher holds no
40
+ * other descriptor, so it never keeps the child's output pipes open.
41
+ */
42
+ const orphanGuard = (fd) => {
43
+ const others = SHELL_FDS.filter((other) => other !== fd).map((other) => `${String(other)}>&-`).join(" ");
44
+ return `{ read -r _ork <&${String(fd)} || { trap '' TERM; kill -s TERM 0; sleep ${String(KILL_AFTER_MS / 1e3)}; kill -s KILL 0; }; } </dev/null >/dev/null 2>&1 ${others} & `;
45
+ };
46
+ /**
47
+ * Keeps `ork`'s end of the watch pipe (`child.stdio[fd]`) open and drained, so
48
+ * it never holds back the child's `close`; returns the release, which writes
49
+ * the line that lets the watcher exit without touching its group.
50
+ */
51
+ const holdGuard = (pipe) => {
52
+ pipe?.on("error", () => {});
53
+ if (pipe instanceof Readable) pipe.resume();
54
+ return () => {
55
+ if (pipe instanceof Writable && pipe.writable) pipe.write("\n");
56
+ };
57
+ };
58
+ export { holdGuard as n, orphanGuard as r, KILL_AFTER_MS as t };
@@ -1,7 +1,7 @@
1
- import { U as openStore, _t as requestPause, gt as requestContinue } from "./src-tsuZRu0H.mjs";
2
- import { t as findRun } from "./find-BVX-bL4u.mjs";
1
+ import { U as openStore, bn as requestPause, yn as requestContinue } from "./src-Dr_fTSu-.mjs";
2
+ import { t as findRun } from "./find-BYLlszqj.mjs";
3
3
  import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
4
- import { i as continueRefusal } from "./in-a-run-cL5N9gpH.mjs";
4
+ import { i as continueRefusal } from "./in-a-run-Bp0k0J09.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  // src/commands/pause.ts
7
7
  /**
@@ -0,0 +1,44 @@
1
+ import { Qn as redact } from "./src-Dr_fTSu-.mjs";
2
+ // ../core/src/follow-ups/post.ts
3
+ /** Posts `f`'s draft as a GitHub comment once `confirmed`, and marks it done. */
4
+ const postFollowUpMessage = async (f, options) => {
5
+ const draft = f.draft;
6
+ if (f.kind !== "message" || draft?.channel !== "github") return {
7
+ ok: false,
8
+ error: "only a message drafted for the github channel is posted; send the others yourself"
9
+ };
10
+ if (!options.confirmed) return {
11
+ ok: false,
12
+ error: "posting to GitHub needs the maintainer's confirmation"
13
+ };
14
+ const body = redact(draft.text, {
15
+ roots: [f.project],
16
+ home: options.home
17
+ });
18
+ const on = /\/pull\/\d+/u.test(draft.to) ? "pr" : "issue";
19
+ const result = await options.gh([
20
+ on,
21
+ "comment",
22
+ draft.to,
23
+ "--body",
24
+ body
25
+ ], { cwd: f.project });
26
+ if (!result.ok) return {
27
+ ok: false,
28
+ error: `gh failed: ${result.stderr.trim() || result.stdout.trim() || "no output"}`
29
+ };
30
+ const url = result.stdout.trim().split("\n").findLast((line) => line.startsWith("https://"));
31
+ const done = {
32
+ ...f,
33
+ status: "done",
34
+ note: `posted${url === void 0 ? "" : ` as ${url}`}`,
35
+ updatedAt: options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString()
36
+ };
37
+ options.rows.putFollowUp(done);
38
+ return {
39
+ ok: true,
40
+ followUp: done,
41
+ ...url === void 0 ? {} : { url }
42
+ };
43
+ };
44
+ export { postFollowUpMessage as t };
@@ -1,4 +1,4 @@
1
- import { U as openStore, ft as addressOf, ht as unlinkNodes, mt as linkNodes } from "./src-tsuZRu0H.mjs";
1
+ import { U as openStore, _n as linkNodes, hn as addressOf, vn as unlinkNodes } from "./src-Dr_fTSu-.mjs";
2
2
  import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  // src/commands/relay.ts