cubefarm 0.3.1 → 0.3.3

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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Leon van Zyl
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leon van Zyl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,94 +1,94 @@
1
- # ✻ cubefarm
2
-
3
- A cartoon 3D office where a team of AI coding agents works through your GitHub issues. You walk the floors, look over their shoulders and watch their pull requests get tested and merged.
4
-
5
- ## Get started
6
-
7
- ```bash
8
- npx cubefarm
9
- ```
10
-
11
- That's it. cubefarm checks your machine, starts the office and opens it in your browser. The first time, `npx` asks whether to install cubefarm: say yes.
12
-
13
- ### What you need
14
-
15
- - **Node.js 22 or newer**: [nodejs.org](https://nodejs.org)
16
- - **git**
17
- - **The GitHub CLI**, signed in: install it from [cli.github.com](https://cli.github.com), then run `gh auth login`
18
- - **A coding agent subscription**: cubefarm brings Claude Code, the default agent and the one your CEO runs: sign in once with `npx cubefarm login`. You don't need to install it. Agents can also run Codex or OpenCode, if you have them installed and signed in.
19
- - **Google Chrome**, for agents that test your app in a browser.
20
-
21
- It runs on Windows, macOS and Linux. Not sure you're ready? `npx cubefarm doctor` checks all of it.
22
-
23
- ### Just want to look around?
24
-
25
- ```bash
26
- npx cubefarm --demo
27
- ```
28
-
29
- Demo mode fakes GitHub and the agents, so it costs nothing and changes nothing.
30
-
31
- ## Your first five minutes
32
-
33
- 1. **Set up your company.** A short wizard asks your name, names your company and introduces your CEO.
34
- 2. **Move in a project.** Pick one of your project folders or a GitHub repo, or start a new one. It gets its own floor. Every project needs to be on GitHub, because issues and pull requests are how the team works.
35
- 3. **Let the CEO plan.** The CEO studies the project, writes its QA checklist, plans the work as GitHub issues and proposes who to hire. Press `P` for your phone to chat with them and approve hires.
36
- 4. **Watch the work.** Developers pick up issues and open pull requests. QA testers review and test each one in a real browser, then post a report with screenshots. With auto-merge on, a pull request merges itself once QA passes and GitHub's checks are green.
37
-
38
- ## What's in the office
39
-
40
- - **Floors**: one per project. Ride the elevator between them.
41
- - **Desks**: walk up behind an agent to watch their monitor. Open it to see their real terminal: every agent is an actual coding agent running on your machine, and you can type into it. It also shows a live browser when they test the UI. Pick the coding agent, model and effort for the whole team or per agent.
42
- - **The QA lab**: every floor has at least one QA tester.
43
- - **The whiteboard**: the Kanban board, from backlog to merged.
44
- - **The lobby**: the manager's office, where you connect projects, hire, file issues and change settings, and the CEO's corner office.
45
-
46
- ## Controls
47
-
48
- | Key | Action |
49
- | --- | --- |
50
- | `W A S D` / arrows | walk |
51
- | `Shift` | run |
52
- | mouse | look around (click the view first) |
53
- | `E` | use what you're looking at: a desk, the whiteboard, the elevator, the manager's computer |
54
- | `P` | your phone |
55
- | `H` | help |
56
- | `Esc` | let go of the mouse, or close a panel |
57
-
58
- ## Commands
59
-
60
- | Command | What it does |
61
- | --- | --- |
62
- | `npx cubefarm` | start the office and open it in your browser |
63
- | `npx cubefarm login` | sign in to Claude Code, the built-in coding agent |
64
- | `npx cubefarm doctor` | check that your machine is ready |
65
- | `npx cubefarm --demo` | fake GitHub and fake agents |
66
- | `npx cubefarm --port 4400` | use another port (the default is 4317) |
67
- | `npx cubefarm --no-open` | don't open the browser |
68
-
69
- ## Updating
70
-
71
- `npx` keeps using the version it downloaded first. When a newer one is out, cubefarm tells you as it starts. To update:
72
-
73
- ```bash
74
- npx cubefarm@latest
75
- ```
76
-
77
- Prefer a permanent install? Run `npm install -g cubefarm`, then start it with `cubefarm`.
78
-
79
- Running it from a clone of this repo (`npm start`)? Then the office updates itself from GitHub once its agents are done: see [Updating the office](docs/how-it-works.md#updating-the-office).
80
-
81
- ## Good to know
82
-
83
- - **It runs on your coding agents' subscriptions.** Agents on the same coding agent draw on the same usage limits. To cap how many work at once, set a session limit in the manager's console.
84
- - **Agents work on your machine, like your own coding agents**: with your skills, MCP servers and settings, each in its own copy of the repo. They can't push to your main branch or merge: the office merges, after QA.
85
- - **Your office lives in `~/.cubefarm`**: settings, clones of your repos and one working copy per agent. Set `SWARM_HOME` to use another folder.
86
-
87
- ## Learn more
88
-
89
- - [How it works](docs/how-it-works.md): the life of an issue, QA, auto-merge, models and usage, the safety model and floor previews
90
- - [Contributing](CONTRIBUTING.md): run it from source, tests, architecture and publishing
91
-
92
- ## License
93
-
94
- [MIT](LICENSE)
1
+ # ✻ cubefarm
2
+
3
+ A cartoon 3D office where a team of AI coding agents works through your GitHub issues. You walk the floors, look over their shoulders and watch their pull requests get tested and merged.
4
+
5
+ ## Get started
6
+
7
+ ```bash
8
+ npx cubefarm
9
+ ```
10
+
11
+ That's it. cubefarm checks your machine, starts the office and opens it in your browser. The first time, `npx` asks whether to install cubefarm: say yes.
12
+
13
+ ### What you need
14
+
15
+ - **Node.js 22 or newer**: [nodejs.org](https://nodejs.org)
16
+ - **git**
17
+ - **The GitHub CLI**, signed in: install it from [cli.github.com](https://cli.github.com), then run `gh auth login`
18
+ - **A coding agent subscription**: cubefarm brings Claude Code, the default agent and the one your CEO runs: sign in once with `npx cubefarm login`. You don't need to install it. Agents can also run Codex or OpenCode, if you have them installed and signed in.
19
+ - **Google Chrome**, for agents that test your app in a browser.
20
+
21
+ It runs on Windows, macOS and Linux. Not sure you're ready? `npx cubefarm doctor` checks all of it.
22
+
23
+ ### Just want to look around?
24
+
25
+ ```bash
26
+ npx cubefarm --demo
27
+ ```
28
+
29
+ Demo mode fakes GitHub and the agents, so it costs nothing and changes nothing.
30
+
31
+ ## Your first five minutes
32
+
33
+ 1. **Set up your company.** A short wizard asks your name, names your company and introduces your CEO.
34
+ 2. **Move in a project.** Pick one of your project folders or a GitHub repo, or start a new one. It gets its own floor. Every project needs to be on GitHub, because issues and pull requests are how the team works.
35
+ 3. **Let the CEO plan.** The CEO studies the project, writes its QA checklist, plans the work as GitHub issues and proposes who to hire. Press `P` for your phone to chat with them and approve hires.
36
+ 4. **Watch the work.** Developers pick up issues and open pull requests. QA testers review and test each one in a real browser, then post a report with screenshots. With auto-merge on, a pull request merges itself once QA passes and GitHub's checks are green.
37
+
38
+ ## What's in the office
39
+
40
+ - **Floors**: one per project. Ride the elevator between them.
41
+ - **Desks**: walk up behind an agent to watch their monitor. Open it to see their real terminal: every agent is an actual coding agent running on your machine, and you can type into it. It also shows a live browser when they test the UI. Pick the coding agent, model and effort for the whole team or per agent.
42
+ - **The QA lab**: every floor has at least one QA tester.
43
+ - **The whiteboard**: the Kanban board, from backlog to merged.
44
+ - **The lobby**: the manager's office, where you connect projects, hire, file issues and change settings, and the CEO's corner office.
45
+
46
+ ## Controls
47
+
48
+ | Key | Action |
49
+ | --- | --- |
50
+ | `W A S D` / arrows | walk |
51
+ | `Shift` | run |
52
+ | mouse | look around (click the view first) |
53
+ | `E` | use what you're looking at: a desk, the whiteboard, the elevator, the manager's computer |
54
+ | `P` | your phone |
55
+ | `H` | help |
56
+ | `Esc` | let go of the mouse, or close a panel |
57
+
58
+ ## Commands
59
+
60
+ | Command | What it does |
61
+ | --- | --- |
62
+ | `npx cubefarm` | start the office and open it in your browser |
63
+ | `npx cubefarm login` | sign in to Claude Code, the built-in coding agent |
64
+ | `npx cubefarm doctor` | check that your machine is ready |
65
+ | `npx cubefarm --demo` | fake GitHub and fake agents |
66
+ | `npx cubefarm --port 4400` | use another port (the default is 4317) |
67
+ | `npx cubefarm --no-open` | don't open the browser |
68
+
69
+ ## Updating
70
+
71
+ `npx` keeps using the version it downloaded first. When a newer one is out, cubefarm tells you as it starts. To update:
72
+
73
+ ```bash
74
+ npx cubefarm@latest
75
+ ```
76
+
77
+ Prefer a permanent install? Run `npm install -g cubefarm`, then start it with `cubefarm`.
78
+
79
+ Running it from a clone of this repo (`npm start`)? Then the office updates itself from GitHub once its agents are done: see [Updating the office](docs/how-it-works.md#updating-the-office).
80
+
81
+ ## Good to know
82
+
83
+ - **It runs on your coding agents' subscriptions.** Agents on the same coding agent draw on the same usage limits. To cap how many work at once, set a session limit in the manager's console.
84
+ - **Agents work on your machine, like your own coding agents**: with your skills, MCP servers and settings, each in its own copy of the repo. They can't push to your main branch or merge: the office merges, after QA.
85
+ - **Your office lives in `~/.cubefarm`**: settings, clones of your repos and one working copy per agent. Set `SWARM_HOME` to use another folder.
86
+
87
+ ## Learn more
88
+
89
+ - [How it works](docs/how-it-works.md): the life of an issue, QA, auto-merge, models and usage, the safety model and floor previews
90
+ - [Contributing](CONTRIBUTING.md): run it from source, tests, architecture and publishing
91
+
92
+ ## License
93
+
94
+ [MIT](LICENSE)
package/dist/index.html CHANGED
@@ -1,17 +1,17 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>cubefarm</title>
7
- <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><path d='M50 6 92 30 50 54 8 30z' fill='%23ffc2a6'/><path d='M8 30 50 54v42L8 72z' fill='%23ff8a5b'/><path d='M92 30 50 54v42l42-24z' fill='%23d9602f'/></svg>" />
8
- <link rel="preconnect" href="https://fonts.googleapis.com" />
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
- <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>cubefarm</title>
7
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><path d='M50 6 92 30 50 54 8 30z' fill='%23ffc2a6'/><path d='M8 30 50 54v42L8 72z' fill='%23ff8a5b'/><path d='M92 30 50 54v42l42-24z' fill='%23d9602f'/></svg>" />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
11
11
  <script type="module" crossorigin src="/assets/index-BCFM-9fU.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-BU33ZUhQ.css">
13
- </head>
14
- <body>
15
- <div id="root"></div>
16
- </body>
17
- </html>
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -1068,6 +1068,26 @@ function launchArgs(id, ctx) {
1068
1068
  }
1069
1069
  }
1070
1070
  }
1071
+ function oneAtATime() {
1072
+ const tails = /* @__PURE__ */ new Map();
1073
+ return (key, step) => {
1074
+ const next = (tails.get(key) ?? Promise.resolve()).then(step).then(
1075
+ () => void 0,
1076
+ () => void 0
1077
+ );
1078
+ tails.set(key, next);
1079
+ void next.then(() => tails.get(key) === next && tails.delete(key));
1080
+ return next;
1081
+ };
1082
+ }
1083
+ var threadSteps = oneAtATime();
1084
+ function codexThread(action, id, after) {
1085
+ return threadSteps(id, async () => {
1086
+ await after;
1087
+ const cmd = commandFor("codex");
1088
+ if (cmd) await run(cmd.file, [...cmd.args, action, id], { timeoutMs: 3e4 });
1089
+ });
1090
+ }
1071
1091
  var TRUST_PROMPT = /Quick safety check|Do you trust the (files|contents) (in|of) this|trust this folder|allow Codex to work in this folder/i;
1072
1092
  function trustKey(screen) {
1073
1093
  if (!TRUST_PROMPT.test(screen)) return null;
@@ -1486,17 +1506,23 @@ var hooksHinted = false;
1486
1506
  var IDLE_MS = 30 * 6e4;
1487
1507
  var ALLOW = { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", permissionDecisionReason: "Approved by cubefarm" } };
1488
1508
  var toolInput = (b) => b.tool_input && typeof b.tool_input === "object" ? b.tool_input : {};
1509
+ var exitOf = (p) => new Promise((resolve) => {
1510
+ p.onExit(() => resolve());
1511
+ setTimeout(resolve, 5e3);
1512
+ });
1489
1513
  function endLive(live) {
1490
- if (lives.get(live.term) !== live) return;
1514
+ if (lives.get(live.term) !== live) return Promise.resolve();
1491
1515
  lives.delete(live.term);
1492
1516
  clearTimeout(live.idleTimer);
1493
1517
  routes.delete(live.token);
1494
1518
  live.term.releaseIdle = null;
1495
1519
  live.term.bind(null);
1520
+ const gone = live.proc ? exitOf(live.proc) : void 0;
1496
1521
  live.proc?.kill();
1497
1522
  live.proc = null;
1498
1523
  live.term.note(`\u2500\u2500 ${cliLabel(live.cli)} session ended \u2500\u2500`);
1499
1524
  fs6.rm(live.dir, { recursive: true, force: true, maxRetries: 5 }, () => void 0);
1525
+ return live.cli === "codex" && live.resumeId ? codexThread("archive", live.resumeId, gone) : Promise.resolve();
1500
1526
  }
1501
1527
  var metaOf = (l) => ({ agentId: l.agentId, cli: l.cli, token: l.token, dir: l.dir, resumeId: l.resumeId, busy: !!l.session });
1502
1528
  var remember = (l) => l.proc?.setMeta(metaOf(l));
@@ -1529,6 +1555,7 @@ async function reconnectClis(terminalFor) {
1529
1555
  const term = meta.agentId ? terminalFor(meta.agentId) : null;
1530
1556
  if (h.exit !== null || !term || lives.has(term) || !isCli(meta.cli) || !meta.token || !meta.dir || !meta.agentId) {
1531
1557
  discardPty(h);
1558
+ if (meta.cli === "codex" && meta.resumeId) void codexThread("archive", meta.resumeId, h.exit === null ? new Promise((r) => setTimeout(r, 5e3)) : void 0);
1532
1559
  continue;
1533
1560
  }
1534
1561
  const l = { agentId: meta.agentId, cli: meta.cli, term, proc: adoptPty(h), token: meta.token, dir: meta.dir, resumeId: meta.resumeId ?? null, statusLine: "\u{1F3E2} cubefarm", shots: /* @__PURE__ */ new Set(), session: null };
@@ -1543,8 +1570,8 @@ function officeProcesses() {
1543
1570
  const pids = [keeperPid(), ...[...lives.values()].map((l) => l.proc?.pid ?? 0)].filter((p) => p > 0);
1544
1571
  return { pids, markers: [SESSIONS_DIR, BIN_DIR] };
1545
1572
  }
1546
- function releaseClis(restart) {
1547
- if (!restart) for (const l of [...lives.values()]) endLive(l);
1573
+ async function releaseClis(restart) {
1574
+ if (!restart) await Promise.all([...lives.values()].map(endLive));
1548
1575
  leaveKeeper();
1549
1576
  }
1550
1577
  function idleHook(live, b) {
@@ -1707,7 +1734,7 @@ function startCliSession(opts, callbacks) {
1707
1734
  if (sub) return {};
1708
1735
  busy();
1709
1736
  const prompt2 = String(b.prompt ?? "").trim();
1710
- const i = officePrompts.findIndex((p2) => p2.slice(0, 60) === prompt2.slice(0, 60));
1737
+ const i = officePrompts.findIndex((p) => p.slice(0, 60) === prompt2.slice(0, 60));
1711
1738
  if (i >= 0) officePrompts.splice(i, 1);
1712
1739
  else if (prompt2) log([{ kind: "manager", text: `\u25B6 Typed in the terminal: ${clip(prompt2.split("\n")[0], 200)}` }]);
1713
1740
  return {};
@@ -1743,7 +1770,7 @@ function startCliSession(opts, callbacks) {
1743
1770
  const thread = typeof b.session_id === "string" ? b.session_id : "";
1744
1771
  if (cli === "codex" && thread !== mainThread) {
1745
1772
  const input = String(b.input ?? "").trim();
1746
- if (mainThread || !launched.some((p2) => input.startsWith(p2.slice(0, 40)))) return {};
1773
+ if (mainThread || !launched.some((p) => input.startsWith(p.slice(0, 40)))) return {};
1747
1774
  mainThread = thread;
1748
1775
  }
1749
1776
  if (thread) {
@@ -1875,46 +1902,51 @@ ${JSON.stringify(opts.outputSchema)}`;
1875
1902
  term.note(`\u2500\u2500 ${label}${opts.label ? ` \xB7 ${opts.label}` : ""} \u2500\u2500`);
1876
1903
  const resumeId = cli === "claude" ? sessionId : opts.resumeSessionId ?? null;
1877
1904
  const l = { agentId: opts.agentId ?? "", cli, term, proc: null, token, dir, resumeId, statusLine, shots: /* @__PURE__ */ new Set(), session: { hook, exited } };
1878
- let p;
1879
- try {
1880
- p = spawnPty(cmd.file, [...cmd.args, ...launch2.args], { cols: term.cols, rows: term.rows, cwd: opts.cwd, env }, metaOf(l));
1881
- } catch (err) {
1882
- setTimeout(() => finish({ ok: false, text: "", errors: [`Could not start ${label}: ${err.message}`] }), 0);
1883
- return handle;
1884
- }
1885
- l.proc = p;
1886
- live = l;
1887
- wire(l, opts.office);
1888
- shotTimer = setInterval(collectShots, 2e3);
1889
- screenTimer = setInterval(() => {
1890
- if (done || Date.now() - started > BOOT_MS * 2) return clearInterval(screenTimer);
1891
- const screen = term.screen();
1892
- const review = hookReviewKey(screen);
1893
- const key = trustKeys < 12 && Date.now() - trustedAt > 3e3 ? trustKey(screen) ?? review : null;
1894
- if (key === "down") {
1895
- trustKeys += 1;
1896
- quietly(() => p.write(term.appCursor ? "\x1BOB" : "\x1B[B"));
1897
- } else if (key === "enter") {
1898
- trustKeys += 1;
1899
- trustedAt = Date.now();
1900
- if (!review) log([{ kind: "system", text: `\u2713 Trusted the worktree for ${label}.` }]);
1901
- else if (!hooksHinted) {
1902
- hooksHinted = true;
1903
- log([{ kind: "system", text: `\u2139 To see ${label}'s steps here, trust the office's hooks once: in this terminal, type /hooks and press t. Until then the office shows its task.` }]);
1905
+ const begin = () => {
1906
+ if (done) return;
1907
+ let p;
1908
+ try {
1909
+ p = spawnPty(cmd.file, [...cmd.args, ...launch2.args], { cols: term.cols, rows: term.rows, cwd: opts.cwd, env }, metaOf(l));
1910
+ } catch (err) {
1911
+ setTimeout(() => finish({ ok: false, text: "", errors: [`Could not start ${label}: ${err.message}`] }), 0);
1912
+ return;
1913
+ }
1914
+ l.proc = p;
1915
+ live = l;
1916
+ wire(l, opts.office);
1917
+ shotTimer = setInterval(collectShots, 2e3);
1918
+ screenTimer = setInterval(() => {
1919
+ if (done || Date.now() - started > BOOT_MS * 2) return clearInterval(screenTimer);
1920
+ const screen = term.screen();
1921
+ const review = hookReviewKey(screen);
1922
+ const key = trustKeys < 12 && Date.now() - trustedAt > 3e3 ? trustKey(screen) ?? review : null;
1923
+ if (key === "down") {
1924
+ trustKeys += 1;
1925
+ quietly(() => p.write(term.appCursor ? "\x1BOB" : "\x1B[B"));
1926
+ } else if (key === "enter") {
1927
+ trustKeys += 1;
1928
+ trustedAt = Date.now();
1929
+ if (!review) log([{ kind: "system", text: `\u2713 Trusted the worktree for ${label}.` }]);
1930
+ else if (!hooksHinted) {
1931
+ hooksHinted = true;
1932
+ log([{ kind: "system", text: `\u2139 To see ${label}'s steps here, trust the office's hooks once: in this terminal, type /hooks and press t. Until then the office shows its task.` }]);
1933
+ }
1934
+ quietly(() => p.write("\r"));
1935
+ }
1936
+ if (!nudged.has("login") && /Not logged in|run \/login|Select login method|Sign in with ChatGPT|Please login|Invalid API key/i.test(screen)) {
1937
+ nudged.add("login");
1938
+ log([{ kind: "error", text: `\u26A0 ${label} needs you to sign in: open the terminal and sign in there, or run "${cli}" in a terminal of your own.` }]);
1939
+ } else if (!nudged.has("setup") && !trustKey(screen) && Date.now() - trustedAt > 5e3 && /Choose the text style|Press Enter to continue|Bypass Permissions mode|Settings Error/i.test(screen)) {
1940
+ nudged.add("setup");
1941
+ log([{ kind: "error", text: `\u26A0 ${label} is waiting on a setup screen: open the terminal to answer it.` }]);
1904
1942
  }
1905
- quietly(() => p.write("\r"));
1906
- }
1907
- if (!nudged.has("login") && /Not logged in|run \/login|Select login method|Sign in with ChatGPT|Please login|Invalid API key/i.test(screen)) {
1908
- nudged.add("login");
1909
- log([{ kind: "error", text: `\u26A0 ${label} needs you to sign in: open the terminal and sign in there, or run "${cli}" in a terminal of your own.` }]);
1910
- } else if (!nudged.has("setup") && !trustKey(screen) && Date.now() - trustedAt > 5e3 && /Choose the text style|Press Enter to continue|Bypass Permissions mode|Settings Error/i.test(screen)) {
1911
- nudged.add("setup");
1912
- log([{ kind: "error", text: `\u26A0 ${label} is waiting on a setup screen: open the terminal to answer it.` }]);
1913
- }
1914
- }, 1e3);
1915
- bootTimer = setTimeout(() => {
1916
- if (!done && !begun && cli === "claude") log([{ kind: "error", text: `\u26A0 ${label} hasn't started on the task yet. Open the terminal to see what it's waiting for.` }]);
1917
- }, BOOT_MS);
1943
+ }, 1e3);
1944
+ bootTimer = setTimeout(() => {
1945
+ if (!done && !begun && cli === "claude") log([{ kind: "error", text: `\u26A0 ${label} hasn't started on the task yet. Open the terminal to see what it's waiting for.` }]);
1946
+ }, BOOT_MS);
1947
+ };
1948
+ if (cli === "codex" && opts.resumeSessionId) void codexThread("unarchive", opts.resumeSessionId).then(begin);
1949
+ else begin();
1918
1950
  return handle;
1919
1951
  }
1920
1952
 
@@ -2673,7 +2705,7 @@ Closes #${pr.closesIssues[0] ?? "?"}`,
2673
2705
  reconnectClis: async () => [],
2674
2706
  // fake sessions end with the office
2675
2707
  hooksReady: () => void 0,
2676
- releaseClis: () => void 0,
2708
+ releaseClis: async () => void 0,
2677
2709
  detectClis: async () => CLIS.map((c) => ({ id: c.id, label: c.label, installed: true, version: "demo", integrated: c.integrated })),
2678
2710
  previews: demoPreviews,
2679
2711
  office: demoOffice
@@ -4717,7 +4749,7 @@ var Swarm = class {
4717
4749
  */
4718
4750
  async shutdown(restart = false) {
4719
4751
  await this.writeState().catch((err) => console.warn("could not save the state", err));
4720
- this.backend.releaseClis(restart);
4752
+ await this.backend.releaseClis(restart);
4721
4753
  await this.saveTerminals(true);
4722
4754
  await this.previews.stopAll(this.state.repos);
4723
4755
  }