@paradow/ledge 0.40.0 → 0.41.0-snapshot.41719405620.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"]),
@@ -3487,6 +3493,19 @@ var init_src = __esm({
3487
3493
  }
3488
3494
  });
3489
3495
 
3496
+ // src/port.ts
3497
+ function ledgePort(env = process.env) {
3498
+ const raw = env.LEDGE_PORT;
3499
+ const n = raw && /^\d+$/.test(raw) ? Number(raw) : NaN;
3500
+ return Number.isInteger(n) && n >= 1 && n <= 65535 ? n : PROTOCOL_PORT;
3501
+ }
3502
+ var init_port = __esm({
3503
+ "src/port.ts"() {
3504
+ "use strict";
3505
+ init_src();
3506
+ }
3507
+ });
3508
+
3490
3509
  // src/paths.ts
3491
3510
  import { homedir } from "os";
3492
3511
  import { join } from "path";
@@ -3665,7 +3684,7 @@ async function sendMessageAndAwaitAck(client, msg, timeoutMs) {
3665
3684
  return client.next("message.ack", timeoutMs);
3666
3685
  }
3667
3686
  function connect(opts = {}) {
3668
- const port = opts.port ?? PROTOCOL_PORT;
3687
+ const port = opts.port ?? ledgePort();
3669
3688
  const host = opts.host ?? "127.0.0.1";
3670
3689
  let token;
3671
3690
  try {
@@ -3743,6 +3762,7 @@ var DaemonRefusedError;
3743
3762
  var init_client = __esm({
3744
3763
  "src/client.ts"() {
3745
3764
  "use strict";
3765
+ init_port();
3746
3766
  init_auth();
3747
3767
  init_src();
3748
3768
  DaemonRefusedError = class extends Error {
@@ -3788,11 +3808,11 @@ function stripNote(text7) {
3788
3808
  }
3789
3809
  function formatLedgeMessage(m) {
3790
3810
  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}`;
3811
+ const open6 = `<ledge-message from="${from}" tool="${m.tool}"${m.id ? ` id="${attr(m.id)}"` : ""}>${messageNote(from, m.to)} `;
3812
+ if (!m.id || inline(m.text)) return `${open6}${ledgeBody(m.text)}${LEDGE_CLOSE}`;
3793
3813
  const first = m.summary?.trim() || m.text.trim().split(/\r\n|\r|\n/)[0].trim();
3794
3814
  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}`;
3815
+ 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
3816
  }
3797
3817
  function ledgeMessages(text7) {
3798
3818
  const out = [];
@@ -4576,7 +4596,7 @@ async function spawnSession(opts) {
4576
4596
  const agent = opts.command ?? [AGENT_BIN[opts.tool], ...agentIdArgs(opts.tool, id), ...agentSessionId ? ["--session-id", agentSessionId] : [], ...opts.args];
4577
4597
  const env = opts.env ?? process.env;
4578
4598
  const transcriptPath = agentSessionId ? claudeTranscriptPath(opts.cwd, agentSessionId, env) : void 0;
4579
- const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
4599
+ const command = opts.recordExit && process.platform !== "win32" ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
4580
4600
  const key = sessionKey(id);
4581
4601
  await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(env, { LEDGE_SESSION: id, ...key ? { [SESSION_KEY_VAR]: key } : {}, LEDGE_NAME: name, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
4582
4602
  const at = now2();
@@ -4947,7 +4967,7 @@ __export(who_exports, {
4947
4967
  });
4948
4968
  import { basename as basename5 } from "path";
4949
4969
  function formatWho(sessions, self, projects) {
4950
- const live3 = sessions.filter((s) => s.state !== "exited" && !isArchived(s));
4970
+ const live3 = sessions.filter((s) => s.state !== "exited" && !isArchived(s) || isStoppedOmpSeat(s));
4951
4971
  if (live3.length === 0) return "aucune autre session";
4952
4972
  const rows = live3.map((s) => [
4953
4973
  s.id === self ? "*" : " ",
@@ -5195,8 +5215,8 @@ function underCodexDaemon(read = (p) => readFileSync5(p, "utf8")) {
5195
5215
  let pid = process.pid;
5196
5216
  for (let depth = 0; depth < 8; depth++) {
5197
5217
  try {
5198
- const stat7 = read(`/proc/${pid}/stat`);
5199
- pid = Number(stat7.slice(stat7.lastIndexOf(")") + 2).split(" ")[1]);
5218
+ const stat8 = read(`/proc/${pid}/stat`);
5219
+ pid = Number(stat8.slice(stat8.lastIndexOf(")") + 2).split(" ")[1]);
5200
5220
  if (!pid || pid <= 1) return false;
5201
5221
  if (read(`/proc/${pid}/cmdline`).split("\0").includes("app-server")) return true;
5202
5222
  } catch {
@@ -6764,9 +6784,9 @@ ${done.output}` : command;
6764
6784
  const peer = (m.sender ? this.peerOf(m.sender) : void 0) ?? this.peerOf(m.from) ?? (m.name ? this.peerOf(m.name) : void 0);
6765
6785
  if (!peer) return this.put(id, recv);
6766
6786
  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);
6787
+ const open6 = this.items(peer).filter((i) => i.kind === "sent" && i.peer === id && i.status !== "read");
6788
+ const byId2 = m.id ? open6.find((i) => mailId(i.id) === m.id) : void 0;
6789
+ const sent = byId2 ?? [...open6].reverse().find((i) => head2(i.text) === head2(recv.text)) ?? open6.at(-1);
6770
6790
  if (sent) this.put(peer, { ...sent, status: "read", received: recv.id });
6771
6791
  return this.put(id, { ...recv, ...byId2 ? { text: byId2.text } : {}, peer, ...sent ? { sent: sent.id } : {} });
6772
6792
  }
@@ -7147,9 +7167,9 @@ function parseTrust(pane) {
7147
7167
  while (o >= 0 && isBlank(lines[o])) o--;
7148
7168
  const block2 = [];
7149
7169
  while (o >= 0 && !isBlank(lines[o])) block2.unshift(lines[o--]);
7150
- const options = block2.map((l) => l.trim().replace(/^❯\s*/, ""));
7170
+ const options = block2.map((l) => l.trim().replace(CURSOR, ""));
7151
7171
  const trust = options.findIndex((l) => TRUST_OPTION.test(l));
7152
- const selected = block2.findIndex((l) => l.trim().startsWith("\u276F"));
7172
+ const selected = block2.findIndex((l) => CURSOR.test(l.trim()));
7153
7173
  if (trust < 0 || selected < 0) return null;
7154
7174
  const at = lines.findIndex((l) => /^\s*Accessing workspace:/.test(l));
7155
7175
  const path3 = at < 0 ? "" : lines.slice(at + 1, footer).find((l) => !isBlank(l))?.trim() ?? "";
@@ -7195,7 +7215,7 @@ function inputBox(lines) {
7195
7215
  if (bottom < 0) return null;
7196
7216
  for (let top = bottom - 1; top >= 0 && bottom - top <= INPUT_MAX2 + 1; top--) {
7197
7217
  if (!isInputTop(lines[top])) continue;
7198
- return lines[top + 1].trimStart().startsWith("\u276F") ? { top, bottom } : null;
7218
+ return CURSOR.test(lines[top + 1].trimStart()) ? { top, bottom } : null;
7199
7219
  }
7200
7220
  return null;
7201
7221
  }
@@ -7234,7 +7254,7 @@ function sentFrom(text7) {
7234
7254
  const lines = pane.split("\n").map((l) => l.replace(/\s+$/, ""));
7235
7255
  const box = inputBox(lines);
7236
7256
  if (!box) return true;
7237
- const draft = lines.slice(box.top + 1, box.bottom).join("").replace(/\s+/g, "").replace(/^❯/, "");
7257
+ const draft = lines.slice(box.top + 1, box.bottom).join("").replace(/\s+/g, "").replace(CURSOR, "");
7238
7258
  return !draft.includes("[Pastedtext#") && !(head2 && draft.startsWith(head2));
7239
7259
  };
7240
7260
  }
@@ -7270,16 +7290,18 @@ function notePlan(note, pane) {
7270
7290
  if (row3 >= 0) return rowPlan(row3 + 1, text7);
7271
7291
  return [{ keys: ["Escape"] }, { until: atRest, denied: true }, ...CLEAR, ...submit(text7, true)];
7272
7292
  }
7273
- var OPTION, FOOTER, PLAN_FOOTER, PREVIEW_FOOTER, CHAT_LINE, PREVIEW_EDGE, GUTTER, REVIEW_QUESTION, RULE, DASHED, INPUT_TOP, FOOTER_SLACK, isBlank, isRule, dashedBody, TRUST_FOOTER, TRUST_OPTION, squash, STATUS_SLACK, INPUT_MAX2, isInputRule, isInputTop, TITLE_RULE, BUSY, SPINNER, digit, focused, FREE_ROW, CHAT_FOCUSED, NO_FREE_ROW, CLEAR, REPLY_ENTER_DELAY_MS, send, submit, PLAN_ACCEPT, ALWAYS, AUTO_MODE, PLAN_ALWAYS, CLEAR_CONTEXT, PLAN_NOTE_ROW, claudeAdapter;
7293
+ var CUR, CURSOR, OPTION, FOOTER, PLAN_FOOTER, PREVIEW_FOOTER, CHAT_LINE, PREVIEW_EDGE, GUTTER, REVIEW_QUESTION, RULE, DASHED, INPUT_TOP, FOOTER_SLACK, isBlank, isRule, dashedBody, TRUST_FOOTER, TRUST_OPTION, squash, STATUS_SLACK, INPUT_MAX2, isInputRule, isInputTop, TITLE_RULE, BUSY, SPINNER, digit, focused, FREE_ROW, CHAT_FOCUSED, NO_FREE_ROW, CLEAR, REPLY_ENTER_DELAY_MS, send, submit, PLAN_ACCEPT, ALWAYS, AUTO_MODE, PLAN_ALWAYS, CLEAR_CONTEXT, PLAN_NOTE_ROW, claudeAdapter;
7274
7294
  var init_claude = __esm({
7275
7295
  "src/adapters/claude.ts"() {
7276
7296
  "use strict";
7277
7297
  init_types();
7278
- OPTION = /^\s*(?:❯\s*)?(\d+)\.\s+(\S.*?)\s*$/;
7298
+ CUR = process.platform === "win32" ? "[\u276F>]" : "\u276F";
7299
+ CURSOR = new RegExp(`^${CUR}\\s*`);
7300
+ OPTION = new RegExp(`^\\s*(?:${CUR}\\s*)?(\\d+)\\.\\s+(\\S.*?)\\s*$`);
7279
7301
  FOOTER = /^\s*(?:(?:Enter to select\b.*)?Esc to cancel\b|ctrl\+g to edit\b)/;
7280
7302
  PLAN_FOOTER = /^\s*ctrl\+g to edit\b/;
7281
7303
  PREVIEW_FOOTER = /\bn to add notes\b/;
7282
- CHAT_LINE = /^\s*(?:❯\s*)?Chat about this$/;
7304
+ CHAT_LINE = new RegExp(`^\\s*(?:${CUR}\\s*)?Chat about this$`);
7283
7305
  PREVIEW_EDGE = /\s{2,}[┌│└]/;
7284
7306
  GUTTER = /^│\s*/;
7285
7307
  REVIEW_QUESTION = /^\s*Ready to submit your answers\?$/;
@@ -7299,14 +7321,14 @@ var init_claude = __esm({
7299
7321
  isInputTop = (l) => INPUT_TOP.test(l.trim());
7300
7322
  TITLE_RULE = /^─{10,} (\S.*?) ─+$/;
7301
7323
  BUSY = /\besc to interrupt\b/;
7302
- SPINNER = /^[^\s❯●⎿│─]\s\S+…(?: \(|$)/;
7324
+ SPINNER = new RegExp(`^[^\\s\u276F${process.platform === "win32" ? ">" : ""}\u25CF\u23BF\u2502\u2500]\\s\\S+\u2026(?: \\(|$)`);
7303
7325
  digit = (n) => [{ text: String(n) }];
7304
7326
  focused = (n) => {
7305
- const row3 = new RegExp(`^\\s*\u276F\\s*${n}\\.\\s`, "m");
7327
+ const row3 = new RegExp(`^\\s*${CUR}\\s*${n}\\.\\s`, "m");
7306
7328
  return (p) => row3.test(p);
7307
7329
  };
7308
7330
  FREE_ROW = /^Type something\.?$/;
7309
- CHAT_FOCUSED = /^\s*❯\s*Chat about this\s*$/m;
7331
+ CHAT_FOCUSED = new RegExp(`^\\s*${CUR}\\s*Chat about this\\s*$`, "m");
7310
7332
  NO_FREE_ROW = "no free-text row in this dialog, answer in the terminal";
7311
7333
  CLEAR = [{ keys: ["C-u"] }];
7312
7334
  REPLY_ENTER_DELAY_MS = 300;
@@ -7388,6 +7410,7 @@ function parseDialog2(pane) {
7388
7410
  }
7389
7411
  const f = lines[footer];
7390
7412
  const kind = APPROVAL_FOOTER.test(f) ? "approval" : QUESTION_FOOTER.test(f) ? "question" : SELECT_FOOTER.test(f) ? "plan" : "confirm";
7413
+ if (kind === "plan" && header.some((l) => SANDBOX_SETUP.test(l))) return { kind: "sandbox", header, options: raw.map(label), details: raw.map(detail), raw };
7391
7414
  if (kind === "plan" && !header.some((l) => /^\s*Implement this plan\?\s*$/.test(l))) return null;
7392
7415
  return { kind, header, options: raw.map(label), details: raw.map(detail), raw };
7393
7416
  }
@@ -7430,6 +7453,7 @@ function detectPrompt2(pane) {
7430
7453
  if (!d) return null;
7431
7454
  if (d.kind === "question") return { kind: "question", text: questionText(d.header), options: d.options, ...d.details.some(Boolean) ? { optionDetails: d.details } : {} };
7432
7455
  if (d.kind === "plan") return { kind: "plan", text: "Implement this plan?", options: d.options };
7456
+ if (d.kind === "sandbox") return { kind: "question", text: "Set up the Codex agent sandbox (Windows)", options: d.options };
7433
7457
  if (d.kind === "confirm") return { kind: "permission", text: confirmText(d.header), options: d.options };
7434
7458
  const cmd = d.header.findIndex((l) => /^\s*\$\s/.test(l));
7435
7459
  const command = cmd >= 0 ? shownCommand(rawRun(d.header, cmd)) : void 0;
@@ -7479,7 +7503,25 @@ function noteKeys(d, note) {
7479
7503
  const no2 = d?.kind === "plan" ? optionNumber2(d.options, /^No\b/) : null;
7480
7504
  return [...no2 ? digit2(no2) : [{ keys: ["Escape"] }], { until: atRest2, denied: true }, { text: flatten(note) }, { waitMs: REPLY_ENTER_DELAY_MS2 }, { keys: ["Enter"] }];
7481
7505
  }
7482
- var APPROVAL_FOOTER, QUESTION_FOOTER, SELECT_FOOTER, CONFIRM_FOOTER, FOOTERS2, OPTION2, HEADER_SPAN, isBlank2, isFooter, split, label, detail, run, BELOW_INPUT, BUSY2, BUSY_SPAN, digit2, REPLY_ENTER_DELAY_MS2, ALWAYS2, codexAdapter;
7506
+ function draftShown(text7) {
7507
+ const head2 = kept(text7).slice(0, 20);
7508
+ return (pane) => {
7509
+ if (!head2) return true;
7510
+ const lines = split(pane);
7511
+ const i = inputLine(lines);
7512
+ if (i < 0) return false;
7513
+ const draft = lines.slice(i).join("").replace(/\s+/g, "").replace(/^›/, "");
7514
+ return /\[PastedContent\d+chars\]/.test(draft) || kept(draft).startsWith(head2);
7515
+ };
7516
+ }
7517
+ function codexReplyPlan(text7, pending, platform, pane) {
7518
+ const flat = flatten(text7);
7519
+ if (pending?.kind === "question" && /^[1-9]$/.test(flat) && pane && parseDialog2(pane)?.kind === "sandbox") return [...digit2(Number(flat)), { waitMs: REPLY_ENTER_DELAY_MS2 }, { keys: ["Enter"] }];
7520
+ if (pending?.kind === "question" && /^[1-9]$/.test(flat)) return digit2(Number(flat));
7521
+ if (platform === "win32") return [{ text: flat }, { until: draftShown(flat), ms: DRAFT_SHOWN_MS }, { waitMs: REPLY_ENTER_DELAY_MS2 }, { keys: ["Enter"] }];
7522
+ return [{ text: flat }, { waitMs: REPLY_ENTER_DELAY_MS2 }, { keys: ["Enter"] }];
7523
+ }
7524
+ var APPROVAL_FOOTER, QUESTION_FOOTER, SELECT_FOOTER, SANDBOX_SETUP, CONFIRM_FOOTER, FOOTERS2, OPTION2, HEADER_SPAN, isBlank2, isFooter, split, label, detail, run, BELOW_INPUT, BUSY2, BUSY_SPAN, digit2, REPLY_ENTER_DELAY_MS2, ALWAYS2, DRAFT_SHOWN_MS, kept, codexAdapter;
7483
7525
  var init_codex = __esm({
7484
7526
  "src/adapters/codex.ts"() {
7485
7527
  "use strict";
@@ -7487,6 +7529,7 @@ var init_codex = __esm({
7487
7529
  APPROVAL_FOOTER = /^\s*Press enter to confirm or esc to cancel\s*$/;
7488
7530
  QUESTION_FOOTER = /\benter to submit\b/;
7489
7531
  SELECT_FOOTER = /^\s*enter select · esc back\s*$/;
7532
+ SANDBOX_SETUP = /^\s*Set up the Codex agent sandbox\b/;
7490
7533
  CONFIRM_FOOTER = /^\s*enter confirm · esc skip\s*$/;
7491
7534
  FOOTERS2 = [APPROVAL_FOOTER, QUESTION_FOOTER, SELECT_FOOTER, CONFIRM_FOOTER];
7492
7535
  OPTION2 = /^\s*(?:›\s*)?(\d+)\.\s+(\S.*)$/;
@@ -7503,6 +7546,8 @@ var init_codex = __esm({
7503
7546
  digit2 = (n) => [{ text: String(n) }];
7504
7547
  REPLY_ENTER_DELAY_MS2 = 300;
7505
7548
  ALWAYS2 = /don['’]t ask again/i;
7549
+ DRAFT_SHOWN_MS = 1e4;
7550
+ kept = (s) => s.replace(/[^\p{L}\p{N}]/gu, "");
7506
7551
  codexAdapter = {
7507
7552
  tool: "codex",
7508
7553
  detectPrompt: detectPrompt2,
@@ -7511,7 +7556,7 @@ var init_codex = __esm({
7511
7556
  atRest: atRest2,
7512
7557
  keysFor(decision, pending, pane, note) {
7513
7558
  const d = pane ? parseDialog2(pane) : null;
7514
- if (d) assertDialogIs(pending, d.kind === "approval" || d.kind === "confirm" ? "permission" : d.kind);
7559
+ if (d) assertDialogIs(pending, d.kind === "approval" || d.kind === "confirm" ? "permission" : d.kind === "sandbox" ? "question" : d.kind);
7515
7560
  if (decision === "always") return alwaysKeys(d);
7516
7561
  if (decision === "deny" && note) return noteKeys(d, note);
7517
7562
  if (decision === "deny") {
@@ -7529,11 +7574,7 @@ var init_codex = __esm({
7529
7574
  if (pending.kind === "question") return digit2(1);
7530
7575
  return [{ text: "y" }];
7531
7576
  },
7532
- replyPlan(text7, pending) {
7533
- const flat = flatten(text7);
7534
- if (pending?.kind === "question" && /^[1-9]$/.test(flat)) return digit2(Number(flat));
7535
- return [{ text: flat }, { waitMs: REPLY_ENTER_DELAY_MS2 }, { keys: ["Enter"] }];
7536
- },
7577
+ replyPlan: (text7, pending, pane) => codexReplyPlan(text7, pending, process.platform, pane),
7537
7578
  // Esc interrupts the turn; Ctrl+C clears the draft and a second one quits Codex.
7538
7579
  interruptKeys: () => ["Escape"],
7539
7580
  // Codex 0.162, seen in tmux: Ctrl+U clears a typed draft, then `/exit` and `/quit` both quit it (a draft cleared that
@@ -7838,6 +7879,203 @@ var init_verify = __esm({
7838
7879
  }
7839
7880
  });
7840
7881
 
7882
+ // src/daemon/omp/seats.ts
7883
+ import { homedir as homedir5 } from "os";
7884
+ import { join as join10 } from "path";
7885
+ import { open, readFile as readFile7, stat as stat2 } from "fs/promises";
7886
+ function entryState(raw) {
7887
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
7888
+ const e = raw;
7889
+ const at = typeof e.timestamp === "string" && !Number.isNaN(Date.parse(e.timestamp)) ? new Date(e.timestamp).toISOString() : void 0;
7890
+ const of = (state2) => ({ state: state2, ...at ? { at } : {} });
7891
+ if (e.type === "custom_message") return of("working");
7892
+ if (e.type === "custom") return e.customType === "tool_execution_start" ? of("working") : null;
7893
+ if (e.type !== "message" || !e.message || typeof e.message !== "object") return null;
7894
+ const m = e.message;
7895
+ if (m.role === "user" || m.role === "toolResult") return of("working");
7896
+ if (m.role !== "assistant") return null;
7897
+ return typeof m.stopReason === "string" ? of(m.stopReason === "toolUse" ? "working" : "idle") : null;
7898
+ }
7899
+ async function nextNewline(fh, from, size) {
7900
+ const buf2 = Buffer.alloc(BLOCK3);
7901
+ for (let at = from; at < size; ) {
7902
+ const { bytesRead } = await fh.read(buf2, 0, Math.min(BLOCK3, size - at), at);
7903
+ if (bytesRead === 0) return -1;
7904
+ const nl = buf2.subarray(0, bytesRead).indexOf(10);
7905
+ if (nl !== -1) return at + nl;
7906
+ at += bytesRead;
7907
+ }
7908
+ return -1;
7909
+ }
7910
+ function createOmpSeats(deps) {
7911
+ const { store } = deps;
7912
+ const root2 = deps.root ?? ompSeatsRoot();
7913
+ const follows = /* @__PURE__ */ new Map();
7914
+ let running = false, timer2 = null;
7915
+ async function journalOf(seat2) {
7916
+ try {
7917
+ const file = JSON.parse(await readFile7(join10(root2, seat2, "runner-state.json"), "utf8")).sessionFile;
7918
+ return typeof file === "string" && file ? file : null;
7919
+ } catch {
7920
+ return null;
7921
+ }
7922
+ }
7923
+ async function followOne(s) {
7924
+ const file = await journalOf(s.rig.seat);
7925
+ if (!file) return;
7926
+ let size;
7927
+ try {
7928
+ size = (await stat2(file)).size;
7929
+ } catch {
7930
+ return;
7931
+ }
7932
+ let f = follows.get(s.id);
7933
+ if (!f || f.file !== file) {
7934
+ const known2 = await lastState(file, size);
7935
+ f = { file, offset: known2.end };
7936
+ follows.set(s.id, f);
7937
+ if (known2.state) apply(s.id, known2.state);
7938
+ return;
7939
+ }
7940
+ if (size < f.offset) {
7941
+ f.offset = size;
7942
+ return;
7943
+ }
7944
+ if (size === f.offset) return;
7945
+ const fh = await open(file, "r");
7946
+ let chunk, end2;
7947
+ try {
7948
+ const readAt2 = async (want) => {
7949
+ const b3 = Buffer.alloc(want);
7950
+ return b3.subarray(0, (await fh.read(b3, 0, want, f.offset)).bytesRead);
7951
+ };
7952
+ chunk = await readAt2(Math.min(size - f.offset, READ_BYTES));
7953
+ end2 = chunk.lastIndexOf(10);
7954
+ if (end2 === -1 && chunk.length < size - f.offset) {
7955
+ chunk = await readAt2(Math.min(size - f.offset, BACK_BYTES));
7956
+ end2 = chunk.lastIndexOf(10);
7957
+ }
7958
+ if (end2 === -1 && chunk.length === BACK_BYTES) {
7959
+ const next4 = await nextNewline(fh, f.offset + chunk.length, size);
7960
+ if (next4 !== -1) {
7961
+ log(`omp seats: an entry of ${s.rig.seat} over ${BACK_BYTES} bytes was skipped`);
7962
+ f.offset = next4 + 1;
7963
+ }
7964
+ return;
7965
+ }
7966
+ } finally {
7967
+ await fh.close();
7968
+ }
7969
+ if (end2 === -1) return;
7970
+ const lines = chunk.subarray(0, end2).toString("utf8").split("\n");
7971
+ f.offset += end2 + 1;
7972
+ let last = null;
7973
+ for (const line2 of lines) {
7974
+ if (!line2.trim()) continue;
7975
+ let entry2;
7976
+ try {
7977
+ entry2 = JSON.parse(line2);
7978
+ } catch {
7979
+ continue;
7980
+ }
7981
+ last = entryState(entry2) ?? last;
7982
+ }
7983
+ if (last) apply(s.id, last);
7984
+ }
7985
+ function apply(id, last) {
7986
+ const cur = store.get(id);
7987
+ if (!cur || cur.state === "exited") return;
7988
+ const at = last.at ?? (/* @__PURE__ */ new Date()).toISOString();
7989
+ if (cur.state !== last.state || at > cur.lastActivityAt) store.upsert({ ...cur, state: last.state, lastActivityAt: at > cur.lastActivityAt ? at : cur.lastActivityAt });
7990
+ }
7991
+ async function lastState(file, size) {
7992
+ const fh = await open(file, "r");
7993
+ try {
7994
+ const newlineBefore = async (before2) => {
7995
+ const buf2 = Buffer.alloc(BLOCK3);
7996
+ for (let to = before2; to > 0; ) {
7997
+ if (size - to > BACK_BYTES) return null;
7998
+ const from = Math.max(0, to - BLOCK3);
7999
+ const { bytesRead } = await fh.read(buf2, 0, to - from, from);
8000
+ const at = buf2.subarray(0, bytesRead).lastIndexOf(10);
8001
+ if (at !== -1) return from + at;
8002
+ to = from;
8003
+ }
8004
+ return -1;
8005
+ };
8006
+ const last = await newlineBefore(size);
8007
+ if (last === null || last === -1) return { state: null, end: last === -1 ? 0 : size };
8008
+ const end2 = last + 1;
8009
+ for (let lineEnd = last; lineEnd > 0; ) {
8010
+ const start = await newlineBefore(lineEnd);
8011
+ if (start === null) break;
8012
+ const from = start + 1;
8013
+ const line2 = Buffer.alloc(lineEnd - from);
8014
+ await fh.read(line2, 0, line2.length, from);
8015
+ let entry2 = null;
8016
+ try {
8017
+ entry2 = JSON.parse(line2.toString("utf8"));
8018
+ } catch {
8019
+ }
8020
+ const state2 = entryState(entry2);
8021
+ if (state2) return { state: state2, end: end2 };
8022
+ if (start === -1) break;
8023
+ lineEnd = start;
8024
+ }
8025
+ return { state: null, end: end2 };
8026
+ } finally {
8027
+ await fh.close();
8028
+ }
8029
+ }
8030
+ async function tick() {
8031
+ if (running) return;
8032
+ running = true;
8033
+ try {
8034
+ const live3 = /* @__PURE__ */ new Set();
8035
+ for (const s of store.all()) {
8036
+ if (!isOmpSeat2(s) || !s.rig || s.state === "exited" || isArchived(s)) continue;
8037
+ live3.add(s.id);
8038
+ try {
8039
+ await followOne(s);
8040
+ } catch (e) {
8041
+ log(`omp seats: journal of ${s.rig.seat} not read: ${e.message}`);
8042
+ }
8043
+ }
8044
+ for (const id of follows.keys()) if (!live3.has(id)) follows.delete(id);
8045
+ } finally {
8046
+ running = false;
8047
+ }
8048
+ }
8049
+ return {
8050
+ tick,
8051
+ start() {
8052
+ if (!timer2) {
8053
+ timer2 = setInterval(() => {
8054
+ void tick();
8055
+ }, deps.intervalMs ?? 1e3);
8056
+ timer2.unref?.();
8057
+ }
8058
+ },
8059
+ stop() {
8060
+ if (timer2) clearInterval(timer2);
8061
+ timer2 = null;
8062
+ }
8063
+ };
8064
+ }
8065
+ var ompSeatsRoot, BACK_BYTES, BLOCK3, READ_BYTES, isOmpSeat2;
8066
+ var init_seats = __esm({
8067
+ "src/daemon/omp/seats.ts"() {
8068
+ "use strict";
8069
+ init_src();
8070
+ init_log();
8071
+ ompSeatsRoot = (env = process.env) => join10(env.OPENRIG_HOME?.trim() || join10(homedir5(), ".openrig"), "state", "omp");
8072
+ BACK_BYTES = 16 * 1024 * 1024;
8073
+ BLOCK3 = 64 * 1024;
8074
+ READ_BYTES = 4 * 1024 * 1024;
8075
+ isOmpSeat2 = (s) => s.source === "rig" && s.tool === "omp" && s.backend === void 0;
8076
+ }
8077
+ });
8078
+
7841
8079
  // src/daemon/poller.ts
7842
8080
  import { createHash as createHash4 } from "crypto";
7843
8081
  function startPoller(opts) {
@@ -7902,7 +8140,7 @@ function startPoller(opts) {
7902
8140
  }
7903
8141
  if (s.state === "exited") {
7904
8142
  forget(s.id);
7905
- if (!s.archivedAt && s.exitedAt) {
8143
+ if (!s.archivedAt && s.exitedAt && !isOmpSeat2(s)) {
7906
8144
  const since = Math.max(Date.parse(s.exitedAt), s.restoredAt ? Date.parse(s.restoredAt) : -Infinity);
7907
8145
  if (t - since > retireMs) store.archive(s.id, now2());
7908
8146
  }
@@ -7915,10 +8153,12 @@ function startPoller(opts) {
7915
8153
  }
7916
8154
  if (!s.tmuxSession) return;
7917
8155
  if (deadCheck && !await tmux.hasSession(s.tmuxSession)) {
7918
- store.apply(s.id, { name: "PaneGone", at: now2(), source: "daemon" });
8156
+ const code3 = tmux.exitCode?.(s.tmuxSession);
8157
+ store.apply(s.id, code3 === void 0 ? { name: "PaneGone", at: now2(), source: "daemon" } : { name: "SessionEnd", at: now2(), source: "daemon", exitCode: code3 });
7919
8158
  forget(s.id);
7920
8159
  return;
7921
8160
  }
8161
+ if (isOmpSeat2(s)) return;
7922
8162
  const full = await tmux.capture(s.tmuxSession, PREVIEW_CAPTURE_LINES);
7923
8163
  if (full === null) return;
7924
8164
  const pane = full.split("\n").slice(-DETECT_LINES).join("\n");
@@ -7983,6 +8223,7 @@ var init_poller = __esm({
7983
8223
  init_log();
7984
8224
  init_git();
7985
8225
  init_transcript();
8226
+ init_seats();
7986
8227
  init_adapters();
7987
8228
  DETECT_LINES = 60;
7988
8229
  REST_MS = 2e3;
@@ -8528,7 +8769,7 @@ var init_project = __esm({
8528
8769
 
8529
8770
  // src/daemon/omp/host.ts
8530
8771
  import { randomBytes as randomBytes4 } from "crypto";
8531
- import { basename as basename7, join as join10 } from "path";
8772
+ import { basename as basename7, join as join11 } from "path";
8532
8773
  import { mkdir as mkdir7 } from "fs/promises";
8533
8774
  function ompArgs(o) {
8534
8775
  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 +8781,7 @@ function createOmpHost(deps) {
8540
8781
  const live3 = /* @__PURE__ */ new Map();
8541
8782
  let generations = 0;
8542
8783
  async function startProcess(id, o) {
8543
- const dir = join10(deps.dir, id);
8784
+ const dir = join11(deps.dir, id);
8544
8785
  await mkdir7(dir, { recursive: true });
8545
8786
  if (await reapOwner(dir)) log(`omp ${id}: ended the process a previous daemon left`);
8546
8787
  const key = sessionKey(id);
@@ -8720,15 +8961,15 @@ function createOmpHost(deps) {
8720
8961
  async answer(id, text7) {
8721
8962
  const l = live3.get(id);
8722
8963
  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);
8964
+ const open6 = l.tracking.requests[0];
8965
+ if (!open6) return "nothing to answer";
8966
+ const a = answerFor(open6, text7);
8726
8967
  if ("refused" in a) return a.refused;
8727
8968
  l.tracking.requests.shift();
8728
8969
  try {
8729
8970
  await l.rpc.write(a.frame);
8730
8971
  } catch (e) {
8731
- l.tracking.requests.unshift(open5);
8972
+ l.tracking.requests.unshift(open6);
8732
8973
  return e.message;
8733
8974
  }
8734
8975
  store.apply(id, { name: "Decided", at: now2(), source: "daemon" });
@@ -8777,15 +9018,15 @@ function createOmpHost(deps) {
8777
9018
  async answerAsk(id, requestId3, answers, cancel = false) {
8778
9019
  const l = live3.get(id);
8779
9020
  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);
9021
+ const open6 = l.tracking.requests[0];
9022
+ if (!open6 || open6.id !== requestId3) return "the request changed, look again";
9023
+ const a = cancel ? { frame: { type: "extension_ui_response", id: open6.id, cancelled: true } } : askAnswerFor(open6, answers);
8783
9024
  if ("refused" in a) return a.refused;
8784
9025
  l.tracking.requests.shift();
8785
9026
  try {
8786
9027
  await l.rpc.write(a.frame);
8787
9028
  } catch (e) {
8788
- l.tracking.requests.unshift(open5);
9029
+ l.tracking.requests.unshift(open6);
8789
9030
  return e.message;
8790
9031
  }
8791
9032
  store.apply(id, { name: "Decided", at: now2(), source: "daemon" });
@@ -8871,9 +9112,9 @@ var init_catalog = __esm({
8871
9112
  // src/daemon/repos.ts
8872
9113
  import { execFile as execFile2 } from "child_process";
8873
9114
  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";
9115
+ import { open as open2, stat as stat3 } from "fs/promises";
9116
+ import { homedir as homedir6 } from "os";
9117
+ import { join as join12, resolve as resolve2 } from "path";
8877
9118
  function runGit(cwd, args) {
8878
9119
  return new Promise((res) => {
8879
9120
  execFile2("git", ["-C", cwd, ...args], { timeout: GIT_TIMEOUT_MS, windowsHide: true }, (err, out) => res(err ? null : out.trim() || null));
@@ -8891,7 +9132,7 @@ async function slot(job) {
8891
9132
  }
8892
9133
  }
8893
9134
  async function readGitFile(path3, max = 64 * 1024) {
8894
- const fh = await open(path3, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
9135
+ const fh = await open2(path3, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
8895
9136
  try {
8896
9137
  if (!(await fh.stat()).isFile()) throw new Error(`not a regular file: ${path3}`);
8897
9138
  const buf2 = Buffer.alloc(max + 1);
@@ -8905,7 +9146,7 @@ async function readGitFile(path3, max = 64 * 1024) {
8905
9146
  async function headBranch(dotGit) {
8906
9147
  let head2;
8907
9148
  try {
8908
- head2 = (await readGitFile(join11(dotGit, "HEAD"), MAX_HEAD_BYTES)).trim();
9149
+ head2 = (await readGitFile(join12(dotGit, "HEAD"), MAX_HEAD_BYTES)).trim();
8909
9150
  } catch {
8910
9151
  return null;
8911
9152
  }
@@ -8914,23 +9155,23 @@ async function headBranch(dotGit) {
8914
9155
  return /^[0-9a-f]{7,}$/i.test(head2) ? head2.slice(0, 7) : null;
8915
9156
  }
8916
9157
  async function inspectRepos(paths2, opts = {}) {
8917
- const home2 = opts.home ?? homedir5();
9158
+ const home2 = opts.home ?? homedir6();
8918
9159
  const git = opts.git ?? runGit;
8919
9160
  return Promise.all(paths2.map((raw) => slot(async () => {
8920
9161
  const input = raw.trim();
8921
9162
  const path3 = resolve2(input === "~" ? home2 : input.startsWith("~/") ? resolve2(home2, input.slice(2)) : input);
8922
9163
  let isDir2 = false;
8923
9164
  try {
8924
- isDir2 = (await stat2(path3)).isDirectory();
9165
+ isDir2 = (await stat3(path3)).isDirectory();
8925
9166
  } catch {
8926
9167
  }
8927
9168
  if (!isDir2) return { path: raw, exists: false, branch: null };
8928
9169
  let dotDir = false;
8929
9170
  try {
8930
- dotDir = (await stat2(join11(path3, ".git"))).isDirectory();
9171
+ dotDir = (await stat3(join12(path3, ".git"))).isDirectory();
8931
9172
  } catch {
8932
9173
  }
8933
- if (dotDir) return { path: raw, exists: true, branch: await headBranch(join11(path3, ".git")) };
9174
+ if (dotDir) return { path: raw, exists: true, branch: await headBranch(join12(path3, ".git")) };
8934
9175
  const deadline = Date.now() + GIT_TIMEOUT_MS;
8935
9176
  const bounded = (args) => new Promise((res) => {
8936
9177
  const t = setTimeout(() => res(null), Math.max(0, deadline - Date.now()));
@@ -8962,7 +9203,7 @@ var init_repos2 = __esm({
8962
9203
 
8963
9204
  // src/daemon/scan.ts
8964
9205
  import { readdir as fsReaddir } from "fs/promises";
8965
- import { dirname as dirname4, isAbsolute, join as join12, resolve as resolve3, sep as sep2 } from "path";
9206
+ import { dirname as dirname4, isAbsolute, join as join13, resolve as resolve3, sep as sep2 } from "path";
8966
9207
  function extraRoots(home2, cwds) {
8967
9208
  const h = resolve3(home2);
8968
9209
  const out = [];
@@ -9008,19 +9249,19 @@ function originUrl(config) {
9008
9249
  }
9009
9250
  return url || void 0;
9010
9251
  }
9011
- async function remoteOf(dir, dotIsFile, readFile19) {
9252
+ async function remoteOf(dir, dotIsFile, readFile20) {
9012
9253
  try {
9013
- let gitDir = join12(dir, ".git");
9254
+ let gitDir = join13(dir, ".git");
9014
9255
  if (dotIsFile) {
9015
- const m = /^gitdir:\s*(.+)$/m.exec(await readFile19(gitDir));
9256
+ const m = /^gitdir:\s*(.+)$/m.exec(await readFile20(gitDir));
9016
9257
  if (!m) return void 0;
9017
9258
  gitDir = resolve3(dir, m[1].trim());
9018
9259
  try {
9019
- gitDir = resolve3(gitDir, (await readFile19(join12(gitDir, "commondir"))).trim());
9260
+ gitDir = resolve3(gitDir, (await readFile20(join13(gitDir, "commondir"))).trim());
9020
9261
  } catch {
9021
9262
  }
9022
9263
  }
9023
- const url = originUrl(await readFile19(join12(gitDir, "config")));
9264
+ const url = originUrl(await readFile20(join13(gitDir, "config")));
9024
9265
  return url ? repositoryId(url) : void 0;
9025
9266
  } catch {
9026
9267
  return void 0;
@@ -9037,18 +9278,18 @@ function manifestName(file, text7) {
9037
9278
  } else name = /<artifactId>\s*([^<]+?)\s*<\/artifactId>/.exec(text7.replace(/<!--[\s\S]*?-->/g, "").replace(/<parent>[\s\S]*?<\/parent>/g, ""))?.[1];
9038
9279
  return typeof name === "string" && name.trim() && name.length <= 200 ? name.trim() : void 0;
9039
9280
  }
9040
- async function describe(dir, dotIsFile, home2, readFile19, manifest) {
9281
+ async function describe(dir, dotIsFile, home2, readFile20, manifest) {
9041
9282
  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
9283
+ dotIsFile ? inspectRepos([dir], { home: home2 }).then((r) => r[0]?.branch ?? null) : headBranch(join13(dir, ".git")),
9284
+ remoteOf(dir, dotIsFile, readFile20),
9285
+ manifest ? readFile20(join13(dir, manifest)).then((text7) => manifestName(manifest, text7), () => void 0) : void 0
9045
9286
  ]);
9046
9287
  return { path: dir, branch, ...remote ? { remote } : {}, ...named ? { manifest: named } : {} };
9047
9288
  }
9048
9289
  async function scanRepos(opts) {
9049
9290
  const now2 = opts.now ?? Date.now;
9050
9291
  const readdir5 = opts.readdir ?? ((p) => fsReaddir(p, { withFileTypes: true }));
9051
- const readFile19 = opts.readFile ?? ((p) => readGitFile(p));
9292
+ const readFile20 = opts.readFile ?? ((p) => readGitFile(p));
9052
9293
  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
9294
  const deadline = now2() + ms;
9054
9295
  let timer2;
@@ -9083,11 +9324,11 @@ async function scanRepos(opts) {
9083
9324
  if (!entries) return;
9084
9325
  const dot = entries.find((e) => e.name === ".git" && (e.isDirectory() || e.isFile()));
9085
9326
  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));
9327
+ if (dot) found.push(describe(d.path, dot.isFile(), opts.home, readFile20, manifest));
9087
9328
  if (dot && d.path !== home2 || d.depth >= depth) return;
9088
9329
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP.has(e.name)).map((e) => e.name).sort();
9089
9330
  for (const name of names) {
9090
- const path3 = join12(d.path, name);
9331
+ const path3 = join13(d.path, name);
9091
9332
  if (!seen.has(path3)) {
9092
9333
  seen.add(path3);
9093
9334
  queue.push({ path: path3, depth: d.depth + 1 });
@@ -9131,23 +9372,23 @@ var init_scan2 = __esm({
9131
9372
  });
9132
9373
 
9133
9374
  // 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";
9375
+ import { mkdir as mkdir8, readdir as readdir3, stat as stat4 } from "fs/promises";
9376
+ import { homedir as homedir7 } from "os";
9377
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join14, resolve as resolve4 } from "path";
9137
9378
  async function resolveExact(raw, home2) {
9138
9379
  const expanded = raw === "~" ? home2 : raw.startsWith("~/") ? resolve4(home2, raw.slice(2)) : raw;
9139
9380
  if (!isAbsolute2(expanded)) throw new Error(`not an absolute path: ${raw}`);
9140
9381
  const path3 = resolve4(expanded);
9141
9382
  let s;
9142
9383
  try {
9143
- s = await stat3(path3);
9384
+ s = await stat4(path3);
9144
9385
  } catch {
9145
9386
  throw new Error(`no such folder: ${path3}`);
9146
9387
  }
9147
9388
  if (!s.isDirectory()) throw new Error(`not a folder: ${path3}`);
9148
9389
  return path3;
9149
9390
  }
9150
- async function resolveFolder(input, home2 = homedir6()) {
9391
+ async function resolveFolder(input, home2 = homedir7()) {
9151
9392
  return resolveExact(input.trim(), home2);
9152
9393
  }
9153
9394
  async function subfolders(path3) {
@@ -9156,7 +9397,7 @@ async function subfolders(path3) {
9156
9397
  if (d.isDirectory()) return d.name;
9157
9398
  if (!d.isSymbolicLink()) return null;
9158
9399
  try {
9159
- return (await stat3(join13(path3, d.name))).isDirectory() ? d.name : null;
9400
+ return (await stat4(join14(path3, d.name))).isDirectory() ? d.name : null;
9160
9401
  } catch {
9161
9402
  return null;
9162
9403
  }
@@ -9164,42 +9405,42 @@ async function subfolders(path3) {
9164
9405
  return found.filter((n) => n !== null).sort(byName.compare);
9165
9406
  }
9166
9407
  async function gitBranchOf(dir, git) {
9167
- const dot = join13(dir, ".git");
9408
+ const dot = join14(dir, ".git");
9168
9409
  let s;
9169
9410
  try {
9170
- s = await stat3(dot);
9411
+ s = await stat4(dot);
9171
9412
  } catch {
9172
9413
  return null;
9173
9414
  }
9174
9415
  if (s.isFile()) return (await inspectRepos([dir], { git }))[0]?.branch ?? null;
9175
9416
  return s.isDirectory() ? headBranch(dot) : null;
9176
9417
  }
9177
- async function listFolder(input, home2 = homedir6(), requestId3, git) {
9418
+ async function listFolder(input, home2 = homedir7(), requestId3, git) {
9178
9419
  const tag = requestId3 === void 0 ? {} : { requestId: requestId3 };
9179
9420
  const asked2 = input || "~";
9180
9421
  try {
9181
9422
  const path3 = await resolveExact(asked2, home2);
9182
9423
  const parent = dirname5(path3);
9183
9424
  const entries = await subfolders(path3);
9184
- const branches = await Promise.all(entries.map((name) => gitBranchOf(join13(path3, name), git)));
9425
+ const branches = await Promise.all(entries.map((name) => gitBranchOf(join14(path3, name), git)));
9185
9426
  const repos = entries.flatMap((name, i) => branches[i] === null ? [] : [{ name, branch: branches[i] }]);
9186
9427
  return { type: "folder.listing", path: path3, parent: parent === path3 ? null : parent, entries, repos, ...tag };
9187
9428
  } catch (e) {
9188
9429
  return { type: "folder.listing", path: asked2, parent: null, entries: [], error: e.message, ...tag };
9189
9430
  }
9190
9431
  }
9191
- async function createFolder(parent, name, home2 = homedir6(), requestId3) {
9432
+ async function createFolder(parent, name, home2 = homedir7(), requestId3) {
9192
9433
  const at = await listFolder(parent, home2, requestId3);
9193
9434
  if (at.error) return at;
9194
9435
  const clean = name.trim();
9195
9436
  if (!clean || clean === "." || clean === ".." || /[/\0]/.test(clean)) return { ...at, error: `invalid folder name: ${name}` };
9196
9437
  try {
9197
- await mkdir8(join13(at.path, clean));
9438
+ await mkdir8(join14(at.path, clean));
9198
9439
  } catch (e) {
9199
9440
  const code3 = e.code;
9200
9441
  return { ...at, error: code3 === "EEXIST" ? `already exists: ${clean}` : `cannot create ${clean}: ${e.message}` };
9201
9442
  }
9202
- return listFolder(join13(at.path, clean), home2, requestId3);
9443
+ return listFolder(join14(at.path, clean), home2, requestId3);
9203
9444
  }
9204
9445
  var byName;
9205
9446
  var init_folders = __esm({
@@ -9263,7 +9504,7 @@ function taskOf2(ref, ctx) {
9263
9504
  return taskClosed(t) ? no("closed", `task ${t.id} is closed`) : t;
9264
9505
  }
9265
9506
  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;
9507
+ 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
9508
  }
9268
9509
  function cardRefusal(row3) {
9269
9510
  if (row3.action !== "session.decide" || !row3.detailCut || row3.args.decision !== "accept") return null;
@@ -9316,6 +9557,7 @@ var init_actions2 = __esm({
9316
9557
  "src/daemon/actions.ts"() {
9317
9558
  "use strict";
9318
9559
  init_src();
9560
+ init_seats();
9319
9561
  DECIDABLE = ["permission", "diff", "plan"];
9320
9562
  KIND = { permission: "permission", diff: "diff", plan: "plan" };
9321
9563
  no = (code3, refused) => ({ refused, code: code3 });
@@ -9435,13 +9677,13 @@ ${p.body}` : p.text) }
9435
9677
  });
9436
9678
 
9437
9679
  // 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";
9680
+ import { access, constants as constants2, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
9681
+ import { delimiter, join as join15 } from "path";
9440
9682
  import { fileURLToPath as fileURLToPath2 } from "url";
9441
9683
  async function readGuide(files = GUIDE_FILES) {
9442
9684
  for (const f of files) {
9443
9685
  try {
9444
- return await readFile7(f, "utf8");
9686
+ return await readFile8(f, "utf8");
9445
9687
  } catch {
9446
9688
  }
9447
9689
  }
@@ -9453,7 +9695,7 @@ async function installedAgents(path3) {
9453
9695
  for (const tool of ToolSchema.options) {
9454
9696
  for (const d of dirs) {
9455
9697
  try {
9456
- await access(join14(d, AGENT_BIN2[tool]), constants2.X_OK);
9698
+ await access(join15(d, AGENT_BIN2[tool]), constants2.X_OK);
9457
9699
  found.push(tool);
9458
9700
  break;
9459
9701
  } catch {
@@ -9485,7 +9727,7 @@ function ledgeMd(info) {
9485
9727
  ].join("\n");
9486
9728
  }
9487
9729
  async function writeLedgeMd(dir, info) {
9488
- await writeFile8(join14(dir, LEDGE_MD), ledgeMd(info));
9730
+ await writeFile8(join15(dir, LEDGE_MD), ledgeMd(info));
9489
9731
  }
9490
9732
  var LEDGE_MD, GUIDE_FILES, AGENT_BIN2, RISK;
9491
9733
  var init_guide = __esm({
@@ -9500,8 +9742,8 @@ var init_guide = __esm({
9500
9742
  });
9501
9743
 
9502
9744
  // 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";
9745
+ import { open as open3, readdir as readdir4, stat as stat5, writeFile as writeFile9 } from "fs/promises";
9746
+ import { join as join16 } from "path";
9505
9747
  function scalarValue(raw) {
9506
9748
  const v2 = raw.trim();
9507
9749
  const q = v2[0];
@@ -9533,7 +9775,7 @@ function copyName(file, taken) {
9533
9775
  return name;
9534
9776
  }
9535
9777
  async function head(path3, max) {
9536
- const fh = await open2(path3, "r");
9778
+ const fh = await open3(path3, "r");
9537
9779
  try {
9538
9780
  const buf2 = Buffer.alloc(max);
9539
9781
  const { bytesRead } = await fh.read(buf2, 0, max, 0);
@@ -9552,12 +9794,12 @@ async function writeNotes(dir, from, steps, max = NOTE_MAX2) {
9552
9794
  if (!files.length) lines.push(`- (none: the user can add Markdown files to \`${from}\`, with an optional front matter \`title\` and \`when\`)`);
9553
9795
  const taken = /* @__PURE__ */ new Set();
9554
9796
  for (const file of files.slice(0, NOTES_MAX)) {
9555
- const path3 = join15(from, file);
9797
+ const path3 = join16(from, file);
9556
9798
  let text7;
9557
9799
  let size;
9558
9800
  let mtime;
9559
9801
  try {
9560
- const st = await stat4(path3);
9802
+ const st = await stat5(path3);
9561
9803
  size = st.size;
9562
9804
  mtime = st.mtime.toISOString();
9563
9805
  text7 = await head(path3, max);
@@ -9574,15 +9816,15 @@ async function writeNotes(dir, from, steps, max = NOTE_MAX2) {
9574
9816
  const when = fields.when?.trim() ? `when: ${line(fields.when)}` : `first line: ${line(body.split("\n").find((l) => l.trim()) ?? "") || "(empty)"}`;
9575
9817
  const truncated = size > max;
9576
9818
  const name = copyName(file, taken);
9577
- await writeFile9(join15(dir, name), truncated ? `${text7}
9819
+ await writeFile9(join16(dir, name), truncated ? `${text7}
9578
9820
 
9579
9821
  [truncated by Ledge: the first ${max} of ${size} bytes]
9580
9822
  ` : text7);
9581
- steps.set(name, { kind: "repo", name: file, path: join15(from, file) });
9823
+ steps.set(name, { kind: "repo", name: file, path: join16(from, file) });
9582
9824
  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
9825
  }
9584
9826
  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"));
9827
+ await writeFile9(join16(dir, NOTES_INDEX), [...lines, ""].join("\n"));
9586
9828
  }
9587
9829
  var NOTE_MAX2, NOTES_MAX, NOTES_INDEX, unquote, line;
9588
9830
  var init_notes = __esm({
@@ -9601,8 +9843,8 @@ var init_notes = __esm({
9601
9843
 
9602
9844
  // src/chat/context.ts
9603
9845
  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";
9846
+ import { homedir as homedir8 } from "os";
9847
+ import { basename as basename8, join as join17, resolve as resolve5 } from "path";
9606
9848
  async function writeContext(dir, input) {
9607
9849
  await rm2(dir, { recursive: true, force: true });
9608
9850
  await mkdir9(dir, { recursive: true });
@@ -9631,29 +9873,29 @@ async function writeContext(dir, input) {
9631
9873
  const pane = await input.capture(s).catch(() => null);
9632
9874
  if (pane?.length) {
9633
9875
  const f = `pane-${s.id}.txt`;
9634
- await writeFile10(join16(dir, f), pane.join("\n"));
9876
+ await writeFile10(join17(dir, f), pane.join("\n"));
9635
9877
  steps.set(f, { kind: "pane", id: s.id, name: s.name });
9636
9878
  lines.push(`- terminal, last lines: ${f}`);
9637
9879
  }
9638
9880
  if (s.lastReply?.text.trim()) {
9639
9881
  const f = `reply-${s.id}.md`;
9640
- await writeFile10(join16(dir, f), s.lastReply.text);
9882
+ await writeFile10(join17(dir, f), s.lastReply.text);
9641
9883
  steps.set(f, { kind: "reply", id: s.id, name: s.name });
9642
9884
  lines.push(`- last reply (${s.lastReply.at}): ${f}`);
9643
9885
  }
9644
9886
  const g = await input.git(s.cwd).catch(() => null);
9645
9887
  if (g) {
9646
9888
  const f = `git-${s.id}.txt`;
9647
- await writeFile10(join16(dir, f), g);
9889
+ await writeFile10(join17(dir, f), g);
9648
9890
  steps.set(f, { kind: "git", id: s.id, name: s.name });
9649
9891
  lines.push(`- git status, log and diff stat: ${f}`);
9650
9892
  }
9651
9893
  lines.push("");
9652
9894
  }
9653
- await writeFile10(join16(dir, "sessions.md"), lines.join("\n"));
9895
+ await writeFile10(join17(dir, "sessions.md"), lines.join("\n"));
9654
9896
  steps.set("sessions.md", { kind: "sessions" });
9655
9897
  const recents = input.recents ?? [];
9656
- await writeFile10(join16(dir, "folders.md"), [
9898
+ await writeFile10(join17(dir, "folders.md"), [
9657
9899
  "# Recent folders",
9658
9900
  "",
9659
9901
  "The folders the user started sessions in, most recent first.",
@@ -9661,7 +9903,7 @@ async function writeContext(dir, input) {
9661
9903
  ...recents.length ? recents.map((r) => `- ${r.project}: ${r.cwd} (last used ${r.lastAt})`) : ["- (none yet)"],
9662
9904
  ""
9663
9905
  ].join("\n"));
9664
- if (withProjects) await writeFile10(join16(dir, "projects.md"), [
9906
+ if (withProjects) await writeFile10(join17(dir, "projects.md"), [
9665
9907
  "# Projects",
9666
9908
  "",
9667
9909
  ...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 +9921,7 @@ async function writeContext(dir, input) {
9679
9921
  async function writeTasksContext(dir, taskList, steps) {
9680
9922
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9681
9923
  const shown = taskList.filter((t) => !taskClosed(t) || t.updatedAt.slice(0, 10) === day);
9682
- await writeFile10(join16(dir, "tasks.md"), [
9924
+ await writeFile10(join17(dir, "tasks.md"), [
9683
9925
  "# Tasks",
9684
9926
  "",
9685
9927
  "Open tasks and those closed today. A giver `Ledge` is the user, through you.",
@@ -9691,7 +9933,7 @@ async function writeTasksContext(dir, taskList, steps) {
9691
9933
  }
9692
9934
  function coversHome(path3) {
9693
9935
  const p = resolve5(path3);
9694
- const home2 = resolve5(homedir7());
9936
+ const home2 = resolve5(homedir8());
9695
9937
  return p === home2 || home2.startsWith(p.endsWith("/") ? p : `${p}/`);
9696
9938
  }
9697
9939
  function stepFor(path3, ctx) {
@@ -9730,14 +9972,14 @@ var LEDGE_VERSION;
9730
9972
  var init_version = __esm({
9731
9973
  "src/version.ts"() {
9732
9974
  "use strict";
9733
- LEDGE_VERSION = true ? "0.40.0" : "0.0.0-dev";
9975
+ LEDGE_VERSION = true ? "0.41.0-snapshot.41719405620.1" : "0.0.0-dev";
9734
9976
  }
9735
9977
  });
9736
9978
 
9737
9979
  // src/chat/forge.ts
9738
9980
  import { execFile as execFile4 } from "child_process";
9739
9981
  import { writeFile as writeFile11 } from "fs/promises";
9740
- import { join as join17 } from "path";
9982
+ import { join as join18 } from "path";
9741
9983
  function forgeOf(remote) {
9742
9984
  const id = repositoryId(remote);
9743
9985
  if (!id) return null;
@@ -9802,12 +10044,12 @@ function createForge(deps = {}) {
9802
10044
  return;
9803
10045
  }
9804
10046
  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"));
10047
+ await writeFile11(join18(ctx.dir, file), parts2.flatMap((s) => s.lines).join("\n"));
9806
10048
  const sources = parts2.flatMap((s) => s.source ? [s.source] : []);
9807
10049
  ctx.steps.set(file, { kind: "repo", name, path: sources.join(", ") || file });
9808
10050
  index2.push(`- ${name}: ${file}`);
9809
10051
  }));
9810
- await writeFile11(join17(ctx.dir, "forge.md"), [...index2, ""].join("\n"));
10052
+ await writeFile11(join18(ctx.dir, "forge.md"), [...index2, ""].join("\n"));
9811
10053
  }
9812
10054
  };
9813
10055
  }
@@ -10074,7 +10316,7 @@ var init_codex2 = __esm({
10074
10316
  // src/chat/cursor.ts
10075
10317
  import { spawn as nodeSpawn5 } from "child_process";
10076
10318
  import { copyFileSync, writeFileSync } from "fs";
10077
- import { extname, join as join18 } from "path";
10319
+ import { extname, join as join19 } from "path";
10078
10320
  function cursorArgs(o) {
10079
10321
  return [
10080
10322
  "-p",
@@ -10116,10 +10358,10 @@ function parseCursorLine(line2) {
10116
10358
  }
10117
10359
  function runCursor(o, onEvent, spawnImpl = nodeSpawn5) {
10118
10360
  const [file = "cursor-agent", ...pre] = o.bin ?? ["cursor-agent"];
10119
- writeFileSync(join18(o.cwd, "AGENTS.md"), `${o.persona ?? buildPersona("cursor")}
10361
+ writeFileSync(join19(o.cwd, "AGENTS.md"), `${o.persona ?? buildPersona("cursor")}
10120
10362
  `);
10121
10363
  const copies = (o.images ?? []).map((img, n) => {
10122
- const to = join18(o.cwd, `image-${n + 1}${extname(img.path)}`);
10364
+ const to = join19(o.cwd, `image-${n + 1}${extname(img.path)}`);
10123
10365
  copyFileSync(img.path, to);
10124
10366
  return to;
10125
10367
  });
@@ -10203,15 +10445,15 @@ var init_omp = __esm({
10203
10445
 
10204
10446
  // src/chat/service.ts
10205
10447
  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";
10209
- function droppedNote(total, kept, reasons, counted) {
10210
- const dropped = total - kept;
10448
+ import { mkdir as mkdir10, readFile as readFile9, rm as rm3, writeFile as writeFile12 } from "fs/promises";
10449
+ import { homedir as homedir9 } from "os";
10450
+ import { basename as basename9, dirname as dirname6, join as join20, resolve as resolve6 } from "path";
10451
+ function droppedNote(total, kept2, reasons, counted) {
10452
+ const dropped = total - kept2;
10211
10453
  if (dropped <= 0) return "";
10212
10454
  const rest = dropped - counted;
10213
10455
  const why = reasons.length === 0 ? [] : [...reasons, ...rest > 0 ? [`${rest} autre${rest > 1 ? "s" : ""}`] : []];
10214
- const head2 = total === 1 ? "Note : la carte ne reprend pas l\u2019action propos\xE9e" : kept === 0 ? `Note : la carte ne reprend aucune des ${total} actions propos\xE9es` : `Note : la carte ne reprend que ${kept} des ${total} actions propos\xE9es`;
10456
+ const head2 = total === 1 ? "Note : la carte ne reprend pas l\u2019action propos\xE9e" : kept2 === 0 ? `Note : la carte ne reprend aucune des ${total} actions propos\xE9es` : `Note : la carte ne reprend que ${kept2} des ${total} actions propos\xE9es`;
10215
10457
  return why.length ? `${head2} (${why.join(" ; ")}).` : `${head2}.`;
10216
10458
  }
10217
10459
  function tooWide(path3, userHome) {
@@ -10226,7 +10468,7 @@ function createChat(deps) {
10226
10468
  const now2 = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10227
10469
  const today = deps.today ?? (() => now2().slice(0, 10));
10228
10470
  const uuid = deps.uuid ?? randomUUID7;
10229
- const dayFile = join19(deps.home, "chat.json");
10471
+ const dayFile = join20(deps.home, "chat.json");
10230
10472
  let turns2 = [];
10231
10473
  let turnsDay = "";
10232
10474
  let busy = false;
@@ -10249,7 +10491,7 @@ function createChat(deps) {
10249
10491
  const day = today();
10250
10492
  if (!fresh && !freshNext) {
10251
10493
  try {
10252
- const saved = JSON.parse(await readFile8(dayFile, "utf8"));
10494
+ const saved = JSON.parse(await readFile9(dayFile, "utf8"));
10253
10495
  if (saved.day === day && saved.sessionId && (saved.tool ?? "claude") === tool) return { sessionId: saved.sessionId, resume: true };
10254
10496
  } catch {
10255
10497
  }
@@ -10265,7 +10507,7 @@ function createChat(deps) {
10265
10507
  const p = await deps.tmux.capture(s.tmuxSession, PANE_LINES);
10266
10508
  return p === null ? null : p.split("\n");
10267
10509
  };
10268
- const contextDir = join19(deps.home, "chat", "context");
10510
+ const contextDir = join20(deps.home, "chat", "context");
10269
10511
  async function proposalFrom(raw, tool, qid) {
10270
10512
  const parsed = parseActionBlock("session", raw);
10271
10513
  if ("error" in parsed) {
@@ -10342,7 +10584,7 @@ function createChat(deps) {
10342
10584
  for (const r of block2.repos) {
10343
10585
  try {
10344
10586
  const path3 = await resolveFolder(r.path, deps.userHome);
10345
- const wide = tooWide(path3, resolve6(deps.userHome ?? homedir8()));
10587
+ const wide = tooWide(path3, resolve6(deps.userHome ?? homedir9()));
10346
10588
  if (wide) {
10347
10589
  log(`chat ${qid}: project path dropped: ${wide}: ${path3}`);
10348
10590
  continue;
@@ -10458,7 +10700,7 @@ function createChat(deps) {
10458
10700
  let resultError = null;
10459
10701
  const handle2 = runTool(tool, { question: mine.told.text ? `${mine.told.text}
10460
10702
 
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) => {
10703
+ ${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
10704
  if (e.type === "tool") {
10463
10705
  const step3 = e.path ? stepFor(e.path, ctx) : null;
10464
10706
  if (step3 && !turn.steps.some((s) => sameStep(s, step3))) {
@@ -10523,8 +10765,8 @@ ${turn.question}` : turn.question, cwd: ctx.dir, addDirs: ctx.repoPaths, session
10523
10765
  if (taskBlocks.length > MAX_PROPOSALS) log(`chat ${turn.qid}: ${taskBlocks.length - MAX_PROPOSALS} tasks beyond ${MAX_PROPOSALS} ignored`);
10524
10766
  const tasks = tasksFrom(taskBlocks.slice(0, MAX_PROPOSALS), turn.qid);
10525
10767
  const { rows: actions, reasons, counted } = actionsFrom(generic, turn.qid, new Set(closes.map((c) => c.id)));
10526
- const kept = proposals.length + (relay ? 1 : 0) + closes.length + (projectProposal ? 1 : 0) + tasks.length + actions.length;
10527
- const note = droppedNote(found.length, kept, [...reasons, ...unreadable > 0 ? [`${unreadable} illisible${unreadable > 1 ? "s" : ""}`] : []], counted + unreadable);
10768
+ const kept2 = proposals.length + (relay ? 1 : 0) + closes.length + (projectProposal ? 1 : 0) + tasks.length + actions.length;
10769
+ const note = droppedNote(found.length, kept2, [...reasons, ...unreadable > 0 ? [`${unreadable} illisible${unreadable > 1 ? "s" : ""}`] : []], counted + unreadable);
10528
10770
  const text7 = note ? answerText ? `${answerText}
10529
10771
 
10530
10772
  ${note}` : note : answerText;
@@ -10745,8 +10987,8 @@ var init_service = __esm({
10745
10987
  });
10746
10988
 
10747
10989
  // src/chat/threads.ts
10748
- import { readFile as readFile9, rm as rm4 } from "fs/promises";
10749
- import { join as join20 } from "path";
10990
+ import { readFile as readFile10, rm as rm4 } from "fs/promises";
10991
+ import { join as join21 } from "path";
10750
10992
  function createSlots(max = 3) {
10751
10993
  let used = 0;
10752
10994
  return {
@@ -10767,7 +11009,7 @@ function createChatThreads(deps) {
10767
11009
  const devices = /* @__PURE__ */ new Map();
10768
11010
  const emitters = /* @__PURE__ */ new Map();
10769
11011
  const dropping = /* @__PURE__ */ new Map();
10770
- const deviceHome = (id) => join20(deps.home, "mobile", "chat", id);
11012
+ const deviceHome = (id) => join21(deps.home, "mobile", "chat", id);
10771
11013
  const device = (id) => {
10772
11014
  checkId(id);
10773
11015
  if (dropping.has(id)) throw new Error("device thread is being dropped");
@@ -10796,7 +11038,7 @@ function createChatThreads(deps) {
10796
11038
  /** The PC's tool of the day file, whatever its day: `claude` without one. */
10797
11039
  async deviceTool() {
10798
11040
  try {
10799
- const t = JSON.parse(await readFile9(join20(deps.home, "chat.json"), "utf8")).tool;
11041
+ const t = JSON.parse(await readFile10(join21(deps.home, "chat.json"), "utf8")).tool;
10800
11042
  return t === "claude" || t === "codex" || t === "cursor" || t === "omp" ? t : "claude";
10801
11043
  } catch {
10802
11044
  return "claude";
@@ -10963,7 +11205,7 @@ var init_island2 = __esm({
10963
11205
 
10964
11206
  // src/daemon/documents.ts
10965
11207
  import { constants as constants3 } from "fs";
10966
- import { open as open3, realpath } from "fs/promises";
11208
+ import { open as open4, realpath } from "fs/promises";
10967
11209
  import { isAbsolute as isAbsolute3, dirname as dirname7, extname as extname2, resolve as resolve7 } from "path";
10968
11210
  async function readDocument(request2, context) {
10969
11211
  const source2 = request2.path;
@@ -10984,10 +11226,10 @@ async function readDocument(request2, context) {
10984
11226
  async function readAt(request2, path3) {
10985
11227
  let file;
10986
11228
  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");
11229
+ file = await open4(path3, constants3.O_RDONLY | constants3.O_NONBLOCK);
11230
+ const stat8 = await file.stat();
11231
+ if (!stat8.isFile()) return failure(request2, "not_file");
11232
+ if (stat8.size > DOCUMENT_MAX_BYTES) return failure(request2, "too_large");
10991
11233
  const chunk = Buffer.allocUnsafe(64 * 1024);
10992
11234
  const parts2 = [];
10993
11235
  let bytes4 = 0;
@@ -11010,7 +11252,7 @@ async function readAt(request2, path3) {
11010
11252
  path: await realpath(path3),
11011
11253
  text: text7,
11012
11254
  bytes: bytes4,
11013
- modifiedAt: stat7.mtime.toISOString()
11255
+ modifiedAt: stat8.mtime.toISOString()
11014
11256
  };
11015
11257
  } catch (e) {
11016
11258
  return failure(request2, errorCode(e));
@@ -11039,9 +11281,9 @@ var init_documents2 = __esm({
11039
11281
 
11040
11282
  // src/daemon/models.ts
11041
11283
  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";
11284
+ import { readFile as readFile11 } from "fs/promises";
11285
+ import { homedir as homedir10 } from "os";
11286
+ import { join as join22 } from "path";
11045
11287
  function claudeModels(settingsModel) {
11046
11288
  const efforts = [...CLAUDE_EFFORTS];
11047
11289
  const models = CLAUDE_MODELS.map((m) => ({ ...m, efforts, ...m.id === settingsModel ? { isDefault: true } : {} }));
@@ -11085,15 +11327,15 @@ function codexConfig(toml) {
11085
11327
  function createModels(deps = {}) {
11086
11328
  const env = deps.env ?? (async () => process.env);
11087
11329
  const run4 = deps.run ?? execRun2(env);
11088
- const readText2 = deps.readText ?? ((p) => readFile10(p, "utf8").catch(() => ""));
11330
+ const readText2 = deps.readText ?? ((p) => readFile11(p, "utf8").catch(() => ""));
11089
11331
  const now2 = deps.now ?? Date.now, ttl = deps.ttlMs ?? MODELS_TTL_MS;
11090
11332
  const cache = /* @__PURE__ */ new Map();
11091
11333
  const flying = /* @__PURE__ */ new Map();
11092
11334
  async function ask3(tool) {
11093
11335
  const e = await env();
11094
- const home2 = e.HOME ?? homedir9();
11336
+ const home2 = e.HOME ?? homedir10();
11095
11337
  if (tool === "claude") {
11096
- const settings = await readText2(join21(e.CLAUDE_CONFIG_DIR ?? join21(home2, ".claude"), "settings.json"));
11338
+ const settings = await readText2(join22(e.CLAUDE_CONFIG_DIR ?? join22(home2, ".claude"), "settings.json"));
11097
11339
  let model;
11098
11340
  try {
11099
11341
  model = settings ? JSON.parse(settings).model : void 0;
@@ -11109,7 +11351,7 @@ function createModels(deps = {}) {
11109
11351
  if (r.code === null) throw new Error("not installed");
11110
11352
  if (r.code !== 0) throw new Error(r.stderr.trim().split("\n").pop() || `exit ${r.code}`);
11111
11353
  if (tool === "cursor") return parseCursorModels(r.stdout);
11112
- return parseCodexModels(r.stdout, codexConfig(await readText2(join21(e.CODEX_HOME ?? join21(home2, ".codex"), "config.toml"))));
11354
+ return parseCodexModels(r.stdout, codexConfig(await readText2(join22(e.CODEX_HOME ?? join22(home2, ".codex"), "config.toml"))));
11113
11355
  }
11114
11356
  return {
11115
11357
  list(tool) {
@@ -11160,9 +11402,9 @@ var init_models2 = __esm({
11160
11402
 
11161
11403
  // src/daemon/commands.ts
11162
11404
  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";
11405
+ import { stat as stat6 } from "fs/promises";
11406
+ import { homedir as homedir11 } from "os";
11407
+ import { join as join23 } from "path";
11166
11408
  function claudeSlashRefusal(text7) {
11167
11409
  const m = /^\/(\S+)(\s+\S[\s\S]*)?$/.exec(text7.trim());
11168
11410
  if (!m) return null;
@@ -11271,13 +11513,13 @@ function probeClaude(o, spawnImpl = nodeSpawn7) {
11271
11513
  function createCommands(deps = {}) {
11272
11514
  const env = deps.env ?? (async () => process.env);
11273
11515
  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));
11516
+ const mtime = deps.mtime ?? ((p) => stat6(p).then((s) => s.mtimeMs, () => null));
11275
11517
  const now2 = deps.now ?? Date.now, ttl = deps.ttlMs ?? COMMANDS_TTL_MS;
11276
11518
  const cache = /* @__PURE__ */ new Map();
11277
11519
  const flying = /* @__PURE__ */ new Map();
11278
11520
  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")];
11521
+ const home2 = e.CLAUDE_CONFIG_DIR ?? join23(e.HOME ?? homedir11(), ".claude");
11522
+ const paths2 = [join23(cwd, ".claude", "commands"), join23(cwd, ".claude", "skills"), join23(home2, "commands"), join23(home2, "skills"), join23(home2, "plugins", "installed_plugins.json")];
11281
11523
  return (await Promise.all(paths2.map(mtime))).join(",");
11282
11524
  }
11283
11525
  return {
@@ -11455,7 +11697,7 @@ var init_rigSpec = __esm({
11455
11697
  });
11456
11698
 
11457
11699
  // 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";
11700
+ import { basename as basename10, dirname as dirname8, isAbsolute as isAbsolute5, join as join24, resolve as resolve9, sep as sep3 } from "path";
11459
11701
  import { parse as parseYaml, stringify as stringify2, YAMLParseError } from "yaml";
11460
11702
  function readLibrary(listed2) {
11461
11703
  const out = [];
@@ -11487,15 +11729,15 @@ function parseSpec(text7) {
11487
11729
  }
11488
11730
  }
11489
11731
  function specPath(p, specDir, home2, openrigHome) {
11490
- if (p.startsWith("openrig-home:")) return join23(openrigHome, p.slice("openrig-home:".length));
11732
+ if (p.startsWith("openrig-home:")) return join24(openrigHome, p.slice("openrig-home:".length));
11491
11733
  if (p === "~") return home2;
11492
- if (p.startsWith("~/")) return join23(home2, p.slice(2));
11734
+ if (p.startsWith("~/")) return join24(home2, p.slice(2));
11493
11735
  return isAbsolute5(p) ? resolve9(p) : resolve9(specDir, p);
11494
11736
  }
11495
11737
  function importPath(ref, specDir) {
11496
11738
  const p = ref.startsWith("local:") ? resolve9(specDir, ref.slice(6)) : ref.startsWith("path:") ? resolve9(ref.slice(5)) : null;
11497
11739
  if (!p) return null;
11498
- return /\.ya?ml$/i.test(p) ? p : join23(p, "agent.yaml");
11740
+ return /\.ya?ml$/i.test(p) ? p : join24(p, "agent.yaml");
11499
11741
  }
11500
11742
  function declared(spec, ctx, ns, origin) {
11501
11743
  const out = [];
@@ -11724,7 +11966,7 @@ function layerOf(ownerRoot, path3, c) {
11724
11966
  if (c.agentDir && root2 === c.agentDir) return c.profileFiles.has(path3) ? "profile" : "agent";
11725
11967
  if (c.rigRoot && root2 === c.rigRoot) return path3 === c.cultureFile ? "culture" : c.memberFiles.has(path3) ? "member" : c.podFiles.has(path3) ? "pod" : "rig";
11726
11968
  if (/[/\\]@openrig[/\\]cli[/\\]/.test(root2) || /[/\\]daemon[/\\]assets$/.test(root2)) return "openrig";
11727
- if (under3(root2, join23(c.openrigHome, "state", "seats"))) return "seat";
11969
+ if (under3(root2, join24(c.openrigHome, "state", "seats"))) return "seat";
11728
11970
  return "unknown";
11729
11971
  }
11730
11972
  function layerOrigin(layer, c, path3) {
@@ -11734,7 +11976,7 @@ function layerOrigin(layer, c, path3) {
11734
11976
  case "profile":
11735
11977
  return { kind: "here", key: "profiles.startup.files" };
11736
11978
  case "culture":
11737
- return { kind: "inherited", from: "rig", ref: c.rig, key: "culture_file", ...c.rigRoot ? { path: join23(c.rigRoot, path3) } : {} };
11979
+ return { kind: "inherited", from: "rig", ref: c.rig, key: "culture_file", ...c.rigRoot ? { path: join24(c.rigRoot, path3) } : {} };
11738
11980
  case "rig":
11739
11981
  return { kind: "inherited", from: "rig", ref: c.rig };
11740
11982
  case "pod":
@@ -11762,12 +12004,12 @@ async function seatView(s, spec, profile, io, openrigHome, plugins) {
11762
12004
  const cwdV = str10(node2.cwd) ?? str10(shown.cwd);
11763
12005
  const cwd = cwdV ? { value: cwdV, origin: memberOrigin("cwd") } : { value: null, origin: { kind: "unknown", why: "not_exposed" } };
11764
12006
  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) } : {} });
12007
+ const cultureFile = sourced(cultureV, { kind: "inherited", from: "rig", ref: s.rig, key: "culture_file", ...cultureV && s.rigRoot ? { path: join24(s.rigRoot, cultureV) } : {} });
11766
12008
  const base3 = s.rigRoot ?? (s.rigSpecPath ? dirname8(s.rigSpecPath) : "/");
11767
12009
  const memberStartup = writtenStartup(m.startup, "startup", "member", base3, (key) => memberOrigin(key));
11768
12010
  const pod = s.written?.pod ?? {};
11769
12011
  const effectiveRuntime = str10(node2.runtime) ?? runtime.value ?? void 0;
11770
- const into = cwdV ? join23(cwdV, guidanceFile(effectiveRuntime, s.rigDoc)) : void 0;
12012
+ const into = cwdV ? join24(cwdV, guidanceFile(effectiveRuntime, s.rigDoc)) : void 0;
11771
12013
  const profileName = str10(m.profile) ?? str10(shown.profile) ?? "default";
11772
12014
  const lc = {
11773
12015
  agentDir: s.agentDir,
@@ -12003,10 +12245,14 @@ var init_openrigRoles2 = __esm({
12003
12245
  import { execFile as execFile6 } from "child_process";
12004
12246
  import { get as httpGet } from "http";
12005
12247
  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";
12248
+ import { cp, mkdir as mkdir11, open as openFile, readFile as readFile12, stat as statFile, writeFile as writeFile13 } from "fs/promises";
12007
12249
  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";
12250
+ import { homedir as homedir12 } from "os";
12251
+ import { basename as basename11, delimiter as delimiter2, dirname as dirname9, isAbsolute as isAbsolute6, join as join25, resolve as resolve10, sep as sep4 } from "path";
12252
+ function specSource(raw) {
12253
+ const s = str11(raw);
12254
+ return s === "builtin" || s?.startsWith("builtin_") ? "builtin" : "user";
12255
+ }
12010
12256
  function toSeat(raw) {
12011
12257
  const n = obj9(raw);
12012
12258
  const logicalId = str11(n.logicalId), session = str11(n.canonicalSessionName);
@@ -12079,17 +12325,17 @@ function findRig(env, exists = existsSync2, list3 = (dir) => {
12079
12325
  return [];
12080
12326
  }
12081
12327
  }) {
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));
12328
+ for (const dir of (env.PATH ?? "").split(delimiter2)) if (dir && exists(join25(dir, "rig"))) return dir;
12329
+ const home2 = env.HOME ?? homedir12();
12330
+ const managed = (root2, sub) => list3(root2).filter((v2) => /^v?\d/.test(v2)).sort(byVersionDesc).map((v2) => join25(root2, v2, sub));
12085
12331
  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")
12332
+ ...managed(join25(home2, ".local", "share", "nvm"), "bin"),
12333
+ ...managed(join25(env.NVM_DIR ?? join25(home2, ".nvm"), "versions", "node"), "bin"),
12334
+ join25(home2, ".volta", "bin"),
12335
+ join25(home2, ".npm-global", "bin"),
12336
+ join25(home2, ".local", "share", "pnpm")
12091
12337
  ];
12092
- return candidates.find((dir) => exists(join24(dir, "rig"))) ?? null;
12338
+ return candidates.find((dir) => exists(join25(dir, "rig"))) ?? null;
12093
12339
  }
12094
12340
  function readCheck(output) {
12095
12341
  const j = obj9(parse2(output));
@@ -12118,11 +12364,11 @@ function localGet(port, path3, timeoutMs) {
12118
12364
  function createOpenRig(deps = {}) {
12119
12365
  const run4 = deps.run ?? execRun3(deps.env ?? (async () => process.env));
12120
12366
  const files = deps.fs ?? realFs;
12121
- const home2 = deps.home ?? homedir11();
12122
- const rigsDir = deps.rigsDir ?? join24(home2, ".ledge", "rigs");
12367
+ const home2 = deps.home ?? homedir12();
12368
+ const rigsDir = deps.rigsDir ?? join25(home2, ".ledge", "rigs");
12123
12369
  const now2 = deps.now ?? Date.now;
12124
12370
  const roleIo = deps.roleIo ?? realRoleIo;
12125
- const openrigHome = deps.openrigHome ?? process.env.OPENRIG_HOME ?? join24(home2, ".openrig");
12371
+ const openrigHome = deps.openrigHome ?? process.env.OPENRIG_HOME ?? join25(home2, ".openrig");
12126
12372
  const http = deps.http ?? (async (path3) => {
12127
12373
  const port = (await state2()).daemon.port;
12128
12374
  if (!port) return void 0;
@@ -12206,7 +12452,7 @@ function createOpenRig(deps = {}) {
12206
12452
  const e = obj9(raw), name = str11(e.name), path3 = str11(e.resolvedSourcePath) ?? str11(e.sourcePath);
12207
12453
  if (e.kind !== "rig" || !name || !path3 || name === KERNEL_RIG) continue;
12208
12454
  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() } : {} });
12455
+ const spec = text7 === null ? null : readSpec(text7, { name, source: specSource(e.sourceType), path: path3, ...str11(e.summary) ? { summary: str11(e.summary).trim() } : {} });
12210
12456
  if (spec) out.push(spec);
12211
12457
  }
12212
12458
  return { specs: out.sort((a, b3) => a.source === b3.source ? a.name.localeCompare(b3.name) : a.source === "user" ? -1 : 1) };
@@ -12216,7 +12462,7 @@ function createOpenRig(deps = {}) {
12216
12462
  if (acting) return { ok: false, plan: plan2, commands: [], error: "busy" };
12217
12463
  acting = true;
12218
12464
  try {
12219
- const cwd = l.cwd === "~" ? home2 : l.cwd.startsWith("~/") ? join24(home2, l.cwd.slice(2)) : l.cwd;
12465
+ const cwd = l.cwd === "~" ? home2 : l.cwd.startsWith("~/") ? join25(home2, l.cwd.slice(2)) : l.cwd;
12220
12466
  if (!isAbsolute6(cwd) || !files.exists(cwd)) return { ok: false, plan: plan2, commands: [], error: "cwd_missing" };
12221
12467
  const listed2 = await specs();
12222
12468
  if (listed2.error) return { ok: false, plan: plan2, commands: [], error: listed2.error };
@@ -12228,17 +12474,17 @@ function createOpenRig(deps = {}) {
12228
12474
  const rename7 = l.name !== spec.name;
12229
12475
  if (changesSpec(l) || l.save || rename7 && !can2.name) {
12230
12476
  const composed = composeSpec(parseYaml2(text7), dirname9(spec.path), l);
12231
- dir = join24(rigsDir, l.name);
12477
+ dir = join25(rigsDir, l.name);
12232
12478
  await files.copyDir(dirname9(spec.path), dir);
12233
- await files.write(join24(dir, "rig.yaml"), composed.yaml);
12234
- source2 = join24(dir, "rig.yaml");
12479
+ await files.write(join25(dir, "rig.yaml"), composed.yaml);
12480
+ source2 = join25(dir, "rig.yaml");
12235
12481
  yaml = composed.yaml;
12236
12482
  seats = composed.seats;
12237
12483
  } else {
12238
12484
  yaml = text7;
12239
12485
  seats = specSeats(spec);
12240
12486
  }
12241
- if (!dir && rename7 && !plan2) await files.write(join24(rigsDir, l.name, "source.json"), JSON.stringify({ spec: spec.name, path: spec.path }));
12487
+ if (!dir && rename7 && !plan2) await files.write(join25(rigsDir, l.name, "source.json"), JSON.stringify({ spec: spec.name, path: spec.path }));
12242
12488
  const commands = [["up", source2, ...!dir && rename7 ? ["--name", l.name] : [], "--cwd", cwd, ...l.nonInterruptive ? ["--non-interruptive"] : [], ...plan2 ? ["--plan"] : ["--yes"], "--json"]];
12243
12489
  if (!plan2 && l.save && dir) commands.push(["specs", "add", dir, "--json"]);
12244
12490
  const shown = commands.map(showRigCommand);
@@ -12262,9 +12508,9 @@ function createOpenRig(deps = {}) {
12262
12508
  }
12263
12509
  }
12264
12510
  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);
12511
+ const mine = join25(rigsDir, rig);
12512
+ if (files.exists(join25(mine, "rig.yaml"))) return mine;
12513
+ const marker = await files.read(join25(mine, "source.json")).catch(() => null);
12268
12514
  const path3 = marker === null ? void 0 : str11(obj9(parse2(marker)).path);
12269
12515
  if (path3) return dirname9(path3);
12270
12516
  const listed2 = await specs();
@@ -12294,7 +12540,7 @@ function createOpenRig(deps = {}) {
12294
12540
  const member = memberOf(show, a.pod, a.from.slice(a.pod.length + 1));
12295
12541
  const fragment2 = member ? fragmentOf(member, a.id, root2) : null;
12296
12542
  if (!fragment2) return { ok: false, commands: [], ...none, error: "rig_root_unknown" };
12297
- const path3 = join24(rigsDir, rigName, "seats", `${a.pod}.${a.id}.json`);
12543
+ const path3 = join25(rigsDir, rigName, "seats", `${a.pod}.${a.id}.json`);
12298
12544
  await files.write(path3, JSON.stringify(fragment2, null, 2));
12299
12545
  commands.push(["add", rig.id, a.pod, path3, ...root2 ? ["--rig-root", root2] : [], "--json"]);
12300
12546
  }
@@ -12344,7 +12590,7 @@ function createOpenRig(deps = {}) {
12344
12590
  const r = await run4(["spec", "show", rig.id, "--json"], STATUS_MS);
12345
12591
  if (r.code !== 0) continue;
12346
12592
  const root2 = rig.name === KERNEL_RIG ? null : await rootOf(rig.name).catch(() => null);
12347
- const specFile = root2 ? join24(root2, "rig.yaml") : null;
12593
+ const specFile = root2 ? join25(root2, "rig.yaml") : null;
12348
12594
  const text7 = specFile ? await roleIo.read(specFile) : null;
12349
12595
  const written = parseSpec(text7);
12350
12596
  const shown = obj9(parse2(r.stdout));
@@ -12508,14 +12754,14 @@ var init_openrig2 = __esm({
12508
12754
  if (!dir) return { code: null, stdout: "", stderr: "rig: command not found" };
12509
12755
  const e = { ...base3, PATH: [dir, base3.PATH].filter(Boolean).join(delimiter2), NO_COLOR: "1", CLICOLOR: "0", FORCE_COLOR: "0" };
12510
12756
  return new Promise((res) => {
12511
- execFile6(join24(dir, "rig"), args, { env: e, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
12757
+ execFile6(join25(dir, "rig"), args, { env: e, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
12512
12758
  const code3 = !err ? 0 : err.code === "ENOENT" ? null : typeof err.code === "number" ? err.code : 1;
12513
12759
  res({ code: code3, stdout: String(stdout), stderr: String(stderr) || (err && code3 !== 0 ? err.message : "") });
12514
12760
  });
12515
12761
  });
12516
12762
  };
12517
12763
  realRoleIo = {
12518
- read: (p) => readFile11(p, "utf8").catch(() => null),
12764
+ read: (p) => readFile12(p, "utf8").catch(() => null),
12519
12765
  stat: (p) => statFile(p).then((st) => ({ size: st.size, mtimeMs: st.mtimeMs, file: st.isFile() })).catch(() => null),
12520
12766
  head: async (p, max) => {
12521
12767
  const h = await openFile(p, "r").catch(() => null);
@@ -12533,7 +12779,7 @@ var init_openrig2 = __esm({
12533
12779
  FILES_TTL_MS = 5 * 6e4;
12534
12780
  HTTP_MS = 3e3;
12535
12781
  realFs = {
12536
- read: (p) => readFile11(p, "utf8"),
12782
+ read: (p) => readFile12(p, "utf8"),
12537
12783
  exists: (p) => existsSync2(p),
12538
12784
  copyDir: (from, to) => cp(from, to, { recursive: true, force: true, filter: (src) => basename11(src) !== "rig.yaml" }),
12539
12785
  write: async (p, text7) => {
@@ -12553,7 +12799,7 @@ function goalOf(tags) {
12553
12799
  return t ? t.slice(GOAL_TAG.length + 1) || null : null;
12554
12800
  }
12555
12801
  function parseTeam(body) {
12556
- const m = BLOCK3.exec(body);
12802
+ const m = BLOCK4.exec(body);
12557
12803
  if (!m) return null;
12558
12804
  let raw;
12559
12805
  try {
@@ -12578,13 +12824,13 @@ function parseTeam(body) {
12578
12824
  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
12825
  };
12580
12826
  }
12581
- var GOAL_TAG, goalTag, BLOCK3, obj10, text2, strings, roleOf3, withoutTeam;
12827
+ var GOAL_TAG, goalTag, BLOCK4, obj10, text2, strings, roleOf3, withoutTeam;
12582
12828
  var init_goalTeam = __esm({
12583
12829
  "src/daemon/goalTeam.ts"() {
12584
12830
  "use strict";
12585
12831
  GOAL_TAG = "ledge-goal";
12586
12832
  goalTag = (goalQitemId2) => `${GOAL_TAG}:${goalQitemId2}`;
12587
- BLOCK3 = /```ledge-team[^\n]*\n([\s\S]*?)```/;
12833
+ BLOCK4 = /```ledge-team[^\n]*\n([\s\S]*?)```/;
12588
12834
  obj10 = (v2) => v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : {};
12589
12835
  text2 = (v2) => typeof v2 === "string" && v2.trim() ? v2.trim() : null;
12590
12836
  strings = (v2) => Array.isArray(v2) ? v2.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim()) : [];
@@ -12592,7 +12838,7 @@ var init_goalTeam = __esm({
12592
12838
  const m = /^([A-Za-z0-9][A-Za-z0-9_]*)-([A-Za-z0-9][A-Za-z0-9_-]*)@/.exec(seat2);
12593
12839
  return m ? `${m[1]}.${m[2]}` : null;
12594
12840
  };
12595
- withoutTeam = (body) => body.replace(BLOCK3, "").trim();
12841
+ withoutTeam = (body) => body.replace(BLOCK4, "").trim();
12596
12842
  }
12597
12843
  });
12598
12844
 
@@ -12727,8 +12973,8 @@ function createHumanChannel(deps) {
12727
12973
  return byAddress;
12728
12974
  };
12729
12975
  const row3 = async (url, id, at) => {
12730
- const kept = rows.get(id);
12731
- if (kept && at && kept.at === at) return { row: kept.row };
12976
+ const kept2 = rows.get(id);
12977
+ if (kept2 && at && kept2.at === at) return { row: kept2.row };
12732
12978
  const r = await http("GET", `${url}/api/queue/${encodeURIComponent(id)}`);
12733
12979
  if (r.status === 404) return "gone";
12734
12980
  if (r.status !== 200 || !RowShape.safeParse(r.json).success || obj11(r.json).qitemId !== id) return "failed";
@@ -13076,8 +13322,8 @@ function createGoals(deps) {
13076
13322
  known2 = /* @__PURE__ */ new Map();
13077
13323
  return { goals: [] };
13078
13324
  }
13079
- const kept = () => ({ goals: [...known2.values()], partial: true });
13080
- if (st.daemon.state !== "running" || !st.daemon.port) return kept();
13325
+ const kept2 = () => ({ goals: [...known2.values()], partial: true });
13326
+ if (st.daemon.state !== "running" || !st.daemon.port) return kept2();
13081
13327
  const url = `http://127.0.0.1:${st.daemon.port}`;
13082
13328
  let partial = false, missed = false;
13083
13329
  const rows = async (path3, limit = LIST_LIMIT) => {
@@ -13090,7 +13336,7 @@ function createGoals(deps) {
13090
13336
  return rowsOf(r.json);
13091
13337
  };
13092
13338
  const listed2 = await rows(`/api/queue/list?sourceSession=${encodeURIComponent(PERSON)}`);
13093
- if (!listed2) return kept();
13339
+ if (!listed2) return kept2();
13094
13340
  const mine = listed2.filter((r) => str13(r.qitemId)?.startsWith("qitem-ledge-goal-") && !str13(r.qitemId).startsWith("qitem-ledge-goal-reply-") && tagsOf(r).includes(GOAL_TAG)).sort((a, b3) => (str13(b3.tsCreated) ?? "").localeCompare(str13(a.tsCreated) ?? "")).slice(0, GOALS_MAX);
13095
13341
  if (!mine.length) {
13096
13342
  known2 = /* @__PURE__ */ new Map();
@@ -13217,7 +13463,7 @@ function createRelay(d) {
13217
13463
  const now2 = d.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13218
13464
  const typing = d.typing ?? /* @__PURE__ */ new Set();
13219
13465
  const sending = /* @__PURE__ */ new Set();
13220
- const open5 = (id) => free(d.store.get(id)) && !typing.has(id) && !d.closing?.has(id);
13466
+ const open6 = (id) => free(d.store.get(id)) && !typing.has(id) && !d.closing?.has(id);
13221
13467
  const emitTo = (thread) => (m) => {
13222
13468
  try {
13223
13469
  d.threads.emitterOf(thread)?.(m);
@@ -13261,14 +13507,14 @@ function createRelay(d) {
13261
13507
  }
13262
13508
  function kick(id) {
13263
13509
  const q = queued().find((x) => x.relay.id === id);
13264
- if (!q || !open5(id)) return;
13510
+ if (!q || !open6(id)) return;
13265
13511
  void deliverAndKick(q.thread, q.qid, q.relay).catch((e) => log(`relay ${q.qid} failed: ${e.message}`));
13266
13512
  }
13267
13513
  async function deliverAndKick(thread, qid, r) {
13268
13514
  const refused = await deliver(thread, qid, r);
13269
13515
  if (refused) {
13270
13516
  if (queued().some((q) => q.relay.id === r.id)) kick(r.id);
13271
- else if (open5(r.id)) d.next?.(r.id);
13517
+ else if (open6(r.id)) d.next?.(r.id);
13272
13518
  }
13273
13519
  return refused;
13274
13520
  }
@@ -13285,7 +13531,7 @@ function createRelay(d) {
13285
13531
  }
13286
13532
  return;
13287
13533
  }
13288
- if (open5(id)) kick(id);
13534
+ if (open6(id)) kick(id);
13289
13535
  } catch (e) {
13290
13536
  log(`relay change handler failed: ${e.message}`);
13291
13537
  }
@@ -13303,7 +13549,7 @@ function createRelay(d) {
13303
13549
  mark(thread, qid, { error: m, code: STOPPED_CODE });
13304
13550
  throw new Error(m);
13305
13551
  }
13306
- if (open5(r.id)) {
13552
+ if (open6(r.id)) {
13307
13553
  if (queued().some((q) => q.relay.id === r.id)) {
13308
13554
  markQueued(thread, qid);
13309
13555
  kick(r.id);
@@ -13324,7 +13570,7 @@ function createRelay(d) {
13324
13570
  settle(id) {
13325
13571
  try {
13326
13572
  if (queued().some((q) => q.relay.id === id)) kick(id);
13327
- else if (open5(id)) d.next?.(id);
13573
+ else if (open6(id)) d.next?.(id);
13328
13574
  } catch (e) {
13329
13575
  log(`relay settle failed: ${e.message}`);
13330
13576
  }
@@ -13553,7 +13799,7 @@ var init_act = __esm({
13553
13799
 
13554
13800
  // src/daemon/handover.ts
13555
13801
  import { mkdirSync, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
13556
- import { join as join25 } from "path";
13802
+ import { join as join26 } from "path";
13557
13803
  function reliefOf(get, s) {
13558
13804
  let cur = s;
13559
13805
  for (let hops = 0; cur && hops < 50; hops++) {
@@ -13571,7 +13817,7 @@ function createHandovers(d) {
13571
13817
  const now2 = d.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
13572
13818
  const briefMs = d.briefMs ?? BRIEF_MS;
13573
13819
  const waiting2 = /* @__PURE__ */ new Map();
13574
- const file = (id) => join25(d.dir, `${id}.md`);
13820
+ const file = (id) => join26(d.dir, `${id}.md`);
13575
13821
  const mark = (id, h) => {
13576
13822
  const cur = d.store.get(id);
13577
13823
  if (cur) d.store.upsert({ ...cur, handover: { ...h, at: now2() } });
@@ -14025,7 +14271,7 @@ var init_broadcast2 = __esm({
14025
14271
  });
14026
14272
 
14027
14273
  // src/daemon/context.ts
14028
- import { open as open4 } from "fs/promises";
14274
+ import { open as open5 } from "fs/promises";
14029
14275
  function longWindow(model) {
14030
14276
  if (!model) return false;
14031
14277
  if (model.includes("[1m]")) return true;
@@ -14033,7 +14279,7 @@ function longWindow(model) {
14033
14279
  return !!m && Number(m[1]) >= 5;
14034
14280
  }
14035
14281
  async function tail(path3) {
14036
- const f = await open4(path3, "r");
14282
+ const f = await open5(path3, "r");
14037
14283
  try {
14038
14284
  const { size } = await f.stat();
14039
14285
  const start = Math.max(0, size - TAIL_BYTES2);
@@ -14117,7 +14363,7 @@ var init_context2 = __esm({
14117
14363
  import { randomUUID as randomUUID8 } from "crypto";
14118
14364
  import { mkdtemp, rm as rm5 } from "fs/promises";
14119
14365
  import { tmpdir } from "os";
14120
- import { join as join26 } from "path";
14366
+ import { join as join27 } from "path";
14121
14367
  function parseRoute(answer, context) {
14122
14368
  const raw = answer.trim().replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, "");
14123
14369
  const result = RouteResultSchema.parse(JSON.parse(raw));
@@ -14143,7 +14389,7 @@ function createRouter(deps) {
14143
14389
  let dir;
14144
14390
  let timer2;
14145
14391
  try {
14146
- dir = await mkdtemp(join26(tmpdir(), "ledge-routing-"));
14392
+ dir = await mkdtemp(join27(tmpdir(), "ledge-routing-"));
14147
14393
  const env = { ...await deps.env(), LEDGE_CHAT: "1" };
14148
14394
  delete env.ANTHROPIC_API_KEY;
14149
14395
  delete env.LEDGE_SESSION;
@@ -14379,7 +14625,16 @@ function compareVersions(a, b3) {
14379
14625
  const d = (x[i] ?? 0) - (y[i] ?? 0);
14380
14626
  if (d) return d > 0 ? 1 : -1;
14381
14627
  }
14382
- return a === b3 ? 0 : a.includes("-") ? -1 : b3.includes("-") ? 1 : 0;
14628
+ if (a === b3) return 0;
14629
+ const ax = a.match(/-snapshot\.(\d+)\.(\d+)$/), bx = b3.match(/-snapshot\.(\d+)\.(\d+)$/);
14630
+ if (ax && bx) {
14631
+ for (const i of [1, 2]) {
14632
+ const x2 = BigInt(ax[i]), y2 = BigInt(bx[i]);
14633
+ if (x2 !== y2) return x2 > y2 ? 1 : -1;
14634
+ }
14635
+ return 0;
14636
+ }
14637
+ return a.includes("-") ? -1 : b3.includes("-") ? 1 : 0;
14383
14638
  }
14384
14639
  var parts;
14385
14640
  var init_semver = __esm({
@@ -15173,15 +15428,15 @@ var init_operations = __esm({
15173
15428
  });
15174
15429
 
15175
15430
  // src/mobile/risk.ts
15176
- import { homedir as homedir12 } from "os";
15177
- import { resolve as resolve11, sep as sep5, join as join27 } from "path";
15431
+ import { homedir as homedir13 } from "os";
15432
+ import { resolve as resolve11, sep as sep5, join as join28 } from "path";
15178
15433
  function isSensitive(cwd, p) {
15179
15434
  if (p.includes("~")) return true;
15180
15435
  const full = resolve11(cwd, p);
15181
15436
  const segs = full.split(sep5);
15182
15437
  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));
15438
+ const home2 = homedir13();
15439
+ return [join28(home2, ".ssh"), join28(home2, ".ledge")].some((d) => full === d || full.startsWith(d + sep5));
15185
15440
  }
15186
15441
  function classifyShell(command, cwd) {
15187
15442
  if (PUSH.test(command)) return high("pushes");
@@ -15435,18 +15690,18 @@ function toPhoneItem(item) {
15435
15690
  return { item: out, cut: cut3 };
15436
15691
  }
15437
15692
  function toPhoneThread(items) {
15438
- const kept = [];
15693
+ const kept2 = [];
15439
15694
  const cut3 = [];
15440
15695
  let used = 2;
15441
- for (let i = items.length - 1; i >= 0 && kept.length < THREAD_ITEMS_MAX; i--) {
15696
+ for (let i = items.length - 1; i >= 0 && kept2.length < THREAD_ITEMS_MAX; i--) {
15442
15697
  const projected = toPhoneItem(items[i]);
15443
- const size = bytes2(projected.item) + (kept.length > 0 ? 1 : 0);
15698
+ const size = bytes2(projected.item) + (kept2.length > 0 ? 1 : 0);
15444
15699
  if (used + size > THREAD_BUDGET) break;
15445
15700
  used += size;
15446
- kept.push(projected.item);
15701
+ kept2.push(projected.item);
15447
15702
  if (projected.cut) cut3.push(projected.item.id);
15448
15703
  }
15449
- return { items: kept.reverse(), cut: cut3.reverse(), more: kept.length < items.length };
15704
+ return { items: kept2.reverse(), cut: cut3.reverse(), more: kept2.length < items.length };
15450
15705
  }
15451
15706
  function capabilities() {
15452
15707
  const caps = {};
@@ -16951,9 +17206,13 @@ var init_notifier = __esm({
16951
17206
  // src/daemon/server.ts
16952
17207
  import { createServer } from "http";
16953
17208
  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";
17209
+ import { homedir as homedir14 } from "os";
17210
+ import { basename as basename12, join as join29, resolve as resolve12 } from "path";
16956
17211
  import { WebSocketServer, WebSocket as WebSocket3 } from "ws";
17212
+ function withoutHarness(s) {
17213
+ const { pending: _p, lastReply: _r, transcriptPath: _t, agentSessionId: _a, agentSessionFile: _f, model: _m, effort: _e, modelNext: _n, queue: _q, backend: _b, ...rest } = s;
17214
+ return { ...rest, helpers: [] };
17215
+ }
16957
17216
  function resumeKey(s) {
16958
17217
  if (s.tool === "omp") return s.agentSessionFile;
16959
17218
  if (s.agentSessionId) return s.agentSessionId;
@@ -17003,11 +17262,12 @@ async function startServer(opts) {
17003
17262
  const agentEnv2 = opts.agentEnv ?? (async () => ({ ...process.env, ...await loginShellEnv() }));
17004
17263
  const attachments = opts.attachments ?? new AttachmentStore(paths().attachments);
17005
17264
  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() });
17265
+ 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
17266
  const chat = opts.chat ?? threads.desktop;
17008
17267
  const router = createRouter({ env: agentEnv2 });
17009
- const omp2 = opts.omp ?? createOmpHost({ store, env: agentEnv2, dir: join28(paths().dir, "omp"), now: now2, attachments });
17268
+ const omp2 = opts.omp ?? createOmpHost({ store, env: agentEnv2, dir: join29(paths().dir, "omp"), now: now2, attachments });
17010
17269
  const openrig = opts.openrig ?? createOpenRig({ env: agentEnv2 });
17270
+ const ompSeatReadOnly = (s) => new Refusal(`${s.name} is the OpenRig OMP seat ${s.rig?.seat ?? ""}: Ledge follows its state only`, "read_only");
17011
17271
  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
17272
  const adoptSeat = (id, seat2) => {
17013
17273
  const session = store.get(id);
@@ -17020,9 +17280,22 @@ async function startServer(opts) {
17020
17280
  const seatId = (seat2) => `rig-${seat2.replace(/[^A-Za-z0-9._-]/g, "-")}`;
17021
17281
  async function discoverSeats(st) {
17022
17282
  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));
17283
+ const followed = new Map(store.all().filter((x) => x.rig && x.state !== "exited").map((x) => [x.rig.seat, x.id]));
17024
17284
  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;
17285
+ const tool = SEAT_TOOLS[n.runtime];
17286
+ if (!tool || n.sessionStatus !== "running") continue;
17287
+ const live3 = followed.get(n.session);
17288
+ if (live3 !== void 0) {
17289
+ const s = store.get(live3);
17290
+ if (s && s.tool !== tool && (s.source === "rig" || s.source === "hook_only")) {
17291
+ if (s.source === "hook_only" && !await tmux.hasSession(n.session).catch(() => false)) continue;
17292
+ const cur = store.get(live3);
17293
+ if (!cur || cur.state === "exited" || cur.tool === tool) continue;
17294
+ store.upsert({ ...withoutHarness(cur), tool, state: "idle", source: "rig", tmuxSession: n.session, lastActivityAt: now2() });
17295
+ log(`openrig: seat ${n.session} now runs ${n.runtime}`);
17296
+ }
17297
+ continue;
17298
+ }
17026
17299
  if (!await tmux.hasSession(n.session).catch(() => false)) continue;
17027
17300
  const before2 = store.all().filter((x) => x.rig?.seat === n.session).sort((a, b3) => b3.lastActivityAt.localeCompare(a.lastActivityAt))[0];
17028
17301
  const id = before2?.id ?? seatId(n.session), old = store.get(id), at = now2();
@@ -17031,13 +17304,14 @@ async function startServer(opts) {
17031
17304
  if (old) {
17032
17305
  if (old.archivedAt) store.unarchive(id, at);
17033
17306
  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 });
17307
+ const same2 = rest.tool === tool ? rest : { ...withoutHarness(rest), tool };
17308
+ store.upsert({ ...same2, state: "idle", tmuxSession: n.session, source: "rig", rig, lastActivityAt: at });
17035
17309
  } else {
17036
17310
  const name = pickName([...namesInUse(store.all()), ...heldNames(store.all())]);
17037
17311
  store.upsert({
17038
17312
  id,
17039
17313
  name,
17040
- tool: n.runtime === "codex" ? "codex" : "claude",
17314
+ tool,
17041
17315
  cwd,
17042
17316
  project: basename12(cwd) || r.name,
17043
17317
  title: `${name} \xB7 ${n.logicalId}`,
@@ -17051,10 +17325,21 @@ async function startServer(opts) {
17051
17325
  });
17052
17326
  log(`openrig: seat ${n.session} followed as ${name}`);
17053
17327
  }
17054
- followed.add(n.session);
17328
+ followed.set(n.session, id);
17329
+ }
17330
+ if (!st.rigs.some((r) => r.nodes.length > 0)) return;
17331
+ const listed2 = new Set(st.rigs.flatMap((r) => r.nodes.map((n) => n.session)));
17332
+ const unsure = new Set(st.rigs.filter((r) => r.seats > 0 && r.nodes.length === 0).map((r) => r.name));
17333
+ for (const s of store.all()) {
17334
+ if (isOmpSeat2(s) && s.rig && s.state === "exited" && !isArchived(s) && !listed2.has(s.rig.seat) && !unsure.has(s.rig.name)) {
17335
+ store.archive(s.id, now2());
17336
+ log(`openrig: seat ${s.rig.seat} is gone from OpenRig`);
17337
+ }
17055
17338
  }
17056
17339
  }
17057
17340
  const SEATS_EVERY_MS = 3e4;
17341
+ const ompSeats = opts.ompSeats ?? createOmpSeats({ store });
17342
+ ompSeats.start();
17058
17343
  const seatTimer = setInterval(() => {
17059
17344
  void openrig.state().then(discoverSeats).catch((e) => log(`openrig: seats not read: ${e.message}`));
17060
17345
  }, SEATS_EVERY_MS);
@@ -17100,7 +17385,7 @@ async function startServer(opts) {
17100
17385
  }).catch((e) => log(`openrig: seat ${seat2} not read: ${e.message}`));
17101
17386
  };
17102
17387
  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())) });
17388
+ const models = opts.models ?? createModels({ env: agentEnv2, omp: async () => ompChoices(await omp2.catalog(opts.home ?? homedir14())) });
17104
17389
  const token = await ensureDaemonToken();
17105
17390
  const httpServer = createServer(unauthorized);
17106
17391
  const sockets = /* @__PURE__ */ new Set();
@@ -17124,7 +17409,7 @@ async function startServer(opts) {
17124
17409
  await new Promise((res, rej) => {
17125
17410
  const onError = (e) => rej(e);
17126
17411
  wss.once("error", onError);
17127
- httpServer.listen(opts.port ?? PROTOCOL_PORT, opts.host ?? "127.0.0.1", () => {
17412
+ httpServer.listen(opts.port ?? ledgePort(), opts.host ?? "127.0.0.1", () => {
17128
17413
  wss.off("error", onError);
17129
17414
  res();
17130
17415
  });
@@ -17140,7 +17425,7 @@ async function startServer(opts) {
17140
17425
  const islandFile = opts.islandFile ?? paths().island;
17141
17426
  let island = readIsland(islandFile);
17142
17427
  const islandMsg = () => ({ type: "island", prefs: island });
17143
- const contextFile = opts.contextFile ?? join28(paths().dir, "context.json");
17428
+ const contextFile = opts.contextFile ?? join29(paths().dir, "context.json");
17144
17429
  let contextPrefs = readContextPrefs(contextFile);
17145
17430
  const contextMsg = () => ({ type: "context.prefs", prefs: contextPrefs });
17146
17431
  const mobile = opts.mobile;
@@ -17239,7 +17524,7 @@ async function startServer(opts) {
17239
17524
  tasks,
17240
17525
  launch: (req) => launch3(req),
17241
17526
  close: (s) => closeSession(s),
17242
- dir: join28(paths().dir, "handovers"),
17527
+ dir: join29(paths().dir, "handovers"),
17243
17528
  now: now2,
17244
17529
  ...opts.handoverBriefMs !== void 0 ? { briefMs: opts.handoverBriefMs } : {}
17245
17530
  });
@@ -17327,7 +17612,7 @@ async function startServer(opts) {
17327
17612
  else if ("waitMs" in step3) await new Promise((r) => setTimeout(r, step3.waitMs));
17328
17613
  else if ("keys" in step3) await tmux.sendKeys(s.tmuxSession, step3.keys);
17329
17614
  else {
17330
- const end2 = Date.now() + UNTIL_MS;
17615
+ const end2 = Date.now() + (step3.ms ?? UNTIL_MS);
17331
17616
  for (; ; ) {
17332
17617
  const pane = await tmux.capture(s.tmuxSession, UNTIL_LINES);
17333
17618
  if (pane !== null && step3.until(pane)) break;
@@ -17515,6 +17800,7 @@ async function startServer(opts) {
17515
17800
  const s = store.get(id);
17516
17801
  if (!s) return unknownSession();
17517
17802
  if (s.source === "hook_only") return readOnly();
17803
+ if (isOmpSeat2(s)) return ompSeatReadOnly(s);
17518
17804
  if (closing.has(id)) return new Refusal("the session is being closed", "busy");
17519
17805
  const ids = how.attachments ?? [];
17520
17806
  const refs = [];
@@ -17848,7 +18134,7 @@ async function startServer(opts) {
17848
18134
  let scanning = null;
17849
18135
  function sharedScan() {
17850
18136
  if (scanning) return scanning;
17851
- const home2 = opts.home ?? homedir13();
18137
+ const home2 = opts.home ?? homedir14();
17852
18138
  const extra = extraRoots(home2, [...store.recents().map((r) => r.cwd), ...store.all().filter((s) => s.state !== "exited").map((s) => s.cwd)]);
17853
18139
  const walk = (opts.scan ?? scanRepos)({ home: home2, extra }).finally(() => {
17854
18140
  if (scanning === walk) scanning = null;
@@ -18127,6 +18413,7 @@ async function startServer(opts) {
18127
18413
  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
18414
  }
18129
18415
  if (!s?.tmuxSession) return;
18416
+ if (isOmpSeat2(s)) return refuse(ompSeatReadOnly(s), msg.id);
18130
18417
  await tmux.sendKeys(s.tmuxSession, adapterFor(s.tool).interruptKeys());
18131
18418
  return;
18132
18419
  }
@@ -18357,7 +18644,7 @@ async function startServer(opts) {
18357
18644
  relay,
18358
18645
  launchChat: (thread, qid, rows) => {
18359
18646
  const c = thread === "desktop" ? threads.desktop : threads.device(thread.slice("device:".length));
18360
- const userHome = resolve12(opts.home ?? homedir13());
18647
+ const userHome = resolve12(opts.home ?? homedir14());
18361
18648
  return launchFromChat({
18362
18649
  chat: c,
18363
18650
  emit: threads.emitterOf(thread) ?? (() => {
@@ -18501,6 +18788,7 @@ async function startServer(opts) {
18501
18788
  close: () => new Promise((res) => {
18502
18789
  clearInterval(sweepTimer);
18503
18790
  clearInterval(seatTimer);
18791
+ ompSeats.stop();
18504
18792
  human.stop();
18505
18793
  if (goalsTimer) clearTimeout(goalsTimer);
18506
18794
  void reducer.close();
@@ -18520,10 +18808,11 @@ async function startServer(opts) {
18520
18808
  })
18521
18809
  };
18522
18810
  }
18523
- var CONTINUE_ARGS, RESUME_ARGS, MULTI_DIR_ARGS, MODEL_ARGS, EFFORT_ARGS, NoteNotTyped, DELETED_TTL_MS, RESPAWN_HOOKS_MS, ALLOWED_ORIGINS;
18811
+ var SEAT_TOOLS, CONTINUE_ARGS, RESUME_ARGS, MULTI_DIR_ARGS, MODEL_ARGS, EFFORT_ARGS, NoteNotTyped, DELETED_TTL_MS, RESPAWN_HOOKS_MS, ALLOWED_ORIGINS;
18524
18812
  var init_server = __esm({
18525
18813
  "src/daemon/server.ts"() {
18526
18814
  "use strict";
18815
+ init_port();
18527
18816
  init_src();
18528
18817
  init_src();
18529
18818
  init_attachments2();
@@ -18540,6 +18829,7 @@ var init_server = __esm({
18540
18829
  init_spawn();
18541
18830
  init_host();
18542
18831
  init_catalog();
18832
+ init_seats();
18543
18833
  init_repos2();
18544
18834
  init_scan2();
18545
18835
  init_folders();
@@ -18578,6 +18868,7 @@ var init_server = __esm({
18578
18868
  init_challenges();
18579
18869
  init_webauthn();
18580
18870
  init_frames();
18871
+ SEAT_TOOLS = { "claude-code": "claude", codex: "codex", omp: "omp" };
18581
18872
  CONTINUE_ARGS = { claude: ["--continue"], codex: ["resume", "--last"], cursor: ["--continue"] };
18582
18873
  RESUME_ARGS = { claude: (id) => ["--resume", id], codex: (id) => ["resume", id], cursor: (id) => ["--resume", id] };
18583
18874
  MULTI_DIR_ARGS = { claude: (dirs) => ["--add-dir", ...dirs] };
@@ -18606,7 +18897,7 @@ var init_server = __esm({
18606
18897
  // src/tmux.ts
18607
18898
  import { execFile as execFile7 } from "child_process";
18608
18899
  import { existsSync as existsSync3 } from "fs";
18609
- import { join as join29 } from "path";
18900
+ import { join as join30 } from "path";
18610
18901
  function runFile(file, args, env, opts) {
18611
18902
  return new Promise((resolve13) => {
18612
18903
  execFile7(file, args, { maxBuffer: 4 * 1024 * 1024, env, ...opts?.timeoutMs ? { timeout: opts.timeoutMs, killSignal: "SIGTERM" } : {} }, (err, stdout, stderr) => {
@@ -18616,7 +18907,7 @@ function runFile(file, args, env, opts) {
18616
18907
  });
18617
18908
  }
18618
18909
  function systemdAvailable(env = process.env, exists = existsSync3) {
18619
- return !!env.XDG_RUNTIME_DIR && exists(join29(env.XDG_RUNTIME_DIR, "systemd", "private"));
18910
+ return !!env.XDG_RUNTIME_DIR && exists(join30(env.XDG_RUNTIME_DIR, "systemd", "private"));
18620
18911
  }
18621
18912
  function newSessionCommand(tmuxArgs, scoped, unit) {
18622
18913
  if (!scoped) return { file: "tmux", args: tmuxArgs };
@@ -18730,6 +19021,203 @@ var init_tmux = __esm({
18730
19021
  }
18731
19022
  });
18732
19023
 
19024
+ // src/panes/keys.ts
19025
+ function keyBytes(key) {
19026
+ const named = NAMED[key];
19027
+ if (named !== void 0) return named;
19028
+ const ctrl = /^C-([a-z])$/.exec(key);
19029
+ if (ctrl) return String.fromCharCode(ctrl[1].charCodeAt(0) - 96);
19030
+ if ([...key].length === 1) return key;
19031
+ throw new Error(`unknown key: ${key}`);
19032
+ }
19033
+ var NAMED;
19034
+ var init_keys = __esm({
19035
+ "src/panes/keys.ts"() {
19036
+ "use strict";
19037
+ NAMED = {
19038
+ Enter: "\r",
19039
+ Escape: "\x1B",
19040
+ Tab: " ",
19041
+ BTab: "\x1B[Z",
19042
+ BSpace: "\x7F",
19043
+ Space: " ",
19044
+ Up: "\x1B[A",
19045
+ Down: "\x1B[B",
19046
+ Right: "\x1B[C",
19047
+ Left: "\x1B[D"
19048
+ };
19049
+ }
19050
+ });
19051
+
19052
+ // src/panes/capture.ts
19053
+ function captureLines(buffer, lines, opts = {}) {
19054
+ const join42 = opts.join ?? true;
19055
+ const out = [];
19056
+ for (let i = 0; i < buffer.length; i++) {
19057
+ const line2 = buffer.getLine(i);
19058
+ if (!line2) continue;
19059
+ const text7 = line2.translateToString(true);
19060
+ if (join42 && line2.isWrapped && out.length) out[out.length - 1] += text7;
19061
+ else out.push(text7);
19062
+ }
19063
+ return out.join("\n").replace(/\s+$/, "").split("\n").slice(-lines).join("\n");
19064
+ }
19065
+ var init_capture = __esm({
19066
+ "src/panes/capture.ts"() {
19067
+ "use strict";
19068
+ }
19069
+ });
19070
+
19071
+ // src/panes/resolve.ts
19072
+ import { existsSync as existsSync4, statSync as statSync3 } from "fs";
19073
+ import { join as join31, isAbsolute as isAbsolute7, win32 } from "path";
19074
+ function resolveCommand(file, env, platform = process.platform) {
19075
+ if (platform !== "win32" || isAbsolute7(file) || win32.isAbsolute(file) || /[\\/]/.test(file)) return file;
19076
+ const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH");
19077
+ const dirs = (key ? env[key] ?? "" : "").split(";").filter(Boolean);
19078
+ const names = /\.(exe|com)$/i.test(file) ? [file] : [`${file}.exe`, `${file}.com`];
19079
+ for (const dir of dirs) for (const name of names) {
19080
+ const p = join31(dir, name);
19081
+ if (existsSync4(p) && statSync3(p).isFile()) return p;
19082
+ }
19083
+ throw new Error(`${file}: no .exe found on the PATH`);
19084
+ }
19085
+ var init_resolve = __esm({
19086
+ "src/panes/resolve.ts"() {
19087
+ "use strict";
19088
+ }
19089
+ });
19090
+
19091
+ // src/panes/conpty.ts
19092
+ import { execFile as execFile8 } from "child_process";
19093
+ async function load(spawn8) {
19094
+ const xterm = await import("@xterm/headless");
19095
+ return { spawn: spawn8 ?? (await import("node-pty")).spawn, Terminal: (xterm.default ?? xterm).Terminal };
19096
+ }
19097
+ function killTree(pane) {
19098
+ const pid = pane.pty.pid;
19099
+ if (process.platform === "win32") {
19100
+ execFile8("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, () => {
19101
+ });
19102
+ return;
19103
+ }
19104
+ try {
19105
+ process.kill(-pid, "SIGKILL");
19106
+ } catch {
19107
+ try {
19108
+ pane.pty.kill("SIGKILL");
19109
+ } catch {
19110
+ }
19111
+ }
19112
+ }
19113
+ function conptyPanes(opts = {}) {
19114
+ const cols = opts.cols ?? 200, rows = opts.rows ?? 50;
19115
+ let loaded;
19116
+ const live3 = /* @__PURE__ */ new Map();
19117
+ const codes = /* @__PURE__ */ new Map();
19118
+ const get = (name) => live3.get(name);
19119
+ const settle = (term) => new Promise((r) => term.write("", r));
19120
+ return {
19121
+ async newSession({ name, cwd, env, unset = [], command }) {
19122
+ if (live3.has(name)) throw new Error(`pane ${name} exists`);
19123
+ const { spawn: spawn8, Terminal } = await (loaded ??= load(opts.spawn));
19124
+ if (live3.has(name)) throw new Error(`pane ${name} exists`);
19125
+ const childEnv = {};
19126
+ for (const [k, v2] of Object.entries(env)) if (!unset.includes(k)) childEnv[k] = v2;
19127
+ const term = new Terminal({ cols, rows, scrollback: 5e3, allowProposedApi: true });
19128
+ const pty = spawn8(resolveCommand(command[0], childEnv), command.slice(1), { name: "xterm-256color", cols, rows, cwd, env: childEnv });
19129
+ codes.delete(name);
19130
+ let done;
19131
+ const exited = new Promise((r) => {
19132
+ done = r;
19133
+ });
19134
+ pty.onData((d) => term.write(d));
19135
+ pty.onExit((e) => {
19136
+ codes.set(name, e.exitCode);
19137
+ live3.delete(name);
19138
+ term.dispose();
19139
+ done();
19140
+ });
19141
+ live3.set(name, { pty, term, exited });
19142
+ },
19143
+ async sendText(name, text7) {
19144
+ if (/[\r\n]/.test(text7)) throw new Error("sendText: text must not contain newlines; flatten it first");
19145
+ const p = get(name);
19146
+ if (!p) throw new Error(`no pane ${name}`);
19147
+ const chars = [...text7];
19148
+ for (let i = 0; i < chars.length; i += SLICE) {
19149
+ if (i) await new Promise((r) => setTimeout(r, SLICE_GAP_MS));
19150
+ p.pty.write(chars.slice(i, i + SLICE).join(""));
19151
+ }
19152
+ },
19153
+ async sendKeys(name, keys) {
19154
+ const p = get(name);
19155
+ if (!p) throw new Error(`no pane ${name}`);
19156
+ const bytes4 = keys.map(keyBytes);
19157
+ for (const b3 of bytes4) p.pty.write(b3);
19158
+ },
19159
+ async capture(name, lines) {
19160
+ const p = get(name);
19161
+ if (!p) return null;
19162
+ await settle(p.term);
19163
+ return captureLines(p.term.buffer.active, lines, { join: false });
19164
+ },
19165
+ async width(name) {
19166
+ return get(name)?.term.cols ?? null;
19167
+ },
19168
+ async paneCwd() {
19169
+ return null;
19170
+ },
19171
+ async hasSession(name) {
19172
+ return live3.has(name);
19173
+ },
19174
+ async listSessions() {
19175
+ return [...live3.keys()];
19176
+ },
19177
+ async killSession(name) {
19178
+ const p = get(name);
19179
+ if (!p) return;
19180
+ killTree(p);
19181
+ await Promise.race([p.exited, new Promise((r) => setTimeout(r, 5e3))]);
19182
+ },
19183
+ async setOption() {
19184
+ },
19185
+ attachArgs() {
19186
+ throw new Error("attach is not available in the ConPTY spike");
19187
+ },
19188
+ controlArgs() {
19189
+ return null;
19190
+ },
19191
+ exitCode(name) {
19192
+ return codes.get(name);
19193
+ }
19194
+ };
19195
+ }
19196
+ var SLICE, SLICE_GAP_MS;
19197
+ var init_conpty = __esm({
19198
+ "src/panes/conpty.ts"() {
19199
+ "use strict";
19200
+ init_keys();
19201
+ init_capture();
19202
+ init_resolve();
19203
+ SLICE = 256;
19204
+ SLICE_GAP_MS = 20;
19205
+ }
19206
+ });
19207
+
19208
+ // src/panes/pick.ts
19209
+ function pickTmux(platform, env, socket = "ledge") {
19210
+ if (platform === "win32" || env.LEDGE_PANES === "conpty") return conptyPanes();
19211
+ return routedTmux(realTmux(socket), realTmux(null));
19212
+ }
19213
+ var init_pick = __esm({
19214
+ "src/panes/pick.ts"() {
19215
+ "use strict";
19216
+ init_tmux();
19217
+ init_conpty();
19218
+ }
19219
+ });
19220
+
18733
19221
  // src/cli/setup.ts
18734
19222
  var setup_exports = {};
18735
19223
  __export(setup_exports, {
@@ -18749,10 +19237,10 @@ __export(setup_exports, {
18749
19237
  systemdUnit: () => systemdUnit,
18750
19238
  writeBin: () => writeBin
18751
19239
  });
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";
18753
- 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";
19240
+ 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";
19241
+ import { execFile as execFile9 } from "child_process";
19242
+ import { homedir as homedir15, userInfo } from "os";
19243
+ import { join as join32, dirname as dirname10 } from "path";
18756
19244
  function mergeHooks(settings, events, command, file) {
18757
19245
  if (settings === void 0) settings = {};
18758
19246
  if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error(`${file} root is not an object, not modified`);
@@ -18800,7 +19288,7 @@ function mergeCursorHooks(config, command = "ledge hook cursor") {
18800
19288
  async function mergeJsonFile(path3, merge, name) {
18801
19289
  let raw = null;
18802
19290
  try {
18803
- raw = await readFile12(path3, "utf8");
19291
+ raw = await readFile13(path3, "utf8");
18804
19292
  } catch (e) {
18805
19293
  if (e.code !== "ENOENT") throw e;
18806
19294
  }
@@ -18836,7 +19324,7 @@ WantedBy=default.target
18836
19324
  async function writeIfChanged(path3, content3) {
18837
19325
  let current2 = null;
18838
19326
  try {
18839
- current2 = await readFile12(path3, "utf8");
19327
+ current2 = await readFile13(path3, "utf8");
18840
19328
  } catch (e) {
18841
19329
  if (e.code !== "ENOENT") throw e;
18842
19330
  }
@@ -18850,7 +19338,7 @@ async function writeIfChanged(path3, content3) {
18850
19338
  async function stopDetachedDaemon(pidFile) {
18851
19339
  let pid;
18852
19340
  try {
18853
- pid = Number((await readFile12(pidFile, "utf8")).trim());
19341
+ pid = Number((await readFile13(pidFile, "utf8")).trim());
18854
19342
  } catch {
18855
19343
  return null;
18856
19344
  }
@@ -18880,10 +19368,10 @@ async function stopDetachedDaemon(pidFile) {
18880
19368
  async function installService(opts = {}) {
18881
19369
  const out = [];
18882
19370
  const run4 = opts.run ?? execRunner;
18883
- const unitDir = opts.unitDir ?? join30(homedir14(), ".config", "systemd", "user");
19371
+ const unitDir = opts.unitDir ?? join32(homedir15(), ".config", "systemd", "user");
18884
19372
  const node2 = opts.node ?? await realpath2(process.execPath);
18885
19373
  const script = opts.script ?? await realpath2(process.argv[1]);
18886
- const changed = await writeIfChanged(join30(unitDir, SERVICE), systemdUnit({ node: node2, script }));
19374
+ const changed = await writeIfChanged(join32(unitDir, SERVICE), systemdUnit({ node: node2, script }));
18887
19375
  const reload = await run4("systemctl", ["--user", "daemon-reload"]);
18888
19376
  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
19377
  const active = (await run4("systemctl", ["--user", "is-active", SERVICE])).code === 0;
@@ -18912,7 +19400,7 @@ async function installService(opts = {}) {
18912
19400
  }
18913
19401
  async function isDir(path3) {
18914
19402
  try {
18915
- return (await stat6(path3)).isDirectory();
19403
+ return (await stat7(path3)).isDirectory();
18916
19404
  } catch {
18917
19405
  return false;
18918
19406
  }
@@ -18921,7 +19409,7 @@ async function writeBin(opts) {
18921
19409
  const st = await lstat(opts.link).catch(() => null);
18922
19410
  const was = st?.isSymbolicLink() ? await readlink(opts.link) : null;
18923
19411
  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(() => "") : "";
19412
+ const current2 = st && was === null && st.isFile() ? await readFile13(opts.link, "utf8").catch(() => "") : "";
18925
19413
  if (st && was === null && !current2.startsWith(BIN_MARK)) return null;
18926
19414
  const script = `${BIN_MARK}: the hooks run \`ledge\` wherever node is not on the PATH.
18927
19415
  exec ${shQuote(opts.node)} ${shQuote(opts.target)} "$@"
@@ -18937,24 +19425,24 @@ async function runSetup(opts = {}) {
18937
19425
  const out = [];
18938
19426
  await ensureLedgeDir();
18939
19427
  out.push("~/.ledge ready (tmux.conf written if missing)");
18940
- const settingsPath2 = opts.settingsPath ?? join30(homedir14(), ".claude", "settings.json");
19428
+ const settingsPath2 = opts.settingsPath ?? join32(homedir15(), ".claude", "settings.json");
18941
19429
  if (await mergeJsonFile(settingsPath2, mergeClaudeHooks, "settings.json")) out.push(`Claude Code hooks added in ${settingsPath2}`);
18942
19430
  else out.push("Claude Code hooks already present");
18943
- const codexDir = opts.codexDir ?? process.env.CODEX_HOME ?? join30(homedir14(), ".codex");
19431
+ const codexDir = opts.codexDir ?? process.env.CODEX_HOME ?? join32(homedir15(), ".codex");
18944
19432
  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`);
19433
+ else if (await mergeJsonFile(join32(codexDir, "hooks.json"), mergeCodexHooks, "hooks.json")) {
19434
+ out.push(`Codex hooks added in ${join32(codexDir, "hooks.json")}; Codex asks to review them on its next start: trust them there or from Ledge`);
18947
19435
  } else out.push("Codex hooks already present");
18948
- const cursorDir = opts.cursorDir ?? join30(homedir14(), ".cursor");
19436
+ const cursorDir = opts.cursorDir ?? join32(homedir15(), ".cursor");
18949
19437
  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")}`);
19438
+ else if (await mergeJsonFile(join32(cursorDir, "hooks.json"), mergeCursorHooks, "hooks.json")) out.push(`Cursor Agent hooks added in ${join32(cursorDir, "hooks.json")}`);
18951
19439
  else out.push("Cursor Agent hooks already present");
18952
19440
  if (opts.service ?? true) {
18953
19441
  const cli = opts.cli ?? process.argv[1] ?? "";
18954
19442
  if (cli.endsWith(".ts")) out.push("systemd service skipped: run setup from the built CLI (pnpm --filter @paradow/ledge build, then ledge setup)");
18955
19443
  else {
18956
19444
  const script = await realpath2(cli);
18957
- const line2 = await writeBin({ link: opts.link ?? join30(homedir14(), ".local", "bin", "ledge"), node: process.execPath, target: script });
19445
+ const line2 = await writeBin({ link: opts.link ?? join32(homedir15(), ".local", "bin", "ledge"), node: process.execPath, target: script });
18958
19446
  if (line2) out.push(line2);
18959
19447
  out.push(...await installService({ script, unitDir: opts.unitDir, run: opts.run }));
18960
19448
  }
@@ -19005,7 +19493,7 @@ var init_setup = __esm({
19005
19493
  ];
19006
19494
  SERVICE = "ledge-daemon.service";
19007
19495
  execRunner = (cmd, args) => new Promise((resolve13) => {
19008
- execFile8(cmd, args, (err, stdout, stderr) => {
19496
+ execFile9(cmd, args, (err, stdout, stderr) => {
19009
19497
  const code3 = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
19010
19498
  resolve13({ code: code3, stdout: String(stdout), stderr: String(stderr) });
19011
19499
  });
@@ -19016,10 +19504,10 @@ var init_setup = __esm({
19016
19504
  });
19017
19505
 
19018
19506
  // src/update/guard.ts
19019
- import { readFile as readFile13 } from "fs/promises";
19507
+ import { readFile as readFile14 } from "fs/promises";
19020
19508
  async function tmuxServerAtRisk(opts = {}) {
19021
19509
  const runTmux = opts.runTmux ?? run2;
19022
- const readCgroup = opts.readCgroup ?? ((pid2) => readFile13(`/proc/${pid2}/cgroup`, "utf8"));
19510
+ const readCgroup = opts.readCgroup ?? ((pid2) => readFile14(`/proc/${pid2}/cgroup`, "utf8"));
19023
19511
  const r = await runTmux(["-L", opts.socket ?? "ledge", "list-sessions", "-F", "#{pid}"]);
19024
19512
  if (r.code !== 0) return false;
19025
19513
  const pid = r.stdout.split("\n").map((s) => s.trim()).find(Boolean);
@@ -19041,26 +19529,26 @@ var init_guard = __esm({
19041
19529
  });
19042
19530
 
19043
19531
  // src/update/self.ts
19044
- 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";
19048
- function gitRoot(script, exists = existsSync4) {
19532
+ import { existsSync as existsSync5 } from "fs";
19533
+ import { readFile as readFile15, realpath as realpath3 } from "fs/promises";
19534
+ import { homedir as homedir16 } from "os";
19535
+ import { dirname as dirname11, join as join33 } from "path";
19536
+ function gitRoot(script, exists = existsSync5) {
19049
19537
  for (let dir = dirname11(script); ; dir = dirname11(dir)) {
19050
- if (exists(join31(dir, ".git"))) return dir;
19538
+ if (exists(join33(dir, ".git"))) return dir;
19051
19539
  if (dirname11(dir) === dir) return null;
19052
19540
  }
19053
19541
  }
19054
- function installKind(script, exists = existsSync4) {
19542
+ function installKind(script, exists = existsSync5) {
19055
19543
  if (script.includes("/node_modules/@paradow/ledge/")) return "npm";
19056
19544
  return gitRoot(script, exists) ? "git" : "unknown";
19057
19545
  }
19058
19546
  async function daemonInfo() {
19059
19547
  const script = await realpath3(process.argv[1] ?? "").catch(() => process.argv[1] ?? "");
19060
- const cgroup = await readFile14("/proc/self/cgroup", "utf8").catch(() => "");
19548
+ const cgroup = await readFile15("/proc/self/cgroup", "utf8").catch(() => "");
19061
19549
  const install = installKind(script);
19062
19550
  const root2 = install === "git" ? gitRoot(script) : null;
19063
- return { version: LEDGE_VERSION, install, systemd: underSystemd(process.env, cgroup), home: homedir15(), ...root2 ? { manual: manualCommand(root2) } : {} };
19551
+ return { version: LEDGE_VERSION, install, systemd: underSystemd(process.env, cgroup), home: homedir16(), ...root2 ? { manual: manualCommand(root2) } : {} };
19064
19552
  }
19065
19553
  var underSystemd, manualCommand;
19066
19554
  var init_self = __esm({
@@ -19074,7 +19562,7 @@ var init_self = __esm({
19074
19562
  });
19075
19563
 
19076
19564
  // src/update/run-update.ts
19077
- import { dirname as dirname12, join as join32 } from "path";
19565
+ import { dirname as dirname12, join as join34 } from "path";
19078
19566
  function createDaemonUpdater(deps) {
19079
19567
  let busy = false;
19080
19568
  let restarting = false;
@@ -19163,7 +19651,7 @@ var init_run_update = __esm({
19163
19651
  PACKAGE = "@paradow/ledge";
19164
19652
  NPM_TIMEOUT_MS = 3 * 6e4;
19165
19653
  RESTART_COMMAND = "systemctl --user restart ledge-daemon";
19166
- npmCommand = (execPath, version) => ({ file: join32(dirname12(execPath), "npm"), args: ["i", "-g", `${PACKAGE}@${version}`] });
19654
+ npmCommand = (execPath, version) => ({ file: join34(dirname12(execPath), "npm"), args: ["i", "-g", `${PACKAGE}@${version}`] });
19167
19655
  lastLines = (output, n = 3) => output.split("\n").map((l) => l.trimEnd()).filter(Boolean).slice(-n).join("\n");
19168
19656
  npmEnv = (execPath, env) => ({ ...env, PATH: `${dirname12(execPath)}:${env.PATH ?? ""}` });
19169
19657
  }
@@ -20677,12 +21165,12 @@ __export(daemon_exports, {
20677
21165
  startDaemon: () => startDaemon
20678
21166
  });
20679
21167
  import { writeFile as writeFile15, rm as rm7 } from "fs/promises";
20680
- import { join as join33 } from "path";
21168
+ import { join as join35 } from "path";
20681
21169
  async function startDaemon(opts = {}) {
20682
21170
  await ensureLedgeDir();
20683
21171
  const store = new Store();
20684
21172
  const loaded = await store.load();
20685
- const tmux = opts.tmux ?? routedTmux(realTmux(opts.tmuxSocket ?? "ledge"), realTmux(null));
21173
+ const tmux = opts.tmux ?? pickTmux(process.platform, process.env, opts.tmuxSocket ?? "ledge");
20686
21174
  let live3 = null;
20687
21175
  try {
20688
21176
  live3 = new Set(await tmux.listSessions());
@@ -20694,7 +21182,7 @@ async function startDaemon(opts = {}) {
20694
21182
  }
20695
21183
  for (const s of store.all()) {
20696
21184
  if (backendOf(s) !== "omp-rpc") continue;
20697
- await reapOwner(join33(paths().dir, "omp", s.id)).catch(() => false);
21185
+ await reapOwner(join35(paths().dir, "omp", s.id)).catch(() => false);
20698
21186
  if (s.state === "exited") continue;
20699
21187
  const at = (/* @__PURE__ */ new Date()).toISOString();
20700
21188
  store.apply(s.id, { name: "StopFailure", at, source: "daemon", text: "Ledge restarted: the OMP process ended with it" });
@@ -20760,7 +21248,7 @@ var init_daemon = __esm({
20760
21248
  init_attachments2();
20761
21249
  init_server();
20762
21250
  init_poller();
20763
- init_tmux();
21251
+ init_pick();
20764
21252
  init_paths();
20765
21253
  init_log();
20766
21254
  init_self();
@@ -20781,9 +21269,9 @@ __export(uninstall_exports, {
20781
21269
  runUninstall: () => runUninstall,
20782
21270
  uninstallArgs: () => uninstallArgs
20783
21271
  });
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";
21272
+ import { readFile as readFile16, writeFile as writeFile16, rename as rename6, rm as rm8, lstat as lstat2, realpath as realpath4 } from "fs/promises";
21273
+ import { homedir as homedir17 } from "os";
21274
+ import { join as join36 } from "path";
20787
21275
  function prune(config, file, keep) {
20788
21276
  if (!isObject(config)) throw new Error(`${file} root is not an object, not modified`);
20789
21277
  if (config.hooks === void 0) return { settings: config, removed: 0 };
@@ -20792,16 +21280,16 @@ function prune(config, file, keep) {
20792
21280
  let removed = 0;
20793
21281
  for (const [ev, list3] of Object.entries(config.hooks)) {
20794
21282
  if (!Array.isArray(list3)) throw new Error(`${file} hooks.${ev} is not an array, not modified`);
20795
- const kept = [];
21283
+ const kept2 = [];
20796
21284
  for (const entry2 of list3) {
20797
21285
  const next4 = keep(entry2);
20798
21286
  if (next4 === null) removed++;
20799
21287
  else {
20800
21288
  if (next4 !== entry2) removed++;
20801
- kept.push(next4);
21289
+ kept2.push(next4);
20802
21290
  }
20803
21291
  }
20804
- if (kept.length || !list3.length) hooks[ev] = kept;
21292
+ if (kept2.length || !list3.length) hooks[ev] = kept2;
20805
21293
  }
20806
21294
  const { hooks: _drop, ...rest } = config;
20807
21295
  return { settings: Object.keys(hooks).length ? { ...rest, hooks } : rest, removed };
@@ -20820,7 +21308,7 @@ function removeCursorHooks(config) {
20820
21308
  async function pruneJsonFile(path3, remove, name) {
20821
21309
  let raw;
20822
21310
  try {
20823
- raw = await readFile15(path3, "utf8");
21311
+ raw = await readFile16(path3, "utf8");
20824
21312
  } catch (e) {
20825
21313
  if (e.code === "ENOENT") return null;
20826
21314
  throw e;
@@ -20841,7 +21329,7 @@ async function pruneJsonFile(path3, remove, name) {
20841
21329
  async function removeService(opts = {}) {
20842
21330
  const out = [];
20843
21331
  const run4 = opts.run ?? execRunner;
20844
- const unit = join34(opts.unitDir ?? join34(homedir16(), ".config", "systemd", "user"), SERVICE);
21332
+ const unit = join36(opts.unitDir ?? join36(homedir17(), ".config", "systemd", "user"), SERVICE);
20845
21333
  let present = true;
20846
21334
  try {
20847
21335
  await lstat2(unit);
@@ -20866,7 +21354,7 @@ async function runningSessions(run4, socket) {
20866
21354
  async function removeLink(link3, script) {
20867
21355
  try {
20868
21356
  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;
21357
+ if (st.isFile() ? !(await readFile16(link3, "utf8")).startsWith(BIN_MARK) : !st.isSymbolicLink() || await realpath4(link3) !== await realpath4(script)) return false;
20870
21358
  } catch {
20871
21359
  return false;
20872
21360
  }
@@ -20889,9 +21377,9 @@ async function runUninstall(opts = {}) {
20889
21377
  };
20890
21378
  const run4 = opts.run ?? execRunner;
20891
21379
  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]
21380
+ ["Claude Code", opts.settingsPath ?? join36(homedir17(), ".claude", "settings.json"), (c) => removeClaudeHooks(c, "settings.json")],
21381
+ ["Codex", join36(opts.codexDir ?? process.env.CODEX_HOME ?? join36(homedir17(), ".codex"), "hooks.json"), (c) => removeClaudeHooks(c, "hooks.json")],
21382
+ ["Cursor Agent", join36(opts.cursorDir ?? join36(homedir17(), ".cursor"), "hooks.json"), removeCursorHooks]
20895
21383
  ];
20896
21384
  for (const [agent, path3, remove] of files) {
20897
21385
  await step3(async () => {
@@ -20913,7 +21401,7 @@ async function runUninstall(opts = {}) {
20913
21401
  });
20914
21402
  else lines.push(`${dir} kept (session journal and log): \`ledge uninstall --purge\` removes it`);
20915
21403
  const script = opts.script ?? process.argv[1];
20916
- const link3 = opts.link ?? join34(homedir16(), ".local", "bin", "ledge");
21404
+ const link3 = opts.link ?? join36(homedir17(), ".local", "bin", "ledge");
20917
21405
  if (script) await step3(async () => {
20918
21406
  if (await removeLink(link3, script)) lines.push(`${link3} removed`);
20919
21407
  });
@@ -21111,7 +21599,7 @@ function createTokenSource(read) {
21111
21599
  fns.forEach((f) => f(m));
21112
21600
  }
21113
21601
  };
21114
- const load = async () => {
21602
+ const load2 = async () => {
21115
21603
  let r, timer2;
21116
21604
  const unavailable = { kind: "unavailable" };
21117
21605
  try {
@@ -21132,7 +21620,7 @@ function createTokenSource(read) {
21132
21620
  return {
21133
21621
  get(fresh) {
21134
21622
  if (!fresh && cached !== null) return Promise.resolve({ kind: "token", token: cached });
21135
- inflight ??= load().finally(() => {
21623
+ inflight ??= load2().finally(() => {
21136
21624
  inflight = null;
21137
21625
  });
21138
21626
  return inflight;
@@ -21171,10 +21659,10 @@ function createDaemonClient(opts) {
21171
21659
  };
21172
21660
  const schedule = () => {
21173
21661
  if (closed) return;
21174
- timer2 = setTimeout(open5, backoff);
21662
+ timer2 = setTimeout(open6, backoff);
21175
21663
  backoff = Math.min(max, backoff * 2);
21176
21664
  };
21177
- function open5() {
21665
+ function open6() {
21178
21666
  if (closed) return;
21179
21667
  setStatus(status === "open" ? "connecting" : status);
21180
21668
  if (!token) {
@@ -21238,7 +21726,7 @@ function createDaemonClient(opts) {
21238
21726
  ws = null;
21239
21727
  old?.close();
21240
21728
  setStatus("connecting");
21241
- open5();
21729
+ open6();
21242
21730
  },
21243
21731
  close: () => {
21244
21732
  closed = true;
@@ -21690,8 +22178,8 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
21690
22178
  }
21691
22179
  function history() {
21692
22180
  const list3 = [...turns2.values()].sort((a, b3) => a.askedAt.localeCompare(b3.askedAt)).slice(-50);
21693
- const kept = new Set(list3.map((t) => t.qid));
21694
- for (const id of turns2.keys()) if (!kept.has(id)) {
22181
+ const kept2 = new Set(list3.map((t) => t.qid));
22182
+ for (const id of turns2.keys()) if (!kept2.has(id)) {
21695
22183
  turns2.delete(id);
21696
22184
  bindings.delete(id);
21697
22185
  }
@@ -23011,7 +23499,7 @@ __export(launch_exports, {
23011
23499
  parseLaunchArgs: () => parseLaunchArgs
23012
23500
  });
23013
23501
  import { spawn as spawn3 } from "child_process";
23014
- import { readFile as readFile16, rm as rm9 } from "fs/promises";
23502
+ import { readFile as readFile17, rm as rm9 } from "fs/promises";
23015
23503
  import { createConnection } from "net";
23016
23504
  function parseLaunchArgs(argv) {
23017
23505
  const args = [];
@@ -23063,7 +23551,7 @@ async function probe(port, timeoutMs) {
23063
23551
  }
23064
23552
  async function ensureDaemon(opts = {}) {
23065
23553
  await ensureLedgeDir();
23066
- const port = opts.port ?? PROTOCOL_PORT;
23554
+ const port = opts.port ?? ledgePort();
23067
23555
  const timeoutMs = opts.timeoutMs ?? 2e3;
23068
23556
  const first = await probe(port, timeoutMs);
23069
23557
  if (first.client) return { client: first.client };
@@ -23124,7 +23612,7 @@ async function readExitCode(id) {
23124
23612
  const file = exitFile(id);
23125
23613
  let raw = null;
23126
23614
  try {
23127
- raw = await readFile16(file, "utf8");
23615
+ raw = await readFile17(file, "utf8");
23128
23616
  } catch {
23129
23617
  }
23130
23618
  await rm9(file, { force: true });
@@ -23134,7 +23622,7 @@ async function readExitCode(id) {
23134
23622
  async function launch(tool, argv, opts = {}) {
23135
23623
  await ensureLedgeDir();
23136
23624
  const { name, args } = parseLaunchArgs(argv);
23137
- const port = opts.port ?? PROTOCOL_PORT;
23625
+ const port = opts.port ?? ledgePort();
23138
23626
  const socket = opts.tmuxSocket ?? TMUX_SOCKET;
23139
23627
  const tmux = realTmux(socket);
23140
23628
  const cwd = opts.cwd ?? process.cwd();
@@ -23176,6 +23664,7 @@ var TMUX_SOCKET, sleep2, closeQuietly, DaemonError, UNTRACKED;
23176
23664
  var init_launch2 = __esm({
23177
23665
  "src/cli/launch.ts"() {
23178
23666
  "use strict";
23667
+ init_port();
23179
23668
  init_src();
23180
23669
  init_client();
23181
23670
  init_tmux();
@@ -23201,6 +23690,7 @@ var init_launch2 = __esm({
23201
23690
  import { spawn as spawn4 } from "child_process";
23202
23691
  function attachCommand(fleet, s) {
23203
23692
  if (s.offline) return { why: "offline" };
23693
+ if (isStoppedOmpSeat(s)) return { why: "stopped" };
23204
23694
  if (backendOf(s) === "omp-rpc") return { why: "omp" };
23205
23695
  if (!s.tmuxSession) return { why: "none" };
23206
23696
  const argv = fleet.terminal(s.id);
@@ -23222,10 +23712,10 @@ var init_attach = __esm({
23222
23712
  });
23223
23713
 
23224
23714
  // src/tui/clipboard.ts
23225
- import { execFile as execFile9 } from "child_process";
23226
- import { mkdtemp as mkdtemp2, readFile as readFile17, rm as rm10 } from "fs/promises";
23715
+ import { execFile as execFile10 } from "child_process";
23716
+ import { mkdtemp as mkdtemp2, readFile as readFile18, rm as rm10 } from "fs/promises";
23227
23717
  import { tmpdir as tmpdir2 } from "os";
23228
- import { join as join35 } from "path";
23718
+ import { join as join37 } from "path";
23229
23719
  async function fromWindows(run4) {
23230
23720
  const { stdout } = await run4(POWERSHELL, ["-NoProfile", "-STA", "-Command", SCRIPT], { cwd: "/mnt/c", maxBuffer: MAX_BUFFER, encoding: "buffer" });
23231
23721
  const line2 = stdout.toString("utf8").trim();
@@ -23250,15 +23740,15 @@ async function fromLinux(run4) {
23250
23740
  return null;
23251
23741
  }
23252
23742
  async function fromMac(run4) {
23253
- const dir = await mkdtemp2(join35(tmpdir2(), "ledge-clip-"));
23254
- const file = join35(dir, "clip.png");
23743
+ const dir = await mkdtemp2(join37(tmpdir2(), "ledge-clip-"));
23744
+ const file = join37(dir, "clip.png");
23255
23745
  try {
23256
23746
  const script = `set f to open for access POSIX file "${file}" with write permission
23257
23747
  set eof f to 0
23258
23748
  write (the clipboard as \xABclass PNGf\xBB) to f
23259
23749
  close access f`;
23260
23750
  await run4("osascript", ["-e", script], { maxBuffer: MAX_BUFFER, encoding: "buffer" });
23261
- return fromBytes(await readFile17(file));
23751
+ return fromBytes(await readFile18(file));
23262
23752
  } catch {
23263
23753
  return null;
23264
23754
  } finally {
@@ -23281,7 +23771,7 @@ var init_clipboard = __esm({
23281
23771
  init_imageInfo();
23282
23772
  CLIPBOARD_TIMEOUT_MS = 1e4;
23283
23773
  runFile2 = (file, args, o) => new Promise((resolve13, reject) => {
23284
- execFile9(file, args, { ...o, timeout: CLIPBOARD_TIMEOUT_MS }, (err, stdout) => err ? reject(err) : resolve13({ stdout }));
23774
+ execFile10(file, args, { ...o, timeout: CLIPBOARD_TIMEOUT_MS }, (err, stdout) => err ? reject(err) : resolve13({ stdout }));
23285
23775
  });
23286
23776
  POWERSHELL = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe";
23287
23777
  MAX_BUFFER = 4 * 1024 * 1024;
@@ -23689,7 +24179,7 @@ var init_fr = __esm({
23689
24179
  plural = (n, one2, many) => n > 1 ? many : one2;
23690
24180
  fr3 = {
23691
24181
  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" },
24182
+ 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
24183
  time: {
23694
24184
  now: "\xE0 l\u2019instant",
23695
24185
  decimal: ",",
@@ -23712,6 +24202,7 @@ var init_fr = __esm({
23712
24202
  openImage: "ouvrir l'image",
23713
24203
  navigate: "naviguer",
23714
24204
  open: "ouvrir",
24205
+ attachSeat: "attacher son terminal",
23715
24206
  answer: "r\xE9pondre",
23716
24207
  attach: "attacher (tmux)",
23717
24208
  ask: "demander \xE0 Ledge",
@@ -23786,6 +24277,7 @@ var init_fr = __esm({
23786
24277
  noPreview: "Pas d\u2019aper\xE7u.",
23787
24278
  noExchange: "Aucun \xE9change.",
23788
24279
  sessions: (n) => `${n} ${plural(n, "session", "sessions")}`,
24280
+ sessionsOf: (live3, all2) => `${live3} ${plural(live3, "session", "sessions")} sur ${all2}`,
23789
24281
  outside: "Hors projet",
23790
24282
  offline: "hors ligne",
23791
24283
  readOnly: "lecture seule",
@@ -24009,6 +24501,7 @@ var init_fr = __esm({
24009
24501
  noteTooLong: (max) => `Note trop longue : ${max} caract\xE8res au plus. Elle reste dans le champ.`,
24010
24502
  noNote: (n) => `${n} ne prend pas de note ici.`,
24011
24503
  noTerminalOmp: (n) => `${n} est pilot\xE9 par Ledge via le protocole d\u2019OMP : pas de terminal \xE0 attacher.`,
24504
+ seatStopped: (n) => `${n} est arr\xEAt\xE9 : son terminal n\u2019existe plus. 6 ouvre OpenRig, o\xF9 est son rig.`,
24012
24505
  noTerminal: (n) => `Pas de terminal pour ${n}.`,
24013
24506
  attachFailed: (code3) => `tmux s\u2019est arr\xEAt\xE9 avec le code ${code3}.`,
24014
24507
  modelSwitch: "Ce daemon ne change pas le mod\xE8le d\u2019une session en cours.",
@@ -24257,7 +24750,7 @@ var init_en = __esm({
24257
24750
  plural2 = (n, one2, many) => n > 1 ? many : one2;
24258
24751
  en3 = {
24259
24752
  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" },
24753
+ state: { approval: "Needs you", question: "A question", starting: "Starting", idle: "Available", working: "Working", done: "Finished", error: "Error", readonly: "Read-only", stopped: "Stopped" },
24261
24754
  time: { now: "just now", decimal: ".", s: (n) => `${n}s`, min: (n) => `${n} min`, h: (n) => `${n} h`, d: (n) => `${n} d` },
24262
24755
  tabs: { overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", openrig: "OpenRig" },
24263
24756
  bar: {
@@ -24273,6 +24766,7 @@ var init_en = __esm({
24273
24766
  openImage: "open image",
24274
24767
  navigate: "move",
24275
24768
  open: "open",
24769
+ attachSeat: "attach its terminal",
24276
24770
  answer: "answer",
24277
24771
  attach: "attach (tmux)",
24278
24772
  ask: "ask Ledge",
@@ -24346,6 +24840,7 @@ var init_en = __esm({
24346
24840
  noPreview: "No preview.",
24347
24841
  noExchange: "No message yet.",
24348
24842
  sessions: (n) => `${n} ${plural2(n, "session", "sessions")}`,
24843
+ sessionsOf: (live3, all2) => `${live3} ${live3 === 1 ? "session" : "sessions"} of ${all2}`,
24349
24844
  outside: "No project",
24350
24845
  offline: "offline",
24351
24846
  readOnly: "read-only",
@@ -24565,6 +25060,7 @@ var init_en = __esm({
24565
25060
  noteTooLong: (max) => `Note too long: ${max} characters at most. It stays in the field.`,
24566
25061
  noNote: (n) => `${n} takes no note here.`,
24567
25062
  noTerminalOmp: (n) => `${n} is driven by Ledge over OMP's protocol: there is no terminal to attach to.`,
25063
+ seatStopped: (n) => `${n} is stopped: its terminal is gone. 6 opens OpenRig, where its rig is.`,
24568
25064
  noTerminal: (n) => `No terminal for ${n}.`,
24569
25065
  attachFailed: (code3) => `tmux stopped with code ${code3}.`,
24570
25066
  modelSwitch: "This daemon cannot change a running session\u2019s model.",
@@ -24829,15 +25325,19 @@ function overview(sessions, projects) {
24829
25325
  const known2 = new Map((projects?.projects ?? []).map((p) => [p.id, p]));
24830
25326
  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
25327
  const rigNodes = /* @__PURE__ */ new Map();
25328
+ const rigNodeOf = (rig) => {
25329
+ const kind = rig === "kernel" ? "kernel" : "rig";
25330
+ const key = kind === "kernel" ? "g:kernel" : `g:rig:${rig}`;
25331
+ let node2 = rigNodes.get(key);
25332
+ if (!node2) {
25333
+ node2 = { key, label: kind === "kernel" ? "Kernel" : rig, kind, branches: [{ branch: "", sessions: [] }], count: 0 };
25334
+ rigNodes.set(key, node2);
25335
+ }
25336
+ return node2;
25337
+ };
24832
25338
  for (const s of live3) {
24833
25339
  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
- }
25340
+ const node3 = rigNodeOf(s.rig.name);
24841
25341
  node3.branches[0].sessions.push(s);
24842
25342
  node3.count += 1;
24843
25343
  continue;
@@ -24858,6 +25358,12 @@ function overview(sessions, projects) {
24858
25358
  row3.sessions.push(s);
24859
25359
  node2.count += 1;
24860
25360
  }
25361
+ for (const s of sessions) {
25362
+ if (!isStoppedOmpSeat(s) || !s.rig) continue;
25363
+ const node2 = rigNodeOf(s.rig.name);
25364
+ node2.branches[0].sessions.push(s);
25365
+ node2.shown = (node2.shown ?? node2.count) + 1;
25366
+ }
24861
25367
  const groups = [...rigNodes.values()].sort((a, b3) => a.kind === b3.kind ? a.label.localeCompare(b3.label) : a.kind === "kernel" ? 1 : -1);
24862
25368
  const tree2 = [...[...nodes.values()].filter((n) => n.count > 0), ...groups];
24863
25369
  const order2 = tree2.flatMap((n) => n.branches.flatMap((b3) => b3.sessions.map((s) => s.id)));
@@ -25118,7 +25624,7 @@ var init_theme = __esm({
25118
25624
 
25119
25625
  // src/tui/model/cells.ts
25120
25626
  import stringWidth from "string-width";
25121
- function join36(line2, a) {
25627
+ function join38(line2, a) {
25122
25628
  const out = [];
25123
25629
  for (const c of a) {
25124
25630
  const last = out[out.length - 1];
@@ -25133,9 +25639,9 @@ function cut2(line2, width, ellipsis = "\u2026") {
25133
25639
  const a = split2(line2), room = width - cells(ellipsis);
25134
25640
  let n = 0, i = 0;
25135
25641
  while (i < a.length && n + a[i].w <= room) n += a[i++].w;
25136
- const kept = join36(line2, a.slice(0, i));
25642
+ const kept2 = join38(line2, a.slice(0, i));
25137
25643
  const color2 = line2[a[Math.max(0, i - 1)]?.seg ?? 0]?.color;
25138
- return [...kept, { text: ellipsis, ...color2 ? { color: color2 } : {} }];
25644
+ return [...kept2, { text: ellipsis, ...color2 ? { color: color2 } : {} }];
25139
25645
  }
25140
25646
  function wrapSegs(line2, width, first = width) {
25141
25647
  const a = split2(line2);
@@ -25152,7 +25658,7 @@ function wrapSegs(line2, width, first = width) {
25152
25658
  }
25153
25659
  w = Math.max(4, width);
25154
25660
  if (j >= a.length || a[j].g === "\n") {
25155
- out.push(join36(line2, a.slice(i, j)));
25661
+ out.push(join38(line2, a.slice(i, j)));
25156
25662
  i = j + 1;
25157
25663
  if (j >= a.length) break;
25158
25664
  if (i >= a.length) out.push([]);
@@ -25161,7 +25667,7 @@ function wrapSegs(line2, width, first = width) {
25161
25667
  const end2 = a[j].g === " " ? j : space > i ? space : Math.max(j, i + 1);
25162
25668
  let k = end2;
25163
25669
  while (k > i && a[k - 1].g === " ") k--;
25164
- out.push(join36(line2, a.slice(i, k)));
25670
+ out.push(join38(line2, a.slice(i, k)));
25165
25671
  i = end2;
25166
25672
  while (i < a.length && a[i].g === " ") i++;
25167
25673
  if (a[i]?.g === "\n") {
@@ -30374,16 +30880,16 @@ function resolveAllAttention(events, context) {
30374
30880
  let nextEvents;
30375
30881
  while (++index2 < events.length) {
30376
30882
  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)) {
30883
+ let open6 = index2;
30884
+ while (open6--) {
30885
+ if (events[open6][0] === "exit" && events[open6][1].type === "attentionSequence" && events[open6][1]._open && // If the markers are the same:
30886
+ context.sliceSerialize(events[open6][1]).charCodeAt(0) === context.sliceSerialize(events[index2][1]).charCodeAt(0)) {
30887
+ 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
30888
  continue;
30383
30889
  }
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;
30890
+ 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
30891
  const start = {
30386
- ...events[open5][1].end
30892
+ ...events[open6][1].end
30387
30893
  };
30388
30894
  const end2 = {
30389
30895
  ...events[index2][1].start
@@ -30394,7 +30900,7 @@ function resolveAllAttention(events, context) {
30394
30900
  type: use > 1 ? "strongSequence" : "emphasisSequence",
30395
30901
  start,
30396
30902
  end: {
30397
- ...events[open5][1].end
30903
+ ...events[open6][1].end
30398
30904
  }
30399
30905
  };
30400
30906
  const closingSequence = {
@@ -30407,7 +30913,7 @@ function resolveAllAttention(events, context) {
30407
30913
  const text7 = {
30408
30914
  type: use > 1 ? "strongText" : "emphasisText",
30409
30915
  start: {
30410
- ...events[open5][1].end
30916
+ ...events[open6][1].end
30411
30917
  },
30412
30918
  end: {
30413
30919
  ...events[index2][1].start
@@ -30422,26 +30928,26 @@ function resolveAllAttention(events, context) {
30422
30928
  ...closingSequence.end
30423
30929
  }
30424
30930
  };
30425
- events[open5][1].end = {
30931
+ events[open6][1].end = {
30426
30932
  ...openingSequence.start
30427
30933
  };
30428
30934
  events[index2][1].start = {
30429
30935
  ...closingSequence.end
30430
30936
  };
30431
30937
  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]]);
30938
+ if (events[open6][1].end.offset - events[open6][1].start.offset) {
30939
+ nextEvents = push(nextEvents, [["enter", events[open6][1], context], ["exit", events[open6][1], context]]);
30434
30940
  }
30435
30941
  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));
30942
+ nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open6 + 1, index2), context));
30437
30943
  nextEvents = push(nextEvents, [["exit", text7, context], ["enter", closingSequence, context], ["exit", closingSequence, context], ["exit", group, context]]);
30438
30944
  let offset = 0;
30439
30945
  if (events[index2][1].end.offset - events[index2][1].start.offset) {
30440
30946
  offset = 2;
30441
30947
  nextEvents = push(nextEvents, [["enter", events[index2][1], context], ["exit", events[index2][1], context]]);
30442
30948
  }
30443
- splice(events, open5 - 1, index2 - open5 + 3, nextEvents);
30444
- index2 = open5 + nextEvents.length - offset - 2;
30949
+ splice(events, open6 - 1, index2 - open6 + 3, nextEvents);
30950
+ index2 = open6 + nextEvents.length - offset - 2;
30445
30951
  break;
30446
30952
  }
30447
30953
  }
@@ -30473,10 +30979,10 @@ function tokenizeAttention(effects, ok4) {
30473
30979
  }
30474
30980
  const token = effects.exit("attentionSequence");
30475
30981
  const after2 = classifyCharacter(code3);
30476
- const open5 = !after2 || after2 === 2 && before2 || attentionMarkers2.includes(code3) && code3 !== 42 && code3 !== 95;
30982
+ const open6 = !after2 || after2 === 2 && before2 || attentionMarkers2.includes(code3) && code3 !== 42 && code3 !== 95;
30477
30983
  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));
30984
+ token._open = Boolean(marker === 42 ? open6 : open6 && (before2 || !close));
30985
+ token._close = Boolean(marker === 42 ? close : close && (after2 || !open6));
30480
30986
  return ok4(code3);
30481
30987
  }
30482
30988
  }
@@ -30510,9 +31016,9 @@ function tokenizeAutolink(effects, ok4, nok) {
30510
31016
  effects.consume(code3);
30511
31017
  effects.exit("autolinkMarker");
30512
31018
  effects.enter("autolinkProtocol");
30513
- return open5;
31019
+ return open6;
30514
31020
  }
30515
- function open5(code3) {
31021
+ function open6(code3) {
30516
31022
  if (asciiAlpha(code3)) {
30517
31023
  effects.consume(code3);
30518
31024
  return schemeOrEmailAtext;
@@ -30793,9 +31299,9 @@ function tokenizeCharacterReference(effects, ok4, nok) {
30793
31299
  effects.enter("characterReferenceMarker");
30794
31300
  effects.consume(code3);
30795
31301
  effects.exit("characterReferenceMarker");
30796
- return open5;
31302
+ return open6;
30797
31303
  }
30798
- function open5(code3) {
31304
+ function open6(code3) {
30799
31305
  if (code3 === 35) {
30800
31306
  effects.enter("characterReferenceMarkerNumeric");
30801
31307
  effects.consume(code3);
@@ -32196,9 +32702,9 @@ function tokenizeHtmlFlow(effects, ok4, nok) {
32196
32702
  effects.enter("htmlFlow");
32197
32703
  effects.enter("htmlFlowData");
32198
32704
  effects.consume(code3);
32199
- return open5;
32705
+ return open6;
32200
32706
  }
32201
- function open5(code3) {
32707
+ function open6(code3) {
32202
32708
  if (code3 === 33) {
32203
32709
  effects.consume(code3);
32204
32710
  return declarationOpen;
@@ -32546,9 +33052,9 @@ function tokenizeHtmlText(effects, ok4, nok) {
32546
33052
  effects.enter("htmlText");
32547
33053
  effects.enter("htmlTextData");
32548
33054
  effects.consume(code3);
32549
- return open5;
33055
+ return open6;
32550
33056
  }
32551
- function open5(code3) {
33057
+ function open6(code3) {
32552
33058
  if (code3 === 33) {
32553
33059
  effects.consume(code3);
32554
33060
  return declarationOpen;
@@ -32870,12 +33376,12 @@ function resolveAllLabelEnd(events) {
32870
33376
  function resolveToLabelEnd(events, context) {
32871
33377
  let index2 = events.length;
32872
33378
  let offset = 0;
32873
- let open5;
33379
+ let open6;
32874
33380
  let close;
32875
33381
  let media;
32876
33382
  while (index2--) {
32877
33383
  const token = events[index2][1];
32878
- if (open5) {
33384
+ if (open6) {
32879
33385
  if (token.type === "link" || token.type === "labelLink" && token._inactive) {
32880
33386
  break;
32881
33387
  }
@@ -32884,7 +33390,7 @@ function resolveToLabelEnd(events, context) {
32884
33390
  }
32885
33391
  } else if (close) {
32886
33392
  if (events[index2][0] === "enter" && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {
32887
- open5 = index2;
33393
+ open6 = index2;
32888
33394
  if (token.type !== "labelLink") {
32889
33395
  offset = 2;
32890
33396
  break;
@@ -32895,9 +33401,9 @@ function resolveToLabelEnd(events, context) {
32895
33401
  }
32896
33402
  }
32897
33403
  const group = {
32898
- type: events[open5][1].type === "labelLink" ? "link" : "image",
33404
+ type: events[open6][1].type === "labelLink" ? "link" : "image",
32899
33405
  start: {
32900
- ...events[open5][1].start
33406
+ ...events[open6][1].start
32901
33407
  },
32902
33408
  end: {
32903
33409
  ...events[events.length - 1][1].end
@@ -32906,7 +33412,7 @@ function resolveToLabelEnd(events, context) {
32906
33412
  const label2 = {
32907
33413
  type: "label",
32908
33414
  start: {
32909
- ...events[open5][1].start
33415
+ ...events[open6][1].start
32910
33416
  },
32911
33417
  end: {
32912
33418
  ...events[close][1].end
@@ -32915,20 +33421,20 @@ function resolveToLabelEnd(events, context) {
32915
33421
  const text7 = {
32916
33422
  type: "labelText",
32917
33423
  start: {
32918
- ...events[open5 + offset + 2][1].end
33424
+ ...events[open6 + offset + 2][1].end
32919
33425
  },
32920
33426
  end: {
32921
33427
  ...events[close - 2][1].start
32922
33428
  }
32923
33429
  };
32924
33430
  media = [["enter", group, context], ["enter", label2, context]];
32925
- media = push(media, events.slice(open5 + 1, open5 + offset + 3));
33431
+ media = push(media, events.slice(open6 + 1, open6 + offset + 3));
32926
33432
  media = push(media, [["enter", text7, context]]);
32927
- media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open5 + offset + 4, close - 3), context));
33433
+ media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open6 + offset + 4, close - 3), context));
32928
33434
  media = push(media, [["exit", text7, context], events[close - 2], events[close - 1], ["exit", label2, context]]);
32929
33435
  media = push(media, events.slice(close + 1));
32930
33436
  media = push(media, [["exit", group, context]]);
32931
- splice(events, open5, events.length, media);
33437
+ splice(events, open6, events.length, media);
32932
33438
  return events;
32933
33439
  }
32934
33440
  function tokenizeLabelEnd(effects, ok4, nok) {
@@ -33099,9 +33605,9 @@ function tokenizeLabelStartImage(effects, ok4, nok) {
33099
33605
  effects.enter("labelImageMarker");
33100
33606
  effects.consume(code3);
33101
33607
  effects.exit("labelImageMarker");
33102
- return open5;
33608
+ return open6;
33103
33609
  }
33104
- function open5(code3) {
33610
+ function open6(code3) {
33105
33611
  if (code3 === 91) {
33106
33612
  effects.enter("labelMarker");
33107
33613
  effects.consume(code3);
@@ -33933,30 +34439,30 @@ function gfmStrikethrough(options) {
33933
34439
  let index2 = -1;
33934
34440
  while (++index2 < events.length) {
33935
34441
  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) {
34442
+ let open6 = index2;
34443
+ while (open6--) {
34444
+ if (events[open6][0] === "exit" && events[open6][1].type === "strikethroughSequenceTemporary" && events[open6][1]._open && // If the sizes are the same:
34445
+ events[index2][1].end.offset - events[index2][1].start.offset === events[open6][1].end.offset - events[open6][1].start.offset) {
33940
34446
  events[index2][1].type = "strikethroughSequence";
33941
- events[open5][1].type = "strikethroughSequence";
34447
+ events[open6][1].type = "strikethroughSequence";
33942
34448
  const strikethrough = {
33943
34449
  type: "strikethrough",
33944
- start: Object.assign({}, events[open5][1].start),
34450
+ start: Object.assign({}, events[open6][1].start),
33945
34451
  end: Object.assign({}, events[index2][1].end)
33946
34452
  };
33947
34453
  const text7 = {
33948
34454
  type: "strikethroughText",
33949
- start: Object.assign({}, events[open5][1].end),
34455
+ start: Object.assign({}, events[open6][1].end),
33950
34456
  end: Object.assign({}, events[index2][1].start)
33951
34457
  };
33952
- const nextEvents = [["enter", strikethrough, context], ["enter", events[open5][1], context], ["exit", events[open5][1], context], ["enter", text7, context]];
34458
+ const nextEvents = [["enter", strikethrough, context], ["enter", events[open6][1], context], ["exit", events[open6][1], context], ["enter", text7, context]];
33953
34459
  const insideSpan2 = context.parser.constructs.insideSpan.null;
33954
34460
  if (insideSpan2) {
33955
- splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan2, events.slice(open5 + 1, index2), context));
34461
+ splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan2, events.slice(open6 + 1, index2), context));
33956
34462
  }
33957
34463
  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;
34464
+ splice(events, open6 - 1, index2 - open6 + 3, nextEvents);
34465
+ index2 = open6 + nextEvents.length - 2;
33960
34466
  break;
33961
34467
  }
33962
34468
  }
@@ -34558,8 +35064,8 @@ function gfmTaskListItem() {
34558
35064
  }
34559
35065
  function tokenizeTasklistCheck(effects, ok4, nok) {
34560
35066
  const self = this;
34561
- return open5;
34562
- function open5(code3) {
35067
+ return open6;
35068
+ function open6(code3) {
34563
35069
  if (
34564
35070
  // Exit if there’s stuff before.
34565
35071
  self.previous !== null || // Exit if not in the first content that is the first child of a list
@@ -36048,8 +36554,8 @@ function compiler(options) {
36048
36554
  events[start][1]._spread = listSpread;
36049
36555
  }
36050
36556
  function opener(create, and) {
36051
- return open5;
36052
- function open5(token) {
36557
+ return open6;
36558
+ function open6(token) {
36053
36559
  enter.call(this, create(token), token);
36054
36560
  if (and) and.call(this, token);
36055
36561
  }
@@ -36081,19 +36587,19 @@ function compiler(options) {
36081
36587
  }
36082
36588
  function exit3(token, onExitError) {
36083
36589
  const node2 = this.stack.pop();
36084
- const open5 = this.tokenStack.pop();
36085
- if (!open5) {
36590
+ const open6 = this.tokenStack.pop();
36591
+ if (!open6) {
36086
36592
  throw new Error("Cannot close `" + token.type + "` (" + stringifyPosition({
36087
36593
  start: token.start,
36088
36594
  end: token.end
36089
36595
  }) + "): it\u2019s not open");
36090
36596
  }
36091
- if (open5[0].type !== token.type) {
36597
+ if (open6[0].type !== token.type) {
36092
36598
  if (onExitError) {
36093
- onExitError.call(this, token, open5[0]);
36599
+ onExitError.call(this, token, open6[0]);
36094
36600
  } else {
36095
- const handler = open5[1] || defaultOnError;
36096
- handler.call(this, token, open5[0]);
36601
+ const handler = open6[1] || defaultOnError;
36602
+ handler.call(this, token, open6[0]);
36097
36603
  }
36098
36604
  }
36099
36605
  node2.position.end = point2(token.end);
@@ -38351,10 +38857,10 @@ function quote4(text7, width, max, g) {
38351
38857
  const w = width - 4;
38352
38858
  const lines = wrapSegs([{ text: `\xAB ${oneLine2(text7)} \xBB`, color: C.dim }], w);
38353
38859
  if (lines.length <= max) return lines.map((l) => [pad(4), ...l]);
38354
- const kept = lines.slice(0, max);
38355
- const last = kept[max - 1];
38356
- kept[max - 1] = [...cut2(last, w - 2 - cells(g.ellipsis), ""), { text: `${g.ellipsis} \xBB`, color: C.dim }];
38357
- return kept.map((l) => [pad(4), ...l]);
38860
+ const kept2 = lines.slice(0, max);
38861
+ const last = kept2[max - 1];
38862
+ kept2[max - 1] = [...cut2(last, w - 2 - cells(g.ellipsis), ""), { text: `${g.ellipsis} \xBB`, color: C.dim }];
38863
+ return kept2.map((l) => [pad(4), ...l]);
38358
38864
  }
38359
38865
  function aboutDraft(body, t, g) {
38360
38866
  const line2 = firstLine2(body);
@@ -39205,7 +39711,7 @@ var init_KeyBar = __esm({
39205
39711
  // src/tui/ui/Frame.tsx
39206
39712
  import { Box as Box5, Text as Text4 } from "ink";
39207
39713
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
39208
- function Frame({ crumb, crumbShort, keys, kept, children }) {
39714
+ function Frame({ crumb, crumbShort, keys, kept: kept2, children }) {
39209
39715
  const { d, machines, t, g, lastToast, editing, client } = useTui();
39210
39716
  const { rows, columns: columns2 } = useScreenSize();
39211
39717
  const live3 = listed(d.sessions);
@@ -39213,7 +39719,7 @@ function Frame({ crumb, crumbShort, keys, kept, children }) {
39213
39719
  const waiting2 = pending.length + openDecisions(useTasks(client)).length;
39214
39720
  const status = d.status === "open" ? null : d.status === "connecting" ? t.bar.connecting : t.bar.unreachable;
39215
39721
  if (columns2 < MIN.columns || rows < MIN.rows) {
39216
- const why = [...pending[0] ? [t.small.waits(pending[0].name, pending[0].pending?.kind === "question")] : [], ...kept ? [t.small.kept] : []].join(" \xB7 ");
39722
+ const why = [...pending[0] ? [t.small.waits(pending[0].name, pending[0].pending?.kind === "question")] : [], ...kept2 ? [t.small.kept] : []].join(" \xB7 ");
39217
39723
  return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", height: rows, width: columns2, alignItems: "center", justifyContent: "center", children: [
39218
39724
  /* @__PURE__ */ jsxs4(Text4, { color: C.coral, bold: true, children: [
39219
39725
  g.logo,
@@ -39513,7 +40019,8 @@ function Overview() {
39513
40019
  return true;
39514
40020
  }
39515
40021
  if (key.return) {
39516
- if (selected) go("session");
40022
+ if (selected && isOmpSeat(selected)) attach2(selected);
40023
+ else if (selected) go("session");
39517
40024
  return true;
39518
40025
  }
39519
40026
  if (!selected) return false;
@@ -39534,7 +40041,7 @@ function Overview() {
39534
40041
  return false;
39535
40042
  });
39536
40043
  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) }] : [],
40044
+ ...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
40045
  ...n.branches.flatMap((b3) => b3.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: kind === "rig" ? 2 : 0, group: kind })))
39539
40046
  ]);
39540
40047
  const rigRows = groupRows("rig"), kernelRows = groupRows("kernel");
@@ -39556,6 +40063,7 @@ function Overview() {
39556
40063
  const stateW = Math.max(...Object.values(t.state).map((x) => x.length)) + 1;
39557
40064
  const topicW = Math.max(6, inner - 6 - nameW - stateW - 3);
39558
40065
  const askW = Math.max(6, inner - 2 - nameW - 9 - 7);
40066
+ const roleW = Math.max(11, ...rigRows.map((r) => r.kind === "session" ? [...r.s.rig?.role ?? ""].length + 2 : 0));
39559
40067
  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
40068
  const st = botState(s);
39561
40069
  return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: s.id === sel ? C.sel : void 0, children: [
@@ -39621,16 +40129,18 @@ function Overview() {
39621
40129
  ] }) : null,
39622
40130
  /* @__PURE__ */ jsxs6(Text7, { color: C.faint, children: [
39623
40131
  " ",
39624
- t.overview.sessions(r.count)
40132
+ r.shown !== void 0 ? t.overview.sessionsOf(r.count, r.shown) : t.overview.sessions(r.count)
39625
40133
  ] })
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) });
40134
+ ] }, 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
40135
  const rigsBox = groupBox(t.overview.rigsGroup, rigRows, rigsHeight);
39628
40136
  const kernelBox = groupBox(t.overview.kernelGroup, kernelRows, kernelHeight);
40137
+ const stopped2 = !!selected && isStoppedOmpSeat(selected);
39629
40138
  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]] : [
40139
+ // On an OMP seat ↵ attaches its terminal; on a stopped one, neither ↵ nor a has anything to attach.
39630
40140
  ["j k", t.keys.navigate],
39631
- ["\u21B5", t.keys.open],
40141
+ ...stopped2 ? [] : [["\u21B5", selected && isOmpSeat(selected) ? t.keys.attachSeat : t.keys.open]],
39632
40142
  ...view.yours.length ? [["tab", t.keys.next], ["y n", t.keys.answer]] : [],
39633
- ["a", t.keys.attach],
40143
+ ...stopped2 ? [] : [["a", t.keys.attach]],
39634
40144
  ...writable2 ? [["m", t.keys.writeProject]] : [],
39635
40145
  ...decisions.length ? [["D", t.keys.decide]] : [],
39636
40146
  ...near.length ? [["H", t.keys.handover]] : [],
@@ -39705,21 +40215,23 @@ function Overview() {
39705
40215
  ) : null
39706
40216
  ] });
39707
40217
  }
39708
- function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }) {
40218
+ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group, roleW = 11 }) {
39709
40219
  const { t, g } = useTui();
39710
40220
  const st = botState(s);
39711
40221
  const n = helperCount(s);
40222
+ const stopped2 = isStoppedOmpSeat(s);
40223
+ const roleTool = group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : `${(s.rig?.role ?? "").padEnd(roleW)}${s.tool}`;
39712
40224
  return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: on ? C.sel : void 0, children: [
39713
40225
  /* @__PURE__ */ jsx10(Text7, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
39714
40226
  /* @__PURE__ */ jsx10(Text7, { children: " ".repeat(2 + indent2) }),
39715
- /* @__PURE__ */ jsxs6(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: [
40227
+ /* @__PURE__ */ jsxs6(Text7, { color: s.offline || stopped2 ? C.faint : STATE_COLOR[st], children: [
39716
40228
  g[st],
39717
40229
  " "
39718
40230
  ] }),
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: [
40231
+ /* @__PURE__ */ jsx10(Text7, { color: stopped2 ? C.faint : s.offline ? C.dim : C.fg, bold: !stopped2, children: fit3(s.name, nameW) }),
40232
+ group ? /* @__PURE__ */ jsxs6(Text7, { color: stopped2 ? C.faint : C.dim, children: [
39721
40233
  " ",
39722
- fit3(group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : s.rig?.role ?? "", topicW - 1),
40234
+ fit3(roleTool, topicW - 1),
39723
40235
  " "
39724
40236
  ] }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
39725
40237
  s.rig ? /* @__PURE__ */ jsxs6(Text7, { color: C.blue, children: [
@@ -39731,7 +40243,7 @@ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }
39731
40243
  " "
39732
40244
  ] })
39733
40245
  ] }),
39734
- /* @__PURE__ */ jsx10(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: fit3(s.offline ? t.overview.offline : t.state[st], stateW).trimEnd() }),
40246
+ /* @__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
40247
  n > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: C.violet, children: [
39736
40248
  " +",
39737
40249
  n
@@ -39770,7 +40282,7 @@ var init_Overview = __esm({
39770
40282
  // src/tui/images.tsx
39771
40283
  import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
39772
40284
  import { tmpdir as tmpdir3 } from "os";
39773
- import { join as join37 } from "path";
40285
+ import { join as join39 } from "path";
39774
40286
  import { useRef as useRef8, useState as useState4 } from "react";
39775
40287
  import { Text as Text8 } from "ink";
39776
40288
  import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -39873,9 +40385,9 @@ function useOpenImage() {
39873
40385
  const { ref, route } = item;
39874
40386
  void (async () => {
39875
40387
  const { mime, data } = await getImage(client, ref.id, route);
39876
- const dir = join37(tmpdir3(), "ledge-images");
40388
+ const dir = join39(tmpdir3(), "ledge-images");
39877
40389
  await mkdir13(dir, { recursive: true, mode: 448 });
39878
- const file = join37(dir, `${ref.id}.${EXT2[mime]}`);
40390
+ const file = join39(dir, `${ref.id}.${EXT2[mime]}`);
39879
40391
  await writeFile17(file, Buffer.from(data, "base64"), { mode: 384 });
39880
40392
  await openFile2(file);
39881
40393
  })().catch((e) => toast({ text: t.images.openFailed(e instanceof AttachmentFailure ? e.reason : e.message), tone: "error" }));
@@ -40258,18 +40770,18 @@ function SessionScreen() {
40258
40770
  return /* @__PURE__ */ jsx13(SessionView, { s }, s.id);
40259
40771
  }
40260
40772
  function SessionView({ s }) {
40261
- const { client, d, t, g, actions, attach: attach2, toast, machines, kept, lastToast, tone: tone2, select, go } = useTui();
40773
+ const { client, d, t, g, actions, attach: attach2, toast, machines, kept: kept2, lastToast, tone: tone2, select, go } = useTui();
40262
40774
  const { columns: columns2, rows } = useScreenSize();
40263
40775
  const [mode, setMode] = useState8(() => {
40264
- if (kept.openReply !== s.id) return "read";
40265
- kept.openReply = void 0;
40776
+ if (kept2.openReply !== s.id) return "read";
40777
+ kept2.openReply = void 0;
40266
40778
  return "reply";
40267
40779
  });
40268
- const [draft, setDraftState] = useState8(() => kept.drafts.get(s.id) ?? "");
40780
+ const [draft, setDraftState] = useState8(() => kept2.drafts.get(s.id) ?? "");
40269
40781
  const setDraft = (x) => {
40270
40782
  setDraftState(x);
40271
- if (x) kept.drafts.set(s.id, x);
40272
- else kept.drafts.delete(s.id);
40783
+ if (x) kept2.drafts.set(s.id, x);
40784
+ else kept2.drafts.delete(s.id);
40273
40785
  };
40274
40786
  const [browsing, setBrowsing] = useState8(false);
40275
40787
  const [diffs, setDiffs] = useState8(false);
@@ -40333,9 +40845,9 @@ function SessionView({ s }) {
40333
40845
  toast({ text: t.toast.readOnly(target.name), tone: "info" });
40334
40846
  return;
40335
40847
  }
40336
- const quote5 = aboutDraft(text7, t, g), was = kept.drafts.get(target.id) ?? "";
40337
- kept.drafts.set(target.id, was.startsWith(quote5) ? was : `${quote5}${was}`);
40338
- kept.openReply = target.id;
40848
+ const quote5 = aboutDraft(text7, t, g), was = kept2.drafts.get(target.id) ?? "";
40849
+ kept2.drafts.set(target.id, was.startsWith(quote5) ? was : `${quote5}${was}`);
40850
+ kept2.openReply = target.id;
40339
40851
  select(target.id);
40340
40852
  go("session");
40341
40853
  };
@@ -40357,7 +40869,7 @@ function SessionView({ s }) {
40357
40869
  }
40358
40870
  const taken = withImages2 ? chips2.clear() : [];
40359
40871
  const back2 = (x) => {
40360
- if (!kept.drafts.get(s.id)) setDraft(x);
40872
+ if (!kept2.drafts.get(s.id)) setDraft(x);
40361
40873
  if (taken.length) chips2.putBack(taken);
40362
40874
  };
40363
40875
  const how = taken.length ? { attachments: taken.flatMap((c) => c.id ? [c.id] : []) } : void 0;
@@ -40365,7 +40877,7 @@ function SessionView({ s }) {
40365
40877
  if (taken.length) chips2.putBack(taken);
40366
40878
  return false;
40367
40879
  }
40368
- if (text7.trim()) kept.sent.set(s.id, [...kept.sent.get(s.id) ?? [], text7]);
40880
+ if (text7.trim()) kept2.sent.set(s.id, [...kept2.sent.get(s.id) ?? [], text7]);
40369
40881
  if (!fromHistory) setDraft("");
40370
40882
  setMode("read");
40371
40883
  return true;
@@ -40517,7 +41029,7 @@ function SessionView({ s }) {
40517
41029
  onSubmit: send2,
40518
41030
  onEscape: leave,
40519
41031
  onBrowse: setBrowsing,
40520
- history: kept.sent.get(s.id) ?? [],
41032
+ history: kept2.sent.get(s.id) ?? [],
40521
41033
  active: true,
40522
41034
  placeholder: mode === "note" ? t.session.noteFor(s.name) : t.session.replyTo(s.name)
40523
41035
  }
@@ -40660,11 +41172,11 @@ function cardPlan(actions, room) {
40660
41172
  const raw = rows.map((r) => r.state ?? "proposed");
40661
41173
  const stopAt = raw.findIndex((s) => s === "failed" || s === "refused" || s === "stale");
40662
41174
  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]);
41175
+ const open6 = shown.flatMap((s, i) => ["done", "running", "denied", "stale", "cut"].includes(s) ? [] : [i]);
40664
41176
  const skip = stopAt >= 0 && raw[stopAt] !== "failed";
40665
- const rowsAll = skip ? open5.filter((i) => i !== stopAt) : stopAt >= 0 && states[stopAt] === "cut" ? [] : open5;
41177
+ const rowsAll = skip ? open6.filter((i) => i !== stopAt) : stopAt >= 0 && states[stopAt] === "cut" ? [] : open6;
40666
41178
  const all2 = { kind: stopAt < 0 ? "all" : skip ? "skip" : "resume", rows: rowsAll, k: stopAt + 1 };
40667
- return { rows, shown, open: open5, stopAt, all: all2, cutLines };
41179
+ return { rows, shown, open: open6, stopAt, all: all2, cutLines };
40668
41180
  }
40669
41181
  function firstRow(p) {
40670
41182
  const act = p.shown.findIndex((s) => s === "proposed" || s === "held" || s === "failed");
@@ -40965,13 +41477,13 @@ import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef11, useS
40965
41477
  import { Box as Box12, Text as Text12, useInput as useInput4 } from "ink";
40966
41478
  import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
40967
41479
  function Ask() {
40968
- const { client, d, machines, t, g, toast, go, kept, small, tone: tone2, select } = useTui();
41480
+ const { client, d, machines, t, g, toast, go, kept: kept2, small, tone: tone2, select } = useTui();
40969
41481
  const { columns: columns2, rows: height } = useScreenSize();
40970
- const [draft, setDraftState] = useState10(() => kept.drafts.get("ask") ?? "");
41482
+ const [draft, setDraftState] = useState10(() => kept2.drafts.get("ask") ?? "");
40971
41483
  const setDraft = (x) => {
40972
41484
  setDraftState(x);
40973
- if (x) kept.drafts.set("ask", x);
40974
- else kept.drafts.delete("ask");
41485
+ if (x) kept2.drafts.set("ask", x);
41486
+ else kept2.drafts.delete("ask");
40975
41487
  };
40976
41488
  const [browsing, setBrowsing] = useState10(false);
40977
41489
  const [confirm, setConfirm] = useState10(false);
@@ -40994,7 +41506,7 @@ function Ask() {
40994
41506
  if (turn.error) {
40995
41507
  sentAsk.current = null;
40996
41508
  chips2.putBack(s.taken);
40997
- setDraft(kept.drafts.get("ask") || s.text);
41509
+ setDraft(kept2.drafts.get("ask") || s.text);
40998
41510
  } else if (turn.answeredAt) sentAsk.current = null;
40999
41511
  }, [d.chat]);
41000
41512
  const cards = useMemo4(() => openCards(d.chat).slice(0, 2), [d.chat]);
@@ -41051,7 +41563,7 @@ function Ask() {
41051
41563
  const asked2 = { question: text7.trim(), askedAt: (/* @__PURE__ */ new Date()).toISOString() };
41052
41564
  daemonStore.beginTurn({ qid, ...asked2, steps: [] });
41053
41565
  client.send({ type: "chat.ask", qid, text: asked2.question, ...images.length ? { images } : {} });
41054
- kept.asked.push(asked2);
41566
+ kept2.asked.push(asked2);
41055
41567
  if (images.length) sentAsk.current = { qid, taken: chips2.clear(), text: asked2.question };
41056
41568
  if (!fromHistory) setDraft("");
41057
41569
  return true;
@@ -41262,7 +41774,7 @@ function Ask() {
41262
41774
  ["ctrl+n", t.keys.newChat]
41263
41775
  ];
41264
41776
  };
41265
- const history = askHistory(d.chat, kept.asked);
41777
+ const history = askHistory(d.chat, kept2.asked);
41266
41778
  const keys = confirm ? [["n esc", t.keys.keep], ["y", t.keys.wipe]] : focus === -1 && browsing ? [["\u21B5", t.keys.resend], ["\u2191 \u2193", t.keys.history], ["esc", t.keys.backToDraft]] : focus === -1 ? [["\u21B5", t.keys.sendOnly], ...review$, ["alt+\u21B5", t.keys.newLine], ["esc", t.keys.back], ["PgUp PgDn", t.keys.scroll], ["\u2191 \u2193", t.keys.history], ["tab", cards.length ? t.keys.nextCard : t.keys.conversation], ["alt+v", t.keys.image], ...turnImages.length ? [["alt+o", t.keys.openImage]] : [], ["ctrl+n", t.keys.newChat], ...stop] : focus === CONVO ? [["j k", t.keys.line], ["PgUp PgDn", t.keys.page], ["G", t.keys.end], ...stop, ["tab", t.keys.question], ["esc", t.keys.question]] : card?.kind === "action" ? [...actionKeys(card.qid), ...stop, ...review$] : [["\u21B5", t.keys.send], ["tab", focus + 1 < cards.length ? t.keys.nextCard : t.keys.conversation], ["ctrl+n", t.keys.newChat], ...stop, ["esc", t.keys.back]];
41267
41779
  const foot = up > 0 ? asking && live3?.qid === asking.qid ? t.ask.answering : t.session.scrolled(lines.length - view - up, up) : lines.length > view ? focus === CONVO ? t.session.above(lines.length - view) : t.ask.above(lines.length - view) : void 0;
41268
41780
  const reading = focus === CONVO;
@@ -42052,10 +42564,10 @@ function matches(r, q) {
42052
42564
  }
42053
42565
  function listItems2(roles, query, builtinOpen) {
42054
42566
  const by = (a, b3) => a.name.localeCompare(b3.name) || a.id.localeCompare(b3.id);
42055
- const kept = roles.filter((r) => matches(r, query)).sort(by);
42056
- 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 })) : []] };
42567
+ const kept2 = roles.filter((r) => matches(r, query)).sort(by);
42568
+ const mine = kept2.filter((r) => r.source === "user"), builtin = kept2.filter((r) => r.source === "builtin");
42569
+ const open6 = builtinOpen || query.trim() !== "";
42570
+ return { mine, builtin, items: [...mine.map((role) => ({ kind: "role", role })), ...builtin.length ? [{ kind: "builtin" }] : [], ...open6 ? builtin.map((role) => ({ kind: "role", role })) : []] };
42059
42571
  }
42060
42572
  function listLines(roles, o, t, g) {
42061
42573
  const r = t.roles, w = o.width;
@@ -42087,11 +42599,11 @@ function listLines(roles, o, t, g) {
42087
42599
  if (mine.length) lines.push([]);
42088
42600
  const s = i === o.sel;
42089
42601
  if (s) selLine = lines.length;
42090
- const open5 = o.builtinOpen || o.query.trim() !== "";
42602
+ const open6 = o.builtinOpen || o.query.trim() !== "";
42091
42603
  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 } : {} }]);
42604
+ 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
42605
  i++;
42094
- if (open5) builtin.forEach(roleLine);
42606
+ if (open6) builtin.forEach(roleLine);
42095
42607
  }
42096
42608
  return { lines, selLine };
42097
42609
  }
@@ -42187,7 +42699,7 @@ function param(label2, value, o, t, g, w, narrow, extra = {}) {
42187
42699
  const usedL = cells(lab.map((x) => x.text).join("")), usedV = cells(val.map((x) => x.text).join(""));
42188
42700
  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
42701
  }
42190
- function paramRows(d, seat2, files, open5, o, t) {
42702
+ function paramRows(d, seat2, files, open6, o, t) {
42191
42703
  const r = t.roles, w = o.width, g = o.g, n = o.narrow;
42192
42704
  const P = (label2, value, origin, extra = {}) => param(label2, value, origin, t, g, w, n, extra);
42193
42705
  const out = [];
@@ -42228,8 +42740,8 @@ function paramRows(d, seat2, files, open5, o, t) {
42228
42740
  const u = prof.uses;
42229
42741
  const loadout = seat2?.live?.skills;
42230
42742
  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" });
42743
+ out.push(P(r.p.skills, v(`${r.skillsProjected(loadout.length)} ${open6.has("loadout") ? g.unfolded : g.folded}`), { kind: "inherited", from: "launch" }, { fold: "loadout" }));
42744
+ 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
42745
  }
42234
42746
  const list3 = (label2, l, what, fold) => {
42235
42747
  if (!l.declared) {
@@ -42243,9 +42755,9 @@ function paramRows(d, seat2, files, open5, o, t) {
42243
42755
  l.items.forEach((x, i) => {
42244
42756
  const na = !!(seat2 && x.runtime && seat2.runtime.value && x.runtime !== seat2.runtime.value);
42245
42757
  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 } : {});
42758
+ 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
42759
  out.push(na ? { ...row3, line: row3.line.map((s) => ({ ...s, color: C.faint })) } : row3);
42248
- if (key && open5.has(key)) {
42760
+ if (key && open6.has(key)) {
42249
42761
  const body = x.plugin ? x.plugin.skills.join(", ") : (x.absolutePath && files[x.absolutePath]?.content) ?? t.roles.reading;
42250
42762
  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
42763
  }
@@ -42333,9 +42845,9 @@ function yamlRows(d, seat2, files, o, t) {
42333
42845
  }
42334
42846
  return out;
42335
42847
  }
42336
- function filesFor(d, seat2, tab, at, open5) {
42848
+ function filesFor(d, seat2, tab, at, open6) {
42337
42849
  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);
42850
+ if (tab === 1) return (profileOf(d, seat2)?.uses.runtimeResources.items ?? []).filter((x) => x.absolutePath && open6.has(`rr:${x.id}`)).map((x) => x.absolutePath);
42339
42851
  if (tab === 2) {
42340
42852
  const p = pieces(d, seat2)[at];
42341
42853
  return p?.kind === "entry" && p.entry.absolutePath && !isMissing(p.entry) ? [p.entry.absolutePath] : [];
@@ -42412,7 +42924,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42412
42924
  const [scroll, setScroll] = useState14(0);
42413
42925
  const [at, setAt] = useState14(0);
42414
42926
  const [reading, setReading] = useState14(false);
42415
- const [open5, setOpen] = useState14(/* @__PURE__ */ new Set());
42927
+ const [open6, setOpen] = useState14(/* @__PURE__ */ new Set());
42416
42928
  const [files, setFiles] = useState14({});
42417
42929
  const { items } = listItems2(roles.roles, query, builtinOpen);
42418
42930
  const firstRole = items.find((i) => i.kind === "role");
@@ -42438,7 +42950,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42438
42950
  asks.current.reset();
42439
42951
  setFiles({});
42440
42952
  }, [currentId, machine?.id]);
42441
- const wanted = (d ? filesFor(d, seat2, shownTab, at, open5) : []).join("\n");
42953
+ const wanted = (d ? filesFor(d, seat2, shownTab, at, open6) : []).join("\n");
42442
42954
  useEffect13(() => {
42443
42955
  if (!wanted || !machine || !currentId) return;
42444
42956
  const id = currentId, mid = machine.id;
@@ -42451,7 +42963,7 @@ function RolesScreen({ onRigs, machineId, onMachine }) {
42451
42963
  const bodyH = Math.max(3, rows - 4);
42452
42964
  const head2 = d && summary ? headRows(d, summary, { seat: seat2, tab: shownTab, width: w, narrow: !wide, seats }, t) : [];
42453
42965
  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);
42966
+ 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
42967
  const room = Math.max(1, bodyH - head2.length);
42456
42968
  const maxScroll = Math.max(0, body.length - room);
42457
42969
  const params = shownTab === 1 ? body.map((x, i) => x.rail ? i : -1).filter((i) => i >= 0) : [];
@@ -42709,7 +43221,7 @@ function RigsView({ initialMachine, onRoles }) {
42709
43221
  const rigs = listedRigs(state2);
42710
43222
  const [at, setAt] = useState15(0);
42711
43223
  const selectedId = useRef13(null);
42712
- const [open5, setOpen] = useState15(/* @__PURE__ */ new Set());
43224
+ const [open6, setOpen] = useState15(/* @__PURE__ */ new Set());
42713
43225
  const [seatAt, setSeatAt] = useState15(null);
42714
43226
  const [confirm, setConfirm] = useState15(null);
42715
43227
  const [seats, setSeats] = useState15(null);
@@ -42719,7 +43231,7 @@ function RigsView({ initialMachine, onRoles }) {
42719
43231
  }, [ids]);
42720
43232
  const current2 = rigs[Math.min(at, rigs.length - 1)];
42721
43233
  selectedId.current = current2?.id ?? null;
42722
- const seatNodes = current2 && open5.has(current2.id) ? current2.nodes : [];
43234
+ const seatNodes = current2 && open6.has(current2.id) ? current2.nodes : [];
42723
43235
  const seatSel = seatAt !== null && seatAt < seatNodes.length ? seatAt : null;
42724
43236
  const down = () => {
42725
43237
  if (seatSel !== null ? seatSel + 1 < seatNodes.length : seatNodes.length > 0) {
@@ -42737,7 +43249,7 @@ function RigsView({ initialMachine, onRoles }) {
42737
43249
  }
42738
43250
  const n = step2(at, -1, rigs.length), prev = rigs[n];
42739
43251
  setAt(n);
42740
- setSeatAt(prev && open5.has(prev.id) && prev.nodes.length ? prev.nodes.length - 1 : null);
43252
+ setSeatAt(prev && open6.has(prev.id) && prev.nodes.length ? prev.nodes.length - 1 : null);
42741
43253
  };
42742
43254
  const run4 = (a) => {
42743
43255
  void rig.act(a).then((d) => {
@@ -42897,12 +43409,12 @@ function RigsView({ initialMachine, onRoles }) {
42897
43409
  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
43410
  const crumb = [state2?.version ? o.version(state2.version) : o.title, machine?.label, t.roles.rigs].filter(Boolean).join(" \xB7 ");
42899
43411
  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 }),
43412
+ !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
43413
  confirm ? /* @__PURE__ */ jsx22(ConfirmBox, { confirm }) : null,
42902
43414
  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
43415
  ] });
42904
43416
  }
42905
- function Body({ state: state2, rigs, at, seatAt, open: open5, machineLabel: machineLabel2, roleOf: roleOf4 }) {
43417
+ function Body({ state: state2, rigs, at, seatAt, open: open6, machineLabel: machineLabel2, roleOf: roleOf4 }) {
42906
43418
  const { t } = useTui();
42907
43419
  const o = t.openrig;
42908
43420
  const up = daemonUp(state2);
@@ -42940,10 +43452,10 @@ function Body({ state: state2, rigs, at, seatAt, open: open5, machineLabel: mach
42940
43452
  ] }, n.session))
42941
43453
  ] })
42942
43454
  ] }),
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)) })
43455
+ /* @__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
43456
  ] });
42945
43457
  }
42946
- function RigRow({ rig, selected, seatAt, open: open5, roleOf: roleOf4 }) {
43458
+ function RigRow({ rig, selected, seatAt, open: open6, roleOf: roleOf4 }) {
42947
43459
  const { t } = useTui();
42948
43460
  const o = t.openrig;
42949
43461
  const look = rigLook(rig), l = LOOK[look];
@@ -42953,14 +43465,14 @@ function RigRow({ rig, selected, seatAt, open: open5, roleOf: roleOf4 }) {
42953
43465
  return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
42954
43466
  /* @__PURE__ */ jsx22(Box17, { backgroundColor: selected ? C.sel : void 0, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
42955
43467
  /* @__PURE__ */ jsx22(Text18, { color: selected ? C.coral : C.faint, children: selected ? "\u258C" : " " }),
42956
- /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: open5 ? "\u25BE " : "\u25B8 " }),
43468
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: open6 ? "\u25BE " : "\u25B8 " }),
42957
43469
  /* @__PURE__ */ jsx22(Text18, { color: C.fg, bold: true, children: rig.name.padEnd(16) }),
42958
43470
  /* @__PURE__ */ jsx22(Text18, { color: l.color(), children: ` ${l.glyph} ${o.look[look]}`.padEnd(18) }),
42959
43471
  /* @__PURE__ */ jsx22(Text18, { color: C.fg, children: o.seats(rig.running, rig.seats).padEnd(7) }),
42960
43472
  note ? /* @__PURE__ */ jsx22(Text18, { color: note.color, children: note.text.padEnd(34) }) : /* @__PURE__ */ jsx22(Text18, { children: "".padEnd(34) }),
42961
43473
  /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: when })
42962
43474
  ] }) }),
42963
- open5 ? rig.nodes.map((n, i) => {
43475
+ open6 ? rig.nodes.map((n, i) => {
42964
43476
  const s = i === seatAt, bg = s ? C.sel : void 0, role = roleOf4(rig.name, n.logicalId);
42965
43477
  return /* @__PURE__ */ jsx22(Box17, { backgroundColor: bg, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
42966
43478
  /* @__PURE__ */ jsx22(Text18, { color: C.coral, children: s ? "\u258C" : " " }),
@@ -43417,7 +43929,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
43417
43929
  const [lastToast, setToast] = useState17(null);
43418
43930
  const [editing, setEditingState] = useState17(false);
43419
43931
  const keys = useRef15(null);
43420
- const kept = useRef15({ drafts: /* @__PURE__ */ new Map(), sent: /* @__PURE__ */ new Map(), asked: [] }).current;
43932
+ const kept2 = useRef15({ drafts: /* @__PURE__ */ new Map(), sent: /* @__PURE__ */ new Map(), asked: [] }).current;
43421
43933
  const armed = useRef15(0);
43422
43934
  useEffect16(() => {
43423
43935
  if (!lastToast) return;
@@ -43472,7 +43984,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
43472
43984
  const attach2 = useCallback3((s) => {
43473
43985
  const cmd = attachCommand(fleet, s);
43474
43986
  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" });
43987
+ 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
43988
  return;
43477
43989
  }
43478
43990
  void suspendTerminal(async () => {
@@ -43517,14 +44029,14 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
43517
44029
  runInTerminal,
43518
44030
  env,
43519
44031
  keys,
43520
- kept,
44032
+ kept: kept2,
43521
44033
  readImage: readImage ?? (() => readClipboardImage(env)),
43522
44034
  openFile: openFile2 ?? ((file) => openImage(file, env))
43523
44035
  };
43524
44036
  useInput8((input, key) => {
43525
44037
  if (key.ctrl && input === "c") {
43526
44038
  if (keys.current?.(input, key)) return;
43527
- const drafts = [...kept.drafts].some(([id, x]) => x.trim() && (id === "ask" || d.byId.has(id)));
44039
+ const drafts = [...kept2.drafts].some(([id, x]) => x.trim() && (id === "ask" || d.byId.has(id)));
43528
44040
  if (drafts && Date.now() - armed.current > QUIT_MS) {
43529
44041
  armed.current = Date.now();
43530
44042
  toast({ text: t.toast.draftQuit, tone: "info", ms: QUIT_MS });
@@ -43610,7 +44122,7 @@ async function readToken() {
43610
44122
  }
43611
44123
  }
43612
44124
  function createTuiFleet(opts = {}) {
43613
- const local = LOCAL_MACHINE.url, port = opts.port ?? PROTOCOL_PORT;
44125
+ const local = LOCAL_MACHINE.url, port = opts.port ?? ledgePort();
43614
44126
  const backoff = { ...opts.minBackoffMs ? { minBackoffMs: opts.minBackoffMs } : {}, ...opts.maxBackoffMs ? { maxBackoffMs: opts.maxBackoffMs } : {} };
43615
44127
  return createFleetClient(
43616
44128
  [LOCAL_MACHINE],
@@ -43621,7 +44133,7 @@ function createTuiFleet(opts = {}) {
43621
44133
  var init_fleet2 = __esm({
43622
44134
  "src/tui/fleet.ts"() {
43623
44135
  "use strict";
43624
- init_src();
44136
+ init_port();
43625
44137
  init_src3();
43626
44138
  init_auth();
43627
44139
  }
@@ -43772,7 +44284,7 @@ var init_glyphs = __esm({
43772
44284
 
43773
44285
  // src/tui/settings.ts
43774
44286
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
43775
- import { join as join38 } from "path";
44287
+ import { join as join40 } from "path";
43776
44288
  import { z as z32 } from "zod";
43777
44289
  function defaultLang(env) {
43778
44290
  const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
@@ -43798,14 +44310,14 @@ var init_settings = __esm({
43798
44310
  "use strict";
43799
44311
  init_paths();
43800
44312
  SettingsSchema = z32.object({ lang: z32.enum(["fr", "en"]).optional(), theme: z32.enum(["auto", "dark", "light"]).optional() }).passthrough();
43801
- settingsPath = () => join38(ledgeDir(), "tui.json");
44313
+ settingsPath = () => join40(ledgeDir(), "tui.json");
43802
44314
  }
43803
44315
  });
43804
44316
 
43805
44317
  // src/tui/terminalProfile.ts
43806
- 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";
44318
+ import { execFile as execFile11, spawn as spawn6 } from "child_process";
44319
+ import { mkdir as mkdir14, readFile as readFile19, realpath as realpath5, writeFile as writeFile18 } from "fs/promises";
44320
+ import { dirname as dirname13, join as join41 } from "path";
43809
44321
  import { fileURLToPath as fileURLToPath4 } from "url";
43810
44322
  import { promisify } from "util";
43811
44323
  function movesToLedgeTab(env, here, script) {
@@ -43833,13 +44345,13 @@ function fragment(commandline, iconWin) {
43833
44345
  }
43834
44346
  async function profileLoaded(localAppData) {
43835
44347
  for (const rel of SETTINGS) {
43836
- const text7 = await readFile18(join39(localAppData, rel), "utf8").catch(() => "");
44348
+ const text7 = await readFile19(join41(localAppData, rel), "utf8").catch(() => "");
43837
44349
  if (new RegExp(`"source"\\s*:\\s*"${PROFILE}"`).test(text7)) return true;
43838
44350
  }
43839
44351
  return false;
43840
44352
  }
43841
44353
  async function writeIfChanged2(path3, content3) {
43842
- const current2 = await readFile18(path3).catch(() => null);
44354
+ const current2 = await readFile19(path3).catch(() => null);
43843
44355
  if (current2 && current2.equals(Buffer.from(content3))) return false;
43844
44356
  await writeFile18(path3, content3);
43845
44357
  return true;
@@ -43851,9 +44363,9 @@ async function openInLedgeTab(o) {
43851
44363
  await writeIfChanged2(o.launcher, launcherScript(o.node, o.script, o.ledgeHome));
43852
44364
  const dir = fragmentDir(o.localAppData.linux);
43853
44365
  await mkdir14(dir, { recursive: true });
43854
- await writeIfChanged2(join39(dir, "ledge.ico"), await readFile18(o.icon ?? ICON));
44366
+ await writeIfChanged2(join41(dir, "ledge.ico"), await readFile19(o.icon ?? ICON));
43855
44367
  const iconWin = `${o.localAppData.win}\\Microsoft\\Windows Terminal\\Fragments\\${PROFILE}\\ledge.ico`;
43856
- await writeIfChanged2(join39(dir, "ledge.json"), fragment(commandline, iconWin));
44368
+ await writeIfChanged2(join41(dir, "ledge.json"), fragment(commandline, iconWin));
43857
44369
  const deadline = Date.now() + (o.waitMs ?? 15e3);
43858
44370
  for (let first = true; !await profileLoaded(o.localAppData.linux); first = false) {
43859
44371
  if (Date.now() > deadline) return false;
@@ -43874,11 +44386,11 @@ async function openTuiInLedgeTab(env, onWait) {
43874
44386
  distro: env.WSL_DISTRO_NAME,
43875
44387
  node: await realpath5(process.execPath),
43876
44388
  script: await realpath5(process.argv[1]),
43877
- launcher: join39(ledgeDir(), "tui.sh"),
44389
+ launcher: join41(ledgeDir(), "tui.sh"),
43878
44390
  ledgeHome: env.LEDGE_HOME,
43879
44391
  onWait,
43880
44392
  wt: (args) => new Promise((resolve13, reject) => {
43881
- const child = spawn6(join39(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
44393
+ const child = spawn6(join41(linux, "Microsoft", "WindowsApps", "wt.exe"), args, { cwd: "/mnt/c", stdio: "ignore", detached: true });
43882
44394
  child.once("error", reject);
43883
44395
  child.once("spawn", () => {
43884
44396
  child.unref();
@@ -43902,10 +44414,10 @@ var init_terminalProfile = __esm({
43902
44414
  "Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json",
43903
44415
  "Microsoft/Windows Terminal/settings.json"
43904
44416
  ];
43905
- fragmentDir = (localAppData) => join39(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
44417
+ fragmentDir = (localAppData) => join41(localAppData, "Microsoft", "Windows Terminal", "Fragments", PROFILE);
43906
44418
  PROFILE_ID = "{d10fee13-0978-595f-a851-b60ec80f11c9}";
43907
44419
  shQuote2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
43908
- run3 = promisify(execFile10);
44420
+ run3 = promisify(execFile11);
43909
44421
  }
43910
44422
  });
43911
44423
 
@@ -44074,7 +44586,7 @@ function bridge(opts = {}) {
44074
44586
  process.stderr.write(`${line2}
44075
44587
  `);
44076
44588
  }), "ledge bridge");
44077
- const ws = new WebSocket6(`ws://127.0.0.1:${opts.port ?? PROTOCOL_PORT}`, { maxPayload: MAX_FRAME, handshakeTimeout: 1e4, headers: bearerHeaders() });
44589
+ const ws = new WebSocket6(`ws://127.0.0.1:${opts.port ?? ledgePort()}`, { maxPayload: MAX_FRAME, handshakeTimeout: 1e4, headers: bearerHeaders() });
44078
44590
  return new Promise((resolve13, reject) => {
44079
44591
  let settled4 = false, off = () => {
44080
44592
  };
@@ -44142,6 +44654,7 @@ function bridge(opts = {}) {
44142
44654
  var init_bridge = __esm({
44143
44655
  "src/cli/bridge.ts"() {
44144
44656
  "use strict";
44657
+ init_port();
44145
44658
  init_src();
44146
44659
  init_auth();
44147
44660
  init_frames();
@@ -44192,20 +44705,20 @@ async function attach(id, opts = {}) {
44192
44705
  let tmux = realTmux(opts.tmuxSocket ?? TMUX_SOCKET2);
44193
44706
  let tmuxName = `ledge-${id}`;
44194
44707
  if (!await tmux.hasSession(tmuxName)) {
44195
- const seat2 = await seatSession(id, opts.port ?? PROTOCOL_PORT);
44708
+ const seat2 = await seatSession(id, opts.port ?? ledgePort());
44196
44709
  if (seat2) {
44197
44710
  tmux = realTmux(opts.seatSocket === void 0 ? null : opts.seatSocket);
44198
44711
  tmuxName = seat2;
44199
44712
  }
44200
44713
  }
44201
44714
  if (!await tmux.hasSession(tmuxName)) {
44202
- const native = await nativeSession(id, opts.port ?? PROTOCOL_PORT);
44715
+ const native = await nativeSession(id, opts.port ?? ledgePort());
44203
44716
  console.error(native ? `ledge attach: ${native} is driven by Ledge over OMP's own protocol: there is no terminal to attach to (open a shell from the app)` : `ledge attach: no session ${id}`);
44204
44717
  return 1;
44205
44718
  }
44206
44719
  let title3 = tmuxName;
44207
44720
  try {
44208
- const c = await connect({ port: opts.port ?? PROTOCOL_PORT, timeoutMs: 800 });
44721
+ const c = await connect({ port: opts.port ?? ledgePort(), timeoutMs: 800 });
44209
44722
  const snap = await c.next("snapshot");
44210
44723
  title3 = snap.sessions.find((s) => s.id === id)?.title ?? title3;
44211
44724
  await Promise.race([c.close(), new Promise((r) => setTimeout(r, 500))]);
@@ -44221,6 +44734,7 @@ var TMUX_SOCKET2;
44221
44734
  var init_attach2 = __esm({
44222
44735
  "src/cli/attach.ts"() {
44223
44736
  "use strict";
44737
+ init_port();
44224
44738
  init_src();
44225
44739
  init_client();
44226
44740
  init_tmux();