@paradow/ledge 0.40.0 → 0.41.0

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 (2) hide show
  1. package/dist/cli/index.js +634 -366
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -116,6 +116,12 @@ function backendOf(s) {
116
116
  function isArchived(s) {
117
117
  return s.archivedAt !== void 0;
118
118
  }
119
+ function isOmpSeat(s) {
120
+ return s.source === "rig" && s.tool === "omp" && s.backend === void 0;
121
+ }
122
+ function isStoppedOmpSeat(s) {
123
+ return isOmpSeat(s) && s.state === "exited" && !isArchived(s);
124
+ }
119
125
  var ToolSchema, OmpApprovalSchema, BackendSchema, SessionStateSchema, AskQuestionSchema, PendingSchema, ReplySchema, HelperSchema, TopicSourceSchema, SessionSchema;
120
126
  var init_session = __esm({
121
127
  "../protocol/src/session.ts"() {
@@ -3788,11 +3794,11 @@ function stripNote(text7) {
3788
3794
  }
3789
3795
  function formatLedgeMessage(m) {
3790
3796
  const from = attr(m.from);
3791
- const open5 = `<ledge-message from="${from}" tool="${m.tool}"${m.id ? ` id="${attr(m.id)}"` : ""}>${messageNote(from, m.to)} `;
3792
- if (!m.id || inline(m.text)) return `${open5}${ledgeBody(m.text)}${LEDGE_CLOSE}`;
3797
+ const open6 = `<ledge-message from="${from}" tool="${m.tool}"${m.id ? ` id="${attr(m.id)}"` : ""}>${messageNote(from, m.to)} `;
3798
+ if (!m.id || inline(m.text)) return `${open6}${ledgeBody(m.text)}${LEDGE_CLOSE}`;
3793
3799
  const first = m.summary?.trim() || m.text.trim().split(/\r\n|\r|\n/)[0].trim();
3794
3800
  const head2 = ledgeBody(first.length > POINTER_HEAD ? `${first.slice(0, POINTER_HEAD)}\u2026` : first);
3795
- return `${open5}${head2} \u2026 [message de ${m.text.length} caract\xE8res, ${lineCount(m.text)} lignes : lis-le en entier avec ledge read ${attr(m.id)}]${LEDGE_CLOSE}`;
3801
+ return `${open6}${head2} \u2026 [message de ${m.text.length} caract\xE8res, ${lineCount(m.text)} lignes : lis-le en entier avec ledge read ${attr(m.id)}]${LEDGE_CLOSE}`;
3796
3802
  }
3797
3803
  function ledgeMessages(text7) {
3798
3804
  const out = [];
@@ -4947,7 +4953,7 @@ __export(who_exports, {
4947
4953
  });
4948
4954
  import { basename as basename5 } from "path";
4949
4955
  function formatWho(sessions, self, projects) {
4950
- const live3 = sessions.filter((s) => s.state !== "exited" && !isArchived(s));
4956
+ const live3 = sessions.filter((s) => s.state !== "exited" && !isArchived(s) || isStoppedOmpSeat(s));
4951
4957
  if (live3.length === 0) return "aucune autre session";
4952
4958
  const rows = live3.map((s) => [
4953
4959
  s.id === self ? "*" : " ",
@@ -5195,8 +5201,8 @@ function underCodexDaemon(read = (p) => readFileSync5(p, "utf8")) {
5195
5201
  let pid = process.pid;
5196
5202
  for (let depth = 0; depth < 8; depth++) {
5197
5203
  try {
5198
- const stat7 = read(`/proc/${pid}/stat`);
5199
- pid = Number(stat7.slice(stat7.lastIndexOf(")") + 2).split(" ")[1]);
5204
+ const stat8 = read(`/proc/${pid}/stat`);
5205
+ pid = Number(stat8.slice(stat8.lastIndexOf(")") + 2).split(" ")[1]);
5200
5206
  if (!pid || pid <= 1) return false;
5201
5207
  if (read(`/proc/${pid}/cmdline`).split("\0").includes("app-server")) return true;
5202
5208
  } catch {
@@ -6764,9 +6770,9 @@ ${done.output}` : command;
6764
6770
  const peer = (m.sender ? this.peerOf(m.sender) : void 0) ?? this.peerOf(m.from) ?? (m.name ? this.peerOf(m.name) : void 0);
6765
6771
  if (!peer) return this.put(id, recv);
6766
6772
  const head2 = (t) => ledgeBody(t).trim().slice(0, 200);
6767
- const open5 = this.items(peer).filter((i) => i.kind === "sent" && i.peer === id && i.status !== "read");
6768
- const byId2 = m.id ? open5.find((i) => mailId(i.id) === m.id) : void 0;
6769
- const sent = byId2 ?? [...open5].reverse().find((i) => head2(i.text) === head2(recv.text)) ?? open5.at(-1);
6773
+ const open6 = this.items(peer).filter((i) => i.kind === "sent" && i.peer === id && i.status !== "read");
6774
+ const byId2 = m.id ? open6.find((i) => mailId(i.id) === m.id) : void 0;
6775
+ const sent = byId2 ?? [...open6].reverse().find((i) => head2(i.text) === head2(recv.text)) ?? open6.at(-1);
6770
6776
  if (sent) this.put(peer, { ...sent, status: "read", received: recv.id });
6771
6777
  return this.put(id, { ...recv, ...byId2 ? { text: byId2.text } : {}, peer, ...sent ? { sent: sent.id } : {} });
6772
6778
  }
@@ -7838,6 +7844,203 @@ var init_verify = __esm({
7838
7844
  }
7839
7845
  });
7840
7846
 
7847
+ // src/daemon/omp/seats.ts
7848
+ import { homedir as homedir5 } from "os";
7849
+ import { join as join10 } from "path";
7850
+ import { open, readFile as readFile7, stat as stat2 } from "fs/promises";
7851
+ function entryState(raw) {
7852
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
7853
+ const e = raw;
7854
+ const at = typeof e.timestamp === "string" && !Number.isNaN(Date.parse(e.timestamp)) ? new Date(e.timestamp).toISOString() : void 0;
7855
+ const of = (state2) => ({ state: state2, ...at ? { at } : {} });
7856
+ if (e.type === "custom_message") return of("working");
7857
+ if (e.type === "custom") return e.customType === "tool_execution_start" ? of("working") : null;
7858
+ if (e.type !== "message" || !e.message || typeof e.message !== "object") return null;
7859
+ const m = e.message;
7860
+ if (m.role === "user" || m.role === "toolResult") return of("working");
7861
+ if (m.role !== "assistant") return null;
7862
+ return typeof m.stopReason === "string" ? of(m.stopReason === "toolUse" ? "working" : "idle") : null;
7863
+ }
7864
+ async function nextNewline(fh, from, size) {
7865
+ const buf2 = Buffer.alloc(BLOCK3);
7866
+ for (let at = from; at < size; ) {
7867
+ const { bytesRead } = await fh.read(buf2, 0, Math.min(BLOCK3, size - at), at);
7868
+ if (bytesRead === 0) return -1;
7869
+ const nl = buf2.subarray(0, bytesRead).indexOf(10);
7870
+ if (nl !== -1) return at + nl;
7871
+ at += bytesRead;
7872
+ }
7873
+ return -1;
7874
+ }
7875
+ function createOmpSeats(deps) {
7876
+ const { store } = deps;
7877
+ const root2 = deps.root ?? ompSeatsRoot();
7878
+ const follows = /* @__PURE__ */ new Map();
7879
+ let running = false, timer2 = null;
7880
+ async function journalOf(seat2) {
7881
+ try {
7882
+ const file = JSON.parse(await readFile7(join10(root2, seat2, "runner-state.json"), "utf8")).sessionFile;
7883
+ return typeof file === "string" && file ? file : null;
7884
+ } catch {
7885
+ return null;
7886
+ }
7887
+ }
7888
+ async function followOne(s) {
7889
+ const file = await journalOf(s.rig.seat);
7890
+ if (!file) return;
7891
+ let size;
7892
+ try {
7893
+ size = (await stat2(file)).size;
7894
+ } catch {
7895
+ return;
7896
+ }
7897
+ let f = follows.get(s.id);
7898
+ if (!f || f.file !== file) {
7899
+ const known2 = await lastState(file, size);
7900
+ f = { file, offset: known2.end };
7901
+ follows.set(s.id, f);
7902
+ if (known2.state) apply(s.id, known2.state);
7903
+ return;
7904
+ }
7905
+ if (size < f.offset) {
7906
+ f.offset = size;
7907
+ return;
7908
+ }
7909
+ if (size === f.offset) return;
7910
+ const fh = await open(file, "r");
7911
+ let chunk, end2;
7912
+ try {
7913
+ const readAt2 = async (want) => {
7914
+ const b3 = Buffer.alloc(want);
7915
+ return b3.subarray(0, (await fh.read(b3, 0, want, f.offset)).bytesRead);
7916
+ };
7917
+ chunk = await readAt2(Math.min(size - f.offset, READ_BYTES));
7918
+ end2 = chunk.lastIndexOf(10);
7919
+ if (end2 === -1 && chunk.length < size - f.offset) {
7920
+ chunk = await readAt2(Math.min(size - f.offset, BACK_BYTES));
7921
+ end2 = chunk.lastIndexOf(10);
7922
+ }
7923
+ if (end2 === -1 && chunk.length === BACK_BYTES) {
7924
+ const next4 = await nextNewline(fh, f.offset + chunk.length, size);
7925
+ if (next4 !== -1) {
7926
+ log(`omp seats: an entry of ${s.rig.seat} over ${BACK_BYTES} bytes was skipped`);
7927
+ f.offset = next4 + 1;
7928
+ }
7929
+ return;
7930
+ }
7931
+ } finally {
7932
+ await fh.close();
7933
+ }
7934
+ if (end2 === -1) return;
7935
+ const lines = chunk.subarray(0, end2).toString("utf8").split("\n");
7936
+ f.offset += end2 + 1;
7937
+ let last = null;
7938
+ for (const line2 of lines) {
7939
+ if (!line2.trim()) continue;
7940
+ let entry2;
7941
+ try {
7942
+ entry2 = JSON.parse(line2);
7943
+ } catch {
7944
+ continue;
7945
+ }
7946
+ last = entryState(entry2) ?? last;
7947
+ }
7948
+ if (last) apply(s.id, last);
7949
+ }
7950
+ function apply(id, last) {
7951
+ const cur = store.get(id);
7952
+ if (!cur || cur.state === "exited") return;
7953
+ const at = last.at ?? (/* @__PURE__ */ new Date()).toISOString();
7954
+ if (cur.state !== last.state || at > cur.lastActivityAt) store.upsert({ ...cur, state: last.state, lastActivityAt: at > cur.lastActivityAt ? at : cur.lastActivityAt });
7955
+ }
7956
+ async function lastState(file, size) {
7957
+ const fh = await open(file, "r");
7958
+ try {
7959
+ const newlineBefore = async (before2) => {
7960
+ const buf2 = Buffer.alloc(BLOCK3);
7961
+ for (let to = before2; to > 0; ) {
7962
+ if (size - to > BACK_BYTES) return null;
7963
+ const from = Math.max(0, to - BLOCK3);
7964
+ const { bytesRead } = await fh.read(buf2, 0, to - from, from);
7965
+ const at = buf2.subarray(0, bytesRead).lastIndexOf(10);
7966
+ if (at !== -1) return from + at;
7967
+ to = from;
7968
+ }
7969
+ return -1;
7970
+ };
7971
+ const last = await newlineBefore(size);
7972
+ if (last === null || last === -1) return { state: null, end: last === -1 ? 0 : size };
7973
+ const end2 = last + 1;
7974
+ for (let lineEnd = last; lineEnd > 0; ) {
7975
+ const start = await newlineBefore(lineEnd);
7976
+ if (start === null) break;
7977
+ const from = start + 1;
7978
+ const line2 = Buffer.alloc(lineEnd - from);
7979
+ await fh.read(line2, 0, line2.length, from);
7980
+ let entry2 = null;
7981
+ try {
7982
+ entry2 = JSON.parse(line2.toString("utf8"));
7983
+ } catch {
7984
+ }
7985
+ const state2 = entryState(entry2);
7986
+ if (state2) return { state: state2, end: end2 };
7987
+ if (start === -1) break;
7988
+ lineEnd = start;
7989
+ }
7990
+ return { state: null, end: end2 };
7991
+ } finally {
7992
+ await fh.close();
7993
+ }
7994
+ }
7995
+ async function tick() {
7996
+ if (running) return;
7997
+ running = true;
7998
+ try {
7999
+ const live3 = /* @__PURE__ */ new Set();
8000
+ for (const s of store.all()) {
8001
+ if (!isOmpSeat2(s) || !s.rig || s.state === "exited" || isArchived(s)) continue;
8002
+ live3.add(s.id);
8003
+ try {
8004
+ await followOne(s);
8005
+ } catch (e) {
8006
+ log(`omp seats: journal of ${s.rig.seat} not read: ${e.message}`);
8007
+ }
8008
+ }
8009
+ for (const id of follows.keys()) if (!live3.has(id)) follows.delete(id);
8010
+ } finally {
8011
+ running = false;
8012
+ }
8013
+ }
8014
+ return {
8015
+ tick,
8016
+ start() {
8017
+ if (!timer2) {
8018
+ timer2 = setInterval(() => {
8019
+ void tick();
8020
+ }, deps.intervalMs ?? 1e3);
8021
+ timer2.unref?.();
8022
+ }
8023
+ },
8024
+ stop() {
8025
+ if (timer2) clearInterval(timer2);
8026
+ timer2 = null;
8027
+ }
8028
+ };
8029
+ }
8030
+ var ompSeatsRoot, BACK_BYTES, BLOCK3, READ_BYTES, isOmpSeat2;
8031
+ var init_seats = __esm({
8032
+ "src/daemon/omp/seats.ts"() {
8033
+ "use strict";
8034
+ init_src();
8035
+ init_log();
8036
+ ompSeatsRoot = (env = process.env) => join10(env.OPENRIG_HOME?.trim() || join10(homedir5(), ".openrig"), "state", "omp");
8037
+ BACK_BYTES = 16 * 1024 * 1024;
8038
+ BLOCK3 = 64 * 1024;
8039
+ READ_BYTES = 4 * 1024 * 1024;
8040
+ isOmpSeat2 = (s) => s.source === "rig" && s.tool === "omp" && s.backend === void 0;
8041
+ }
8042
+ });
8043
+
7841
8044
  // src/daemon/poller.ts
7842
8045
  import { createHash as createHash4 } from "crypto";
7843
8046
  function startPoller(opts) {
@@ -7902,7 +8105,7 @@ function startPoller(opts) {
7902
8105
  }
7903
8106
  if (s.state === "exited") {
7904
8107
  forget(s.id);
7905
- if (!s.archivedAt && s.exitedAt) {
8108
+ if (!s.archivedAt && s.exitedAt && !isOmpSeat2(s)) {
7906
8109
  const since = Math.max(Date.parse(s.exitedAt), s.restoredAt ? Date.parse(s.restoredAt) : -Infinity);
7907
8110
  if (t - since > retireMs) store.archive(s.id, now2());
7908
8111
  }
@@ -7919,6 +8122,7 @@ function startPoller(opts) {
7919
8122
  forget(s.id);
7920
8123
  return;
7921
8124
  }
8125
+ if (isOmpSeat2(s)) return;
7922
8126
  const full = await tmux.capture(s.tmuxSession, PREVIEW_CAPTURE_LINES);
7923
8127
  if (full === null) return;
7924
8128
  const pane = full.split("\n").slice(-DETECT_LINES).join("\n");
@@ -7983,6 +8187,7 @@ var init_poller = __esm({
7983
8187
  init_log();
7984
8188
  init_git();
7985
8189
  init_transcript();
8190
+ init_seats();
7986
8191
  init_adapters();
7987
8192
  DETECT_LINES = 60;
7988
8193
  REST_MS = 2e3;
@@ -8528,7 +8733,7 @@ var init_project = __esm({
8528
8733
 
8529
8734
  // src/daemon/omp/host.ts
8530
8735
  import { randomBytes as randomBytes4 } from "crypto";
8531
- import { basename as basename7, join as join10 } from "path";
8736
+ import { basename as basename7, join as join11 } from "path";
8532
8737
  import { mkdir as mkdir7 } from "fs/promises";
8533
8738
  function ompArgs(o) {
8534
8739
  return ["--mode", "rpc-ui", "--session-dir", o.dir, "--cwd", o.cwd, "--approval-mode", o.approval ?? APPROVAL_MODE, ...ompModelArgs(o.model, o.effort), ...(o.dirs ?? []).flatMap((d) => ["--add-dir", d]), ...o.resumeFile ? ["--resume", o.resumeFile] : []];
@@ -8540,7 +8745,7 @@ function createOmpHost(deps) {
8540
8745
  const live3 = /* @__PURE__ */ new Map();
8541
8746
  let generations = 0;
8542
8747
  async function startProcess(id, o) {
8543
- const dir = join10(deps.dir, id);
8748
+ const dir = join11(deps.dir, id);
8544
8749
  await mkdir7(dir, { recursive: true });
8545
8750
  if (await reapOwner(dir)) log(`omp ${id}: ended the process a previous daemon left`);
8546
8751
  const key = sessionKey(id);
@@ -8720,15 +8925,15 @@ function createOmpHost(deps) {
8720
8925
  async answer(id, text7) {
8721
8926
  const l = live3.get(id);
8722
8927
  if (!l) return "the session is not running";
8723
- const open5 = l.tracking.requests[0];
8724
- if (!open5) return "nothing to answer";
8725
- const a = answerFor(open5, text7);
8928
+ const open6 = l.tracking.requests[0];
8929
+ if (!open6) return "nothing to answer";
8930
+ const a = answerFor(open6, text7);
8726
8931
  if ("refused" in a) return a.refused;
8727
8932
  l.tracking.requests.shift();
8728
8933
  try {
8729
8934
  await l.rpc.write(a.frame);
8730
8935
  } catch (e) {
8731
- l.tracking.requests.unshift(open5);
8936
+ l.tracking.requests.unshift(open6);
8732
8937
  return e.message;
8733
8938
  }
8734
8939
  store.apply(id, { name: "Decided", at: now2(), source: "daemon" });
@@ -8777,15 +8982,15 @@ function createOmpHost(deps) {
8777
8982
  async answerAsk(id, requestId3, answers, cancel = false) {
8778
8983
  const l = live3.get(id);
8779
8984
  if (!l) return "the session is not running";
8780
- const open5 = l.tracking.requests[0];
8781
- if (!open5 || open5.id !== requestId3) return "the request changed, look again";
8782
- const a = cancel ? { frame: { type: "extension_ui_response", id: open5.id, cancelled: true } } : askAnswerFor(open5, answers);
8985
+ const open6 = l.tracking.requests[0];
8986
+ if (!open6 || open6.id !== requestId3) return "the request changed, look again";
8987
+ const a = cancel ? { frame: { type: "extension_ui_response", id: open6.id, cancelled: true } } : askAnswerFor(open6, answers);
8783
8988
  if ("refused" in a) return a.refused;
8784
8989
  l.tracking.requests.shift();
8785
8990
  try {
8786
8991
  await l.rpc.write(a.frame);
8787
8992
  } catch (e) {
8788
- l.tracking.requests.unshift(open5);
8993
+ l.tracking.requests.unshift(open6);
8789
8994
  return e.message;
8790
8995
  }
8791
8996
  store.apply(id, { name: "Decided", at: now2(), source: "daemon" });
@@ -8871,9 +9076,9 @@ var init_catalog = __esm({
8871
9076
  // src/daemon/repos.ts
8872
9077
  import { execFile as execFile2 } from "child_process";
8873
9078
  import { constants } from "fs";
8874
- import { open, stat as stat2 } from "fs/promises";
8875
- import { homedir as homedir5 } from "os";
8876
- import { join as join11, resolve as resolve2 } from "path";
9079
+ import { open as open2, stat as stat3 } from "fs/promises";
9080
+ import { homedir as homedir6 } from "os";
9081
+ import { join as join12, resolve as resolve2 } from "path";
8877
9082
  function runGit(cwd, args) {
8878
9083
  return new Promise((res) => {
8879
9084
  execFile2("git", ["-C", cwd, ...args], { timeout: GIT_TIMEOUT_MS, windowsHide: true }, (err, out) => res(err ? null : out.trim() || null));
@@ -8891,7 +9096,7 @@ async function slot(job) {
8891
9096
  }
8892
9097
  }
8893
9098
  async function readGitFile(path3, max = 64 * 1024) {
8894
- const fh = await open(path3, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
9099
+ const fh = await open2(path3, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
8895
9100
  try {
8896
9101
  if (!(await fh.stat()).isFile()) throw new Error(`not a regular file: ${path3}`);
8897
9102
  const buf2 = Buffer.alloc(max + 1);
@@ -8905,7 +9110,7 @@ async function readGitFile(path3, max = 64 * 1024) {
8905
9110
  async function headBranch(dotGit) {
8906
9111
  let head2;
8907
9112
  try {
8908
- head2 = (await readGitFile(join11(dotGit, "HEAD"), MAX_HEAD_BYTES)).trim();
9113
+ head2 = (await readGitFile(join12(dotGit, "HEAD"), MAX_HEAD_BYTES)).trim();
8909
9114
  } catch {
8910
9115
  return null;
8911
9116
  }
@@ -8914,23 +9119,23 @@ async function headBranch(dotGit) {
8914
9119
  return /^[0-9a-f]{7,}$/i.test(head2) ? head2.slice(0, 7) : null;
8915
9120
  }
8916
9121
  async function inspectRepos(paths2, opts = {}) {
8917
- const home2 = opts.home ?? homedir5();
9122
+ const home2 = opts.home ?? homedir6();
8918
9123
  const git = opts.git ?? runGit;
8919
9124
  return Promise.all(paths2.map((raw) => slot(async () => {
8920
9125
  const input = raw.trim();
8921
9126
  const path3 = resolve2(input === "~" ? home2 : input.startsWith("~/") ? resolve2(home2, input.slice(2)) : input);
8922
9127
  let isDir2 = false;
8923
9128
  try {
8924
- isDir2 = (await stat2(path3)).isDirectory();
9129
+ isDir2 = (await stat3(path3)).isDirectory();
8925
9130
  } catch {
8926
9131
  }
8927
9132
  if (!isDir2) return { path: raw, exists: false, branch: null };
8928
9133
  let dotDir = false;
8929
9134
  try {
8930
- dotDir = (await stat2(join11(path3, ".git"))).isDirectory();
9135
+ dotDir = (await stat3(join12(path3, ".git"))).isDirectory();
8931
9136
  } catch {
8932
9137
  }
8933
- if (dotDir) return { path: raw, exists: true, branch: await headBranch(join11(path3, ".git")) };
9138
+ if (dotDir) return { path: raw, exists: true, branch: await headBranch(join12(path3, ".git")) };
8934
9139
  const deadline = Date.now() + GIT_TIMEOUT_MS;
8935
9140
  const bounded = (args) => new Promise((res) => {
8936
9141
  const t = setTimeout(() => res(null), Math.max(0, deadline - Date.now()));
@@ -8962,7 +9167,7 @@ var init_repos2 = __esm({
8962
9167
 
8963
9168
  // src/daemon/scan.ts
8964
9169
  import { readdir as fsReaddir } from "fs/promises";
8965
- import { dirname as dirname4, isAbsolute, join as join12, resolve as resolve3, sep as sep2 } from "path";
9170
+ import { dirname as dirname4, isAbsolute, join as join13, resolve as resolve3, sep as sep2 } from "path";
8966
9171
  function extraRoots(home2, cwds) {
8967
9172
  const h = resolve3(home2);
8968
9173
  const out = [];
@@ -9008,19 +9213,19 @@ function originUrl(config) {
9008
9213
  }
9009
9214
  return url || void 0;
9010
9215
  }
9011
- async function remoteOf(dir, dotIsFile, readFile19) {
9216
+ async function remoteOf(dir, dotIsFile, readFile20) {
9012
9217
  try {
9013
- let gitDir = join12(dir, ".git");
9218
+ let gitDir = join13(dir, ".git");
9014
9219
  if (dotIsFile) {
9015
- const m = /^gitdir:\s*(.+)$/m.exec(await readFile19(gitDir));
9220
+ const m = /^gitdir:\s*(.+)$/m.exec(await readFile20(gitDir));
9016
9221
  if (!m) return void 0;
9017
9222
  gitDir = resolve3(dir, m[1].trim());
9018
9223
  try {
9019
- gitDir = resolve3(gitDir, (await readFile19(join12(gitDir, "commondir"))).trim());
9224
+ gitDir = resolve3(gitDir, (await readFile20(join13(gitDir, "commondir"))).trim());
9020
9225
  } catch {
9021
9226
  }
9022
9227
  }
9023
- const url = originUrl(await readFile19(join12(gitDir, "config")));
9228
+ const url = originUrl(await readFile20(join13(gitDir, "config")));
9024
9229
  return url ? repositoryId(url) : void 0;
9025
9230
  } catch {
9026
9231
  return void 0;
@@ -9037,18 +9242,18 @@ function manifestName(file, text7) {
9037
9242
  } else name = /<artifactId>\s*([^<]+?)\s*<\/artifactId>/.exec(text7.replace(/<!--[\s\S]*?-->/g, "").replace(/<parent>[\s\S]*?<\/parent>/g, ""))?.[1];
9038
9243
  return typeof name === "string" && name.trim() && name.length <= 200 ? name.trim() : void 0;
9039
9244
  }
9040
- async function describe(dir, dotIsFile, home2, readFile19, manifest) {
9245
+ async function describe(dir, dotIsFile, home2, readFile20, manifest) {
9041
9246
  const [branch, remote, named] = await Promise.all([
9042
- dotIsFile ? inspectRepos([dir], { home: home2 }).then((r) => r[0]?.branch ?? null) : headBranch(join12(dir, ".git")),
9043
- remoteOf(dir, dotIsFile, readFile19),
9044
- manifest ? readFile19(join12(dir, manifest)).then((text7) => manifestName(manifest, text7), () => void 0) : void 0
9247
+ dotIsFile ? inspectRepos([dir], { home: home2 }).then((r) => r[0]?.branch ?? null) : headBranch(join13(dir, ".git")),
9248
+ remoteOf(dir, dotIsFile, readFile20),
9249
+ manifest ? readFile20(join13(dir, manifest)).then((text7) => manifestName(manifest, text7), () => void 0) : void 0
9045
9250
  ]);
9046
9251
  return { path: dir, branch, ...remote ? { remote } : {}, ...named ? { manifest: named } : {} };
9047
9252
  }
9048
9253
  async function scanRepos(opts) {
9049
9254
  const now2 = opts.now ?? Date.now;
9050
9255
  const readdir5 = opts.readdir ?? ((p) => fsReaddir(p, { withFileTypes: true }));
9051
- const readFile19 = opts.readFile ?? ((p) => readGitFile(p));
9256
+ const readFile20 = opts.readFile ?? ((p) => readGitFile(p));
9052
9257
  const depth = opts.limits?.depth ?? SCAN_DEPTH, maxDirs = opts.limits?.dirs ?? SCAN_DIRS, ms = opts.limits?.ms ?? SCAN_MS, maxRepos = opts.limits?.repos ?? SCAN_REPOS;
9053
9258
  const deadline = now2() + ms;
9054
9259
  let timer2;
@@ -9083,11 +9288,11 @@ async function scanRepos(opts) {
9083
9288
  if (!entries) return;
9084
9289
  const dot = entries.find((e) => e.name === ".git" && (e.isDirectory() || e.isFile()));
9085
9290
  const manifest = dot ? MANIFESTS.find((m) => entries.some((e) => e.name === m && e.isFile())) : void 0;
9086
- if (dot) found.push(describe(d.path, dot.isFile(), opts.home, readFile19, manifest));
9291
+ if (dot) found.push(describe(d.path, dot.isFile(), opts.home, readFile20, manifest));
9087
9292
  if (dot && d.path !== home2 || d.depth >= depth) return;
9088
9293
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP.has(e.name)).map((e) => e.name).sort();
9089
9294
  for (const name of names) {
9090
- const path3 = join12(d.path, name);
9295
+ const path3 = join13(d.path, name);
9091
9296
  if (!seen.has(path3)) {
9092
9297
  seen.add(path3);
9093
9298
  queue.push({ path: path3, depth: d.depth + 1 });
@@ -9131,23 +9336,23 @@ var init_scan2 = __esm({
9131
9336
  });
9132
9337
 
9133
9338
  // src/daemon/folders.ts
9134
- import { mkdir as mkdir8, readdir as readdir3, stat as stat3 } from "fs/promises";
9135
- import { homedir as homedir6 } from "os";
9136
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join13, resolve as resolve4 } from "path";
9339
+ import { mkdir as mkdir8, readdir as readdir3, stat as stat4 } from "fs/promises";
9340
+ import { homedir as homedir7 } from "os";
9341
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join14, resolve as resolve4 } from "path";
9137
9342
  async function resolveExact(raw, home2) {
9138
9343
  const expanded = raw === "~" ? home2 : raw.startsWith("~/") ? resolve4(home2, raw.slice(2)) : raw;
9139
9344
  if (!isAbsolute2(expanded)) throw new Error(`not an absolute path: ${raw}`);
9140
9345
  const path3 = resolve4(expanded);
9141
9346
  let s;
9142
9347
  try {
9143
- s = await stat3(path3);
9348
+ s = await stat4(path3);
9144
9349
  } catch {
9145
9350
  throw new Error(`no such folder: ${path3}`);
9146
9351
  }
9147
9352
  if (!s.isDirectory()) throw new Error(`not a folder: ${path3}`);
9148
9353
  return path3;
9149
9354
  }
9150
- async function resolveFolder(input, home2 = homedir6()) {
9355
+ async function resolveFolder(input, home2 = homedir7()) {
9151
9356
  return resolveExact(input.trim(), home2);
9152
9357
  }
9153
9358
  async function subfolders(path3) {
@@ -9156,7 +9361,7 @@ async function subfolders(path3) {
9156
9361
  if (d.isDirectory()) return d.name;
9157
9362
  if (!d.isSymbolicLink()) return null;
9158
9363
  try {
9159
- return (await stat3(join13(path3, d.name))).isDirectory() ? d.name : null;
9364
+ return (await stat4(join14(path3, d.name))).isDirectory() ? d.name : null;
9160
9365
  } catch {
9161
9366
  return null;
9162
9367
  }
@@ -9164,42 +9369,42 @@ async function subfolders(path3) {
9164
9369
  return found.filter((n) => n !== null).sort(byName.compare);
9165
9370
  }
9166
9371
  async function gitBranchOf(dir, git) {
9167
- const dot = join13(dir, ".git");
9372
+ const dot = join14(dir, ".git");
9168
9373
  let s;
9169
9374
  try {
9170
- s = await stat3(dot);
9375
+ s = await stat4(dot);
9171
9376
  } catch {
9172
9377
  return null;
9173
9378
  }
9174
9379
  if (s.isFile()) return (await inspectRepos([dir], { git }))[0]?.branch ?? null;
9175
9380
  return s.isDirectory() ? headBranch(dot) : null;
9176
9381
  }
9177
- async function listFolder(input, home2 = homedir6(), requestId3, git) {
9382
+ async function listFolder(input, home2 = homedir7(), requestId3, git) {
9178
9383
  const tag = requestId3 === void 0 ? {} : { requestId: requestId3 };
9179
9384
  const asked2 = input || "~";
9180
9385
  try {
9181
9386
  const path3 = await resolveExact(asked2, home2);
9182
9387
  const parent = dirname5(path3);
9183
9388
  const entries = await subfolders(path3);
9184
- const branches = await Promise.all(entries.map((name) => gitBranchOf(join13(path3, name), git)));
9389
+ const branches = await Promise.all(entries.map((name) => gitBranchOf(join14(path3, name), git)));
9185
9390
  const repos = entries.flatMap((name, i) => branches[i] === null ? [] : [{ name, branch: branches[i] }]);
9186
9391
  return { type: "folder.listing", path: path3, parent: parent === path3 ? null : parent, entries, repos, ...tag };
9187
9392
  } catch (e) {
9188
9393
  return { type: "folder.listing", path: asked2, parent: null, entries: [], error: e.message, ...tag };
9189
9394
  }
9190
9395
  }
9191
- async function createFolder(parent, name, home2 = homedir6(), requestId3) {
9396
+ async function createFolder(parent, name, home2 = homedir7(), requestId3) {
9192
9397
  const at = await listFolder(parent, home2, requestId3);
9193
9398
  if (at.error) return at;
9194
9399
  const clean = name.trim();
9195
9400
  if (!clean || clean === "." || clean === ".." || /[/\0]/.test(clean)) return { ...at, error: `invalid folder name: ${name}` };
9196
9401
  try {
9197
- await mkdir8(join13(at.path, clean));
9402
+ await mkdir8(join14(at.path, clean));
9198
9403
  } catch (e) {
9199
9404
  const code3 = e.code;
9200
9405
  return { ...at, error: code3 === "EEXIST" ? `already exists: ${clean}` : `cannot create ${clean}: ${e.message}` };
9201
9406
  }
9202
- return listFolder(join13(at.path, clean), home2, requestId3);
9407
+ return listFolder(join14(at.path, clean), home2, requestId3);
9203
9408
  }
9204
9409
  var byName;
9205
9410
  var init_folders = __esm({
@@ -9263,7 +9468,7 @@ function taskOf2(ref, ctx) {
9263
9468
  return taskClosed(t) ? no("closed", `task ${t.id} is closed`) : t;
9264
9469
  }
9265
9470
  function writable(s) {
9266
- return s.source === "hook_only" ? no("read_only", `${s.name} is read-only`) : s.state === "exited" ? no("exited", `${s.name} has exited`) : null;
9471
+ return s.source === "hook_only" || isOmpSeat2(s) ? no("read_only", `${s.name} is read-only`) : s.state === "exited" ? no("exited", `${s.name} has exited`) : null;
9267
9472
  }
9268
9473
  function cardRefusal(row3) {
9269
9474
  if (row3.action !== "session.decide" || !row3.detailCut || row3.args.decision !== "accept") return null;
@@ -9316,6 +9521,7 @@ var init_actions2 = __esm({
9316
9521
  "src/daemon/actions.ts"() {
9317
9522
  "use strict";
9318
9523
  init_src();
9524
+ init_seats();
9319
9525
  DECIDABLE = ["permission", "diff", "plan"];
9320
9526
  KIND = { permission: "permission", diff: "diff", plan: "plan" };
9321
9527
  no = (code3, refused) => ({ refused, code: code3 });
@@ -9435,13 +9641,13 @@ ${p.body}` : p.text) }
9435
9641
  });
9436
9642
 
9437
9643
  // src/chat/guide.ts
9438
- import { access, constants as constants2, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
9439
- import { delimiter, join as join14 } from "path";
9644
+ import { access, constants as constants2, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
9645
+ import { delimiter, join as join15 } from "path";
9440
9646
  import { fileURLToPath as fileURLToPath2 } from "url";
9441
9647
  async function readGuide(files = GUIDE_FILES) {
9442
9648
  for (const f of files) {
9443
9649
  try {
9444
- return await readFile7(f, "utf8");
9650
+ return await readFile8(f, "utf8");
9445
9651
  } catch {
9446
9652
  }
9447
9653
  }
@@ -9453,7 +9659,7 @@ async function installedAgents(path3) {
9453
9659
  for (const tool of ToolSchema.options) {
9454
9660
  for (const d of dirs) {
9455
9661
  try {
9456
- await access(join14(d, AGENT_BIN2[tool]), constants2.X_OK);
9662
+ await access(join15(d, AGENT_BIN2[tool]), constants2.X_OK);
9457
9663
  found.push(tool);
9458
9664
  break;
9459
9665
  } catch {
@@ -9485,7 +9691,7 @@ function ledgeMd(info) {
9485
9691
  ].join("\n");
9486
9692
  }
9487
9693
  async function writeLedgeMd(dir, info) {
9488
- await writeFile8(join14(dir, LEDGE_MD), ledgeMd(info));
9694
+ await writeFile8(join15(dir, LEDGE_MD), ledgeMd(info));
9489
9695
  }
9490
9696
  var LEDGE_MD, GUIDE_FILES, AGENT_BIN2, RISK;
9491
9697
  var init_guide = __esm({
@@ -9500,8 +9706,8 @@ var init_guide = __esm({
9500
9706
  });
9501
9707
 
9502
9708
  // src/chat/notes.ts
9503
- import { open as open2, readdir as readdir4, stat as stat4, writeFile as writeFile9 } from "fs/promises";
9504
- import { join as join15 } from "path";
9709
+ import { open as open3, readdir as readdir4, stat as stat5, writeFile as writeFile9 } from "fs/promises";
9710
+ import { join as join16 } from "path";
9505
9711
  function scalarValue(raw) {
9506
9712
  const v2 = raw.trim();
9507
9713
  const q = v2[0];
@@ -9533,7 +9739,7 @@ function copyName(file, taken) {
9533
9739
  return name;
9534
9740
  }
9535
9741
  async function head(path3, max) {
9536
- const fh = await open2(path3, "r");
9742
+ const fh = await open3(path3, "r");
9537
9743
  try {
9538
9744
  const buf2 = Buffer.alloc(max);
9539
9745
  const { bytesRead } = await fh.read(buf2, 0, max, 0);
@@ -9552,12 +9758,12 @@ async function writeNotes(dir, from, steps, max = NOTE_MAX2) {
9552
9758
  if (!files.length) lines.push(`- (none: the user can add Markdown files to \`${from}\`, with an optional front matter \`title\` and \`when\`)`);
9553
9759
  const taken = /* @__PURE__ */ new Set();
9554
9760
  for (const file of files.slice(0, NOTES_MAX)) {
9555
- const path3 = join15(from, file);
9761
+ const path3 = join16(from, file);
9556
9762
  let text7;
9557
9763
  let size;
9558
9764
  let mtime;
9559
9765
  try {
9560
- const st = await stat4(path3);
9766
+ const st = await stat5(path3);
9561
9767
  size = st.size;
9562
9768
  mtime = st.mtime.toISOString();
9563
9769
  text7 = await head(path3, max);
@@ -9574,15 +9780,15 @@ async function writeNotes(dir, from, steps, max = NOTE_MAX2) {
9574
9780
  const when = fields.when?.trim() ? `when: ${line(fields.when)}` : `first line: ${line(body.split("\n").find((l) => l.trim()) ?? "") || "(empty)"}`;
9575
9781
  const truncated = size > max;
9576
9782
  const name = copyName(file, taken);
9577
- await writeFile9(join15(dir, name), truncated ? `${text7}
9783
+ await writeFile9(join16(dir, name), truncated ? `${text7}
9578
9784
 
9579
9785
  [truncated by Ledge: the first ${max} of ${size} bytes]
9580
9786
  ` : text7);
9581
- steps.set(name, { kind: "repo", name: file, path: join15(from, file) });
9787
+ steps.set(name, { kind: "repo", name: file, path: join16(from, file) });
9582
9788
  lines.push(`- ${name} \u2014 \xAB ${title3} \xBB \u2014 ${when} \u2014 modified ${mtime}${truncated ? ` \u2014 TRUNCATED: only the first ${max} of ${size} bytes are copied` : ""}`);
9583
9789
  }
9584
9790
  if (files.length > NOTES_MAX) lines.push(`- (${files.length - NOTES_MAX} more notes not copied: Ledge copies ${NOTES_MAX} at most)`);
9585
- await writeFile9(join15(dir, NOTES_INDEX), [...lines, ""].join("\n"));
9791
+ await writeFile9(join16(dir, NOTES_INDEX), [...lines, ""].join("\n"));
9586
9792
  }
9587
9793
  var NOTE_MAX2, NOTES_MAX, NOTES_INDEX, unquote, line;
9588
9794
  var init_notes = __esm({
@@ -9601,8 +9807,8 @@ var init_notes = __esm({
9601
9807
 
9602
9808
  // src/chat/context.ts
9603
9809
  import { mkdir as mkdir9, rm as rm2, writeFile as writeFile10 } from "fs/promises";
9604
- import { homedir as homedir7 } from "os";
9605
- import { basename as basename8, join as join16, resolve as resolve5 } from "path";
9810
+ import { homedir as homedir8 } from "os";
9811
+ import { basename as basename8, join as join17, resolve as resolve5 } from "path";
9606
9812
  async function writeContext(dir, input) {
9607
9813
  await rm2(dir, { recursive: true, force: true });
9608
9814
  await mkdir9(dir, { recursive: true });
@@ -9631,29 +9837,29 @@ async function writeContext(dir, input) {
9631
9837
  const pane = await input.capture(s).catch(() => null);
9632
9838
  if (pane?.length) {
9633
9839
  const f = `pane-${s.id}.txt`;
9634
- await writeFile10(join16(dir, f), pane.join("\n"));
9840
+ await writeFile10(join17(dir, f), pane.join("\n"));
9635
9841
  steps.set(f, { kind: "pane", id: s.id, name: s.name });
9636
9842
  lines.push(`- terminal, last lines: ${f}`);
9637
9843
  }
9638
9844
  if (s.lastReply?.text.trim()) {
9639
9845
  const f = `reply-${s.id}.md`;
9640
- await writeFile10(join16(dir, f), s.lastReply.text);
9846
+ await writeFile10(join17(dir, f), s.lastReply.text);
9641
9847
  steps.set(f, { kind: "reply", id: s.id, name: s.name });
9642
9848
  lines.push(`- last reply (${s.lastReply.at}): ${f}`);
9643
9849
  }
9644
9850
  const g = await input.git(s.cwd).catch(() => null);
9645
9851
  if (g) {
9646
9852
  const f = `git-${s.id}.txt`;
9647
- await writeFile10(join16(dir, f), g);
9853
+ await writeFile10(join17(dir, f), g);
9648
9854
  steps.set(f, { kind: "git", id: s.id, name: s.name });
9649
9855
  lines.push(`- git status, log and diff stat: ${f}`);
9650
9856
  }
9651
9857
  lines.push("");
9652
9858
  }
9653
- await writeFile10(join16(dir, "sessions.md"), lines.join("\n"));
9859
+ await writeFile10(join17(dir, "sessions.md"), lines.join("\n"));
9654
9860
  steps.set("sessions.md", { kind: "sessions" });
9655
9861
  const recents = input.recents ?? [];
9656
- await writeFile10(join16(dir, "folders.md"), [
9862
+ await writeFile10(join17(dir, "folders.md"), [
9657
9863
  "# Recent folders",
9658
9864
  "",
9659
9865
  "The folders the user started sessions in, most recent first.",
@@ -9661,7 +9867,7 @@ async function writeContext(dir, input) {
9661
9867
  ...recents.length ? recents.map((r) => `- ${r.project}: ${r.cwd} (last used ${r.lastAt})`) : ["- (none yet)"],
9662
9868
  ""
9663
9869
  ].join("\n"));
9664
- if (withProjects) await writeFile10(join16(dir, "projects.md"), [
9870
+ if (withProjects) await writeFile10(join17(dir, "projects.md"), [
9665
9871
  "# Projects",
9666
9872
  "",
9667
9873
  ...projects.projects.length ? projects.projects.flatMap((p) => [`## ${oneLine(p.name)}`, ...p.description.trim() ? [oneLine(p.description)] : [], "", ...p.repos.map((r) => `- ${r.machine}: ${r.path}`), ""]) : ["No projects.", ""],
@@ -9679,7 +9885,7 @@ async function writeContext(dir, input) {
9679
9885
  async function writeTasksContext(dir, taskList, steps) {
9680
9886
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9681
9887
  const shown = taskList.filter((t) => !taskClosed(t) || t.updatedAt.slice(0, 10) === day);
9682
- await writeFile10(join16(dir, "tasks.md"), [
9888
+ await writeFile10(join17(dir, "tasks.md"), [
9683
9889
  "# Tasks",
9684
9890
  "",
9685
9891
  "Open tasks and those closed today. A giver `Ledge` is the user, through you.",
@@ -9691,7 +9897,7 @@ async function writeTasksContext(dir, taskList, steps) {
9691
9897
  }
9692
9898
  function coversHome(path3) {
9693
9899
  const p = resolve5(path3);
9694
- const home2 = resolve5(homedir7());
9900
+ const home2 = resolve5(homedir8());
9695
9901
  return p === home2 || home2.startsWith(p.endsWith("/") ? p : `${p}/`);
9696
9902
  }
9697
9903
  function stepFor(path3, ctx) {
@@ -9730,14 +9936,14 @@ var LEDGE_VERSION;
9730
9936
  var init_version = __esm({
9731
9937
  "src/version.ts"() {
9732
9938
  "use strict";
9733
- LEDGE_VERSION = true ? "0.40.0" : "0.0.0-dev";
9939
+ LEDGE_VERSION = true ? "0.41.0" : "0.0.0-dev";
9734
9940
  }
9735
9941
  });
9736
9942
 
9737
9943
  // src/chat/forge.ts
9738
9944
  import { execFile as execFile4 } from "child_process";
9739
9945
  import { writeFile as writeFile11 } from "fs/promises";
9740
- import { join as join17 } from "path";
9946
+ import { join as join18 } from "path";
9741
9947
  function forgeOf(remote) {
9742
9948
  const id = repositoryId(remote);
9743
9949
  if (!id) return null;
@@ -9802,12 +10008,12 @@ function createForge(deps = {}) {
9802
10008
  return;
9803
10009
  }
9804
10010
  const parts2 = await Promise.all(local.map((r) => section(r.path)));
9805
- await writeFile11(join17(ctx.dir, file), parts2.flatMap((s) => s.lines).join("\n"));
10011
+ await writeFile11(join18(ctx.dir, file), parts2.flatMap((s) => s.lines).join("\n"));
9806
10012
  const sources = parts2.flatMap((s) => s.source ? [s.source] : []);
9807
10013
  ctx.steps.set(file, { kind: "repo", name, path: sources.join(", ") || file });
9808
10014
  index2.push(`- ${name}: ${file}`);
9809
10015
  }));
9810
- await writeFile11(join17(ctx.dir, "forge.md"), [...index2, ""].join("\n"));
10016
+ await writeFile11(join18(ctx.dir, "forge.md"), [...index2, ""].join("\n"));
9811
10017
  }
9812
10018
  };
9813
10019
  }
@@ -10074,7 +10280,7 @@ var init_codex2 = __esm({
10074
10280
  // src/chat/cursor.ts
10075
10281
  import { spawn as nodeSpawn5 } from "child_process";
10076
10282
  import { copyFileSync, writeFileSync } from "fs";
10077
- import { extname, join as join18 } from "path";
10283
+ import { extname, join as join19 } from "path";
10078
10284
  function cursorArgs(o) {
10079
10285
  return [
10080
10286
  "-p",
@@ -10116,10 +10322,10 @@ function parseCursorLine(line2) {
10116
10322
  }
10117
10323
  function runCursor(o, onEvent, spawnImpl = nodeSpawn5) {
10118
10324
  const [file = "cursor-agent", ...pre] = o.bin ?? ["cursor-agent"];
10119
- writeFileSync(join18(o.cwd, "AGENTS.md"), `${o.persona ?? buildPersona("cursor")}
10325
+ writeFileSync(join19(o.cwd, "AGENTS.md"), `${o.persona ?? buildPersona("cursor")}
10120
10326
  `);
10121
10327
  const copies = (o.images ?? []).map((img, n) => {
10122
- const to = join18(o.cwd, `image-${n + 1}${extname(img.path)}`);
10328
+ const to = join19(o.cwd, `image-${n + 1}${extname(img.path)}`);
10123
10329
  copyFileSync(img.path, to);
10124
10330
  return to;
10125
10331
  });
@@ -10203,9 +10409,9 @@ var init_omp = __esm({
10203
10409
 
10204
10410
  // src/chat/service.ts
10205
10411
  import { randomUUID as randomUUID7 } from "crypto";
10206
- import { mkdir as mkdir10, readFile as readFile8, rm as rm3, writeFile as writeFile12 } from "fs/promises";
10207
- import { homedir as homedir8 } from "os";
10208
- import { basename as basename9, dirname as dirname6, join as join19, resolve as resolve6 } from "path";
10412
+ import { mkdir as mkdir10, readFile as readFile9, rm as rm3, writeFile as writeFile12 } from "fs/promises";
10413
+ import { homedir as homedir9 } from "os";
10414
+ import { basename as basename9, dirname as dirname6, join as join20, resolve as resolve6 } from "path";
10209
10415
  function droppedNote(total, kept, reasons, counted) {
10210
10416
  const dropped = total - kept;
10211
10417
  if (dropped <= 0) return "";
@@ -10226,7 +10432,7 @@ function createChat(deps) {
10226
10432
  const now2 = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10227
10433
  const today = deps.today ?? (() => now2().slice(0, 10));
10228
10434
  const uuid = deps.uuid ?? randomUUID7;
10229
- const dayFile = join19(deps.home, "chat.json");
10435
+ const dayFile = join20(deps.home, "chat.json");
10230
10436
  let turns2 = [];
10231
10437
  let turnsDay = "";
10232
10438
  let busy = false;
@@ -10249,7 +10455,7 @@ function createChat(deps) {
10249
10455
  const day = today();
10250
10456
  if (!fresh && !freshNext) {
10251
10457
  try {
10252
- const saved = JSON.parse(await readFile8(dayFile, "utf8"));
10458
+ const saved = JSON.parse(await readFile9(dayFile, "utf8"));
10253
10459
  if (saved.day === day && saved.sessionId && (saved.tool ?? "claude") === tool) return { sessionId: saved.sessionId, resume: true };
10254
10460
  } catch {
10255
10461
  }
@@ -10265,7 +10471,7 @@ function createChat(deps) {
10265
10471
  const p = await deps.tmux.capture(s.tmuxSession, PANE_LINES);
10266
10472
  return p === null ? null : p.split("\n");
10267
10473
  };
10268
- const contextDir = join19(deps.home, "chat", "context");
10474
+ const contextDir = join20(deps.home, "chat", "context");
10269
10475
  async function proposalFrom(raw, tool, qid) {
10270
10476
  const parsed = parseActionBlock("session", raw);
10271
10477
  if ("error" in parsed) {
@@ -10342,7 +10548,7 @@ function createChat(deps) {
10342
10548
  for (const r of block2.repos) {
10343
10549
  try {
10344
10550
  const path3 = await resolveFolder(r.path, deps.userHome);
10345
- const wide = tooWide(path3, resolve6(deps.userHome ?? homedir8()));
10551
+ const wide = tooWide(path3, resolve6(deps.userHome ?? homedir9()));
10346
10552
  if (wide) {
10347
10553
  log(`chat ${qid}: project path dropped: ${wide}: ${path3}`);
10348
10554
  continue;
@@ -10458,7 +10664,7 @@ function createChat(deps) {
10458
10664
  let resultError = null;
10459
10665
  const handle2 = runTool(tool, { question: mine.told.text ? `${mine.told.text}
10460
10666
 
10461
- ${turn.question}` : turn.question, cwd: ctx.dir, addDirs: ctx.repoPaths, sessionId: sessionId2, resume, env, bin: deps.bins?.[tool] ?? deps.bin, ...images.length > 0 ? { images } : {}, ...tool === "omp" ? { sessionDir: join19(deps.home, "chat", "omp", sessionId2) } : {} }, (e) => {
10667
+ ${turn.question}` : turn.question, cwd: ctx.dir, addDirs: ctx.repoPaths, sessionId: sessionId2, resume, env, bin: deps.bins?.[tool] ?? deps.bin, ...images.length > 0 ? { images } : {}, ...tool === "omp" ? { sessionDir: join20(deps.home, "chat", "omp", sessionId2) } : {} }, (e) => {
10462
10668
  if (e.type === "tool") {
10463
10669
  const step3 = e.path ? stepFor(e.path, ctx) : null;
10464
10670
  if (step3 && !turn.steps.some((s) => sameStep(s, step3))) {
@@ -10745,8 +10951,8 @@ var init_service = __esm({
10745
10951
  });
10746
10952
 
10747
10953
  // src/chat/threads.ts
10748
- import { readFile as readFile9, rm as rm4 } from "fs/promises";
10749
- import { join as join20 } from "path";
10954
+ import { readFile as readFile10, rm as rm4 } from "fs/promises";
10955
+ import { join as join21 } from "path";
10750
10956
  function createSlots(max = 3) {
10751
10957
  let used = 0;
10752
10958
  return {
@@ -10767,7 +10973,7 @@ function createChatThreads(deps) {
10767
10973
  const devices = /* @__PURE__ */ new Map();
10768
10974
  const emitters = /* @__PURE__ */ new Map();
10769
10975
  const dropping = /* @__PURE__ */ new Map();
10770
- const deviceHome = (id) => join20(deps.home, "mobile", "chat", id);
10976
+ const deviceHome = (id) => join21(deps.home, "mobile", "chat", id);
10771
10977
  const device = (id) => {
10772
10978
  checkId(id);
10773
10979
  if (dropping.has(id)) throw new Error("device thread is being dropped");
@@ -10796,7 +11002,7 @@ function createChatThreads(deps) {
10796
11002
  /** The PC's tool of the day file, whatever its day: `claude` without one. */
10797
11003
  async deviceTool() {
10798
11004
  try {
10799
- const t = JSON.parse(await readFile9(join20(deps.home, "chat.json"), "utf8")).tool;
11005
+ const t = JSON.parse(await readFile10(join21(deps.home, "chat.json"), "utf8")).tool;
10800
11006
  return t === "claude" || t === "codex" || t === "cursor" || t === "omp" ? t : "claude";
10801
11007
  } catch {
10802
11008
  return "claude";
@@ -10963,7 +11169,7 @@ var init_island2 = __esm({
10963
11169
 
10964
11170
  // src/daemon/documents.ts
10965
11171
  import { constants as constants3 } from "fs";
10966
- import { open as open3, realpath } from "fs/promises";
11172
+ import { open as open4, realpath } from "fs/promises";
10967
11173
  import { isAbsolute as isAbsolute3, dirname as dirname7, extname as extname2, resolve as resolve7 } from "path";
10968
11174
  async function readDocument(request2, context) {
10969
11175
  const source2 = request2.path;
@@ -10984,10 +11190,10 @@ async function readDocument(request2, context) {
10984
11190
  async function readAt(request2, path3) {
10985
11191
  let file;
10986
11192
  try {
10987
- file = await open3(path3, constants3.O_RDONLY | constants3.O_NONBLOCK);
10988
- const stat7 = await file.stat();
10989
- if (!stat7.isFile()) return failure(request2, "not_file");
10990
- if (stat7.size > DOCUMENT_MAX_BYTES) return failure(request2, "too_large");
11193
+ file = await open4(path3, constants3.O_RDONLY | constants3.O_NONBLOCK);
11194
+ const stat8 = await file.stat();
11195
+ if (!stat8.isFile()) return failure(request2, "not_file");
11196
+ if (stat8.size > DOCUMENT_MAX_BYTES) return failure(request2, "too_large");
10991
11197
  const chunk = Buffer.allocUnsafe(64 * 1024);
10992
11198
  const parts2 = [];
10993
11199
  let bytes4 = 0;
@@ -11010,7 +11216,7 @@ async function readAt(request2, path3) {
11010
11216
  path: await realpath(path3),
11011
11217
  text: text7,
11012
11218
  bytes: bytes4,
11013
- modifiedAt: stat7.mtime.toISOString()
11219
+ modifiedAt: stat8.mtime.toISOString()
11014
11220
  };
11015
11221
  } catch (e) {
11016
11222
  return failure(request2, errorCode(e));
@@ -11039,9 +11245,9 @@ var init_documents2 = __esm({
11039
11245
 
11040
11246
  // src/daemon/models.ts
11041
11247
  import { execFile as execFile5 } from "child_process";
11042
- import { readFile as readFile10 } from "fs/promises";
11043
- import { homedir as homedir9 } from "os";
11044
- import { join as join21 } from "path";
11248
+ import { readFile as readFile11 } from "fs/promises";
11249
+ import { homedir as homedir10 } from "os";
11250
+ import { join as join22 } from "path";
11045
11251
  function claudeModels(settingsModel) {
11046
11252
  const efforts = [...CLAUDE_EFFORTS];
11047
11253
  const models = CLAUDE_MODELS.map((m) => ({ ...m, efforts, ...m.id === settingsModel ? { isDefault: true } : {} }));
@@ -11085,15 +11291,15 @@ function codexConfig(toml) {
11085
11291
  function createModels(deps = {}) {
11086
11292
  const env = deps.env ?? (async () => process.env);
11087
11293
  const run4 = deps.run ?? execRun2(env);
11088
- const readText2 = deps.readText ?? ((p) => readFile10(p, "utf8").catch(() => ""));
11294
+ const readText2 = deps.readText ?? ((p) => readFile11(p, "utf8").catch(() => ""));
11089
11295
  const now2 = deps.now ?? Date.now, ttl = deps.ttlMs ?? MODELS_TTL_MS;
11090
11296
  const cache = /* @__PURE__ */ new Map();
11091
11297
  const flying = /* @__PURE__ */ new Map();
11092
11298
  async function ask3(tool) {
11093
11299
  const e = await env();
11094
- const home2 = e.HOME ?? homedir9();
11300
+ const home2 = e.HOME ?? homedir10();
11095
11301
  if (tool === "claude") {
11096
- const settings = await readText2(join21(e.CLAUDE_CONFIG_DIR ?? join21(home2, ".claude"), "settings.json"));
11302
+ const settings = await readText2(join22(e.CLAUDE_CONFIG_DIR ?? join22(home2, ".claude"), "settings.json"));
11097
11303
  let model;
11098
11304
  try {
11099
11305
  model = settings ? JSON.parse(settings).model : void 0;
@@ -11109,7 +11315,7 @@ function createModels(deps = {}) {
11109
11315
  if (r.code === null) throw new Error("not installed");
11110
11316
  if (r.code !== 0) throw new Error(r.stderr.trim().split("\n").pop() || `exit ${r.code}`);
11111
11317
  if (tool === "cursor") return parseCursorModels(r.stdout);
11112
- return parseCodexModels(r.stdout, codexConfig(await readText2(join21(e.CODEX_HOME ?? join21(home2, ".codex"), "config.toml"))));
11318
+ return parseCodexModels(r.stdout, codexConfig(await readText2(join22(e.CODEX_HOME ?? join22(home2, ".codex"), "config.toml"))));
11113
11319
  }
11114
11320
  return {
11115
11321
  list(tool) {
@@ -11160,9 +11366,9 @@ var init_models2 = __esm({
11160
11366
 
11161
11367
  // src/daemon/commands.ts
11162
11368
  import { spawn as nodeSpawn7 } from "child_process";
11163
- import { stat as stat5 } from "fs/promises";
11164
- import { homedir as homedir10 } from "os";
11165
- import { join as join22 } from "path";
11369
+ import { stat as stat6 } from "fs/promises";
11370
+ import { homedir as homedir11 } from "os";
11371
+ import { join as join23 } from "path";
11166
11372
  function claudeSlashRefusal(text7) {
11167
11373
  const m = /^\/(\S+)(\s+\S[\s\S]*)?$/.exec(text7.trim());
11168
11374
  if (!m) return null;
@@ -11271,13 +11477,13 @@ function probeClaude(o, spawnImpl = nodeSpawn7) {
11271
11477
  function createCommands(deps = {}) {
11272
11478
  const env = deps.env ?? (async () => process.env);
11273
11479
  const claude = deps.claude ?? ((cwd, e) => probeClaude({ ...deps.claudeBin ? { bin: deps.claudeBin } : {}, cwd, env: e }));
11274
- const mtime = deps.mtime ?? ((p) => stat5(p).then((s) => s.mtimeMs, () => null));
11480
+ const mtime = deps.mtime ?? ((p) => stat6(p).then((s) => s.mtimeMs, () => null));
11275
11481
  const now2 = deps.now ?? Date.now, ttl = deps.ttlMs ?? COMMANDS_TTL_MS;
11276
11482
  const cache = /* @__PURE__ */ new Map();
11277
11483
  const flying = /* @__PURE__ */ new Map();
11278
11484
  async function mark(cwd, e) {
11279
- const home2 = e.CLAUDE_CONFIG_DIR ?? join22(e.HOME ?? homedir10(), ".claude");
11280
- const paths2 = [join22(cwd, ".claude", "commands"), join22(cwd, ".claude", "skills"), join22(home2, "commands"), join22(home2, "skills"), join22(home2, "plugins", "installed_plugins.json")];
11485
+ const home2 = e.CLAUDE_CONFIG_DIR ?? join23(e.HOME ?? homedir11(), ".claude");
11486
+ const paths2 = [join23(cwd, ".claude", "commands"), join23(cwd, ".claude", "skills"), join23(home2, "commands"), join23(home2, "skills"), join23(home2, "plugins", "installed_plugins.json")];
11281
11487
  return (await Promise.all(paths2.map(mtime))).join(",");
11282
11488
  }
11283
11489
  return {
@@ -11455,7 +11661,7 @@ var init_rigSpec = __esm({
11455
11661
  });
11456
11662
 
11457
11663
  // src/daemon/openrigRoles.ts
11458
- import { basename as basename10, dirname as dirname8, isAbsolute as isAbsolute5, join as join23, resolve as resolve9, sep as sep3 } from "path";
11664
+ import { basename as basename10, dirname as dirname8, isAbsolute as isAbsolute5, join as join24, resolve as resolve9, sep as sep3 } from "path";
11459
11665
  import { parse as parseYaml, stringify as stringify2, YAMLParseError } from "yaml";
11460
11666
  function readLibrary(listed2) {
11461
11667
  const out = [];
@@ -11487,15 +11693,15 @@ function parseSpec(text7) {
11487
11693
  }
11488
11694
  }
11489
11695
  function specPath(p, specDir, home2, openrigHome) {
11490
- if (p.startsWith("openrig-home:")) return join23(openrigHome, p.slice("openrig-home:".length));
11696
+ if (p.startsWith("openrig-home:")) return join24(openrigHome, p.slice("openrig-home:".length));
11491
11697
  if (p === "~") return home2;
11492
- if (p.startsWith("~/")) return join23(home2, p.slice(2));
11698
+ if (p.startsWith("~/")) return join24(home2, p.slice(2));
11493
11699
  return isAbsolute5(p) ? resolve9(p) : resolve9(specDir, p);
11494
11700
  }
11495
11701
  function importPath(ref, specDir) {
11496
11702
  const p = ref.startsWith("local:") ? resolve9(specDir, ref.slice(6)) : ref.startsWith("path:") ? resolve9(ref.slice(5)) : null;
11497
11703
  if (!p) return null;
11498
- return /\.ya?ml$/i.test(p) ? p : join23(p, "agent.yaml");
11704
+ return /\.ya?ml$/i.test(p) ? p : join24(p, "agent.yaml");
11499
11705
  }
11500
11706
  function declared(spec, ctx, ns, origin) {
11501
11707
  const out = [];
@@ -11724,7 +11930,7 @@ function layerOf(ownerRoot, path3, c) {
11724
11930
  if (c.agentDir && root2 === c.agentDir) return c.profileFiles.has(path3) ? "profile" : "agent";
11725
11931
  if (c.rigRoot && root2 === c.rigRoot) return path3 === c.cultureFile ? "culture" : c.memberFiles.has(path3) ? "member" : c.podFiles.has(path3) ? "pod" : "rig";
11726
11932
  if (/[/\\]@openrig[/\\]cli[/\\]/.test(root2) || /[/\\]daemon[/\\]assets$/.test(root2)) return "openrig";
11727
- if (under3(root2, join23(c.openrigHome, "state", "seats"))) return "seat";
11933
+ if (under3(root2, join24(c.openrigHome, "state", "seats"))) return "seat";
11728
11934
  return "unknown";
11729
11935
  }
11730
11936
  function layerOrigin(layer, c, path3) {
@@ -11734,7 +11940,7 @@ function layerOrigin(layer, c, path3) {
11734
11940
  case "profile":
11735
11941
  return { kind: "here", key: "profiles.startup.files" };
11736
11942
  case "culture":
11737
- return { kind: "inherited", from: "rig", ref: c.rig, key: "culture_file", ...c.rigRoot ? { path: join23(c.rigRoot, path3) } : {} };
11943
+ return { kind: "inherited", from: "rig", ref: c.rig, key: "culture_file", ...c.rigRoot ? { path: join24(c.rigRoot, path3) } : {} };
11738
11944
  case "rig":
11739
11945
  return { kind: "inherited", from: "rig", ref: c.rig };
11740
11946
  case "pod":
@@ -11762,12 +11968,12 @@ async function seatView(s, spec, profile, io, openrigHome, plugins) {
11762
11968
  const cwdV = str10(node2.cwd) ?? str10(shown.cwd);
11763
11969
  const cwd = cwdV ? { value: cwdV, origin: memberOrigin("cwd") } : { value: null, origin: { kind: "unknown", why: "not_exposed" } };
11764
11970
  const cultureV = str10(s.rigDoc.culture_file) ?? str10(s.rigDoc.cultureFile);
11765
- const cultureFile = sourced(cultureV, { kind: "inherited", from: "rig", ref: s.rig, key: "culture_file", ...cultureV && s.rigRoot ? { path: join23(s.rigRoot, cultureV) } : {} });
11971
+ const cultureFile = sourced(cultureV, { kind: "inherited", from: "rig", ref: s.rig, key: "culture_file", ...cultureV && s.rigRoot ? { path: join24(s.rigRoot, cultureV) } : {} });
11766
11972
  const base3 = s.rigRoot ?? (s.rigSpecPath ? dirname8(s.rigSpecPath) : "/");
11767
11973
  const memberStartup = writtenStartup(m.startup, "startup", "member", base3, (key) => memberOrigin(key));
11768
11974
  const pod = s.written?.pod ?? {};
11769
11975
  const effectiveRuntime = str10(node2.runtime) ?? runtime.value ?? void 0;
11770
- const into = cwdV ? join23(cwdV, guidanceFile(effectiveRuntime, s.rigDoc)) : void 0;
11976
+ const into = cwdV ? join24(cwdV, guidanceFile(effectiveRuntime, s.rigDoc)) : void 0;
11771
11977
  const profileName = str10(m.profile) ?? str10(shown.profile) ?? "default";
11772
11978
  const lc = {
11773
11979
  agentDir: s.agentDir,
@@ -12003,10 +12209,14 @@ var init_openrigRoles2 = __esm({
12003
12209
  import { execFile as execFile6 } from "child_process";
12004
12210
  import { get as httpGet } from "http";
12005
12211
  import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
12006
- import { cp, mkdir as mkdir11, open as openFile, readFile as readFile11, stat as statFile, writeFile as writeFile13 } from "fs/promises";
12212
+ import { cp, mkdir as mkdir11, open as openFile, readFile as readFile12, stat as statFile, writeFile as writeFile13 } from "fs/promises";
12007
12213
  import { parse as parseYaml2 } from "yaml";
12008
- import { homedir as homedir11 } from "os";
12009
- import { basename as basename11, delimiter as delimiter2, dirname as dirname9, isAbsolute as isAbsolute6, join as join24, resolve as resolve10, sep as sep4 } from "path";
12214
+ import { homedir as homedir12 } from "os";
12215
+ import { basename as basename11, delimiter as delimiter2, dirname as dirname9, isAbsolute as isAbsolute6, join as join25, resolve as resolve10, sep as sep4 } from "path";
12216
+ function specSource(raw) {
12217
+ const s = str11(raw);
12218
+ return s === "builtin" || s?.startsWith("builtin_") ? "builtin" : "user";
12219
+ }
12010
12220
  function toSeat(raw) {
12011
12221
  const n = obj9(raw);
12012
12222
  const logicalId = str11(n.logicalId), session = str11(n.canonicalSessionName);
@@ -12079,17 +12289,17 @@ function findRig(env, exists = existsSync2, list3 = (dir) => {
12079
12289
  return [];
12080
12290
  }
12081
12291
  }) {
12082
- for (const dir of (env.PATH ?? "").split(delimiter2)) if (dir && exists(join24(dir, "rig"))) return dir;
12083
- const home2 = env.HOME ?? homedir11();
12084
- const managed = (root2, sub) => list3(root2).filter((v2) => /^v?\d/.test(v2)).sort(byVersionDesc).map((v2) => join24(root2, v2, sub));
12292
+ for (const dir of (env.PATH ?? "").split(delimiter2)) if (dir && exists(join25(dir, "rig"))) return dir;
12293
+ const home2 = env.HOME ?? homedir12();
12294
+ const managed = (root2, sub) => list3(root2).filter((v2) => /^v?\d/.test(v2)).sort(byVersionDesc).map((v2) => join25(root2, v2, sub));
12085
12295
  const candidates = [
12086
- ...managed(join24(home2, ".local", "share", "nvm"), "bin"),
12087
- ...managed(join24(env.NVM_DIR ?? join24(home2, ".nvm"), "versions", "node"), "bin"),
12088
- join24(home2, ".volta", "bin"),
12089
- join24(home2, ".npm-global", "bin"),
12090
- join24(home2, ".local", "share", "pnpm")
12296
+ ...managed(join25(home2, ".local", "share", "nvm"), "bin"),
12297
+ ...managed(join25(env.NVM_DIR ?? join25(home2, ".nvm"), "versions", "node"), "bin"),
12298
+ join25(home2, ".volta", "bin"),
12299
+ join25(home2, ".npm-global", "bin"),
12300
+ join25(home2, ".local", "share", "pnpm")
12091
12301
  ];
12092
- return candidates.find((dir) => exists(join24(dir, "rig"))) ?? null;
12302
+ return candidates.find((dir) => exists(join25(dir, "rig"))) ?? null;
12093
12303
  }
12094
12304
  function readCheck(output) {
12095
12305
  const j = obj9(parse2(output));
@@ -12118,11 +12328,11 @@ function localGet(port, path3, timeoutMs) {
12118
12328
  function createOpenRig(deps = {}) {
12119
12329
  const run4 = deps.run ?? execRun3(deps.env ?? (async () => process.env));
12120
12330
  const files = deps.fs ?? realFs;
12121
- const home2 = deps.home ?? homedir11();
12122
- const rigsDir = deps.rigsDir ?? join24(home2, ".ledge", "rigs");
12331
+ const home2 = deps.home ?? homedir12();
12332
+ const rigsDir = deps.rigsDir ?? join25(home2, ".ledge", "rigs");
12123
12333
  const now2 = deps.now ?? Date.now;
12124
12334
  const roleIo = deps.roleIo ?? realRoleIo;
12125
- const openrigHome = deps.openrigHome ?? process.env.OPENRIG_HOME ?? join24(home2, ".openrig");
12335
+ const openrigHome = deps.openrigHome ?? process.env.OPENRIG_HOME ?? join25(home2, ".openrig");
12126
12336
  const http = deps.http ?? (async (path3) => {
12127
12337
  const port = (await state2()).daemon.port;
12128
12338
  if (!port) return void 0;
@@ -12206,7 +12416,7 @@ function createOpenRig(deps = {}) {
12206
12416
  const e = obj9(raw), name = str11(e.name), path3 = str11(e.resolvedSourcePath) ?? str11(e.sourcePath);
12207
12417
  if (e.kind !== "rig" || !name || !path3 || name === KERNEL_RIG) continue;
12208
12418
  const text7 = await files.read(path3).catch(() => null);
12209
- const spec = text7 === null ? null : readSpec(text7, { name, source: e.sourceType === "builtin" ? "builtin" : "user", path: path3, ...str11(e.summary) ? { summary: str11(e.summary).trim() } : {} });
12419
+ const spec = text7 === null ? null : readSpec(text7, { name, source: specSource(e.sourceType), path: path3, ...str11(e.summary) ? { summary: str11(e.summary).trim() } : {} });
12210
12420
  if (spec) out.push(spec);
12211
12421
  }
12212
12422
  return { specs: out.sort((a, b3) => a.source === b3.source ? a.name.localeCompare(b3.name) : a.source === "user" ? -1 : 1) };
@@ -12216,7 +12426,7 @@ function createOpenRig(deps = {}) {
12216
12426
  if (acting) return { ok: false, plan: plan2, commands: [], error: "busy" };
12217
12427
  acting = true;
12218
12428
  try {
12219
- const cwd = l.cwd === "~" ? home2 : l.cwd.startsWith("~/") ? join24(home2, l.cwd.slice(2)) : l.cwd;
12429
+ const cwd = l.cwd === "~" ? home2 : l.cwd.startsWith("~/") ? join25(home2, l.cwd.slice(2)) : l.cwd;
12220
12430
  if (!isAbsolute6(cwd) || !files.exists(cwd)) return { ok: false, plan: plan2, commands: [], error: "cwd_missing" };
12221
12431
  const listed2 = await specs();
12222
12432
  if (listed2.error) return { ok: false, plan: plan2, commands: [], error: listed2.error };
@@ -12228,17 +12438,17 @@ function createOpenRig(deps = {}) {
12228
12438
  const rename7 = l.name !== spec.name;
12229
12439
  if (changesSpec(l) || l.save || rename7 && !can2.name) {
12230
12440
  const composed = composeSpec(parseYaml2(text7), dirname9(spec.path), l);
12231
- dir = join24(rigsDir, l.name);
12441
+ dir = join25(rigsDir, l.name);
12232
12442
  await files.copyDir(dirname9(spec.path), dir);
12233
- await files.write(join24(dir, "rig.yaml"), composed.yaml);
12234
- source2 = join24(dir, "rig.yaml");
12443
+ await files.write(join25(dir, "rig.yaml"), composed.yaml);
12444
+ source2 = join25(dir, "rig.yaml");
12235
12445
  yaml = composed.yaml;
12236
12446
  seats = composed.seats;
12237
12447
  } else {
12238
12448
  yaml = text7;
12239
12449
  seats = specSeats(spec);
12240
12450
  }
12241
- if (!dir && rename7 && !plan2) await files.write(join24(rigsDir, l.name, "source.json"), JSON.stringify({ spec: spec.name, path: spec.path }));
12451
+ if (!dir && rename7 && !plan2) await files.write(join25(rigsDir, l.name, "source.json"), JSON.stringify({ spec: spec.name, path: spec.path }));
12242
12452
  const commands = [["up", source2, ...!dir && rename7 ? ["--name", l.name] : [], "--cwd", cwd, ...l.nonInterruptive ? ["--non-interruptive"] : [], ...plan2 ? ["--plan"] : ["--yes"], "--json"]];
12243
12453
  if (!plan2 && l.save && dir) commands.push(["specs", "add", dir, "--json"]);
12244
12454
  const shown = commands.map(showRigCommand);
@@ -12262,9 +12472,9 @@ function createOpenRig(deps = {}) {
12262
12472
  }
12263
12473
  }
12264
12474
  async function rootOf(rig) {
12265
- const mine = join24(rigsDir, rig);
12266
- if (files.exists(join24(mine, "rig.yaml"))) return mine;
12267
- const marker = await files.read(join24(mine, "source.json")).catch(() => null);
12475
+ const mine = join25(rigsDir, rig);
12476
+ if (files.exists(join25(mine, "rig.yaml"))) return mine;
12477
+ const marker = await files.read(join25(mine, "source.json")).catch(() => null);
12268
12478
  const path3 = marker === null ? void 0 : str11(obj9(parse2(marker)).path);
12269
12479
  if (path3) return dirname9(path3);
12270
12480
  const listed2 = await specs();
@@ -12294,7 +12504,7 @@ function createOpenRig(deps = {}) {
12294
12504
  const member = memberOf(show, a.pod, a.from.slice(a.pod.length + 1));
12295
12505
  const fragment2 = member ? fragmentOf(member, a.id, root2) : null;
12296
12506
  if (!fragment2) return { ok: false, commands: [], ...none, error: "rig_root_unknown" };
12297
- const path3 = join24(rigsDir, rigName, "seats", `${a.pod}.${a.id}.json`);
12507
+ const path3 = join25(rigsDir, rigName, "seats", `${a.pod}.${a.id}.json`);
12298
12508
  await files.write(path3, JSON.stringify(fragment2, null, 2));
12299
12509
  commands.push(["add", rig.id, a.pod, path3, ...root2 ? ["--rig-root", root2] : [], "--json"]);
12300
12510
  }
@@ -12344,7 +12554,7 @@ function createOpenRig(deps = {}) {
12344
12554
  const r = await run4(["spec", "show", rig.id, "--json"], STATUS_MS);
12345
12555
  if (r.code !== 0) continue;
12346
12556
  const root2 = rig.name === KERNEL_RIG ? null : await rootOf(rig.name).catch(() => null);
12347
- const specFile = root2 ? join24(root2, "rig.yaml") : null;
12557
+ const specFile = root2 ? join25(root2, "rig.yaml") : null;
12348
12558
  const text7 = specFile ? await roleIo.read(specFile) : null;
12349
12559
  const written = parseSpec(text7);
12350
12560
  const shown = obj9(parse2(r.stdout));
@@ -12508,14 +12718,14 @@ var init_openrig2 = __esm({
12508
12718
  if (!dir) return { code: null, stdout: "", stderr: "rig: command not found" };
12509
12719
  const e = { ...base3, PATH: [dir, base3.PATH].filter(Boolean).join(delimiter2), NO_COLOR: "1", CLICOLOR: "0", FORCE_COLOR: "0" };
12510
12720
  return new Promise((res) => {
12511
- execFile6(join24(dir, "rig"), args, { env: e, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
12721
+ execFile6(join25(dir, "rig"), args, { env: e, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
12512
12722
  const code3 = !err ? 0 : err.code === "ENOENT" ? null : typeof err.code === "number" ? err.code : 1;
12513
12723
  res({ code: code3, stdout: String(stdout), stderr: String(stderr) || (err && code3 !== 0 ? err.message : "") });
12514
12724
  });
12515
12725
  });
12516
12726
  };
12517
12727
  realRoleIo = {
12518
- read: (p) => readFile11(p, "utf8").catch(() => null),
12728
+ read: (p) => readFile12(p, "utf8").catch(() => null),
12519
12729
  stat: (p) => statFile(p).then((st) => ({ size: st.size, mtimeMs: st.mtimeMs, file: st.isFile() })).catch(() => null),
12520
12730
  head: async (p, max) => {
12521
12731
  const h = await openFile(p, "r").catch(() => null);
@@ -12533,7 +12743,7 @@ var init_openrig2 = __esm({
12533
12743
  FILES_TTL_MS = 5 * 6e4;
12534
12744
  HTTP_MS = 3e3;
12535
12745
  realFs = {
12536
- read: (p) => readFile11(p, "utf8"),
12746
+ read: (p) => readFile12(p, "utf8"),
12537
12747
  exists: (p) => existsSync2(p),
12538
12748
  copyDir: (from, to) => cp(from, to, { recursive: true, force: true, filter: (src) => basename11(src) !== "rig.yaml" }),
12539
12749
  write: async (p, text7) => {
@@ -12553,7 +12763,7 @@ function goalOf(tags) {
12553
12763
  return t ? t.slice(GOAL_TAG.length + 1) || null : null;
12554
12764
  }
12555
12765
  function parseTeam(body) {
12556
- const m = BLOCK3.exec(body);
12766
+ const m = BLOCK4.exec(body);
12557
12767
  if (!m) return null;
12558
12768
  let raw;
12559
12769
  try {
@@ -12578,13 +12788,13 @@ function parseTeam(body) {
12578
12788
  launch: kind === "launch" ? { agents: typeof l.agents === "number" && Number.isInteger(l.agents) ? l.agents : null, runtimes: strings(l.runtimes), plan: text2(l.plan), writes: strings(l.writes), permissions: text2(l.permissions) } : null
12579
12789
  };
12580
12790
  }
12581
- var GOAL_TAG, goalTag, BLOCK3, obj10, text2, strings, roleOf3, withoutTeam;
12791
+ var GOAL_TAG, goalTag, BLOCK4, obj10, text2, strings, roleOf3, withoutTeam;
12582
12792
  var init_goalTeam = __esm({
12583
12793
  "src/daemon/goalTeam.ts"() {
12584
12794
  "use strict";
12585
12795
  GOAL_TAG = "ledge-goal";
12586
12796
  goalTag = (goalQitemId2) => `${GOAL_TAG}:${goalQitemId2}`;
12587
- BLOCK3 = /```ledge-team[^\n]*\n([\s\S]*?)```/;
12797
+ BLOCK4 = /```ledge-team[^\n]*\n([\s\S]*?)```/;
12588
12798
  obj10 = (v2) => v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : {};
12589
12799
  text2 = (v2) => typeof v2 === "string" && v2.trim() ? v2.trim() : null;
12590
12800
  strings = (v2) => Array.isArray(v2) ? v2.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim()) : [];
@@ -12592,7 +12802,7 @@ var init_goalTeam = __esm({
12592
12802
  const m = /^([A-Za-z0-9][A-Za-z0-9_]*)-([A-Za-z0-9][A-Za-z0-9_-]*)@/.exec(seat2);
12593
12803
  return m ? `${m[1]}.${m[2]}` : null;
12594
12804
  };
12595
- withoutTeam = (body) => body.replace(BLOCK3, "").trim();
12805
+ withoutTeam = (body) => body.replace(BLOCK4, "").trim();
12596
12806
  }
12597
12807
  });
12598
12808
 
@@ -13217,7 +13427,7 @@ function createRelay(d) {
13217
13427
  const now2 = d.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13218
13428
  const typing = d.typing ?? /* @__PURE__ */ new Set();
13219
13429
  const sending = /* @__PURE__ */ new Set();
13220
- const open5 = (id) => free(d.store.get(id)) && !typing.has(id) && !d.closing?.has(id);
13430
+ const open6 = (id) => free(d.store.get(id)) && !typing.has(id) && !d.closing?.has(id);
13221
13431
  const emitTo = (thread) => (m) => {
13222
13432
  try {
13223
13433
  d.threads.emitterOf(thread)?.(m);
@@ -13261,14 +13471,14 @@ function createRelay(d) {
13261
13471
  }
13262
13472
  function kick(id) {
13263
13473
  const q = queued().find((x) => x.relay.id === id);
13264
- if (!q || !open5(id)) return;
13474
+ if (!q || !open6(id)) return;
13265
13475
  void deliverAndKick(q.thread, q.qid, q.relay).catch((e) => log(`relay ${q.qid} failed: ${e.message}`));
13266
13476
  }
13267
13477
  async function deliverAndKick(thread, qid, r) {
13268
13478
  const refused = await deliver(thread, qid, r);
13269
13479
  if (refused) {
13270
13480
  if (queued().some((q) => q.relay.id === r.id)) kick(r.id);
13271
- else if (open5(r.id)) d.next?.(r.id);
13481
+ else if (open6(r.id)) d.next?.(r.id);
13272
13482
  }
13273
13483
  return refused;
13274
13484
  }
@@ -13285,7 +13495,7 @@ function createRelay(d) {
13285
13495
  }
13286
13496
  return;
13287
13497
  }
13288
- if (open5(id)) kick(id);
13498
+ if (open6(id)) kick(id);
13289
13499
  } catch (e) {
13290
13500
  log(`relay change handler failed: ${e.message}`);
13291
13501
  }
@@ -13303,7 +13513,7 @@ function createRelay(d) {
13303
13513
  mark(thread, qid, { error: m, code: STOPPED_CODE });
13304
13514
  throw new Error(m);
13305
13515
  }
13306
- if (open5(r.id)) {
13516
+ if (open6(r.id)) {
13307
13517
  if (queued().some((q) => q.relay.id === r.id)) {
13308
13518
  markQueued(thread, qid);
13309
13519
  kick(r.id);
@@ -13324,7 +13534,7 @@ function createRelay(d) {
13324
13534
  settle(id) {
13325
13535
  try {
13326
13536
  if (queued().some((q) => q.relay.id === id)) kick(id);
13327
- else if (open5(id)) d.next?.(id);
13537
+ else if (open6(id)) d.next?.(id);
13328
13538
  } catch (e) {
13329
13539
  log(`relay settle failed: ${e.message}`);
13330
13540
  }
@@ -13553,7 +13763,7 @@ var init_act = __esm({
13553
13763
 
13554
13764
  // src/daemon/handover.ts
13555
13765
  import { mkdirSync, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
13556
- import { join as join25 } from "path";
13766
+ import { join as join26 } from "path";
13557
13767
  function reliefOf(get, s) {
13558
13768
  let cur = s;
13559
13769
  for (let hops = 0; cur && hops < 50; hops++) {
@@ -13571,7 +13781,7 @@ function createHandovers(d) {
13571
13781
  const now2 = d.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13572
13782
  const briefMs = d.briefMs ?? BRIEF_MS;
13573
13783
  const waiting2 = /* @__PURE__ */ new Map();
13574
- const file = (id) => join25(d.dir, `${id}.md`);
13784
+ const file = (id) => join26(d.dir, `${id}.md`);
13575
13785
  const mark = (id, h) => {
13576
13786
  const cur = d.store.get(id);
13577
13787
  if (cur) d.store.upsert({ ...cur, handover: { ...h, at: now2() } });
@@ -14025,7 +14235,7 @@ var init_broadcast2 = __esm({
14025
14235
  });
14026
14236
 
14027
14237
  // src/daemon/context.ts
14028
- import { open as open4 } from "fs/promises";
14238
+ import { open as open5 } from "fs/promises";
14029
14239
  function longWindow(model) {
14030
14240
  if (!model) return false;
14031
14241
  if (model.includes("[1m]")) return true;
@@ -14033,7 +14243,7 @@ function longWindow(model) {
14033
14243
  return !!m && Number(m[1]) >= 5;
14034
14244
  }
14035
14245
  async function tail(path3) {
14036
- const f = await open4(path3, "r");
14246
+ const f = await open5(path3, "r");
14037
14247
  try {
14038
14248
  const { size } = await f.stat();
14039
14249
  const start = Math.max(0, size - TAIL_BYTES2);
@@ -14117,7 +14327,7 @@ var init_context2 = __esm({
14117
14327
  import { randomUUID as randomUUID8 } from "crypto";
14118
14328
  import { mkdtemp, rm as rm5 } from "fs/promises";
14119
14329
  import { tmpdir } from "os";
14120
- import { join as join26 } from "path";
14330
+ import { join as join27 } from "path";
14121
14331
  function parseRoute(answer, context) {
14122
14332
  const raw = answer.trim().replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
14123
14333
  const result = RouteResultSchema.parse(JSON.parse(raw));
@@ -14143,7 +14353,7 @@ function createRouter(deps) {
14143
14353
  let dir;
14144
14354
  let timer2;
14145
14355
  try {
14146
- dir = await mkdtemp(join26(tmpdir(), "ledge-routing-"));
14356
+ dir = await mkdtemp(join27(tmpdir(), "ledge-routing-"));
14147
14357
  const env = { ...await deps.env(), LEDGE_CHAT: "1" };
14148
14358
  delete env.ANTHROPIC_API_KEY;
14149
14359
  delete env.LEDGE_SESSION;
@@ -15173,15 +15383,15 @@ var init_operations = __esm({
15173
15383
  });
15174
15384
 
15175
15385
  // src/mobile/risk.ts
15176
- import { homedir as homedir12 } from "os";
15177
- import { resolve as resolve11, sep as sep5, join as join27 } from "path";
15386
+ import { homedir as homedir13 } from "os";
15387
+ import { resolve as resolve11, sep as sep5, join as join28 } from "path";
15178
15388
  function isSensitive(cwd, p) {
15179
15389
  if (p.includes("~")) return true;
15180
15390
  const full = resolve11(cwd, p);
15181
15391
  const segs = full.split(sep5);
15182
15392
  if (segs.includes(".git") || segs.some((s) => s.startsWith(".env"))) return true;
15183
- const home2 = homedir12();
15184
- return [join27(home2, ".ssh"), join27(home2, ".ledge")].some((d) => full === d || full.startsWith(d + sep5));
15393
+ const home2 = homedir13();
15394
+ return [join28(home2, ".ssh"), join28(home2, ".ledge")].some((d) => full === d || full.startsWith(d + sep5));
15185
15395
  }
15186
15396
  function classifyShell(command, cwd) {
15187
15397
  if (PUSH.test(command)) return high("pushes");
@@ -16951,9 +17161,13 @@ var init_notifier = __esm({
16951
17161
  // src/daemon/server.ts
16952
17162
  import { createServer } from "http";
16953
17163
  import { randomUUID as randomUUID10 } from "crypto";
16954
- import { homedir as homedir13 } from "os";
16955
- import { basename as basename12, join as join28, resolve as resolve12 } from "path";
17164
+ import { homedir as homedir14 } from "os";
17165
+ import { basename as basename12, join as join29, resolve as resolve12 } from "path";
16956
17166
  import { WebSocketServer, WebSocket as WebSocket3 } from "ws";
17167
+ function withoutHarness(s) {
17168
+ const { pending: _p, lastReply: _r, transcriptPath: _t, agentSessionId: _a, agentSessionFile: _f, model: _m, effort: _e, modelNext: _n, queue: _q, backend: _b, ...rest } = s;
17169
+ return { ...rest, helpers: [] };
17170
+ }
16957
17171
  function resumeKey(s) {
16958
17172
  if (s.tool === "omp") return s.agentSessionFile;
16959
17173
  if (s.agentSessionId) return s.agentSessionId;
@@ -17003,11 +17217,12 @@ async function startServer(opts) {
17003
17217
  const agentEnv2 = opts.agentEnv ?? (async () => ({ ...process.env, ...await loginShellEnv() }));
17004
17218
  const attachments = opts.attachments ?? new AttachmentStore(paths().attachments);
17005
17219
  const reducer = new CaptureReducer();
17006
- const threads = createChatThreads({ attachments, store, tmux, home: paths().dir, notesDir: join28(paths().dir, "context"), userHome: opts.home, env: agentEnv2, bin: opts.claudeBin, now: now2, projects: () => projects, forge: createForge({ env: agentEnv2 }), tasks: () => tasks.all() });
17220
+ const threads = createChatThreads({ attachments, store, tmux, home: paths().dir, notesDir: join29(paths().dir, "context"), userHome: opts.home, env: agentEnv2, bin: opts.claudeBin, now: now2, projects: () => projects, forge: createForge({ env: agentEnv2 }), tasks: () => tasks.all() });
17007
17221
  const chat = opts.chat ?? threads.desktop;
17008
17222
  const router = createRouter({ env: agentEnv2 });
17009
- const omp2 = opts.omp ?? createOmpHost({ store, env: agentEnv2, dir: join28(paths().dir, "omp"), now: now2, attachments });
17223
+ const omp2 = opts.omp ?? createOmpHost({ store, env: agentEnv2, dir: join29(paths().dir, "omp"), now: now2, attachments });
17010
17224
  const openrig = opts.openrig ?? createOpenRig({ env: agentEnv2 });
17225
+ const ompSeatReadOnly = (s) => new Refusal(`${s.name} is the OpenRig OMP seat ${s.rig?.seat ?? ""}: Ledge follows its state only`, "read_only");
17011
17226
  const rigOwned = (s) => new Refusal(`${s.name} is the OpenRig seat ${s.rig?.seat ?? s.tmuxSession ?? ""}: stop or restart it from OpenRig`, "read_only");
17012
17227
  const adoptSeat = (id, seat2) => {
17013
17228
  const session = store.get(id);
@@ -17020,9 +17235,22 @@ async function startServer(opts) {
17020
17235
  const seatId = (seat2) => `rig-${seat2.replace(/[^A-Za-z0-9._-]/g, "-")}`;
17021
17236
  async function discoverSeats(st) {
17022
17237
  if (st.daemon.state !== "running") return;
17023
- const followed = new Set(store.all().filter((x) => x.rig && x.state !== "exited").map((x) => x.rig.seat));
17238
+ const followed = new Map(store.all().filter((x) => x.rig && x.state !== "exited").map((x) => [x.rig.seat, x.id]));
17024
17239
  for (const r of st.rigs) for (const n of r.nodes) {
17025
- if (n.runtime !== "claude-code" && n.runtime !== "codex" || n.sessionStatus !== "running" || followed.has(n.session)) continue;
17240
+ const tool = SEAT_TOOLS[n.runtime];
17241
+ if (!tool || n.sessionStatus !== "running") continue;
17242
+ const live3 = followed.get(n.session);
17243
+ if (live3 !== void 0) {
17244
+ const s = store.get(live3);
17245
+ if (s && s.tool !== tool && (s.source === "rig" || s.source === "hook_only")) {
17246
+ if (s.source === "hook_only" && !await tmux.hasSession(n.session).catch(() => false)) continue;
17247
+ const cur = store.get(live3);
17248
+ if (!cur || cur.state === "exited" || cur.tool === tool) continue;
17249
+ store.upsert({ ...withoutHarness(cur), tool, state: "idle", source: "rig", tmuxSession: n.session, lastActivityAt: now2() });
17250
+ log(`openrig: seat ${n.session} now runs ${n.runtime}`);
17251
+ }
17252
+ continue;
17253
+ }
17026
17254
  if (!await tmux.hasSession(n.session).catch(() => false)) continue;
17027
17255
  const before2 = store.all().filter((x) => x.rig?.seat === n.session).sort((a, b3) => b3.lastActivityAt.localeCompare(a.lastActivityAt))[0];
17028
17256
  const id = before2?.id ?? seatId(n.session), old = store.get(id), at = now2();
@@ -17031,13 +17259,14 @@ async function startServer(opts) {
17031
17259
  if (old) {
17032
17260
  if (old.archivedAt) store.unarchive(id, at);
17033
17261
  const { exitedAt: _e, exitCode: _c, pending: _p, ...rest } = store.get(id);
17034
- store.upsert({ ...rest, state: "idle", tmuxSession: n.session, source: "rig", rig, lastActivityAt: at });
17262
+ const same2 = rest.tool === tool ? rest : { ...withoutHarness(rest), tool };
17263
+ store.upsert({ ...same2, state: "idle", tmuxSession: n.session, source: "rig", rig, lastActivityAt: at });
17035
17264
  } else {
17036
17265
  const name = pickName([...namesInUse(store.all()), ...heldNames(store.all())]);
17037
17266
  store.upsert({
17038
17267
  id,
17039
17268
  name,
17040
- tool: n.runtime === "codex" ? "codex" : "claude",
17269
+ tool,
17041
17270
  cwd,
17042
17271
  project: basename12(cwd) || r.name,
17043
17272
  title: `${name} \xB7 ${n.logicalId}`,
@@ -17051,10 +17280,21 @@ async function startServer(opts) {
17051
17280
  });
17052
17281
  log(`openrig: seat ${n.session} followed as ${name}`);
17053
17282
  }
17054
- followed.add(n.session);
17283
+ followed.set(n.session, id);
17284
+ }
17285
+ if (!st.rigs.some((r) => r.nodes.length > 0)) return;
17286
+ const listed2 = new Set(st.rigs.flatMap((r) => r.nodes.map((n) => n.session)));
17287
+ const unsure = new Set(st.rigs.filter((r) => r.seats > 0 && r.nodes.length === 0).map((r) => r.name));
17288
+ for (const s of store.all()) {
17289
+ if (isOmpSeat2(s) && s.rig && s.state === "exited" && !isArchived(s) && !listed2.has(s.rig.seat) && !unsure.has(s.rig.name)) {
17290
+ store.archive(s.id, now2());
17291
+ log(`openrig: seat ${s.rig.seat} is gone from OpenRig`);
17292
+ }
17055
17293
  }
17056
17294
  }
17057
17295
  const SEATS_EVERY_MS = 3e4;
17296
+ const ompSeats = opts.ompSeats ?? createOmpSeats({ store });
17297
+ ompSeats.start();
17058
17298
  const seatTimer = setInterval(() => {
17059
17299
  void openrig.state().then(discoverSeats).catch((e) => log(`openrig: seats not read: ${e.message}`));
17060
17300
  }, SEATS_EVERY_MS);
@@ -17100,7 +17340,7 @@ async function startServer(opts) {
17100
17340
  }).catch((e) => log(`openrig: seat ${seat2} not read: ${e.message}`));
17101
17341
  };
17102
17342
  const commands = opts.commands ?? createCommands({ env: agentEnv2, ...opts.claudeBin ? { claudeBin: opts.claudeBin } : {} });
17103
- const models = opts.models ?? createModels({ env: agentEnv2, omp: async () => ompChoices(await omp2.catalog(opts.home ?? homedir13())) });
17343
+ const models = opts.models ?? createModels({ env: agentEnv2, omp: async () => ompChoices(await omp2.catalog(opts.home ?? homedir14())) });
17104
17344
  const token = await ensureDaemonToken();
17105
17345
  const httpServer = createServer(unauthorized);
17106
17346
  const sockets = /* @__PURE__ */ new Set();
@@ -17140,7 +17380,7 @@ async function startServer(opts) {
17140
17380
  const islandFile = opts.islandFile ?? paths().island;
17141
17381
  let island = readIsland(islandFile);
17142
17382
  const islandMsg = () => ({ type: "island", prefs: island });
17143
- const contextFile = opts.contextFile ?? join28(paths().dir, "context.json");
17383
+ const contextFile = opts.contextFile ?? join29(paths().dir, "context.json");
17144
17384
  let contextPrefs = readContextPrefs(contextFile);
17145
17385
  const contextMsg = () => ({ type: "context.prefs", prefs: contextPrefs });
17146
17386
  const mobile = opts.mobile;
@@ -17239,7 +17479,7 @@ async function startServer(opts) {
17239
17479
  tasks,
17240
17480
  launch: (req) => launch3(req),
17241
17481
  close: (s) => closeSession(s),
17242
- dir: join28(paths().dir, "handovers"),
17482
+ dir: join29(paths().dir, "handovers"),
17243
17483
  now: now2,
17244
17484
  ...opts.handoverBriefMs !== void 0 ? { briefMs: opts.handoverBriefMs } : {}
17245
17485
  });
@@ -17515,6 +17755,7 @@ async function startServer(opts) {
17515
17755
  const s = store.get(id);
17516
17756
  if (!s) return unknownSession();
17517
17757
  if (s.source === "hook_only") return readOnly();
17758
+ if (isOmpSeat2(s)) return ompSeatReadOnly(s);
17518
17759
  if (closing.has(id)) return new Refusal("the session is being closed", "busy");
17519
17760
  const ids = how.attachments ?? [];
17520
17761
  const refs = [];
@@ -17848,7 +18089,7 @@ async function startServer(opts) {
17848
18089
  let scanning = null;
17849
18090
  function sharedScan() {
17850
18091
  if (scanning) return scanning;
17851
- const home2 = opts.home ?? homedir13();
18092
+ const home2 = opts.home ?? homedir14();
17852
18093
  const extra = extraRoots(home2, [...store.recents().map((r) => r.cwd), ...store.all().filter((s) => s.state !== "exited").map((s) => s.cwd)]);
17853
18094
  const walk = (opts.scan ?? scanRepos)({ home: home2, extra }).finally(() => {
17854
18095
  if (scanning === walk) scanning = null;
@@ -18127,6 +18368,7 @@ async function startServer(opts) {
18127
18368
  return void await omp2.interruptAndRestore(s.id).then(({ entries, imagesDropped }) => reply({ type: "session.queue.restored", id: s.id, texts: entries.map((e) => e.text), entries, ...imagesDropped ? { imagesDropped } : {} }), (e) => refuse(e.message, msg.id));
18128
18369
  }
18129
18370
  if (!s?.tmuxSession) return;
18371
+ if (isOmpSeat2(s)) return refuse(ompSeatReadOnly(s), msg.id);
18130
18372
  await tmux.sendKeys(s.tmuxSession, adapterFor(s.tool).interruptKeys());
18131
18373
  return;
18132
18374
  }
@@ -18357,7 +18599,7 @@ async function startServer(opts) {
18357
18599
  relay,
18358
18600
  launchChat: (thread, qid, rows) => {
18359
18601
  const c = thread === "desktop" ? threads.desktop : threads.device(thread.slice("device:".length));
18360
- const userHome = resolve12(opts.home ?? homedir13());
18602
+ const userHome = resolve12(opts.home ?? homedir14());
18361
18603
  return launchFromChat({
18362
18604
  chat: c,
18363
18605
  emit: threads.emitterOf(thread) ?? (() => {
@@ -18501,6 +18743,7 @@ async function startServer(opts) {
18501
18743
  close: () => new Promise((res) => {
18502
18744
  clearInterval(sweepTimer);
18503
18745
  clearInterval(seatTimer);
18746
+ ompSeats.stop();
18504
18747
  human.stop();
18505
18748
  if (goalsTimer) clearTimeout(goalsTimer);
18506
18749
  void reducer.close();
@@ -18520,7 +18763,7 @@ async function startServer(opts) {
18520
18763
  })
18521
18764
  };
18522
18765
  }
18523
- var CONTINUE_ARGS, RESUME_ARGS, MULTI_DIR_ARGS, MODEL_ARGS, EFFORT_ARGS, NoteNotTyped, DELETED_TTL_MS, RESPAWN_HOOKS_MS, ALLOWED_ORIGINS;
18766
+ var SEAT_TOOLS, CONTINUE_ARGS, RESUME_ARGS, MULTI_DIR_ARGS, MODEL_ARGS, EFFORT_ARGS, NoteNotTyped, DELETED_TTL_MS, RESPAWN_HOOKS_MS, ALLOWED_ORIGINS;
18524
18767
  var init_server = __esm({
18525
18768
  "src/daemon/server.ts"() {
18526
18769
  "use strict";
@@ -18540,6 +18783,7 @@ var init_server = __esm({
18540
18783
  init_spawn();
18541
18784
  init_host();
18542
18785
  init_catalog();
18786
+ init_seats();
18543
18787
  init_repos2();
18544
18788
  init_scan2();
18545
18789
  init_folders();
@@ -18578,6 +18822,7 @@ var init_server = __esm({
18578
18822
  init_challenges();
18579
18823
  init_webauthn();
18580
18824
  init_frames();
18825
+ SEAT_TOOLS = { "claude-code": "claude", codex: "codex", omp: "omp" };
18581
18826
  CONTINUE_ARGS = { claude: ["--continue"], codex: ["resume", "--last"], cursor: ["--continue"] };
18582
18827
  RESUME_ARGS = { claude: (id) => ["--resume", id], codex: (id) => ["resume", id], cursor: (id) => ["--resume", id] };
18583
18828
  MULTI_DIR_ARGS = { claude: (dirs) => ["--add-dir", ...dirs] };
@@ -18606,7 +18851,7 @@ var init_server = __esm({
18606
18851
  // src/tmux.ts
18607
18852
  import { execFile as execFile7 } from "child_process";
18608
18853
  import { existsSync as existsSync3 } from "fs";
18609
- import { join as join29 } from "path";
18854
+ import { join as join30 } from "path";
18610
18855
  function runFile(file, args, env, opts) {
18611
18856
  return new Promise((resolve13) => {
18612
18857
  execFile7(file, args, { maxBuffer: 4 * 1024 * 1024, env, ...opts?.timeoutMs ? { timeout: opts.timeoutMs, killSignal: "SIGTERM" } : {} }, (err, stdout, stderr) => {
@@ -18616,7 +18861,7 @@ function runFile(file, args, env, opts) {
18616
18861
  });
18617
18862
  }
18618
18863
  function systemdAvailable(env = process.env, exists = existsSync3) {
18619
- return !!env.XDG_RUNTIME_DIR && exists(join29(env.XDG_RUNTIME_DIR, "systemd", "private"));
18864
+ return !!env.XDG_RUNTIME_DIR && exists(join30(env.XDG_RUNTIME_DIR, "systemd", "private"));
18620
18865
  }
18621
18866
  function newSessionCommand(tmuxArgs, scoped, unit) {
18622
18867
  if (!scoped) return { file: "tmux", args: tmuxArgs };
@@ -18749,10 +18994,10 @@ __export(setup_exports, {
18749
18994
  systemdUnit: () => systemdUnit,
18750
18995
  writeBin: () => writeBin
18751
18996
  });
18752
- import { readFile as readFile12, writeFile as writeFile14, mkdir as mkdir12, rename as rename5, realpath as realpath2, rm as rm6, stat as stat6, lstat, readlink } from "fs/promises";
18997
+ import { readFile as readFile13, writeFile as writeFile14, mkdir as mkdir12, rename as rename5, realpath as realpath2, rm as rm6, stat as stat7, lstat, readlink } from "fs/promises";
18753
18998
  import { execFile as execFile8 } from "child_process";
18754
- import { homedir as homedir14, userInfo } from "os";
18755
- import { join as join30, dirname as dirname10 } from "path";
18999
+ import { homedir as homedir15, userInfo } from "os";
19000
+ import { join as join31, dirname as dirname10 } from "path";
18756
19001
  function mergeHooks(settings, events, command, file) {
18757
19002
  if (settings === void 0) settings = {};
18758
19003
  if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error(`${file} root is not an object, not modified`);
@@ -18800,7 +19045,7 @@ function mergeCursorHooks(config, command = "ledge hook cursor") {
18800
19045
  async function mergeJsonFile(path3, merge, name) {
18801
19046
  let raw = null;
18802
19047
  try {
18803
- raw = await readFile12(path3, "utf8");
19048
+ raw = await readFile13(path3, "utf8");
18804
19049
  } catch (e) {
18805
19050
  if (e.code !== "ENOENT") throw e;
18806
19051
  }
@@ -18836,7 +19081,7 @@ WantedBy=default.target
18836
19081
  async function writeIfChanged(path3, content3) {
18837
19082
  let current2 = null;
18838
19083
  try {
18839
- current2 = await readFile12(path3, "utf8");
19084
+ current2 = await readFile13(path3, "utf8");
18840
19085
  } catch (e) {
18841
19086
  if (e.code !== "ENOENT") throw e;
18842
19087
  }
@@ -18850,7 +19095,7 @@ async function writeIfChanged(path3, content3) {
18850
19095
  async function stopDetachedDaemon(pidFile) {
18851
19096
  let pid;
18852
19097
  try {
18853
- pid = Number((await readFile12(pidFile, "utf8")).trim());
19098
+ pid = Number((await readFile13(pidFile, "utf8")).trim());
18854
19099
  } catch {
18855
19100
  return null;
18856
19101
  }
@@ -18880,10 +19125,10 @@ async function stopDetachedDaemon(pidFile) {
18880
19125
  async function installService(opts = {}) {
18881
19126
  const out = [];
18882
19127
  const run4 = opts.run ?? execRunner;
18883
- const unitDir = opts.unitDir ?? join30(homedir14(), ".config", "systemd", "user");
19128
+ const unitDir = opts.unitDir ?? join31(homedir15(), ".config", "systemd", "user");
18884
19129
  const node2 = opts.node ?? await realpath2(process.execPath);
18885
19130
  const script = opts.script ?? await realpath2(process.argv[1]);
18886
- const changed = await writeIfChanged(join30(unitDir, SERVICE), systemdUnit({ node: node2, script }));
19131
+ const changed = await writeIfChanged(join31(unitDir, SERVICE), systemdUnit({ node: node2, script }));
18887
19132
  const reload = await run4("systemctl", ["--user", "daemon-reload"]);
18888
19133
  if (reload.code !== 0) throw new Error(`systemd user session unavailable (systemctl --user daemon-reload failed: ${reload.stderr.trim() || reload.code}); the daemon will still be started on demand by ledge claude`);
18889
19134
  const active = (await run4("systemctl", ["--user", "is-active", SERVICE])).code === 0;
@@ -18912,7 +19157,7 @@ async function installService(opts = {}) {
18912
19157
  }
18913
19158
  async function isDir(path3) {
18914
19159
  try {
18915
- return (await stat6(path3)).isDirectory();
19160
+ return (await stat7(path3)).isDirectory();
18916
19161
  } catch {
18917
19162
  return false;
18918
19163
  }
@@ -18921,7 +19166,7 @@ async function writeBin(opts) {
18921
19166
  const st = await lstat(opts.link).catch(() => null);
18922
19167
  const was = st?.isSymbolicLink() ? await readlink(opts.link) : null;
18923
19168
  if (was !== null && !was.endsWith("/dist/cli/index.js")) return null;
18924
- const current2 = st && was === null && st.isFile() ? await readFile12(opts.link, "utf8").catch(() => "") : "";
19169
+ const current2 = st && was === null && st.isFile() ? await readFile13(opts.link, "utf8").catch(() => "") : "";
18925
19170
  if (st && was === null && !current2.startsWith(BIN_MARK)) return null;
18926
19171
  const script = `${BIN_MARK}: the hooks run \`ledge\` wherever node is not on the PATH.
18927
19172
  exec ${shQuote(opts.node)} ${shQuote(opts.target)} "$@"
@@ -18937,24 +19182,24 @@ async function runSetup(opts = {}) {
18937
19182
  const out = [];
18938
19183
  await ensureLedgeDir();
18939
19184
  out.push("~/.ledge ready (tmux.conf written if missing)");
18940
- const settingsPath2 = opts.settingsPath ?? join30(homedir14(), ".claude", "settings.json");
19185
+ const settingsPath2 = opts.settingsPath ?? join31(homedir15(), ".claude", "settings.json");
18941
19186
  if (await mergeJsonFile(settingsPath2, mergeClaudeHooks, "settings.json")) out.push(`Claude Code hooks added in ${settingsPath2}`);
18942
19187
  else out.push("Claude Code hooks already present");
18943
- const codexDir = opts.codexDir ?? process.env.CODEX_HOME ?? join30(homedir14(), ".codex");
19188
+ const codexDir = opts.codexDir ?? process.env.CODEX_HOME ?? join31(homedir15(), ".codex");
18944
19189
  if (!await isDir(codexDir)) out.push(`Codex not found (${codexDir} missing), Codex hooks skipped`);
18945
- else if (await mergeJsonFile(join30(codexDir, "hooks.json"), mergeCodexHooks, "hooks.json")) {
18946
- out.push(`Codex hooks added in ${join30(codexDir, "hooks.json")}; Codex asks to review them on its next start: trust them there or from Ledge`);
19190
+ else if (await mergeJsonFile(join31(codexDir, "hooks.json"), mergeCodexHooks, "hooks.json")) {
19191
+ out.push(`Codex hooks added in ${join31(codexDir, "hooks.json")}; Codex asks to review them on its next start: trust them there or from Ledge`);
18947
19192
  } else out.push("Codex hooks already present");
18948
- const cursorDir = opts.cursorDir ?? join30(homedir14(), ".cursor");
19193
+ const cursorDir = opts.cursorDir ?? join31(homedir15(), ".cursor");
18949
19194
  if (!await isDir(cursorDir)) out.push(`Cursor Agent not found (${cursorDir} missing), Cursor Agent hooks skipped`);
18950
- else if (await mergeJsonFile(join30(cursorDir, "hooks.json"), mergeCursorHooks, "hooks.json")) out.push(`Cursor Agent hooks added in ${join30(cursorDir, "hooks.json")}`);
19195
+ else if (await mergeJsonFile(join31(cursorDir, "hooks.json"), mergeCursorHooks, "hooks.json")) out.push(`Cursor Agent hooks added in ${join31(cursorDir, "hooks.json")}`);
18951
19196
  else out.push("Cursor Agent hooks already present");
18952
19197
  if (opts.service ?? true) {
18953
19198
  const cli = opts.cli ?? process.argv[1] ?? "";
18954
19199
  if (cli.endsWith(".ts")) out.push("systemd service skipped: run setup from the built CLI (pnpm --filter @paradow/ledge build, then ledge setup)");
18955
19200
  else {
18956
19201
  const script = await realpath2(cli);
18957
- const line2 = await writeBin({ link: opts.link ?? join30(homedir14(), ".local", "bin", "ledge"), node: process.execPath, target: script });
19202
+ const line2 = await writeBin({ link: opts.link ?? join31(homedir15(), ".local", "bin", "ledge"), node: process.execPath, target: script });
18958
19203
  if (line2) out.push(line2);
18959
19204
  out.push(...await installService({ script, unitDir: opts.unitDir, run: opts.run }));
18960
19205
  }
@@ -19016,10 +19261,10 @@ var init_setup = __esm({
19016
19261
  });
19017
19262
 
19018
19263
  // src/update/guard.ts
19019
- import { readFile as readFile13 } from "fs/promises";
19264
+ import { readFile as readFile14 } from "fs/promises";
19020
19265
  async function tmuxServerAtRisk(opts = {}) {
19021
19266
  const runTmux = opts.runTmux ?? run2;
19022
- const readCgroup = opts.readCgroup ?? ((pid2) => readFile13(`/proc/${pid2}/cgroup`, "utf8"));
19267
+ const readCgroup = opts.readCgroup ?? ((pid2) => readFile14(`/proc/${pid2}/cgroup`, "utf8"));
19023
19268
  const r = await runTmux(["-L", opts.socket ?? "ledge", "list-sessions", "-F", "#{pid}"]);
19024
19269
  if (r.code !== 0) return false;
19025
19270
  const pid = r.stdout.split("\n").map((s) => s.trim()).find(Boolean);
@@ -19042,12 +19287,12 @@ var init_guard = __esm({
19042
19287
 
19043
19288
  // src/update/self.ts
19044
19289
  import { existsSync as existsSync4 } from "fs";
19045
- import { readFile as readFile14, realpath as realpath3 } from "fs/promises";
19046
- import { homedir as homedir15 } from "os";
19047
- import { dirname as dirname11, join as join31 } from "path";
19290
+ import { readFile as readFile15, realpath as realpath3 } from "fs/promises";
19291
+ import { homedir as homedir16 } from "os";
19292
+ import { dirname as dirname11, join as join32 } from "path";
19048
19293
  function gitRoot(script, exists = existsSync4) {
19049
19294
  for (let dir = dirname11(script); ; dir = dirname11(dir)) {
19050
- if (exists(join31(dir, ".git"))) return dir;
19295
+ if (exists(join32(dir, ".git"))) return dir;
19051
19296
  if (dirname11(dir) === dir) return null;
19052
19297
  }
19053
19298
  }
@@ -19057,10 +19302,10 @@ function installKind(script, exists = existsSync4) {
19057
19302
  }
19058
19303
  async function daemonInfo() {
19059
19304
  const script = await realpath3(process.argv[1] ?? "").catch(() => process.argv[1] ?? "");
19060
- const cgroup = await readFile14("/proc/self/cgroup", "utf8").catch(() => "");
19305
+ const cgroup = await readFile15("/proc/self/cgroup", "utf8").catch(() => "");
19061
19306
  const install = installKind(script);
19062
19307
  const root2 = install === "git" ? gitRoot(script) : null;
19063
- return { version: LEDGE_VERSION, install, systemd: underSystemd(process.env, cgroup), home: homedir15(), ...root2 ? { manual: manualCommand(root2) } : {} };
19308
+ return { version: LEDGE_VERSION, install, systemd: underSystemd(process.env, cgroup), home: homedir16(), ...root2 ? { manual: manualCommand(root2) } : {} };
19064
19309
  }
19065
19310
  var underSystemd, manualCommand;
19066
19311
  var init_self = __esm({
@@ -19074,7 +19319,7 @@ var init_self = __esm({
19074
19319
  });
19075
19320
 
19076
19321
  // src/update/run-update.ts
19077
- import { dirname as dirname12, join as join32 } from "path";
19322
+ import { dirname as dirname12, join as join33 } from "path";
19078
19323
  function createDaemonUpdater(deps) {
19079
19324
  let busy = false;
19080
19325
  let restarting = false;
@@ -19163,7 +19408,7 @@ var init_run_update = __esm({
19163
19408
  PACKAGE = "@paradow/ledge";
19164
19409
  NPM_TIMEOUT_MS = 3 * 6e4;
19165
19410
  RESTART_COMMAND = "systemctl --user restart ledge-daemon";
19166
- npmCommand = (execPath, version) => ({ file: join32(dirname12(execPath), "npm"), args: ["i", "-g", `${PACKAGE}@${version}`] });
19411
+ npmCommand = (execPath, version) => ({ file: join33(dirname12(execPath), "npm"), args: ["i", "-g", `${PACKAGE}@${version}`] });
19167
19412
  lastLines = (output, n = 3) => output.split("\n").map((l) => l.trimEnd()).filter(Boolean).slice(-n).join("\n");
19168
19413
  npmEnv = (execPath, env) => ({ ...env, PATH: `${dirname12(execPath)}:${env.PATH ?? ""}` });
19169
19414
  }
@@ -20677,7 +20922,7 @@ __export(daemon_exports, {
20677
20922
  startDaemon: () => startDaemon
20678
20923
  });
20679
20924
  import { writeFile as writeFile15, rm as rm7 } from "fs/promises";
20680
- import { join as join33 } from "path";
20925
+ import { join as join34 } from "path";
20681
20926
  async function startDaemon(opts = {}) {
20682
20927
  await ensureLedgeDir();
20683
20928
  const store = new Store();
@@ -20694,7 +20939,7 @@ async function startDaemon(opts = {}) {
20694
20939
  }
20695
20940
  for (const s of store.all()) {
20696
20941
  if (backendOf(s) !== "omp-rpc") continue;
20697
- await reapOwner(join33(paths().dir, "omp", s.id)).catch(() => false);
20942
+ await reapOwner(join34(paths().dir, "omp", s.id)).catch(() => false);
20698
20943
  if (s.state === "exited") continue;
20699
20944
  const at = (/* @__PURE__ */ new Date()).toISOString();
20700
20945
  store.apply(s.id, { name: "StopFailure", at, source: "daemon", text: "Ledge restarted: the OMP process ended with it" });
@@ -20781,9 +21026,9 @@ __export(uninstall_exports, {
20781
21026
  runUninstall: () => runUninstall,
20782
21027
  uninstallArgs: () => uninstallArgs
20783
21028
  });
20784
- import { readFile as readFile15, writeFile as writeFile16, rename as rename6, rm as rm8, lstat as lstat2, realpath as realpath4 } from "fs/promises";
20785
- import { homedir as homedir16 } from "os";
20786
- import { join as join34 } from "path";
21029
+ import { readFile as readFile16, writeFile as writeFile16, rename as rename6, rm as rm8, lstat as lstat2, realpath as realpath4 } from "fs/promises";
21030
+ import { homedir as homedir17 } from "os";
21031
+ import { join as join35 } from "path";
20787
21032
  function prune(config, file, keep) {
20788
21033
  if (!isObject(config)) throw new Error(`${file} root is not an object, not modified`);
20789
21034
  if (config.hooks === void 0) return { settings: config, removed: 0 };
@@ -20820,7 +21065,7 @@ function removeCursorHooks(config) {
20820
21065
  async function pruneJsonFile(path3, remove, name) {
20821
21066
  let raw;
20822
21067
  try {
20823
- raw = await readFile15(path3, "utf8");
21068
+ raw = await readFile16(path3, "utf8");
20824
21069
  } catch (e) {
20825
21070
  if (e.code === "ENOENT") return null;
20826
21071
  throw e;
@@ -20841,7 +21086,7 @@ async function pruneJsonFile(path3, remove, name) {
20841
21086
  async function removeService(opts = {}) {
20842
21087
  const out = [];
20843
21088
  const run4 = opts.run ?? execRunner;
20844
- const unit = join34(opts.unitDir ?? join34(homedir16(), ".config", "systemd", "user"), SERVICE);
21089
+ const unit = join35(opts.unitDir ?? join35(homedir17(), ".config", "systemd", "user"), SERVICE);
20845
21090
  let present = true;
20846
21091
  try {
20847
21092
  await lstat2(unit);
@@ -20866,7 +21111,7 @@ async function runningSessions(run4, socket) {
20866
21111
  async function removeLink(link3, script) {
20867
21112
  try {
20868
21113
  const st = await lstat2(link3);
20869
- if (st.isFile() ? !(await readFile15(link3, "utf8")).startsWith(BIN_MARK) : !st.isSymbolicLink() || await realpath4(link3) !== await realpath4(script)) return false;
21114
+ if (st.isFile() ? !(await readFile16(link3, "utf8")).startsWith(BIN_MARK) : !st.isSymbolicLink() || await realpath4(link3) !== await realpath4(script)) return false;
20870
21115
  } catch {
20871
21116
  return false;
20872
21117
  }
@@ -20889,9 +21134,9 @@ async function runUninstall(opts = {}) {
20889
21134
  };
20890
21135
  const run4 = opts.run ?? execRunner;
20891
21136
  const files = [
20892
- ["Claude Code", opts.settingsPath ?? join34(homedir16(), ".claude", "settings.json"), (c) => removeClaudeHooks(c, "settings.json")],
20893
- ["Codex", join34(opts.codexDir ?? process.env.CODEX_HOME ?? join34(homedir16(), ".codex"), "hooks.json"), (c) => removeClaudeHooks(c, "hooks.json")],
20894
- ["Cursor Agent", join34(opts.cursorDir ?? join34(homedir16(), ".cursor"), "hooks.json"), removeCursorHooks]
21137
+ ["Claude Code", opts.settingsPath ?? join35(homedir17(), ".claude", "settings.json"), (c) => removeClaudeHooks(c, "settings.json")],
21138
+ ["Codex", join35(opts.codexDir ?? process.env.CODEX_HOME ?? join35(homedir17(), ".codex"), "hooks.json"), (c) => removeClaudeHooks(c, "hooks.json")],
21139
+ ["Cursor Agent", join35(opts.cursorDir ?? join35(homedir17(), ".cursor"), "hooks.json"), removeCursorHooks]
20895
21140
  ];
20896
21141
  for (const [agent, path3, remove] of files) {
20897
21142
  await step3(async () => {
@@ -20913,7 +21158,7 @@ async function runUninstall(opts = {}) {
20913
21158
  });
20914
21159
  else lines.push(`${dir} kept (session journal and log): \`ledge uninstall --purge\` removes it`);
20915
21160
  const script = opts.script ?? process.argv[1];
20916
- const link3 = opts.link ?? join34(homedir16(), ".local", "bin", "ledge");
21161
+ const link3 = opts.link ?? join35(homedir17(), ".local", "bin", "ledge");
20917
21162
  if (script) await step3(async () => {
20918
21163
  if (await removeLink(link3, script)) lines.push(`${link3} removed`);
20919
21164
  });
@@ -21171,10 +21416,10 @@ function createDaemonClient(opts) {
21171
21416
  };
21172
21417
  const schedule = () => {
21173
21418
  if (closed) return;
21174
- timer2 = setTimeout(open5, backoff);
21419
+ timer2 = setTimeout(open6, backoff);
21175
21420
  backoff = Math.min(max, backoff * 2);
21176
21421
  };
21177
- function open5() {
21422
+ function open6() {
21178
21423
  if (closed) return;
21179
21424
  setStatus(status === "open" ? "connecting" : status);
21180
21425
  if (!token) {
@@ -21238,7 +21483,7 @@ function createDaemonClient(opts) {
21238
21483
  ws = null;
21239
21484
  old?.close();
21240
21485
  setStatus("connecting");
21241
- open5();
21486
+ open6();
21242
21487
  },
21243
21488
  close: () => {
21244
21489
  closed = true;
@@ -23011,7 +23256,7 @@ __export(launch_exports, {
23011
23256
  parseLaunchArgs: () => parseLaunchArgs
23012
23257
  });
23013
23258
  import { spawn as spawn3 } from "child_process";
23014
- import { readFile as readFile16, rm as rm9 } from "fs/promises";
23259
+ import { readFile as readFile17, rm as rm9 } from "fs/promises";
23015
23260
  import { createConnection } from "net";
23016
23261
  function parseLaunchArgs(argv) {
23017
23262
  const args = [];
@@ -23124,7 +23369,7 @@ async function readExitCode(id) {
23124
23369
  const file = exitFile(id);
23125
23370
  let raw = null;
23126
23371
  try {
23127
- raw = await readFile16(file, "utf8");
23372
+ raw = await readFile17(file, "utf8");
23128
23373
  } catch {
23129
23374
  }
23130
23375
  await rm9(file, { force: true });
@@ -23201,6 +23446,7 @@ var init_launch2 = __esm({
23201
23446
  import { spawn as spawn4 } from "child_process";
23202
23447
  function attachCommand(fleet, s) {
23203
23448
  if (s.offline) return { why: "offline" };
23449
+ if (isStoppedOmpSeat(s)) return { why: "stopped" };
23204
23450
  if (backendOf(s) === "omp-rpc") return { why: "omp" };
23205
23451
  if (!s.tmuxSession) return { why: "none" };
23206
23452
  const argv = fleet.terminal(s.id);
@@ -23223,9 +23469,9 @@ var init_attach = __esm({
23223
23469
 
23224
23470
  // src/tui/clipboard.ts
23225
23471
  import { execFile as execFile9 } from "child_process";
23226
- import { mkdtemp as mkdtemp2, readFile as readFile17, rm as rm10 } from "fs/promises";
23472
+ import { mkdtemp as mkdtemp2, readFile as readFile18, rm as rm10 } from "fs/promises";
23227
23473
  import { tmpdir as tmpdir2 } from "os";
23228
- import { join as join35 } from "path";
23474
+ import { join as join36 } from "path";
23229
23475
  async function fromWindows(run4) {
23230
23476
  const { stdout } = await run4(POWERSHELL, ["-NoProfile", "-STA", "-Command", SCRIPT], { cwd: "/mnt/c", maxBuffer: MAX_BUFFER, encoding: "buffer" });
23231
23477
  const line2 = stdout.toString("utf8").trim();
@@ -23250,15 +23496,15 @@ async function fromLinux(run4) {
23250
23496
  return null;
23251
23497
  }
23252
23498
  async function fromMac(run4) {
23253
- const dir = await mkdtemp2(join35(tmpdir2(), "ledge-clip-"));
23254
- const file = join35(dir, "clip.png");
23499
+ const dir = await mkdtemp2(join36(tmpdir2(), "ledge-clip-"));
23500
+ const file = join36(dir, "clip.png");
23255
23501
  try {
23256
23502
  const script = `set f to open for access POSIX file "${file}" with write permission
23257
23503
  set eof f to 0
23258
23504
  write (the clipboard as \xABclass PNGf\xBB) to f
23259
23505
  close access f`;
23260
23506
  await run4("osascript", ["-e", script], { maxBuffer: MAX_BUFFER, encoding: "buffer" });
23261
- return fromBytes(await readFile17(file));
23507
+ return fromBytes(await readFile18(file));
23262
23508
  } catch {
23263
23509
  return null;
23264
23510
  } finally {
@@ -23689,7 +23935,7 @@ var init_fr = __esm({
23689
23935
  plural = (n, one2, many) => n > 1 ? many : one2;
23690
23936
  fr3 = {
23691
23937
  lang: { fr: "Fran\xE7ais", en: "English" },
23692
- state: { approval: "Votre accord", question: "Une question", starting: "D\xE9marrage", idle: "Disponible", working: "Au travail", done: "Termin\xE9e", error: "Erreur", readonly: "Lecture seule" },
23938
+ state: { approval: "Votre accord", question: "Une question", starting: "D\xE9marrage", idle: "Disponible", working: "Au travail", done: "Termin\xE9e", error: "Erreur", readonly: "Lecture seule", stopped: "Arr\xEAt\xE9" },
23693
23939
  time: {
23694
23940
  now: "\xE0 l\u2019instant",
23695
23941
  decimal: ",",
@@ -23712,6 +23958,7 @@ var init_fr = __esm({
23712
23958
  openImage: "ouvrir l'image",
23713
23959
  navigate: "naviguer",
23714
23960
  open: "ouvrir",
23961
+ attachSeat: "attacher son terminal",
23715
23962
  answer: "r\xE9pondre",
23716
23963
  attach: "attacher (tmux)",
23717
23964
  ask: "demander \xE0 Ledge",
@@ -23786,6 +24033,7 @@ var init_fr = __esm({
23786
24033
  noPreview: "Pas d\u2019aper\xE7u.",
23787
24034
  noExchange: "Aucun \xE9change.",
23788
24035
  sessions: (n) => `${n} ${plural(n, "session", "sessions")}`,
24036
+ sessionsOf: (live3, all2) => `${live3} ${plural(live3, "session", "sessions")} sur ${all2}`,
23789
24037
  outside: "Hors projet",
23790
24038
  offline: "hors ligne",
23791
24039
  readOnly: "lecture seule",
@@ -24009,6 +24257,7 @@ var init_fr = __esm({
24009
24257
  noteTooLong: (max) => `Note trop longue : ${max} caract\xE8res au plus. Elle reste dans le champ.`,
24010
24258
  noNote: (n) => `${n} ne prend pas de note ici.`,
24011
24259
  noTerminalOmp: (n) => `${n} est pilot\xE9 par Ledge via le protocole d\u2019OMP : pas de terminal \xE0 attacher.`,
24260
+ seatStopped: (n) => `${n} est arr\xEAt\xE9 : son terminal n\u2019existe plus. 6 ouvre OpenRig, o\xF9 est son rig.`,
24012
24261
  noTerminal: (n) => `Pas de terminal pour ${n}.`,
24013
24262
  attachFailed: (code3) => `tmux s\u2019est arr\xEAt\xE9 avec le code ${code3}.`,
24014
24263
  modelSwitch: "Ce daemon ne change pas le mod\xE8le d\u2019une session en cours.",
@@ -24257,7 +24506,7 @@ var init_en = __esm({
24257
24506
  plural2 = (n, one2, many) => n > 1 ? many : one2;
24258
24507
  en3 = {
24259
24508
  lang: { fr: "Fran\xE7ais", en: "English" },
24260
- state: { approval: "Needs you", question: "A question", starting: "Starting", idle: "Available", working: "Working", done: "Finished", error: "Error", readonly: "Read-only" },
24509
+ state: { approval: "Needs you", question: "A question", starting: "Starting", idle: "Available", working: "Working", done: "Finished", error: "Error", readonly: "Read-only", stopped: "Stopped" },
24261
24510
  time: { now: "just now", decimal: ".", s: (n) => `${n}s`, min: (n) => `${n} min`, h: (n) => `${n} h`, d: (n) => `${n} d` },
24262
24511
  tabs: { overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", openrig: "OpenRig" },
24263
24512
  bar: {
@@ -24273,6 +24522,7 @@ var init_en = __esm({
24273
24522
  openImage: "open image",
24274
24523
  navigate: "move",
24275
24524
  open: "open",
24525
+ attachSeat: "attach its terminal",
24276
24526
  answer: "answer",
24277
24527
  attach: "attach (tmux)",
24278
24528
  ask: "ask Ledge",
@@ -24346,6 +24596,7 @@ var init_en = __esm({
24346
24596
  noPreview: "No preview.",
24347
24597
  noExchange: "No message yet.",
24348
24598
  sessions: (n) => `${n} ${plural2(n, "session", "sessions")}`,
24599
+ sessionsOf: (live3, all2) => `${live3} ${live3 === 1 ? "session" : "sessions"} of ${all2}`,
24349
24600
  outside: "No project",
24350
24601
  offline: "offline",
24351
24602
  readOnly: "read-only",
@@ -24565,6 +24816,7 @@ var init_en = __esm({
24565
24816
  noteTooLong: (max) => `Note too long: ${max} characters at most. It stays in the field.`,
24566
24817
  noNote: (n) => `${n} takes no note here.`,
24567
24818
  noTerminalOmp: (n) => `${n} is driven by Ledge over OMP's protocol: there is no terminal to attach to.`,
24819
+ seatStopped: (n) => `${n} is stopped: its terminal is gone. 6 opens OpenRig, where its rig is.`,
24568
24820
  noTerminal: (n) => `No terminal for ${n}.`,
24569
24821
  attachFailed: (code3) => `tmux stopped with code ${code3}.`,
24570
24822
  modelSwitch: "This daemon cannot change a running session\u2019s model.",
@@ -24829,15 +25081,19 @@ function overview(sessions, projects) {
24829
25081
  const known2 = new Map((projects?.projects ?? []).map((p) => [p.id, p]));
24830
25082
  for (const p of projects?.projects ?? []) nodes.set(`p:${p.id}`, { key: `p:${p.id}`, label: p.name, color: p.color, branches: [], count: 0 });
24831
25083
  const rigNodes = /* @__PURE__ */ new Map();
25084
+ const rigNodeOf = (rig) => {
25085
+ const kind = rig === "kernel" ? "kernel" : "rig";
25086
+ const key = kind === "kernel" ? "g:kernel" : `g:rig:${rig}`;
25087
+ let node2 = rigNodes.get(key);
25088
+ if (!node2) {
25089
+ node2 = { key, label: kind === "kernel" ? "Kernel" : rig, kind, branches: [{ branch: "", sessions: [] }], count: 0 };
25090
+ rigNodes.set(key, node2);
25091
+ }
25092
+ return node2;
25093
+ };
24832
25094
  for (const s of live3) {
24833
25095
  if (s.rig) {
24834
- const kind = s.rig.name === "kernel" ? "kernel" : "rig";
24835
- const key2 = kind === "kernel" ? "g:kernel" : `g:rig:${s.rig.name}`;
24836
- let node3 = rigNodes.get(key2);
24837
- if (!node3) {
24838
- node3 = { key: key2, label: kind === "kernel" ? "Kernel" : s.rig.name, kind, branches: [{ branch: "", sessions: [] }], count: 0 };
24839
- rigNodes.set(key2, node3);
24840
- }
25096
+ const node3 = rigNodeOf(s.rig.name);
24841
25097
  node3.branches[0].sessions.push(s);
24842
25098
  node3.count += 1;
24843
25099
  continue;
@@ -24858,6 +25114,12 @@ function overview(sessions, projects) {
24858
25114
  row3.sessions.push(s);
24859
25115
  node2.count += 1;
24860
25116
  }
25117
+ for (const s of sessions) {
25118
+ if (!isStoppedOmpSeat(s) || !s.rig) continue;
25119
+ const node2 = rigNodeOf(s.rig.name);
25120
+ node2.branches[0].sessions.push(s);
25121
+ node2.shown = (node2.shown ?? node2.count) + 1;
25122
+ }
24861
25123
  const groups = [...rigNodes.values()].sort((a, b3) => a.kind === b3.kind ? a.label.localeCompare(b3.label) : a.kind === "kernel" ? 1 : -1);
24862
25124
  const tree2 = [...[...nodes.values()].filter((n) => n.count > 0), ...groups];
24863
25125
  const order2 = tree2.flatMap((n) => n.branches.flatMap((b3) => b3.sessions.map((s) => s.id)));
@@ -25118,7 +25380,7 @@ var init_theme = __esm({
25118
25380
 
25119
25381
  // src/tui/model/cells.ts
25120
25382
  import stringWidth from "string-width";
25121
- function join36(line2, a) {
25383
+ function join37(line2, a) {
25122
25384
  const out = [];
25123
25385
  for (const c of a) {
25124
25386
  const last = out[out.length - 1];
@@ -25133,7 +25395,7 @@ function cut2(line2, width, ellipsis = "\u2026") {
25133
25395
  const a = split2(line2), room = width - cells(ellipsis);
25134
25396
  let n = 0, i = 0;
25135
25397
  while (i < a.length && n + a[i].w <= room) n += a[i++].w;
25136
- const kept = join36(line2, a.slice(0, i));
25398
+ const kept = join37(line2, a.slice(0, i));
25137
25399
  const color2 = line2[a[Math.max(0, i - 1)]?.seg ?? 0]?.color;
25138
25400
  return [...kept, { text: ellipsis, ...color2 ? { color: color2 } : {} }];
25139
25401
  }
@@ -25152,7 +25414,7 @@ function wrapSegs(line2, width, first = width) {
25152
25414
  }
25153
25415
  w = Math.max(4, width);
25154
25416
  if (j >= a.length || a[j].g === "\n") {
25155
- out.push(join36(line2, a.slice(i, j)));
25417
+ out.push(join37(line2, a.slice(i, j)));
25156
25418
  i = j + 1;
25157
25419
  if (j >= a.length) break;
25158
25420
  if (i >= a.length) out.push([]);
@@ -25161,7 +25423,7 @@ function wrapSegs(line2, width, first = width) {
25161
25423
  const end2 = a[j].g === " " ? j : space > i ? space : Math.max(j, i + 1);
25162
25424
  let k = end2;
25163
25425
  while (k > i && a[k - 1].g === " ") k--;
25164
- out.push(join36(line2, a.slice(i, k)));
25426
+ out.push(join37(line2, a.slice(i, k)));
25165
25427
  i = end2;
25166
25428
  while (i < a.length && a[i].g === " ") i++;
25167
25429
  if (a[i]?.g === "\n") {
@@ -30374,16 +30636,16 @@ function resolveAllAttention(events, context) {
30374
30636
  let nextEvents;
30375
30637
  while (++index2 < events.length) {
30376
30638
  if (events[index2][0] === "enter" && events[index2][1].type === "attentionSequence" && events[index2][1]._close) {
30377
- let open5 = index2;
30378
- while (open5--) {
30379
- if (events[open5][0] === "exit" && events[open5][1].type === "attentionSequence" && events[open5][1]._open && // If the markers are the same:
30380
- context.sliceSerialize(events[open5][1]).charCodeAt(0) === context.sliceSerialize(events[index2][1]).charCodeAt(0)) {
30381
- if ((events[open5][1]._close || events[index2][1]._open) && (events[index2][1].end.offset - events[index2][1].start.offset) % 3 && !((events[open5][1].end.offset - events[open5][1].start.offset + events[index2][1].end.offset - events[index2][1].start.offset) % 3)) {
30639
+ let open6 = index2;
30640
+ while (open6--) {
30641
+ if (events[open6][0] === "exit" && events[open6][1].type === "attentionSequence" && events[open6][1]._open && // If the markers are the same:
30642
+ context.sliceSerialize(events[open6][1]).charCodeAt(0) === context.sliceSerialize(events[index2][1]).charCodeAt(0)) {
30643
+ if ((events[open6][1]._close || events[index2][1]._open) && (events[index2][1].end.offset - events[index2][1].start.offset) % 3 && !((events[open6][1].end.offset - events[open6][1].start.offset + events[index2][1].end.offset - events[index2][1].start.offset) % 3)) {
30382
30644
  continue;
30383
30645
  }
30384
- const use = events[open5][1].end.offset - events[open5][1].start.offset > 1 && events[index2][1].end.offset - events[index2][1].start.offset > 1 ? 2 : 1;
30646
+ const use = events[open6][1].end.offset - events[open6][1].start.offset > 1 && events[index2][1].end.offset - events[index2][1].start.offset > 1 ? 2 : 1;
30385
30647
  const start = {
30386
- ...events[open5][1].end
30648
+ ...events[open6][1].end
30387
30649
  };
30388
30650
  const end2 = {
30389
30651
  ...events[index2][1].start
@@ -30394,7 +30656,7 @@ function resolveAllAttention(events, context) {
30394
30656
  type: use > 1 ? "strongSequence" : "emphasisSequence",
30395
30657
  start,
30396
30658
  end: {
30397
- ...events[open5][1].end
30659
+ ...events[open6][1].end
30398
30660
  }
30399
30661
  };
30400
30662
  const closingSequence = {
@@ -30407,7 +30669,7 @@ function resolveAllAttention(events, context) {
30407
30669
  const text7 = {
30408
30670
  type: use > 1 ? "strongText" : "emphasisText",
30409
30671
  start: {
30410
- ...events[open5][1].end
30672
+ ...events[open6][1].end
30411
30673
  },
30412
30674
  end: {
30413
30675
  ...events[index2][1].start
@@ -30422,26 +30684,26 @@ function resolveAllAttention(events, context) {
30422
30684
  ...closingSequence.end
30423
30685
  }
30424
30686
  };
30425
- events[open5][1].end = {
30687
+ events[open6][1].end = {
30426
30688
  ...openingSequence.start
30427
30689
  };
30428
30690
  events[index2][1].start = {
30429
30691
  ...closingSequence.end
30430
30692
  };
30431
30693
  nextEvents = [];
30432
- if (events[open5][1].end.offset - events[open5][1].start.offset) {
30433
- nextEvents = push(nextEvents, [["enter", events[open5][1], context], ["exit", events[open5][1], context]]);
30694
+ if (events[open6][1].end.offset - events[open6][1].start.offset) {
30695
+ nextEvents = push(nextEvents, [["enter", events[open6][1], context], ["exit", events[open6][1], context]]);
30434
30696
  }
30435
30697
  nextEvents = push(nextEvents, [["enter", group, context], ["enter", openingSequence, context], ["exit", openingSequence, context], ["enter", text7, context]]);
30436
- nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open5 + 1, index2), context));
30698
+ nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open6 + 1, index2), context));
30437
30699
  nextEvents = push(nextEvents, [["exit", text7, context], ["enter", closingSequence, context], ["exit", closingSequence, context], ["exit", group, context]]);
30438
30700
  let offset = 0;
30439
30701
  if (events[index2][1].end.offset - events[index2][1].start.offset) {
30440
30702
  offset = 2;
30441
30703
  nextEvents = push(nextEvents, [["enter", events[index2][1], context], ["exit", events[index2][1], context]]);
30442
30704
  }
30443
- splice(events, open5 - 1, index2 - open5 + 3, nextEvents);
30444
- index2 = open5 + nextEvents.length - offset - 2;
30705
+ splice(events, open6 - 1, index2 - open6 + 3, nextEvents);
30706
+ index2 = open6 + nextEvents.length - offset - 2;
30445
30707
  break;
30446
30708
  }
30447
30709
  }
@@ -30473,10 +30735,10 @@ function tokenizeAttention(effects, ok4) {
30473
30735
  }
30474
30736
  const token = effects.exit("attentionSequence");
30475
30737
  const after2 = classifyCharacter(code3);
30476
- const open5 = !after2 || after2 === 2 && before2 || attentionMarkers2.includes(code3) && code3 !== 42 && code3 !== 95;
30738
+ const open6 = !after2 || after2 === 2 && before2 || attentionMarkers2.includes(code3) && code3 !== 42 && code3 !== 95;
30477
30739
  const close = !before2 || before2 === 2 && after2 || attentionMarkers2.includes(previous3) && previous3 !== 42 && previous3 !== 95;
30478
- token._open = Boolean(marker === 42 ? open5 : open5 && (before2 || !close));
30479
- token._close = Boolean(marker === 42 ? close : close && (after2 || !open5));
30740
+ token._open = Boolean(marker === 42 ? open6 : open6 && (before2 || !close));
30741
+ token._close = Boolean(marker === 42 ? close : close && (after2 || !open6));
30480
30742
  return ok4(code3);
30481
30743
  }
30482
30744
  }
@@ -30510,9 +30772,9 @@ function tokenizeAutolink(effects, ok4, nok) {
30510
30772
  effects.consume(code3);
30511
30773
  effects.exit("autolinkMarker");
30512
30774
  effects.enter("autolinkProtocol");
30513
- return open5;
30775
+ return open6;
30514
30776
  }
30515
- function open5(code3) {
30777
+ function open6(code3) {
30516
30778
  if (asciiAlpha(code3)) {
30517
30779
  effects.consume(code3);
30518
30780
  return schemeOrEmailAtext;
@@ -30793,9 +31055,9 @@ function tokenizeCharacterReference(effects, ok4, nok) {
30793
31055
  effects.enter("characterReferenceMarker");
30794
31056
  effects.consume(code3);
30795
31057
  effects.exit("characterReferenceMarker");
30796
- return open5;
31058
+ return open6;
30797
31059
  }
30798
- function open5(code3) {
31060
+ function open6(code3) {
30799
31061
  if (code3 === 35) {
30800
31062
  effects.enter("characterReferenceMarkerNumeric");
30801
31063
  effects.consume(code3);
@@ -32196,9 +32458,9 @@ function tokenizeHtmlFlow(effects, ok4, nok) {
32196
32458
  effects.enter("htmlFlow");
32197
32459
  effects.enter("htmlFlowData");
32198
32460
  effects.consume(code3);
32199
- return open5;
32461
+ return open6;
32200
32462
  }
32201
- function open5(code3) {
32463
+ function open6(code3) {
32202
32464
  if (code3 === 33) {
32203
32465
  effects.consume(code3);
32204
32466
  return declarationOpen;
@@ -32546,9 +32808,9 @@ function tokenizeHtmlText(effects, ok4, nok) {
32546
32808
  effects.enter("htmlText");
32547
32809
  effects.enter("htmlTextData");
32548
32810
  effects.consume(code3);
32549
- return open5;
32811
+ return open6;
32550
32812
  }
32551
- function open5(code3) {
32813
+ function open6(code3) {
32552
32814
  if (code3 === 33) {
32553
32815
  effects.consume(code3);
32554
32816
  return declarationOpen;
@@ -32870,12 +33132,12 @@ function resolveAllLabelEnd(events) {
32870
33132
  function resolveToLabelEnd(events, context) {
32871
33133
  let index2 = events.length;
32872
33134
  let offset = 0;
32873
- let open5;
33135
+ let open6;
32874
33136
  let close;
32875
33137
  let media;
32876
33138
  while (index2--) {
32877
33139
  const token = events[index2][1];
32878
- if (open5) {
33140
+ if (open6) {
32879
33141
  if (token.type === "link" || token.type === "labelLink" && token._inactive) {
32880
33142
  break;
32881
33143
  }
@@ -32884,7 +33146,7 @@ function resolveToLabelEnd(events, context) {
32884
33146
  }
32885
33147
  } else if (close) {
32886
33148
  if (events[index2][0] === "enter" && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {
32887
- open5 = index2;
33149
+ open6 = index2;
32888
33150
  if (token.type !== "labelLink") {
32889
33151
  offset = 2;
32890
33152
  break;
@@ -32895,9 +33157,9 @@ function resolveToLabelEnd(events, context) {
32895
33157
  }
32896
33158
  }
32897
33159
  const group = {
32898
- type: events[open5][1].type === "labelLink" ? "link" : "image",
33160
+ type: events[open6][1].type === "labelLink" ? "link" : "image",
32899
33161
  start: {
32900
- ...events[open5][1].start
33162
+ ...events[open6][1].start
32901
33163
  },
32902
33164
  end: {
32903
33165
  ...events[events.length - 1][1].end
@@ -32906,7 +33168,7 @@ function resolveToLabelEnd(events, context) {
32906
33168
  const label2 = {
32907
33169
  type: "label",
32908
33170
  start: {
32909
- ...events[open5][1].start
33171
+ ...events[open6][1].start
32910
33172
  },
32911
33173
  end: {
32912
33174
  ...events[close][1].end
@@ -32915,20 +33177,20 @@ function resolveToLabelEnd(events, context) {
32915
33177
  const text7 = {
32916
33178
  type: "labelText",
32917
33179
  start: {
32918
- ...events[open5 + offset + 2][1].end
33180
+ ...events[open6 + offset + 2][1].end
32919
33181
  },
32920
33182
  end: {
32921
33183
  ...events[close - 2][1].start
32922
33184
  }
32923
33185
  };
32924
33186
  media = [["enter", group, context], ["enter", label2, context]];
32925
- media = push(media, events.slice(open5 + 1, open5 + offset + 3));
33187
+ media = push(media, events.slice(open6 + 1, open6 + offset + 3));
32926
33188
  media = push(media, [["enter", text7, context]]);
32927
- media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open5 + offset + 4, close - 3), context));
33189
+ media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open6 + offset + 4, close - 3), context));
32928
33190
  media = push(media, [["exit", text7, context], events[close - 2], events[close - 1], ["exit", label2, context]]);
32929
33191
  media = push(media, events.slice(close + 1));
32930
33192
  media = push(media, [["exit", group, context]]);
32931
- splice(events, open5, events.length, media);
33193
+ splice(events, open6, events.length, media);
32932
33194
  return events;
32933
33195
  }
32934
33196
  function tokenizeLabelEnd(effects, ok4, nok) {
@@ -33099,9 +33361,9 @@ function tokenizeLabelStartImage(effects, ok4, nok) {
33099
33361
  effects.enter("labelImageMarker");
33100
33362
  effects.consume(code3);
33101
33363
  effects.exit("labelImageMarker");
33102
- return open5;
33364
+ return open6;
33103
33365
  }
33104
- function open5(code3) {
33366
+ function open6(code3) {
33105
33367
  if (code3 === 91) {
33106
33368
  effects.enter("labelMarker");
33107
33369
  effects.consume(code3);
@@ -33933,30 +34195,30 @@ function gfmStrikethrough(options) {
33933
34195
  let index2 = -1;
33934
34196
  while (++index2 < events.length) {
33935
34197
  if (events[index2][0] === "enter" && events[index2][1].type === "strikethroughSequenceTemporary" && events[index2][1]._close) {
33936
- let open5 = index2;
33937
- while (open5--) {
33938
- if (events[open5][0] === "exit" && events[open5][1].type === "strikethroughSequenceTemporary" && events[open5][1]._open && // If the sizes are the same:
33939
- events[index2][1].end.offset - events[index2][1].start.offset === events[open5][1].end.offset - events[open5][1].start.offset) {
34198
+ let open6 = index2;
34199
+ while (open6--) {
34200
+ if (events[open6][0] === "exit" && events[open6][1].type === "strikethroughSequenceTemporary" && events[open6][1]._open && // If the sizes are the same:
34201
+ events[index2][1].end.offset - events[index2][1].start.offset === events[open6][1].end.offset - events[open6][1].start.offset) {
33940
34202
  events[index2][1].type = "strikethroughSequence";
33941
- events[open5][1].type = "strikethroughSequence";
34203
+ events[open6][1].type = "strikethroughSequence";
33942
34204
  const strikethrough = {
33943
34205
  type: "strikethrough",
33944
- start: Object.assign({}, events[open5][1].start),
34206
+ start: Object.assign({}, events[open6][1].start),
33945
34207
  end: Object.assign({}, events[index2][1].end)
33946
34208
  };
33947
34209
  const text7 = {
33948
34210
  type: "strikethroughText",
33949
- start: Object.assign({}, events[open5][1].end),
34211
+ start: Object.assign({}, events[open6][1].end),
33950
34212
  end: Object.assign({}, events[index2][1].start)
33951
34213
  };
33952
- const nextEvents = [["enter", strikethrough, context], ["enter", events[open5][1], context], ["exit", events[open5][1], context], ["enter", text7, context]];
34214
+ const nextEvents = [["enter", strikethrough, context], ["enter", events[open6][1], context], ["exit", events[open6][1], context], ["enter", text7, context]];
33953
34215
  const insideSpan2 = context.parser.constructs.insideSpan.null;
33954
34216
  if (insideSpan2) {
33955
- splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan2, events.slice(open5 + 1, index2), context));
34217
+ splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan2, events.slice(open6 + 1, index2), context));
33956
34218
  }
33957
34219
  splice(nextEvents, nextEvents.length, 0, [["exit", text7, context], ["enter", events[index2][1], context], ["exit", events[index2][1], context], ["exit", strikethrough, context]]);
33958
- splice(events, open5 - 1, index2 - open5 + 3, nextEvents);
33959
- index2 = open5 + nextEvents.length - 2;
34220
+ splice(events, open6 - 1, index2 - open6 + 3, nextEvents);
34221
+ index2 = open6 + nextEvents.length - 2;
33960
34222
  break;
33961
34223
  }
33962
34224
  }
@@ -34558,8 +34820,8 @@ function gfmTaskListItem() {
34558
34820
  }
34559
34821
  function tokenizeTasklistCheck(effects, ok4, nok) {
34560
34822
  const self = this;
34561
- return open5;
34562
- function open5(code3) {
34823
+ return open6;
34824
+ function open6(code3) {
34563
34825
  if (
34564
34826
  // Exit if there’s stuff before.
34565
34827
  self.previous !== null || // Exit if not in the first content that is the first child of a list
@@ -36048,8 +36310,8 @@ function compiler(options) {
36048
36310
  events[start][1]._spread = listSpread;
36049
36311
  }
36050
36312
  function opener(create, and) {
36051
- return open5;
36052
- function open5(token) {
36313
+ return open6;
36314
+ function open6(token) {
36053
36315
  enter.call(this, create(token), token);
36054
36316
  if (and) and.call(this, token);
36055
36317
  }
@@ -36081,19 +36343,19 @@ function compiler(options) {
36081
36343
  }
36082
36344
  function exit3(token, onExitError) {
36083
36345
  const node2 = this.stack.pop();
36084
- const open5 = this.tokenStack.pop();
36085
- if (!open5) {
36346
+ const open6 = this.tokenStack.pop();
36347
+ if (!open6) {
36086
36348
  throw new Error("Cannot close `" + token.type + "` (" + stringifyPosition({
36087
36349
  start: token.start,
36088
36350
  end: token.end
36089
36351
  }) + "): it\u2019s not open");
36090
36352
  }
36091
- if (open5[0].type !== token.type) {
36353
+ if (open6[0].type !== token.type) {
36092
36354
  if (onExitError) {
36093
- onExitError.call(this, token, open5[0]);
36355
+ onExitError.call(this, token, open6[0]);
36094
36356
  } else {
36095
- const handler = open5[1] || defaultOnError;
36096
- handler.call(this, token, open5[0]);
36357
+ const handler = open6[1] || defaultOnError;
36358
+ handler.call(this, token, open6[0]);
36097
36359
  }
36098
36360
  }
36099
36361
  node2.position.end = point2(token.end);
@@ -39513,7 +39775,8 @@ function Overview() {
39513
39775
  return true;
39514
39776
  }
39515
39777
  if (key.return) {
39516
- if (selected) go("session");
39778
+ if (selected && isOmpSeat(selected)) attach2(selected);
39779
+ else if (selected) go("session");
39517
39780
  return true;
39518
39781
  }
39519
39782
  if (!selected) return false;
@@ -39534,7 +39797,7 @@ function Overview() {
39534
39797
  return false;
39535
39798
  });
39536
39799
  const groupRows = (kind) => view.tree.filter((n) => n.kind === kind).flatMap((n) => [
39537
- ...kind === "rig" ? [{ kind: "project", key: n.key, label: n.label, color: C.blue, count: n.count, rig: true, folder: folderOf(n.branches[0]?.sessions[0]?.cwd) }] : [],
39800
+ ...kind === "rig" ? [{ kind: "project", key: n.key, label: n.label, color: C.blue, count: n.count, ...n.shown !== void 0 && { shown: n.shown }, rig: true, folder: folderOf(n.branches[0]?.sessions[0]?.cwd) }] : [],
39538
39801
  ...n.branches.flatMap((b3) => b3.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: kind === "rig" ? 2 : 0, group: kind })))
39539
39802
  ]);
39540
39803
  const rigRows = groupRows("rig"), kernelRows = groupRows("kernel");
@@ -39556,6 +39819,7 @@ function Overview() {
39556
39819
  const stateW = Math.max(...Object.values(t.state).map((x) => x.length)) + 1;
39557
39820
  const topicW = Math.max(6, inner - 6 - nameW - stateW - 3);
39558
39821
  const askW = Math.max(6, inner - 2 - nameW - 9 - 7);
39822
+ const roleW = Math.max(11, ...rigRows.map((r) => r.kind === "session" ? [...r.s.rig?.role ?? ""].length + 2 : 0));
39559
39823
  const yoursBox = /* @__PURE__ */ jsx10(TitledBox, { title: t.overview.yours(yoursCount), color: yoursCount ? C.red : C.line, titleColor: yoursCount ? C.red : C.dim, height: yoursHeight + 2, children: yoursCount === 0 ? /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: t.overview.nobodyWaits }) : [...view.yours.slice(0, yoursHeight).map((s) => {
39560
39824
  const st = botState(s);
39561
39825
  return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: s.id === sel ? C.sel : void 0, children: [
@@ -39621,16 +39885,18 @@ function Overview() {
39621
39885
  ] }) : null,
39622
39886
  /* @__PURE__ */ jsxs6(Text7, { color: C.faint, children: [
39623
39887
  " ",
39624
- t.overview.sessions(r.count)
39888
+ r.shown !== void 0 ? t.overview.sessionsOf(r.count, r.shown) : t.overview.sessions(r.count)
39625
39889
  ] })
39626
- ] }, r.key) : r.kind === "session" ? /* @__PURE__ */ jsx10(SessionRow, { s: r.s, on: r.s.id === sel, nameW, topicW: Math.max(6, topicW - r.indent), stateW, indent: r.indent, group: r.group }, r.key) : null) });
39890
+ ] }, r.key) : r.kind === "session" ? /* @__PURE__ */ jsx10(SessionRow, { s: r.s, on: r.s.id === sel, nameW, topicW: Math.max(6, topicW - r.indent), stateW, indent: r.indent, group: r.group, roleW }, r.key) : null) });
39627
39891
  const rigsBox = groupBox(t.overview.rigsGroup, rigRows, rigsHeight);
39628
39892
  const kernelBox = groupBox(t.overview.kernelGroup, kernelRows, kernelHeight);
39893
+ const stopped2 = !!selected && isStoppedOmpSeat(selected);
39629
39894
  const keys = writing ? [["\u21B5", t.keys.sendOnly], ["alt+\u21B5", t.keys.newLine], ["esc", t.keys.cancel]] : deciding?.confirm ? [["y", t.keys.sendAnswer], ["n esc", t.keys.edit]] : deciding ? [["\u21B5", t.keys.review], ["alt+\u21B5", t.keys.newLine], ["esc", t.keys.cancel]] : [
39895
+ // On an OMP seat ↵ attaches its terminal; on a stopped one, neither ↵ nor a has anything to attach.
39630
39896
  ["j k", t.keys.navigate],
39631
- ["\u21B5", t.keys.open],
39897
+ ...stopped2 ? [] : [["\u21B5", selected && isOmpSeat(selected) ? t.keys.attachSeat : t.keys.open]],
39632
39898
  ...view.yours.length ? [["tab", t.keys.next], ["y n", t.keys.answer]] : [],
39633
- ["a", t.keys.attach],
39899
+ ...stopped2 ? [] : [["a", t.keys.attach]],
39634
39900
  ...writable2 ? [["m", t.keys.writeProject]] : [],
39635
39901
  ...decisions.length ? [["D", t.keys.decide]] : [],
39636
39902
  ...near.length ? [["H", t.keys.handover]] : [],
@@ -39705,21 +39971,23 @@ function Overview() {
39705
39971
  ) : null
39706
39972
  ] });
39707
39973
  }
39708
- function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }) {
39974
+ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group, roleW = 11 }) {
39709
39975
  const { t, g } = useTui();
39710
39976
  const st = botState(s);
39711
39977
  const n = helperCount(s);
39978
+ const stopped2 = isStoppedOmpSeat(s);
39979
+ const roleTool = group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : `${(s.rig?.role ?? "").padEnd(roleW)}${s.tool}`;
39712
39980
  return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: on ? C.sel : void 0, children: [
39713
39981
  /* @__PURE__ */ jsx10(Text7, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
39714
39982
  /* @__PURE__ */ jsx10(Text7, { children: " ".repeat(2 + indent2) }),
39715
- /* @__PURE__ */ jsxs6(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: [
39983
+ /* @__PURE__ */ jsxs6(Text7, { color: s.offline || stopped2 ? C.faint : STATE_COLOR[st], children: [
39716
39984
  g[st],
39717
39985
  " "
39718
39986
  ] }),
39719
- /* @__PURE__ */ jsx10(Text7, { color: s.offline ? C.dim : C.fg, bold: true, children: fit3(s.name, nameW) }),
39720
- group ? /* @__PURE__ */ jsxs6(Text7, { color: C.dim, children: [
39987
+ /* @__PURE__ */ jsx10(Text7, { color: stopped2 ? C.faint : s.offline ? C.dim : C.fg, bold: !stopped2, children: fit3(s.name, nameW) }),
39988
+ group ? /* @__PURE__ */ jsxs6(Text7, { color: stopped2 ? C.faint : C.dim, children: [
39721
39989
  " ",
39722
- fit3(group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : s.rig?.role ?? "", topicW - 1),
39990
+ fit3(roleTool, topicW - 1),
39723
39991
  " "
39724
39992
  ] }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
39725
39993
  s.rig ? /* @__PURE__ */ jsxs6(Text7, { color: C.blue, children: [
@@ -39731,7 +39999,7 @@ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }
39731
39999
  " "
39732
40000
  ] })
39733
40001
  ] }),
39734
- /* @__PURE__ */ jsx10(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: fit3(s.offline ? t.overview.offline : t.state[st], stateW).trimEnd() }),
40002
+ /* @__PURE__ */ jsx10(Text7, { color: s.offline || stopped2 ? C.faint : STATE_COLOR[st], children: fit3(s.offline ? t.overview.offline : stopped2 ? t.state.stopped : t.state[st], stateW).trimEnd() }),
39735
40003
  n > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: C.violet, children: [
39736
40004
  " +",
39737
40005
  n
@@ -39770,7 +40038,7 @@ var init_Overview = __esm({
39770
40038
  // src/tui/images.tsx
39771
40039
  import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
39772
40040
  import { tmpdir as tmpdir3 } from "os";
39773
- import { join as join37 } from "path";
40041
+ import { join as join38 } from "path";
39774
40042
  import { useRef as useRef8, useState as useState4 } from "react";
39775
40043
  import { Text as Text8 } from "ink";
39776
40044
  import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -39873,9 +40141,9 @@ function useOpenImage() {
39873
40141
  const { ref, route } = item;
39874
40142
  void (async () => {
39875
40143
  const { mime, data } = await getImage(client, ref.id, route);
39876
- const dir = join37(tmpdir3(), "ledge-images");
40144
+ const dir = join38(tmpdir3(), "ledge-images");
39877
40145
  await mkdir13(dir, { recursive: true, mode: 448 });
39878
- const file = join37(dir, `${ref.id}.${EXT2[mime]}`);
40146
+ const file = join38(dir, `${ref.id}.${EXT2[mime]}`);
39879
40147
  await writeFile17(file, Buffer.from(data, "base64"), { mode: 384 });
39880
40148
  await openFile2(file);
39881
40149
  })().catch((e) => toast({ text: t.images.openFailed(e instanceof AttachmentFailure ? e.reason : e.message), tone: "error" }));
@@ -40660,11 +40928,11 @@ function cardPlan(actions, room) {
40660
40928
  const raw = rows.map((r) => r.state ?? "proposed");
40661
40929
  const stopAt = raw.findIndex((s) => s === "failed" || s === "refused" || s === "stale");
40662
40930
  const shown = states.map((s, i) => s === "proposed" && stopAt >= 0 && i > stopAt ? "held" : s);
40663
- const open5 = shown.flatMap((s, i) => ["done", "running", "denied", "stale", "cut"].includes(s) ? [] : [i]);
40931
+ const open6 = shown.flatMap((s, i) => ["done", "running", "denied", "stale", "cut"].includes(s) ? [] : [i]);
40664
40932
  const skip = stopAt >= 0 && raw[stopAt] !== "failed";
40665
- const rowsAll = skip ? open5.filter((i) => i !== stopAt) : stopAt >= 0 && states[stopAt] === "cut" ? [] : open5;
40933
+ const rowsAll = skip ? open6.filter((i) => i !== stopAt) : stopAt >= 0 && states[stopAt] === "cut" ? [] : open6;
40666
40934
  const all2 = { kind: stopAt < 0 ? "all" : skip ? "skip" : "resume", rows: rowsAll, k: stopAt + 1 };
40667
- return { rows, shown, open: open5, stopAt, all: all2, cutLines };
40935
+ return { rows, shown, open: open6, stopAt, all: all2, cutLines };
40668
40936
  }
40669
40937
  function firstRow(p) {
40670
40938
  const act = p.shown.findIndex((s) => s === "proposed" || s === "held" || s === "failed");
@@ -42054,8 +42322,8 @@ function listItems2(roles, query, builtinOpen) {
42054
42322
  const by = (a, b3) => a.name.localeCompare(b3.name) || a.id.localeCompare(b3.id);
42055
42323
  const kept = roles.filter((r) => matches(r, query)).sort(by);
42056
42324
  const mine = kept.filter((r) => r.source === "user"), builtin = kept.filter((r) => r.source === "builtin");
42057
- const open5 = builtinOpen || query.trim() !== "";
42058
- return { mine, builtin, items: [...mine.map((role) => ({ kind: "role", role })), ...builtin.length ? [{ kind: "builtin" }] : [], ...open5 ? builtin.map((role) => ({ kind: "role", role })) : []] };
42325
+ const open6 = builtinOpen || query.trim() !== "";
42326
+ return { mine, builtin, items: [...mine.map((role) => ({ kind: "role", role })), ...builtin.length ? [{ kind: "builtin" }] : [], ...open6 ? builtin.map((role) => ({ kind: "role", role })) : []] };
42059
42327
  }
42060
42328
  function listLines(roles, o, t, g) {
42061
42329
  const r = t.roles, w = o.width;
@@ -42087,11 +42355,11 @@ function listLines(roles, o, t, g) {
42087
42355
  if (mine.length) lines.push([]);
42088
42356
  const s = i === o.sel;
42089
42357
  if (s) selLine = lines.length;
42090
- const open5 = o.builtinOpen || o.query.trim() !== "";
42358
+ const open6 = o.builtinOpen || o.query.trim() !== "";
42091
42359
  const bg = s ? C.sel : void 0;
42092
- lines.push([{ text: s ? "\u258C" : " ", color: s && o.focused ? C.coral : C.faint, ...bg ? { bg } : {} }, { text: `${open5 ? g.unfolded : g.folded} ${r.builtin}`, color: C.dim, ...bg ? { bg } : {} }, { text: ` \xB7 ${builtin.length}`, color: C.faint, ...bg ? { bg } : {} }]);
42360
+ lines.push([{ text: s ? "\u258C" : " ", color: s && o.focused ? C.coral : C.faint, ...bg ? { bg } : {} }, { text: `${open6 ? g.unfolded : g.folded} ${r.builtin}`, color: C.dim, ...bg ? { bg } : {} }, { text: ` \xB7 ${builtin.length}`, color: C.faint, ...bg ? { bg } : {} }]);
42093
42361
  i++;
42094
- if (open5) builtin.forEach(roleLine);
42362
+ if (open6) builtin.forEach(roleLine);
42095
42363
  }
42096
42364
  return { lines, selLine };
42097
42365
  }
@@ -42187,7 +42455,7 @@ function param(label2, value, o, t, g, w, narrow, extra = {}) {
42187
42455
  const usedL = cells(lab.map((x) => x.text).join("")), usedV = cells(val.map((x) => x.text).join(""));
42188
42456
  return { line: [...lab, { text: " ".repeat(lw - usedL) }, ...val, { text: " ".repeat(Math.max(1, w - lw - usedV - cells(p))) }, { text: p, color: provColor(k) }], rail: k, ...extra };
42189
42457
  }
42190
- function paramRows(d, seat2, files, open5, o, t) {
42458
+ function paramRows(d, seat2, files, open6, o, t) {
42191
42459
  const r = t.roles, w = o.width, g = o.g, n = o.narrow;
42192
42460
  const P = (label2, value, origin, extra = {}) => param(label2, value, origin, t, g, w, n, extra);
42193
42461
  const out = [];
@@ -42228,8 +42496,8 @@ function paramRows(d, seat2, files, open5, o, t) {
42228
42496
  const u = prof.uses;
42229
42497
  const loadout = seat2?.live?.skills;
42230
42498
  if (loadout) {
42231
- out.push(P(r.p.skills, v(`${r.skillsProjected(loadout.length)} ${open5.has("loadout") ? g.unfolded : g.folded}`), { kind: "inherited", from: "launch" }, { fold: "loadout" }));
42232
- if (open5.has("loadout")) for (const s of loadout) out.push({ line: [{ text: " ".repeat(n ? 16 : 19) }, { text: s.id, color: C.dim }], rail: "inh" });
42499
+ out.push(P(r.p.skills, v(`${r.skillsProjected(loadout.length)} ${open6.has("loadout") ? g.unfolded : g.folded}`), { kind: "inherited", from: "launch" }, { fold: "loadout" }));
42500
+ if (open6.has("loadout")) for (const s of loadout) out.push({ line: [{ text: " ".repeat(n ? 16 : 19) }, { text: s.id, color: C.dim }], rail: "inh" });
42233
42501
  }
42234
42502
  const list3 = (label2, l, what, fold) => {
42235
42503
  if (!l.declared) {
@@ -42243,9 +42511,9 @@ function paramRows(d, seat2, files, open5, o, t) {
42243
42511
  l.items.forEach((x, i) => {
42244
42512
  const na = !!(seat2 && x.runtime && seat2.runtime.value && x.runtime !== seat2.runtime.value);
42245
42513
  const key = fold?.(x);
42246
- const row3 = P(i === 0 ? label2 : "", v(`${what(x)}${na ? ` \xB7 ${r.na(x.runtime)}` : ""}${key ? ` ${open5.has(key) ? g.unfolded : g.folded}` : ""}`, na ? C.faint : C.fg), x.origin, key ? { fold: key } : {});
42514
+ const row3 = P(i === 0 ? label2 : "", v(`${what(x)}${na ? ` \xB7 ${r.na(x.runtime)}` : ""}${key ? ` ${open6.has(key) ? g.unfolded : g.folded}` : ""}`, na ? C.faint : C.fg), x.origin, key ? { fold: key } : {});
42247
42515
  out.push(na ? { ...row3, line: row3.line.map((s) => ({ ...s, color: C.faint })) } : row3);
42248
- if (key && open5.has(key)) {
42516
+ if (key && open6.has(key)) {
42249
42517
  const body = x.plugin ? x.plugin.skills.join(", ") : (x.absolutePath && files[x.absolutePath]?.content) ?? t.roles.reading;
42250
42518
  for (const bl of body.split("\n")) out.push({ line: cut2([{ text: " ".repeat(n ? 16 : 19) }, { text: bl, color: C.dim }], w), rail: "inh" });
42251
42519
  }
@@ -42333,9 +42601,9 @@ function yamlRows(d, seat2, files, o, t) {
42333
42601
  }
42334
42602
  return out;
42335
42603
  }
42336
- function filesFor(d, seat2, tab, at, open5) {
42604
+ function filesFor(d, seat2, tab, at, open6) {
42337
42605
  if (tab === 0) return roleDocs(d, seat2).map((e) => e.absolutePath).filter((x) => !!x);
42338
- if (tab === 1) return (profileOf(d, seat2)?.uses.runtimeResources.items ?? []).filter((x) => x.absolutePath && open5.has(`rr:${x.id}`)).map((x) => x.absolutePath);
42606
+ if (tab === 1) return (profileOf(d, seat2)?.uses.runtimeResources.items ?? []).filter((x) => x.absolutePath && open6.has(`rr:${x.id}`)).map((x) => x.absolutePath);
42339
42607
  if (tab === 2) {
42340
42608
  const p = pieces(d, seat2)[at];
42341
42609
  return p?.kind === "entry" && p.entry.absolutePath && !isMissing(p.entry) ? [p.entry.absolutePath] : [];
@@ -42412,7 +42680,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42412
42680
  const [scroll, setScroll] = useState14(0);
42413
42681
  const [at, setAt] = useState14(0);
42414
42682
  const [reading, setReading] = useState14(false);
42415
- const [open5, setOpen] = useState14(/* @__PURE__ */ new Set());
42683
+ const [open6, setOpen] = useState14(/* @__PURE__ */ new Set());
42416
42684
  const [files, setFiles] = useState14({});
42417
42685
  const { items } = listItems2(roles.roles, query, builtinOpen);
42418
42686
  const firstRole = items.find((i) => i.kind === "role");
@@ -42438,7 +42706,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42438
42706
  asks.current.reset();
42439
42707
  setFiles({});
42440
42708
  }, [currentId, machine?.id]);
42441
- const wanted = (d ? filesFor(d, seat2, shownTab, at, open5) : []).join("\n");
42709
+ const wanted = (d ? filesFor(d, seat2, shownTab, at, open6) : []).join("\n");
42442
42710
  useEffect13(() => {
42443
42711
  if (!wanted || !machine || !currentId) return;
42444
42712
  const id = currentId, mid = machine.id;
@@ -42451,7 +42719,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42451
42719
  const bodyH = Math.max(3, rows - 4);
42452
42720
  const head2 = d && summary ? headRows(d, summary, { seat: seat2, tab: shownTab, width: w, narrow: !wide, seats }, t) : [];
42453
42721
  const ctx = d && shownTab === 2 ? contextRows(d, seat2, files, at, { width: w, raw, narrow: !wide, g, lang }, t) : null;
42454
- const body = !d ? [] : shownTab === 0 ? roleRows(d, seat2, files, { width: w, raw, narrow: !wide, g, lang }, t) : shownTab === 1 ? paramRows(d, seat2, files, open5, { width: w, narrow: !wide, g }, t) : shownTab === 2 ? ctx.rows : yamlRows(d, seat2, files, { width: w }, t);
42722
+ const body = !d ? [] : shownTab === 0 ? roleRows(d, seat2, files, { width: w, raw, narrow: !wide, g, lang }, t) : shownTab === 1 ? paramRows(d, seat2, files, open6, { width: w, narrow: !wide, g }, t) : shownTab === 2 ? ctx.rows : yamlRows(d, seat2, files, { width: w }, t);
42455
42723
  const room = Math.max(1, bodyH - head2.length);
42456
42724
  const maxScroll = Math.max(0, body.length - room);
42457
42725
  const params = shownTab === 1 ? body.map((x, i) => x.rail ? i : -1).filter((i) => i >= 0) : [];
@@ -42709,7 +42977,7 @@ function RigsView({ initialMachine, onRoles }) {
42709
42977
  const rigs = listedRigs(state2);
42710
42978
  const [at, setAt] = useState15(0);
42711
42979
  const selectedId = useRef13(null);
42712
- const [open5, setOpen] = useState15(/* @__PURE__ */ new Set());
42980
+ const [open6, setOpen] = useState15(/* @__PURE__ */ new Set());
42713
42981
  const [seatAt, setSeatAt] = useState15(null);
42714
42982
  const [confirm, setConfirm] = useState15(null);
42715
42983
  const [seats, setSeats] = useState15(null);
@@ -42719,7 +42987,7 @@ function RigsView({ initialMachine, onRoles }) {
42719
42987
  }, [ids]);
42720
42988
  const current2 = rigs[Math.min(at, rigs.length - 1)];
42721
42989
  selectedId.current = current2?.id ?? null;
42722
- const seatNodes = current2 && open5.has(current2.id) ? current2.nodes : [];
42990
+ const seatNodes = current2 && open6.has(current2.id) ? current2.nodes : [];
42723
42991
  const seatSel = seatAt !== null && seatAt < seatNodes.length ? seatAt : null;
42724
42992
  const down = () => {
42725
42993
  if (seatSel !== null ? seatSel + 1 < seatNodes.length : seatNodes.length > 0) {
@@ -42737,7 +43005,7 @@ function RigsView({ initialMachine, onRoles }) {
42737
43005
  }
42738
43006
  const n = step2(at, -1, rigs.length), prev = rigs[n];
42739
43007
  setAt(n);
42740
- setSeatAt(prev && open5.has(prev.id) && prev.nodes.length ? prev.nodes.length - 1 : null);
43008
+ setSeatAt(prev && open6.has(prev.id) && prev.nodes.length ? prev.nodes.length - 1 : null);
42741
43009
  };
42742
43010
  const run4 = (a) => {
42743
43011
  void rig.act(a).then((d) => {
@@ -42897,12 +43165,12 @@ function RigsView({ initialMachine, onRoles }) {
42897
43165
  const keys = seats ? [["\u2191 \u2193", t.rigSeats.role], ["- +", t.rigSeats.count], ["\u2190 \u2192", t.rigSeats.delegationKey], ["\u21B5", t.rigSeats.apply], ["esc", t.rigSeats.cancel]] : confirm ? [["y", o.yes], ["n", o.no]] : [["tab", o.machine], ["v", t.roles.k.view], ["j k", t.keys.navigate], ["\u21B5", o.unfold], ...seatSel !== null ? [["i", t.roles.k.role]] : [], ["s", o.toggle], ["R", o.restart], ["\xB1 =", t.rigSeats.key], ["+", t.rigLaunch.launchKey], ["D", o.daemonKey], ["K", o.kernelKey], ["O", o.operatorKey], ["esc", t.keys.back]];
42898
43166
  const crumb = [state2?.version ? o.version(state2.version) : o.title, machine?.label, t.roles.rigs].filter(Boolean).join(" \xB7 ");
42899
43167
  return /* @__PURE__ */ jsxs17(Frame, { crumb, crumbShort: o.title, keys, children: [
42900
- !machine ? null : rig.error ? /* @__PURE__ */ jsx22(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx22(Text18, { color: C.yellow, children: rig.error === "offline" ? o.offline : o.outdated }) }) : !state2 ? /* @__PURE__ */ jsx22(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: o.loading }) }) : !state2.installed ? /* @__PURE__ */ jsx22(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: o.notInstalled }) }) : seats && seatRig ? /* @__PURE__ */ jsx22(RigSeats, { rig: seatRig, machine: machine.label, rows, counts: seats.counts, at: seats.at, delegation: seats.delegation, plan: planOf(seatRig, rows, seats.counts), applying: seats.applying }) : /* @__PURE__ */ jsx22(Body, { state: state2, rigs, at, seatAt: seatSel, open: open5, machineLabel: machine.label, roleOf: roleOf4 }),
43168
+ !machine ? null : rig.error ? /* @__PURE__ */ jsx22(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx22(Text18, { color: C.yellow, children: rig.error === "offline" ? o.offline : o.outdated }) }) : !state2 ? /* @__PURE__ */ jsx22(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: o.loading }) }) : !state2.installed ? /* @__PURE__ */ jsx22(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: o.notInstalled }) }) : seats && seatRig ? /* @__PURE__ */ jsx22(RigSeats, { rig: seatRig, machine: machine.label, rows, counts: seats.counts, at: seats.at, delegation: seats.delegation, plan: planOf(seatRig, rows, seats.counts), applying: seats.applying }) : /* @__PURE__ */ jsx22(Body, { state: state2, rigs, at, seatAt: seatSel, open: open6, machineLabel: machine.label, roleOf: roleOf4 }),
42901
43169
  confirm ? /* @__PURE__ */ jsx22(ConfirmBox, { confirm }) : null,
42902
43170
  rig.pending ? /* @__PURE__ */ jsx22(Box17, { paddingX: 1, height: 1, children: /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: C.blue, children: o.running(confirmOf(state2 ?? EMPTY2, rig.pending).commands.join(" && ")) }) }) : null
42903
43171
  ] });
42904
43172
  }
42905
- function Body({ state: state2, rigs, at, seatAt, open: open5, machineLabel: machineLabel2, roleOf: roleOf4 }) {
43173
+ function Body({ state: state2, rigs, at, seatAt, open: open6, machineLabel: machineLabel2, roleOf: roleOf4 }) {
42906
43174
  const { t } = useTui();
42907
43175
  const o = t.openrig;
42908
43176
  const up = daemonUp(state2);
@@ -42940,10 +43208,10 @@ function Body({ state: state2, rigs, at, seatAt, open: open5, machineLabel: mach
42940
43208
  ] }, n.session))
42941
43209
  ] })
42942
43210
  ] }),
42943
- /* @__PURE__ */ jsx22(TitledBox, { title: `${o.rigs} \xB7 ${rigs.length}`, flexGrow: 1, children: !up ? /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: o.stoppedDaemon }) : !rigs.length ? /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: o.noRigs }) : rigs.map((r, i) => /* @__PURE__ */ jsx22(RigRow, { rig: r, selected: i === at && seatAt === null, seatAt: i === at ? seatAt : null, open: open5.has(r.id), roleOf: roleOf4 }, r.id)) })
43211
+ /* @__PURE__ */ jsx22(TitledBox, { title: `${o.rigs} \xB7 ${rigs.length}`, flexGrow: 1, children: !up ? /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: o.stoppedDaemon }) : !rigs.length ? /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: o.noRigs }) : rigs.map((r, i) => /* @__PURE__ */ jsx22(RigRow, { rig: r, selected: i === at && seatAt === null, seatAt: i === at ? seatAt : null, open: open6.has(r.id), roleOf: roleOf4 }, r.id)) })
42944
43212
  ] });
42945
43213
  }
42946
- function RigRow({ rig, selected, seatAt, open: open5, roleOf: roleOf4 }) {
43214
+ function RigRow({ rig, selected, seatAt, open: open6, roleOf: roleOf4 }) {
42947
43215
  const { t } = useTui();
42948
43216
  const o = t.openrig;
42949
43217
  const look = rigLook(rig), l = LOOK[look];
@@ -42953,14 +43221,14 @@ function RigRow({ rig, selected, seatAt, open: open5, roleOf: roleOf4 }) {
42953
43221
  return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
42954
43222
  /* @__PURE__ */ jsx22(Box17, { backgroundColor: selected ? C.sel : void 0, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
42955
43223
  /* @__PURE__ */ jsx22(Text18, { color: selected ? C.coral : C.faint, children: selected ? "\u258C" : " " }),
42956
- /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: open5 ? "\u25BE " : "\u25B8 " }),
43224
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: open6 ? "\u25BE " : "\u25B8 " }),
42957
43225
  /* @__PURE__ */ jsx22(Text18, { color: C.fg, bold: true, children: rig.name.padEnd(16) }),
42958
43226
  /* @__PURE__ */ jsx22(Text18, { color: l.color(), children: ` ${l.glyph} ${o.look[look]}`.padEnd(18) }),
42959
43227
  /* @__PURE__ */ jsx22(Text18, { color: C.fg, children: o.seats(rig.running, rig.seats).padEnd(7) }),
42960
43228
  note ? /* @__PURE__ */ jsx22(Text18, { color: note.color, children: note.text.padEnd(34) }) : /* @__PURE__ */ jsx22(Text18, { children: "".padEnd(34) }),
42961
43229
  /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: when })
42962
43230
  ] }) }),
42963
- open5 ? rig.nodes.map((n, i) => {
43231
+ open6 ? rig.nodes.map((n, i) => {
42964
43232
  const s = i === seatAt, bg = s ? C.sel : void 0, role = roleOf4(rig.name, n.logicalId);
42965
43233
  return /* @__PURE__ */ jsx22(Box17, { backgroundColor: bg, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
42966
43234
  /* @__PURE__ */ jsx22(Text18, { color: C.coral, children: s ? "\u258C" : " " }),
@@ -43472,7 +43740,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
43472
43740
  const attach2 = useCallback3((s) => {
43473
43741
  const cmd = attachCommand(fleet, s);
43474
43742
  if ("why" in cmd) {
43475
- toast({ text: cmd.why === "offline" ? t.toast.offline(s.name) : cmd.why === "omp" ? t.toast.noTerminalOmp(s.name) : t.toast.noTerminal(s.name), tone: "info" });
43743
+ toast({ text: cmd.why === "offline" ? t.toast.offline(s.name) : cmd.why === "omp" ? t.toast.noTerminalOmp(s.name) : cmd.why === "stopped" ? t.toast.seatStopped(s.name) : t.toast.noTerminal(s.name), tone: "info" });
43476
43744
  return;
43477
43745
  }
43478
43746
  void suspendTerminal(async () => {
@@ -43772,7 +44040,7 @@ var init_glyphs = __esm({
43772
44040
 
43773
44041
  // src/tui/settings.ts
43774
44042
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
43775
- import { join as join38 } from "path";
44043
+ import { join as join39 } from "path";
43776
44044
  import { z as z32 } from "zod";
43777
44045
  function defaultLang(env) {
43778
44046
  const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
@@ -43798,14 +44066,14 @@ var init_settings = __esm({
43798
44066
  "use strict";
43799
44067
  init_paths();
43800
44068
  SettingsSchema = z32.object({ lang: z32.enum(["fr", "en"]).optional(), theme: z32.enum(["auto", "dark", "light"]).optional() }).passthrough();
43801
- settingsPath = () => join38(ledgeDir(), "tui.json");
44069
+ settingsPath = () => join39(ledgeDir(), "tui.json");
43802
44070
  }
43803
44071
  });
43804
44072
 
43805
44073
  // src/tui/terminalProfile.ts
43806
44074
  import { execFile as execFile10, spawn as spawn6 } from "child_process";
43807
- import { mkdir as mkdir14, readFile as readFile18, realpath as realpath5, writeFile as writeFile18 } from "fs/promises";
43808
- import { dirname as dirname13, join as join39 } from "path";
44075
+ import { mkdir as mkdir14, readFile as readFile19, realpath as realpath5, writeFile as writeFile18 } from "fs/promises";
44076
+ import { dirname as dirname13, join as join40 } from "path";
43809
44077
  import { fileURLToPath as fileURLToPath4 } from "url";
43810
44078
  import { promisify } from "util";
43811
44079
  function movesToLedgeTab(env, here, script) {
@@ -43833,13 +44101,13 @@ function fragment(commandline, iconWin) {
43833
44101
  }
43834
44102
  async function profileLoaded(localAppData) {
43835
44103
  for (const rel of SETTINGS) {
43836
- const text7 = await readFile18(join39(localAppData, rel), "utf8").catch(() => "");
44104
+ const text7 = await readFile19(join40(localAppData, rel), "utf8").catch(() => "");
43837
44105
  if (new RegExp(`"source"\\s*:\\s*"${PROFILE}"`).test(text7)) return true;
43838
44106
  }
43839
44107
  return false;
43840
44108
  }
43841
44109
  async function writeIfChanged2(path3, content3) {
43842
- const current2 = await readFile18(path3).catch(() => null);
44110
+ const current2 = await readFile19(path3).catch(() => null);
43843
44111
  if (current2 && current2.equals(Buffer.from(content3))) return false;
43844
44112
  await writeFile18(path3, content3);
43845
44113
  return true;
@@ -43851,9 +44119,9 @@ async function openInLedgeTab(o) {
43851
44119
  await writeIfChanged2(o.launcher, launcherScript(o.node, o.script, o.ledgeHome));
43852
44120
  const dir = fragmentDir(o.localAppData.linux);
43853
44121
  await mkdir14(dir, { recursive: true });
43854
- await writeIfChanged2(join39(dir, "ledge.ico"), await readFile18(o.icon ?? ICON));
44122
+ await writeIfChanged2(join40(dir, "ledge.ico"), await readFile19(o.icon ?? ICON));
43855
44123
  const iconWin = `${o.localAppData.win}\\Microsoft\\Windows Terminal\\Fragments\\${PROFILE}\\ledge.ico`;
43856
- await writeIfChanged2(join39(dir, "ledge.json"), fragment(commandline, iconWin));
44124
+ await writeIfChanged2(join40(dir, "ledge.json"), fragment(commandline, iconWin));
43857
44125
  const deadline = Date.now() + (o.waitMs ?? 15e3);
43858
44126
  for (let first = true; !await profileLoaded(o.localAppData.linux); first = false) {
43859
44127
  if (Date.now() > deadline) return false;
@@ -43874,11 +44142,11 @@ async function openTuiInLedgeTab(env, onWait) {
43874
44142
  distro: env.WSL_DISTRO_NAME,
43875
44143
  node: await realpath5(process.execPath),
43876
44144
  script: await realpath5(process.argv[1]),
43877
- launcher: join39(ledgeDir(), "tui.sh"),
44145
+ launcher: join40(ledgeDir(), "tui.sh"),
43878
44146
  ledgeHome: env.LEDGE_HOME,
43879
44147
  onWait,
43880
44148
  wt: (args) => new Promise((resolve13, reject) => {
43881
- const child = spawn6(join39(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
44149
+ const child = spawn6(join40(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
43882
44150
  child.once("error", reject);
43883
44151
  child.once("spawn", () => {
43884
44152
  child.unref();
@@ -43902,7 +44170,7 @@ var init_terminalProfile = __esm({
43902
44170
  "Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json",
43903
44171
  "Microsoft/Windows Terminal/settings.json"
43904
44172
  ];
43905
- fragmentDir = (localAppData) => join39(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
44173
+ fragmentDir = (localAppData) => join40(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
43906
44174
  PROFILE_ID = "{d10fee13-0978-595f-a851-b60ec80f11c9}";
43907
44175
  shQuote2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
43908
44176
  run3 = promisify(execFile10);