toolroll 0.9.100 → 0.9.101

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 (41) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/accent-colors.js +1 -1
  3. package/dist/activity-line.d.ts +1 -1
  4. package/dist/activity-line.js +1 -1
  5. package/dist/browser/terminal.css +1 -1
  6. package/dist/browser/terminal.js +42 -42
  7. package/dist/browser/workspace.css +2 -2
  8. package/dist/browser/workspace.js +32 -32
  9. package/dist/browser-workspace.d.ts +31 -2
  10. package/dist/design/kit.css +94 -2
  11. package/dist/design/kit.d.ts +12 -4
  12. package/dist/design/kit.js +8 -2
  13. package/dist/design/tokens.css +5 -5
  14. package/dist/first-run.d.ts +10 -6
  15. package/dist/first-run.js +19 -18
  16. package/dist/guides.js +1 -1
  17. package/dist/operate.js +2 -0
  18. package/dist/scope.d.ts +5 -0
  19. package/dist/scope.js +8 -1
  20. package/dist/server/chrome.d.ts +1 -1
  21. package/dist/server/chrome.js +1 -1
  22. package/dist/server/console.css +46 -17
  23. package/dist/server/kit.d.ts +36 -3
  24. package/dist/server/kit.js +33 -4
  25. package/dist/server/render-chat.d.ts +2 -0
  26. package/dist/server/render-chat.js +1 -0
  27. package/dist/server/render-pages.js +1 -1
  28. package/dist/server/render-results.js +3 -3
  29. package/dist/server/render-tasks.d.ts +10 -0
  30. package/dist/server/render-tasks.js +231 -62
  31. package/dist/server/task-views.js +5 -1
  32. package/dist/settings-groups-ui.js +1 -1
  33. package/dist/store.js +14 -3
  34. package/dist/task-archive.d.ts +3 -0
  35. package/dist/task-archive.js +5 -0
  36. package/dist/task-status.d.ts +2 -2
  37. package/dist/task-status.js +3 -3
  38. package/dist/tui.js +1 -1
  39. package/dist/workspace-ui.d.ts +69 -0
  40. package/dist/workspace-ui.js +103 -0
  41. package/package.json +1 -1
package/dist/store.js CHANGED
@@ -16,7 +16,7 @@ import { V156_SCHEMA, V156_STEPS } from "./fallback-schema.js";
16
16
  import { V158_ADDED_COLUMNS, V158_SCHEMA, V158_STEPS, LEAD_FAILURE_DETAILS, LEAD_ROUTES } from "./lead-latency-schema.js";
17
17
  import { V157_SCHEMA, V157_STEPS } from "./chat-live-schema.js";
18
18
  import { V159_ADDED_COLUMNS, V159_SCHEMA, V159_STEPS } from "./one-step-chat-schema.js";
19
- import { refArchived, taskArchived } from "./task-archive.js";
19
+ import { refArchived, taskArchived, taskCancelled } from "./task-archive.js";
20
20
  export { refArchived, taskArchived } from "./task-archive.js";
21
21
  import { CHAT_PROVIDERS, CHAT_SCHEMA } from "./contracts/chat-tables.js";
22
22
  import {} from "./chat-core.js";
@@ -8073,7 +8073,11 @@ export class Store {
8073
8073
  * exactly what the signed digest was bound to. Returns false when the
8074
8074
  * scope vanished mid-ceremony. */
8075
8075
  sealScopeApproval(taskId, by, now, mutation = {}, basis, approverKind) {
8076
- return (this.once(mutation, "sealScopeApproval", () => {
8076
+ // ONE TRANSACTION, the primitive's own (Codex review of the steps page, round 2): every check below is read in
8077
+ // the same write transaction as the seal and the release authority it opens, so a task cancelled or put away on
8078
+ // another connection can't slip between the check and the write, and a seal whose release authority can't open
8079
+ // rolls back whole even when the caller opened no transaction. Reentrant: inside a caller's transaction it joins.
8080
+ const seal = () => this.once(mutation, "sealScopeApproval", () => {
8077
8081
  // ONLY A PERSON'S SEAL HAS NO BASIS (approval policy, phase 1): with none given this is the person's
8078
8082
  // ceremony (scope.ts approve), so a machine — the lead's token, a remote API run — never mints one, whatever
8079
8083
  // road it came by.
@@ -8082,6 +8086,10 @@ export class Store {
8082
8086
  // A PUT-AWAY TASK (v145, task-tidy.md) is never approved, by any road: restore it first.
8083
8087
  if (taskArchived(this, taskId))
8084
8088
  return false;
8089
+ // A CANCELLED TASK the same (Codex review of the steps page, finding 2): no seal, and so no release authority,
8090
+ // is written for work that was cancelled — whichever road asks (a stale form, the CLI, a rule, the schedule).
8091
+ if (taskCancelled(this, taskId))
8092
+ return false;
8085
8093
  // A RELEASE WAITING FOR CI (Codex review round 1, finding 4): filed while its pull request's checks still ran,
8086
8094
  // only the automatic road (which proves CI itself) seals it until the plane read exact-head CI passing.
8087
8095
  if (basis?.kind !== "auto-check" && basis?.kind !== "auto-green" && releaseCiHoldOf(this, taskId) !== null)
@@ -8308,7 +8316,10 @@ export class Store {
8308
8316
  }
8309
8317
  }
8310
8318
  return changed.changes > 0;
8311
- }) === true);
8319
+ }) === true;
8320
+ // Already inside a transaction (a caller's transact(), or a savepoint opened outside one): it joins that one,
8321
+ // which owns the commit and the rollback. Otherwise it opens its own.
8322
+ return this.db.isTransaction === true ? seal() : this.transact(seal);
8312
8323
  }
8313
8324
  /** Whether this task's scope carries a LIVE seal: approved, and the
8314
8325
  * approved digest is the current one (a rewritten scope unseals). The
@@ -8,3 +8,6 @@ export declare function taskArchived(store: Store, taskId: string): boolean;
8
8
  /** Whether a flow card's work is put away: its current task or its first build task (a later stage keeps the first
9
9
  * and may clear the current one). Read from the card's own row now, so a put away that committed first holds it. */
10
10
  export declare function cardTaskArchived(store: Store, cardId: number): boolean;
11
+ /** Whether the built-in task with this id is cancelled. Every door that approves, seals or carries a release asks
12
+ * this beside the put-away check: a cancelled task takes no new yes (fail closed), by any road. */
13
+ export declare function taskCancelled(store: Store, taskId: string): boolean;
@@ -14,3 +14,8 @@ export function cardTaskArchived(store, cardId) {
14
14
  return store.handle.prepare(`SELECT 1 FROM flow_card c JOIN task_ref r ON r.backend = 'built-in' AND r.external_id IN (c.task, c.primary_task)
15
15
  JOIN task_archive a ON a.task_ref = r.id WHERE c.id = ?`).get(cardId) !== undefined;
16
16
  }
17
+ /** Whether the built-in task with this id is cancelled. Every door that approves, seals or carries a release asks
18
+ * this beside the put-away check: a cancelled task takes no new yes (fail closed), by any road. */
19
+ export function taskCancelled(store, taskId) {
20
+ return store.handle.prepare("SELECT 1 FROM task WHERE id = ? AND state = 'cancelled'").get(taskId) !== undefined;
21
+ }
@@ -18,7 +18,7 @@ export declare const OPEN_RESULT = "Open result";
18
18
  export declare const MACHINE_CHECK_RUNNING = "Running the check on the prepared version.";
19
19
  export type DetailKey = "checks" | "pull-request" | "requirements" | "evidence";
20
20
  /** The row's small icon: the only coloured part of a detail. `failed` is red
21
- * and only ever appears under the Failed headline; `note` is amber. */
21
+ * and only ever appears under the Failed headline; `note` is ink, with a caution mark. */
22
22
  export type DetailMark = "ok" | "running" | "none" | "note" | "failed";
23
23
  export type StatusAction = {
24
24
  label: string;
@@ -302,7 +302,7 @@ export declare function statusWhyHtml(status: TaskStatus, extra?: readonly strin
302
302
  detail: string;
303
303
  }[]): Html;
304
304
  /** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
305
- export declare const TASK_STATUS_CSS = ".status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-warning,#ab6400)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-warning,#ab6400)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-warning,#ab6400)}";
305
+ export declare const TASK_STATUS_CSS = ".status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-ink)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-ink)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-ink)}";
306
306
  /** A dispatch diagnosis as its stage, for the phone and CLI reads that hold
307
307
  * no assignment. A done task is Complete once a person marked its result;
308
308
  * a refuted result whose project check failed is Failed. */
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Severity: red belongs to the Failed headline alone. A detail problem that
10
10
  * doesn't undo the outcome (a pull request that couldn't open, shortened
11
- * output) is an amber note on its own row with one action; the exact
11
+ * output) is an ink note on its own row with one action; the exact
12
12
  * technical reason stays one tap away in `why`. */
13
13
  import { DEFAULT_LEAD_NAME } from "./lead-identity.js";
14
14
  import { html } from "./html.js";
@@ -209,7 +209,7 @@ function sentenceOf(headline, facts) {
209
209
  case "Stopped": return reason ?? "Stopped by a person. The work so far is kept.";
210
210
  }
211
211
  }
212
- /** A detail problem: red only under Failed, otherwise an amber note. */
212
+ /** A detail problem: red only under Failed, otherwise an ink note. */
213
213
  const problem = (headline) => headline === "Failed" ? "failed" : "note";
214
214
  function detailsOf(headline, facts) {
215
215
  const rows = [];
@@ -627,7 +627,7 @@ export function statusWhyHtml(status, extra = [], diagnostics = []) {
627
627
  return html `<details class="status-why"><summary>${STATUS_MORE}</summary>${lines.map(one => html `<p class="meta">${one}</p>`)}${diagnostics.map(one => html `<p class="meta" data-work-diagnostic="${one.token}">${one.label} · ${one.detail}</p>`)}</details>`;
628
628
  }
629
629
  /** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
630
- export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-warning,#ab6400)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-warning,#ab6400)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-warning,#ab6400)}`;
630
+ export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-ink)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-ink)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-ink)}`;
631
631
  /** A dispatch diagnosis as its stage, for the phone and CLI reads that hold
632
632
  * no assignment. A done task is Complete once a person marked its result;
633
633
  * a refuted result whose project check failed is Failed. */
package/dist/tui.js CHANGED
@@ -67,7 +67,7 @@ Read about how to prevent this error on https://github.com/vadimdemedes/ink/#isr
67
67
  `))}patchConsole(){this.options.debug||(this.restoreConsole=_C((u,a)=>{u==="stdout"&&this.writeToStdout(a),u==="stderr"&&(a.startsWith("The above error occurred")||this.writeToStderr(a))}))}initKittyKeyboard(){if(!this.options.kittyKeyboard)return;let u=this.options.kittyKeyboard,a=u.mode??"auto";if(a==="disabled"||!this.options.stdin.isTTY||!this.options.stdout.isTTY)return;let l=u.flags??["disambiguateEscapeCodes"];if(a==="enabled"){this.enableKittyProtocol(l);return}let r=xi.env.TERM??"",E=xi.env.TERM_PROGRAM??"",B="KITTY_WINDOW_ID"in xi.env||r==="xterm-kitty"||E==="WezTerm"||E==="ghostty";!qe&&B&&this.confirmKittySupport(l)}confirmKittySupport(u){let{stdin:a,stdout:l}=this.options,r=[],E=()=>{this.cancelKittyDetection=void 0,clearTimeout(c),a.removeListener("data",B);let Q=Cy(r);r=[],Q.length>0&&a.unshift(Buffer.from(Q))},B=Q=>{let y=typeof Q=="string"?Buffer.from(Q):Q;for(let N of y)r.push(N);sy(r)&&(E(),this.isUnmounted||this.enableKittyProtocol(u))};a.on("data",B);let c=setTimeout(E,200);this.cancelKittyDetection=E,l.write("\x1B[?u")}enableKittyProtocol(u){this.options.stdout.write(`\x1B[>${_Q(u)}u`),this.kittyProtocolEnabled=!0}};var cy=(e,u)=>{let a={stdout:xo.stdout,stdin:xo.stdin,stderr:xo.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0,maxFps:30,incrementalRendering:!1,concurrent:!1,...Qy(u)},l=fy(a.stdout,()=>new za(a),a.concurrent??!1);return l.render(e),{rerender:l.render,unmount(){l.unmount()},waitUntilExit:l.waitUntilExit,cleanup:()=>Ui.delete(a.stdout),clear:l.clear}},TE=cy,Qy=(e={})=>e instanceof Iy?{stdout:e,stdin:xo.stdin}:e,fy=(e,u,a)=>{let l=Ui.get(e);return l?l.isConcurrent!==a&&console.warn(`Warning: render() was called with concurrent: ${a}, but the existing instance for this stdout uses concurrent: ${l.isConcurrent}. The concurrent option only takes effect on the first render. Call unmount() first if you need to change the rendering mode.`):(l=u(),Ui.set(e,l)),l};var Dy=aA(Zl(),1);var To=aA(SA(),1);var XQ=aA(SA(),1);var dy=aA(SA(),1);var py=aA(SA(),1);var LE=aA(SA(),1);import{Buffer as my}from"node:buffer";var hy=/^(?:\x1b)([a-zA-Z0-9])$/,yy=/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/,jQ={OP:"f1",OQ:"f2",OR:"f3",OS:"f4","[11~":"f1","[12~":"f2","[13~":"f3","[14~":"f4","[[A":"f1","[[B":"f2","[[C":"f3","[[D":"f4","[[E":"f5","[15~":"f5","[17~":"f6","[18~":"f7","[19~":"f8","[20~":"f9","[21~":"f10","[23~":"f11","[24~":"f12","[A":"up","[B":"down","[C":"right","[D":"left","[E":"clear","[F":"end","[H":"home",OA:"up",OB:"down",OC:"right",OD:"left",OE:"clear",OF:"end",OH:"home","[1~":"home","[2~":"insert","[3~":"delete","[4~":"end","[5~":"pageup","[6~":"pagedown","[[5~":"pageup","[[6~":"pagedown","[7~":"home","[8~":"end","[a":"up","[b":"down","[c":"right","[d":"left","[e":"clear","[2$":"insert","[3$":"delete","[5$":"pageup","[6$":"pagedown","[7$":"home","[8$":"end",Oa:"up",Ob:"down",Oc:"right",Od:"left",Oe:"clear","[2^":"insert","[3^":"delete","[5^":"pageup","[6^":"pagedown","[7^":"home","[8^":"end","[Z":"tab"},VQ=[...Object.values(jQ),"backspace"],Sy=e=>["[a","[b","[c","[d","[e","[2$","[3$","[5$","[6$","[7$","[8$","[Z"].includes(e),Fy=e=>["Oa","Ob","Oc","Od","Oe","[2^","[3^","[5^","[6^","[7^","[8^"].includes(e),WQ=/^\x1b\[(\d+)(?:;(\d+)(?::(\d+))?(?:;([\d:]+))?)?u$/,Ny=/^\x1b\[(\d+);(\d+):(\d+)([A-Za-z~])$/,Ry={A:"up",B:"down",C:"right",D:"left",E:"clear",F:"end",H:"home",P:"f1",Q:"f2",R:"f3",S:"f4"},vy={2:"insert",3:"delete",5:"pageup",6:"pagedown",7:"home",8:"end",11:"f1",12:"f2",13:"f3",14:"f4",15:"f5",17:"f6",18:"f7",19:"f8",20:"f9",21:"f10",23:"f11",24:"f12"},ZQ={27:"escape",9:"tab",127:"delete",8:"backspace",57358:"capslock",57359:"scrolllock",57360:"numlock",57361:"printscreen",57362:"pause",57363:"menu",57376:"f13",57377:"f14",57378:"f15",57379:"f16",57380:"f17",57381:"f18",57382:"f19",57383:"f20",57384:"f21",57385:"f22",57386:"f23",57387:"f24",57388:"f25",57389:"f26",57390:"f27",57391:"f28",57392:"f29",57393:"f30",57394:"f31",57395:"f32",57396:"f33",57397:"f34",57398:"f35",57399:"kp0",57400:"kp1",57401:"kp2",57402:"kp3",57403:"kp4",57404:"kp5",57405:"kp6",57406:"kp7",57407:"kp8",57408:"kp9",57409:"kpdecimal",57410:"kpdivide",57411:"kpmultiply",57412:"kpsubtract",57413:"kpadd",57414:"kpenter",57415:"kpequal",57416:"kpseparator",57417:"kpleft",57418:"kpright",57419:"kpup",57420:"kpdown",57421:"kppageup",57422:"kppagedown",57423:"kphome",57424:"kpend",57425:"kpinsert",57426:"kpdelete",57427:"kpbegin",57428:"mediaplay",57429:"mediapause",57430:"mediaplaypause",57431:"mediareverse",57432:"mediastop",57433:"mediafastforward",57434:"mediarewind",57435:"mediatracknext",57436:"mediatrackprevious",57437:"mediarecord",57438:"lowervolume",57439:"raisevolume",57440:"mutevolume",57441:"leftshift",57442:"leftcontrol",57443:"leftalt",57444:"leftsuper",57445:"lefthyper",57446:"leftmeta",57447:"rightshift",57448:"rightcontrol",57449:"rightalt",57450:"rightsuper",57451:"righthyper",57452:"rightmeta",57453:"isoLevel3Shift",57454:"isoLevel5Shift"},PQ=e=>e>=0&&e<=1114111&&!(e>=55296&&e<=57343),bE=e=>PQ(e)?String.fromCodePoint(e):"?";function $Q(e){return e===3?"release":e===2?"repeat":"press"}function Af(e){return{ctrl:!!(e&We.ctrl),shift:!!(e&We.shift),meta:!!(e&We.meta),option:!!(e&We.alt),super:!!(e&We.super),hyper:!!(e&We.hyper),capsLock:!!(e&We.capsLock),numLock:!!(e&We.numLock)}}var wy=e=>{let u=WQ.exec(e);if(!u)return null;let a=parseInt(u[1],10),l=u[2]?Math.max(0,parseInt(u[2],10)-1):0,r=u[3]?parseInt(u[3],10):1,E=u[4];if(!PQ(a))return null;let B;E&&(B=E.split(":").map(y=>bE(parseInt(y,10))).join(""));let c,Q;return a===32?(c="space",Q=!0):a===13?(c="return",Q=!0):ZQ[a]?(c=ZQ[a],Q=!1):a>=1&&a<=26?(c=String.fromCodePoint(a+96),Q=!1):(c=bE(a).toLowerCase(),Q=!0),Q&&!B&&(B=bE(a)),{name:c,...Af(l),eventType:$Q(r),sequence:e,raw:e,isKittyProtocol:!0,isPrintable:Q,text:B}},Gy=e=>{let u=Ny.exec(e);if(!u)return null;let a=parseInt(u[1],10),l=Math.max(0,parseInt(u[2],10)-1),r=parseInt(u[3],10),E=u[4],B=E==="~"?vy[a]:Ry[E];return B?{name:B,...Af(l),eventType:$Q(r),sequence:e,raw:e,isKittyProtocol:!0,isPrintable:!1}:null},My=(e="")=>{let u;my.isBuffer(e)?e[0]>127&&e[1]===void 0?(e[0]-=128,e="\x1B"+String(e)):e=String(e):e!==void 0&&typeof e!="string"?e=String(e):e||(e="");let a=wy(e);if(a)return a;let l=Gy(e);if(l)return l;if(WQ.test(e))return{name:"",ctrl:!1,meta:!1,shift:!1,option:!1,sequence:e,raw:e,isKittyProtocol:!0,isPrintable:!1};let r={name:"",ctrl:!1,meta:!1,shift:!1,option:!1,sequence:e,raw:e};if(r.sequence=r.sequence||e||r.name,e==="\r"||e==="\x1B\r")r.raw=void 0,r.name="return",r.option=e.length===2;else if(e===`
68
68
  `)r.name="enter";else if(e===" ")r.name="tab";else if(e==="\b"||e==="\x1B\b")r.name="backspace",r.meta=e.charAt(0)==="\x1B";else if(e===""||e==="\x1B")r.name="delete",r.meta=e.charAt(0)==="\x1B";else if(e==="\x1B"||e==="\x1B\x1B")r.name="escape",r.meta=e.length===2;else if(e===" "||e==="\x1B ")r.name="space",r.meta=e.length===2;else if(e.length===1&&e<="")r.name=String.fromCharCode(e.charCodeAt(0)+97-1),r.ctrl=!0;else if(e.length===1&&e>="0"&&e<="9")r.name="number";else if(e.length===1&&e>="a"&&e<="z")r.name=e;else if(e.length===1&&e>="A"&&e<="Z")r.name=e.toLowerCase(),r.shift=!0;else if(u=hy.exec(e))r.meta=!0,r.shift=/^[A-Z]$/.test(u[1]);else if(u=yy.exec(e)){let E=[...e];E[0]==="\x1B"&&E[1]==="\x1B"&&(r.option=!0);let B=[u[1],u[2],u[4],u[6]].filter(Boolean).join(""),c=(u[3]||u[5]||1)-1;r.ctrl=!!(c&4),r.meta=!!(c&10),r.shift=!!(c&1),r.code=B,r.name=jQ[B],r.shift=Sy(B)||r.shift,r.ctrl=Fy(B)||r.ctrl}return r},tf=My;var ef=aA(SA(),1);var Hy=()=>(0,ef.useContext)(Ro),bo=Hy;var Uy=(e,u={})=>{let{stdin:a,setRawMode:l,internal_exitOnCtrlC:r,internal_eventEmitter:E}=bo();(0,LE.useEffect)(()=>{if(u.isActive!==!1)return l(!0),()=>{l(!1)}},[u.isActive,l]),(0,LE.useEffect)(()=>{if(u.isActive===!1)return;let B=c=>{let Q=tf(c),y={upArrow:Q.name==="up",downArrow:Q.name==="down",leftArrow:Q.name==="left",rightArrow:Q.name==="right",pageDown:Q.name==="pagedown",pageUp:Q.name==="pageup",home:Q.name==="home",end:Q.name==="end",return:Q.name==="return",escape:Q.name==="escape",ctrl:Q.ctrl,shift:Q.shift,tab:Q.name==="tab",backspace:Q.name==="backspace",delete:Q.name==="delete",meta:Q.meta||Q.name==="escape"||Q.option,super:Q.super??!1,hyper:Q.hyper??!1,capsLock:Q.capsLock??!1,numLock:Q.numLock??!1,eventType:Q.eventType},N;Q.isKittyProtocol?Q.isPrintable?N=Q.text??Q.name:Q.ctrl&&Q.name.length===1?N=Q.name:N="":Q.ctrl?N=Q.name:N=Q.sequence,!Q.isKittyProtocol&&VQ.includes(Q.name)&&(N=""),N.startsWith("\x1B")&&(N=N.slice(1)),N.length===1&&typeof N[0]=="string"&&/[A-Z]/.test(N[0])&&(y.shift=!0),(!(N==="c"&&y.ctrl)||!r)&&fe.batchedUpdates(()=>{e(N,y)})};return E?.on("input",B),()=>{E?.removeListener("input",B)}},[u.isActive,a,r,e])},OE=Uy;var nf=aA(SA(),1);var xy=()=>(0,nf.useContext)(No),kE=xy;var Ty=aA(SA(),1);var by=aA(SA(),1);var JE=aA(SA(),1);var Ly=aA(SA(),1);var Oy=aA(SA(),1);var Lo=aA(SA(),1);import{pathToFileURL as Py}from"node:url";var ky=["[data-ink-approve]",'[data-ink-act="accept"]',"[data-accept-result][data-primary-action]"],Jy=['[data-act="request-changes"]','[data-act="revise"]','a.result-feedback-link[href="#request-changes"]'],Ky=['[data-act="retry"]','form[data-retry] button[type="submit"]','form[data-rebuild] button[type="submit"]'],KE=[{keys:"n",says:"new task",web:{kind:"go",href:"/tasks/new"},tui:"new"},{keys:"a",says:"Approve or Accept (presses the button on the page)",web:{kind:"press",selectors:ky},tui:"ink"},{keys:"b",says:"Send back (opens its note)",web:{kind:"press",selectors:Jy},tui:"send-back"},{keys:"r",says:"Retry (presses the button on the page)",web:{kind:"press",selectors:Ky},tui:"retry"},{keys:"/",says:"search",web:{kind:"search"},tui:{none:"The terminal has no search; press w to open the console."}},{keys:"g h",says:"go Home",web:{kind:"go",href:"/"},tui:"list"},{keys:"g t",says:"go to Tasks",web:{kind:"go",href:"/tasks"},tui:"list"},{keys:"?",says:"these shortcuts",web:{kind:"help"},tui:"help"}],uf=e=>e.split(" ").join(" then ");var PA=aA(SA(),1);var Yy=/[‪-‮⁦-⁩]/g;function _y(e){return e.replace(/(?:\x1b\[|\u009b)[0-?]*[ -/]*[@-~]/g,"").replace(/(?:\x1b\]|\u009d)[^\x07\x1b\u009c]*(?:\x07|\x1b\\|\u009c)?/g,"").replace(/\x1b[ -/]*[0-~]/g,"").replace(/\t/g," ").replace(/[\x00-\x1f\x7f-\x9f]/g,"").replace(new RegExp("\\p{Cf}","gu"),"").replace(Yy,"")}function af(e){return e.split(`
69
69
  `).map(_y).join(`
70
- `)}var YE=["you","working","done"],lf={you:"Waiting on you",working:"Working",done:"Done"};var X=aA(qa(),1);function at({children:e,...u}){return(0,X.jsx)(EA,{...u,children:af(e)})}var Xy={new:"new task",ink:"approve the plan (shows it in full first), or accept the result","send-back":"send the result back",retry:"retry",list:"back to the task list",help:"these keys"},Zy=["j / k or ↑ / ↓ move","Enter open the task",...KE.filter(e=>typeof e.tui=="string").map(e=>`${uf(e.keys).padEnd(17)}${Xy[e.tui]}`),"c accept the result","s stop the running attempt","1–9 answer the task's question","w open in the browser","Esc back","q quit"];function jy(e){let u=a=>a.group===null?YE.length:YE.indexOf(a.group);return e.map((a,l)=>({row:a,index:l})).sort((a,l)=>u(a.row)-u(l.row)||a.index-l.index).map(a=>a.row)}var Vy=e=>e.group===null?"Other":lf[e.group],Wy=e=>[...e].filter(u=>u>=" "&&u!=="").join("");function Ef({source:e,session:u,onExit:a,pollMs:l=1e3}){let{exit:r}=kE(),[E,B]=(0,PA.useState)(null),[c,Q]=(0,PA.useState)(null),[y,N]=(0,PA.useState)(0),[w,h]=(0,PA.useState)({kind:"main"}),[v,H]=(0,PA.useState)(null),[L,M]=(0,PA.useState)(null),[W,FA]=(0,PA.useState)([]),NA=(0,PA.useRef)(null),gA=(0,PA.useRef)(0),HA=(0,PA.useRef)(!1),vA=jy(E?.rows??[]),QA=vA[Math.min(y,Math.max(vA.length-1,0))],XA=(0,PA.useCallback)(async()=>{let T=await e.snapshot();B(T),T.rows.length===0&&Q(await e.setup())},[e]);(0,PA.useEffect)(()=>{XA()},[XA]),(0,PA.useEffect)(()=>{let T=setInterval(()=>{gA.current+=1;let _=e.changed();(_!==NA.current||gA.current%5===0)&&(NA.current=_,XA())},l);return()=>clearInterval(T)},[e,XA,l]);let St=QA?.id??null;(0,PA.useEffect)(()=>{if(St===null){FA([]);return}FA(e.live(St));let T=setInterval(()=>FA(e.live(St)),l);return()=>clearInterval(T)},[e,St,l]);let Ut=T=>{HA.current||(HA.current=!0,r(),a?.(T))},zt=(0,PA.useRef)(!1),TA=async T=>{M("Working…");try{let _=await e.act(T);return _.ok?(H(_.message),await XA(),T.kind==="new"&&u.shouldAskBackground()&&h({kind:"background"}),!0):(_.needsPassword?h({kind:"password",message:_.message}):H(_.message),!1)}finally{M(null)}},Ft=async(T,_)=>{let P=await e.task(T);P===null?H("That task can't be read now."):_(P)},xt=T=>Ft(T,_=>{_.plan===null?H("No plan is waiting for your approval on this task."):h({kind:"approve",task:_})}),Ot=T=>Ft(T,_=>{_.plan!==null?h({kind:"approve",task:_}):_.hasResult?TA({kind:"complete",id:T}):H("No plan or result is waiting for you on this task.")}),Tt=(T,_)=>{if(_===null){H("Nothing is running on this task.");return}TA({kind:"stop",id:T,runId:_})},qt=(T,_)=>{let P=T.decisions[0],V=P?.options[_];P===void 0||V===void 0||TA({kind:"answer",decision:P.id,option:V.id}).then(At=>{At&&h({kind:"main"})})},GA=()=>{M("Opening the browser…"),u.openBrowser().then(T=>{H(T),M(null)})},It=()=>{u.owned()?h({kind:"quit"}):Ut("leave")},de=(T,_,P)=>{if(zt.current&&(zt.current=!1,T==="h"||T==="t"))return h({kind:"main"}),!0;if(T==="g")return zt.current=!0,!0;if(T==="/"){let V=KE.find(At=>At.keys==="/").tui;return H(typeof V=="string"?null:V.none),!0}return T==="n"?(h({kind:"compose",purpose:"new",id:null,text:"",project:0}),!0):T==="w"?(GA(),!0):T==="?"?(h({kind:"help"}),!0):T==="q"?(It(),!0):_===null?!1:T==="a"?(Ot(_),!0):T==="s"?(Tt(_,P),!0):T==="b"?(h({kind:"compose",purpose:"revise",id:_,text:"",project:0}),!0):T==="r"?(TA({kind:"retry",id:_}),!0):T==="c"?(TA({kind:"complete",id:_}),!0):!1},pe=(T,_)=>{if(L===null){if(_.ctrl&&T==="c"){It();return}switch(w.kind){case"main":{if(T==="j"||_.downArrow){N(P=>Math.min(P+1,Math.max(vA.length-1,0)));return}if(T==="k"||_.upArrow){N(P=>Math.max(P-1,0));return}if(_.return&&QA!==void 0){Ft(QA.id,P=>h({kind:"task",task:P}));return}if(_.escape){H(null);return}de(T,QA?.id??null,QA?.liveRunId??null);return}case"task":{if(_.escape||_.leftArrow){h({kind:"main"});return}if(/^[1-9]$/.test(T)){qt(w.task,Number(T)-1);return}de(T,w.task.id,w.task.liveRunId);return}case"approve":{if(_.escape||T==="n"){h({kind:"main"}),H("Nothing approved.");return}if(T==="y"){let{task:P}=w;TA({kind:"approve",id:P.id,digest:P.plan.digest}).then(V=>{V&&h({kind:"main"})})}return}case"build":{if(_.escape){h({kind:"main"}),H("Nothing started.");return}if(T==="p"){let{text:P,repo:V}=w;h({kind:"main"}),TA({kind:"new",text:P,repo:V});return}if(_.return){let{preview:P,digest:V}=w.terms;TA({kind:"build",preview:P,digest:V}).then(At=>{At&&h({kind:"main"})})}return}case"compose":{if(_.escape){h({kind:"main"});return}let P=E?.projects??[];if(_.tab&&w.purpose==="new"&&P.length>1){h({...w,project:(w.project+1)%P.length});return}if(_.backspace||_.delete){h({...w,text:w.text.slice(0,-1)});return}if(_.return){let At=w.text.trim();if(At==="")return;let Z=w.purpose==="new"?{kind:"new",text:At,repo:P[w.project]??null}:{kind:"revise",id:w.id,text:At};if(h({kind:"main"}),Z.kind==="new"){M("Reading it…"),e.fastTerms(At,Z.repo).catch(()=>null).then(uA=>{M(null),uA===null?TA(Z):h({kind:"build",terms:uA,text:At,repo:Z.repo})});return}TA(Z);return}let V=Wy(T);V!==""&&h({...w,text:w.text+V});return}case"quit":{T==="s"?Ut("stop"):T==="b"?Ut("keep"):_.escape&&h({kind:"main"});return}case"background":{let P=T==="y"||T==="Y"||_.return;if(!P&&T!=="n"&&T!=="N")return;h({kind:"main"}),M(P?"Moving Toolroll to the background…":"Saving…"),u.answerBackground(P).then(V=>{H(V),M(null)});return}default:(_.escape||_.return||T==="q")&&h({kind:"main"})}}},Xt=(0,PA.useRef)(pe);Xt.current=pe,OE((0,PA.useCallback)((T,_)=>Xt.current(T,_),[]));let me=L??v;return(0,X.jsxs)(RA,{flexDirection:"column",children:[(0,X.jsxs)(RA,{justifyContent:"space-between",children:[(0,X.jsx)(EA,{bold:!0,children:"Toolroll"}),E?.needsYou?(0,X.jsx)(at,{color:"yellow",children:E.needsYou}):(0,X.jsx)(EA,{dimColor:!0,children:"Nothing needs you"}),E?.learning?(0,X.jsx)(at,{dimColor:!0,children:E.learning}):null]}),$A(),me===null?null:(0,X.jsx)(at,{children:me}),(0,X.jsx)(EA,{dimColor:!0,children:Zt()})]});function Zt(){switch(w.kind){case"main":return vA.length===0?"n new task · w browser · ? help · q quit":"j/k move · Enter open · n new · a approve/accept · b send back · r retry · s stop · w browser · ? help · q quit";case"task":return"a approve/accept · b send back · r retry · s stop · 1–9 answer · w browser · Esc back";case"approve":return"y approve · Esc cancel";case"build":return"Enter Build · p plan it first · Esc cancel";case"compose":return w.purpose==="new"&&(E?.projects.length??0)>1?"Enter send · Tab project · Esc cancel":"Enter send · Esc cancel";case"quit":return"s stop · b background · Esc cancel";case"background":return"y yes · n no";default:return"Esc back"}}function $A(){switch(w.kind){case"help":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Keys"}),Zy.map(T=>(0,X.jsx)(EA,{children:T},T))]});case"password":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"This needs your password"}),(0,X.jsx)(at,{children:w.message}),E?.passwordSet?(0,X.jsx)(EA,{children:"Sign in in the browser, then do it there."}):(0,X.jsx)(EA,{children:"Set a password: run `toolroll password set`"})]});case"quit":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Quit Toolroll?"}),(0,X.jsx)(EA,{children:"s Stop agents and quit (running work is saved)"}),(0,X.jsx)(EA,{children:"b Keep running in the background"})]});case"background":return(0,X.jsx)(RA,{borderStyle:"single",paddingX:1,children:(0,X.jsx)(EA,{bold:!0,children:"Keep Toolroll running in the background? (Y/n)"})});case"build":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Build approves exactly this:"}),w.terms.terms.map((T,_)=>(0,X.jsx)(at,{children:`${T.label}: ${T.words}`},_))]});case"compose":{let T=E?.projects[w.project]??null;return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:w.purpose==="new"?"New task":"What should change?"}),w.purpose==="new"&&T!==null?(0,X.jsx)(at,{dimColor:!0,children:`In ${T}`}):null,(0,X.jsxs)(EA,{children:[(0,X.jsx)(at,{children:w.text}),(0,X.jsx)(EA,{inverse:!0,children:" "})]})]})}case"approve":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:`Approve this plan? ${w.task.title}`}),w.task.plan.lines.map((T,_)=>(0,X.jsx)(at,{children:T},_))]});case"task":{let{task:T}=w,_=T.decisions[0];return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:T.title}),(0,X.jsx)(at,{children:`${T.status}${T.detail===""?"":` · ${T.detail}`}`}),T.plan===null?null:(0,X.jsx)(EA,{children:"A plan waits for your approval (a)."}),(T.ready?.length??0)===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:T.ready.map((P,V)=>(0,X.jsx)(at,{...V===0?{bold:!0}:{},children:P},V))}),_===void 0?null:(0,X.jsxs)(RA,{flexDirection:"column",marginTop:1,children:[(0,X.jsx)(at,{bold:!0,children:_.question}),_.options.map((P,V)=>(0,X.jsx)(at,{children:`${V+1} ${P.label}`},P.id))]}),W.length===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:W.slice(-12).map((P,V)=>(0,X.jsx)(at,{dimColor:!0,children:P},V))})]})}default:return E===null?(0,X.jsx)(EA,{dimColor:!0,children:"Loading…"}):vA.length===0?(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Get to your first result"}),(c?.lines??["No tasks yet. Press n to write one."]).map((T,_)=>(0,X.jsx)(at,{children:T},_))]}):(0,X.jsxs)(RA,{children:[(0,X.jsx)(RA,{flexDirection:"column",width:"45%",borderStyle:"single",paddingX:1,children:vA.map((T,_)=>(0,X.jsxs)(PA.Fragment,{children:[_===0||vA[_-1].group!==T.group?(0,X.jsx)(EA,{bold:!0,children:Vy(T)}):null,(0,X.jsxs)(EA,{inverse:T===QA,wrap:"truncate-end",children:[T===QA?"> ":" ",(0,X.jsx)(at,{children:T.title})," ",(0,X.jsx)(at,{...T.needsYou?{color:"yellow"}:{dimColor:!0},children:T.status})]})]},T.id))}),(0,X.jsx)(RA,{flexDirection:"column",flexGrow:1,borderStyle:"single",paddingX:1,children:QA===void 0?(0,X.jsx)(EA,{dimColor:!0,children:"Nothing selected"}):(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(at,{bold:!0,wrap:"truncate-end",children:QA.title}),(0,X.jsx)(at,{children:`${QA.status}${QA.detail===""?"":` · ${QA.detail}`}`}),W.length===0?(0,X.jsx)(EA,{dimColor:!0,children:QA.liveRunId===null?"No agent is working on it now.":"Waiting for output…"}):W.slice(-12).map((T,_)=>(0,X.jsx)(at,{dimColor:!0,wrap:"truncate-end",children:T},_))]})})]})}}}function Bf(){let e=[{id:"t3",title:"Sample: upgrade the test runner",status:"Needs you",detail:"Plan ready to approve",needsYou:!0,liveRunId:null,repo:null,group:"you"},{id:"t1",title:"Sample: fix the login redirect",status:"Running",detail:"Sample task (not real)",needsYou:!1,liveRunId:1,repo:null,group:"working"},{id:"t2",title:"Sample: add CSV export",status:"Complete",detail:"Sample task (not real)",needsYou:!1,liveRunId:null,repo:null,group:"done"}];return{fastTerms:async()=>null,snapshot:async()=>({rows:e,needsYou:"1 task needs you: Sample: upgrade the test runner",projects:[],passwordSet:!1}),task:async u=>{let a=e.find(r=>r.id===u);return a===void 0?null:{id:u,title:a.title,status:a.status,detail:a.detail,liveRunId:a.liveRunId,hasResult:a.status==="Complete",decisions:[],plan:a.needsYou?{digest:"sample",lines:["Goal: upgrade the test runner (sample)"]}:null}},live:u=>u==="t1"?["Editing src/auth.ts (sample)"]:[],changed:()=>0,act:async()=>({ok:!1,message:"This is a sample: nothing changes.",needsPassword:!1}),setup:async()=>null}}function sf(){return{owned:()=>!1,openBrowser:async()=>"This is a sample.",shouldAskBackground:()=>!1,answerBackground:async()=>"This is a sample."}}var If=aA(qa(),1);async function $y(e=Bf(),u=sf()){if(!process.stdin.isTTY||!process.stdout.isTTY)throw new Error("The terminal UI needs an interactive terminal.");let a="leave";return await TE((0,If.jsx)(Ef,{source:e,session:u,onExit:r=>{a=r}}),{exitOnCtrlC:!1}).waitUntilExit(),a}var Cf=process.argv[1];Cf!==void 0&&import.meta.url===Py(Cf).href&&$y().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
70
+ `)}var YE=["you","working","done"],lf={you:"Waiting on you",working:"Working",done:"Done"};var X=aA(qa(),1);function at({children:e,...u}){return(0,X.jsx)(EA,{...u,children:af(e)})}var Xy={new:"new task",ink:"approve the plan (shows it in full first), or accept the result","send-back":"send the result back",retry:"retry",list:"back to the task list",help:"these keys"},Zy=["j / k or ↑ / ↓ move","Enter open the task",...KE.filter(e=>typeof e.tui=="string").map(e=>`${uf(e.keys).padEnd(17)}${Xy[e.tui]}`),"c accept the result","s stop the running attempt","1–9 answer the task's question","w open in the browser","Esc back","q quit"];function jy(e){let u=a=>a.group===null?YE.length:YE.indexOf(a.group);return e.map((a,l)=>({row:a,index:l})).sort((a,l)=>u(a.row)-u(l.row)||a.index-l.index).map(a=>a.row)}var Vy=e=>e.group===null?"Other":lf[e.group],Wy=e=>[...e].filter(u=>u>=" "&&u!=="").join("");function Ef({source:e,session:u,onExit:a,pollMs:l=1e3}){let{exit:r}=kE(),[E,B]=(0,PA.useState)(null),[c,Q]=(0,PA.useState)(null),[y,N]=(0,PA.useState)(0),[w,h]=(0,PA.useState)({kind:"main"}),[v,H]=(0,PA.useState)(null),[L,M]=(0,PA.useState)(null),[W,FA]=(0,PA.useState)([]),NA=(0,PA.useRef)(null),gA=(0,PA.useRef)(0),HA=(0,PA.useRef)(!1),vA=jy(E?.rows??[]),QA=vA[Math.min(y,Math.max(vA.length-1,0))],XA=(0,PA.useCallback)(async()=>{let T=await e.snapshot();B(T),T.rows.length===0&&Q(await e.setup())},[e]);(0,PA.useEffect)(()=>{XA()},[XA]),(0,PA.useEffect)(()=>{let T=setInterval(()=>{gA.current+=1;let _=e.changed();(_!==NA.current||gA.current%5===0)&&(NA.current=_,XA())},l);return()=>clearInterval(T)},[e,XA,l]);let St=QA?.id??null;(0,PA.useEffect)(()=>{if(St===null){FA([]);return}FA(e.live(St));let T=setInterval(()=>FA(e.live(St)),l);return()=>clearInterval(T)},[e,St,l]);let Ut=T=>{HA.current||(HA.current=!0,r(),a?.(T))},zt=(0,PA.useRef)(!1),TA=async T=>{M("Working…");try{let _=await e.act(T);return _.ok?(H(_.message),await XA(),T.kind==="new"&&u.shouldAskBackground()&&h({kind:"background"}),!0):(_.needsPassword?h({kind:"password",message:_.message}):H(_.message),!1)}finally{M(null)}},Ft=async(T,_)=>{let P=await e.task(T);P===null?H("That task can't be read now."):_(P)},xt=T=>Ft(T,_=>{_.plan===null?H("No plan is waiting for your approval on this task."):h({kind:"approve",task:_})}),Ot=T=>Ft(T,_=>{_.plan!==null?h({kind:"approve",task:_}):_.hasResult?TA({kind:"complete",id:T}):H("No plan or result is waiting for you on this task.")}),Tt=(T,_)=>{if(_===null){H("Nothing is running on this task.");return}TA({kind:"stop",id:T,runId:_})},qt=(T,_)=>{let P=T.decisions[0],V=P?.options[_];P===void 0||V===void 0||TA({kind:"answer",decision:P.id,option:V.id}).then(At=>{At&&h({kind:"main"})})},GA=()=>{M("Opening the browser…"),u.openBrowser().then(T=>{H(T),M(null)})},It=()=>{u.owned()?h({kind:"quit"}):Ut("leave")},de=(T,_,P)=>{if(zt.current&&(zt.current=!1,T==="h"||T==="t"))return h({kind:"main"}),!0;if(T==="g")return zt.current=!0,!0;if(T==="/"){let V=KE.find(At=>At.keys==="/").tui;return H(typeof V=="string"?null:V.none),!0}return T==="n"?(h({kind:"compose",purpose:"new",id:null,text:"",project:0}),!0):T==="w"?(GA(),!0):T==="?"?(h({kind:"help"}),!0):T==="q"?(It(),!0):_===null?!1:T==="a"?(Ot(_),!0):T==="s"?(Tt(_,P),!0):T==="b"?(h({kind:"compose",purpose:"revise",id:_,text:"",project:0}),!0):T==="r"?(TA({kind:"retry",id:_}),!0):T==="c"?(TA({kind:"complete",id:_}),!0):!1},pe=(T,_)=>{if(L===null){if(_.ctrl&&T==="c"){It();return}switch(w.kind){case"main":{if(T==="j"||_.downArrow){N(P=>Math.min(P+1,Math.max(vA.length-1,0)));return}if(T==="k"||_.upArrow){N(P=>Math.max(P-1,0));return}if(_.return&&QA!==void 0){Ft(QA.id,P=>h({kind:"task",task:P}));return}if(_.escape){H(null);return}de(T,QA?.id??null,QA?.liveRunId??null);return}case"task":{if(_.escape||_.leftArrow){h({kind:"main"});return}if(/^[1-9]$/.test(T)){qt(w.task,Number(T)-1);return}de(T,w.task.id,w.task.liveRunId);return}case"approve":{if(_.escape||T==="n"){h({kind:"main"}),H("Nothing approved.");return}if(T==="y"){let{task:P}=w;TA({kind:"approve",id:P.id,digest:P.plan.digest}).then(V=>{V&&h({kind:"main"})})}return}case"build":{if(_.escape){h({kind:"main"}),H("Nothing started.");return}if(T==="p"){let{text:P,repo:V}=w;h({kind:"main"}),TA({kind:"new",text:P,repo:V});return}if(_.return){let{preview:P,digest:V}=w.terms;TA({kind:"build",preview:P,digest:V}).then(At=>{At&&h({kind:"main"})})}return}case"compose":{if(_.escape){h({kind:"main"});return}let P=E?.projects??[];if(_.tab&&w.purpose==="new"&&P.length>1){h({...w,project:(w.project+1)%P.length});return}if(_.backspace||_.delete){h({...w,text:w.text.slice(0,-1)});return}if(_.return){let At=w.text.trim();if(At==="")return;let Z=w.purpose==="new"?{kind:"new",text:At,repo:P[w.project]??null}:{kind:"revise",id:w.id,text:At};if(h({kind:"main"}),Z.kind==="new"){M("Reading it…"),e.fastTerms(At,Z.repo).catch(()=>null).then(uA=>{M(null),uA===null?TA(Z):h({kind:"build",terms:uA,text:At,repo:Z.repo})});return}TA(Z);return}let V=Wy(T);V!==""&&h({...w,text:w.text+V});return}case"quit":{T==="s"?Ut("stop"):T==="b"?Ut("keep"):_.escape&&h({kind:"main"});return}case"background":{let P=T==="y"||T==="Y"||_.return;if(!P&&T!=="n"&&T!=="N")return;h({kind:"main"}),M(P?"Moving Toolroll to the background…":"Saving…"),u.answerBackground(P).then(V=>{H(V),M(null)});return}default:(_.escape||_.return||T==="q")&&h({kind:"main"})}}},Xt=(0,PA.useRef)(pe);Xt.current=pe,OE((0,PA.useCallback)((T,_)=>Xt.current(T,_),[]));let me=L??v;return(0,X.jsxs)(RA,{flexDirection:"column",children:[(0,X.jsxs)(RA,{justifyContent:"space-between",children:[(0,X.jsx)(EA,{bold:!0,children:"Toolroll"}),E?.needsYou?(0,X.jsx)(at,{bold:!0,children:E.needsYou}):(0,X.jsx)(EA,{dimColor:!0,children:"Nothing needs you"}),E?.learning?(0,X.jsx)(at,{dimColor:!0,children:E.learning}):null]}),$A(),me===null?null:(0,X.jsx)(at,{children:me}),(0,X.jsx)(EA,{dimColor:!0,children:Zt()})]});function Zt(){switch(w.kind){case"main":return vA.length===0?"n new task · w browser · ? help · q quit":"j/k move · Enter open · n new · a approve/accept · b send back · r retry · s stop · w browser · ? help · q quit";case"task":return"a approve/accept · b send back · r retry · s stop · 1–9 answer · w browser · Esc back";case"approve":return"y approve · Esc cancel";case"build":return"Enter Build · p plan it first · Esc cancel";case"compose":return w.purpose==="new"&&(E?.projects.length??0)>1?"Enter send · Tab project · Esc cancel":"Enter send · Esc cancel";case"quit":return"s stop · b background · Esc cancel";case"background":return"y yes · n no";default:return"Esc back"}}function $A(){switch(w.kind){case"help":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Keys"}),Zy.map(T=>(0,X.jsx)(EA,{children:T},T))]});case"password":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"This needs your password"}),(0,X.jsx)(at,{children:w.message}),E?.passwordSet?(0,X.jsx)(EA,{children:"Sign in in the browser, then do it there."}):(0,X.jsx)(EA,{children:"Set a password: run `toolroll password set`"})]});case"quit":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Quit Toolroll?"}),(0,X.jsx)(EA,{children:"s Stop agents and quit (running work is saved)"}),(0,X.jsx)(EA,{children:"b Keep running in the background"})]});case"background":return(0,X.jsx)(RA,{borderStyle:"single",paddingX:1,children:(0,X.jsx)(EA,{bold:!0,children:"Keep Toolroll running in the background? (Y/n)"})});case"build":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Build approves exactly this:"}),w.terms.terms.map((T,_)=>(0,X.jsx)(at,{children:`${T.label}: ${T.words}`},_))]});case"compose":{let T=E?.projects[w.project]??null;return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:w.purpose==="new"?"New task":"What should change?"}),w.purpose==="new"&&T!==null?(0,X.jsx)(at,{dimColor:!0,children:`In ${T}`}):null,(0,X.jsxs)(EA,{children:[(0,X.jsx)(at,{children:w.text}),(0,X.jsx)(EA,{inverse:!0,children:" "})]})]})}case"approve":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:`Approve this plan? ${w.task.title}`}),w.task.plan.lines.map((T,_)=>(0,X.jsx)(at,{children:T},_))]});case"task":{let{task:T}=w,_=T.decisions[0];return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:T.title}),(0,X.jsx)(at,{children:`${T.status}${T.detail===""?"":` · ${T.detail}`}`}),T.plan===null?null:(0,X.jsx)(EA,{children:"A plan waits for your approval (a)."}),(T.ready?.length??0)===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:T.ready.map((P,V)=>(0,X.jsx)(at,{...V===0?{bold:!0}:{},children:P},V))}),_===void 0?null:(0,X.jsxs)(RA,{flexDirection:"column",marginTop:1,children:[(0,X.jsx)(at,{bold:!0,children:_.question}),_.options.map((P,V)=>(0,X.jsx)(at,{children:`${V+1} ${P.label}`},P.id))]}),W.length===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:W.slice(-12).map((P,V)=>(0,X.jsx)(at,{dimColor:!0,children:P},V))})]})}default:return E===null?(0,X.jsx)(EA,{dimColor:!0,children:"Loading…"}):vA.length===0?(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Get to your first result"}),(c?.lines??["No tasks yet. Press n to write one."]).map((T,_)=>(0,X.jsx)(at,{children:T},_))]}):(0,X.jsxs)(RA,{children:[(0,X.jsx)(RA,{flexDirection:"column",width:"45%",borderStyle:"single",paddingX:1,children:vA.map((T,_)=>(0,X.jsxs)(PA.Fragment,{children:[_===0||vA[_-1].group!==T.group?(0,X.jsx)(EA,{bold:!0,children:Vy(T)}):null,(0,X.jsxs)(EA,{inverse:T===QA,wrap:"truncate-end",children:[T===QA?"> ":" ",(0,X.jsx)(at,{children:T.title})," ",(0,X.jsx)(at,{...T.needsYou?{bold:!0}:{dimColor:!0},children:T.status})]})]},T.id))}),(0,X.jsx)(RA,{flexDirection:"column",flexGrow:1,borderStyle:"single",paddingX:1,children:QA===void 0?(0,X.jsx)(EA,{dimColor:!0,children:"Nothing selected"}):(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(at,{bold:!0,wrap:"truncate-end",children:QA.title}),(0,X.jsx)(at,{children:`${QA.status}${QA.detail===""?"":` · ${QA.detail}`}`}),W.length===0?(0,X.jsx)(EA,{dimColor:!0,children:QA.liveRunId===null?"No agent is working on it now.":"Waiting for output…"}):W.slice(-12).map((T,_)=>(0,X.jsx)(at,{dimColor:!0,wrap:"truncate-end",children:T},_))]})})]})}}}function Bf(){let e=[{id:"t3",title:"Sample: upgrade the test runner",status:"Needs you",detail:"Plan ready to approve",needsYou:!0,liveRunId:null,repo:null,group:"you"},{id:"t1",title:"Sample: fix the login redirect",status:"Running",detail:"Sample task (not real)",needsYou:!1,liveRunId:1,repo:null,group:"working"},{id:"t2",title:"Sample: add CSV export",status:"Complete",detail:"Sample task (not real)",needsYou:!1,liveRunId:null,repo:null,group:"done"}];return{fastTerms:async()=>null,snapshot:async()=>({rows:e,needsYou:"1 task needs you: Sample: upgrade the test runner",projects:[],passwordSet:!1}),task:async u=>{let a=e.find(r=>r.id===u);return a===void 0?null:{id:u,title:a.title,status:a.status,detail:a.detail,liveRunId:a.liveRunId,hasResult:a.status==="Complete",decisions:[],plan:a.needsYou?{digest:"sample",lines:["Goal: upgrade the test runner (sample)"]}:null}},live:u=>u==="t1"?["Editing src/auth.ts (sample)"]:[],changed:()=>0,act:async()=>({ok:!1,message:"This is a sample: nothing changes.",needsPassword:!1}),setup:async()=>null}}function sf(){return{owned:()=>!1,openBrowser:async()=>"This is a sample.",shouldAskBackground:()=>!1,answerBackground:async()=>"This is a sample."}}var If=aA(qa(),1);async function $y(e=Bf(),u=sf()){if(!process.stdin.isTTY||!process.stdout.isTTY)throw new Error("The terminal UI needs an interactive terminal.");let a="leave";return await TE((0,If.jsx)(Ef,{source:e,session:u,onExit:r=>{a=r}}),{exitOnCtrlC:!1}).waitUntilExit(),a}var Cf=process.argv[1];Cf!==void 0&&import.meta.url===Py(Cf).href&&$y().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
71
71
  `),process.exitCode=1});export{$y as startTui};
72
72
  /*! Bundled license information:
73
73
 
@@ -314,3 +314,72 @@ export type BuildProgress = {
314
314
  export declare function buildProgressOf(steps: readonly BuildStep[] | null | undefined): BuildProgress | null;
315
315
  /** Earlier attempts that stopped before the one now running, in one quiet line. */
316
316
  export declare function earlierAttemptsWords(count: number): string;
317
+ /** One row of a task page's steps (docs/plans/task-page-steps.md): the step's label, how its marker reads, one plain
318
+ * sentence and a time, whether it has something to open, and whether it starts open. `mark`: done (an ink disc with
319
+ * a check), needs (waits on this person: ink), live (something runs now: blue), wait (current, waiting on nothing
320
+ * this person does), stuck (stopped there: vermilion), skipped (a dashed ring), todo (not reached: grey). */
321
+ export type StepRow = {
322
+ key: "plan" | "approve" | "build" | "checks" | "ready";
323
+ label: string;
324
+ state: "done" | "current" | "stuck" | "skipped" | "todo";
325
+ mark: "done" | "needs" | "live" | "wait" | "stuck" | "skipped" | "todo";
326
+ sentence: string;
327
+ /** When the step happened (ISO), shown as a clock time; null when nothing recorded says. */
328
+ at: string | null;
329
+ needsYou: boolean;
330
+ /** It holds something to read or do: a finished row opens on press, the current one is open. */
331
+ opens: boolean;
332
+ open: boolean;
333
+ };
334
+ /** What `stepRowsOf` reads: facts the task page's view already carries. */
335
+ export type StepRowFacts = {
336
+ journey?: readonly {
337
+ key: StepRow["key"];
338
+ label: string;
339
+ state: StepRow["state"];
340
+ needsYou?: true;
341
+ say?: string;
342
+ }[] | null;
343
+ scout: boolean;
344
+ status: {
345
+ status: {
346
+ headline: string;
347
+ };
348
+ attempts: readonly unknown[];
349
+ } | null;
350
+ /** Builds that stopped before this one: with more than one version, the Build row's history. */
351
+ earlier?: {
352
+ attempts: readonly unknown[];
353
+ } | null;
354
+ approved?: {
355
+ by: string;
356
+ at: string;
357
+ automatic: boolean;
358
+ rule: string | null;
359
+ } | null;
360
+ approve?: {
361
+ heading: string;
362
+ } | null;
363
+ approval?: string;
364
+ progress?: {
365
+ line: string;
366
+ } | null;
367
+ plan?: {
368
+ lead: string | null;
369
+ by: string | null;
370
+ at: string | null;
371
+ planning: boolean;
372
+ updating?: boolean;
373
+ } | null;
374
+ result?: {
375
+ selected: unknown;
376
+ } | null;
377
+ thread?: readonly {
378
+ kind: string;
379
+ at: string;
380
+ }[];
381
+ release?: unknown;
382
+ };
383
+ /** The task page's rows, from its road (first-run.ts taskRoad) and the facts beside it. Null when the task has no road
384
+ * (cancelled, or no status): the page keeps its status block then. Pure: no clock, no store. */
385
+ export declare function stepRowsOf(view: StepRowFacts): StepRow[] | null;
@@ -624,3 +624,106 @@ export function buildProgressOf(steps) {
624
624
  export function earlierAttemptsWords(count) {
625
625
  return `${count} earlier attempt${count === 1 ? "" : "s"} stopped`;
626
626
  }
627
+ /** The task page's rows, from its road (first-run.ts taskRoad) and the facts beside it. Null when the task has no road
628
+ * (cancelled, or no status): the page keeps its status block then. Pure: no clock, no store. */
629
+ export function stepRowsOf(view) {
630
+ const road = view.journey ?? null;
631
+ if (road === null || road.length === 0)
632
+ return null;
633
+ const headline = view.status?.status.headline ?? null;
634
+ const runs = headline === "Planning" || headline === "Building";
635
+ const approvalOpen = view.approve != null || road.some(step => step.key === "approve" && step.state === "current");
636
+ const hasResult = view.result != null;
637
+ // Earlier versions or stopped builds: the Build row holds them, and opens for them before the next build starts.
638
+ const hasHistory = (view.status?.attempts.length ?? 0) > 1 || (view.earlier?.attempts.length ?? 0) > 0;
639
+ const lastResult = [...(view.thread ?? [])].filter(one => one.kind === "result").map(one => one.at).sort().at(-1) ?? null;
640
+ const checksStuck = road.some(step => step.key === "checks" && step.state === "stuck");
641
+ // A step stopped there, in a word (its box under it says why).
642
+ const stoppedWord = headline === "Failed" ? "Failed" : headline === "Stopped" ? "Stopped" : headline === "Waiting" || headline === "Queued" ? "Waiting" : "Stopped here";
643
+ const words = (step) => {
644
+ const here = step.state === "current" || step.state === "stuck";
645
+ switch (step.key) {
646
+ case "plan":
647
+ if (step.state === "done" || step.state === "skipped") {
648
+ const by = view.plan?.by ?? null;
649
+ if (approvalOpen)
650
+ return { sentence: by === null ? "Shown below" : `Written by ${by} · shown below`, at: view.plan?.at ?? null };
651
+ return { sentence: view.plan?.lead ?? (by === null ? "Plan written" : `Written by ${by}`), at: view.plan?.at ?? null };
652
+ }
653
+ // Stopped at Plan with a plan already written (a held task whose plan waits for approval): it says it stopped, never "No plan yet".
654
+ if (here)
655
+ return { sentence: view.plan?.updating === true ? "Updating the plan" : view.plan?.planning === true || headline === "Planning" ? "Writing the plan" : step.needsYou ? ""
656
+ : step.state === "stuck" && view.approve != null ? stoppedWord : "No plan yet", at: null };
657
+ return { sentence: "A plan for you to approve", at: null };
658
+ case "approve":
659
+ if (step.state === "done") {
660
+ const by = view.approved ?? null;
661
+ return { sentence: by === null ? "Approved" : by.automatic ? "Approved automatically" : `Approved by ${by.by}`, at: by?.at ?? null };
662
+ }
663
+ if (step.state === "skipped")
664
+ return { sentence: "No approval was asked for", at: null };
665
+ if (here)
666
+ return { sentence: step.needsYou ? "" : "Waiting for approval", at: null };
667
+ return { sentence: view.scout ? "You approve before it starts" : "You approve before anything is built", at: null };
668
+ case "build":
669
+ if (step.state === "done")
670
+ return { sentence: view.scout ? "The investigation finished" : "The build finished", at: lastResult };
671
+ if (step.state === "skipped")
672
+ return { sentence: "Didn't run", at: null };
673
+ // The open row's box says the rest: its head says only how the step stands.
674
+ if (here)
675
+ return { sentence: step.needsYou ? "" : runs ? "Working now" : step.state === "stuck" ? stoppedWord : "Waiting to start", at: null };
676
+ return { sentence: `${approvalOpen ? "Starts when you approve" : "Starts after approval"}${hasHistory ? ". Earlier attempts are here." : ""}`, at: null };
677
+ case "checks": {
678
+ const said = step.say ?? null;
679
+ if (step.state === "done")
680
+ return { sentence: "Project checks passed", at: null };
681
+ if (step.state === "skipped")
682
+ return { sentence: said === "Checks not run" ? "The checks didn't run" : said === "Checks at release" ? "Checked at release" : "No checks were recorded", at: null };
683
+ if (step.state === "stuck")
684
+ return { sentence: step.needsYou && said === null ? "" : said === "Checks unreadable" ? "The checks couldn't be read" : said === "Checks failed" ? "The checks failed" : stoppedWord, at: null };
685
+ if (step.state === "current")
686
+ return { sentence: said === "Checks running" ? "The checks are running" : step.needsYou ? "" : "Waiting for the checks", at: null };
687
+ return { sentence: "The project's checks run on the result", at: null };
688
+ }
689
+ case "ready":
690
+ if (step.state === "done")
691
+ return { sentence: headline === "Complete" ? "Finished" : view.scout ? "Report ready" : "Ready", at: lastResult };
692
+ if (here)
693
+ return { sentence: step.needsYou ? "" : step.state === "stuck" ? stoppedWord : "Waiting", at: null };
694
+ if (hasResult)
695
+ return { sentence: checksStuck ? "Not ready: the checks didn't pass. The result is here." : "Not ready yet. The result so far is here.", at: null };
696
+ return { sentence: view.scout ? "A report for you to read" : "A result for you to review", at: null };
697
+ }
698
+ };
699
+ const rows = road.map((step) => {
700
+ const said = words(step);
701
+ const here = step.state === "current" || step.state === "stuck";
702
+ const needsYou = step.needsYou === true && here;
703
+ const mark = step.state === "done" ? "done" : step.state === "skipped" ? "skipped" : step.state === "todo" ? "todo"
704
+ // Vermilion only for a failure: a stopped or held step waits, in grey.
705
+ : needsYou ? "needs" : step.state === "stuck" ? (headline === "Failed" || step.say === "Checks failed" || step.say === "Checks unreadable" ? "stuck" : "wait")
706
+ : runs || step.say === "Checks running" ? "live" : "wait";
707
+ // A step not reached opens only when it holds something already there: a result before Ready was reached, or
708
+ // earlier attempts before the next build starts.
709
+ // An approval this person can give is always on the page, open, wherever the road stands (a held task stops at
710
+ // Plan with its plan still waiting for a yes): the Approval row holds its form.
711
+ const approvalHere = step.key === "approve" && view.approve != null;
712
+ const opens = step.state !== "todo" || (step.key === "ready" && hasResult) || (step.key === "build" && hasHistory) || approvalHere;
713
+ return { key: step.key, label: step.label, state: step.state, mark, sentence: said.sentence, at: said.at, needsYou, opens, open: here || approvalHere };
714
+ });
715
+ // A finished road whose task still waits on this person (a result to review): the last step it reached is theirs.
716
+ const waitsOnViewer = headline === "Ready for review" || headline === "Needs you";
717
+ if (waitsOnViewer && !rows.some(row => row.state === "current" || row.state === "stuck")) {
718
+ const last = [...rows].reverse().find(row => row.state === "done");
719
+ if (last !== undefined)
720
+ Object.assign(last, { state: "current", mark: "needs", needsYou: true, sentence: "", at: null, open: true, opens: true });
721
+ }
722
+ // Nothing happening (a finished task): the last step it reached is the one open.
723
+ if (!rows.some(row => row.open)) {
724
+ const last = [...rows].reverse().find(row => row.opens && row.state !== "todo");
725
+ if (last !== undefined)
726
+ last.open = true;
727
+ }
728
+ return rows;
729
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.100",
3
+ "version": "0.9.101",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"