@paradow/ledge 0.40.0 → 0.41.0-snapshot.41716003612.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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"() {
@@ -2277,7 +2283,7 @@ var init_messages = __esm({
2277
2283
  agentSessionId: z24.string().optional()
2278
2284
  });
2279
2285
  RecentSchema = z24.object({ cwd: z24.string().min(1), project: z24.string().min(1), lastAt: z24.string().datetime() });
2280
- SEMVER = /^\d+\.\d+\.\d+$/;
2286
+ SEMVER = /^\d+\.\d+\.\d+(?:-snapshot\.[1-9]\d*\.[1-9]\d*)?$/;
2281
2287
  DaemonInfoSchema = z24.object({
2282
2288
  version: z24.string(),
2283
2289
  install: z24.enum(["npm", "git", "unknown"]),
@@ -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-snapshot.41716003612.1" : "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;
@@ -14379,7 +14589,16 @@ function compareVersions(a, b3) {
14379
14589
  const d = (x[i] ?? 0) - (y[i] ?? 0);
14380
14590
  if (d) return d > 0 ? 1 : -1;
14381
14591
  }
14382
- return a === b3 ? 0 : a.includes("-") ? -1 : b3.includes("-") ? 1 : 0;
14592
+ if (a === b3) return 0;
14593
+ const ax = a.match(/-snapshot\.(\d+)\.(\d+)$/), bx = b3.match(/-snapshot\.(\d+)\.(\d+)$/);
14594
+ if (ax && bx) {
14595
+ for (const i of [1, 2]) {
14596
+ const x2 = BigInt(ax[i]), y2 = BigInt(bx[i]);
14597
+ if (x2 !== y2) return x2 > y2 ? 1 : -1;
14598
+ }
14599
+ return 0;
14600
+ }
14601
+ return a.includes("-") ? -1 : b3.includes("-") ? 1 : 0;
14383
14602
  }
14384
14603
  var parts;
14385
14604
  var init_semver = __esm({
@@ -15173,15 +15392,15 @@ var init_operations = __esm({
15173
15392
  });
15174
15393
 
15175
15394
  // src/mobile/risk.ts
15176
- import { homedir as homedir12 } from "os";
15177
- import { resolve as resolve11, sep as sep5, join as join27 } from "path";
15395
+ import { homedir as homedir13 } from "os";
15396
+ import { resolve as resolve11, sep as sep5, join as join28 } from "path";
15178
15397
  function isSensitive(cwd, p) {
15179
15398
  if (p.includes("~")) return true;
15180
15399
  const full = resolve11(cwd, p);
15181
15400
  const segs = full.split(sep5);
15182
15401
  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));
15402
+ const home2 = homedir13();
15403
+ return [join28(home2, ".ssh"), join28(home2, ".ledge")].some((d) => full === d || full.startsWith(d + sep5));
15185
15404
  }
15186
15405
  function classifyShell(command, cwd) {
15187
15406
  if (PUSH.test(command)) return high("pushes");
@@ -16951,9 +17170,13 @@ var init_notifier = __esm({
16951
17170
  // src/daemon/server.ts
16952
17171
  import { createServer } from "http";
16953
17172
  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";
17173
+ import { homedir as homedir14 } from "os";
17174
+ import { basename as basename12, join as join29, resolve as resolve12 } from "path";
16956
17175
  import { WebSocketServer, WebSocket as WebSocket3 } from "ws";
17176
+ function withoutHarness(s) {
17177
+ const { pending: _p, lastReply: _r, transcriptPath: _t, agentSessionId: _a, agentSessionFile: _f, model: _m, effort: _e, modelNext: _n, queue: _q, backend: _b, ...rest } = s;
17178
+ return { ...rest, helpers: [] };
17179
+ }
16957
17180
  function resumeKey(s) {
16958
17181
  if (s.tool === "omp") return s.agentSessionFile;
16959
17182
  if (s.agentSessionId) return s.agentSessionId;
@@ -17003,11 +17226,12 @@ async function startServer(opts) {
17003
17226
  const agentEnv2 = opts.agentEnv ?? (async () => ({ ...process.env, ...await loginShellEnv() }));
17004
17227
  const attachments = opts.attachments ?? new AttachmentStore(paths().attachments);
17005
17228
  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() });
17229
+ 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
17230
  const chat = opts.chat ?? threads.desktop;
17008
17231
  const router = createRouter({ env: agentEnv2 });
17009
- const omp2 = opts.omp ?? createOmpHost({ store, env: agentEnv2, dir: join28(paths().dir, "omp"), now: now2, attachments });
17232
+ const omp2 = opts.omp ?? createOmpHost({ store, env: agentEnv2, dir: join29(paths().dir, "omp"), now: now2, attachments });
17010
17233
  const openrig = opts.openrig ?? createOpenRig({ env: agentEnv2 });
17234
+ const ompSeatReadOnly = (s) => new Refusal(`${s.name} is the OpenRig OMP seat ${s.rig?.seat ?? ""}: Ledge follows its state only`, "read_only");
17011
17235
  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
17236
  const adoptSeat = (id, seat2) => {
17013
17237
  const session = store.get(id);
@@ -17020,9 +17244,22 @@ async function startServer(opts) {
17020
17244
  const seatId = (seat2) => `rig-${seat2.replace(/[^A-Za-z0-9._-]/g, "-")}`;
17021
17245
  async function discoverSeats(st) {
17022
17246
  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));
17247
+ const followed = new Map(store.all().filter((x) => x.rig && x.state !== "exited").map((x) => [x.rig.seat, x.id]));
17024
17248
  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;
17249
+ const tool = SEAT_TOOLS[n.runtime];
17250
+ if (!tool || n.sessionStatus !== "running") continue;
17251
+ const live3 = followed.get(n.session);
17252
+ if (live3 !== void 0) {
17253
+ const s = store.get(live3);
17254
+ if (s && s.tool !== tool && (s.source === "rig" || s.source === "hook_only")) {
17255
+ if (s.source === "hook_only" && !await tmux.hasSession(n.session).catch(() => false)) continue;
17256
+ const cur = store.get(live3);
17257
+ if (!cur || cur.state === "exited" || cur.tool === tool) continue;
17258
+ store.upsert({ ...withoutHarness(cur), tool, state: "idle", source: "rig", tmuxSession: n.session, lastActivityAt: now2() });
17259
+ log(`openrig: seat ${n.session} now runs ${n.runtime}`);
17260
+ }
17261
+ continue;
17262
+ }
17026
17263
  if (!await tmux.hasSession(n.session).catch(() => false)) continue;
17027
17264
  const before2 = store.all().filter((x) => x.rig?.seat === n.session).sort((a, b3) => b3.lastActivityAt.localeCompare(a.lastActivityAt))[0];
17028
17265
  const id = before2?.id ?? seatId(n.session), old = store.get(id), at = now2();
@@ -17031,13 +17268,14 @@ async function startServer(opts) {
17031
17268
  if (old) {
17032
17269
  if (old.archivedAt) store.unarchive(id, at);
17033
17270
  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 });
17271
+ const same2 = rest.tool === tool ? rest : { ...withoutHarness(rest), tool };
17272
+ store.upsert({ ...same2, state: "idle", tmuxSession: n.session, source: "rig", rig, lastActivityAt: at });
17035
17273
  } else {
17036
17274
  const name = pickName([...namesInUse(store.all()), ...heldNames(store.all())]);
17037
17275
  store.upsert({
17038
17276
  id,
17039
17277
  name,
17040
- tool: n.runtime === "codex" ? "codex" : "claude",
17278
+ tool,
17041
17279
  cwd,
17042
17280
  project: basename12(cwd) || r.name,
17043
17281
  title: `${name} \xB7 ${n.logicalId}`,
@@ -17051,10 +17289,21 @@ async function startServer(opts) {
17051
17289
  });
17052
17290
  log(`openrig: seat ${n.session} followed as ${name}`);
17053
17291
  }
17054
- followed.add(n.session);
17292
+ followed.set(n.session, id);
17293
+ }
17294
+ if (!st.rigs.some((r) => r.nodes.length > 0)) return;
17295
+ const listed2 = new Set(st.rigs.flatMap((r) => r.nodes.map((n) => n.session)));
17296
+ const unsure = new Set(st.rigs.filter((r) => r.seats > 0 && r.nodes.length === 0).map((r) => r.name));
17297
+ for (const s of store.all()) {
17298
+ if (isOmpSeat2(s) && s.rig && s.state === "exited" && !isArchived(s) && !listed2.has(s.rig.seat) && !unsure.has(s.rig.name)) {
17299
+ store.archive(s.id, now2());
17300
+ log(`openrig: seat ${s.rig.seat} is gone from OpenRig`);
17301
+ }
17055
17302
  }
17056
17303
  }
17057
17304
  const SEATS_EVERY_MS = 3e4;
17305
+ const ompSeats = opts.ompSeats ?? createOmpSeats({ store });
17306
+ ompSeats.start();
17058
17307
  const seatTimer = setInterval(() => {
17059
17308
  void openrig.state().then(discoverSeats).catch((e) => log(`openrig: seats not read: ${e.message}`));
17060
17309
  }, SEATS_EVERY_MS);
@@ -17100,7 +17349,7 @@ async function startServer(opts) {
17100
17349
  }).catch((e) => log(`openrig: seat ${seat2} not read: ${e.message}`));
17101
17350
  };
17102
17351
  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())) });
17352
+ const models = opts.models ?? createModels({ env: agentEnv2, omp: async () => ompChoices(await omp2.catalog(opts.home ?? homedir14())) });
17104
17353
  const token = await ensureDaemonToken();
17105
17354
  const httpServer = createServer(unauthorized);
17106
17355
  const sockets = /* @__PURE__ */ new Set();
@@ -17140,7 +17389,7 @@ async function startServer(opts) {
17140
17389
  const islandFile = opts.islandFile ?? paths().island;
17141
17390
  let island = readIsland(islandFile);
17142
17391
  const islandMsg = () => ({ type: "island", prefs: island });
17143
- const contextFile = opts.contextFile ?? join28(paths().dir, "context.json");
17392
+ const contextFile = opts.contextFile ?? join29(paths().dir, "context.json");
17144
17393
  let contextPrefs = readContextPrefs(contextFile);
17145
17394
  const contextMsg = () => ({ type: "context.prefs", prefs: contextPrefs });
17146
17395
  const mobile = opts.mobile;
@@ -17239,7 +17488,7 @@ async function startServer(opts) {
17239
17488
  tasks,
17240
17489
  launch: (req) => launch3(req),
17241
17490
  close: (s) => closeSession(s),
17242
- dir: join28(paths().dir, "handovers"),
17491
+ dir: join29(paths().dir, "handovers"),
17243
17492
  now: now2,
17244
17493
  ...opts.handoverBriefMs !== void 0 ? { briefMs: opts.handoverBriefMs } : {}
17245
17494
  });
@@ -17515,6 +17764,7 @@ async function startServer(opts) {
17515
17764
  const s = store.get(id);
17516
17765
  if (!s) return unknownSession();
17517
17766
  if (s.source === "hook_only") return readOnly();
17767
+ if (isOmpSeat2(s)) return ompSeatReadOnly(s);
17518
17768
  if (closing.has(id)) return new Refusal("the session is being closed", "busy");
17519
17769
  const ids = how.attachments ?? [];
17520
17770
  const refs = [];
@@ -17848,7 +18098,7 @@ async function startServer(opts) {
17848
18098
  let scanning = null;
17849
18099
  function sharedScan() {
17850
18100
  if (scanning) return scanning;
17851
- const home2 = opts.home ?? homedir13();
18101
+ const home2 = opts.home ?? homedir14();
17852
18102
  const extra = extraRoots(home2, [...store.recents().map((r) => r.cwd), ...store.all().filter((s) => s.state !== "exited").map((s) => s.cwd)]);
17853
18103
  const walk = (opts.scan ?? scanRepos)({ home: home2, extra }).finally(() => {
17854
18104
  if (scanning === walk) scanning = null;
@@ -18127,6 +18377,7 @@ async function startServer(opts) {
18127
18377
  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
18378
  }
18129
18379
  if (!s?.tmuxSession) return;
18380
+ if (isOmpSeat2(s)) return refuse(ompSeatReadOnly(s), msg.id);
18130
18381
  await tmux.sendKeys(s.tmuxSession, adapterFor(s.tool).interruptKeys());
18131
18382
  return;
18132
18383
  }
@@ -18357,7 +18608,7 @@ async function startServer(opts) {
18357
18608
  relay,
18358
18609
  launchChat: (thread, qid, rows) => {
18359
18610
  const c = thread === "desktop" ? threads.desktop : threads.device(thread.slice("device:".length));
18360
- const userHome = resolve12(opts.home ?? homedir13());
18611
+ const userHome = resolve12(opts.home ?? homedir14());
18361
18612
  return launchFromChat({
18362
18613
  chat: c,
18363
18614
  emit: threads.emitterOf(thread) ?? (() => {
@@ -18501,6 +18752,7 @@ async function startServer(opts) {
18501
18752
  close: () => new Promise((res) => {
18502
18753
  clearInterval(sweepTimer);
18503
18754
  clearInterval(seatTimer);
18755
+ ompSeats.stop();
18504
18756
  human.stop();
18505
18757
  if (goalsTimer) clearTimeout(goalsTimer);
18506
18758
  void reducer.close();
@@ -18520,7 +18772,7 @@ async function startServer(opts) {
18520
18772
  })
18521
18773
  };
18522
18774
  }
18523
- var CONTINUE_ARGS, RESUME_ARGS, MULTI_DIR_ARGS, MODEL_ARGS, EFFORT_ARGS, NoteNotTyped, DELETED_TTL_MS, RESPAWN_HOOKS_MS, ALLOWED_ORIGINS;
18775
+ var SEAT_TOOLS, CONTINUE_ARGS, RESUME_ARGS, MULTI_DIR_ARGS, MODEL_ARGS, EFFORT_ARGS, NoteNotTyped, DELETED_TTL_MS, RESPAWN_HOOKS_MS, ALLOWED_ORIGINS;
18524
18776
  var init_server = __esm({
18525
18777
  "src/daemon/server.ts"() {
18526
18778
  "use strict";
@@ -18540,6 +18792,7 @@ var init_server = __esm({
18540
18792
  init_spawn();
18541
18793
  init_host();
18542
18794
  init_catalog();
18795
+ init_seats();
18543
18796
  init_repos2();
18544
18797
  init_scan2();
18545
18798
  init_folders();
@@ -18578,6 +18831,7 @@ var init_server = __esm({
18578
18831
  init_challenges();
18579
18832
  init_webauthn();
18580
18833
  init_frames();
18834
+ SEAT_TOOLS = { "claude-code": "claude", codex: "codex", omp: "omp" };
18581
18835
  CONTINUE_ARGS = { claude: ["--continue"], codex: ["resume", "--last"], cursor: ["--continue"] };
18582
18836
  RESUME_ARGS = { claude: (id) => ["--resume", id], codex: (id) => ["resume", id], cursor: (id) => ["--resume", id] };
18583
18837
  MULTI_DIR_ARGS = { claude: (dirs) => ["--add-dir", ...dirs] };
@@ -18606,7 +18860,7 @@ var init_server = __esm({
18606
18860
  // src/tmux.ts
18607
18861
  import { execFile as execFile7 } from "child_process";
18608
18862
  import { existsSync as existsSync3 } from "fs";
18609
- import { join as join29 } from "path";
18863
+ import { join as join30 } from "path";
18610
18864
  function runFile(file, args, env, opts) {
18611
18865
  return new Promise((resolve13) => {
18612
18866
  execFile7(file, args, { maxBuffer: 4 * 1024 * 1024, env, ...opts?.timeoutMs ? { timeout: opts.timeoutMs, killSignal: "SIGTERM" } : {} }, (err, stdout, stderr) => {
@@ -18616,7 +18870,7 @@ function runFile(file, args, env, opts) {
18616
18870
  });
18617
18871
  }
18618
18872
  function systemdAvailable(env = process.env, exists = existsSync3) {
18619
- return !!env.XDG_RUNTIME_DIR && exists(join29(env.XDG_RUNTIME_DIR, "systemd", "private"));
18873
+ return !!env.XDG_RUNTIME_DIR && exists(join30(env.XDG_RUNTIME_DIR, "systemd", "private"));
18620
18874
  }
18621
18875
  function newSessionCommand(tmuxArgs, scoped, unit) {
18622
18876
  if (!scoped) return { file: "tmux", args: tmuxArgs };
@@ -18749,10 +19003,10 @@ __export(setup_exports, {
18749
19003
  systemdUnit: () => systemdUnit,
18750
19004
  writeBin: () => writeBin
18751
19005
  });
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";
19006
+ 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
19007
  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";
19008
+ import { homedir as homedir15, userInfo } from "os";
19009
+ import { join as join31, dirname as dirname10 } from "path";
18756
19010
  function mergeHooks(settings, events, command, file) {
18757
19011
  if (settings === void 0) settings = {};
18758
19012
  if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error(`${file} root is not an object, not modified`);
@@ -18800,7 +19054,7 @@ function mergeCursorHooks(config, command = "ledge hook cursor") {
18800
19054
  async function mergeJsonFile(path3, merge, name) {
18801
19055
  let raw = null;
18802
19056
  try {
18803
- raw = await readFile12(path3, "utf8");
19057
+ raw = await readFile13(path3, "utf8");
18804
19058
  } catch (e) {
18805
19059
  if (e.code !== "ENOENT") throw e;
18806
19060
  }
@@ -18836,7 +19090,7 @@ WantedBy=default.target
18836
19090
  async function writeIfChanged(path3, content3) {
18837
19091
  let current2 = null;
18838
19092
  try {
18839
- current2 = await readFile12(path3, "utf8");
19093
+ current2 = await readFile13(path3, "utf8");
18840
19094
  } catch (e) {
18841
19095
  if (e.code !== "ENOENT") throw e;
18842
19096
  }
@@ -18850,7 +19104,7 @@ async function writeIfChanged(path3, content3) {
18850
19104
  async function stopDetachedDaemon(pidFile) {
18851
19105
  let pid;
18852
19106
  try {
18853
- pid = Number((await readFile12(pidFile, "utf8")).trim());
19107
+ pid = Number((await readFile13(pidFile, "utf8")).trim());
18854
19108
  } catch {
18855
19109
  return null;
18856
19110
  }
@@ -18880,10 +19134,10 @@ async function stopDetachedDaemon(pidFile) {
18880
19134
  async function installService(opts = {}) {
18881
19135
  const out = [];
18882
19136
  const run4 = opts.run ?? execRunner;
18883
- const unitDir = opts.unitDir ?? join30(homedir14(), ".config", "systemd", "user");
19137
+ const unitDir = opts.unitDir ?? join31(homedir15(), ".config", "systemd", "user");
18884
19138
  const node2 = opts.node ?? await realpath2(process.execPath);
18885
19139
  const script = opts.script ?? await realpath2(process.argv[1]);
18886
- const changed = await writeIfChanged(join30(unitDir, SERVICE), systemdUnit({ node: node2, script }));
19140
+ const changed = await writeIfChanged(join31(unitDir, SERVICE), systemdUnit({ node: node2, script }));
18887
19141
  const reload = await run4("systemctl", ["--user", "daemon-reload"]);
18888
19142
  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
19143
  const active = (await run4("systemctl", ["--user", "is-active", SERVICE])).code === 0;
@@ -18912,7 +19166,7 @@ async function installService(opts = {}) {
18912
19166
  }
18913
19167
  async function isDir(path3) {
18914
19168
  try {
18915
- return (await stat6(path3)).isDirectory();
19169
+ return (await stat7(path3)).isDirectory();
18916
19170
  } catch {
18917
19171
  return false;
18918
19172
  }
@@ -18921,7 +19175,7 @@ async function writeBin(opts) {
18921
19175
  const st = await lstat(opts.link).catch(() => null);
18922
19176
  const was = st?.isSymbolicLink() ? await readlink(opts.link) : null;
18923
19177
  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(() => "") : "";
19178
+ const current2 = st && was === null && st.isFile() ? await readFile13(opts.link, "utf8").catch(() => "") : "";
18925
19179
  if (st && was === null && !current2.startsWith(BIN_MARK)) return null;
18926
19180
  const script = `${BIN_MARK}: the hooks run \`ledge\` wherever node is not on the PATH.
18927
19181
  exec ${shQuote(opts.node)} ${shQuote(opts.target)} "$@"
@@ -18937,24 +19191,24 @@ async function runSetup(opts = {}) {
18937
19191
  const out = [];
18938
19192
  await ensureLedgeDir();
18939
19193
  out.push("~/.ledge ready (tmux.conf written if missing)");
18940
- const settingsPath2 = opts.settingsPath ?? join30(homedir14(), ".claude", "settings.json");
19194
+ const settingsPath2 = opts.settingsPath ?? join31(homedir15(), ".claude", "settings.json");
18941
19195
  if (await mergeJsonFile(settingsPath2, mergeClaudeHooks, "settings.json")) out.push(`Claude Code hooks added in ${settingsPath2}`);
18942
19196
  else out.push("Claude Code hooks already present");
18943
- const codexDir = opts.codexDir ?? process.env.CODEX_HOME ?? join30(homedir14(), ".codex");
19197
+ const codexDir = opts.codexDir ?? process.env.CODEX_HOME ?? join31(homedir15(), ".codex");
18944
19198
  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`);
19199
+ else if (await mergeJsonFile(join31(codexDir, "hooks.json"), mergeCodexHooks, "hooks.json")) {
19200
+ 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
19201
  } else out.push("Codex hooks already present");
18948
- const cursorDir = opts.cursorDir ?? join30(homedir14(), ".cursor");
19202
+ const cursorDir = opts.cursorDir ?? join31(homedir15(), ".cursor");
18949
19203
  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")}`);
19204
+ else if (await mergeJsonFile(join31(cursorDir, "hooks.json"), mergeCursorHooks, "hooks.json")) out.push(`Cursor Agent hooks added in ${join31(cursorDir, "hooks.json")}`);
18951
19205
  else out.push("Cursor Agent hooks already present");
18952
19206
  if (opts.service ?? true) {
18953
19207
  const cli = opts.cli ?? process.argv[1] ?? "";
18954
19208
  if (cli.endsWith(".ts")) out.push("systemd service skipped: run setup from the built CLI (pnpm --filter @paradow/ledge build, then ledge setup)");
18955
19209
  else {
18956
19210
  const script = await realpath2(cli);
18957
- const line2 = await writeBin({ link: opts.link ?? join30(homedir14(), ".local", "bin", "ledge"), node: process.execPath, target: script });
19211
+ const line2 = await writeBin({ link: opts.link ?? join31(homedir15(), ".local", "bin", "ledge"), node: process.execPath, target: script });
18958
19212
  if (line2) out.push(line2);
18959
19213
  out.push(...await installService({ script, unitDir: opts.unitDir, run: opts.run }));
18960
19214
  }
@@ -19016,10 +19270,10 @@ var init_setup = __esm({
19016
19270
  });
19017
19271
 
19018
19272
  // src/update/guard.ts
19019
- import { readFile as readFile13 } from "fs/promises";
19273
+ import { readFile as readFile14 } from "fs/promises";
19020
19274
  async function tmuxServerAtRisk(opts = {}) {
19021
19275
  const runTmux = opts.runTmux ?? run2;
19022
- const readCgroup = opts.readCgroup ?? ((pid2) => readFile13(`/proc/${pid2}/cgroup`, "utf8"));
19276
+ const readCgroup = opts.readCgroup ?? ((pid2) => readFile14(`/proc/${pid2}/cgroup`, "utf8"));
19023
19277
  const r = await runTmux(["-L", opts.socket ?? "ledge", "list-sessions", "-F", "#{pid}"]);
19024
19278
  if (r.code !== 0) return false;
19025
19279
  const pid = r.stdout.split("\n").map((s) => s.trim()).find(Boolean);
@@ -19042,12 +19296,12 @@ var init_guard = __esm({
19042
19296
 
19043
19297
  // src/update/self.ts
19044
19298
  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";
19299
+ import { readFile as readFile15, realpath as realpath3 } from "fs/promises";
19300
+ import { homedir as homedir16 } from "os";
19301
+ import { dirname as dirname11, join as join32 } from "path";
19048
19302
  function gitRoot(script, exists = existsSync4) {
19049
19303
  for (let dir = dirname11(script); ; dir = dirname11(dir)) {
19050
- if (exists(join31(dir, ".git"))) return dir;
19304
+ if (exists(join32(dir, ".git"))) return dir;
19051
19305
  if (dirname11(dir) === dir) return null;
19052
19306
  }
19053
19307
  }
@@ -19057,10 +19311,10 @@ function installKind(script, exists = existsSync4) {
19057
19311
  }
19058
19312
  async function daemonInfo() {
19059
19313
  const script = await realpath3(process.argv[1] ?? "").catch(() => process.argv[1] ?? "");
19060
- const cgroup = await readFile14("/proc/self/cgroup", "utf8").catch(() => "");
19314
+ const cgroup = await readFile15("/proc/self/cgroup", "utf8").catch(() => "");
19061
19315
  const install = installKind(script);
19062
19316
  const root2 = install === "git" ? gitRoot(script) : null;
19063
- return { version: LEDGE_VERSION, install, systemd: underSystemd(process.env, cgroup), home: homedir15(), ...root2 ? { manual: manualCommand(root2) } : {} };
19317
+ return { version: LEDGE_VERSION, install, systemd: underSystemd(process.env, cgroup), home: homedir16(), ...root2 ? { manual: manualCommand(root2) } : {} };
19064
19318
  }
19065
19319
  var underSystemd, manualCommand;
19066
19320
  var init_self = __esm({
@@ -19074,7 +19328,7 @@ var init_self = __esm({
19074
19328
  });
19075
19329
 
19076
19330
  // src/update/run-update.ts
19077
- import { dirname as dirname12, join as join32 } from "path";
19331
+ import { dirname as dirname12, join as join33 } from "path";
19078
19332
  function createDaemonUpdater(deps) {
19079
19333
  let busy = false;
19080
19334
  let restarting = false;
@@ -19163,7 +19417,7 @@ var init_run_update = __esm({
19163
19417
  PACKAGE = "@paradow/ledge";
19164
19418
  NPM_TIMEOUT_MS = 3 * 6e4;
19165
19419
  RESTART_COMMAND = "systemctl --user restart ledge-daemon";
19166
- npmCommand = (execPath, version) => ({ file: join32(dirname12(execPath), "npm"), args: ["i", "-g", `${PACKAGE}@${version}`] });
19420
+ npmCommand = (execPath, version) => ({ file: join33(dirname12(execPath), "npm"), args: ["i", "-g", `${PACKAGE}@${version}`] });
19167
19421
  lastLines = (output, n = 3) => output.split("\n").map((l) => l.trimEnd()).filter(Boolean).slice(-n).join("\n");
19168
19422
  npmEnv = (execPath, env) => ({ ...env, PATH: `${dirname12(execPath)}:${env.PATH ?? ""}` });
19169
19423
  }
@@ -20677,7 +20931,7 @@ __export(daemon_exports, {
20677
20931
  startDaemon: () => startDaemon
20678
20932
  });
20679
20933
  import { writeFile as writeFile15, rm as rm7 } from "fs/promises";
20680
- import { join as join33 } from "path";
20934
+ import { join as join34 } from "path";
20681
20935
  async function startDaemon(opts = {}) {
20682
20936
  await ensureLedgeDir();
20683
20937
  const store = new Store();
@@ -20694,7 +20948,7 @@ async function startDaemon(opts = {}) {
20694
20948
  }
20695
20949
  for (const s of store.all()) {
20696
20950
  if (backendOf(s) !== "omp-rpc") continue;
20697
- await reapOwner(join33(paths().dir, "omp", s.id)).catch(() => false);
20951
+ await reapOwner(join34(paths().dir, "omp", s.id)).catch(() => false);
20698
20952
  if (s.state === "exited") continue;
20699
20953
  const at = (/* @__PURE__ */ new Date()).toISOString();
20700
20954
  store.apply(s.id, { name: "StopFailure", at, source: "daemon", text: "Ledge restarted: the OMP process ended with it" });
@@ -20781,9 +21035,9 @@ __export(uninstall_exports, {
20781
21035
  runUninstall: () => runUninstall,
20782
21036
  uninstallArgs: () => uninstallArgs
20783
21037
  });
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";
21038
+ import { readFile as readFile16, writeFile as writeFile16, rename as rename6, rm as rm8, lstat as lstat2, realpath as realpath4 } from "fs/promises";
21039
+ import { homedir as homedir17 } from "os";
21040
+ import { join as join35 } from "path";
20787
21041
  function prune(config, file, keep) {
20788
21042
  if (!isObject(config)) throw new Error(`${file} root is not an object, not modified`);
20789
21043
  if (config.hooks === void 0) return { settings: config, removed: 0 };
@@ -20820,7 +21074,7 @@ function removeCursorHooks(config) {
20820
21074
  async function pruneJsonFile(path3, remove, name) {
20821
21075
  let raw;
20822
21076
  try {
20823
- raw = await readFile15(path3, "utf8");
21077
+ raw = await readFile16(path3, "utf8");
20824
21078
  } catch (e) {
20825
21079
  if (e.code === "ENOENT") return null;
20826
21080
  throw e;
@@ -20841,7 +21095,7 @@ async function pruneJsonFile(path3, remove, name) {
20841
21095
  async function removeService(opts = {}) {
20842
21096
  const out = [];
20843
21097
  const run4 = opts.run ?? execRunner;
20844
- const unit = join34(opts.unitDir ?? join34(homedir16(), ".config", "systemd", "user"), SERVICE);
21098
+ const unit = join35(opts.unitDir ?? join35(homedir17(), ".config", "systemd", "user"), SERVICE);
20845
21099
  let present = true;
20846
21100
  try {
20847
21101
  await lstat2(unit);
@@ -20866,7 +21120,7 @@ async function runningSessions(run4, socket) {
20866
21120
  async function removeLink(link3, script) {
20867
21121
  try {
20868
21122
  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;
21123
+ if (st.isFile() ? !(await readFile16(link3, "utf8")).startsWith(BIN_MARK) : !st.isSymbolicLink() || await realpath4(link3) !== await realpath4(script)) return false;
20870
21124
  } catch {
20871
21125
  return false;
20872
21126
  }
@@ -20889,9 +21143,9 @@ async function runUninstall(opts = {}) {
20889
21143
  };
20890
21144
  const run4 = opts.run ?? execRunner;
20891
21145
  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]
21146
+ ["Claude Code", opts.settingsPath ?? join35(homedir17(), ".claude", "settings.json"), (c) => removeClaudeHooks(c, "settings.json")],
21147
+ ["Codex", join35(opts.codexDir ?? process.env.CODEX_HOME ?? join35(homedir17(), ".codex"), "hooks.json"), (c) => removeClaudeHooks(c, "hooks.json")],
21148
+ ["Cursor Agent", join35(opts.cursorDir ?? join35(homedir17(), ".cursor"), "hooks.json"), removeCursorHooks]
20895
21149
  ];
20896
21150
  for (const [agent, path3, remove] of files) {
20897
21151
  await step3(async () => {
@@ -20913,7 +21167,7 @@ async function runUninstall(opts = {}) {
20913
21167
  });
20914
21168
  else lines.push(`${dir} kept (session journal and log): \`ledge uninstall --purge\` removes it`);
20915
21169
  const script = opts.script ?? process.argv[1];
20916
- const link3 = opts.link ?? join34(homedir16(), ".local", "bin", "ledge");
21170
+ const link3 = opts.link ?? join35(homedir17(), ".local", "bin", "ledge");
20917
21171
  if (script) await step3(async () => {
20918
21172
  if (await removeLink(link3, script)) lines.push(`${link3} removed`);
20919
21173
  });
@@ -21171,10 +21425,10 @@ function createDaemonClient(opts) {
21171
21425
  };
21172
21426
  const schedule = () => {
21173
21427
  if (closed) return;
21174
- timer2 = setTimeout(open5, backoff);
21428
+ timer2 = setTimeout(open6, backoff);
21175
21429
  backoff = Math.min(max, backoff * 2);
21176
21430
  };
21177
- function open5() {
21431
+ function open6() {
21178
21432
  if (closed) return;
21179
21433
  setStatus(status === "open" ? "connecting" : status);
21180
21434
  if (!token) {
@@ -21238,7 +21492,7 @@ function createDaemonClient(opts) {
21238
21492
  ws = null;
21239
21493
  old?.close();
21240
21494
  setStatus("connecting");
21241
- open5();
21495
+ open6();
21242
21496
  },
21243
21497
  close: () => {
21244
21498
  closed = true;
@@ -23011,7 +23265,7 @@ __export(launch_exports, {
23011
23265
  parseLaunchArgs: () => parseLaunchArgs
23012
23266
  });
23013
23267
  import { spawn as spawn3 } from "child_process";
23014
- import { readFile as readFile16, rm as rm9 } from "fs/promises";
23268
+ import { readFile as readFile17, rm as rm9 } from "fs/promises";
23015
23269
  import { createConnection } from "net";
23016
23270
  function parseLaunchArgs(argv) {
23017
23271
  const args = [];
@@ -23124,7 +23378,7 @@ async function readExitCode(id) {
23124
23378
  const file = exitFile(id);
23125
23379
  let raw = null;
23126
23380
  try {
23127
- raw = await readFile16(file, "utf8");
23381
+ raw = await readFile17(file, "utf8");
23128
23382
  } catch {
23129
23383
  }
23130
23384
  await rm9(file, { force: true });
@@ -23201,6 +23455,7 @@ var init_launch2 = __esm({
23201
23455
  import { spawn as spawn4 } from "child_process";
23202
23456
  function attachCommand(fleet, s) {
23203
23457
  if (s.offline) return { why: "offline" };
23458
+ if (isStoppedOmpSeat(s)) return { why: "stopped" };
23204
23459
  if (backendOf(s) === "omp-rpc") return { why: "omp" };
23205
23460
  if (!s.tmuxSession) return { why: "none" };
23206
23461
  const argv = fleet.terminal(s.id);
@@ -23223,9 +23478,9 @@ var init_attach = __esm({
23223
23478
 
23224
23479
  // src/tui/clipboard.ts
23225
23480
  import { execFile as execFile9 } from "child_process";
23226
- import { mkdtemp as mkdtemp2, readFile as readFile17, rm as rm10 } from "fs/promises";
23481
+ import { mkdtemp as mkdtemp2, readFile as readFile18, rm as rm10 } from "fs/promises";
23227
23482
  import { tmpdir as tmpdir2 } from "os";
23228
- import { join as join35 } from "path";
23483
+ import { join as join36 } from "path";
23229
23484
  async function fromWindows(run4) {
23230
23485
  const { stdout } = await run4(POWERSHELL, ["-NoProfile", "-STA", "-Command", SCRIPT], { cwd: "/mnt/c", maxBuffer: MAX_BUFFER, encoding: "buffer" });
23231
23486
  const line2 = stdout.toString("utf8").trim();
@@ -23250,15 +23505,15 @@ async function fromLinux(run4) {
23250
23505
  return null;
23251
23506
  }
23252
23507
  async function fromMac(run4) {
23253
- const dir = await mkdtemp2(join35(tmpdir2(), "ledge-clip-"));
23254
- const file = join35(dir, "clip.png");
23508
+ const dir = await mkdtemp2(join36(tmpdir2(), "ledge-clip-"));
23509
+ const file = join36(dir, "clip.png");
23255
23510
  try {
23256
23511
  const script = `set f to open for access POSIX file "${file}" with write permission
23257
23512
  set eof f to 0
23258
23513
  write (the clipboard as \xABclass PNGf\xBB) to f
23259
23514
  close access f`;
23260
23515
  await run4("osascript", ["-e", script], { maxBuffer: MAX_BUFFER, encoding: "buffer" });
23261
- return fromBytes(await readFile17(file));
23516
+ return fromBytes(await readFile18(file));
23262
23517
  } catch {
23263
23518
  return null;
23264
23519
  } finally {
@@ -23689,7 +23944,7 @@ var init_fr = __esm({
23689
23944
  plural = (n, one2, many) => n > 1 ? many : one2;
23690
23945
  fr3 = {
23691
23946
  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" },
23947
+ 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
23948
  time: {
23694
23949
  now: "\xE0 l\u2019instant",
23695
23950
  decimal: ",",
@@ -23712,6 +23967,7 @@ var init_fr = __esm({
23712
23967
  openImage: "ouvrir l'image",
23713
23968
  navigate: "naviguer",
23714
23969
  open: "ouvrir",
23970
+ attachSeat: "attacher son terminal",
23715
23971
  answer: "r\xE9pondre",
23716
23972
  attach: "attacher (tmux)",
23717
23973
  ask: "demander \xE0 Ledge",
@@ -23786,6 +24042,7 @@ var init_fr = __esm({
23786
24042
  noPreview: "Pas d\u2019aper\xE7u.",
23787
24043
  noExchange: "Aucun \xE9change.",
23788
24044
  sessions: (n) => `${n} ${plural(n, "session", "sessions")}`,
24045
+ sessionsOf: (live3, all2) => `${live3} ${plural(live3, "session", "sessions")} sur ${all2}`,
23789
24046
  outside: "Hors projet",
23790
24047
  offline: "hors ligne",
23791
24048
  readOnly: "lecture seule",
@@ -24009,6 +24266,7 @@ var init_fr = __esm({
24009
24266
  noteTooLong: (max) => `Note trop longue : ${max} caract\xE8res au plus. Elle reste dans le champ.`,
24010
24267
  noNote: (n) => `${n} ne prend pas de note ici.`,
24011
24268
  noTerminalOmp: (n) => `${n} est pilot\xE9 par Ledge via le protocole d\u2019OMP : pas de terminal \xE0 attacher.`,
24269
+ seatStopped: (n) => `${n} est arr\xEAt\xE9 : son terminal n\u2019existe plus. 6 ouvre OpenRig, o\xF9 est son rig.`,
24012
24270
  noTerminal: (n) => `Pas de terminal pour ${n}.`,
24013
24271
  attachFailed: (code3) => `tmux s\u2019est arr\xEAt\xE9 avec le code ${code3}.`,
24014
24272
  modelSwitch: "Ce daemon ne change pas le mod\xE8le d\u2019une session en cours.",
@@ -24257,7 +24515,7 @@ var init_en = __esm({
24257
24515
  plural2 = (n, one2, many) => n > 1 ? many : one2;
24258
24516
  en3 = {
24259
24517
  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" },
24518
+ state: { approval: "Needs you", question: "A question", starting: "Starting", idle: "Available", working: "Working", done: "Finished", error: "Error", readonly: "Read-only", stopped: "Stopped" },
24261
24519
  time: { now: "just now", decimal: ".", s: (n) => `${n}s`, min: (n) => `${n} min`, h: (n) => `${n} h`, d: (n) => `${n} d` },
24262
24520
  tabs: { overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", openrig: "OpenRig" },
24263
24521
  bar: {
@@ -24273,6 +24531,7 @@ var init_en = __esm({
24273
24531
  openImage: "open image",
24274
24532
  navigate: "move",
24275
24533
  open: "open",
24534
+ attachSeat: "attach its terminal",
24276
24535
  answer: "answer",
24277
24536
  attach: "attach (tmux)",
24278
24537
  ask: "ask Ledge",
@@ -24346,6 +24605,7 @@ var init_en = __esm({
24346
24605
  noPreview: "No preview.",
24347
24606
  noExchange: "No message yet.",
24348
24607
  sessions: (n) => `${n} ${plural2(n, "session", "sessions")}`,
24608
+ sessionsOf: (live3, all2) => `${live3} ${live3 === 1 ? "session" : "sessions"} of ${all2}`,
24349
24609
  outside: "No project",
24350
24610
  offline: "offline",
24351
24611
  readOnly: "read-only",
@@ -24565,6 +24825,7 @@ var init_en = __esm({
24565
24825
  noteTooLong: (max) => `Note too long: ${max} characters at most. It stays in the field.`,
24566
24826
  noNote: (n) => `${n} takes no note here.`,
24567
24827
  noTerminalOmp: (n) => `${n} is driven by Ledge over OMP's protocol: there is no terminal to attach to.`,
24828
+ seatStopped: (n) => `${n} is stopped: its terminal is gone. 6 opens OpenRig, where its rig is.`,
24568
24829
  noTerminal: (n) => `No terminal for ${n}.`,
24569
24830
  attachFailed: (code3) => `tmux stopped with code ${code3}.`,
24570
24831
  modelSwitch: "This daemon cannot change a running session\u2019s model.",
@@ -24829,15 +25090,19 @@ function overview(sessions, projects) {
24829
25090
  const known2 = new Map((projects?.projects ?? []).map((p) => [p.id, p]));
24830
25091
  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
25092
  const rigNodes = /* @__PURE__ */ new Map();
25093
+ const rigNodeOf = (rig) => {
25094
+ const kind = rig === "kernel" ? "kernel" : "rig";
25095
+ const key = kind === "kernel" ? "g:kernel" : `g:rig:${rig}`;
25096
+ let node2 = rigNodes.get(key);
25097
+ if (!node2) {
25098
+ node2 = { key, label: kind === "kernel" ? "Kernel" : rig, kind, branches: [{ branch: "", sessions: [] }], count: 0 };
25099
+ rigNodes.set(key, node2);
25100
+ }
25101
+ return node2;
25102
+ };
24832
25103
  for (const s of live3) {
24833
25104
  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
- }
25105
+ const node3 = rigNodeOf(s.rig.name);
24841
25106
  node3.branches[0].sessions.push(s);
24842
25107
  node3.count += 1;
24843
25108
  continue;
@@ -24858,6 +25123,12 @@ function overview(sessions, projects) {
24858
25123
  row3.sessions.push(s);
24859
25124
  node2.count += 1;
24860
25125
  }
25126
+ for (const s of sessions) {
25127
+ if (!isStoppedOmpSeat(s) || !s.rig) continue;
25128
+ const node2 = rigNodeOf(s.rig.name);
25129
+ node2.branches[0].sessions.push(s);
25130
+ node2.shown = (node2.shown ?? node2.count) + 1;
25131
+ }
24861
25132
  const groups = [...rigNodes.values()].sort((a, b3) => a.kind === b3.kind ? a.label.localeCompare(b3.label) : a.kind === "kernel" ? 1 : -1);
24862
25133
  const tree2 = [...[...nodes.values()].filter((n) => n.count > 0), ...groups];
24863
25134
  const order2 = tree2.flatMap((n) => n.branches.flatMap((b3) => b3.sessions.map((s) => s.id)));
@@ -25118,7 +25389,7 @@ var init_theme = __esm({
25118
25389
 
25119
25390
  // src/tui/model/cells.ts
25120
25391
  import stringWidth from "string-width";
25121
- function join36(line2, a) {
25392
+ function join37(line2, a) {
25122
25393
  const out = [];
25123
25394
  for (const c of a) {
25124
25395
  const last = out[out.length - 1];
@@ -25133,7 +25404,7 @@ function cut2(line2, width, ellipsis = "\u2026") {
25133
25404
  const a = split2(line2), room = width - cells(ellipsis);
25134
25405
  let n = 0, i = 0;
25135
25406
  while (i < a.length && n + a[i].w <= room) n += a[i++].w;
25136
- const kept = join36(line2, a.slice(0, i));
25407
+ const kept = join37(line2, a.slice(0, i));
25137
25408
  const color2 = line2[a[Math.max(0, i - 1)]?.seg ?? 0]?.color;
25138
25409
  return [...kept, { text: ellipsis, ...color2 ? { color: color2 } : {} }];
25139
25410
  }
@@ -25152,7 +25423,7 @@ function wrapSegs(line2, width, first = width) {
25152
25423
  }
25153
25424
  w = Math.max(4, width);
25154
25425
  if (j >= a.length || a[j].g === "\n") {
25155
- out.push(join36(line2, a.slice(i, j)));
25426
+ out.push(join37(line2, a.slice(i, j)));
25156
25427
  i = j + 1;
25157
25428
  if (j >= a.length) break;
25158
25429
  if (i >= a.length) out.push([]);
@@ -25161,7 +25432,7 @@ function wrapSegs(line2, width, first = width) {
25161
25432
  const end2 = a[j].g === " " ? j : space > i ? space : Math.max(j, i + 1);
25162
25433
  let k = end2;
25163
25434
  while (k > i && a[k - 1].g === " ") k--;
25164
- out.push(join36(line2, a.slice(i, k)));
25435
+ out.push(join37(line2, a.slice(i, k)));
25165
25436
  i = end2;
25166
25437
  while (i < a.length && a[i].g === " ") i++;
25167
25438
  if (a[i]?.g === "\n") {
@@ -30374,16 +30645,16 @@ function resolveAllAttention(events, context) {
30374
30645
  let nextEvents;
30375
30646
  while (++index2 < events.length) {
30376
30647
  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)) {
30648
+ let open6 = index2;
30649
+ while (open6--) {
30650
+ if (events[open6][0] === "exit" && events[open6][1].type === "attentionSequence" && events[open6][1]._open && // If the markers are the same:
30651
+ context.sliceSerialize(events[open6][1]).charCodeAt(0) === context.sliceSerialize(events[index2][1]).charCodeAt(0)) {
30652
+ 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
30653
  continue;
30383
30654
  }
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;
30655
+ 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
30656
  const start = {
30386
- ...events[open5][1].end
30657
+ ...events[open6][1].end
30387
30658
  };
30388
30659
  const end2 = {
30389
30660
  ...events[index2][1].start
@@ -30394,7 +30665,7 @@ function resolveAllAttention(events, context) {
30394
30665
  type: use > 1 ? "strongSequence" : "emphasisSequence",
30395
30666
  start,
30396
30667
  end: {
30397
- ...events[open5][1].end
30668
+ ...events[open6][1].end
30398
30669
  }
30399
30670
  };
30400
30671
  const closingSequence = {
@@ -30407,7 +30678,7 @@ function resolveAllAttention(events, context) {
30407
30678
  const text7 = {
30408
30679
  type: use > 1 ? "strongText" : "emphasisText",
30409
30680
  start: {
30410
- ...events[open5][1].end
30681
+ ...events[open6][1].end
30411
30682
  },
30412
30683
  end: {
30413
30684
  ...events[index2][1].start
@@ -30422,26 +30693,26 @@ function resolveAllAttention(events, context) {
30422
30693
  ...closingSequence.end
30423
30694
  }
30424
30695
  };
30425
- events[open5][1].end = {
30696
+ events[open6][1].end = {
30426
30697
  ...openingSequence.start
30427
30698
  };
30428
30699
  events[index2][1].start = {
30429
30700
  ...closingSequence.end
30430
30701
  };
30431
30702
  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]]);
30703
+ if (events[open6][1].end.offset - events[open6][1].start.offset) {
30704
+ nextEvents = push(nextEvents, [["enter", events[open6][1], context], ["exit", events[open6][1], context]]);
30434
30705
  }
30435
30706
  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));
30707
+ nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open6 + 1, index2), context));
30437
30708
  nextEvents = push(nextEvents, [["exit", text7, context], ["enter", closingSequence, context], ["exit", closingSequence, context], ["exit", group, context]]);
30438
30709
  let offset = 0;
30439
30710
  if (events[index2][1].end.offset - events[index2][1].start.offset) {
30440
30711
  offset = 2;
30441
30712
  nextEvents = push(nextEvents, [["enter", events[index2][1], context], ["exit", events[index2][1], context]]);
30442
30713
  }
30443
- splice(events, open5 - 1, index2 - open5 + 3, nextEvents);
30444
- index2 = open5 + nextEvents.length - offset - 2;
30714
+ splice(events, open6 - 1, index2 - open6 + 3, nextEvents);
30715
+ index2 = open6 + nextEvents.length - offset - 2;
30445
30716
  break;
30446
30717
  }
30447
30718
  }
@@ -30473,10 +30744,10 @@ function tokenizeAttention(effects, ok4) {
30473
30744
  }
30474
30745
  const token = effects.exit("attentionSequence");
30475
30746
  const after2 = classifyCharacter(code3);
30476
- const open5 = !after2 || after2 === 2 && before2 || attentionMarkers2.includes(code3) && code3 !== 42 && code3 !== 95;
30747
+ const open6 = !after2 || after2 === 2 && before2 || attentionMarkers2.includes(code3) && code3 !== 42 && code3 !== 95;
30477
30748
  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));
30749
+ token._open = Boolean(marker === 42 ? open6 : open6 && (before2 || !close));
30750
+ token._close = Boolean(marker === 42 ? close : close && (after2 || !open6));
30480
30751
  return ok4(code3);
30481
30752
  }
30482
30753
  }
@@ -30510,9 +30781,9 @@ function tokenizeAutolink(effects, ok4, nok) {
30510
30781
  effects.consume(code3);
30511
30782
  effects.exit("autolinkMarker");
30512
30783
  effects.enter("autolinkProtocol");
30513
- return open5;
30784
+ return open6;
30514
30785
  }
30515
- function open5(code3) {
30786
+ function open6(code3) {
30516
30787
  if (asciiAlpha(code3)) {
30517
30788
  effects.consume(code3);
30518
30789
  return schemeOrEmailAtext;
@@ -30793,9 +31064,9 @@ function tokenizeCharacterReference(effects, ok4, nok) {
30793
31064
  effects.enter("characterReferenceMarker");
30794
31065
  effects.consume(code3);
30795
31066
  effects.exit("characterReferenceMarker");
30796
- return open5;
31067
+ return open6;
30797
31068
  }
30798
- function open5(code3) {
31069
+ function open6(code3) {
30799
31070
  if (code3 === 35) {
30800
31071
  effects.enter("characterReferenceMarkerNumeric");
30801
31072
  effects.consume(code3);
@@ -32196,9 +32467,9 @@ function tokenizeHtmlFlow(effects, ok4, nok) {
32196
32467
  effects.enter("htmlFlow");
32197
32468
  effects.enter("htmlFlowData");
32198
32469
  effects.consume(code3);
32199
- return open5;
32470
+ return open6;
32200
32471
  }
32201
- function open5(code3) {
32472
+ function open6(code3) {
32202
32473
  if (code3 === 33) {
32203
32474
  effects.consume(code3);
32204
32475
  return declarationOpen;
@@ -32546,9 +32817,9 @@ function tokenizeHtmlText(effects, ok4, nok) {
32546
32817
  effects.enter("htmlText");
32547
32818
  effects.enter("htmlTextData");
32548
32819
  effects.consume(code3);
32549
- return open5;
32820
+ return open6;
32550
32821
  }
32551
- function open5(code3) {
32822
+ function open6(code3) {
32552
32823
  if (code3 === 33) {
32553
32824
  effects.consume(code3);
32554
32825
  return declarationOpen;
@@ -32870,12 +33141,12 @@ function resolveAllLabelEnd(events) {
32870
33141
  function resolveToLabelEnd(events, context) {
32871
33142
  let index2 = events.length;
32872
33143
  let offset = 0;
32873
- let open5;
33144
+ let open6;
32874
33145
  let close;
32875
33146
  let media;
32876
33147
  while (index2--) {
32877
33148
  const token = events[index2][1];
32878
- if (open5) {
33149
+ if (open6) {
32879
33150
  if (token.type === "link" || token.type === "labelLink" && token._inactive) {
32880
33151
  break;
32881
33152
  }
@@ -32884,7 +33155,7 @@ function resolveToLabelEnd(events, context) {
32884
33155
  }
32885
33156
  } else if (close) {
32886
33157
  if (events[index2][0] === "enter" && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {
32887
- open5 = index2;
33158
+ open6 = index2;
32888
33159
  if (token.type !== "labelLink") {
32889
33160
  offset = 2;
32890
33161
  break;
@@ -32895,9 +33166,9 @@ function resolveToLabelEnd(events, context) {
32895
33166
  }
32896
33167
  }
32897
33168
  const group = {
32898
- type: events[open5][1].type === "labelLink" ? "link" : "image",
33169
+ type: events[open6][1].type === "labelLink" ? "link" : "image",
32899
33170
  start: {
32900
- ...events[open5][1].start
33171
+ ...events[open6][1].start
32901
33172
  },
32902
33173
  end: {
32903
33174
  ...events[events.length - 1][1].end
@@ -32906,7 +33177,7 @@ function resolveToLabelEnd(events, context) {
32906
33177
  const label2 = {
32907
33178
  type: "label",
32908
33179
  start: {
32909
- ...events[open5][1].start
33180
+ ...events[open6][1].start
32910
33181
  },
32911
33182
  end: {
32912
33183
  ...events[close][1].end
@@ -32915,20 +33186,20 @@ function resolveToLabelEnd(events, context) {
32915
33186
  const text7 = {
32916
33187
  type: "labelText",
32917
33188
  start: {
32918
- ...events[open5 + offset + 2][1].end
33189
+ ...events[open6 + offset + 2][1].end
32919
33190
  },
32920
33191
  end: {
32921
33192
  ...events[close - 2][1].start
32922
33193
  }
32923
33194
  };
32924
33195
  media = [["enter", group, context], ["enter", label2, context]];
32925
- media = push(media, events.slice(open5 + 1, open5 + offset + 3));
33196
+ media = push(media, events.slice(open6 + 1, open6 + offset + 3));
32926
33197
  media = push(media, [["enter", text7, context]]);
32927
- media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open5 + offset + 4, close - 3), context));
33198
+ media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open6 + offset + 4, close - 3), context));
32928
33199
  media = push(media, [["exit", text7, context], events[close - 2], events[close - 1], ["exit", label2, context]]);
32929
33200
  media = push(media, events.slice(close + 1));
32930
33201
  media = push(media, [["exit", group, context]]);
32931
- splice(events, open5, events.length, media);
33202
+ splice(events, open6, events.length, media);
32932
33203
  return events;
32933
33204
  }
32934
33205
  function tokenizeLabelEnd(effects, ok4, nok) {
@@ -33099,9 +33370,9 @@ function tokenizeLabelStartImage(effects, ok4, nok) {
33099
33370
  effects.enter("labelImageMarker");
33100
33371
  effects.consume(code3);
33101
33372
  effects.exit("labelImageMarker");
33102
- return open5;
33373
+ return open6;
33103
33374
  }
33104
- function open5(code3) {
33375
+ function open6(code3) {
33105
33376
  if (code3 === 91) {
33106
33377
  effects.enter("labelMarker");
33107
33378
  effects.consume(code3);
@@ -33933,30 +34204,30 @@ function gfmStrikethrough(options) {
33933
34204
  let index2 = -1;
33934
34205
  while (++index2 < events.length) {
33935
34206
  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) {
34207
+ let open6 = index2;
34208
+ while (open6--) {
34209
+ if (events[open6][0] === "exit" && events[open6][1].type === "strikethroughSequenceTemporary" && events[open6][1]._open && // If the sizes are the same:
34210
+ events[index2][1].end.offset - events[index2][1].start.offset === events[open6][1].end.offset - events[open6][1].start.offset) {
33940
34211
  events[index2][1].type = "strikethroughSequence";
33941
- events[open5][1].type = "strikethroughSequence";
34212
+ events[open6][1].type = "strikethroughSequence";
33942
34213
  const strikethrough = {
33943
34214
  type: "strikethrough",
33944
- start: Object.assign({}, events[open5][1].start),
34215
+ start: Object.assign({}, events[open6][1].start),
33945
34216
  end: Object.assign({}, events[index2][1].end)
33946
34217
  };
33947
34218
  const text7 = {
33948
34219
  type: "strikethroughText",
33949
- start: Object.assign({}, events[open5][1].end),
34220
+ start: Object.assign({}, events[open6][1].end),
33950
34221
  end: Object.assign({}, events[index2][1].start)
33951
34222
  };
33952
- const nextEvents = [["enter", strikethrough, context], ["enter", events[open5][1], context], ["exit", events[open5][1], context], ["enter", text7, context]];
34223
+ const nextEvents = [["enter", strikethrough, context], ["enter", events[open6][1], context], ["exit", events[open6][1], context], ["enter", text7, context]];
33953
34224
  const insideSpan2 = context.parser.constructs.insideSpan.null;
33954
34225
  if (insideSpan2) {
33955
- splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan2, events.slice(open5 + 1, index2), context));
34226
+ splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan2, events.slice(open6 + 1, index2), context));
33956
34227
  }
33957
34228
  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;
34229
+ splice(events, open6 - 1, index2 - open6 + 3, nextEvents);
34230
+ index2 = open6 + nextEvents.length - 2;
33960
34231
  break;
33961
34232
  }
33962
34233
  }
@@ -34558,8 +34829,8 @@ function gfmTaskListItem() {
34558
34829
  }
34559
34830
  function tokenizeTasklistCheck(effects, ok4, nok) {
34560
34831
  const self = this;
34561
- return open5;
34562
- function open5(code3) {
34832
+ return open6;
34833
+ function open6(code3) {
34563
34834
  if (
34564
34835
  // Exit if there’s stuff before.
34565
34836
  self.previous !== null || // Exit if not in the first content that is the first child of a list
@@ -36048,8 +36319,8 @@ function compiler(options) {
36048
36319
  events[start][1]._spread = listSpread;
36049
36320
  }
36050
36321
  function opener(create, and) {
36051
- return open5;
36052
- function open5(token) {
36322
+ return open6;
36323
+ function open6(token) {
36053
36324
  enter.call(this, create(token), token);
36054
36325
  if (and) and.call(this, token);
36055
36326
  }
@@ -36081,19 +36352,19 @@ function compiler(options) {
36081
36352
  }
36082
36353
  function exit3(token, onExitError) {
36083
36354
  const node2 = this.stack.pop();
36084
- const open5 = this.tokenStack.pop();
36085
- if (!open5) {
36355
+ const open6 = this.tokenStack.pop();
36356
+ if (!open6) {
36086
36357
  throw new Error("Cannot close `" + token.type + "` (" + stringifyPosition({
36087
36358
  start: token.start,
36088
36359
  end: token.end
36089
36360
  }) + "): it\u2019s not open");
36090
36361
  }
36091
- if (open5[0].type !== token.type) {
36362
+ if (open6[0].type !== token.type) {
36092
36363
  if (onExitError) {
36093
- onExitError.call(this, token, open5[0]);
36364
+ onExitError.call(this, token, open6[0]);
36094
36365
  } else {
36095
- const handler = open5[1] || defaultOnError;
36096
- handler.call(this, token, open5[0]);
36366
+ const handler = open6[1] || defaultOnError;
36367
+ handler.call(this, token, open6[0]);
36097
36368
  }
36098
36369
  }
36099
36370
  node2.position.end = point2(token.end);
@@ -39513,7 +39784,8 @@ function Overview() {
39513
39784
  return true;
39514
39785
  }
39515
39786
  if (key.return) {
39516
- if (selected) go("session");
39787
+ if (selected && isOmpSeat(selected)) attach2(selected);
39788
+ else if (selected) go("session");
39517
39789
  return true;
39518
39790
  }
39519
39791
  if (!selected) return false;
@@ -39534,7 +39806,7 @@ function Overview() {
39534
39806
  return false;
39535
39807
  });
39536
39808
  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) }] : [],
39809
+ ...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
39810
  ...n.branches.flatMap((b3) => b3.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: kind === "rig" ? 2 : 0, group: kind })))
39539
39811
  ]);
39540
39812
  const rigRows = groupRows("rig"), kernelRows = groupRows("kernel");
@@ -39556,6 +39828,7 @@ function Overview() {
39556
39828
  const stateW = Math.max(...Object.values(t.state).map((x) => x.length)) + 1;
39557
39829
  const topicW = Math.max(6, inner - 6 - nameW - stateW - 3);
39558
39830
  const askW = Math.max(6, inner - 2 - nameW - 9 - 7);
39831
+ const roleW = Math.max(11, ...rigRows.map((r) => r.kind === "session" ? [...r.s.rig?.role ?? ""].length + 2 : 0));
39559
39832
  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
39833
  const st = botState(s);
39561
39834
  return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: s.id === sel ? C.sel : void 0, children: [
@@ -39621,16 +39894,18 @@ function Overview() {
39621
39894
  ] }) : null,
39622
39895
  /* @__PURE__ */ jsxs6(Text7, { color: C.faint, children: [
39623
39896
  " ",
39624
- t.overview.sessions(r.count)
39897
+ r.shown !== void 0 ? t.overview.sessionsOf(r.count, r.shown) : t.overview.sessions(r.count)
39625
39898
  ] })
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) });
39899
+ ] }, 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
39900
  const rigsBox = groupBox(t.overview.rigsGroup, rigRows, rigsHeight);
39628
39901
  const kernelBox = groupBox(t.overview.kernelGroup, kernelRows, kernelHeight);
39902
+ const stopped2 = !!selected && isStoppedOmpSeat(selected);
39629
39903
  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]] : [
39904
+ // On an OMP seat ↵ attaches its terminal; on a stopped one, neither ↵ nor a has anything to attach.
39630
39905
  ["j k", t.keys.navigate],
39631
- ["\u21B5", t.keys.open],
39906
+ ...stopped2 ? [] : [["\u21B5", selected && isOmpSeat(selected) ? t.keys.attachSeat : t.keys.open]],
39632
39907
  ...view.yours.length ? [["tab", t.keys.next], ["y n", t.keys.answer]] : [],
39633
- ["a", t.keys.attach],
39908
+ ...stopped2 ? [] : [["a", t.keys.attach]],
39634
39909
  ...writable2 ? [["m", t.keys.writeProject]] : [],
39635
39910
  ...decisions.length ? [["D", t.keys.decide]] : [],
39636
39911
  ...near.length ? [["H", t.keys.handover]] : [],
@@ -39705,21 +39980,23 @@ function Overview() {
39705
39980
  ) : null
39706
39981
  ] });
39707
39982
  }
39708
- function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }) {
39983
+ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group, roleW = 11 }) {
39709
39984
  const { t, g } = useTui();
39710
39985
  const st = botState(s);
39711
39986
  const n = helperCount(s);
39987
+ const stopped2 = isStoppedOmpSeat(s);
39988
+ const roleTool = group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : `${(s.rig?.role ?? "").padEnd(roleW)}${s.tool}`;
39712
39989
  return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: on ? C.sel : void 0, children: [
39713
39990
  /* @__PURE__ */ jsx10(Text7, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
39714
39991
  /* @__PURE__ */ jsx10(Text7, { children: " ".repeat(2 + indent2) }),
39715
- /* @__PURE__ */ jsxs6(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: [
39992
+ /* @__PURE__ */ jsxs6(Text7, { color: s.offline || stopped2 ? C.faint : STATE_COLOR[st], children: [
39716
39993
  g[st],
39717
39994
  " "
39718
39995
  ] }),
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: [
39996
+ /* @__PURE__ */ jsx10(Text7, { color: stopped2 ? C.faint : s.offline ? C.dim : C.fg, bold: !stopped2, children: fit3(s.name, nameW) }),
39997
+ group ? /* @__PURE__ */ jsxs6(Text7, { color: stopped2 ? C.faint : C.dim, children: [
39721
39998
  " ",
39722
- fit3(group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : s.rig?.role ?? "", topicW - 1),
39999
+ fit3(roleTool, topicW - 1),
39723
40000
  " "
39724
40001
  ] }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
39725
40002
  s.rig ? /* @__PURE__ */ jsxs6(Text7, { color: C.blue, children: [
@@ -39731,7 +40008,7 @@ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }
39731
40008
  " "
39732
40009
  ] })
39733
40010
  ] }),
39734
- /* @__PURE__ */ jsx10(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: fit3(s.offline ? t.overview.offline : t.state[st], stateW).trimEnd() }),
40011
+ /* @__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
40012
  n > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: C.violet, children: [
39736
40013
  " +",
39737
40014
  n
@@ -39770,7 +40047,7 @@ var init_Overview = __esm({
39770
40047
  // src/tui/images.tsx
39771
40048
  import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
39772
40049
  import { tmpdir as tmpdir3 } from "os";
39773
- import { join as join37 } from "path";
40050
+ import { join as join38 } from "path";
39774
40051
  import { useRef as useRef8, useState as useState4 } from "react";
39775
40052
  import { Text as Text8 } from "ink";
39776
40053
  import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -39873,9 +40150,9 @@ function useOpenImage() {
39873
40150
  const { ref, route } = item;
39874
40151
  void (async () => {
39875
40152
  const { mime, data } = await getImage(client, ref.id, route);
39876
- const dir = join37(tmpdir3(), "ledge-images");
40153
+ const dir = join38(tmpdir3(), "ledge-images");
39877
40154
  await mkdir13(dir, { recursive: true, mode: 448 });
39878
- const file = join37(dir, `${ref.id}.${EXT2[mime]}`);
40155
+ const file = join38(dir, `${ref.id}.${EXT2[mime]}`);
39879
40156
  await writeFile17(file, Buffer.from(data, "base64"), { mode: 384 });
39880
40157
  await openFile2(file);
39881
40158
  })().catch((e) => toast({ text: t.images.openFailed(e instanceof AttachmentFailure ? e.reason : e.message), tone: "error" }));
@@ -40660,11 +40937,11 @@ function cardPlan(actions, room) {
40660
40937
  const raw = rows.map((r) => r.state ?? "proposed");
40661
40938
  const stopAt = raw.findIndex((s) => s === "failed" || s === "refused" || s === "stale");
40662
40939
  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]);
40940
+ const open6 = shown.flatMap((s, i) => ["done", "running", "denied", "stale", "cut"].includes(s) ? [] : [i]);
40664
40941
  const skip = stopAt >= 0 && raw[stopAt] !== "failed";
40665
- const rowsAll = skip ? open5.filter((i) => i !== stopAt) : stopAt >= 0 && states[stopAt] === "cut" ? [] : open5;
40942
+ const rowsAll = skip ? open6.filter((i) => i !== stopAt) : stopAt >= 0 && states[stopAt] === "cut" ? [] : open6;
40666
40943
  const all2 = { kind: stopAt < 0 ? "all" : skip ? "skip" : "resume", rows: rowsAll, k: stopAt + 1 };
40667
- return { rows, shown, open: open5, stopAt, all: all2, cutLines };
40944
+ return { rows, shown, open: open6, stopAt, all: all2, cutLines };
40668
40945
  }
40669
40946
  function firstRow(p) {
40670
40947
  const act = p.shown.findIndex((s) => s === "proposed" || s === "held" || s === "failed");
@@ -42054,8 +42331,8 @@ function listItems2(roles, query, builtinOpen) {
42054
42331
  const by = (a, b3) => a.name.localeCompare(b3.name) || a.id.localeCompare(b3.id);
42055
42332
  const kept = roles.filter((r) => matches(r, query)).sort(by);
42056
42333
  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 })) : []] };
42334
+ const open6 = builtinOpen || query.trim() !== "";
42335
+ return { mine, builtin, items: [...mine.map((role) => ({ kind: "role", role })), ...builtin.length ? [{ kind: "builtin" }] : [], ...open6 ? builtin.map((role) => ({ kind: "role", role })) : []] };
42059
42336
  }
42060
42337
  function listLines(roles, o, t, g) {
42061
42338
  const r = t.roles, w = o.width;
@@ -42087,11 +42364,11 @@ function listLines(roles, o, t, g) {
42087
42364
  if (mine.length) lines.push([]);
42088
42365
  const s = i === o.sel;
42089
42366
  if (s) selLine = lines.length;
42090
- const open5 = o.builtinOpen || o.query.trim() !== "";
42367
+ const open6 = o.builtinOpen || o.query.trim() !== "";
42091
42368
  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 } : {} }]);
42369
+ 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
42370
  i++;
42094
- if (open5) builtin.forEach(roleLine);
42371
+ if (open6) builtin.forEach(roleLine);
42095
42372
  }
42096
42373
  return { lines, selLine };
42097
42374
  }
@@ -42187,7 +42464,7 @@ function param(label2, value, o, t, g, w, narrow, extra = {}) {
42187
42464
  const usedL = cells(lab.map((x) => x.text).join("")), usedV = cells(val.map((x) => x.text).join(""));
42188
42465
  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
42466
  }
42190
- function paramRows(d, seat2, files, open5, o, t) {
42467
+ function paramRows(d, seat2, files, open6, o, t) {
42191
42468
  const r = t.roles, w = o.width, g = o.g, n = o.narrow;
42192
42469
  const P = (label2, value, origin, extra = {}) => param(label2, value, origin, t, g, w, n, extra);
42193
42470
  const out = [];
@@ -42228,8 +42505,8 @@ function paramRows(d, seat2, files, open5, o, t) {
42228
42505
  const u = prof.uses;
42229
42506
  const loadout = seat2?.live?.skills;
42230
42507
  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" });
42508
+ out.push(P(r.p.skills, v(`${r.skillsProjected(loadout.length)} ${open6.has("loadout") ? g.unfolded : g.folded}`), { kind: "inherited", from: "launch" }, { fold: "loadout" }));
42509
+ 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
42510
  }
42234
42511
  const list3 = (label2, l, what, fold) => {
42235
42512
  if (!l.declared) {
@@ -42243,9 +42520,9 @@ function paramRows(d, seat2, files, open5, o, t) {
42243
42520
  l.items.forEach((x, i) => {
42244
42521
  const na = !!(seat2 && x.runtime && seat2.runtime.value && x.runtime !== seat2.runtime.value);
42245
42522
  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 } : {});
42523
+ 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
42524
  out.push(na ? { ...row3, line: row3.line.map((s) => ({ ...s, color: C.faint })) } : row3);
42248
- if (key && open5.has(key)) {
42525
+ if (key && open6.has(key)) {
42249
42526
  const body = x.plugin ? x.plugin.skills.join(", ") : (x.absolutePath && files[x.absolutePath]?.content) ?? t.roles.reading;
42250
42527
  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
42528
  }
@@ -42333,9 +42610,9 @@ function yamlRows(d, seat2, files, o, t) {
42333
42610
  }
42334
42611
  return out;
42335
42612
  }
42336
- function filesFor(d, seat2, tab, at, open5) {
42613
+ function filesFor(d, seat2, tab, at, open6) {
42337
42614
  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);
42615
+ if (tab === 1) return (profileOf(d, seat2)?.uses.runtimeResources.items ?? []).filter((x) => x.absolutePath && open6.has(`rr:${x.id}`)).map((x) => x.absolutePath);
42339
42616
  if (tab === 2) {
42340
42617
  const p = pieces(d, seat2)[at];
42341
42618
  return p?.kind === "entry" && p.entry.absolutePath && !isMissing(p.entry) ? [p.entry.absolutePath] : [];
@@ -42412,7 +42689,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42412
42689
  const [scroll, setScroll] = useState14(0);
42413
42690
  const [at, setAt] = useState14(0);
42414
42691
  const [reading, setReading] = useState14(false);
42415
- const [open5, setOpen] = useState14(/* @__PURE__ */ new Set());
42692
+ const [open6, setOpen] = useState14(/* @__PURE__ */ new Set());
42416
42693
  const [files, setFiles] = useState14({});
42417
42694
  const { items } = listItems2(roles.roles, query, builtinOpen);
42418
42695
  const firstRole = items.find((i) => i.kind === "role");
@@ -42438,7 +42715,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42438
42715
  asks.current.reset();
42439
42716
  setFiles({});
42440
42717
  }, [currentId, machine?.id]);
42441
- const wanted = (d ? filesFor(d, seat2, shownTab, at, open5) : []).join("\n");
42718
+ const wanted = (d ? filesFor(d, seat2, shownTab, at, open6) : []).join("\n");
42442
42719
  useEffect13(() => {
42443
42720
  if (!wanted || !machine || !currentId) return;
42444
42721
  const id = currentId, mid = machine.id;
@@ -42451,7 +42728,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42451
42728
  const bodyH = Math.max(3, rows - 4);
42452
42729
  const head2 = d && summary ? headRows(d, summary, { seat: seat2, tab: shownTab, width: w, narrow: !wide, seats }, t) : [];
42453
42730
  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);
42731
+ 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
42732
  const room = Math.max(1, bodyH - head2.length);
42456
42733
  const maxScroll = Math.max(0, body.length - room);
42457
42734
  const params = shownTab === 1 ? body.map((x, i) => x.rail ? i : -1).filter((i) => i >= 0) : [];
@@ -42709,7 +42986,7 @@ function RigsView({ initialMachine, onRoles }) {
42709
42986
  const rigs = listedRigs(state2);
42710
42987
  const [at, setAt] = useState15(0);
42711
42988
  const selectedId = useRef13(null);
42712
- const [open5, setOpen] = useState15(/* @__PURE__ */ new Set());
42989
+ const [open6, setOpen] = useState15(/* @__PURE__ */ new Set());
42713
42990
  const [seatAt, setSeatAt] = useState15(null);
42714
42991
  const [confirm, setConfirm] = useState15(null);
42715
42992
  const [seats, setSeats] = useState15(null);
@@ -42719,7 +42996,7 @@ function RigsView({ initialMachine, onRoles }) {
42719
42996
  }, [ids]);
42720
42997
  const current2 = rigs[Math.min(at, rigs.length - 1)];
42721
42998
  selectedId.current = current2?.id ?? null;
42722
- const seatNodes = current2 && open5.has(current2.id) ? current2.nodes : [];
42999
+ const seatNodes = current2 && open6.has(current2.id) ? current2.nodes : [];
42723
43000
  const seatSel = seatAt !== null && seatAt < seatNodes.length ? seatAt : null;
42724
43001
  const down = () => {
42725
43002
  if (seatSel !== null ? seatSel + 1 < seatNodes.length : seatNodes.length > 0) {
@@ -42737,7 +43014,7 @@ function RigsView({ initialMachine, onRoles }) {
42737
43014
  }
42738
43015
  const n = step2(at, -1, rigs.length), prev = rigs[n];
42739
43016
  setAt(n);
42740
- setSeatAt(prev && open5.has(prev.id) && prev.nodes.length ? prev.nodes.length - 1 : null);
43017
+ setSeatAt(prev && open6.has(prev.id) && prev.nodes.length ? prev.nodes.length - 1 : null);
42741
43018
  };
42742
43019
  const run4 = (a) => {
42743
43020
  void rig.act(a).then((d) => {
@@ -42897,12 +43174,12 @@ function RigsView({ initialMachine, onRoles }) {
42897
43174
  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
43175
  const crumb = [state2?.version ? o.version(state2.version) : o.title, machine?.label, t.roles.rigs].filter(Boolean).join(" \xB7 ");
42899
43176
  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 }),
43177
+ !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
43178
  confirm ? /* @__PURE__ */ jsx22(ConfirmBox, { confirm }) : null,
42902
43179
  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
43180
  ] });
42904
43181
  }
42905
- function Body({ state: state2, rigs, at, seatAt, open: open5, machineLabel: machineLabel2, roleOf: roleOf4 }) {
43182
+ function Body({ state: state2, rigs, at, seatAt, open: open6, machineLabel: machineLabel2, roleOf: roleOf4 }) {
42906
43183
  const { t } = useTui();
42907
43184
  const o = t.openrig;
42908
43185
  const up = daemonUp(state2);
@@ -42940,10 +43217,10 @@ function Body({ state: state2, rigs, at, seatAt, open: open5, machineLabel: mach
42940
43217
  ] }, n.session))
42941
43218
  ] })
42942
43219
  ] }),
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)) })
43220
+ /* @__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
43221
  ] });
42945
43222
  }
42946
- function RigRow({ rig, selected, seatAt, open: open5, roleOf: roleOf4 }) {
43223
+ function RigRow({ rig, selected, seatAt, open: open6, roleOf: roleOf4 }) {
42947
43224
  const { t } = useTui();
42948
43225
  const o = t.openrig;
42949
43226
  const look = rigLook(rig), l = LOOK[look];
@@ -42953,14 +43230,14 @@ function RigRow({ rig, selected, seatAt, open: open5, roleOf: roleOf4 }) {
42953
43230
  return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
42954
43231
  /* @__PURE__ */ jsx22(Box17, { backgroundColor: selected ? C.sel : void 0, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
42955
43232
  /* @__PURE__ */ jsx22(Text18, { color: selected ? C.coral : C.faint, children: selected ? "\u258C" : " " }),
42956
- /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: open5 ? "\u25BE " : "\u25B8 " }),
43233
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: open6 ? "\u25BE " : "\u25B8 " }),
42957
43234
  /* @__PURE__ */ jsx22(Text18, { color: C.fg, bold: true, children: rig.name.padEnd(16) }),
42958
43235
  /* @__PURE__ */ jsx22(Text18, { color: l.color(), children: ` ${l.glyph} ${o.look[look]}`.padEnd(18) }),
42959
43236
  /* @__PURE__ */ jsx22(Text18, { color: C.fg, children: o.seats(rig.running, rig.seats).padEnd(7) }),
42960
43237
  note ? /* @__PURE__ */ jsx22(Text18, { color: note.color, children: note.text.padEnd(34) }) : /* @__PURE__ */ jsx22(Text18, { children: "".padEnd(34) }),
42961
43238
  /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: when })
42962
43239
  ] }) }),
42963
- open5 ? rig.nodes.map((n, i) => {
43240
+ open6 ? rig.nodes.map((n, i) => {
42964
43241
  const s = i === seatAt, bg = s ? C.sel : void 0, role = roleOf4(rig.name, n.logicalId);
42965
43242
  return /* @__PURE__ */ jsx22(Box17, { backgroundColor: bg, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
42966
43243
  /* @__PURE__ */ jsx22(Text18, { color: C.coral, children: s ? "\u258C" : " " }),
@@ -43472,7 +43749,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
43472
43749
  const attach2 = useCallback3((s) => {
43473
43750
  const cmd = attachCommand(fleet, s);
43474
43751
  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" });
43752
+ 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
43753
  return;
43477
43754
  }
43478
43755
  void suspendTerminal(async () => {
@@ -43772,7 +44049,7 @@ var init_glyphs = __esm({
43772
44049
 
43773
44050
  // src/tui/settings.ts
43774
44051
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
43775
- import { join as join38 } from "path";
44052
+ import { join as join39 } from "path";
43776
44053
  import { z as z32 } from "zod";
43777
44054
  function defaultLang(env) {
43778
44055
  const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
@@ -43798,14 +44075,14 @@ var init_settings = __esm({
43798
44075
  "use strict";
43799
44076
  init_paths();
43800
44077
  SettingsSchema = z32.object({ lang: z32.enum(["fr", "en"]).optional(), theme: z32.enum(["auto", "dark", "light"]).optional() }).passthrough();
43801
- settingsPath = () => join38(ledgeDir(), "tui.json");
44078
+ settingsPath = () => join39(ledgeDir(), "tui.json");
43802
44079
  }
43803
44080
  });
43804
44081
 
43805
44082
  // src/tui/terminalProfile.ts
43806
44083
  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";
44084
+ import { mkdir as mkdir14, readFile as readFile19, realpath as realpath5, writeFile as writeFile18 } from "fs/promises";
44085
+ import { dirname as dirname13, join as join40 } from "path";
43809
44086
  import { fileURLToPath as fileURLToPath4 } from "url";
43810
44087
  import { promisify } from "util";
43811
44088
  function movesToLedgeTab(env, here, script) {
@@ -43833,13 +44110,13 @@ function fragment(commandline, iconWin) {
43833
44110
  }
43834
44111
  async function profileLoaded(localAppData) {
43835
44112
  for (const rel of SETTINGS) {
43836
- const text7 = await readFile18(join39(localAppData, rel), "utf8").catch(() => "");
44113
+ const text7 = await readFile19(join40(localAppData, rel), "utf8").catch(() => "");
43837
44114
  if (new RegExp(`"source"\\s*:\\s*"${PROFILE}"`).test(text7)) return true;
43838
44115
  }
43839
44116
  return false;
43840
44117
  }
43841
44118
  async function writeIfChanged2(path3, content3) {
43842
- const current2 = await readFile18(path3).catch(() => null);
44119
+ const current2 = await readFile19(path3).catch(() => null);
43843
44120
  if (current2 && current2.equals(Buffer.from(content3))) return false;
43844
44121
  await writeFile18(path3, content3);
43845
44122
  return true;
@@ -43851,9 +44128,9 @@ async function openInLedgeTab(o) {
43851
44128
  await writeIfChanged2(o.launcher, launcherScript(o.node, o.script, o.ledgeHome));
43852
44129
  const dir = fragmentDir(o.localAppData.linux);
43853
44130
  await mkdir14(dir, { recursive: true });
43854
- await writeIfChanged2(join39(dir, "ledge.ico"), await readFile18(o.icon ?? ICON));
44131
+ await writeIfChanged2(join40(dir, "ledge.ico"), await readFile19(o.icon ?? ICON));
43855
44132
  const iconWin = `${o.localAppData.win}\\Microsoft\\Windows Terminal\\Fragments\\${PROFILE}\\ledge.ico`;
43856
- await writeIfChanged2(join39(dir, "ledge.json"), fragment(commandline, iconWin));
44133
+ await writeIfChanged2(join40(dir, "ledge.json"), fragment(commandline, iconWin));
43857
44134
  const deadline = Date.now() + (o.waitMs ?? 15e3);
43858
44135
  for (let first = true; !await profileLoaded(o.localAppData.linux); first = false) {
43859
44136
  if (Date.now() > deadline) return false;
@@ -43874,11 +44151,11 @@ async function openTuiInLedgeTab(env, onWait) {
43874
44151
  distro: env.WSL_DISTRO_NAME,
43875
44152
  node: await realpath5(process.execPath),
43876
44153
  script: await realpath5(process.argv[1]),
43877
- launcher: join39(ledgeDir(), "tui.sh"),
44154
+ launcher: join40(ledgeDir(), "tui.sh"),
43878
44155
  ledgeHome: env.LEDGE_HOME,
43879
44156
  onWait,
43880
44157
  wt: (args) => new Promise((resolve13, reject) => {
43881
- const child = spawn6(join39(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
44158
+ const child = spawn6(join40(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
43882
44159
  child.once("error", reject);
43883
44160
  child.once("spawn", () => {
43884
44161
  child.unref();
@@ -43902,7 +44179,7 @@ var init_terminalProfile = __esm({
43902
44179
  "Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json",
43903
44180
  "Microsoft/Windows Terminal/settings.json"
43904
44181
  ];
43905
- fragmentDir = (localAppData) => join39(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
44182
+ fragmentDir = (localAppData) => join40(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
43906
44183
  PROFILE_ID = "{d10fee13-0978-595f-a851-b60ec80f11c9}";
43907
44184
  shQuote2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
43908
44185
  run3 = promisify(execFile10);