@paradow/ledge 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli/{action-HUKZUHQ7.js → action-K42H5BDA.js} +5 -5
  2. package/dist/cli/{attach-ILMHKRUI.js → attach-KEE4A5YP.js} +28 -15
  3. package/dist/cli/{bridge-LFKUCKQP.js → bridge-X327AZY4.js} +2 -2
  4. package/dist/cli/{chunk-VGAPV4DC.js → chunk-25RVT5MR.js} +3 -103
  5. package/dist/cli/{chunk-Q6RMLRPI.js → chunk-3OJCWJAA.js} +2 -2
  6. package/dist/cli/chunk-4OYVM4MT.js +49 -0
  7. package/dist/cli/{chunk-IE52REEI.js → chunk-4TCSADFR.js} +5 -23
  8. package/dist/cli/{chunk-HOOHS3MX.js → chunk-53ISXX2R.js} +1 -1
  9. package/dist/cli/{chunk-6IYPRUQG.js → chunk-54KF5RBY.js} +1 -1
  10. package/dist/cli/{chunk-CXZETRPA.js → chunk-7NHQGP6J.js} +1 -1
  11. package/dist/cli/{chunk-U7J2AN3F.js → chunk-G7XPEVCG.js} +5 -5
  12. package/dist/cli/{chunk-2BLPGRLO.js → chunk-IWY7DSSO.js} +835 -1030
  13. package/dist/cli/{chunk-SUBKKCGA.js → chunk-KPDTYX7F.js} +3 -3
  14. package/dist/cli/chunk-RPH773TY.js +6 -0
  15. package/dist/cli/{chunk-YHZIHIGI.js → chunk-RX5CJ7YH.js} +3 -5
  16. package/dist/cli/{chunk-NLL2EQJ2.js → chunk-T547FLQN.js} +38 -4
  17. package/dist/cli/{chunk-K7UEKSB2.js → chunk-UJ2WBGLW.js} +2 -2
  18. package/dist/cli/{chunk-OPUIV6PR.js → chunk-ULOMKEB6.js} +4 -4
  19. package/dist/cli/{chunk-5JNJFBKZ.js → chunk-V4XKKL6K.js} +13 -40
  20. package/dist/cli/{chunk-EGXOBQZI.js → daemon-RO43S3S5.js} +4655 -2964
  21. package/dist/cli/{decide-3B6LGVNN.js → decide-KZFYL3HY.js} +5 -5
  22. package/dist/cli/{handover-IX7EFDIP.js → handover-BWNMI2RA.js} +5 -5
  23. package/dist/cli/{hook-G7UA6MB6.js → hook-ZLWXI5EQ.js} +21 -71
  24. package/dist/cli/index.js +18 -25
  25. package/dist/cli/launch-GCX7TXOH.js +28 -0
  26. package/dist/cli/{send-55RWY4LZ.js → send-SXZAPVRH.js} +5 -5
  27. package/dist/cli/{setup-K42IVMHS.js → setup-TJ32FMVJ.js} +4 -4
  28. package/dist/cli/{status-NT5ZUYM6.js → status-YDVRES4E.js} +3 -3
  29. package/dist/cli/{task-GABQ4SKY.js → task-TUSOVJK7.js} +5 -5
  30. package/dist/cli/{tui-AMW2XAH5.js → tui-TCVIRZCQ.js} +1411 -1277
  31. package/dist/cli/{uninstall-4IMMR55T.js → uninstall-QO7YMSLA.js} +4 -4
  32. package/dist/cli/{version-35XLTEMX.js → version-BAYSYWFP.js} +1 -1
  33. package/dist/cli/who-6AZYSP7T.js +12 -0
  34. package/dist/ledge-guide.md +26 -49
  35. package/package.json +2 -1
  36. package/dist/cli/chunk-5FKHISVK.js +0 -160
  37. package/dist/cli/chunk-M7364G3S.js +0 -6
  38. package/dist/cli/chunk-QQVWFOPU.js +0 -290
  39. package/dist/cli/daemon-UJTMG3D4.js +0 -2322
  40. package/dist/cli/launch-2GZIRF3A.js +0 -36
  41. package/dist/cli/team-IK2RYXW2.js +0 -205
  42. package/dist/cli/who-4FRIGYVT.js +0 -12
@@ -1,17 +1,14 @@
1
- import {
2
- attachEnv
3
- } from "./chunk-5JNJFBKZ.js";
4
1
  import {
5
2
  imageInfo
6
- } from "./chunk-EGXOBQZI.js";
3
+ } from "./chunk-4OYVM4MT.js";
4
+ import {
5
+ attachEnv
6
+ } from "./chunk-V4XKKL6K.js";
7
7
  import {
8
8
  MAX_FRAME
9
9
  } from "./chunk-OLLQBK6N.js";
10
- import "./chunk-QQVWFOPU.js";
11
- import "./chunk-VGAPV4DC.js";
12
- import "./chunk-5FKHISVK.js";
13
- import "./chunk-IE52REEI.js";
14
- import "./chunk-CXZETRPA.js";
10
+ import "./chunk-4TCSADFR.js";
11
+ import "./chunk-7NHQGP6J.js";
15
12
  import {
16
13
  ACTION_ERROR_CODES,
17
14
  ATTACHMENTS_MAX,
@@ -22,6 +19,7 @@ import {
22
19
  ChatTurnSchema,
23
20
  ContextPrefsSchema,
24
21
  IslandPrefsSchema,
22
+ KERNEL_RIG,
25
23
  LEDGE_GIVER,
26
24
  MachinesSchema,
27
25
  NOTE_MAX,
@@ -30,7 +28,7 @@ import {
30
28
  PROJECT_COLORS,
31
29
  PROTOCOL_PORT,
32
30
  ProjectsSchema,
33
- ROLE_PROMPT_MAX,
31
+ RIG_POLICIES,
34
32
  backendOf,
35
33
  contextNear,
36
34
  isArchived,
@@ -42,15 +40,18 @@ import {
42
40
  projectOf,
43
41
  proposalRows,
44
42
  readDaemonToken,
43
+ rigCommands,
44
+ seatPlan,
45
+ seatsByRole,
46
+ showRigCommand,
45
47
  taskClosed,
46
48
  transmissions
47
- } from "./chunk-2BLPGRLO.js";
48
- import "./chunk-M7364G3S.js";
49
- import "./chunk-NLL2EQJ2.js";
50
- import "./chunk-6IYPRUQG.js";
49
+ } from "./chunk-IWY7DSSO.js";
50
+ import "./chunk-T547FLQN.js";
51
+ import "./chunk-54KF5RBY.js";
51
52
  import {
52
53
  ledgeDir
53
- } from "./chunk-HOOHS3MX.js";
54
+ } from "./chunk-53ISXX2R.js";
54
55
  import {
55
56
  __commonJS,
56
57
  __export,
@@ -346,7 +347,7 @@ var daemonStore = {
346
347
  return;
347
348
  }
348
349
  if (m.type === "chat.step" || m.type === "chat.delta" || m.type === "chat.done" || m.type === "chat.error") {
349
- const chat = state.chat.map((t) => t.qid !== m.qid ? t : m.type === "chat.step" ? { ...t, steps: [...t.steps, m.step] } : m.type === "chat.done" ? { ...t, answer: m.text, answeredAt: m.at, ...m.proposal ? { proposal: m.proposal } : {}, ...m.proposals ? { proposals: m.proposals } : {}, ...m.relay ? { relay: m.relay } : {}, ...m.closes ? { closes: m.closes } : {}, ...m.projectProposal ? { projectProposal: m.projectProposal } : {}, ...m.team ? { team: m.team } : {}, ...m.tasks ? { tasks: m.tasks } : {}, ...m.actions ? { actions: m.actions } : {} } : m.type === "chat.error" ? { ...t, error: m.message } : t);
350
+ const chat = state.chat.map((t) => t.qid !== m.qid ? t : m.type === "chat.step" ? { ...t, steps: [...t.steps, m.step] } : m.type === "chat.done" ? { ...t, answer: m.text, answeredAt: m.at, ...m.proposal ? { proposal: m.proposal } : {}, ...m.proposals ? { proposals: m.proposals } : {}, ...m.relay ? { relay: m.relay } : {}, ...m.closes ? { closes: m.closes } : {}, ...m.projectProposal ? { projectProposal: m.projectProposal } : {}, ...m.tasks ? { tasks: m.tasks } : {}, ...m.actions ? { actions: m.actions } : {} } : m.type === "chat.error" ? { ...t, error: m.message } : t);
350
351
  const chatLive = m.type === "chat.delta" ? { qid: m.qid, text: (state.chatLive?.qid === m.qid ? state.chatLive.text : "") + m.text } : m.type === "chat.step" ? state.chatLive : null;
351
352
  state = { ...state, chat, chatLive };
352
353
  emit();
@@ -542,9 +543,6 @@ function useMachines(client) {
542
543
  var nullSnapshot = () => null;
543
544
  var noTasks = [];
544
545
  var emptyTasks = () => noTasks;
545
- function useTeams(client) {
546
- return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, client.fleet?.teams ?? nullSnapshot, nullSnapshot);
547
- }
548
546
  var defaultNear = () => CONTEXT_NEAR;
549
547
  function useContextNear(client) {
550
548
  return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, () => client.fleet?.contextNear() ?? CONTEXT_NEAR, defaultNear);
@@ -574,13 +572,14 @@ function changeOf(was, now2) {
574
572
  }
575
573
 
576
574
  // ../fleet/src/fleet.ts
577
- var toWire = (list3) => list3.map(({ id, label, ssh }) => ({ id, label, ...ssh ? { ssh } : {} }));
575
+ var toWire = (list3) => list3.map(({ id, label, ssh, disabled }) => ({ id, label, ...ssh ? { ssh } : {}, ...disabled ? { disabled: true } : {} }));
578
576
  var OPERATIONS = /* @__PURE__ */ new Set(["session.launch", "session.relay", "session.relay.cancel", "session.close"]);
579
577
  function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
580
578
  const prefix = opts.prefix ?? "ledge";
581
579
  const HISTORY_KEY = `${prefix}.fleet.history`, MACHINES_KEY = `${prefix}.machines`;
582
580
  let configs = parseMachines(config), selected = configs[0].id, connected = false, configuring = false, state2 = [];
583
581
  const peers = /* @__PURE__ */ new Map();
582
+ const active = () => configs.filter((c) => !c.disabled);
584
583
  const machineMessages = /* @__PURE__ */ new Set();
585
584
  const messages = /* @__PURE__ */ new Set(), statuses = /* @__PURE__ */ new Set(), listeners2 = /* @__PURE__ */ new Set();
586
585
  const turns2 = /* @__PURE__ */ new Map(), bindings = /* @__PURE__ */ new Map();
@@ -590,7 +589,14 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
590
589
  const scans = /* @__PURE__ */ new Map();
591
590
  const attachmentAsks = /* @__PURE__ */ new Map();
592
591
  const modelLists = /* @__PURE__ */ new Map();
593
- const roleLists = /* @__PURE__ */ new Map();
592
+ const rigFailure = (kind, requestId, machineId, error) => kind === "act" ? { type: "openrig.done", requestId, machineId, ok: false, commands: [], error } : kind === "specs" ? { type: "openrig.specList", requestId, machineId, specs: [], error } : kind === "launch" ? { type: "openrig.launched", requestId, machineId, ok: false, plan: false, commands: [], error } : kind === "seats" ? { type: "openrig.reseated", requestId, machineId, ok: false, commands: [], added: [], removed: [], error } : { type: "openrig.state", requestId, machineId, error };
593
+ const rigAsks = /* @__PURE__ */ new Map();
594
+ const answerFailedRigs = (machine, error) => {
595
+ for (const [requestId, a] of rigAsks) if (a.machine === machine) {
596
+ rigAsks.delete(requestId);
597
+ emit2(rigFailure(a.kind, requestId, machine, error));
598
+ }
599
+ };
594
600
  const operations = /* @__PURE__ */ new Map();
595
601
  const closeRows = /* @__PURE__ */ new Map();
596
602
  const endOperation = (machine) => {
@@ -624,25 +630,17 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
624
630
  emit2({ type: "models.listing", requestId, tool: l.tool, models: [], error });
625
631
  }
626
632
  };
627
- const answerFailedRoles = (machine, error) => {
628
- for (const [requestId, owner] of roleLists) if (owner === machine) {
629
- roleLists.delete(requestId);
630
- emit2({ type: "roles.listing", requestId, roles: [], templates: [], problems: [], error });
631
- }
632
- };
633
633
  let source2 = "local";
634
634
  let projects = null;
635
635
  let island;
636
636
  let contextNear2;
637
637
  let mobile;
638
638
  let mobileError;
639
- let teams = null;
640
639
  let tasks = /* @__PURE__ */ new Map();
641
640
  let taskList = [];
642
641
  const taskLists = /* @__PURE__ */ new Set();
643
642
  let listSeq = 0;
644
643
  const local = (id) => id === LEDGE_GIVER ? id : qualify("local", id);
645
- const teamOut = (t) => ({ ...t, seats: t.seats.map((s) => ({ ...s, sessionId: s.sessionId === null ? null : qualify("local", s.sessionId) })) });
646
644
  const taskOut = (t) => ({
647
645
  ...t,
648
646
  giver: local(t.giver),
@@ -654,14 +652,15 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
654
652
  taskList = [...tasks.values()].sort((a, b2) => a.at.localeCompare(b2.at));
655
653
  listeners2.forEach((fn) => fn());
656
654
  };
655
+ const knowsTasks = (version) => {
656
+ const [major = 0, minor = 0] = (version ?? "").split(".").map(Number);
657
+ return major > 0 || minor >= 17;
658
+ };
657
659
  const askTasks = (p) => {
658
- const ask2 = (extra) => {
659
- const rid = `fleet-tasks-${++listSeq}`;
660
- taskLists.add(rid);
661
- p.client.send({ type: "task.cmd", rid, by: LEDGE_GIVER, op: "list", all: true, ...extra });
662
- };
663
- ask2({});
664
- for (const t of teams ?? []) if (t.state !== "dissolved") ask2({ team: t.id });
660
+ if (!knowsTasks(p.snapshot?.daemon?.version)) return;
661
+ const rid = `fleet-tasks-${++listSeq}`;
662
+ taskLists.add(rid);
663
+ p.client.send({ type: "task.cmd", rid, by: LEDGE_GIVER, op: "list", all: true });
665
664
  };
666
665
  let routing = null, resetAt = "", lastStatus = "connecting";
667
666
  const emit2 = (m) => messages.forEach((fn) => fn(m));
@@ -710,30 +709,30 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
710
709
  const stateSessions = (p) => (stale(p) ? p.snapshot.sessions.map((s) => ({ ...s, offline: true })) : p.snapshot?.sessions ?? []).filter((s) => !isArchived(s));
711
710
  function refresh() {
712
711
  if (configuring) return;
713
- state2 = configs.map((c) => {
712
+ state2 = active().map((c) => {
714
713
  const p = peers.get(c.id);
715
714
  return { ...c, status: p.status, ready: available(p), recents: p.snapshot?.recents ?? [], sessions: stateSessions(p), launcher: p.snapshot?.launcher ?? null, version: p.snapshot?.daemon?.version, daemon: p.snapshot?.daemon, error: p.error };
716
715
  });
717
- const next2 = status();
718
- if (next2 !== lastStatus) {
719
- lastStatus = next2;
720
- statuses.forEach((fn) => fn(next2));
716
+ const next3 = status();
717
+ if (next3 !== lastStatus) {
718
+ lastStatus = next3;
719
+ statuses.forEach((fn) => fn(next3));
721
720
  }
722
721
  listeners2.forEach((fn) => fn());
723
722
  }
724
723
  function publish() {
725
724
  if (configuring) return;
726
- state2 = configs.map((c) => {
725
+ state2 = active().map((c) => {
727
726
  const p = peers.get(c.id);
728
727
  return { ...c, status: p.status, ready: available(p), recents: p.snapshot?.recents ?? [], sessions: stateSessions(p), launcher: p.snapshot?.launcher ?? null, version: p.snapshot?.daemon?.version, daemon: p.snapshot?.daemon, error: p.error };
729
728
  });
730
729
  const primary = peers.get("local")?.snapshot;
731
730
  emit2({ type: "snapshot", sessions: [...peers.values()].filter((p) => available(p) || stale(p)).flatMap((p) => p.snapshot.sessions.map((s) => session(p, s))), recents: available(peers.get(selected)) ? peers.get(selected).snapshot.recents : [], launcher: primary?.launcher, daemon: primary?.daemon });
732
731
  for (const p of peers.values()) if (available(p) || stale(p)) for (const [id, lines] of p.previews) emit2({ type: "session.preview", id: qualify(p.config.id, id), lines });
733
- const next2 = status();
734
- if (next2 !== lastStatus) {
735
- lastStatus = next2;
736
- statuses.forEach((fn) => fn(next2));
732
+ const next3 = status();
733
+ if (next3 !== lastStatus) {
734
+ lastStatus = next3;
735
+ statuses.forEach((fn) => fn(next3));
737
736
  }
738
737
  listeners2.forEach((fn) => fn());
739
738
  }
@@ -764,15 +763,15 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
764
763
  ...raw.actions ? { actions: raw.actions.map((a) => ({ ...a, ...a.session ? { session: qualify(p.config.id, a.session) } : {} })) } : {}
765
764
  };
766
765
  }
767
- function apply(next2) {
766
+ function apply(next3) {
768
767
  configuring = true;
769
- for (const [id, p] of peers) if (!next2.some((c) => c.id === id && c.url === p.config.url && c.ssh === p.config.ssh)) {
768
+ for (const [id, p] of peers) if (!next3.some((c) => !c.disabled && c.id === id && c.url === p.config.url && c.ssh === p.config.ssh)) {
770
769
  p.client.close();
771
770
  p.off.forEach((off) => off());
772
771
  peers.delete(id);
773
772
  }
774
- configs = next2;
775
- for (const c of configs) {
773
+ configs = next3;
774
+ for (const c of active()) {
776
775
  const p = peers.get(c.id);
777
776
  if (p) p.config = c;
778
777
  else add(c);
@@ -788,15 +787,15 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
788
787
  const machine = p.config.id;
789
788
  if (m.type === "machines") {
790
789
  if (machine !== "local" || !opts.machinesFromDaemon) return;
791
- let next2;
790
+ let next3;
792
791
  try {
793
- next2 = parseMachines(m.machines);
792
+ next3 = parseMachines(m.machines);
794
793
  } catch {
795
794
  return;
796
795
  }
797
- const mine = next2.length === 1 ? loadMachines(prefix) : [];
796
+ const mine = next3.length === 1 ? loadMachines(prefix) : [];
798
797
  source2 = "daemon";
799
- if (next2.length === 1 && mine.length > 1) {
798
+ if (next3.length === 1 && mine.length > 1) {
800
799
  p.client.send({ type: "machines.set", machines: toWire(mine) });
801
800
  return;
802
801
  }
@@ -804,7 +803,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
804
803
  localStorage.removeItem(MACHINES_KEY);
805
804
  } catch {
806
805
  }
807
- apply(next2);
806
+ apply(next3);
808
807
  return;
809
808
  }
810
809
  if (m.type === "projects") {
@@ -825,19 +824,6 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
825
824
  listeners2.forEach((fn) => fn());
826
825
  return;
827
826
  }
828
- if (m.type === "teams" || m.type === "team.updated") {
829
- if (machine !== "local") return;
830
- const known2 = new Set((teams ?? []).map((t) => t.id));
831
- teams = m.type === "teams" ? m.teams.map(teamOut) : [...(teams ?? []).filter((t) => t.id !== m.team.id), teamOut(m.team)].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
832
- listeners2.forEach((fn) => fn());
833
- if (m.type === "team.updated" && !known2.has(m.team.id) && m.team.state !== "dissolved") {
834
- const rid = `fleet-tasks-${++listSeq}`;
835
- taskLists.add(rid);
836
- p.client.send({ type: "task.cmd", rid, by: LEDGE_GIVER, op: "list", all: true, team: m.team.id });
837
- }
838
- if (m.type === "teams" && p.snapshot?.daemon?.teams) askTasks(p);
839
- return;
840
- }
841
827
  if (m.type === "task.updated") {
842
828
  if (machine === "local") putTasks([m.task]);
843
829
  return;
@@ -870,6 +856,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
870
856
  p.restarting = false;
871
857
  p.previews.clear();
872
858
  publish();
859
+ if (machine === "local") askTasks(p);
873
860
  return;
874
861
  }
875
862
  if (m.type === "session.updated") {
@@ -959,16 +946,17 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
959
946
  }
960
947
  return;
961
948
  }
962
- if (m.type === "models.listing") {
963
- if (modelLists.get(m.requestId)?.machine === machine) {
964
- modelLists.delete(m.requestId);
965
- emit2(m);
949
+ if (m.type === "openrig.state" || m.type === "openrig.done" || m.type === "openrig.specList" || m.type === "openrig.launched" || m.type === "openrig.reseated") {
950
+ if (m.requestId !== void 0) {
951
+ if (rigAsks.get(m.requestId)?.machine !== machine) return;
952
+ rigAsks.delete(m.requestId);
966
953
  }
954
+ emit2({ ...m, machineId: machine });
967
955
  return;
968
956
  }
969
- if (m.type === "roles.listing" && m.requestId !== void 0 && roleLists.has(m.requestId)) {
970
- if (roleLists.get(m.requestId) === machine) {
971
- roleLists.delete(m.requestId);
957
+ if (m.type === "models.listing") {
958
+ if (modelLists.get(m.requestId)?.machine === machine) {
959
+ modelLists.delete(m.requestId);
972
960
  emit2(m);
973
961
  }
974
962
  return;
@@ -1015,15 +1003,15 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1015
1003
  const qid = globalQid(machine, m.qid), old = turns2.get(qid);
1016
1004
  if (!old) return;
1017
1005
  if (m.type === "chat.done") {
1018
- const raw = { ...old, qid: m.qid, answer: m.text, answeredAt: m.at, proposal: m.proposal, proposals: m.proposals, relay: m.relay, closes: m.closes, projectProposal: m.projectProposal, team: m.team, tasks: m.tasks, actions: m.actions };
1006
+ const raw = { ...old, qid: m.qid, answer: m.text, answeredAt: m.at, proposal: m.proposal, proposals: m.proposals, relay: m.relay, closes: m.closes, projectProposal: m.projectProposal, tasks: m.tasks, actions: m.actions };
1019
1007
  const t = mappedTurn(p, { ...raw, steps: [] });
1020
1008
  turns2.set(qid, { ...t, steps: old.steps });
1021
1009
  history();
1022
1010
  } else if (m.type === "chat.error") fail(qid, m.message);
1023
1011
  else if (m.type === "chat.step") {
1024
- const step2 = { ...m.step, ...m.step.id ? { id: qualify(machine, m.step.id) } : {} };
1025
- turns2.set(qid, { ...old, steps: [...old.steps, step2] });
1026
- emit2({ ...m, qid, step: step2 });
1012
+ const step3 = { ...m.step, ...m.step.id ? { id: qualify(machine, m.step.id) } : {} };
1013
+ turns2.set(qid, { ...old, steps: [...old.steps, step3] });
1014
+ emit2({ ...m, qid, step: step3 });
1027
1015
  } else emit2({ ...m, qid });
1028
1016
  return;
1029
1017
  }
@@ -1053,8 +1041,8 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1053
1041
  answerFailedModels(machine, "outdated");
1054
1042
  return;
1055
1043
  }
1056
- if (m.request === "roles.list" && [...roleLists.values()].includes(machine)) {
1057
- answerFailedRoles(machine, "outdated");
1044
+ if (m.request?.startsWith("openrig.")) {
1045
+ answerFailedRigs(machine, "outdated");
1058
1046
  return;
1059
1047
  }
1060
1048
  if ((m.request === "folder.list" || m.request === "folder.create") && [...listings.values()].some((l) => l.machine === machine)) {
@@ -1062,7 +1050,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1062
1050
  return;
1063
1051
  }
1064
1052
  if (m.request !== void 0 && OPERATIONS.has(m.request)) endOperation(machine);
1065
- emit2({ ...m, ...m.id ? { id: qualify(machine, m.id) } : {}, message: configs.length > 1 ? `${p.config.label}: ${m.message}` : m.message });
1053
+ emit2({ ...m, ...m.id ? { id: qualify(machine, m.id) } : {}, message: active().length > 1 ? `${p.config.label}: ${m.message}` : m.message });
1066
1054
  return;
1067
1055
  }
1068
1056
  if (m.type === "session.handover.result") {
@@ -1090,7 +1078,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1090
1078
  answerUnfoundListings(c.id, "offline");
1091
1079
  answerFailedScans(c.id, "offline");
1092
1080
  answerFailedModels(c.id, "offline");
1093
- answerFailedRoles(c.id, "offline");
1081
+ answerFailedRigs(c.id, "offline");
1094
1082
  for (const [requestId, owner] of documents) if (owner === c.id) {
1095
1083
  documents.delete(requestId);
1096
1084
  emit2({ type: "document.error", requestId, path: "", code: "read_failed" });
@@ -1110,7 +1098,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1110
1098
  }));
1111
1099
  if (connected) client.connect();
1112
1100
  }
1113
- for (const c of configs) add(c);
1101
+ for (const c of active()) add(c);
1114
1102
  function inventory() {
1115
1103
  return { machines: state2.map((m) => ({ id: m.id, label: m.label, online: m.ready, local: m.id === "local", sessions: m.sessions.slice(0, 500).map((s) => ({ id: s.id, name: s.name, project: s.repo ?? s.project, cwd: s.cwd, topic: s.topic, state: s.state, readOnly: s.source === "hook_only" })), folders: m.recents.slice(0, 100).map((r) => ({ project: r.project, cwd: r.cwd })) })), history: [...turns2.values()].slice(-30).map((t) => ({ question: t.question.slice(0, 2e4), answer: t.answer?.slice(0, 2e4), machineId: t.machineId })) };
1116
1104
  }
@@ -1163,7 +1151,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1163
1151
  const context = inventory();
1164
1152
  turns2.set(msg.qid, { qid: msg.qid, question: msg.text, askedAt: (/* @__PURE__ */ new Date()).toISOString(), steps: [] });
1165
1153
  history();
1166
- if (configs.length === 1) {
1154
+ if (active().length === 1) {
1167
1155
  const p = peers.get("local");
1168
1156
  if (!available(p)) return fail(msg.qid, "Local is offline");
1169
1157
  bindings.set(msg.qid, { machine: "local", raw: msg.qid });
@@ -1216,7 +1204,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1216
1204
  attachmentAsks.set(msg.requestId, machine);
1217
1205
  }
1218
1206
  if (msg.type === "document.read") {
1219
- if (configs.length > 1 && !msg.machineId && !msg.sessionId) {
1207
+ if (active().length > 1 && !msg.machineId && !msg.sessionId) {
1220
1208
  emit2({ type: "document.error", requestId: msg.requestId, path: msg.path, code: "read_failed" });
1221
1209
  return;
1222
1210
  }
@@ -1265,6 +1253,17 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1265
1253
  }
1266
1254
  modelLists.set(msg.requestId, { machine, tool: msg.tool });
1267
1255
  }
1256
+ if (msg.type === "openrig.status" || msg.type === "openrig.act" || msg.type === "openrig.specs" || msg.type === "openrig.launch" || msg.type === "openrig.seats") {
1257
+ machine = msg.machineId ?? "local";
1258
+ const { machineId: _m, ...rest } = msg;
1259
+ outgoing = rest;
1260
+ const kind = msg.type === "openrig.status" ? "status" : msg.type === "openrig.act" ? "act" : msg.type === "openrig.specs" ? "specs" : msg.type === "openrig.seats" ? "seats" : "launch";
1261
+ if (!available(peers.get(machine))) {
1262
+ emit2(rigFailure(kind, msg.requestId, machine, "offline"));
1263
+ return;
1264
+ }
1265
+ rigAsks.set(msg.requestId, { machine, kind });
1266
+ }
1268
1267
  if (msg.type === "folder.list") {
1269
1268
  if (msg.machineId) machine = msg.machineId;
1270
1269
  else if (msg.requestId !== void 0) machine = "local";
@@ -1292,30 +1291,10 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1292
1291
  }
1293
1292
  }
1294
1293
  if (msg.type === "update.daemon") machine = "local";
1295
- if (msg.type === "roles.list" && msg.requestId !== void 0) {
1296
- machine = msg.machineId ?? "local";
1297
- if (!available(peers.get(machine))) {
1298
- emit2({ type: "roles.listing", requestId: msg.requestId, roles: [], templates: [], problems: [], error: "offline" });
1299
- return;
1300
- }
1301
- roleLists.set(msg.requestId, machine);
1302
- }
1303
- if (msg.type === "roles.list" || msg.type === "team.launch") {
1304
- if (!("qid" in msg && msg.qid)) machine = msg.machineId ?? "local";
1305
- const { machineId: _m, ...rest } = outgoing;
1306
- outgoing = rest;
1307
- }
1308
- if (msg.type === "team.join" || msg.type === "team.leave" || msg.type === "team.resume" || msg.type === "team.dissolve" || msg.type === "team.save" || msg.type === "task.cmd" && !msg.qid || msg.type === "action.run") machine = "local";
1309
- if (outgoing.type === "team.join" || outgoing.type === "team.leave") {
1310
- const o = splitId(outgoing.session);
1311
- if (o) {
1312
- machine = o.machine;
1313
- outgoing = { ...outgoing, session: o.id };
1314
- }
1315
- }
1294
+ if (msg.type === "task.cmd" && !msg.qid || msg.type === "action.run") machine = "local";
1316
1295
  if (outgoing.type === "task.cmd") {
1317
1296
  const o = outgoing.to ? splitId(outgoing.to) : null;
1318
- if (o && !outgoing.to.startsWith("@")) outgoing = { ...outgoing, to: o.id };
1297
+ if (o) outgoing = { ...outgoing, to: o.id };
1319
1298
  const b2 = splitId(outgoing.by);
1320
1299
  if (b2 && outgoing.by !== LEDGE_GIVER) outgoing = { ...outgoing, by: b2.id };
1321
1300
  }
@@ -1361,16 +1340,17 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1361
1340
  publish();
1362
1341
  },
1363
1342
  configure(raw) {
1364
- const next2 = parseMachines(raw);
1343
+ const next3 = parseMachines(raw);
1365
1344
  if (source2 === "daemon") {
1366
1345
  const local2 = peers.get("local").client;
1367
1346
  if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
1368
- local2.send({ type: "machines.set", machines: toWire(next2) });
1347
+ local2.send({ type: "machines.set", machines: toWire(next3) });
1369
1348
  return;
1370
1349
  }
1371
- localStorage.setItem(MACHINES_KEY, JSON.stringify(next2));
1372
- apply(next2);
1350
+ localStorage.setItem(MACHINES_KEY, JSON.stringify(next3));
1351
+ apply(next3);
1373
1352
  },
1353
+ configured: () => configs,
1374
1354
  machinesSource: () => source2,
1375
1355
  probe: (ssh) => probeMachine(configs[0].url, ssh, { token: opts.token }),
1376
1356
  // The last try's error is forgotten: the machine is « connecting » until its next answer (a snapshot, or an error again).
@@ -1412,16 +1392,16 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1412
1392
  };
1413
1393
  },
1414
1394
  projects: () => projects,
1415
- setProjects(next2) {
1416
- const valid = ProjectsSchema.parse(next2);
1395
+ setProjects(next3) {
1396
+ const valid = ProjectsSchema.parse(next3);
1417
1397
  const local2 = peers.get("local").client;
1418
1398
  if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
1419
1399
  if (projects === null) throw Object.assign(new Error("Update the local Ledge daemon: it does not know projects"), { name: "DaemonOutdated" });
1420
1400
  local2.send({ type: "projects.set", projects: valid });
1421
1401
  },
1422
1402
  island: () => island,
1423
- setIsland(next2) {
1424
- const valid = IslandPrefsSchema.parse(next2);
1403
+ setIsland(next3) {
1404
+ const valid = IslandPrefsSchema.parse(next3);
1425
1405
  const local2 = peers.get("local").client;
1426
1406
  if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
1427
1407
  if (island === void 0) throw Object.assign(new Error("Update the local Ledge daemon: it does not know the island settings"), { name: "DaemonOutdated" });
@@ -1429,7 +1409,6 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
1429
1409
  },
1430
1410
  mobile: () => mobile,
1431
1411
  mobileError: () => mobileError,
1432
- teams: () => teams,
1433
1412
  tasks: () => taskList,
1434
1413
  contextNear: () => contextNear2,
1435
1414
  setContextNear(near) {
@@ -1512,8 +1491,8 @@ function getImage(client, attachment, route = {}, preview = false, timeoutMs = T
1512
1491
  }
1513
1492
 
1514
1493
  // src/tui/App.tsx
1515
- import { useCallback, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef13, useState as useState14 } from "react";
1516
- import { useApp, useInput as useInput7, useStdout, useWindowSize as useWindowSize2 } from "ink";
1494
+ import { useCallback as useCallback2, useEffect as useEffect15, useMemo as useMemo7, useRef as useRef14, useState as useState16 } from "react";
1495
+ import { useApp, useInput as useInput8, useStdout, useWindowSize as useWindowSize2 } from "ink";
1517
1496
 
1518
1497
  // src/tui/actions.ts
1519
1498
  import { useEffect as useEffect3, useMemo, useRef as useRef2 } from "react";
@@ -1874,7 +1853,7 @@ async function openImage(file, env = process.env, spawnImpl = spawn2, platform =
1874
1853
  // src/tui/context.tsx
1875
1854
  import { createContext, useContext, useEffect as useEffect4, useRef as useRef3 } from "react";
1876
1855
  import { useWindowSize } from "ink";
1877
- var SCREENS = ["overview", "session", "ask", "launch", "settings", "teams"];
1856
+ var SCREENS = ["overview", "session", "ask", "launch", "settings", "openrig"];
1878
1857
  var TuiContext = createContext(null);
1879
1858
  var useTui = () => {
1880
1859
  const tui = useContext(TuiContext);
@@ -1921,7 +1900,7 @@ function formatBytes(n, lang) {
1921
1900
  var b = (name) => `**${name}**`;
1922
1901
  var short = (text5) => text5.length <= 100 ? text5 : `${text5.slice(0, 99)}\u2026`;
1923
1902
  var need = (v, keys, say) => keys.every((k) => v[k]) ? say(v) : void 0;
1924
- var about = (action, v) => action?.startsWith("team.") && v.team ? "team" : action?.startsWith("task.") && v.task ? "task" : v.session ? "session" : v.team ? "team" : v.task ? "task" : null;
1903
+ var about = (action, v) => action?.startsWith("task.") && v.task ? "task" : v.session ? "session" : v.task ? "task" : null;
1925
1904
  var fr = {
1926
1905
  line: {
1927
1906
  "session.model": (v) => need(v, ["session"], (w) => `Passer ${b(w.session)} sur ${v.model ? `${v.model}${v.effort ? `, effort ${v.effort}` : ""}` : v.effort ? `son mod\xE8le par d\xE9faut, effort ${v.effort}` : "son mod\xE8le et son effort par d\xE9faut"}`),
@@ -1931,26 +1910,21 @@ var fr = {
1931
1910
  "session.resume": (v) => need(v, ["session"], (w) => `Reprendre ${b(w.session)} sur sa conversation`),
1932
1911
  "session.close": (v) => need(v, ["session"], (w) => `Fermer ${b(w.session)}`),
1933
1912
  "session.decide": (v) => need(v, ["session", "decision", "kind"], (w) => `${w.decision === "deny" ? "Refuser" : "Accepter"} la demande de ${w.kind} de ${b(w.session)}${v.note ? ", avec une note" : ""}`),
1934
- "team.join": (v) => need(v, ["session", "team", "role"], (w) => `Faire entrer ${b(w.session)} dans ${b(w.team)} comme @${w.role}`),
1935
- "team.leave": (v) => need(v, ["session", "team"], (w) => `Retirer ${b(w.session)} de ${b(w.team)}`),
1936
- "team.resume": (v) => need(v, ["team"], (w) => `Reprendre l\u2019\xE9quipe ${b(w.team)}`),
1937
- "team.dissolve": (v) => need(v, ["team"], (w) => `Dissoudre l\u2019\xE9quipe ${b(w.team)}`),
1938
- "team.save": (v) => need(v, ["team", "name"], (w) => v.replaces ? `Remplacer le mod\xE8le \xAB ${w.name} \xBB par ${b(w.team)}` : `Enregistrer ${b(w.team)} comme mod\xE8le \xAB ${w.name} \xBB`),
1939
1913
  "task.cancel": (v) => need(v, ["task", "owner"], (w) => `Annuler la t\xE2che \xAB ${short(w.task)} \xBB de ${b(w.owner)}`),
1940
1914
  "task.pass": (v) => need(v, ["task", "owner", "session"], (w) => `Passer la t\xE2che \xAB ${short(w.task)} \xBB de ${b(w.owner)} \xE0 ${b(w.session)}`),
1941
1915
  "task.answer": (v) => need(v, ["task", "session"], (w) => `R\xE9pondre \xE0 la d\xE9cision \xAB ${short(w.task)} \xBB de ${b(w.session)}`)
1942
1916
  },
1943
1917
  why: (code3, action, v) => {
1944
- const s = v.session ? b(v.session) : void 0, t = v.team ? b(v.team) : void 0, k = v.task ? `\xAB ${short(v.task)} \xBB` : void 0;
1918
+ const s = v.session ? b(v.session) : void 0, k = v.task ? `\xAB ${short(v.task)} \xBB` : void 0;
1945
1919
  const gone = "cette action n\u2019est plus possible.";
1946
1920
  switch (code3) {
1947
1921
  case "unknown": {
1948
1922
  const on = about(action, v);
1949
- return on === "team" ? `Ledge ne trouve plus l\u2019\xE9quipe ${t}.` : on === "task" ? `Ledge ne trouve plus la t\xE2che ${k}.` : on === "session" ? `Ledge ne trouve plus ${s}.` : gone;
1923
+ return on === "task" ? `Ledge ne trouve plus la t\xE2che ${k}.` : on === "session" ? `Ledge ne trouve plus ${s}.` : gone;
1950
1924
  }
1951
1925
  case "ambiguous": {
1952
1926
  const on = about(action, v);
1953
- return on === "team" ? `plusieurs \xE9quipes s\u2019appellent ${t} : redemande \xE0 Ledge en pr\xE9cisant laquelle.` : on === "session" ? `plusieurs sessions s\u2019appellent ${s} : redemande \xE0 Ledge en pr\xE9cisant laquelle.` : gone;
1927
+ return on === "session" ? `plusieurs sessions s\u2019appellent ${s} : redemande \xE0 Ledge en pr\xE9cisant laquelle.` : gone;
1954
1928
  }
1955
1929
  case "read_only":
1956
1930
  return s ? `${s} est en lecture seule : Ledge ne peut pas lui \xE9crire.` : gone;
@@ -1972,18 +1946,6 @@ var fr = {
1972
1946
  return s ? `les demandes de ${s} se r\xE8glent dans sa session.` : gone;
1973
1947
  case "no_note":
1974
1948
  return s ? `${s} ne peut pas recevoir de note avec cette r\xE9ponse.` : gone;
1975
- case "member":
1976
- return s && t ? `${s} est d\xE9j\xE0 dans l\u2019\xE9quipe ${t}.` : gone;
1977
- case "not_member":
1978
- return s && t ? `${s} n\u2019est pas dans l\u2019\xE9quipe ${t}.` : gone;
1979
- case "unknown_role":
1980
- return v.role ? `le r\xF4le @${v.role} n\u2019existe pas pour ce projet.` : gone;
1981
- case "not_resting":
1982
- return t ? `l\u2019\xE9quipe ${t} n\u2019est pas au repos : rien \xE0 reprendre.` : gone;
1983
- case "dissolved":
1984
- return t ? `l\u2019\xE9quipe ${t} a \xE9t\xE9 dissoute entre-temps.` : gone;
1985
- case "no_seat":
1986
- return t ? `l\u2019\xE9quipe ${t} n\u2019a aucun poste : rien \xE0 enregistrer comme mod\xE8le.` : gone;
1987
1949
  case "closed":
1988
1950
  return k ? `la t\xE2che ${k} est d\xE9j\xE0 close.` : gone;
1989
1951
  case "holder":
@@ -2010,26 +1972,21 @@ var en = {
2010
1972
  "session.resume": (v) => need(v, ["session"], (w) => `Resume ${b(w.session)} on its conversation`),
2011
1973
  "session.close": (v) => need(v, ["session"], (w) => `Close ${b(w.session)}`),
2012
1974
  "session.decide": (v) => need(v, ["session", "decision", "kind"], (w) => `${w.decision === "deny" ? "Deny" : "Accept"} ${b(w.session)}\u2019s ${w.kind} request${v.note ? ", with a note" : ""}`),
2013
- "team.join": (v) => need(v, ["session", "team", "role"], (w) => `Add ${b(w.session)} to ${b(w.team)} as @${w.role}`),
2014
- "team.leave": (v) => need(v, ["session", "team"], (w) => `Remove ${b(w.session)} from ${b(w.team)}`),
2015
- "team.resume": (v) => need(v, ["team"], (w) => `Resume the team ${b(w.team)}`),
2016
- "team.dissolve": (v) => need(v, ["team"], (w) => `Dissolve the team ${b(w.team)}`),
2017
- "team.save": (v) => need(v, ["team", "name"], (w) => v.replaces ? `Replace the template \u201C${w.name}\u201D with ${b(w.team)}` : `Save ${b(w.team)} as the template \u201C${w.name}\u201D`),
2018
1975
  "task.cancel": (v) => need(v, ["task", "owner"], (w) => `Cancel ${b(w.owner)}\u2019s task \u201C${short(w.task)}\u201D`),
2019
1976
  "task.pass": (v) => need(v, ["task", "owner", "session"], (w) => `Pass ${b(w.owner)}\u2019s task \u201C${short(w.task)}\u201D to ${b(w.session)}`),
2020
1977
  "task.answer": (v) => need(v, ["task", "session"], (w) => `Answer ${b(w.session)}\u2019s decision \u201C${short(w.task)}\u201D`)
2021
1978
  },
2022
1979
  why: (code3, action, v) => {
2023
- const s = v.session ? b(v.session) : void 0, t = v.team ? b(v.team) : void 0, k = v.task ? `\u201C${short(v.task)}\u201D` : void 0;
1980
+ const s = v.session ? b(v.session) : void 0, k = v.task ? `\u201C${short(v.task)}\u201D` : void 0;
2024
1981
  const gone = "this action is no longer possible.";
2025
1982
  switch (code3) {
2026
1983
  case "unknown": {
2027
1984
  const on = about(action, v);
2028
- return on === "team" ? `Ledge can no longer find the team ${t}.` : on === "task" ? `Ledge can no longer find the task ${k}.` : on === "session" ? `Ledge can no longer find ${s}.` : gone;
1985
+ return on === "task" ? `Ledge can no longer find the task ${k}.` : on === "session" ? `Ledge can no longer find ${s}.` : gone;
2029
1986
  }
2030
1987
  case "ambiguous": {
2031
1988
  const on = about(action, v);
2032
- return on === "team" ? `several teams are called ${t}: ask Ledge again and say which one.` : on === "session" ? `several sessions are called ${s}: ask Ledge again and say which one.` : gone;
1989
+ return on === "session" ? `several sessions are called ${s}: ask Ledge again and say which one.` : gone;
2033
1990
  }
2034
1991
  case "read_only":
2035
1992
  return s ? `${s} is read-only: Ledge cannot write to it.` : gone;
@@ -2051,18 +2008,6 @@ var en = {
2051
2008
  return s ? `${s}\u2019s requests are answered in its session.` : gone;
2052
2009
  case "no_note":
2053
2010
  return s ? `${s} cannot take a note with this answer.` : gone;
2054
- case "member":
2055
- return s && t ? `${s} is already in the team ${t}.` : gone;
2056
- case "not_member":
2057
- return s && t ? `${s} is not in the team ${t}.` : gone;
2058
- case "unknown_role":
2059
- return v.role ? `there is no @${v.role} role for this project.` : gone;
2060
- case "not_resting":
2061
- return t ? `the team ${t} is not resting: nothing to resume.` : gone;
2062
- case "dissolved":
2063
- return t ? `the team ${t} was dissolved in the meantime.` : gone;
2064
- case "no_seat":
2065
- return t ? `the team ${t} has no seat: nothing to save as a template.` : gone;
2066
2011
  case "closed":
2067
2012
  return k ? `the task ${k} is already closed.` : gone;
2068
2013
  case "holder":
@@ -2097,11 +2042,6 @@ var fr2 = {
2097
2042
  "session.restore": "Restaurer",
2098
2043
  "session.resume": "Reprendre",
2099
2044
  "session.decide": "Accepter",
2100
- "team.join": "Ajouter",
2101
- "team.leave": "Retirer",
2102
- "team.resume": "Reprendre",
2103
- "team.dissolve": "Dissoudre",
2104
- "team.save": "Enregistrer",
2105
2045
  "task.cancel": "Abandonner",
2106
2046
  "task.pass": "Passer le relais",
2107
2047
  "task.answer": "R\xE9pondre"
@@ -2164,11 +2104,6 @@ var en2 = {
2164
2104
  "session.restore": "Restore",
2165
2105
  "session.resume": "Resume",
2166
2106
  "session.decide": "Accept",
2167
- "team.join": "Add",
2168
- "team.leave": "Remove",
2169
- "team.resume": "Resume",
2170
- "team.dissolve": "Dissolve",
2171
- "team.save": "Save",
2172
2107
  "task.cancel": "Drop",
2173
2108
  "task.pass": "Hand over",
2174
2109
  "task.answer": "Answer"
@@ -2232,7 +2167,7 @@ var fr3 = {
2232
2167
  h: (n) => `${n} h`,
2233
2168
  d: (n) => `${n} j`
2234
2169
  },
2235
- tabs: { overview: "Vue d\u2019ensemble", session: "Session", ask: "Demander \xE0 Ledge", launch: "Nouvelle session", settings: "R\xE9glages", teams: "\xC9quipes" },
2170
+ tabs: { overview: "Vue d\u2019ensemble", session: "Session", ask: "Demander \xE0 Ledge", launch: "Nouvelle session", settings: "R\xE9glages", openrig: "OpenRig" },
2236
2171
  bar: {
2237
2172
  sessions: (n) => `${n} ${plural(n, "session", "sessions")}`,
2238
2173
  machines: (n) => `${n} ${plural(n, "machine", "machines")}`,
@@ -2274,11 +2209,6 @@ var fr3 = {
2274
2209
  scroll: "d\xE9filer",
2275
2210
  space: "espace",
2276
2211
  redraw: "autre nom",
2277
- save: "enregistrer l\u2019\xE9quipe",
2278
- tasks: "t\xE2ches de l\u2019\xE9quipe",
2279
- join: "rejoindre une \xE9quipe",
2280
- resume: "reprendre l\u2019\xE9quipe",
2281
- teamMode: "session ou \xE9quipe",
2282
2212
  handover: "passer le relais",
2283
2213
  decide: "r\xE9pondre \xE0 la d\xE9cision",
2284
2214
  sendAnswer: "envoyer la r\xE9ponse",
@@ -2306,16 +2236,12 @@ var fr3 = {
2306
2236
  deny: "refuser",
2307
2237
  ignore: "ignorer",
2308
2238
  review: "revoir",
2309
- openSession: (name) => `ouvrir ${name}`,
2310
- rolesTemplates: "r\xF4les / mod\xE8les",
2311
- folder: "dossier",
2312
- launchRole: "lancer avec ce r\xF4le",
2313
- launchTeam: "lancer cette \xE9quipe",
2314
- openFile: "ouvrir le fichier",
2315
- ignored: "ignor\xE9s",
2316
- reread: "relire"
2239
+ openSession: (name) => `ouvrir ${name}`
2317
2240
  },
2318
2241
  overview: {
2242
+ rigsGroup: "RIGS",
2243
+ kernelGroup: "KERNEL",
2244
+ rigSeat: (rig, role) => `rig ${rig} \xB7 ${role}`,
2319
2245
  yours: (n) => `\xC0 toi \xB7 ${n}`,
2320
2246
  projects: "Projets",
2321
2247
  exchanges: "\xC9changes",
@@ -2336,11 +2262,6 @@ var fr3 = {
2336
2262
  deny: "Refuser",
2337
2263
  always: "Toujours",
2338
2264
  denyWithNote: "Refuser en expliquant",
2339
- common: "table commune",
2340
- teamLine: (done, given) => `\xE9quipe \xB7 ${done}/${given}`,
2341
- resting: "au repos \xB7 R pour reprendre",
2342
- tasks: (team) => `T\xE2ches \xB7 ${team}`,
2343
- noTask: "Aucune t\xE2che.",
2344
2265
  ledge: "Ledge",
2345
2266
  near: (name, pct) => `${name} \xB7 ctx ${pct} % \xB7 H rel\xE8ve`,
2346
2267
  reliefFailed: (name, why) => `La rel\xE8ve de ${name} a \xE9chou\xE9 : ${why}`,
@@ -2352,10 +2273,7 @@ var fr3 = {
2352
2273
  decision: (giver, title2) => `${giver} te demande : ${title2}`,
2353
2274
  decisionTitle: (giver, title2) => `R\xE9pondre \xE0 ${giver} \xB7 ${title2}`,
2354
2275
  decisionHint: "Ta r\xE9ponse, telle que la session la lira\u2026",
2355
- confirmAnswer: (giver) => `Envoyer \xE0 ${giver} ? Une r\xE9ponse ne se reprend pas`,
2356
- joinWhich: (name) => `${name} rejoint quelle \xE9quipe ? (\u2191\u2193 \u21B5, esc)`,
2357
- noTeamHere: "Aucune \xE9quipe dans ce projet.",
2358
- notInTeam: "Cette session n\u2019est dans aucune \xE9quipe."
2276
+ confirmAnswer: (giver) => `Envoyer \xE0 ${giver} ? Une r\xE9ponse ne se reprend pas`
2359
2277
  },
2360
2278
  session: {
2361
2279
  you: "Vous",
@@ -2413,19 +2331,8 @@ var fr3 = {
2413
2331
  offline: "Le daemon local est hors ligne : la question attendra.",
2414
2332
  steps: { sessions: "les sessions", pane: (n) => `le terminal de ${n}`, reply: (n) => `la r\xE9ponse de ${n}`, git: (n) => `l\u2019\xE9tat git de ${n}`, repo: (p) => p },
2415
2333
  operator: (s, m) => `Ledge \xB7 Op\xE9rateur \xB7 voit ${s} ${plural(s, "session", "sessions")}, ${m} ${plural(m, "machine", "machines")}`,
2416
- teamCard: (name) => `\xC9quipe \xB7 ${name}`,
2417
2334
  taskCard: "T\xE2ches \xE0 confier",
2418
- launchTeam: "Lancer l\u2019\xE9quipe",
2419
- save: "Enregistrer",
2420
- saved: "enregistr\xE9e",
2421
- seats: "Si\xE8ges",
2422
- table: "Table",
2423
- newTable: (project) => `nouvelle table dans la pi\xE8ce ${project}`,
2424
- autonomy: { ask: "demande", edits: "modifie sans demander", auto: "autonome" },
2425
- autoWarning: (roles) => `${roles} travaillera sans demander.`,
2426
- teamLaunched: (at) => `\u2713 \xC9quipe lanc\xE9e \xE0 ${at}`,
2427
2335
  give: "Confier",
2428
- waitTeam: "active une fois l\u2019\xE9quipe lanc\xE9e",
2429
2336
  given: (owner, id, state2) => `\u2713 Confi\xE9e \xE0 ${owner} \xB7 ${id} \xB7 ${state2}`,
2430
2337
  taskState: { open: "\xE0 prendre", taken: "prise", done: "finie", refused: "refus\xE9e", dropped: "abandonn\xE9e" },
2431
2338
  placeholder: "Une question pour Ledge\u2026",
@@ -2476,66 +2383,7 @@ var fr3 = {
2476
2383
  thenAttach: "puis a pour s\u2019attacher \xE0 son terminal",
2477
2384
  missing: { prompt: "\xC9crivez une instruction.", machine: "Aucune machine en ligne.", repos: "Choisissez un d\xE9p\xF4t ou un dossier." },
2478
2385
  launching: "Lancement\u2026",
2479
- launched: (name) => `\u2713 ${name} est lanc\xE9e`,
2480
- teamTitle: "Nouvelle \xE9quipe",
2481
- mode: "Mode",
2482
- session: "session",
2483
- team: "\xE9quipe",
2484
- template: "Mod\xE8le",
2485
- custom: "si\xE8ges choisis un par un",
2486
- seats: "Si\xE8ges",
2487
- teamName: "Nom",
2488
- noRole: "aucun r\xF4le : \xE9cris ~/.ledge/roles/<id>.md",
2489
- loadingRoles: "lecture des r\xF4les\u2026",
2490
- teamPreview: (team, project) => `L\u2019\xE9quipe ${team} s\u2019installera \xE0 une nouvelle table de la pi\xE8ce ${project}.`,
2491
- teamLaunched: (name) => `\u2713 \xC9quipe ${name} lanc\xE9e`,
2492
- teamFailed: (why) => `\xC9quipe non lanc\xE9e : ${why}`,
2493
- noSeat: "Choisissez au moins un si\xE8ge."
2494
- },
2495
- catalog: {
2496
- roles: "r\xF4les",
2497
- templates: "mod\xE8les",
2498
- homeOnly: "Perso seulement",
2499
- repoRoles: "d\xE9p\xF4t \xB7 .ledge/roles",
2500
- homeRoles: "perso \xB7 ~/.ledge/roles",
2501
- repoTeams: "d\xE9p\xF4t \xB7 .ledge/teams",
2502
- homeTeams: "perso \xB7 ~/.ledge/teams",
2503
- repo: "d\xE9p\xF4t",
2504
- home: "perso",
2505
- tool: "outil",
2506
- model: "mod\xE8le",
2507
- effort: "effort",
2508
- autonomy: "autonomie",
2509
- file: "fichier",
2510
- usedIn: "dans",
2511
- heldBy: "tenu par",
2512
- teams: "\xE9quipes",
2513
- replaces: (path2) => `remplace ${path2}`,
2514
- toolDefault: "d\xE9faut de l\u2019outil",
2515
- noTemplate: "aucun mod\xE8le",
2516
- nobody: "personne",
2517
- noTeam: "aucune",
2518
- active: "active",
2519
- resting: "au repos",
2520
- instruction: (n, max) => `Consigne \xB7 ${n}/${max}`,
2521
- noInstruction: "Pas de consigne.",
2522
- seats: (n) => `${n} ${plural(n, "poste", "postes")}`,
2523
- autonomyShort: { ask: "demande", edits: "\xE9ditions", auto: "auto" },
2524
- ignored: (n) => `\u26A0 ${n} ${plural(n, "fichier ignor\xE9", "fichiers ignor\xE9s")}`,
2525
- see: "voir",
2526
- problems: "Fichiers ignor\xE9s",
2527
- noProblem: "Aucun fichier ignor\xE9.",
2528
- loading: "lecture des r\xF4les\u2026",
2529
- noAnswer: "Le daemon n\u2019a pas r\xE9pondu.",
2530
- offline: (m) => `${m} est hors ligne.`,
2531
- outdated: (m) => `Le daemon de ${m} est trop ancien pour le catalogue.`,
2532
- onMachine: (m) => `sur ${m}`,
2533
- roleChecking: (id) => `@${id} : lecture du r\xF4le dans ce dossier\u2026`,
2534
- roleReadIn: (cwd) => `celui de ${cwd}`,
2535
- roleElsewhere: (id, cwd) => `@${id} n\u2019est lu que dans ${cwd} : lanc\xE9 sans r\xF4le`,
2536
- noRole: "Aucun r\xF4le ici : \xE9cris .ledge/roles/<id>.md",
2537
- noTemplateHere: "Aucun mod\xE8le ici : \xE9cris .ledge/teams/<id>.yaml",
2538
- role: "R\xF4le"
2386
+ launched: (name) => `\u2713 ${name} est lanc\xE9e`
2539
2387
  },
2540
2388
  settings: { title: "R\xE9glages", language: "Langue", theme: "Th\xE8me", themes: { auto: "Auto", dark: "Sombre", light: "Clair" }, themeAuto: (tone2) => `Auto (fond ${tone2 === "light" ? "clair" : "sombre"})`, context: "Contexte max", contextHint: "au-del\xE0 : passer le relais", contextUnknown: "mettez \xE0 jour le daemon", contextSaved: (pct) => `Contexte max : ${pct} %`, saved: "R\xE9glage enregistr\xE9.", saveFailed: (m) => `R\xE9glage appliqu\xE9, mais non enregistr\xE9 : ${m}` },
2541
2389
  help: {
@@ -2546,8 +2394,9 @@ var fr3 = {
2546
2394
  ask: "Demander \xE0 Ledge",
2547
2395
  launch: "Nouvelle session",
2548
2396
  settings: "R\xE9glages",
2397
+ openrig: "OpenRig",
2398
+ rigLaunch: "Lancer un rig",
2549
2399
  screens: "changer d\u2019\xE9cran",
2550
- teams: "\xC9quipes",
2551
2400
  field: "Saisie",
2552
2401
  wordBefore: "effacer le mot avant",
2553
2402
  more: (above, below) => [above ? `\u2191 ${above} plus haut` : "", below ? `\u2193 ${below} plus bas` : ""].filter(Boolean).join(" \xB7 ")
@@ -2593,9 +2442,6 @@ var fr3 = {
2593
2442
  noTerminal: (n) => `Pas de terminal pour ${n}.`,
2594
2443
  attachFailed: (code3) => `tmux s\u2019est arr\xEAt\xE9 avec le code ${code3}.`,
2595
2444
  modelSwitch: "Ce daemon ne change pas le mod\xE8le d\u2019une session en cours.",
2596
- joined: (n, team) => `${n} rejoint ${team}.`,
2597
- resumed: (team) => `Reprise de l\u2019\xE9quipe ${team} demand\xE9e.`,
2598
- saved: (path2) => `\xC9quipe enregistr\xE9e : ${path2}`,
2599
2445
  draftKept: "Brouillon gard\xE9 : r pour reprendre.",
2600
2446
  draftQuit: "Brouillon en cours : ctrl+c encore pour quitter quand m\xEAme (il sera perdu).",
2601
2447
  exited: (cmd, code3) => `${cmd} s\u2019est arr\xEAt\xE9 avec le code ${code3}.`
@@ -2612,10 +2458,154 @@ var fr3 = {
2612
2458
  question: (n, text5) => `${n} a une question : ${text5}`,
2613
2459
  title: (n) => n > 0 ? `ledge \xB7 ${n} t\u2019${plural(n, "attend", "attendent")}` : "ledge"
2614
2460
  },
2461
+ openrig: {
2462
+ operatorKey: "op\xE9rateur",
2463
+ operatorHint: "O : lui parler",
2464
+ operatorNotYet: "Ledge ne suit pas encore la session de l\u2019op\xE9rateur : r\xE9essayez dans quelques secondes.",
2465
+ activity: (a) => ({ running: "au travail", idle: "disponible", unknown: "\u2014" })[a] ?? a,
2466
+ title: "OpenRig",
2467
+ daemon: "Daemon",
2468
+ kernel: "Kernel",
2469
+ rigs: "Rigs",
2470
+ machine: "machine",
2471
+ toggle: "d\xE9marrer/arr\xEAter",
2472
+ restart: "red\xE9marrer",
2473
+ unfold: "d\xE9plier",
2474
+ daemonKey: "daemon",
2475
+ kernelKey: "kernel",
2476
+ daemonState: { running: "actif", stopped: "arr\xEAt\xE9", stale: "arr\xEAt\xE9 sans arr\xEAt propre", unknown: "inconnu" },
2477
+ kernelState: { ready: "pr\xEAt", starting: "d\xE9marrage", stopped: "arr\xEAt\xE9", skipped: "sans kernel", absent: "absent", unknown: "inconnu" },
2478
+ look: { running: "en marche", starting: "d\xE9marrage", attention: "\xE0 surveiller", stopped: "arr\xEAt\xE9" },
2479
+ daemonMeta: (port, pid) => [port ? `127.0.0.1:${port}` : "", pid ? `pid ${pid}` : ""].filter(Boolean).join(" \xB7 "),
2480
+ restoreHint: "\u21BB au d\xE9marrage : rigs relanc\xE9s",
2481
+ kernelHint: "l\u2019op\xE9rateur qui aide \xE0 lancer un rig",
2482
+ seats: (running, total) => `${running}/${total}`,
2483
+ attention: (n) => `${n} ${plural(n, "si\xE8ge demande", "si\xE8ges demandent")} attention`,
2484
+ since: (uptime) => `depuis ${uptime}`,
2485
+ snapshot: (s) => `snapshot ${s}`,
2486
+ work: (n) => n ? `${n} ${plural(n, "t\xE2che", "t\xE2ches")}` : "",
2487
+ stoppedOnPurpose: "arr\xEAt\xE9 volontairement",
2488
+ noRigs: "Aucun rig sur cette machine.",
2489
+ notInstalled: "OpenRig n\u2019est pas install\xE9 sur cette machine (la commande rig est introuvable).",
2490
+ stoppedDaemon: "Le daemon OpenRig est arr\xEAt\xE9 : D le d\xE9marre.",
2491
+ offline: "Machine hors ligne.",
2492
+ outdated: "Le daemon Ledge de cette machine ne conna\xEEt pas OpenRig : mettez-le \xE0 jour.",
2493
+ loading: "Lecture d\u2019OpenRig\u2026",
2494
+ version: (v) => `OpenRig ${v}`,
2495
+ confirmTitle: "Confirmer",
2496
+ confirm: (what) => `${what} ?`,
2497
+ what: {
2498
+ "daemon.stop": "Arr\xEAter le daemon OpenRig",
2499
+ "daemon.restart": "Red\xE9marrer le daemon OpenRig",
2500
+ "kernel.stop": "Arr\xEAter le kernel",
2501
+ "kernel.restart": "Red\xE9marrer le kernel",
2502
+ "rig.stop": (rig) => `Arr\xEAter le rig \xAB ${rig} \xBB`,
2503
+ "rig.restart": (rig) => `Red\xE9marrer le rig \xAB ${rig} \xBB`
2504
+ },
2505
+ busy: (names, n) => n ? `${n} ${plural(n, "agent au travail s\u2019arr\xEAte", "agents au travail s\u2019arr\xEAtent")} : ${names}.` : "Aucun agent au travail.",
2506
+ yes: "confirmer",
2507
+ no: "annuler",
2508
+ running: (cmd) => `en cours : ${cmd}`,
2509
+ done: (cmd) => `fait : ${cmd}`,
2510
+ failed: (why) => `\xE9chec : ${why}`,
2511
+ why: { busy: "une autre action OpenRig est en cours sur cette machine", not_installed: "OpenRig n\u2019est pas install\xE9", offline: "machine hors ligne", outdated: "daemon Ledge trop ancien" }
2512
+ },
2513
+ rigLaunch: {
2514
+ title: "Lancer un rig",
2515
+ launchKey: "lancer un rig",
2516
+ count: "nombre de si\xE8ges",
2517
+ permissions: "4 \xB7 Permissions",
2518
+ policyKey: "permissions",
2519
+ policyOf: { auto: "Auto", standard: "Standard", open: "Ouvert", yolo: "YOLO", locked: "Verrouill\xE9" },
2520
+ specPolicy: (p) => `celle de la spec (${p})`,
2521
+ noSpecPolicy: "aucune",
2522
+ asRig: "comme le rig",
2523
+ policyHint: {
2524
+ none: "sans politique : Claude en acceptEdits, Codex en workspace-write",
2525
+ auto: "Claude d\xE9cide seul (mode auto) ; Codex garde la posture par d\xE9faut",
2526
+ standard: "tout passe sauf PR, publication, fusion, force push et destructif (demande une personne) ; r\xE8gles appliqu\xE9es une fois traduites dans les r\xE9glages du harnais",
2527
+ open: "tout passe sauf le destructif ; r\xE8gles appliqu\xE9es une fois traduites dans les r\xE9glages du harnais",
2528
+ yolo: "aucune demande : Claude --dangerously-skip-permissions, Codex danger-full-access",
2529
+ locked: "seuls la cha\xEEne d\u2019outils et rig up/down passent ; r\xE8gles appliqu\xE9es une fois traduites dans les r\xE9glages du harnais"
2530
+ },
2531
+ nonInterruptive: "accepter les avertissements de premier lancement (--non-interruptive)",
2532
+ spec: "1 \xB7 Spec de d\xE9part",
2533
+ where: "2 \xB7 O\xF9",
2534
+ roles: "3 \xB7 Si\xE8ges par r\xF4le",
2535
+ delegation: "5 \xB7 Qui d\xE9l\xE8gue \xE0 qui",
2536
+ preview: "Aper\xE7u \xB7 rig.yaml",
2537
+ specHint: "Le mod\xE8le de rig : ses pods, ses r\xF4les, les liens entre eux.",
2538
+ source: { user: "mes specs", builtin: "OpenRig" },
2539
+ seatsOf: (n) => `${n} ${plural(n, "si\xE8ge", "si\xE8ges")}`,
2540
+ folder: "Dossier",
2541
+ name: "Nom",
2542
+ folderHint: "un dossier de cette machine, ~ accept\xE9",
2543
+ projectFolder: (p) => `projet ${p}`,
2544
+ model: "mod\xE8le",
2545
+ defaultModel: "par d\xE9faut",
2546
+ modelKey: "mod\xE8le",
2547
+ rolesHint: "Un compteur \xE0 3 donne build, build-2, build-3, identiques au premier.",
2548
+ pairs: "par paires",
2549
+ all: "chaque si\xE8ge \u2192 tous",
2550
+ pairsHint: "build-2 \u2192 review-2",
2551
+ allHint: "chaque build \u2192 tous les review",
2552
+ save: "enregistrer dans mes specs",
2553
+ notScaled: "un si\xE8ge par r\xF4le : rien \xE0 d\xE9l\xE9guer de plus",
2554
+ loading: "Lecture des specs\u2026",
2555
+ noSpecs: "Aucune spec sur cette machine.",
2556
+ daemonStopped: "D\xE9marrez le daemon OpenRig d\u2019abord (D sur l\u2019\xE9cran OpenRig).",
2557
+ noCheck: "p v\xE9rifie le lancement (rig up --plan), sans rien d\xE9marrer.",
2558
+ checking: "V\xE9rification\u2026",
2559
+ launching: "Lancement\u2026",
2560
+ passes: (seats) => `\u2713 le plan passe \xB7 ${seats} ${plural(seats, "si\xE8ge", "si\xE8ges")}`,
2561
+ planStatus: (s) => `plan : ${s}`,
2562
+ warning: (text5, seats) => seats ? `${text5} (${seats})` : text5,
2563
+ launched: (name, n) => `Rig \xAB ${name} \xBB lanc\xE9 \xB7 ${n} ${plural(n, "si\xE8ge", "si\xE8ges")}.`,
2564
+ check: "v\xE9rifier (--plan)",
2565
+ launch: (n) => `lancer ${n} ${plural(n, "si\xE8ge", "si\xE8ges")}`,
2566
+ missing: { spec: "Choisissez une spec.", cwd: "Indiquez le dossier de travail.", name: "Le nom du rig : lettres, chiffres, . _ - (pas de - au d\xE9but).", seats: "Au moins un si\xE8ge." },
2567
+ why: {
2568
+ busy: "Une autre action OpenRig est en cours sur cette machine.",
2569
+ cwd_missing: "Ce dossier n\u2019existe pas sur la machine.",
2570
+ spec_not_found: "Cette spec n\u2019est plus dans la biblioth\xE8que.",
2571
+ daemon_stopped: "D\xE9marrez le daemon OpenRig d\u2019abord (D sur l\u2019\xE9cran OpenRig).",
2572
+ not_installed: "OpenRig n\u2019est pas install\xE9 sur cette machine.",
2573
+ offline: "Machine hors ligne.",
2574
+ outdated: "Le daemon Ledge de cette machine ne sait pas lancer de rig : mettez-le \xE0 jour."
2575
+ }
2576
+ },
2577
+ rigSeats: {
2578
+ title: (rig, machine) => `Si\xE8ges \xB7 ${rig} \xB7 ${machine}`,
2579
+ key: "si\xE8ges",
2580
+ stays: (now2) => `Le rig reste en marche. Aujourd\u2019hui : ${now2}.`,
2581
+ role: "r\xF4le",
2582
+ count: "nombre",
2583
+ delegationKey: "d\xE9l\xE9gation",
2584
+ apply: "appliquer",
2585
+ cancel: "annuler",
2586
+ pairs: "d\xE9l\xE9gation par paires",
2587
+ all: "chaque ajout \u2192 tous",
2588
+ linksPairs: "liens ajout\xE9s par paires",
2589
+ linksAll: "liens ajout\xE9s \xE0 tous",
2590
+ nothing: "Changez un nombre : - +",
2591
+ notRunning: "Seul un rig en marche change de si\xE8ges.",
2592
+ busy: (seat) => `${seat} travaille : son travail en cours s\u2019arr\xEAte.`,
2593
+ applying: "en cours\u2026",
2594
+ done: (added, removed, links) => `+${added} \u2212${removed} si\xE8ges${links !== void 0 ? `, ${links} ${plural(links, "lien", "liens")}` : ""}`,
2595
+ linksUnsupported: "cet OpenRig ne sait pas lier des si\xE8ges ajout\xE9s \xE0 un rig en marche (avant 0.7)",
2596
+ why: {
2597
+ rig_not_running: "Le rig n\u2019est plus en marche.",
2598
+ rig_root_unknown: "Impossible de copier un si\xE8ge : les agents du rig sont introuvables.",
2599
+ busy: "Une autre action OpenRig est en cours sur cette machine.",
2600
+ not_installed: "OpenRig n\u2019est pas install\xE9 sur cette machine.",
2601
+ offline: "Machine hors ligne.",
2602
+ outdated: "Le daemon Ledge de cette machine ne sait pas changer les si\xE8ges d\u2019un rig : mettez-le \xE0 jour."
2603
+ }
2604
+ },
2615
2605
  small: {
2616
2606
  tooSmall: (w, h) => `Fen\xEAtre trop petite : ${w}\xD7${h}`,
2617
2607
  atLeast: (w, h) => `il faut au moins ${w}\xD7${h}`,
2618
- waits: (name, question) => question ? `${name} a une question` : `${name} attend ton accord`,
2608
+ waits: (name, question2) => question2 ? `${name} a une question` : `${name} attend ton accord`,
2619
2609
  kept: "brouillon gard\xE9"
2620
2610
  },
2621
2611
  cli: { notTty: "ledge tui: needs a terminal (stdin and stdout must be a TTY)", movedToTab: "ledge tui s\u2019ouvre dans l\u2019onglet Ledge.", loadingProfile: "Windows Terminal charge le profil Ledge\u2026" }
@@ -2627,7 +2617,7 @@ var en3 = {
2627
2617
  lang: { fr: "Fran\xE7ais", en: "English" },
2628
2618
  state: { approval: "Needs you", question: "A question", starting: "Starting", idle: "Available", working: "Working", done: "Finished", error: "Error", readonly: "Read-only" },
2629
2619
  time: { now: "just now", decimal: ".", s: (n) => `${n}s`, min: (n) => `${n} min`, h: (n) => `${n} h`, d: (n) => `${n} d` },
2630
- tabs: { overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", teams: "Teams" },
2620
+ tabs: { overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", openrig: "OpenRig" },
2631
2621
  bar: {
2632
2622
  sessions: (n) => `${n} ${plural2(n, "session", "sessions")}`,
2633
2623
  machines: (n) => `${n} ${plural2(n, "machine", "machines")}`,
@@ -2669,11 +2659,6 @@ var en3 = {
2669
2659
  scroll: "scroll",
2670
2660
  space: "space",
2671
2661
  redraw: "another name",
2672
- save: "save the team",
2673
- tasks: "team tasks",
2674
- join: "join a team",
2675
- resume: "resume the team",
2676
- teamMode: "session or team",
2677
2662
  handover: "hand over",
2678
2663
  decide: "answer the decision",
2679
2664
  sendAnswer: "send the answer",
@@ -2701,16 +2686,12 @@ var en3 = {
2701
2686
  deny: "deny",
2702
2687
  ignore: "ignore",
2703
2688
  review: "review",
2704
- openSession: (name) => `open ${name}`,
2705
- rolesTemplates: "roles / templates",
2706
- folder: "folder",
2707
- launchRole: "launch with this role",
2708
- launchTeam: "launch this team",
2709
- openFile: "open the file",
2710
- ignored: "ignored",
2711
- reread: "read again"
2689
+ openSession: (name) => `open ${name}`
2712
2690
  },
2713
2691
  overview: {
2692
+ rigsGroup: "RIGS",
2693
+ kernelGroup: "KERNEL",
2694
+ rigSeat: (rig, role) => `rig ${rig} \xB7 ${role}`,
2714
2695
  yours: (n) => `Yours \xB7 ${n}`,
2715
2696
  projects: "Projects",
2716
2697
  exchanges: "Messages",
@@ -2731,11 +2712,6 @@ var en3 = {
2731
2712
  deny: "Deny",
2732
2713
  always: "Always",
2733
2714
  denyWithNote: "Deny with a note",
2734
- common: "common table",
2735
- teamLine: (done, given) => `team \xB7 ${done}/${given}`,
2736
- resting: "resting \xB7 R to resume",
2737
- tasks: (team) => `Tasks \xB7 ${team}`,
2738
- noTask: "No task.",
2739
2715
  ledge: "Ledge",
2740
2716
  near: (name, pct) => `${name} \xB7 ctx ${pct} % \xB7 H hand over`,
2741
2717
  reliefFailed: (name, why) => `The relief of ${name} failed: ${why}`,
@@ -2747,10 +2723,7 @@ var en3 = {
2747
2723
  decision: (giver, title2) => `${giver} asks you: ${title2}`,
2748
2724
  decisionTitle: (giver, title2) => `Answer ${giver} \xB7 ${title2}`,
2749
2725
  decisionHint: "Your answer, as the session will read it\u2026",
2750
- confirmAnswer: (giver) => `Send to ${giver}? An answer cannot be taken back`,
2751
- joinWhich: (name) => `${name} joins which team? (\u2191\u2193 \u21B5, esc)`,
2752
- noTeamHere: "No team in this project.",
2753
- notInTeam: "This session is in no team."
2726
+ confirmAnswer: (giver) => `Send to ${giver}? An answer cannot be taken back`
2754
2727
  },
2755
2728
  session: {
2756
2729
  you: "You",
@@ -2808,19 +2781,8 @@ var en3 = {
2808
2781
  offline: "The local daemon is offline: the question will wait.",
2809
2782
  steps: { sessions: "the sessions", pane: (n) => `${n}'s terminal`, reply: (n) => `${n}'s reply`, git: (n) => `${n}'s git state`, repo: (p) => p },
2810
2783
  operator: (s, m) => `Ledge \xB7 Operator \xB7 sees ${s} ${plural2(s, "session", "sessions")}, ${m} ${plural2(m, "machine", "machines")}`,
2811
- teamCard: (name) => `Team \xB7 ${name}`,
2812
2784
  taskCard: "Tasks to give",
2813
- launchTeam: "Launch the team",
2814
- save: "Save",
2815
- saved: "saved",
2816
- seats: "Seats",
2817
- table: "Table",
2818
- newTable: (project) => `new table in the ${project} room`,
2819
- autonomy: { ask: "asks", edits: "edits without asking", auto: "autonomous" },
2820
- autoWarning: (roles) => `${roles} will work without asking.`,
2821
- teamLaunched: (at) => `\u2713 Team launched at ${at}`,
2822
2785
  give: "Give",
2823
- waitTeam: "active once the team is launched",
2824
2786
  given: (owner, id, state2) => `\u2713 Given to ${owner} \xB7 ${id} \xB7 ${state2}`,
2825
2787
  taskState: { open: "to take", taken: "taken", done: "done", refused: "refused", dropped: "dropped" },
2826
2788
  placeholder: "A question for Ledge\u2026",
@@ -2876,66 +2838,7 @@ var en3 = {
2876
2838
  thenAttach: "then a to attach to its terminal",
2877
2839
  missing: { prompt: "Write an instruction.", machine: "No machine online.", repos: "Pick a repository or a folder." },
2878
2840
  launching: "Launching\u2026",
2879
- launched: (name) => `\u2713 ${name} is started`,
2880
- teamTitle: "New team",
2881
- mode: "Mode",
2882
- session: "session",
2883
- team: "team",
2884
- template: "Template",
2885
- custom: "seats chosen one by one",
2886
- seats: "Seats",
2887
- teamName: "Name",
2888
- noRole: "no role: write ~/.ledge/roles/<id>.md",
2889
- loadingRoles: "reading the roles\u2026",
2890
- teamPreview: (team, project) => `Team ${team} will sit at a new table in the ${project} room.`,
2891
- teamLaunched: (name) => `\u2713 Team ${name} launched`,
2892
- teamFailed: (why) => `Team not launched: ${why}`,
2893
- noSeat: "Choose at least one seat."
2894
- },
2895
- catalog: {
2896
- roles: "roles",
2897
- templates: "templates",
2898
- homeOnly: "Personal only",
2899
- repoRoles: "repository \xB7 .ledge/roles",
2900
- homeRoles: "personal \xB7 ~/.ledge/roles",
2901
- repoTeams: "repository \xB7 .ledge/teams",
2902
- homeTeams: "personal \xB7 ~/.ledge/teams",
2903
- repo: "repository",
2904
- home: "personal",
2905
- tool: "agent",
2906
- model: "model",
2907
- effort: "effort",
2908
- autonomy: "autonomy",
2909
- file: "file",
2910
- usedIn: "in",
2911
- heldBy: "held by",
2912
- teams: "teams",
2913
- replaces: (path2) => `replaces ${path2}`,
2914
- toolDefault: "the agent\u2019s default",
2915
- noTemplate: "no template",
2916
- nobody: "nobody",
2917
- noTeam: "none",
2918
- active: "active",
2919
- resting: "resting",
2920
- instruction: (n, max) => `Instruction \xB7 ${n}/${max}`,
2921
- noInstruction: "No instruction.",
2922
- seats: (n) => `${n} ${plural2(n, "seat", "seats")}`,
2923
- autonomyShort: { ask: "asks", edits: "edits", auto: "auto" },
2924
- ignored: (n) => `\u26A0 ${n} ${plural2(n, "file", "files")} ignored`,
2925
- see: "see",
2926
- problems: "Ignored files",
2927
- noProblem: "No ignored file.",
2928
- loading: "reading the roles\u2026",
2929
- noAnswer: "The daemon did not answer.",
2930
- offline: (m) => `${m} is offline.`,
2931
- outdated: (m) => `${m}\u2019s daemon is too old for the catalog.`,
2932
- onMachine: (m) => `on ${m}`,
2933
- roleChecking: (id) => `@${id}: reading the role in this folder\u2026`,
2934
- roleReadIn: (cwd) => `the one in ${cwd}`,
2935
- roleElsewhere: (id, cwd) => `@${id} is only read in ${cwd}: launched without a role`,
2936
- noRole: "No role here: write .ledge/roles/<id>.md",
2937
- noTemplateHere: "No template here: write .ledge/teams/<id>.yaml",
2938
- role: "Role"
2841
+ launched: (name) => `\u2713 ${name} is started`
2939
2842
  },
2940
2843
  settings: { title: "Settings", language: "Language", theme: "Theme", themes: { auto: "Auto", dark: "Dark", light: "Light" }, themeAuto: (tone2) => `Auto (${tone2} background)`, context: "Max context", contextHint: "past it: hand over", contextUnknown: "update the daemon", contextSaved: (pct) => `Max context: ${pct} %`, saved: "Setting saved.", saveFailed: (m) => `Setting applied, but not saved: ${m}` },
2941
2844
  help: {
@@ -2946,8 +2849,9 @@ var en3 = {
2946
2849
  ask: "Ask Ledge",
2947
2850
  launch: "New session",
2948
2851
  settings: "Settings",
2852
+ openrig: "OpenRig",
2853
+ rigLaunch: "Launch a rig",
2949
2854
  screens: "switch screen",
2950
- teams: "Teams",
2951
2855
  field: "Typing",
2952
2856
  wordBefore: "delete the word before",
2953
2857
  more: (above, below) => [above ? `\u2191 ${above} above` : "", below ? `\u2193 ${below} below` : ""].filter(Boolean).join(" \xB7 ")
@@ -2986,9 +2890,6 @@ var en3 = {
2986
2890
  noTerminal: (n) => `No terminal for ${n}.`,
2987
2891
  attachFailed: (code3) => `tmux stopped with code ${code3}.`,
2988
2892
  modelSwitch: "This daemon cannot change a running session\u2019s model.",
2989
- joined: (n, team) => `${n} joins ${team}.`,
2990
- resumed: (team) => `Asked to resume team ${team}.`,
2991
- saved: (path2) => `Team saved: ${path2}`,
2992
2893
  draftKept: "Draft kept: r to pick it up.",
2993
2894
  draftQuit: "Draft in progress: ctrl+c again to quit anyway (it will be lost).",
2994
2895
  exited: (cmd, code3) => `${cmd} exited with code ${code3}.`
@@ -3005,10 +2906,154 @@ var en3 = {
3005
2906
  question: (n, text5) => `${n} has a question: ${text5}`,
3006
2907
  title: (n) => n > 0 ? `ledge \xB7 ${n} waiting` : "ledge"
3007
2908
  },
2909
+ openrig: {
2910
+ operatorKey: "operator",
2911
+ operatorHint: "O: talk to it",
2912
+ operatorNotYet: "Ledge does not follow the operator\u2019s session yet: try again in a few seconds.",
2913
+ activity: (a) => ({ running: "working", idle: "available", unknown: "\u2014" })[a] ?? a,
2914
+ title: "OpenRig",
2915
+ daemon: "Daemon",
2916
+ kernel: "Kernel",
2917
+ rigs: "Rigs",
2918
+ machine: "machine",
2919
+ toggle: "start/stop",
2920
+ restart: "restart",
2921
+ unfold: "unfold",
2922
+ daemonKey: "daemon",
2923
+ kernelKey: "kernel",
2924
+ daemonState: { running: "running", stopped: "stopped", stale: "stopped uncleanly", unknown: "unknown" },
2925
+ kernelState: { ready: "ready", starting: "starting", stopped: "stopped", skipped: "no kernel", absent: "absent", unknown: "unknown" },
2926
+ look: { running: "running", starting: "starting", attention: "needs a look", stopped: "stopped" },
2927
+ daemonMeta: (port, pid) => [port ? `127.0.0.1:${port}` : "", pid ? `pid ${pid}` : ""].filter(Boolean).join(" \xB7 "),
2928
+ restoreHint: "\u21BB on start: rigs brought back",
2929
+ kernelHint: "the operator that helps start a rig",
2930
+ seats: (running, total) => `${running}/${total}`,
2931
+ attention: (n) => `${n} ${plural2(n, "seat needs", "seats need")} attention`,
2932
+ since: (uptime) => `up ${uptime}`,
2933
+ snapshot: (s) => `snapshot ${s}`,
2934
+ work: (n) => n ? `${n} ${plural2(n, "task", "tasks")}` : "",
2935
+ stoppedOnPurpose: "stopped on purpose",
2936
+ noRigs: "No rig on this machine.",
2937
+ notInstalled: "OpenRig is not installed on this machine (no rig command).",
2938
+ stoppedDaemon: "The OpenRig daemon is stopped: D starts it.",
2939
+ offline: "Machine offline.",
2940
+ outdated: "This machine\u2019s Ledge daemon does not know OpenRig: update it.",
2941
+ loading: "Reading OpenRig\u2026",
2942
+ version: (v) => `OpenRig ${v}`,
2943
+ confirmTitle: "Confirm",
2944
+ confirm: (what) => `${what}?`,
2945
+ what: {
2946
+ "daemon.stop": "Stop the OpenRig daemon",
2947
+ "daemon.restart": "Restart the OpenRig daemon",
2948
+ "kernel.stop": "Stop the kernel",
2949
+ "kernel.restart": "Restart the kernel",
2950
+ "rig.stop": (rig) => `Stop the rig \u201C${rig}\u201D`,
2951
+ "rig.restart": (rig) => `Restart the rig \u201C${rig}\u201D`
2952
+ },
2953
+ busy: (names, n) => n ? `${n} working ${plural2(n, "agent stops", "agents stop")}: ${names}.` : "No agent at work.",
2954
+ yes: "confirm",
2955
+ no: "cancel",
2956
+ running: (cmd) => `running: ${cmd}`,
2957
+ done: (cmd) => `done: ${cmd}`,
2958
+ failed: (why) => `failed: ${why}`,
2959
+ why: { busy: "another OpenRig action runs on this machine", not_installed: "OpenRig is not installed", offline: "machine offline", outdated: "Ledge daemon too old" }
2960
+ },
2961
+ rigLaunch: {
2962
+ title: "Launch a rig",
2963
+ launchKey: "launch a rig",
2964
+ count: "number of seats",
2965
+ permissions: "4 \xB7 Permissions",
2966
+ policyKey: "permissions",
2967
+ policyOf: { auto: "Auto", standard: "Standard", open: "Open", yolo: "YOLO", locked: "Locked" },
2968
+ specPolicy: (p) => `the spec\u2019s (${p})`,
2969
+ noSpecPolicy: "none",
2970
+ asRig: "as the rig",
2971
+ policyHint: {
2972
+ none: "no policy: Claude in acceptEdits, Codex in workspace-write",
2973
+ auto: "Claude decides on its own (auto mode); Codex keeps the default posture",
2974
+ standard: "everything passes but PRs, publishing, merging, force push and destructive (asks a person); rules applied once translated into the harness settings",
2975
+ open: "everything passes but destructive; rules applied once translated into the harness settings",
2976
+ yolo: "no prompt: Claude --dangerously-skip-permissions, Codex danger-full-access",
2977
+ locked: "only the toolchain and rig up/down pass; rules applied once translated into the harness settings"
2978
+ },
2979
+ nonInterruptive: "accept the first-launch warnings (--non-interruptive)",
2980
+ spec: "1 \xB7 Starting spec",
2981
+ where: "2 \xB7 Where",
2982
+ roles: "3 \xB7 Seats per role",
2983
+ delegation: "5 \xB7 Who delegates to whom",
2984
+ preview: "Preview \xB7 rig.yaml",
2985
+ specHint: "The rig\u2019s model: its pods, its roles, the links between them.",
2986
+ source: { user: "my specs", builtin: "OpenRig" },
2987
+ seatsOf: (n) => `${n} ${plural2(n, "seat", "seats")}`,
2988
+ folder: "Folder",
2989
+ name: "Name",
2990
+ folderHint: "a folder of this machine, ~ accepted",
2991
+ projectFolder: (p) => `project ${p}`,
2992
+ model: "model",
2993
+ defaultModel: "default",
2994
+ modelKey: "model",
2995
+ rolesHint: "A counter at 3 gives build, build-2, build-3, the same as the first.",
2996
+ pairs: "in pairs",
2997
+ all: "each seat \u2192 all",
2998
+ pairsHint: "build-2 \u2192 review-2",
2999
+ allHint: "each build \u2192 every review",
3000
+ save: "save in my specs",
3001
+ notScaled: "one seat per role: nothing more to delegate",
3002
+ loading: "Reading the specs\u2026",
3003
+ noSpecs: "No spec on this machine.",
3004
+ daemonStopped: "Start the OpenRig daemon first (D on the OpenRig screen).",
3005
+ noCheck: "p checks the launch (rig up --plan) without starting anything.",
3006
+ checking: "Checking\u2026",
3007
+ launching: "Launching\u2026",
3008
+ passes: (seats) => `\u2713 the plan passes \xB7 ${seats} ${plural2(seats, "seat", "seats")}`,
3009
+ planStatus: (s) => `plan: ${s}`,
3010
+ warning: (text5, seats) => seats ? `${text5} (${seats})` : text5,
3011
+ launched: (name, n) => `Rig \u201C${name}\u201D launched \xB7 ${n} ${plural2(n, "seat", "seats")}.`,
3012
+ check: "check (--plan)",
3013
+ launch: (n) => `launch ${n} ${plural2(n, "seat", "seats")}`,
3014
+ missing: { spec: "Pick a spec.", cwd: "Give the working folder.", name: "The rig\u2019s name: letters, digits, . _ - (no leading -).", seats: "At least one seat." },
3015
+ why: {
3016
+ busy: "Another OpenRig action runs on this machine.",
3017
+ cwd_missing: "This folder does not exist on the machine.",
3018
+ spec_not_found: "This spec is no longer in the library.",
3019
+ daemon_stopped: "Start the OpenRig daemon first (D on the OpenRig screen).",
3020
+ not_installed: "OpenRig is not installed on this machine.",
3021
+ offline: "Machine offline.",
3022
+ outdated: "This machine\u2019s Ledge daemon cannot launch a rig: update it."
3023
+ }
3024
+ },
3025
+ rigSeats: {
3026
+ title: (rig, machine) => `Seats \xB7 ${rig} \xB7 ${machine}`,
3027
+ key: "seats",
3028
+ stays: (now2) => `The rig keeps running. Now: ${now2}.`,
3029
+ role: "role",
3030
+ count: "number",
3031
+ delegationKey: "delegation",
3032
+ apply: "apply",
3033
+ cancel: "cancel",
3034
+ pairs: "delegation in pairs",
3035
+ all: "each added \u2192 all",
3036
+ linksPairs: "links added in pairs",
3037
+ linksAll: "links added to all",
3038
+ nothing: "Change a number: - +",
3039
+ notRunning: "Only a running rig changes seats.",
3040
+ busy: (seat) => `${seat} is working: its work in progress stops.`,
3041
+ applying: "applying\u2026",
3042
+ done: (added, removed, links) => `+${added} \u2212${removed} seats${links !== void 0 ? `, ${links} ${plural2(links, "link", "links")}` : ""}`,
3043
+ linksUnsupported: "this OpenRig cannot link seats added to a running rig (before 0.7)",
3044
+ why: {
3045
+ rig_not_running: "The rig is no longer running.",
3046
+ rig_root_unknown: "Cannot copy a seat: the rig\u2019s agents cannot be found.",
3047
+ busy: "Another OpenRig action runs on this machine.",
3048
+ not_installed: "OpenRig is not installed on this machine.",
3049
+ offline: "Machine offline.",
3050
+ outdated: "This machine\u2019s Ledge daemon cannot change a rig\u2019s seats: update it."
3051
+ }
3052
+ },
3008
3053
  small: {
3009
3054
  tooSmall: (w, h) => `Window too small: ${w}\xD7${h}`,
3010
3055
  atLeast: (w, h) => `needs at least ${w}\xD7${h}`,
3011
- waits: (name, question) => question ? `${name} has a question` : `${name} waits for your approval`,
3056
+ waits: (name, question2) => question2 ? `${name} has a question` : `${name} waits for your approval`,
3012
3057
  kept: "draft kept"
3013
3058
  },
3014
3059
  cli: { notTty: "ledge tui: needs a terminal (stdin and stdout must be a TTY)", movedToTab: "ledge tui opens in the Ledge tab.", loadingProfile: "Windows Terminal is loading the Ledge profile\u2026" }
@@ -3027,37 +3072,33 @@ var useI18n = () => useContext2(I18nContext);
3027
3072
  // src/tui/model/overview.ts
3028
3073
  var listed = (sessions) => sessions.filter((s) => s.state !== "exited");
3029
3074
  var machineOf = (s) => s.machineId ?? splitId(s.id)?.machine ?? "local";
3030
- function overview(sessions, projects, teams = [], tasks = []) {
3075
+ function overview(sessions, projects) {
3031
3076
  const live = listed(sessions);
3032
3077
  const yours = live.filter(isPending).sort((a, b2) => a.lastActivityAt.localeCompare(b2.lastActivityAt) || a.id.localeCompare(b2.id));
3033
3078
  const nodes = /* @__PURE__ */ new Map();
3034
3079
  const known2 = new Map((projects?.projects ?? []).map((p) => [p.id, p]));
3035
- for (const p of projects?.projects ?? []) nodes.set(`p:${p.id}`, { key: `p:${p.id}`, label: p.name, color: p.color, branches: [], count: 0, teams: [] });
3036
- const rooms = /* @__PURE__ */ new Map();
3037
- for (const team of teams) {
3038
- if (team.state === "dissolved") continue;
3039
- const pid = projects ? projectOf({ machineId: team.machine, cwd: team.cwd }, projects) ?? team.project : team.project;
3040
- const node2 = pid ? nodes.get(`p:${pid}`) : void 0;
3041
- if (!node2) continue;
3042
- const mine = tasks.filter((x) => x.team === team.id && x.giver === LEDGE_GIVER);
3043
- const row2 = { key: `${node2.key}|team:${team.id}`, team, sessions: [], done: mine.filter((x) => x.state === "done").length, given: mine.length };
3044
- node2.teams.push(row2);
3045
- rooms.set(team.id, row2);
3046
- }
3080
+ for (const p of projects?.projects ?? []) nodes.set(`p:${p.id}`, { key: `p:${p.id}`, label: p.name, color: p.color, branches: [], count: 0 });
3081
+ const rigNodes = /* @__PURE__ */ new Map();
3047
3082
  for (const s of live) {
3083
+ if (s.rig) {
3084
+ const kind = s.rig.name === "kernel" ? "kernel" : "rig";
3085
+ const key2 = kind === "kernel" ? "g:kernel" : `g:rig:${s.rig.name}`;
3086
+ let node3 = rigNodes.get(key2);
3087
+ if (!node3) {
3088
+ node3 = { key: key2, label: kind === "kernel" ? "Kernel" : s.rig.name, kind, branches: [{ branch: "", sessions: [] }], count: 0 };
3089
+ rigNodes.set(key2, node3);
3090
+ }
3091
+ node3.branches[0].sessions.push(s);
3092
+ node3.count += 1;
3093
+ continue;
3094
+ }
3048
3095
  const pid = projects ? projectOf({ machineId: machineOf(s), cwd: s.cwd }, projects) : null;
3049
3096
  const key = pid && known2.has(pid) ? `p:${pid}` : `r:${machineOf(s)}:${s.repoPath ?? s.cwd}`;
3050
3097
  let node2 = nodes.get(key);
3051
3098
  if (!node2) {
3052
- node2 = { key, label: s.repo ?? s.project, branches: [], count: 0, teams: [] };
3099
+ node2 = { key, label: s.repo ?? s.project, branches: [], count: 0 };
3053
3100
  nodes.set(key, node2);
3054
3101
  }
3055
- const seat = s.team ? rooms.get(s.team) : void 0;
3056
- if (seat && node2.teams.includes(seat)) {
3057
- seat.sessions.push(s);
3058
- node2.count += 1;
3059
- continue;
3060
- }
3061
3102
  const branch = s.branch ?? "-";
3062
3103
  let row2 = node2.branches.find((b2) => b2.branch === branch);
3063
3104
  if (!row2) {
@@ -3067,8 +3108,9 @@ function overview(sessions, projects, teams = [], tasks = []) {
3067
3108
  row2.sessions.push(s);
3068
3109
  node2.count += 1;
3069
3110
  }
3070
- const tree2 = [...nodes.values()].filter((n) => n.count > 0 || n.teams.length > 0);
3071
- const order2 = tree2.flatMap((n) => [...n.branches.flatMap((b2) => b2.sessions.map((s) => s.id)), ...n.teams.flatMap((x) => x.sessions.map((s) => s.id))]);
3111
+ const groups = [...rigNodes.values()].sort((a, b2) => a.kind === b2.kind ? a.label.localeCompare(b2.label) : a.kind === "kernel" ? 1 : -1);
3112
+ const tree2 = [...[...nodes.values()].filter((n) => n.count > 0), ...groups];
3113
+ const order2 = tree2.flatMap((n) => n.branches.flatMap((b2) => b2.sessions.map((s) => s.id)));
3072
3114
  return { yours, tree: tree2, order: order2, machines: new Set(live.map(machineOf)).size };
3073
3115
  }
3074
3116
  var helperCount = (s) => liveHelpers(s).length;
@@ -3094,13 +3136,13 @@ function exchanges(sessions, max, tasks = [], ledge = "Ledge") {
3094
3136
  const byId = new Map(sessions.map((s) => [s.id, s]));
3095
3137
  const bare = (id) => splitId(id)?.id ?? id;
3096
3138
  const find2 = (id) => byId.get(id) ?? sessions.find((x) => bare(x.id) === bare(id));
3097
- const nameOf = (id, then) => id === LEDGE_GIVER ? ledge : find2(id)?.name ?? then;
3139
+ const nameOf2 = (id, then) => id === LEDGE_GIVER ? ledge : find2(id)?.name ?? then;
3098
3140
  return [...sessions.flatMap((s) => {
3099
3141
  const m = s.lastMessage;
3100
3142
  if (!m) return [];
3101
3143
  const peer = m.peer ? find2(m.peer) : void 0;
3102
3144
  return [{ at: m.at, from: s.name, to: peer?.name ?? m.to, summary: m.summary, kind: "message" }];
3103
- }), ...tasks.flatMap((x) => transmissions(x).map((tr) => ({ at: tr.at, from: nameOf(tr.from, tr.fromName), to: nameOf(tr.to, tr.toName), summary: x.title, kind: "task" })))].sort((a, b2) => b2.at.localeCompare(a.at)).slice(0, max);
3145
+ }), ...tasks.flatMap((x) => transmissions(x).map((tr) => ({ at: tr.at, from: nameOf2(tr.from, tr.fromName), to: nameOf2(tr.to, tr.toName), summary: x.title, kind: "task" })))].sort((a, b2) => b2.at.localeCompare(a.at)).slice(0, max);
3104
3146
  }
3105
3147
  var nearContext = (sessions, near) => sessions.filter((s) => s.state !== "exited" && !s.archivedAt && (s.handover ? s.handover.state === "failed" : contextNear(s.context, near)));
3106
3148
  var closedLedgeTasks = (tasks) => tasks.filter((x) => x.giver === LEDGE_GIVER && taskClosed(x) && !x.seen);
@@ -3272,9 +3314,9 @@ var stateColors = (p) => ({
3272
3314
  var STATE_COLOR = stateColors(C);
3273
3315
  var current = "dark";
3274
3316
  var tone = () => current;
3275
- function setTone(next2) {
3276
- current = next2;
3277
- Object.assign(C, PALETTES[next2]);
3317
+ function setTone(next3) {
3318
+ current = next3;
3319
+ Object.assign(C, PALETTES[next3]);
3278
3320
  Object.assign(STATE_COLOR, stateColors(C));
3279
3321
  }
3280
3322
  var projectColor = (color2) => color2 ? PROJECT_COLORS[color2] : C.dim;
@@ -3532,7 +3574,7 @@ function visitParents(tree2, test, visitor, reverse) {
3532
3574
  check = test;
3533
3575
  }
3534
3576
  const is2 = convert(check);
3535
- const step2 = reverse ? -1 : 1;
3577
+ const step3 = reverse ? -1 : 1;
3536
3578
  factory(tree2, void 0, [])();
3537
3579
  function factory(node2, index2, parents) {
3538
3580
  const value = (
@@ -3569,7 +3611,7 @@ function visitParents(tree2, test, visitor, reverse) {
3569
3611
  node2
3570
3612
  );
3571
3613
  if (nodeAsParent.children && result[0] !== SKIP) {
3572
- offset = (reverse ? nodeAsParent.children.length : -1) + step2;
3614
+ offset = (reverse ? nodeAsParent.children.length : -1) + step3;
3573
3615
  grandparents = parents.concat(nodeAsParent);
3574
3616
  while (offset > -1 && offset < nodeAsParent.children.length) {
3575
3617
  const child = nodeAsParent.children[offset];
@@ -3577,7 +3619,7 @@ function visitParents(tree2, test, visitor, reverse) {
3577
3619
  if (subresult[0] === EXIT) {
3578
3620
  return subresult;
3579
3621
  }
3580
- offset = typeof subresult[1] === "number" ? subresult[1] : offset + step2;
3622
+ offset = typeof subresult[1] === "number" ? subresult[1] : offset + step3;
3581
3623
  }
3582
3624
  }
3583
3625
  }
@@ -8220,9 +8262,9 @@ function tokenizeAutolink(effects, ok3, nok) {
8220
8262
  }
8221
8263
  function emailValue(code3) {
8222
8264
  if ((code3 === 45 || asciiAlphanumeric(code3)) && size++ < 63) {
8223
- const next2 = code3 === 45 ? emailValue : emailLabel;
8265
+ const next3 = code3 === 45 ? emailValue : emailLabel;
8224
8266
  effects.consume(code3);
8225
- return next2;
8267
+ return next3;
8226
8268
  }
8227
8269
  return nok(code3);
8228
8270
  }
@@ -11488,11 +11530,11 @@ function tokenizeTable(effects, ok3, nok) {
11488
11530
  }
11489
11531
  }
11490
11532
  const tail = index2 > -1 ? self.events[index2][1].type : null;
11491
- const next2 = tail === "tableHead" || tail === "tableRow" ? bodyRowStart : headRowBefore;
11492
- if (next2 === bodyRowStart && self.parser.lazy[self.now().line]) {
11533
+ const next3 = tail === "tableHead" || tail === "tableRow" ? bodyRowStart : headRowBefore;
11534
+ if (next3 === bodyRowStart && self.parser.lazy[self.now().line]) {
11493
11535
  return nok(code3);
11494
11536
  }
11495
- return next2(code3);
11537
+ return next3(code3);
11496
11538
  }
11497
11539
  function headRowBefore(code3) {
11498
11540
  effects.enter("tableHead");
@@ -12854,9 +12896,9 @@ function preprocess() {
12854
12896
  break;
12855
12897
  }
12856
12898
  case 9: {
12857
- const next2 = Math.ceil(column / 4) * 4;
12899
+ const next3 = Math.ceil(column / 4) * 4;
12858
12900
  chunks.push(-2);
12859
- while (column++ < next2) {
12901
+ while (column++ < next3) {
12860
12902
  chunks.push(-1);
12861
12903
  }
12862
12904
  break;
@@ -13730,8 +13772,8 @@ function trough() {
13730
13772
  if (typeof callback !== "function") {
13731
13773
  throw new TypeError("Expected function as last argument, not " + callback);
13732
13774
  }
13733
- next2(null, ...values);
13734
- function next2(error, ...output) {
13775
+ next3(null, ...values);
13776
+ function next3(error, ...output) {
13735
13777
  const fn = fns[++middlewareIndex];
13736
13778
  let index2 = -1;
13737
13779
  if (error) {
@@ -13745,7 +13787,7 @@ function trough() {
13745
13787
  }
13746
13788
  values = output;
13747
13789
  if (fn) {
13748
- wrap(fn, next2)(...output);
13790
+ wrap(fn, next3)(...output);
13749
13791
  } else {
13750
13792
  callback(null, ...output);
13751
13793
  }
@@ -15399,6 +15441,11 @@ function fit(s, n) {
15399
15441
  return cps.length > n ? `${cps.slice(0, Math.max(0, n - 1)).join("")}\u2026` : s + " ".repeat(n - cps.length);
15400
15442
  }
15401
15443
 
15444
+ // src/tui/model/rigSeat.ts
15445
+ var rigTag = (s) => s.rig ? `[${s.rig.name}] ` : "";
15446
+ var rigRole = (s) => s.rig ? s.rig.role ?? s.rig.seat : "";
15447
+ var seatSecond = (s, rest) => s.rig ? [rigRole(s), rest].filter(Boolean).join(" \xB7 ") : rest;
15448
+
15402
15449
  // src/tui/ui/now.ts
15403
15450
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
15404
15451
  var listeners = /* @__PURE__ */ new Set();
@@ -15532,10 +15579,10 @@ function fieldLines(text5, cursor, width, o) {
15532
15579
  const at = cursor !== null && cursor >= r.a && cursor < r.b ? cursor : -1;
15533
15580
  if (at < 0) line.push({ text: text5.slice(r.a, r.b), color: o.color });
15534
15581
  else {
15535
- const next2 = after({ text: text5, cursor: at });
15582
+ const next3 = after({ text: text5, cursor: at });
15536
15583
  if (at > r.a) line.push({ text: text5.slice(r.a, at), color: o.color });
15537
- line.push(inv(text5.slice(at, Math.min(next2, r.b))));
15538
- if (next2 < r.b) line.push({ text: text5.slice(next2, r.b), color: o.color });
15584
+ line.push(inv(text5.slice(at, Math.min(next3, r.b))));
15585
+ if (next3 < r.b) line.push({ text: text5.slice(next3, r.b), color: o.color });
15539
15586
  }
15540
15587
  if (cursor === r.b && r.last) line.push(inv(" "));
15541
15588
  return line.filter((s) => s.text !== "" || s === line[0]);
@@ -15653,11 +15700,11 @@ function Field({ value, onChange, onSubmit, onEscape, onBrowse, history = [], ac
15653
15700
  }
15654
15701
  };
15655
15702
  const show = (b2, i) => {
15656
- const next2 = b2 && i !== void 0 ? { ...b2, i } : b2;
15657
- const text5 = next2 ? next2.items[next2.i] : st.current.f.text;
15658
- st.current = { f: { text: text5, cursor: text5.length }, browse: next2, pasted: 0 };
15703
+ const next3 = b2 && i !== void 0 ? { ...b2, i } : b2;
15704
+ const text5 = next3 ? next3.items[next3.i] : st.current.f.text;
15705
+ st.current = { f: { text: text5, cursor: text5.length }, browse: next3, pasted: 0 };
15659
15706
  render2();
15660
- onBrowse?.(inHistory(next2));
15707
+ onBrowse?.(inHistory(next3));
15661
15708
  };
15662
15709
  useInput((input, key) => {
15663
15710
  const { f: f2, browse: browse2 } = st.current;
@@ -15875,6 +15922,10 @@ function SessionCard({ s, note }) {
15875
15922
  " ",
15876
15923
  t.state[state2]
15877
15924
  ] }),
15925
+ s.rig ? /* @__PURE__ */ jsxs5(Text5, { color: C.blue, children: [
15926
+ " ",
15927
+ t.overview.rigSeat(s.rig.name, s.rig.role ?? s.rig.seat)
15928
+ ] }) : null,
15878
15929
  /* @__PURE__ */ jsxs5(Text5, { color: C.dim, children: [
15879
15930
  " ",
15880
15931
  TOOL_NAME[s.tool],
@@ -15952,6 +16003,7 @@ function Tail({ lines, color: color2, empty: empty3, emptyColor }) {
15952
16003
  // src/tui/screens/Overview.tsx
15953
16004
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
15954
16005
  var WIDE = 100;
16006
+ var folderOf = (cwd) => (cwd ?? "").split("/").filter(Boolean).at(-1) ?? "";
15955
16007
  function windowed(rows, selectedKey, height) {
15956
16008
  if (rows.length <= height) return [...rows];
15957
16009
  const at = Math.max(0, rows.findIndex((r) => r.key === selectedKey));
@@ -15964,9 +16016,8 @@ function Overview() {
15964
16016
  const { columns, rows: height } = useScreenSize();
15965
16017
  const wide = columns >= WIDE;
15966
16018
  const left2 = wide ? Math.min(64, Math.max(44, Math.floor(columns * 0.42))) : columns;
15967
- const teams = useTeams(tui.client);
15968
16019
  const tasks = useTasks(tui.client);
15969
- const view = useMemo2(() => overview(d.sessions, projects, teams ?? [], tasks), [d.sessions, projects, teams, tasks]);
16020
+ const view = useMemo2(() => overview(d.sessions, projects), [d.sessions, projects]);
15970
16021
  const sel = selected?.id ?? null;
15971
16022
  const closed = closedLedgeTasks(tasks);
15972
16023
  const decisions = useMemo2(() => openDecisions(tasks), [tasks]);
@@ -15988,11 +16039,6 @@ function Overview() {
15988
16039
  };
15989
16040
  const threshold = useContextNear(tui.client);
15990
16041
  const near = useMemo2(() => nearContext(d.sessions, threshold), [d.sessions, threshold]);
15991
- const [tasksShown, setTasksShown] = useState3(true);
15992
- const [joining, setJoining] = useState3(null);
15993
- const node2 = selected ? view.tree.find((n) => n.branches.some((b2) => b2.sessions.includes(selected)) || n.teams.some((x) => x.sessions.includes(selected))) : void 0;
15994
- const seatTeam = selected?.team ? teams?.find((x) => x.id === selected.team && x.state !== "dissolved") : void 0;
15995
- const choices2 = node2?.teams.map((x) => x.team).filter((x) => x.state !== "dissolved" && x.id !== selected?.team) ?? [];
15996
16042
  useScreenKeys((input, key) => {
15997
16043
  if (deciding) {
15998
16044
  if (!deciding.confirm) {
@@ -16014,50 +16060,6 @@ function Overview() {
16014
16060
  if (decisions[0]) setDeciding({ id: decisions[0].id, draft: "", confirm: false });
16015
16061
  return true;
16016
16062
  }
16017
- if (joining) {
16018
- if (key.escape) {
16019
- setJoining(null);
16020
- return true;
16021
- }
16022
- if (input === "j" || key.downArrow) {
16023
- setJoining({ index: (joining.index + 1) % Math.max(1, choices2.length) });
16024
- return true;
16025
- }
16026
- if (input === "k" || key.upArrow) {
16027
- setJoining({ index: (joining.index + choices2.length - 1) % Math.max(1, choices2.length) });
16028
- return true;
16029
- }
16030
- if (key.return && selected && choices2[joining.index]) {
16031
- const team = choices2[joining.index];
16032
- const role = selected.role ?? team.seats.find((x) => x.sessionId === null)?.role;
16033
- setJoining(null);
16034
- if (!role) {
16035
- tui.toast({ text: t.overview.noTeamHere, tone: "info" });
16036
- return true;
16037
- }
16038
- tui.client.send({ type: "team.join", team: team.id, session: selected.id, role });
16039
- tui.toast({ text: t.toast.joined(selected.name, team.name), tone: "ok" });
16040
- return true;
16041
- }
16042
- return true;
16043
- }
16044
- if (input === "J" && selected) {
16045
- if (choices2.length) setJoining({ index: 0 });
16046
- else tui.toast({ text: t.overview.noTeamHere, tone: "info" });
16047
- return true;
16048
- }
16049
- if (input === "R") {
16050
- const resting = (seatTeam?.state === "resting" ? seatTeam : void 0) ?? node2?.teams.find((x) => x.team.state === "resting")?.team ?? view.tree.flatMap((n) => n.teams).find((x) => x.team.state === "resting")?.team;
16051
- if (resting) {
16052
- tui.client.send({ type: "team.resume", team: resting.id });
16053
- tui.toast({ text: t.toast.resumed(resting.name), tone: "ok" });
16054
- }
16055
- return true;
16056
- }
16057
- if (input === "T") {
16058
- setTasksShown((v) => !v);
16059
- return true;
16060
- }
16061
16063
  if (input === "H") {
16062
16064
  const s = (selected && selected.source === "launcher" && selected.state !== "exited" && selected.tool !== "omp" && (!selected.handover || selected.handover.state === "failed") ? selected : void 0) ?? near.find((x) => x.handover?.state !== "failed") ?? near[0];
16063
16065
  if (!s) return true;
@@ -16085,8 +16087,8 @@ function Overview() {
16085
16087
  return true;
16086
16088
  }
16087
16089
  if (key.tab) {
16088
- const next2 = nextWaiting(view.yours, sel);
16089
- if (next2) select(next2);
16090
+ const next3 = nextWaiting(view.yours, sel);
16091
+ if (next3) select(next3);
16090
16092
  return true;
16091
16093
  }
16092
16094
  if (key.return) {
@@ -16110,22 +16112,25 @@ function Overview() {
16110
16112
  }
16111
16113
  return false;
16112
16114
  });
16113
- const tree2 = view.tree.flatMap((n) => {
16114
- const teamed = n.teams.length > 0;
16115
- const common = n.branches.length > 0;
16115
+ const groupRows = (kind) => view.tree.filter((n) => n.kind === kind).flatMap((n) => [
16116
+ ...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) }] : [],
16117
+ ...n.branches.flatMap((b2) => b2.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: kind === "rig" ? 2 : 0, group: kind })))
16118
+ ]);
16119
+ const rigRows = groupRows("rig"), kernelRows = groupRows("kernel");
16120
+ const tree2 = view.tree.filter((n) => !n.kind).flatMap((n) => {
16116
16121
  return [
16117
16122
  { kind: "project", key: n.key, label: n.label, color: projectColor(n.color), count: n.count },
16118
- ...teamed && common ? [{ kind: "common", key: `${n.key}|common` }] : [],
16119
- ...n.branches.flatMap((b2) => [{ kind: "branch", key: `${n.key}|${b2.branch}`, branch: b2.branch, indent: teamed ? 4 : 2 }, ...b2.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: teamed ? 2 : 0 }))]),
16120
- ...n.teams.flatMap((x) => [{ kind: "team", key: x.key, team: x.team, done: x.done, given: x.given }, ...x.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: 2 }))])
16123
+ ...n.branches.flatMap((b2) => [{ kind: "branch", key: `${n.key}|${b2.branch}`, branch: b2.branch, indent: 2 }, ...b2.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: 0 }))])
16121
16124
  ];
16122
16125
  });
16123
16126
  const yoursCount = view.yours.length + decisions.length + closed.length + near.length;
16124
16127
  const yoursHeight = Math.max(1, Math.min(yoursCount, 6));
16125
- const joinHeight = joining && selected ? Math.max(1, choices2.length) + 2 : 0;
16126
16128
  const answerLines = deciding?.confirm ? wrap2(deciding.draft.trim(), Math.max(10, columns - 4)) : [];
16127
16129
  const decideHeight = !deciding ? 0 : deciding.confirm ? Math.min(answerLines.length, Math.max(1, height - 12)) + 2 : fieldRows(columns, height) + 2;
16128
- const treeHeight = Math.max(3, height - 3 - (yoursHeight + 2) - joinHeight - decideHeight - 3);
16130
+ const allHeight = Math.max(3, height - 3 - (yoursHeight + 2) - decideHeight - 3);
16131
+ const kernelHeight = kernelRows.length ? Math.min(kernelRows.length, 5) : 0;
16132
+ const rigsHeight = rigRows.length ? Math.min(rigRows.length, Math.max(3, Math.floor(allHeight / 3))) : 0;
16133
+ const treeHeight = Math.max(3, allHeight - (rigsHeight ? rigsHeight + 2 : 0) - (kernelHeight ? kernelHeight + 2 : 0));
16129
16134
  const inner = left2 - 4, nameW = 7;
16130
16135
  const stateW = Math.max(...Object.values(t.state).map((x) => x.length)) + 1;
16131
16136
  const topicW = Math.max(6, inner - 6 - nameW - stateW - 3);
@@ -16138,8 +16143,13 @@ function Overview() {
16138
16143
  " "
16139
16144
  ] }),
16140
16145
  /* @__PURE__ */ jsx10(Text7, { color: C.fg, bold: true, children: fit(s.name, nameW) }),
16141
- /* @__PURE__ */ jsx10(Text7, { color: C.dim, children: fit(s.repo ?? s.project, 9) }),
16142
- /* @__PURE__ */ jsx10(Text7, { color: STATE_COLOR[st], children: fit(oneLine(s.pending?.text ?? ""), askW) }),
16146
+ s.rig ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
16147
+ /* @__PURE__ */ jsx10(Text7, { children: " " }),
16148
+ /* @__PURE__ */ jsx10(Text7, { color: C.blue, children: rigTag(s) }),
16149
+ /* @__PURE__ */ jsx10(Text7, { color: C.dim, children: seatSecond(s, "") }),
16150
+ /* @__PURE__ */ jsx10(Text7, { children: " " })
16151
+ ] }) : /* @__PURE__ */ jsx10(Text7, { color: C.dim, children: fit(s.repo ?? s.project, 9) }),
16152
+ /* @__PURE__ */ jsx10(Text7, { color: STATE_COLOR[st], children: fit(oneLine(s.pending?.text ?? ""), s.rig ? Math.max(6, askW + 9 - rigTag(s).length - seatSecond(s, "").length - 4) : askW) }),
16143
16153
  /* @__PURE__ */ jsxs6(Text7, { color: C.faint, children: [
16144
16154
  " ",
16145
16155
  /* @__PURE__ */ jsx10(Ago, { iso: s.lastActivityAt })
@@ -16166,7 +16176,7 @@ function Overview() {
16166
16176
  s.handover?.state === "failed" ? g.fail : "\u25D4",
16167
16177
  " "
16168
16178
  ] }),
16169
- /* @__PURE__ */ jsx10(Text7, { color: C.fg, children: s.handover?.state === "failed" ? t.overview.reliefFailed(s.name, s.handover.reason ?? "?") : t.overview.near(`${s.name}${s.role ? ` @${s.role}` : ""}`, Math.round((s.context?.used ?? 0) / (s.context?.window ?? 1) * 100)) })
16179
+ /* @__PURE__ */ jsx10(Text7, { color: C.fg, children: s.handover?.state === "failed" ? t.overview.reliefFailed(s.name, s.handover.reason ?? "?") : t.overview.near(s.name, Math.round((s.context?.used ?? 0) / (s.context?.window ?? 1) * 100)) })
16170
16180
  ] }, `near:${s.id}`))] });
16171
16181
  const treeBox = /* @__PURE__ */ jsx10(TitledBox, { title: t.overview.projects, flexGrow: 1, children: tree2.length === 0 ? /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: t.overview.noSession }) : windowed(tree2, sel, treeHeight).map((r) => r.kind === "project" ? /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", children: [
16172
16182
  /* @__PURE__ */ jsx10(Text7, { color: r.color, children: g.project }),
@@ -16174,32 +16184,32 @@ function Overview() {
16174
16184
  /* @__PURE__ */ jsx10(Text7, { color: C.fg, bold: true, children: r.label }),
16175
16185
  " ",
16176
16186
  /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: t.overview.sessions(r.count) })
16177
- ] }, r.key) : r.kind === "common" ? /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", color: C.faint, children: [
16178
- " ",
16179
- g.common,
16180
- " ",
16181
- t.overview.common
16182
- ] }, r.key) : r.kind === "team" ? /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", children: [
16183
- /* @__PURE__ */ jsx10(Text7, { children: " " }),
16184
- /* @__PURE__ */ jsx10(Text7, { color: C.coral, children: g.team }),
16185
- " ",
16186
- /* @__PURE__ */ jsx10(Text7, { color: r.team.state === "resting" ? C.dim : C.fg, bold: true, children: r.team.name }),
16187
- " ",
16188
- /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: r.team.state === "resting" ? t.overview.resting : t.overview.teamLine(r.done, r.given) })
16189
16187
  ] }, r.key) : r.kind === "branch" ? /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", color: C.faint, children: [
16190
16188
  " ".repeat(r.indent),
16191
16189
  g.branch,
16192
16190
  " ",
16193
16191
  r.branch
16194
- ] }, r.key) : /* @__PURE__ */ jsx10(SessionRow, { s: r.s, on: r.s.id === sel, nameW, topicW: Math.max(6, topicW - r.indent - (r.s.role ? r.s.role.length + 2 : 0)), stateW, indent: r.indent }, r.key)) });
16192
+ ] }, r.key) : /* @__PURE__ */ jsx10(SessionRow, { s: r.s, on: r.s.id === sel, nameW, topicW: Math.max(6, topicW - r.indent), stateW, indent: r.indent }, r.key)) });
16193
+ const groupBox = (title2, rows, rowsHeight) => rows.length === 0 ? null : /* @__PURE__ */ jsx10(TitledBox, { title: title2, height: rowsHeight + 2, children: windowed(rows, sel, rowsHeight).map((r) => r.kind === "project" ? /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", children: [
16194
+ /* @__PURE__ */ jsx10(Text7, { color: r.color, children: "\u2B21" }),
16195
+ " ",
16196
+ /* @__PURE__ */ jsx10(Text7, { color: C.fg, bold: true, children: r.label }),
16197
+ r.folder ? /* @__PURE__ */ jsxs6(Text7, { color: C.faint, children: [
16198
+ " ",
16199
+ r.folder
16200
+ ] }) : null,
16201
+ /* @__PURE__ */ jsxs6(Text7, { color: C.faint, children: [
16202
+ " ",
16203
+ t.overview.sessions(r.count)
16204
+ ] })
16205
+ ] }, 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) });
16206
+ const rigsBox = groupBox(t.overview.rigsGroup, rigRows, rigsHeight);
16207
+ const kernelBox = groupBox(t.overview.kernelGroup, kernelRows, kernelHeight);
16195
16208
  const keys = 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]] : [
16196
16209
  ["j k", t.keys.navigate],
16197
16210
  ["\u21B5", t.keys.open],
16198
16211
  ...view.yours.length ? [["tab", t.keys.next], ["y n", t.keys.answer]] : [],
16199
16212
  ["a", t.keys.attach],
16200
- ...seatTeam ? [["T", t.keys.tasks]] : [],
16201
- ...choices2.length ? [["J", t.keys.join]] : [],
16202
- ...view.tree.some((n) => n.teams.some((x) => x.team.state === "resting")) ? [["R", t.keys.resume]] : [],
16203
16213
  ...decisions.length ? [["D", t.keys.decide]] : [],
16204
16214
  ...near.length ? [["H", t.keys.handover]] : [],
16205
16215
  ["/", t.keys.ask],
@@ -16208,36 +16218,17 @@ function Overview() {
16208
16218
  ["?", t.keys.help],
16209
16219
  ["q", t.keys.quit]
16210
16220
  ];
16211
- const teamTasks = seatTeam ? tasks.filter((x) => x.team === seatTeam.id && (!taskClosed(x) || x.updatedAt.slice(0, 10) === (/* @__PURE__ */ new Date()).toISOString().slice(0, 10))).slice(-4) : [];
16212
16221
  return /* @__PURE__ */ jsxs6(Frame, { crumb: tui.machines.map((m) => `${m.label} ${m.ready ? g.machine : "\u25CB"}`).join(" "), keys, children: [
16213
16222
  /* @__PURE__ */ jsxs6(Box8, { flexGrow: 1, children: [
16214
16223
  /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", width: left2, flexShrink: 0, children: [
16215
16224
  yoursBox,
16216
- joining && selected ? /* @__PURE__ */ jsx10(TitledBox, { title: t.overview.joinWhich(selected.name), color: C.coral, titleColor: C.coral, height: joinHeight, children: choices2.map((x, i) => /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", backgroundColor: i === joining.index ? C.sel : void 0, children: [
16217
- /* @__PURE__ */ jsx10(Text7, { color: C.coral, children: g.team }),
16218
- " ",
16219
- x.name
16220
- ] }, x.id)) }) : null,
16221
- treeBox
16225
+ treeBox,
16226
+ rigsBox,
16227
+ kernelBox
16222
16228
  ] }),
16223
16229
  wide ? /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", flexGrow: 1, children: [
16224
16230
  selected ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
16225
- /* @__PURE__ */ jsx10(TitledBox, { title: `${selected.name} \xB7 ${oneLine(selected.topic ?? selected.title)}`, color: isPending(selected) ? C.red : C.line, titleColor: isPending(selected) ? C.red : C.dim, children: /* @__PURE__ */ jsx10(SessionCard, { s: selected, note: true }) }),
16226
- seatTeam && tasksShown ? /* @__PURE__ */ jsx10(TitledBox, { title: t.overview.tasks(`${g.team} ${seatTeam.name}`), height: Math.max(3, teamTasks.length + 2), children: teamTasks.length === 0 ? /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: t.overview.noTask }) : teamTasks.map((x) => /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate", children: [
16227
- /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: x.id }),
16228
- " ",
16229
- /* @__PURE__ */ jsx10(Text7, { color: C.fg, children: x.title }),
16230
- " ",
16231
- /* @__PURE__ */ jsxs6(Text7, { color: C.dim, children: [
16232
- x.giverName,
16233
- " ",
16234
- g.arrow,
16235
- " ",
16236
- x.ownerName
16237
- ] }),
16238
- " ",
16239
- /* @__PURE__ */ jsx10(Text7, { color: x.state === "done" ? C.green : x.state === "taken" ? C.blue : C.faint, children: t.ask.taskState[x.state] })
16240
- ] }, x.id)) }) : null,
16231
+ /* @__PURE__ */ jsx10(TitledBox, { title: `${selected.name}${selected.rig ? ` \xB7 ${t.overview.rigSeat(selected.rig.name, selected.rig.role ?? selected.rig.seat)}` : ""} \xB7 ${oneLine(selected.topic ?? selected.title)}`, color: isPending(selected) ? C.red : C.line, titleColor: isPending(selected) ? C.red : C.dim, children: /* @__PURE__ */ jsx10(SessionCard, { s: selected, note: true }) }),
16241
16232
  /* @__PURE__ */ jsx10(TitledBox, { title: t.overview.terminal(paneName(selected)), flexGrow: 1, children: /* @__PURE__ */ jsx10(Tail, { lines: d.previews.get(selected.id) ?? [], color: C.dim, empty: t.overview.noPreview, emptyColor: C.faint }) })
16242
16233
  ] }) : /* @__PURE__ */ jsx10(TitledBox, { title: t.tabs.session, flexGrow: 1, children: /* @__PURE__ */ jsx10(Text7, { color: C.faint, children: t.session.noSession }) }),
16243
16234
  /* @__PURE__ */ jsx10(TitledBox, { title: t.overview.exchanges, height: 5, children: (() => {
@@ -16277,7 +16268,7 @@ function Overview() {
16277
16268
  ) : null
16278
16269
  ] });
16279
16270
  }
16280
- function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0 }) {
16271
+ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0, group }) {
16281
16272
  const { t, g } = useTui();
16282
16273
  const st = botState(s);
16283
16274
  const n = helperCount(s);
@@ -16289,14 +16280,19 @@ function SessionRow({ s, on, nameW, topicW, stateW, indent: indent2 = 0 }) {
16289
16280
  " "
16290
16281
  ] }),
16291
16282
  /* @__PURE__ */ jsx10(Text7, { color: s.offline ? C.dim : C.fg, bold: true, children: fit(s.name, nameW) }),
16292
- s.role ? /* @__PURE__ */ jsxs6(Text7, { color: C.violet, children: [
16293
- "@",
16294
- s.role,
16295
- " "
16296
- ] }) : null,
16297
- /* @__PURE__ */ jsxs6(Text7, { color: C.dim, children: [
16298
- fit(oneLine(s.topic ?? s.title), topicW),
16283
+ group ? /* @__PURE__ */ jsxs6(Text7, { color: C.dim, children: [
16284
+ " ",
16285
+ fit(group === "kernel" ? `${(s.rig?.role ?? "").replace(/\.(agent|lead|worker)$/, "").padEnd(11)}${s.tool}` : s.rig?.role ?? "", topicW - 1),
16299
16286
  " "
16287
+ ] }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
16288
+ s.rig ? /* @__PURE__ */ jsxs6(Text7, { color: C.blue, children: [
16289
+ " ",
16290
+ rigTag(s)
16291
+ ] }) : null,
16292
+ /* @__PURE__ */ jsxs6(Text7, { color: C.dim, children: [
16293
+ fit(seatSecond(s, oneLine(s.topic ?? s.title)), s.rig ? Math.max(0, topicW - rigTag(s).length - 1) : topicW),
16294
+ " "
16295
+ ] })
16300
16296
  ] }),
16301
16297
  /* @__PURE__ */ jsx10(Text7, { color: s.offline ? C.faint : STATE_COLOR[st], children: fit(s.offline ? t.overview.offline : t.state[st], stateW).trimEnd() }),
16302
16298
  n > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: C.violet, children: [
@@ -16327,9 +16323,9 @@ function useChips(put) {
16327
16323
  const { t, toast } = useTui();
16328
16324
  const [items, setItems] = useState4([]);
16329
16325
  const held = useRef8([]);
16330
- const commit = (next2) => {
16331
- held.current = next2;
16332
- setItems(next2);
16326
+ const commit = (next3) => {
16327
+ held.current = next3;
16328
+ setItems(next3);
16333
16329
  };
16334
16330
  const patch = (key, over) => commit(held.current.map((c) => c.key === key ? { ...c, ...over } : c));
16335
16331
  const reasonOf = (e) => {
@@ -16372,10 +16368,10 @@ function useChips(put) {
16372
16368
  }
16373
16369
  };
16374
16370
  }
16375
- function ChipsLine({ chips }) {
16371
+ function ChipsLine({ chips: chips2 }) {
16376
16372
  const { t } = useTui();
16377
- if (chips.length === 0) return null;
16378
- return /* @__PURE__ */ jsx11(Text8, { wrap: "truncate", children: chips.map((c, i) => /* @__PURE__ */ jsxs7(Text8, { children: [
16373
+ if (chips2.length === 0) return null;
16374
+ return /* @__PURE__ */ jsx11(Text8, { wrap: "truncate", children: chips2.map((c, i) => /* @__PURE__ */ jsxs7(Text8, { children: [
16379
16375
  i ? " " : "",
16380
16376
  /* @__PURE__ */ jsxs7(Text8, { color: c.state === "refused" ? C.red : C.dim, children: [
16381
16377
  "\u25A3 ",
@@ -16394,14 +16390,14 @@ function ChipsLine({ chips }) {
16394
16390
  ] }, c.key)) });
16395
16391
  }
16396
16392
  var EXT = { "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp" };
16397
- function usePaste2(chips) {
16393
+ function usePaste2(chips2) {
16398
16394
  const { readImage, toast, t } = useTui();
16399
16395
  const reading = useRef8(false);
16400
16396
  return () => {
16401
16397
  if (reading.current) return;
16402
16398
  reading.current = true;
16403
16399
  void readImage().then((got) => {
16404
- if (got) chips.add(got);
16400
+ if (got) chips2.add(got);
16405
16401
  else toast({ text: t.images.none, tone: "info" });
16406
16402
  }, () => toast({ text: t.images.none, tone: "info" })).finally(() => {
16407
16403
  reading.current = false;
@@ -16556,30 +16552,30 @@ function SessionView({ s }) {
16556
16552
  setAnchor({ key, offset: top - lineKeys.indexOf(key) });
16557
16553
  };
16558
16554
  const images = useMemo3(() => (items ? threadImages(items, unfolded) : []).map((ref) => ({ ref, route: { id: s.id } })), [items, unfolded, s.id]);
16559
- const chips = useChips((image2) => putImage(client, image2, { id: s.id }));
16560
- const paste = usePaste2(chips);
16555
+ const chips2 = useChips((image2) => putImage(client, image2, { id: s.id }));
16556
+ const paste = usePaste2(chips2);
16561
16557
  const openImage2 = useOpenImage();
16562
16558
  const machine = splitId(s.id)?.machine ?? "local";
16563
16559
  const canSwitch = machines.find((m) => m.id === machine)?.daemon?.modelSwitch === true;
16564
16560
  const send = (text5, { fromHistory }) => {
16565
- const withImages = mode === "reply" && !fromHistory && chips.items.length > 0;
16561
+ const withImages = mode === "reply" && !fromHistory && chips2.items.length > 0;
16566
16562
  if (!text5.trim() && !withImages) {
16567
16563
  setMode("read");
16568
16564
  return true;
16569
16565
  }
16570
- if (withImages && !chips.ready) return false;
16566
+ if (withImages && !chips2.ready) return false;
16571
16567
  if (withImages && p?.kind === "question") {
16572
16568
  toast({ text: t.images.dialog, tone: "info" });
16573
16569
  return false;
16574
16570
  }
16575
- const taken = withImages ? chips.clear() : [];
16571
+ const taken = withImages ? chips2.clear() : [];
16576
16572
  const back2 = (x) => {
16577
16573
  if (!kept.drafts.get(s.id)) setDraft(x);
16578
- if (taken.length) chips.putBack(taken);
16574
+ if (taken.length) chips2.putBack(taken);
16579
16575
  };
16580
16576
  const how = taken.length ? { attachments: taken.flatMap((c) => c.id ? [c.id] : []) } : void 0;
16581
16577
  if (!(mode === "note" ? actions.denyWithNote(s, text5.trim(), back2) : actions.reply(s, text5, back2, how))) {
16582
- if (taken.length) chips.putBack(taken);
16578
+ if (taken.length) chips2.putBack(taken);
16583
16579
  return false;
16584
16580
  }
16585
16581
  if (text5.trim()) kept.sent.set(s.id, [...kept.sent.get(s.id) ?? [], text5]);
@@ -16594,7 +16590,7 @@ function SessionView({ s }) {
16594
16590
  };
16595
16591
  useInput3((input, key) => {
16596
16592
  if (key.meta && input === "v") paste();
16597
- else if ((key.backspace || key.delete) && draft === "") chips.removeLast();
16593
+ else if ((key.backspace || key.delete) && draft === "") chips2.removeLast();
16598
16594
  }, { isActive: mode === "reply" });
16599
16595
  useScreenKeys((input, key) => {
16600
16596
  if (mode === "model") return true;
@@ -16680,7 +16676,7 @@ function SessionView({ s }) {
16680
16676
  if (input === "y" || input === "n" || input === "A" || /^[1-9]$/.test(input)) return actions.key(s, input);
16681
16677
  return false;
16682
16678
  });
16683
- const title2 = `${s.name} \xB7 ${oneLine(s.topic ?? s.title)} \xB7 ${TOOL_NAME[s.tool]} \xB7 ${modelLabel(s, t.session.defaultModel)}`;
16679
+ const title2 = `${s.name}${s.rig ? ` \xB7 ${t.overview.rigSeat(s.rig.name, s.rig.role ?? s.rig.seat)}` : ""} \xB7 ${oneLine(s.topic ?? s.title)} \xB7 ${TOOL_NAME[s.tool]} \xB7 ${modelLabel(s, t.session.defaultModel)}`;
16684
16680
  const showsThread = mode !== "model" && !terminal && !!items && lines.length > 0;
16685
16681
  const above = lines.length - view - up;
16686
16682
  const foot = up > 0 ? t.session.scrolled(above, up) : lines.length > view ? t.session.above(lines.length - view) : void 0;
@@ -16688,7 +16684,7 @@ function SessionView({ s }) {
16688
16684
  const editing = mode === "reply" || mode === "note";
16689
16685
  const body = rows - 2 - (lastToast ? 1 : 0);
16690
16686
  const inner = Math.max(20, columns - 4);
16691
- const below = (editing ? Math.min(fieldRows(columns, rows), layout(draft, Math.max(10, columns - 4) - 2).length) + 2 : 3) + (chips.items.length ? 1 : 0);
16687
+ const below = (editing ? Math.min(fieldRows(columns, rows), layout(draft, Math.max(10, columns - 4) - 2).length) + 2 : 3) + (chips2.items.length ? 1 : 0);
16692
16688
  let request2 = p ? ask(s, p, t, inner, 4, !editing) : null;
16693
16689
  const over = request2 ? askRows(request2, inner) + below - body : 0;
16694
16690
  if (p && request2 && over > 0) request2 = ask(s, p, t, inner, Math.max(1, 4 - over), !editing);
@@ -16710,7 +16706,7 @@ function SessionView({ s }) {
16710
16706
  }
16711
16707
  ) : terminal ? /* @__PURE__ */ jsx13(Tail, { lines: d.previews.get(s.id) ?? [], color: C.dim, empty: t.overview.noPreview, emptyColor: C.faint }) : !items ? /* @__PURE__ */ jsx13(Text10, { color: C.faint, children: t.session.loading }) : lines.length === 0 ? /* @__PURE__ */ jsx13(Text10, { color: C.faint, children: t.session.empty }) : /* @__PURE__ */ jsx13(Lines, { lines, up, onHeight: setView }) }) : null,
16712
16708
  p && request2 ? /* @__PURE__ */ jsx13(PendingBox, { s, a: request2 }) : null,
16713
- /* @__PURE__ */ jsx13(ChipsLine, { chips: chips.items }),
16709
+ /* @__PURE__ */ jsx13(ChipsLine, { chips: chips2.items }),
16714
16710
  mode === "reply" || mode === "note" ? /* @__PURE__ */ jsx13(
16715
16711
  Field,
16716
16712
  {
@@ -16780,7 +16776,7 @@ function actionRowOf(a) {
16780
16776
  if (a.vars !== void 0) row2.vars = a.vars;
16781
16777
  if (a.detail !== void 0 && a.action === "session.decide" && a.vars?.kind) row2.request = { kind: a.vars.kind, text: a.detail };
16782
16778
  else if (a.detail !== void 0) row2.detail = a.detail;
16783
- const target = a.vars?.session ?? a.vars?.owner ?? a.vars?.team;
16779
+ const target = a.vars?.session ?? a.vars?.owner;
16784
16780
  if (target !== void 0) row2.target = target;
16785
16781
  if (a.vars?.decision === "accept" || a.vars?.decision === "deny") row2.decision = a.vars.decision;
16786
16782
  if (a.action === "session.decide" && row2.decision !== "deny") row2.deny = { summary: a.summary };
@@ -16873,7 +16869,6 @@ function chatView(turns2, live, o) {
16873
16869
  out.push([{ text: "" }], ...markdownLines(text5, { width: width - 2, g, color: C.fg }).map((l) => [pad(2), ...l]));
16874
16870
  if (streaming !== void 0) out.push([pad(2), { text: g.caret, color: C.coral }]);
16875
16871
  } else out.push([{ text: ` ${t.ask.reading}`, color: C.faint }]);
16876
- if (turn.team?.teamId && turn.team.launchedAt) out.push([{ text: ` ${g.team} ${turn.team.name} \xB7 ${t.ask.teamLaunched(clock(turn.team.launchedAt))}`, color: C.green }]);
16877
16872
  for (const c of turn.tasks ?? []) if (c.taskId) out.push([{ text: ` ${c.title} \xB7 ${t.ask.given(c.ownerName ?? c.toName ?? c.to, c.taskId, t.ask.taskState[c.state ?? "open"])}`, color: C.green }]);
16878
16873
  out.push(...actionTrace(turn, t, g, width));
16879
16874
  for (const l of out) {
@@ -16885,7 +16880,7 @@ function chatView(turns2, live, o) {
16885
16880
  }
16886
16881
  function actionTrace(turn, t, g, width) {
16887
16882
  const a = t.ask.action, c = a.copy;
16888
- const cards = !!(turn.actions || turn.proposal || turn.relay || turn.closes || turn.team || turn.tasks || turn.projectProposal);
16883
+ const cards = !!(turn.actions || turn.proposal || turn.relay || turn.closes || turn.tasks || turn.projectProposal);
16889
16884
  if (turn.ignoredAt) {
16890
16885
  const shown = turn.actions ? cardPlan(turn.actions).shown : [];
16891
16886
  const sent = { done: shown.filter((s) => s === "done").length, denied: shown.filter((s) => s === "denied").length, failed: shown.filter((s) => s === "failed").length };
@@ -16914,7 +16909,6 @@ function openCards(turns2) {
16914
16909
  for (const turn of [...turns2].reverse()) {
16915
16910
  if (!turn.answeredAt || turn.ignoredAt) continue;
16916
16911
  if (turn.actions && !cardClosed(turn.actions)) out.push({ kind: "action", qid: turn.qid });
16917
- if (turn.team && !turn.team.teamId) out.push({ kind: "team", qid: turn.qid });
16918
16912
  const tasks = (turn.tasks ?? []).map((c, i) => c.taskId ? -1 : i).filter((i) => i >= 0);
16919
16913
  if (tasks.length) out.push({ kind: "task", qid: turn.qid, rows: tasks });
16920
16914
  const launch2 = proposalRows(turn).map((p, i) => p.launchedId ? -1 : i).filter((i) => i >= 0);
@@ -17109,8 +17103,8 @@ function Ask() {
17109
17103
  const [focus, setFocus] = useState9(-1);
17110
17104
  const [anchor, setAnchor] = useState9(null);
17111
17105
  const [view, setView] = useState9(10);
17112
- const chips = useChips(null);
17113
- const paste = usePaste2(chips);
17106
+ const chips2 = useChips(null);
17107
+ const paste = usePaste2(chips2);
17114
17108
  const openImage2 = useOpenImage();
17115
17109
  const [busy, setBusy] = useState9(null);
17116
17110
  const [rowOf2, setRowOf] = useState9({});
@@ -17124,7 +17118,7 @@ function Ask() {
17124
17118
  if (!s || !turn) return;
17125
17119
  if (turn.error) {
17126
17120
  sentAsk.current = null;
17127
- chips.putBack(s.taken);
17121
+ chips2.putBack(s.taken);
17128
17122
  setDraft(kept.drafts.get("ask") || s.text);
17129
17123
  } else if (turn.answeredAt) sentAsk.current = null;
17130
17124
  }, [d.chat]);
@@ -17133,7 +17127,7 @@ function Ask() {
17133
17127
  lines: Math.floor((height - 6) / Math.max(1, cards.length)),
17134
17128
  height: (text5, indent2) => text5.split("\n").reduce((n, l) => n + Math.max(1, wrapSegs([{ text: l, color: C.fg }], Math.max(10, columns - 8 - indent2)).length), 0)
17135
17129
  };
17136
- const planOf = (turn) => cardPlan(turn.actions ?? [], room);
17130
+ const planOf2 = (turn) => cardPlan(turn.actions ?? [], room);
17137
17131
  useEffect10(() => {
17138
17132
  if (focus >= cards.length) setFocus(cards.length ? cards.length - 1 : -1);
17139
17133
  }, [cards.length, focus]);
@@ -17150,7 +17144,7 @@ function Ask() {
17150
17144
  }, [confirming, cards]);
17151
17145
  useEffect10(() => {
17152
17146
  const turn = confirming && d.chat.find((x) => x.qid === confirming.qid);
17153
- if (turn && !confirming.rows.every((i) => appliable(planOf(turn).shown[i]))) setConfirming(null);
17147
+ if (turn && !confirming.rows.every((i) => appliable(planOf2(turn).shown[i]))) setConfirming(null);
17154
17148
  });
17155
17149
  const ignored = [...d.chat].filter((x) => x.ignoredAt).sort((a, b2) => b2.ignoredAt.localeCompare(a.ignoredAt))[0];
17156
17150
  useEffect10(() => {
@@ -17172,7 +17166,7 @@ function Ask() {
17172
17166
  const local = machines.find((m) => m.id === "local");
17173
17167
  const asking = d.chat.find((x) => !x.answeredAt && !x.error);
17174
17168
  const ask2 = (text5, { fromHistory }) => {
17175
- const images = fromHistory ? [] : chips.uploads();
17169
+ const images = fromHistory ? [] : chips2.uploads();
17176
17170
  if (!text5.trim() && images.length === 0) return true;
17177
17171
  if (!local?.ready) {
17178
17172
  toast({ text: t.ask.offline, tone: "info" });
@@ -17183,7 +17177,7 @@ function Ask() {
17183
17177
  daemonStore.beginTurn({ qid, ...asked2, steps: [] });
17184
17178
  client.send({ type: "chat.ask", qid, text: asked2.question, ...images.length ? { images } : {} });
17185
17179
  kept.asked.push(asked2);
17186
- if (images.length) sentAsk.current = { qid, taken: chips.clear(), text: asked2.question };
17180
+ if (images.length) sentAsk.current = { qid, taken: chips2.clear(), text: asked2.question };
17187
17181
  if (!fromHistory) setDraft("");
17188
17182
  return true;
17189
17183
  };
@@ -17192,21 +17186,13 @@ function Ask() {
17192
17186
  const turn = d.chat.find((x) => x.qid === card2.qid);
17193
17187
  if (!turn) return;
17194
17188
  if (card2.kind === "task") {
17195
- const row2 = card2.rows.find((r) => {
17196
- const c = turn.tasks[r];
17197
- return !c.to.startsWith("@") || c.team || turn.team?.teamId;
17198
- });
17199
- if (row2 === void 0) {
17200
- toast({ text: t.ask.waitTeam, tone: "info" });
17201
- return;
17202
- }
17189
+ const row2 = card2.rows[0];
17203
17190
  setBusy({ qid: card2.qid, turn, errorSeq: d.errors.at(-1)?.seq ?? 0, at: Date.now() });
17204
17191
  client.send({ type: "task.cmd", rid: randomUUID2(), by: LEDGE_GIVER, op: "give", qid: card2.qid, row: row2 });
17205
17192
  return;
17206
17193
  }
17207
17194
  setBusy({ qid: card2.qid, turn, errorSeq: d.errors.at(-1)?.seq ?? 0, at: Date.now() });
17208
- if (card2.kind === "team") client.send({ type: "team.launch", rid: randomUUID2(), qid: card2.qid });
17209
- else if (card2.kind === "launch") {
17195
+ if (card2.kind === "launch") {
17210
17196
  const first = proposalRows(turn)[card2.rows[0]];
17211
17197
  client.send({ type: "session.launch", tool: first.tool, cwd: first.cwd, ...first.prompt ? { prompt: first.prompt } : {}, qid: card2.qid, rows: card2.rows });
17212
17198
  } else if (card2.kind === "relay") client.send({ type: turn.relay?.status === "queued" ? "session.relay.cancel" : "session.relay", qid: card2.qid });
@@ -17221,7 +17207,7 @@ function Ask() {
17221
17207
  const actionKey = (qid, input, key) => {
17222
17208
  const turn = d.chat.find((x) => x.qid === qid);
17223
17209
  if (!turn?.actions) return false;
17224
- const p = planOf(turn);
17210
+ const p = planOf2(turn);
17225
17211
  const sel = selectedRow(p, rowOf2[qid]);
17226
17212
  const ask3 = (rows) => {
17227
17213
  if (rows.some((i) => p.rows[i].risk === "destructive")) setConfirming({ qid, rows });
@@ -17313,7 +17299,7 @@ function Ask() {
17313
17299
  return;
17314
17300
  }
17315
17301
  if ((key.backspace || key.delete) && l.focus === -1 && l.draft === "") {
17316
- chips.removeLast();
17302
+ chips2.removeLast();
17317
17303
  return;
17318
17304
  }
17319
17305
  if (key.meta && input === "o" && l.turnImages.length) {
@@ -17373,11 +17359,6 @@ function Ask() {
17373
17359
  act(cards[focus]);
17374
17360
  return true;
17375
17361
  }
17376
- if (input === "s" && cards[focus].kind === "team") {
17377
- const team = d.chat.find((x) => x.qid === cards[focus].qid)?.team;
17378
- if (team && !team.template && !team.savedPath) client.send({ type: "team.save", rid: randomUUID2(), qid: cards[focus].qid, name: team.name, seats: team.seats.map((x) => ({ role: x.role, tool: x.tool, ...x.model ? { model: x.model } : {}, ...x.effort ? { effort: x.effort } : {}, ...x.name ? { name: x.name } : {} })) });
17379
- return true;
17380
- }
17381
17362
  if (key.escape) {
17382
17363
  setFocus(-1);
17383
17364
  return true;
@@ -17391,23 +17372,23 @@ function Ask() {
17391
17372
  const turn = d.chat.find((x) => x.qid === qid);
17392
17373
  if (!turn?.actions) return [];
17393
17374
  if (confirming?.qid === qid) return [["y", t.keys.apply], ["esc", t.keys.cancel]];
17394
- const p = planOf(turn);
17375
+ const p = planOf2(turn);
17395
17376
  const sel = selectedRow(p, rowOf2[qid]);
17396
17377
  const r = p.rows[sel], s = p.shown[sel], many = p.rows.length > 1;
17397
- const next2 = ["tab", focus + 1 < cards.length ? t.keys.nextCard : t.keys.conversation];
17398
- if (s === "cut") return [["n", t.keys.deny], ["o", t.keys.openSession(r.target ?? "")], ["esc", t.keys.ignore], next2, ["ctrl+n", t.keys.newChat]];
17399
- if (!p.open.length) return [next2, ["ctrl+n", t.keys.newChat], ["esc", t.keys.back]];
17378
+ const next3 = ["tab", focus + 1 < cards.length ? t.keys.nextCard : t.keys.conversation];
17379
+ if (s === "cut") return [["n", t.keys.deny], ["o", t.keys.openSession(r.target ?? "")], ["esc", t.keys.ignore], next3, ["ctrl+n", t.keys.newChat]];
17380
+ if (!p.open.length) return [next3, ["ctrl+n", t.keys.newChat], ["esc", t.keys.back]];
17400
17381
  return [
17401
17382
  ["\u21B5", many ? t.keys.applyRow : t.keys.apply],
17402
17383
  ...many ? [["a", t.keys.applyAll], ["\u2191 \u2193", t.keys.line]] : [],
17403
17384
  ...deniable(r, s) ? [["n", t.keys.deny]] : [],
17404
17385
  ["esc", t.keys.ignore],
17405
- next2,
17386
+ next3,
17406
17387
  ["ctrl+n", t.keys.newChat]
17407
17388
  ];
17408
17389
  };
17409
17390
  const history = askHistory(d.chat, kept.asked);
17410
- 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], ...card?.kind === "team" ? [["s", t.keys.save]] : [], ["ctrl+n", t.keys.newChat], ...stop, ["esc", t.keys.back]];
17391
+ 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]];
17411
17392
  const foot = up > 0 ? asking && live?.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;
17412
17393
  const reading = focus === CONVO;
17413
17394
  return /* @__PURE__ */ jsxs11(Frame, { crumb: `${t.tabs.ask}`, keys, kept: draft !== "", children: [
@@ -17426,7 +17407,7 @@ function Ask() {
17426
17407
  cards.map((c, i) => {
17427
17408
  const turn = d.chat.find((x) => x.qid === c.qid);
17428
17409
  if (c.kind !== "action") return /* @__PURE__ */ jsx15(CardBox, { card: c, turn, focused: i === focus, busy: busy?.qid === c.qid }, `${c.kind}:${c.qid}`);
17429
- const p = planOf(turn);
17410
+ const p = planOf2(turn);
17430
17411
  const sel = selectedRow(p, rowOf2[c.qid]);
17431
17412
  return /* @__PURE__ */ jsx15(ActionBox, { turn, plan: p, focused: i === focus, busy: busy?.qid === c.qid, sel, confirming: confirming?.qid === c.qid ? confirming.rows : null }, `action:${c.qid}`);
17432
17413
  }),
@@ -17440,7 +17421,7 @@ function Ask() {
17440
17421
  /* @__PURE__ */ jsx15(Button, { label: t.ask.wipe, k: "y" })
17441
17422
  ] })
17442
17423
  ] }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
17443
- /* @__PURE__ */ jsx15(ChipsLine, { chips: chips.items }),
17424
+ /* @__PURE__ */ jsx15(ChipsLine, { chips: chips2.items }),
17444
17425
  /* @__PURE__ */ jsx15(
17445
17426
  Field,
17446
17427
  {
@@ -17463,46 +17444,8 @@ function Ask() {
17463
17444
  function CardBox({ card, turn, focused, busy }) {
17464
17445
  const { t, g } = useTui();
17465
17446
  const color2 = focused ? C.coral : C.line;
17466
- if (card.kind === "team") {
17467
- const team = turn.team;
17468
- const auto = team.seats.filter((x) => x.autonomy === "auto").map((x) => `@${x.role}`);
17469
- return /* @__PURE__ */ jsxs11(TitledBox, { title: t.ask.teamCard(team.name), flexShrink: 0, color: color2, titleColor: C.coral, children: [
17470
- /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate", children: [
17471
- /* @__PURE__ */ jsx15(Text12, { color: C.dim, children: t.ask.folder.padEnd(13) }),
17472
- /* @__PURE__ */ jsx15(Text12, { color: C.faint, children: team.cwd })
17473
- ] }),
17474
- /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate", children: [
17475
- /* @__PURE__ */ jsx15(Text12, { color: C.dim, children: t.ask.table.padEnd(13) }),
17476
- g.team,
17477
- " ",
17478
- t.ask.newTable(team.projectName ?? team.project)
17479
- ] }),
17480
- team.seats.map((x) => /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate", children: [
17481
- /* @__PURE__ */ jsx15(Text12, { color: C.dim, children: "".padEnd(13) }),
17482
- /* @__PURE__ */ jsxs11(Text12, { color: C.violet, children: [
17483
- "@",
17484
- x.role
17485
- ] }),
17486
- " ",
17487
- x.label,
17488
- " ",
17489
- /* @__PURE__ */ jsx15(Text12, { color: C.faint, children: [TOOL_NAME[x.tool], x.model, x.effort].filter(Boolean).join(" \xB7 ") }),
17490
- " ",
17491
- /* @__PURE__ */ jsx15(Text12, { color: x.autonomy === "auto" ? C.yellow : C.faint, children: t.ask.autonomy[x.autonomy ?? "ask"] })
17492
- ] }, x.role)),
17493
- auto.length ? /* @__PURE__ */ jsx15(Text12, { color: C.yellow, wrap: "truncate", children: t.ask.autoWarning(auto.join(", ")) }) : null,
17494
- team.error ? /* @__PURE__ */ jsx15(Text12, { color: C.red, wrap: "truncate", children: team.error }) : null,
17495
- team.saveError && !team.savedPath ? /* @__PURE__ */ jsx15(Text12, { color: C.red, wrap: "truncate", children: team.saveError }) : null,
17496
- /* @__PURE__ */ jsxs11(Box12, { columnGap: 2, children: [
17497
- /* @__PURE__ */ jsx15(Button, { label: busy ? `${g.live} ${t.ask.launchTeam}` : t.ask.launchTeam, k: "\u21B5", primary: focused }),
17498
- !team.template ? team.savedPath ? /* @__PURE__ */ jsx15(Text12, { color: C.faint, children: t.ask.saved }) : /* @__PURE__ */ jsx15(Button, { label: t.ask.save, k: "s" }) : null,
17499
- /* @__PURE__ */ jsx15(Text12, { color: C.faint, children: t.ask.nothingWithoutYou })
17500
- ] })
17501
- ] });
17502
- }
17503
17447
  if (card.kind === "task") {
17504
17448
  const tasks = turn.tasks;
17505
- const waits = !!turn.team && !turn.team.teamId;
17506
17449
  return /* @__PURE__ */ jsxs11(TitledBox, { title: t.ask.taskCard, flexShrink: 0, color: color2, titleColor: C.violet, children: [
17507
17450
  card.rows.map((r) => {
17508
17451
  const c = tasks[r];
@@ -17516,10 +17459,7 @@ function CardBox({ card, turn, focused, busy }) {
17516
17459
  ] }) : null
17517
17460
  ] }, r);
17518
17461
  }),
17519
- /* @__PURE__ */ jsxs11(Box12, { columnGap: 2, children: [
17520
- /* @__PURE__ */ jsx15(Button, { label: busy ? `${g.live} ${t.ask.give}` : t.ask.give, k: "\u21B5", primary: focused && !waits }),
17521
- waits ? /* @__PURE__ */ jsx15(Text12, { color: C.faint, children: t.ask.waitTeam }) : null
17522
- ] })
17462
+ /* @__PURE__ */ jsx15(Box12, { columnGap: 2, children: /* @__PURE__ */ jsx15(Button, { label: busy ? `${g.live} ${t.ask.give}` : t.ask.give, k: "\u21B5", primary: focused }) })
17523
17463
  ] });
17524
17464
  }
17525
17465
  if (card.kind === "launch") {
@@ -17579,7 +17519,7 @@ function CardBox({ card, turn, focused, busy }) {
17579
17519
  }
17580
17520
 
17581
17521
  // src/tui/screens/Launch.tsx
17582
- import { useEffect as useEffect11, useMemo as useMemo5, useRef as useRef11, useState as useState10 } from "react";
17522
+ import { useEffect as useEffect11, useMemo as useMemo5, useState as useState10 } from "react";
17583
17523
  import { Box as Box13, Text as Text14, useInput as useInput6 } from "ink";
17584
17524
 
17585
17525
  // ../fleet/src/launch.ts
@@ -17624,8 +17564,8 @@ function missingOf(f, machines) {
17624
17564
  return null;
17625
17565
  }
17626
17566
  function previewOf(f, sessions, projectIdOf) {
17627
- const machineOf3 = (s) => splitId(s.id)?.machine ?? s.machineId ?? "local";
17628
- const taken = sessions.filter((s) => s.state !== "exited" && machineOf3(s) === f.machine).map((s) => s.name);
17567
+ const machineOf2 = (s) => splitId(s.id)?.machine ?? s.machineId ?? "local";
17568
+ const taken = sessions.filter((s) => s.state !== "exited" && machineOf2(s) === f.machine).map((s) => s.name);
17629
17569
  const seated = f.project === null ? 0 : sessions.filter((s) => projectIdOf(s) === f.project).length;
17630
17570
  return { name: pickName(taken), seat: seated + 1 };
17631
17571
  }
@@ -17646,9 +17586,6 @@ function launchMessage(f, name) {
17646
17586
  };
17647
17587
  }
17648
17588
 
17649
- // src/tui/screens/Launch.tsx
17650
- import { randomUUID as randomUUID3 } from "crypto";
17651
-
17652
17589
  // src/tui/ui/TextInput.tsx
17653
17590
  import { Text as Text13, useInput as useInput5 } from "ink";
17654
17591
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
@@ -17707,25 +17644,9 @@ function ProjectChoice({ project, on }) {
17707
17644
  ] });
17708
17645
  }
17709
17646
  function Launch() {
17710
- const { catalog } = useTui();
17711
- const [prefill] = useState10(() => {
17712
- const p = catalog.current.launch;
17713
- catalog.current.launch = void 0;
17714
- return p;
17715
- });
17716
- const [team, setTeam] = useState10(prefill?.template !== void 0);
17717
- return team ? /* @__PURE__ */ jsx17(TeamLaunch, { onSession: () => setTeam(false), prefill: prefill?.template !== void 0 ? prefill : void 0 }) : /* @__PURE__ */ jsx17(SessionLaunch, { onTeam: () => setTeam(true), prefill: prefill?.template === void 0 ? prefill : void 0 });
17718
- }
17719
- function SessionLaunch({ onTeam, prefill }) {
17720
17647
  const { client, d, machines, projects, t, g, toast, go, select, small } = useTui();
17721
17648
  const list3 = projects?.projects ?? [];
17722
- const rolePick = (r, tool) => r && (r.tool === void 0 || r.tool === tool) ? { model: r.model ?? null, effort: r.effort ?? null } : NO_PICK;
17723
- const [form, setForm] = useState10(() => {
17724
- const tool = prefill?.role?.tool ?? "claude";
17725
- const base = withPick(launchDefaults(list3, machines, prefill?.project ? { project: prefill.project } : {}, tool), rolePick(prefill?.role, tool));
17726
- const f = prefill ? withMachine(base, list3, prefill.machine) : base;
17727
- return prefill?.cwd ? { ...f, repos: [prefill.cwd] } : f;
17728
- });
17649
+ const [form, setForm] = useState10(() => launchDefaults(list3, machines, {}, "claude"));
17729
17650
  const [field, setField] = useState10("prompt");
17730
17651
  const [folder, setFolder] = useState10("");
17731
17652
  const [repoAt, setRepoAt] = useState10(0);
@@ -17749,33 +17670,6 @@ function SessionLaunch({ onTeam, prefill }) {
17749
17670
  return own4.length ? own4 : (machine?.recents ?? []).slice(0, 6).map((r) => r.cwd);
17750
17671
  }, [project, form.machine, machine?.recents]);
17751
17672
  const withFolder = folder.trim() ? { ...form, repos: MULTI_REPO_TOOLS.has(form.tool) ? [folder.trim(), ...form.repos.filter((r) => r !== folder.trim())] : [folder.trim()] } : form;
17752
- const target = withFolder.repos[0] ?? null;
17753
- const sameMachine = !!prefill?.role && withFolder.machine === prefill.machine;
17754
- const readHere = sameMachine && target !== null && target === prefill.cwd;
17755
- const asked2 = sameMachine && !readHere && target !== null ? `${withFolder.machine}\0${target}` : null;
17756
- const [seen, setSeen] = useState10(null);
17757
- useEffect11(() => {
17758
- if (!asked2) return;
17759
- const [machineId, cwd] = asked2.split("\0");
17760
- const requestId = `tui-role-${randomUUID3()}`;
17761
- const off = client.onMessage((m) => {
17762
- if (m.type !== "roles.listing" || m.requestId !== requestId) return;
17763
- off();
17764
- const found = m.error ? null : m.roles.find((r) => r.id === prefill.role.id) ?? null;
17765
- setSeen({ key: asked2, role: found });
17766
- const was = prefill.role;
17767
- if (found && (found.tool !== was.tool || found.model !== was.model || found.effort !== was.effort))
17768
- setForm((f) => found.tool && found.tool !== f.tool ? withTool(f, found.tool, rolePick(found, found.tool)) : withPick(f, rolePick(found, f.tool)));
17769
- });
17770
- const timer2 = setTimeout(() => client.send({ type: "roles.list", cwd, requestId, ...machineId !== "local" ? { machineId } : {} }), 250);
17771
- return () => {
17772
- clearTimeout(timer2);
17773
- off();
17774
- };
17775
- }, [asked2]);
17776
- const checking = asked2 !== null && seen?.key !== asked2;
17777
- const role = readHere ? prefill.role : asked2 !== null && seen?.key === asked2 ? seen.role ?? void 0 : void 0;
17778
- const other = !!role && role.path !== prefill?.role?.path;
17779
17673
  const projectIdOf = (s) => projects ? projectOf(s, projects) : null;
17780
17674
  const drawn = useMemo5(() => previewOf(withFolder, d.sessions, projectIdOf), [form.machine, form.project, d.sessions.length]);
17781
17675
  const [name, setName] = useState10(null);
@@ -17801,17 +17695,13 @@ function SessionLaunch({ onTeam, prefill }) {
17801
17695
  }, [launching, d.lastLaunched, d.errors]);
17802
17696
  const launch2 = () => {
17803
17697
  if (launching) return;
17804
- if (checking && !missing) {
17805
- toast({ text: t.catalog.roleChecking(prefill.role.id), tone: "info" });
17806
- return;
17807
- }
17808
17698
  if (missing) {
17809
17699
  toast({ text: t.launch.missing[missing], tone: "info" });
17810
17700
  setField(missing === "prompt" ? "prompt" : missing === "machine" ? "machine" : "repos");
17811
17701
  return;
17812
17702
  }
17813
17703
  setLaunching({ seq: d.lastLaunched?.seq ?? 0, errorSeq: d.errors.at(-1)?.seq ?? 0, at: Date.now(), name: shownName });
17814
- client.send({ ...launchMessage(withFolder, shownName), ...role ? { role: role.id } : {} });
17704
+ client.send(launchMessage(withFolder, shownName));
17815
17705
  };
17816
17706
  useInput6((input, key) => {
17817
17707
  if (small && !key.escape) return;
@@ -17823,10 +17713,6 @@ function SessionLaunch({ onTeam, prefill }) {
17823
17713
  launch2();
17824
17714
  return;
17825
17715
  }
17826
- if (key.ctrl && input === "t") {
17827
- onTeam();
17828
- return;
17829
- }
17830
17716
  if (key.ctrl && input === "r") {
17831
17717
  setName(pickName([...d.sessions.map((s) => s.name), shownName]));
17832
17718
  return;
@@ -17852,16 +17738,16 @@ function SessionLaunch({ onTeam, prefill }) {
17852
17738
  }
17853
17739
  if (!by) return;
17854
17740
  if (field === "tool") setForm((f) => {
17855
- const next2 = cycle(tools, f.tool, by);
17856
- return withTool(f, next2, rolePick(role, next2));
17741
+ const next3 = cycle(tools, f.tool, by);
17742
+ return withTool(f, next3);
17857
17743
  });
17858
17744
  else if (field === "model") setForm((f) => withPick(f, { model: cycle(modelIds, f.model, by), effort: null }));
17859
17745
  else if (field === "effort") setForm((f) => withPick(f, { model: f.model, effort: cycle(efforts, f.effort, by) }));
17860
17746
  else if (field === "project") {
17861
17747
  const ids = [...list3.map((p) => p.id), null];
17862
17748
  setForm((f) => {
17863
- const next2 = cycle(ids, f.project, by);
17864
- return next2 === null ? { ...f, project: null, repos: [] } : withProject(f, list3, next2);
17749
+ const next3 = cycle(ids, f.project, by);
17750
+ return next3 === null ? { ...f, project: null, repos: [] } : withProject(f, list3, next3);
17865
17751
  });
17866
17752
  setRepoAt(0);
17867
17753
  } else if (field === "machine") {
@@ -17879,43 +17765,8 @@ function SessionLaunch({ onTeam, prefill }) {
17879
17765
  x === cur ? /* @__PURE__ */ jsx17(Text14, { color: C.fg, bold: true, children: `${g.prev} ${label(x)} ${g.next}` }) : /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: label(x) }),
17880
17766
  " "
17881
17767
  ] }, i)) });
17882
- return /* @__PURE__ */ jsxs13(Frame, { crumb: t.launch.title, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+t", t.keys.teamMode], ["ctrl+r", t.keys.redraw], ["esc", t.keys.cancel]], children: [
17768
+ return /* @__PURE__ */ jsxs13(Frame, { crumb: t.launch.title, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+r", t.keys.redraw], ["esc", t.keys.cancel]], children: [
17883
17769
  /* @__PURE__ */ jsxs13(TitledBox, { title: t.launch.title, flexGrow: 1, children: [
17884
- /* @__PURE__ */ jsxs13(Box13, { children: [
17885
- /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: " " }),
17886
- /* @__PURE__ */ jsx17(Box13, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: t.launch.mode }) }),
17887
- /* @__PURE__ */ jsxs13(Text14, { children: [
17888
- /* @__PURE__ */ jsxs13(Text14, { color: C.coral, children: [
17889
- g.radioOn,
17890
- " ",
17891
- t.launch.session
17892
- ] }),
17893
- " ",
17894
- /* @__PURE__ */ jsxs13(Text14, { color: C.dim, children: [
17895
- g.radioOff,
17896
- " ",
17897
- t.launch.team
17898
- ] }),
17899
- " ",
17900
- /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: "ctrl+t" })
17901
- ] })
17902
- ] }),
17903
- prefill?.role ? /* @__PURE__ */ jsxs13(Box13, { children: [
17904
- /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: " " }),
17905
- /* @__PURE__ */ jsx17(Box13, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: t.catalog.role }) }),
17906
- role ? /* @__PURE__ */ jsxs13(Text14, { wrap: "truncate", children: [
17907
- /* @__PURE__ */ jsxs13(Text14, { color: C.violet, children: [
17908
- "@",
17909
- role.id
17910
- ] }),
17911
- " ",
17912
- /* @__PURE__ */ jsx17(Text14, { color: C.fg, children: role.label }),
17913
- other ? /* @__PURE__ */ jsxs13(Text14, { color: C.faint, children: [
17914
- " \xB7 ",
17915
- t.catalog.roleReadIn(target)
17916
- ] }) : null
17917
- ] }) : /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", color: C.faint, children: checking ? t.catalog.roleChecking(prefill.role.id) : t.catalog.roleElsewhere(prefill.role.id, prefill.cwd ?? "~/.ledge") })
17918
- ] }) : null,
17919
17770
  row2("prompt", t.launch.instruction, /* @__PURE__ */ jsx17(TextInput, { value: form.prompt, onChange: (v) => setForm((f) => ({ ...f, prompt: v })), active: field === "prompt" })),
17920
17771
  /* @__PURE__ */ jsx17(Text14, { children: " " }),
17921
17772
  row2("tool", t.launch.agent, /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", children: tools.map((x) => /* @__PURE__ */ jsxs13(Text14, { color: x === form.tool ? C.coral : C.dim, children: [
@@ -17969,216 +17820,6 @@ function SessionLaunch({ onTeam, prefill }) {
17969
17820
  ] })
17970
17821
  ] });
17971
17822
  }
17972
- var TEAM_FIELDS = ["project", "folder", "template", "seats", "name"];
17973
- var autonomyColor = (a) => ({ ask: C.faint, edits: C.dim, auto: C.yellow })[a];
17974
- function TeamLaunch({ onSession, prefill }) {
17975
- const { client, machines, projects, t, g, toast, go, small } = useTui();
17976
- const list3 = projects?.projects ?? [];
17977
- const [projectId, setProjectId] = useState10(prefill ? prefill.project : list3[0]?.id ?? null);
17978
- const project = list3.find((p) => p.id === projectId);
17979
- const folders = useMemo5(() => {
17980
- const own4 = (project?.repos ?? []).filter((r) => r.machine === "local").map((r) => r.path);
17981
- const base = own4.length ? own4 : (machines.find((m) => m.id === "local")?.recents ?? []).slice(0, 6).map((r) => r.cwd);
17982
- return prefill?.cwd && projectId === prefill.project && !base.includes(prefill.cwd) ? [prefill.cwd, ...base] : base;
17983
- }, [project, machines]);
17984
- const [folderAt, setFolderAt] = useState10(() => Math.max(0, prefill?.cwd ? folders.indexOf(prefill.cwd) : 0));
17985
- const wanted = useRef11(prefill?.template);
17986
- const cwd = folders[folderAt] ?? folders[0] ?? null;
17987
- const [field, setField] = useState10("template");
17988
- const [defs, setDefs] = useState10(null);
17989
- const [template, setTemplate] = useState10(null);
17990
- const [seats, setSeats] = useState10([]);
17991
- const [seatAt, setSeatAt] = useState10(0);
17992
- const [name, setName] = useState10("");
17993
- const [launching, setLaunching] = useState10(false);
17994
- useEditing(true);
17995
- useEffect11(() => {
17996
- if (!cwd) {
17997
- setDefs({ roles: [], templates: [] });
17998
- return;
17999
- }
18000
- setDefs(null);
18001
- const requestId = `tui-roles-${randomUUID3()}`;
18002
- const off = client.onMessage((m) => {
18003
- if (m.type !== "roles.listing" || m.requestId !== requestId) return;
18004
- off();
18005
- setDefs({ roles: m.roles, templates: m.templates });
18006
- setSeats(m.roles.map((role) => ({ role, tool: role.tool ?? "claude", on: false })));
18007
- const first = m.templates.find((x) => x.id === wanted.current) ?? m.templates[0];
18008
- wanted.current = void 0;
18009
- setTemplate(first?.id ?? null);
18010
- setName((n) => n || first?.name || project?.name || "");
18011
- });
18012
- client.send({ type: "roles.list", cwd, requestId });
18013
- return off;
18014
- }, [cwd]);
18015
- const tpl = defs?.templates.find((x) => x.id === template);
18016
- const chosen = tpl ? tpl.seats.map((x) => {
18017
- const role = defs.roles.find((r) => r.id === x.role);
18018
- return { id: x.role, tool: x.tool ?? role?.tool ?? "claude", autonomy: role?.autonomy ?? "ask" };
18019
- }) : seats.filter((x) => x.on).map((x) => ({ id: x.role.id, tool: x.tool, autonomy: x.role.autonomy ?? "ask" }));
18020
- const auto = chosen.filter((x) => x.autonomy === "auto").map((x) => `@${x.id}`);
18021
- const teamName = name.trim() || tpl?.name || project?.name || "team";
18022
- const launch2 = () => {
18023
- if (launching || !cwd) return;
18024
- if (!chosen.length) {
18025
- toast({ text: t.launch.noSeat, tone: "info" });
18026
- return;
18027
- }
18028
- const rid = randomUUID3();
18029
- setLaunching(true);
18030
- const off = client.onMessage((m) => {
18031
- if (m.type !== "team.launched" && m.type !== "team.failed" || m.rid !== rid) return;
18032
- off();
18033
- setLaunching(false);
18034
- if (m.type === "team.launched") {
18035
- toast({ text: t.launch.teamLaunched(m.team.name), tone: "ok" });
18036
- go("overview");
18037
- } else toast({ text: t.launch.teamFailed(m.reason), tone: "error" });
18038
- });
18039
- client.send({ type: "team.launch", rid, cwd, name: teamName, ...tpl ? { template: tpl.id } : { seats: chosen.map((x) => ({ role: x.id, tool: x.tool })) } });
18040
- };
18041
- const tools = ["claude", "codex", "cursor", "omp"];
18042
- const templates = [...(defs?.templates ?? []).map((x) => x.id), null];
18043
- useInput6((input, key) => {
18044
- if (small && !key.escape) return;
18045
- if (key.escape) {
18046
- go("overview");
18047
- return;
18048
- }
18049
- if (key.ctrl && input === "s") {
18050
- launch2();
18051
- return;
18052
- }
18053
- if (key.ctrl && input === "t") {
18054
- onSession();
18055
- return;
18056
- }
18057
- if (key.tab || key.downArrow || key.return) {
18058
- setField((f) => cycle(TEAM_FIELDS, f, key.shift ? -1 : 1));
18059
- return;
18060
- }
18061
- if (key.upArrow) {
18062
- setField((f) => cycle(TEAM_FIELDS, f, -1));
18063
- return;
18064
- }
18065
- if (field === "name") return;
18066
- const by = key.rightArrow || input === "l" ? 1 : key.leftArrow || input === "h" ? -1 : 0;
18067
- if (field === "project" && by) {
18068
- const ids = list3.map((p) => p.id);
18069
- if (ids.length) {
18070
- setProjectId((cur) => cycle(ids, cur ?? ids[0], by));
18071
- setFolderAt(0);
18072
- }
18073
- } else if (field === "folder" && by && folders.length) setFolderAt((i) => (i + by + folders.length) % folders.length);
18074
- else if (field === "template" && by) {
18075
- const next2 = cycle(templates, template, by);
18076
- setTemplate(next2);
18077
- const x = defs?.templates.find((y) => y.id === next2);
18078
- if (x) setName(x.name);
18079
- } else if (field === "seats" && !tpl) {
18080
- if (input === "j") setSeatAt((i) => seats.length ? (i + 1) % seats.length : 0);
18081
- else if (input === "k") setSeatAt((i) => seats.length ? (i + seats.length - 1) % seats.length : 0);
18082
- else if (input === " ") setSeats((xs) => xs.map((x, i) => i === seatAt ? { ...x, on: !x.on } : x));
18083
- else if (by) setSeats((xs) => xs.map((x, i) => i === seatAt ? { ...x, tool: cycle(tools, x.tool, by) } : x));
18084
- }
18085
- });
18086
- const row2 = (f, label, value) => /* @__PURE__ */ jsxs13(Box13, { backgroundColor: field === f ? C.sel : void 0, children: [
18087
- /* @__PURE__ */ jsx17(Text14, { color: field === f ? C.coral : C.faint, children: field === f ? "\u258C" : " " }),
18088
- /* @__PURE__ */ jsx17(Box13, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: label }) }),
18089
- /* @__PURE__ */ jsx17(Box13, { flexGrow: 1, overflow: "hidden", flexDirection: "column", children: value })
18090
- ] }, f);
18091
- return /* @__PURE__ */ jsxs13(Frame, { crumb: t.launch.teamTitle, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+t", t.keys.teamMode], ["esc", t.keys.cancel]], children: [
18092
- /* @__PURE__ */ jsxs13(TitledBox, { title: t.launch.teamTitle, flexGrow: 1, children: [
18093
- /* @__PURE__ */ jsxs13(Box13, { children: [
18094
- /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: " " }),
18095
- /* @__PURE__ */ jsx17(Box13, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: t.launch.mode }) }),
18096
- /* @__PURE__ */ jsxs13(Text14, { children: [
18097
- /* @__PURE__ */ jsxs13(Text14, { color: C.dim, children: [
18098
- g.radioOff,
18099
- " ",
18100
- t.launch.session
18101
- ] }),
18102
- " ",
18103
- /* @__PURE__ */ jsxs13(Text14, { color: C.coral, children: [
18104
- g.radioOn,
18105
- " ",
18106
- t.launch.team
18107
- ] }),
18108
- " ",
18109
- /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: "ctrl+t" })
18110
- ] })
18111
- ] }),
18112
- row2("project", t.launch.project, /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", children: list3.length === 0 ? /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: t.launch.none }) : list3.map((p) => /* @__PURE__ */ jsx17(ProjectChoice, { project: p, on: p.id === projectId }, p.id)) })),
18113
- row2("folder", t.launch.folder, /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", children: cwd ? /* @__PURE__ */ jsxs13(Fragment5, { children: [
18114
- /* @__PURE__ */ jsx17(Text14, { color: C.fg, children: cwd }),
18115
- folders.length > 1 ? /* @__PURE__ */ jsxs13(Text14, { color: C.faint, children: [
18116
- " (",
18117
- folderAt + 1,
18118
- "/",
18119
- folders.length,
18120
- ")"
18121
- ] }) : null
18122
- ] }) : /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: t.launch.none }) })),
18123
- row2("template", t.launch.template, defs === null ? /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: t.launch.loadingRoles }) : /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", children: templates.map((x) => /* @__PURE__ */ jsxs13(Text14, { color: x === template ? C.fg : C.dim, bold: x === template, children: [
18124
- x === template ? `${g.prev} ${x ?? t.launch.custom} ${g.next}` : x ?? t.launch.custom,
18125
- " "
18126
- ] }, x ?? "-")) })),
18127
- row2("seats", t.launch.seats, tpl ? /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", children: chosen.map((x) => /* @__PURE__ */ jsxs13(Text14, { children: [
18128
- /* @__PURE__ */ jsxs13(Text14, { color: C.violet, children: [
18129
- "@",
18130
- x.id
18131
- ] }),
18132
- " ",
18133
- /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: TOOL_NAME[x.tool] }),
18134
- " "
18135
- ] }, x.id)) }) : seats.length === 0 ? /* @__PURE__ */ jsx17(Text14, { color: C.faint, children: t.launch.noRole }) : /* @__PURE__ */ jsx17(Fragment5, { children: seats.map((x, i) => /* @__PURE__ */ jsxs13(Text14, { wrap: "truncate", backgroundColor: field === "seats" && i === seatAt ? C.line : void 0, children: [
18136
- /* @__PURE__ */ jsx17(Text14, { color: x.on ? C.green : C.faint, children: x.on ? g.boxOn : g.boxOff }),
18137
- " ",
18138
- /* @__PURE__ */ jsxs13(Text14, { color: C.violet, children: [
18139
- "@",
18140
- x.role.id
18141
- ] }),
18142
- " ",
18143
- x.role.label,
18144
- " ",
18145
- /* @__PURE__ */ jsxs13(Text14, { color: C.dim, children: [
18146
- g.prev,
18147
- " ",
18148
- TOOL_NAME[x.tool],
18149
- " ",
18150
- g.next
18151
- ] }),
18152
- " ",
18153
- /* @__PURE__ */ jsx17(Text14, { color: autonomyColor(x.role.autonomy ?? "ask"), children: t.ask.autonomy[x.role.autonomy ?? "ask"] })
18154
- ] }, x.role.id)) })),
18155
- row2("name", t.launch.teamName, /* @__PURE__ */ jsx17(TextInput, { value: name, onChange: setName, active: field === "name", placeholder: teamName }))
18156
- ] }),
18157
- /* @__PURE__ */ jsxs13(TitledBox, { title: t.launch.preview, color: C.coral, titleColor: C.coral, children: [
18158
- /* @__PURE__ */ jsxs13(Text14, { wrap: "truncate", children: [
18159
- /* @__PURE__ */ jsx17(Text14, { color: C.coral, children: g.team }),
18160
- " ",
18161
- /* @__PURE__ */ jsx17(Text14, { color: C.fg, bold: true, children: t.launch.teamPreview(teamName, project?.name ?? cwd ?? "-") })
18162
- ] }),
18163
- /* @__PURE__ */ jsx17(Text14, { wrap: "truncate", children: chosen.map((x) => /* @__PURE__ */ jsxs13(Text14, { children: [
18164
- /* @__PURE__ */ jsxs13(Text14, { color: C.violet, children: [
18165
- "@",
18166
- x.id
18167
- ] }),
18168
- " ",
18169
- /* @__PURE__ */ jsx17(Text14, { color: C.dim, children: TOOL_NAME[x.tool] }),
18170
- " ",
18171
- /* @__PURE__ */ jsx17(Text14, { color: autonomyColor(x.autonomy), children: t.ask.autonomy[x.autonomy] }),
18172
- " "
18173
- ] }, x.id)) }),
18174
- auto.length ? /* @__PURE__ */ jsx17(Text14, { color: C.yellow, wrap: "truncate", children: t.ask.autoWarning(auto.join(", ")) }) : null,
18175
- /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, columnGap: 2, children: [
18176
- /* @__PURE__ */ jsx17(Button, { label: launching ? t.launch.launching : t.ask.launchTeam, k: "ctrl+s", primary: true }),
18177
- /* @__PURE__ */ jsx17(Button, { label: t.launch.cancel, k: "esc" })
18178
- ] })
18179
- ] })
18180
- ] });
18181
- }
18182
17823
 
18183
17824
  // src/tui/screens/Settings.tsx
18184
17825
  import { useState as useState11 } from "react";
@@ -18284,7 +17925,8 @@ function Help() {
18284
17925
  [t.help.field, [["\u21B5", k.sendOnly], ["alt+\u21B5 ctrl+j", k.newLine], ["\u2191 \u2193", k.lineHistory], ["ctrl \u2190 \u2192", k.word], ["ctrl+w", t.help.wordBefore], ["ctrl+u", k.clear], ["esc", k.keepAndLeave]]],
18285
17926
  [t.help.launch, [["tab \u2191 \u2193", k.nextField], ["\u2190 \u2192", k.choose], [t.keys.space, k.tick], ["ctrl+r", t.keys.redraw], ["ctrl+s", k.start], ["esc", k.cancel]]],
18286
17927
  [t.help.settings, [["\u2191 \u2193", k.navigate], ["\u2190 \u2192", k.change]]],
18287
- [t.help.teams, [["j k", k.navigate], ["\u2190 \u2192", k.rolesTemplates], ["c", k.folder], ["\u21B5", k.launchRole], ["o", k.openFile], ["!", k.ignored]]]
17928
+ [t.help.rigLaunch, [["tab \u2191 \u2193", k.nextField], ["\u2190 \u2192", k.choose], ["- +", t.rigLaunch.count], ["m", t.rigLaunch.modelKey], ["o", t.rigLaunch.policyKey], [t.keys.space, k.tick], ["p ctrl+p", t.rigLaunch.check], ["ctrl+s", k.start], ["esc", k.cancel]]],
17929
+ [t.help.openrig, [["tab", t.openrig.machine], ["j k \u2191 \u2193", k.navigate], ["\u21B5", t.openrig.unfold], ["s", t.openrig.toggle], ["R", t.openrig.restart], ["\xB1 =", t.rigSeats.key], ["+", t.rigLaunch.launchKey], ["D", `${t.openrig.daemonKey} \xB7 ${t.openrig.toggle}`], ["K", `${t.openrig.kernelKey} \xB7 ${t.openrig.toggle}`], ["y n", `${t.openrig.yes} \xB7 ${t.openrig.no}`]]]
18288
17930
  ];
18289
17931
  const lines = helpLines(groups, Math.max(20, columns - 4));
18290
17932
  const [view, setView] = useState12(10);
@@ -18315,419 +17957,917 @@ function Help() {
18315
17957
  return /* @__PURE__ */ jsx19(Frame, { crumb: t.help.title, keys: [["esc", k.back], ...maxTop ? [["j k PgUp PgDn", k.scroll]] : []], children: /* @__PURE__ */ jsx19(TitledBox, { title: t.help.title, flexGrow: 1, foot: foot || void 0, children: /* @__PURE__ */ jsx19(Lines, { lines, up: Math.max(0, below), onHeight: setView }) }) });
18316
17958
  }
18317
17959
 
18318
- // src/tui/screens/Teams.tsx
18319
- import { useEffect as useEffect12, useMemo as useMemo7, useState as useState13 } from "react";
17960
+ // src/tui/screens/OpenRig.tsx
17961
+ import { useEffect as useEffect13, useRef as useRef12, useState as useState14 } from "react";
18320
17962
  import { Box as Box16, Text as Text17 } from "ink";
18321
- import { randomUUID as randomUUID4 } from "crypto";
18322
17963
 
18323
- // src/tui/model/catalog.ts
18324
- var machineOf2 = (s) => s.machineId ?? "local";
18325
- function projectOfFolder(projects, cwd, machine = "local") {
18326
- if (!cwd) return null;
18327
- const hit = (projects?.projects ?? []).find((p) => p.repos.some((r) => r.machine === machine && (cwd === r.path || cwd.startsWith(`${r.path}/`))));
18328
- return hit?.id ?? null;
18329
- }
18330
- function catalogScopes(o) {
18331
- const list3 = o.projects?.projects ?? [];
18332
- const selected = o.selected ? { machine: machineOf2(o.selected), cwd: o.selected.repoPath ?? o.selected.cwd } : void 0;
18333
- const memo2 = o.memo.folder ? { machine: o.memo.machine ?? "local", cwd: o.memo.folder } : void 0;
18334
- const firsts = list3.map((p) => {
18335
- const r = p.repos.find((x) => x.machine === "local") ?? p.repos[0];
18336
- return r && { machine: r.machine, cwd: r.path };
17964
+ // ../fleet/src/openrig.ts
17965
+ import { useCallback, useEffect as useEffect12, useRef as useRef11, useState as useState13 } from "react";
17966
+ var OPENRIG_POLL_MS = 5e3;
17967
+ var next2 = 0;
17968
+ function useOpenRig(client, machine, pollMs = OPENRIG_POLL_MS) {
17969
+ const [view, setView] = useState13({ machine, state: null });
17970
+ const [pending, setPending] = useState13(null);
17971
+ const [last, setLast] = useState13(null);
17972
+ const waiting = useRef11(/* @__PURE__ */ new Map());
17973
+ const asked2 = useRef11(null);
17974
+ const ask2 = useCallback(() => {
17975
+ if (machine === null) return;
17976
+ const requestId = `openrig-${++next2}`;
17977
+ asked2.current = requestId;
17978
+ client.send({ type: "openrig.status", requestId, machineId: machine });
17979
+ }, [client, machine]);
17980
+ useEffect12(() => {
17981
+ setView({ machine, state: null });
17982
+ setPending(null);
17983
+ setLast(null);
17984
+ if (machine === null) return;
17985
+ const off = client.onMessage((m) => {
17986
+ if (m.type === "openrig.state") {
17987
+ if ((m.machineId ?? "local") !== machine) return;
17988
+ if (m.requestId !== void 0 && m.requestId !== asked2.current) return;
17989
+ setView((v) => ({ machine, state: m.state ?? (m.error ? null : v.state), ...m.error ? { error: m.error } : {} }));
17990
+ } else if (m.type === "openrig.done") {
17991
+ const resolve = waiting.current.get(m.requestId);
17992
+ if (!resolve) return;
17993
+ waiting.current.delete(m.requestId);
17994
+ resolve(m);
17995
+ }
17996
+ });
17997
+ ask2();
17998
+ const timer2 = setInterval(ask2, pollMs);
17999
+ return () => {
18000
+ off();
18001
+ clearInterval(timer2);
18002
+ for (const [, r] of waiting.current) r({ type: "openrig.done", requestId: "", ok: false, commands: [], error: "cancelled" });
18003
+ waiting.current.clear();
18004
+ };
18005
+ }, [client, machine, ask2, pollMs]);
18006
+ const act = useCallback((action) => {
18007
+ if (machine === null) return Promise.resolve({ type: "openrig.done", requestId: "", ok: false, commands: [], error: "offline" });
18008
+ const requestId = `openrig-act-${++next2}`;
18009
+ setPending(action);
18010
+ setLast(null);
18011
+ return new Promise((resolve) => {
18012
+ waiting.current.set(requestId, (d) => {
18013
+ setPending((p) => p === action ? null : p);
18014
+ setLast(d);
18015
+ resolve(d);
18016
+ });
18017
+ client.send({ type: "openrig.act", requestId, action, machineId: machine });
18018
+ });
18019
+ }, [client, machine]);
18020
+ const shown = view.machine === machine ? view : { state: null };
18021
+ return { state: shown.state, ..."error" in shown && shown.error ? { error: shown.error } : {}, pending, last, act, refresh: ask2 };
18022
+ }
18023
+ function useRigSpecs(client, machine) {
18024
+ const [answer, setAnswer] = useState13(null);
18025
+ const [round, setRound] = useState13(0);
18026
+ useEffect12(() => {
18027
+ if (machine === null) return;
18028
+ const requestId = `openrig-specs-${++next2}`;
18029
+ setAnswer((a) => a?.machine === machine ? a : null);
18030
+ const off = client.onMessage((m) => {
18031
+ if (m.type !== "openrig.specList" || m.requestId !== requestId) return;
18032
+ setAnswer({ machine, specs: m.specs, ...m.error ? { error: m.error } : {} });
18033
+ });
18034
+ client.send({ type: "openrig.specs", requestId, machineId: machine });
18035
+ return off;
18036
+ }, [client, machine, round]);
18037
+ const reload = useCallback(() => setRound((r) => r + 1), []);
18038
+ if (machine === null) return { status: "idle", specs: [], reload };
18039
+ if (!answer || answer.machine !== machine) return { status: "loading", specs: [], reload };
18040
+ return { status: "ready", specs: answer.specs, ...answer.error ? { error: answer.error } : {}, reload };
18041
+ }
18042
+ function launchRig(client, machine, launch2) {
18043
+ const requestId = `openrig-launch-${++next2}`;
18044
+ return new Promise((resolve) => {
18045
+ const off = client.onMessage((m) => {
18046
+ if (m.type !== "openrig.launched" || m.requestId !== requestId) return;
18047
+ off();
18048
+ resolve(m);
18049
+ });
18050
+ client.send({ type: "openrig.launch", requestId, launch: launch2, machineId: machine });
18337
18051
  });
18338
- const places = [selected, memo2, ...firsts, ...o.recents.slice(0, 6)].filter((x) => !!x?.cwd);
18339
- const seen = /* @__PURE__ */ new Set();
18340
- const label = (x) => {
18341
- const p = list3.find((y) => y.id === projectOfFolder(o.projects, x.cwd, x.machine));
18342
- return p ? `${p.name} ${x.cwd}` : x.cwd;
18343
- };
18052
+ }
18053
+ function reseatRig(client, machine, rig, seats, delegation) {
18054
+ const requestId = `openrig-seats-${++next2}`;
18055
+ return new Promise((resolve) => {
18056
+ const off = client.onMessage((m) => {
18057
+ if (m.type !== "openrig.reseated" || m.requestId !== requestId) return;
18058
+ off();
18059
+ resolve(m);
18060
+ });
18061
+ client.send({ type: "openrig.seats", requestId, machineId: machine, rig, seats, ...delegation ? { delegation } : {} });
18062
+ });
18063
+ }
18064
+
18065
+ // src/tui/model/openrig.ts
18066
+ var listedRigs = (s) => (s?.rigs ?? []).filter((r) => r.name !== KERNEL_RIG);
18067
+ var daemonUp = (s) => !!s?.installed && s.daemon.state === "running";
18068
+ var rigRunning = (r) => r.status === "running" || r.running > 0;
18069
+ function rigLook(r) {
18070
+ if (r.attention > 0) return "attention";
18071
+ if (r.status === "running") return "running";
18072
+ return r.running > 0 ? "starting" : "stopped";
18073
+ }
18074
+ var seatBusy = (s) => s.activity === "running" || s.work > 0;
18075
+ var rigToggle = (r) => rigRunning(r) ? { kind: "rig.stop", rig: r.name, snapshot: true } : { kind: "rig.start", rig: r.name };
18076
+ var daemonToggle = (s) => s.daemon.state === "running" ? { kind: "daemon.stop" } : { kind: "daemon.start", restore: "last" };
18077
+ var kernelToggle = (s) => s.kernel.state === "ready" || s.kernel.state === "starting" ? { kind: "kernel.stop" } : { kind: "kernel.start" };
18078
+ var asksFirst = (a) => a.kind.endsWith(".stop") || a.kind.endsWith(".restart");
18079
+ function busySeats(s, a) {
18080
+ const of = (r) => (r?.nodes ?? []).filter(seatBusy);
18081
+ if (a.kind === "rig.stop" || a.kind === "rig.restart") return of(s.rigs.find((r) => r.name === a.rig)).map((n) => n.logicalId);
18082
+ if (a.kind === "kernel.stop" || a.kind === "kernel.restart") return of(s.rigs.find((r) => r.name === KERNEL_RIG)).map((n) => n.logicalId);
18083
+ if (a.kind === "daemon.stop" || a.kind === "daemon.restart") return s.rigs.flatMap((r) => of(r).map((n) => `${r.name} \xB7 ${n.logicalId}`));
18084
+ return [];
18085
+ }
18086
+ var commandsOf = (a) => rigCommands(a).map(showRigCommand);
18087
+ var confirmOf = (s, a) => ({ action: a, busy: busySeats(s, a), commands: commandsOf(a) });
18088
+ var step2 = (at, by, n) => n ? (at + by + n) % n : 0;
18089
+ function keepRig(rigs, at, id) {
18090
+ const found = id ? rigs.findIndex((r) => r.id === id) : -1;
18091
+ return found >= 0 ? found : Math.max(0, Math.min(at, rigs.length - 1));
18092
+ }
18093
+
18094
+ // src/tui/model/rigSeats.ts
18095
+ var canReseat = (r) => !!r && rigRunning(r) && r.nodes.length > 0;
18096
+ function seatRows(r) {
18097
+ const byId = new Map(r.nodes.map((n) => [n.logicalId, n]));
18098
+ return [...seatsByRole(r.nodes)].map(([role, seats]) => ({ role, seats, first: byId.get(seats[0]) }));
18099
+ }
18100
+ var countsOf = (rows) => Object.fromEntries(rows.map((r) => [r.role, r.seats.length]));
18101
+ var withCount = (counts, role, by) => ({ ...counts, [role]: Math.max(0, Math.min(9, (counts[role] ?? 0) + by)) });
18102
+ var changedCounts = (rows, counts) => Object.fromEntries(rows.filter((r) => (counts[r.role] ?? r.seats.length) !== r.seats.length).map((r) => [r.role, counts[r.role]]));
18103
+ var planOf = (r, rows, counts) => seatPlan(r.nodes, changedCounts(rows, counts));
18104
+ function chips(row2, plan) {
18105
+ const removed = new Set(plan.remove);
18344
18106
  return [
18345
- ...places.filter((x) => {
18346
- const k = `${x.machine}\0${x.cwd}`;
18347
- if (seen.has(k)) return false;
18348
- seen.add(k);
18349
- return true;
18350
- }).map((x) => ({ cwd: x.cwd, machine: x.machine, label: label(x) })),
18351
- { cwd: null, machine: "local", label: o.homeOnly }
18107
+ ...row2.seats.map((id) => ({ id, kind: removed.has(id) ? "removed" : "kept" })),
18108
+ ...plan.add.filter((a) => a.role === row2.role).map((a) => ({ id: a.id, kind: "added" }))
18352
18109
  ];
18353
18110
  }
18354
- function initialScope(scopes, selected, memo2) {
18355
- const own4 = selected ? scopes.find((x) => x.machine === machineOf2(selected) && x.cwd === (selected.repoPath ?? selected.cwd)) : void 0;
18356
- const last = memo2.folder !== void 0 ? scopes.find((x) => x.cwd === memo2.folder && (memo2.folder === null || x.machine === (memo2.machine ?? "local"))) : void 0;
18357
- return own4 ?? last ?? scopes[0];
18358
- }
18359
- var bySource = (list3) => ({ repo: list3.filter((x) => x.source === "repo"), home: list3.filter((x) => x.source === "home") });
18360
- var usedIn = (id, templates) => templates.filter((x) => x.seats.some((s) => s.role === id));
18361
- function heldBy(id, sessions, teams, project, projectIdOf) {
18362
- return sessions.filter((s) => s.role === id && s.state !== "exited" && (project === null || projectIdOf(s) === project)).map((s) => ({ name: s.name, team: teams.find((x) => x.id === s.team && x.state !== "dissolved")?.name }));
18111
+ var busyRemovals = (r, plan) => plan.remove.filter((id) => {
18112
+ const n = r.nodes.find((x) => x.logicalId === id);
18113
+ return !!n && seatBusy(n);
18114
+ });
18115
+ function previewCommands(rig, plan, links) {
18116
+ if (!plan.add.length && !plan.remove.length) return [];
18117
+ return [
18118
+ ...plan.add.map((a) => `rig add ${rig} ${a.pod} ${a.id}`),
18119
+ ...plan.remove.map((id) => `rig remove ${rig} ${id}`),
18120
+ ...plan.add.length ? [`rig edge add \u2026 (${links})`] : [],
18121
+ `rig snapshot ${rig}`
18122
+ ];
18363
18123
  }
18364
- var stepIndex = (length, i, by) => length ? ((i + by) % length + length) % length : 0;
18365
18124
 
18366
- // src/tui/ui/Instruction.tsx
18367
- import { useMemo as useMemo6, useRef as useRef12 } from "react";
18368
- import { Box as Box15, Text as Text16, useBoxMetrics as useBoxMetrics4 } from "ink";
18369
- import { jsx as jsx20 } from "react/jsx-runtime";
18370
- function Instruction({ text: text5, empty: empty3 }) {
18371
- const { g, tone: tone2 } = useTui();
18372
- const ref = useRef12(null);
18373
- const { width, height } = useBoxMetrics4(ref);
18374
- const lines = useMemo6(() => width > 0 ? markdownLines(text5, { width, g, color: C.fg }) : [], [text5, width, g, tone2]);
18375
- return /* @__PURE__ */ jsx20(Box15, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !text5.trim() ? /* @__PURE__ */ jsx20(Text16, { color: C.faint, children: empty3 }) : lines.slice(0, Math.max(1, height)).map((l, i) => /* @__PURE__ */ jsx20(Box15, { flexShrink: 0, children: /* @__PURE__ */ jsx20(LineText, { line: l }) }, i)) });
18125
+ // src/tui/ui/RigSeats.tsx
18126
+ import { Box as Box15, Text as Text16 } from "ink";
18127
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
18128
+ function RigSeats({ rig, machine, rows, counts, at, delegation, plan, applying }) {
18129
+ const { t, g } = useTui();
18130
+ const s = t.rigSeats;
18131
+ const now2 = rows.map((r) => `${r.seats.length} ${r.role}`).join(", ");
18132
+ const commands = previewCommands(rig.name, plan, delegation === "pairs" ? s.linksPairs : s.linksAll);
18133
+ const busy = busyRemovals(rig, plan);
18134
+ return /* @__PURE__ */ jsxs15(TitledBox, { title: s.title(rig.name, machine), color: C.coral, titleColor: C.coral, titleBold: true, flexGrow: 1, children: [
18135
+ /* @__PURE__ */ jsx20(Text16, { wrap: "truncate", color: C.dim, children: s.stays(now2) }),
18136
+ /* @__PURE__ */ jsx20(Text16, { children: " " }),
18137
+ rows.map((r, i) => {
18138
+ const on = i === at, n = counts[r.role] ?? r.seats.length;
18139
+ return /* @__PURE__ */ jsxs15(Box15, { flexShrink: 0, backgroundColor: on ? C.sel : void 0, children: [
18140
+ /* @__PURE__ */ jsx20(Text16, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
18141
+ /* @__PURE__ */ jsx20(Box15, { width: 16, flexShrink: 0, children: /* @__PURE__ */ jsx20(Text16, { wrap: "truncate", color: C.fg, bold: true, children: r.role }) }),
18142
+ /* @__PURE__ */ jsx20(Box15, { width: 26, flexShrink: 0, children: /* @__PURE__ */ jsx20(Text16, { wrap: "truncate", color: C.dim, children: [r.first.runtime, r.first.model].filter(Boolean).join(" \xB7 ") }) }),
18143
+ /* @__PURE__ */ jsx20(Box15, { width: 8, flexShrink: 0, children: /* @__PURE__ */ jsxs15(Text16, { children: [
18144
+ /* @__PURE__ */ jsx20(Text16, { color: C.dim, children: "\u2212 " }),
18145
+ /* @__PURE__ */ jsx20(Text16, { color: n === r.seats.length ? C.fg : C.green, bold: true, children: n }),
18146
+ /* @__PURE__ */ jsx20(Text16, { color: C.dim, children: " +" })
18147
+ ] }) }),
18148
+ /* @__PURE__ */ jsx20(Box15, { flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx20(Text16, { wrap: "truncate", children: chips(r, plan).map((c) => /* @__PURE__ */ jsxs15(Text16, { color: c.kind === "added" ? C.green : c.kind === "removed" ? C.red : C.dim, strikethrough: c.kind === "removed", children: [
18149
+ c.kind === "added" ? `+${c.id}` : c.id,
18150
+ " "
18151
+ ] }, c.id)) }) })
18152
+ ] }, r.role);
18153
+ }),
18154
+ /* @__PURE__ */ jsx20(Text16, { children: " " }),
18155
+ plan.add.length ? /* @__PURE__ */ jsxs15(Text16, { wrap: "truncate", children: [
18156
+ delegation === "pairs" ? /* @__PURE__ */ jsx20(Text16, { color: C.fg, bold: true, children: `${g.prev} ${s.pairs} ${g.next}` }) : /* @__PURE__ */ jsx20(Text16, { color: C.dim, children: s.pairs }),
18157
+ " ",
18158
+ delegation === "all" ? /* @__PURE__ */ jsx20(Text16, { color: C.fg, bold: true, children: `${g.prev} ${s.all} ${g.next}` }) : /* @__PURE__ */ jsx20(Text16, { color: C.dim, children: s.all })
18159
+ ] }) : null,
18160
+ busy.map((id) => /* @__PURE__ */ jsx20(Text16, { wrap: "truncate", color: C.red, children: s.busy(id) }, id)),
18161
+ commands.length ? /* @__PURE__ */ jsx20(Box15, { marginTop: 1, flexDirection: "column", children: commands.map((c) => /* @__PURE__ */ jsx20(Text16, { wrap: "truncate", color: C.faint, children: c }, c)) }) : /* @__PURE__ */ jsx20(Text16, { color: C.faint, children: s.nothing }),
18162
+ applying ? /* @__PURE__ */ jsx20(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text16, { color: C.blue, children: s.applying }) }) : null
18163
+ ] });
18376
18164
  }
18377
18165
 
18378
- // src/tui/screens/Teams.tsx
18379
- import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
18380
- var autonomyColor2 = (a) => a === "auto" ? C.yellow : a === "edits" ? C.blue : C.faint;
18381
- var ROLES_MS = 4e3;
18382
- function Teams() {
18166
+ // src/tui/screens/OpenRig.tsx
18167
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
18168
+ var LOOK = {
18169
+ running: { glyph: "\u25CF", color: () => C.green },
18170
+ starting: { glyph: "\u25CC", color: () => C.blue },
18171
+ attention: { glyph: "\u25B2", color: () => C.yellow },
18172
+ stopped: { glyph: "\u25CB", color: () => C.faint }
18173
+ };
18174
+ function question(t, a) {
18175
+ const w = t.openrig.what;
18176
+ if (a.kind === "rig.stop") return t.openrig.confirm(w["rig.stop"](a.rig));
18177
+ if (a.kind === "rig.restart") return t.openrig.confirm(w["rig.restart"](a.rig));
18178
+ if (a.kind === "daemon.stop" || a.kind === "daemon.restart" || a.kind === "kernel.stop" || a.kind === "kernel.restart") return t.openrig.confirm(w[a.kind]);
18179
+ return "";
18180
+ }
18181
+ function OpenRigScreen() {
18383
18182
  const tui = useTui();
18384
- const { client, d, projects, machines, t, g, selected, catalog, go } = tui;
18385
- const { columns, rows: height } = useScreenSize();
18386
- const wide = columns >= WIDE;
18387
- const left2 = wide ? Math.min(64, Math.max(44, Math.floor(columns * 0.42))) : columns;
18388
- const teams = useTeams(client) ?? [];
18389
- const recents = machines.flatMap((m) => m.recents.map((r) => ({ machine: m.id, cwd: r.cwd })));
18390
- const [memo2] = useState13(() => ({ folder: catalog.current.folder, machine: catalog.current.machine }));
18391
- const scopes = catalogScopes({ selected, memo: memo2, projects, recents, homeOnly: t.catalog.homeOnly });
18392
- const [scope, setScope] = useState13(() => initialScope(scopes, selected, catalog.current));
18393
- const [defs, setDefs] = useState13(null);
18394
- const [view, setView] = useState13("roles");
18395
- const [at, setAt] = useState13({ roles: null, templates: null });
18396
- const [problems, setProblems] = useState13(false);
18397
- const [silent, setSilent] = useState13(false);
18398
- const [reads, setReads] = useState13(0);
18399
- useEffect12(() => {
18400
- setDefs(null);
18401
- setSilent(false);
18402
- const requestId = `tui-catalog-${randomUUID4()}`;
18403
- const off = client.onMessage((m) => {
18404
- if (m.type !== "roles.listing" || m.requestId !== requestId) return;
18405
- off();
18406
- clearTimeout(timer2);
18407
- setDefs({ roles: m.roles, templates: m.templates, problems: m.problems, ...m.error ? { error: m.error } : {} });
18183
+ const { t, machines } = tui;
18184
+ const o = t.openrig;
18185
+ const [machineId, setMachineId] = useState14(tui.kept.rigMachine ?? null);
18186
+ const machine = machines.find((m) => m.id === machineId) ?? machines[0] ?? null;
18187
+ tui.kept.rigMachine = machine?.id ?? null;
18188
+ const rig = useOpenRig(tui.client, machine?.id ?? null);
18189
+ const state2 = rig.state;
18190
+ const rigs = listedRigs(state2);
18191
+ const [at, setAt] = useState14(0);
18192
+ const selectedId = useRef12(null);
18193
+ const [open, setOpen] = useState14(/* @__PURE__ */ new Set());
18194
+ const [confirm, setConfirm] = useState14(null);
18195
+ const [seats, setSeats] = useState14(null);
18196
+ const ids = rigs.map((r) => r.id).join(",");
18197
+ useEffect13(() => {
18198
+ setAt((a) => keepRig(rigs, a, selectedId.current));
18199
+ }, [ids]);
18200
+ const current2 = rigs[Math.min(at, rigs.length - 1)];
18201
+ selectedId.current = current2?.id ?? null;
18202
+ const run2 = (a) => {
18203
+ void rig.act(a).then((d) => {
18204
+ if (d.error === "cancelled") return;
18205
+ tui.toast(d.ok ? { text: o.done(d.commands.join(" && ")), tone: "ok" } : { text: o.failed(o.why[d.error ?? ""] ?? d.error ?? ""), tone: "error" });
18408
18206
  });
18409
- const timer2 = setTimeout(() => {
18410
- off();
18411
- setSilent(true);
18412
- }, ROLES_MS);
18413
- client.send({ type: "roles.list", requestId, ...scope.cwd ? { cwd: scope.cwd } : {}, ...scope.machine !== "local" ? { machineId: scope.machine } : {} });
18414
- return () => {
18415
- off();
18416
- clearTimeout(timer2);
18417
- };
18418
- }, [scope.cwd, scope.machine, reads]);
18419
- const roles = bySource(defs?.roles ?? []), templates = bySource(defs?.templates ?? []);
18420
- const order2 = useMemo7(
18421
- () => view === "roles" ? [...roles.repo.map((role2) => ({ kind: "role", key: role2.id, role: role2 })), ...roles.home.map((role2) => ({ kind: "role", key: role2.id, role: role2 }))] : [...templates.repo.map((template2) => ({ kind: "template", key: template2.id, template: template2 })), ...templates.home.map((template2) => ({ kind: "template", key: template2.id, template: template2 }))],
18422
- [defs, view]
18423
- );
18424
- const current2 = order2.find((r) => r.key === at[view]) ?? order2[0];
18425
- const role = current2?.kind === "role" ? current2.role : void 0;
18426
- const template = current2?.kind === "template" ? current2.template : void 0;
18427
- const project = projectOfFolder(projects, scope.cwd, scope.machine);
18428
- const remote = scope.machine !== "local";
18429
- const machine = machines.find((m) => m.id === scope.machine);
18430
- const machineLabel2 = machine?.label ?? scope.machine;
18431
- const reread = silent || defs?.error === "offline";
18432
- const launchable = !!current2 && !(remote && template);
18207
+ };
18208
+ const ask2 = (a) => {
18209
+ if (!state2) return;
18210
+ if (asksFirst(a)) setConfirm(confirmOf(state2, a));
18211
+ else run2(a);
18212
+ };
18213
+ const seatRig = seats ? rigs.find((r) => r.name === seats.rig) : void 0;
18214
+ const rows = seatRig ? seatRows(seatRig) : [];
18215
+ useEffect13(() => {
18216
+ if (seats && !seats.applying && !canReseat(seatRig)) setSeats(null);
18217
+ }, [seats, seatRig]);
18218
+ const apply = () => {
18219
+ if (!seats || !seatRig || !machine || seats.applying) return;
18220
+ const changed = changedCounts(rows, seats.counts);
18221
+ if (!Object.keys(changed).length) return;
18222
+ const adds = planOf(seatRig, rows, seats.counts).add.length > 0;
18223
+ setSeats((x) => x ? { ...x, applying: true } : x);
18224
+ void reseatRig(tui.client, machine.id, seatRig.name, changed, adds ? seats.delegation : void 0).then((r) => {
18225
+ if (!r.ok) {
18226
+ setSeats((x) => x ? { ...x, applying: false } : x);
18227
+ tui.toast({ text: t.rigSeats.why[r.error ?? ""] ?? r.error ?? "", tone: "error" });
18228
+ return;
18229
+ }
18230
+ setSeats(null);
18231
+ rig.refresh();
18232
+ const done = t.rigSeats.done(r.added.length, r.removed.length, r.links);
18233
+ tui.toast({ text: r.linksUnsupported ? `${done} \xB7 ${t.rigSeats.linksUnsupported}` : done, tone: r.linksUnsupported ? "info" : "ok" });
18234
+ });
18235
+ };
18433
18236
  useScreenKeys((input, key) => {
18434
- if (problems && (key.escape || input === "!")) {
18435
- setProblems(false);
18237
+ if (seats) {
18238
+ if (seats.applying) return true;
18239
+ if (key.escape) {
18240
+ setSeats(null);
18241
+ return true;
18242
+ }
18243
+ if (key.return) {
18244
+ apply();
18245
+ return true;
18246
+ }
18247
+ if (key.upArrow || input === "k") {
18248
+ setSeats((x) => x ? { ...x, at: step2(x.at, -1, rows.length) } : x);
18249
+ return true;
18250
+ }
18251
+ if (key.downArrow || input === "j") {
18252
+ setSeats((x) => x ? { ...x, at: step2(x.at, 1, rows.length) } : x);
18253
+ return true;
18254
+ }
18255
+ const role = rows[seats.at]?.role;
18256
+ if (role && (input === "+" || input === "=")) {
18257
+ setSeats((x) => x ? { ...x, counts: withCount(x.counts, role, 1) } : x);
18258
+ return true;
18259
+ }
18260
+ if (role && input === "-") {
18261
+ setSeats((x) => x ? { ...x, counts: withCount(x.counts, role, -1) } : x);
18262
+ return true;
18263
+ }
18264
+ if (key.leftArrow || key.rightArrow) {
18265
+ setSeats((x) => x ? { ...x, delegation: x.delegation === "pairs" ? "all" : "pairs" } : x);
18266
+ return true;
18267
+ }
18436
18268
  return true;
18437
18269
  }
18438
- if (input === "!") {
18439
- if (defs?.problems.length) setProblems(true);
18270
+ if (confirm) {
18271
+ if (input === "y") {
18272
+ run2(confirm.action);
18273
+ setConfirm(null);
18274
+ return true;
18275
+ }
18276
+ if (input === "n" || key.escape) {
18277
+ setConfirm(null);
18278
+ return true;
18279
+ }
18440
18280
  return true;
18441
18281
  }
18442
- if (input === "r" && reread) {
18443
- setReads((n) => n + 1);
18282
+ if (key.tab) {
18283
+ const i = Math.max(0, machines.findIndex((m) => m.id === machine?.id));
18284
+ setMachineId(machines[step2(i, key.shift ? -1 : 1, machines.length)]?.id ?? null);
18285
+ setOpen(/* @__PURE__ */ new Set());
18444
18286
  return true;
18445
18287
  }
18446
- if (key.leftArrow || key.rightArrow || input === "h" || input === "l") {
18447
- setView((v) => v === "roles" ? "templates" : "roles");
18448
- setProblems(false);
18288
+ if (input === "+") {
18289
+ if (machine) tui.go("rigLaunch");
18449
18290
  return true;
18450
18291
  }
18451
- if (input === "j" || key.downArrow || input === "k" || key.upArrow) {
18452
- const i = Math.max(0, order2.findIndex((r) => r.key === current2?.key));
18453
- const next2 = order2[stepIndex(order2.length, i, input === "j" || key.downArrow ? 1 : -1)];
18454
- if (next2) setAt((x) => ({ ...x, [view]: next2.key }));
18292
+ if (key.upArrow || input === "k") {
18293
+ setAt((i) => step2(i, -1, rigs.length));
18455
18294
  return true;
18456
18295
  }
18457
- if (input === "c") {
18458
- const next2 = scopes[stepIndex(scopes.length, scopes.findIndex((x) => x.cwd === scope.cwd && x.machine === scope.machine), 1)];
18459
- catalog.current.folder = next2.cwd;
18460
- catalog.current.machine = next2.machine;
18461
- setScope(next2);
18462
- setProblems(false);
18296
+ if (key.downArrow || input === "j") {
18297
+ setAt((i) => step2(i, 1, rigs.length));
18463
18298
  return true;
18464
18299
  }
18465
- if (key.return && launchable) {
18466
- catalog.current.launch = { cwd: scope.cwd, machine: scope.machine, project, ...role ? { role } : {}, ...template ? { template: template.id } : {} };
18467
- go("launch");
18300
+ if (key.return) {
18301
+ if (current2) setOpen((s) => {
18302
+ const n = new Set(s);
18303
+ if (n.has(current2.id)) n.delete(current2.id);
18304
+ else n.add(current2.id);
18305
+ return n;
18306
+ });
18307
+ return true;
18308
+ }
18309
+ const busy = rig.pending !== null;
18310
+ if (input === "D") {
18311
+ if (state2?.installed && !busy) ask2(daemonToggle(state2));
18312
+ return true;
18313
+ }
18314
+ if (input === "K") {
18315
+ if (daemonUp(state2) && !busy) ask2(kernelToggle(state2));
18316
+ return true;
18317
+ }
18318
+ if (input === "O") {
18319
+ const op = operatorSession(state2, tui.d.sessions, machine?.id ?? "local");
18320
+ if (op) {
18321
+ tui.select(op.id);
18322
+ tui.go("session");
18323
+ } else if (daemonUp(state2)) tui.toast({ text: o.operatorNotYet, tone: "info" });
18324
+ return true;
18325
+ }
18326
+ if (input === "s") {
18327
+ if (daemonUp(state2) && current2 && !busy) ask2(rigToggle(current2));
18328
+ return true;
18329
+ }
18330
+ if (input === "R") {
18331
+ if (daemonUp(state2) && current2 && !busy) ask2({ kind: "rig.restart", rig: current2.name });
18468
18332
  return true;
18469
18333
  }
18470
- if (input === "o" && current2 && !remote) {
18471
- const editor = (tui.env.VISUAL || tui.env.EDITOR || "vi").split(/\s+/).filter(Boolean);
18472
- tui.runInTerminal([...editor, role?.path ?? template.path]);
18334
+ if (input === "\xB1" || input === "=") {
18335
+ if (!busy && canReseat(current2)) setSeats({ rig: current2.name, counts: countsOf(seatRows(current2)), at: 0, delegation: "pairs", applying: false });
18336
+ else if (current2) tui.toast({ text: t.rigSeats.notRunning, tone: "info" });
18473
18337
  return true;
18474
18338
  }
18475
18339
  return false;
18476
18340
  });
18477
- const head = (key, text5) => ({ kind: "head", key, text: text5 });
18478
- const list3 = view === "roles" ? [
18479
- ...roles.repo.length ? [head("h:repo", t.catalog.repoRoles), ...order2.filter((r) => r.kind === "role" && r.role.source === "repo")] : [],
18480
- ...roles.home.length ? [head("h:home", t.catalog.homeRoles), ...order2.filter((r) => r.kind === "role" && r.role.source === "home")] : []
18481
- ] : [
18482
- ...templates.repo.length ? [head("h:repo", t.catalog.repoTeams), ...order2.filter((r) => r.kind === "template" && r.template.source === "repo")] : [],
18483
- ...templates.home.length ? [head("h:home", t.catalog.homeTeams), ...order2.filter((r) => r.kind === "template" && r.template.source === "home")] : []
18484
- ];
18485
- const issues = defs?.problems ?? [];
18486
- const listHeight = Math.max(3, height - 3 - 3 - 1 - (issues.length ? 2 : 0));
18487
- const inner = left2 - 4;
18488
- const roleById = (id) => defs?.roles.find((r) => r.id === id);
18489
- const autonomy = (r) => r?.autonomy ?? "ask";
18490
- const tabs = /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", children: ["roles", "templates"].map((v, i) => /* @__PURE__ */ jsxs15(Text17, { children: [
18491
- i ? /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: " \xB7 " }) : null,
18492
- /* @__PURE__ */ jsx21(Text17, { color: v === view ? C.coral : C.dim, bold: v === view, children: v === view ? t.catalog[v].toUpperCase() : t.catalog[v] })
18493
- ] }, v)) });
18494
- const listBox = /* @__PURE__ */ jsxs15(TitledBox, { title: scope.label, mark: machines.length > 1 ? { text: `\xB7 ${g.machine} ${machineLabel2}`, color: machine?.ready ? C.green : C.faint } : void 0, flexGrow: 1, children: [
18495
- tabs,
18496
- defs === null ? /* @__PURE__ */ jsx21(Text17, { color: silent ? C.yellow : C.faint, children: silent ? t.catalog.noAnswer : t.catalog.loading }) : defs.error ? /* @__PURE__ */ jsx21(Text17, { color: C.yellow, children: defs.error === "outdated" ? t.catalog.outdated(machineLabel2) : t.catalog.offline(machineLabel2) }) : list3.length === 0 ? /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: view === "roles" ? t.catalog.noRole : t.catalog.noTemplateHere }) : windowed(list3, current2?.key ?? null, listHeight).map((r) => {
18497
- if (r.kind === "head") return /* @__PURE__ */ jsxs15(Text17, { wrap: "truncate", color: C.faint, children: [
18498
- " ",
18499
- r.text
18500
- ] }, r.key);
18501
- const on = r.key === current2?.key;
18502
- return /* @__PURE__ */ jsxs15(Text17, { wrap: "truncate", backgroundColor: on ? C.sel : void 0, children: [
18503
- /* @__PURE__ */ jsx21(Text17, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
18504
- r.kind === "role" ? /* @__PURE__ */ jsxs15(Fragment6, { children: [
18505
- /* @__PURE__ */ jsxs15(Text17, { color: C.violet, children: [
18506
- "@",
18507
- fit(r.role.id, 8)
18341
+ 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], ["j k", t.keys.navigate], ["\u21B5", o.unfold], ["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]];
18342
+ const crumb = [state2?.version ? o.version(state2.version) : o.title, machine?.label].filter(Boolean).join(" \xB7 ");
18343
+ return /* @__PURE__ */ jsxs16(Frame, { crumb, crumbShort: o.title, keys, children: [
18344
+ !machine ? null : rig.error ? /* @__PURE__ */ jsx21(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx21(Text17, { color: C.yellow, children: rig.error === "offline" ? o.offline : o.outdated }) }) : !state2 ? /* @__PURE__ */ jsx21(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: o.loading }) }) : !state2.installed ? /* @__PURE__ */ jsx21(TitledBox, { title: o.title, mark: { text: machine.label, color: C.dim }, flexGrow: 1, children: /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: o.notInstalled }) }) : seats && seatRig ? /* @__PURE__ */ jsx21(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__ */ jsx21(Body, { state: state2, rigs, at, open, machineLabel: machine.label }),
18345
+ confirm ? /* @__PURE__ */ jsx21(ConfirmBox, { confirm }) : null,
18346
+ rig.pending ? /* @__PURE__ */ jsx21(Box16, { paddingX: 1, height: 1, children: /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", color: C.blue, children: o.running(confirmOf(state2 ?? EMPTY2, rig.pending).commands.join(" && ")) }) }) : null
18347
+ ] });
18348
+ }
18349
+ var EMPTY2 = { installed: true, daemon: { state: "unknown" }, kernel: { state: "unknown" }, rigs: [], at: "" };
18350
+ function Body({ state: state2, rigs, at, open, machineLabel: machineLabel2 }) {
18351
+ const { t } = useTui();
18352
+ const o = t.openrig;
18353
+ const up = daemonUp(state2);
18354
+ const kernelColor = state2.kernel.state === "ready" ? C.green : state2.kernel.state === "starting" ? C.blue : C.faint;
18355
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", flexGrow: 1, children: [
18356
+ /* @__PURE__ */ jsxs16(Box16, { children: [
18357
+ /* @__PURE__ */ jsxs16(TitledBox, { title: `${o.daemon} \xB7 ${machineLabel2}`, flexGrow: 1, width: "50%", children: [
18358
+ /* @__PURE__ */ jsxs16(Text17, { wrap: "truncate", children: [
18359
+ /* @__PURE__ */ jsxs16(Text17, { color: up ? C.green : C.faint, children: [
18360
+ up ? "\u25CF " : "\u25CB ",
18361
+ o.daemonState[state2.daemon.state] ?? state2.daemon.state
18508
18362
  ] }),
18509
- " ",
18510
- /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: fit(r.role.label, 12) }),
18511
- " ",
18512
- /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: fit(r.role.tool ? TOOL_NAME[r.role.tool] : "", 13) }),
18513
- " ",
18514
- /* @__PURE__ */ jsx21(Text17, { color: autonomyColor2(autonomy(r.role)), children: t.catalog.autonomyShort[autonomy(r.role)] })
18515
- ] }) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
18516
- /* @__PURE__ */ jsx21(Text17, { color: C.violet, children: g.team }),
18517
- " ",
18518
- /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: fit(r.template.name, 12) }),
18519
- " ",
18520
- /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: fit(r.template.seats.map((s) => `@${s.role}`).join(" "), Math.max(6, inner - 16)) })
18521
- ] })
18522
- ] }, r.key);
18523
- }),
18524
- issues.length ? /* @__PURE__ */ jsxs15(Fragment6, { children: [
18525
- /* @__PURE__ */ jsx21(Text17, { children: " " }),
18526
- /* @__PURE__ */ jsxs15(Text17, { wrap: "truncate", children: [
18527
- /* @__PURE__ */ jsx21(Text17, { color: C.yellow, children: t.catalog.ignored(issues.length) }),
18528
- " ",
18529
- /* @__PURE__ */ jsxs15(Text17, { color: C.faint, children: [
18530
- "! ",
18531
- t.catalog.see
18532
- ] })
18363
+ /* @__PURE__ */ jsxs16(Text17, { color: C.dim, children: [
18364
+ " ",
18365
+ up ? o.daemonMeta(state2.daemon.port, state2.daemon.pid) : state2.daemon.message ?? ""
18366
+ ] })
18367
+ ] }),
18368
+ /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", color: C.faint, children: up ? o.restoreHint : o.stoppedDaemon })
18369
+ ] }),
18370
+ /* @__PURE__ */ jsxs16(TitledBox, { title: o.kernel, flexGrow: 1, width: "50%", children: [
18371
+ /* @__PURE__ */ jsxs16(Text17, { wrap: "truncate", color: up ? kernelColor : C.faint, children: [
18372
+ state2.kernel.state === "ready" ? "\u25CF " : "\u25CB ",
18373
+ o.kernelState[state2.kernel.state] ?? state2.kernel.state
18374
+ ] }),
18375
+ /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", color: C.faint, children: o.kernelHint }),
18376
+ up && (state2.rigs.find((r) => r.name === "kernel")?.nodes ?? []).map((n) => /* @__PURE__ */ jsxs16(Text17, { wrap: "truncate", children: [
18377
+ /* @__PURE__ */ jsx21(Text17, { color: n.sessionStatus !== "running" ? C.faint : n.activity === "running" ? C.blue : C.green, children: "\u25C6 " }),
18378
+ /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: n.logicalId.padEnd(16) }),
18379
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: n.runtime.padEnd(13) }),
18380
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: n.sessionStatus === "running" ? o.activity(n.activity) : n.sessionStatus }),
18381
+ n.logicalId === "operator.agent" && /* @__PURE__ */ jsxs16(Text17, { color: C.faint, children: [
18382
+ " ",
18383
+ o.operatorHint
18384
+ ] })
18385
+ ] }, n.session))
18533
18386
  ] })
18534
- ] }) : null
18387
+ ] }),
18388
+ /* @__PURE__ */ jsx21(TitledBox, { title: `${o.rigs} \xB7 ${rigs.length}`, flexGrow: 1, children: !up ? /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: o.stoppedDaemon }) : !rigs.length ? /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: o.noRigs }) : rigs.map((r, i) => /* @__PURE__ */ jsx21(RigRow, { rig: r, selected: i === at, open: open.has(r.id) }, r.id)) })
18535
18389
  ] });
18536
- const field = (label, value) => /* @__PURE__ */ jsxs15(Text17, { wrap: "truncate", children: [
18537
- /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: fit(label, 11) }),
18538
- value
18390
+ }
18391
+ function RigRow({ rig, selected, open }) {
18392
+ const { t } = useTui();
18393
+ const o = t.openrig;
18394
+ const look = rigLook(rig), l = LOOK[look];
18395
+ const onPurpose = look === "stopped" && rig.nodes.length > 0 && rig.nodes.every((n) => n.stopped);
18396
+ const note = rig.attention > 0 ? { text: o.attention(rig.attention), color: C.yellow } : onPurpose ? { text: o.stoppedOnPurpose, color: C.dim } : null;
18397
+ const when = [rig.uptime ? o.since(rig.uptime) : "", rig.snapshot ? o.snapshot(rig.snapshot) : ""].filter(Boolean).join(" \xB7 ");
18398
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
18399
+ /* @__PURE__ */ jsx21(Box16, { backgroundColor: selected ? C.sel : void 0, children: /* @__PURE__ */ jsxs16(Text17, { wrap: "truncate", children: [
18400
+ /* @__PURE__ */ jsx21(Text17, { color: selected ? C.coral : C.faint, children: selected ? "\u258C" : " " }),
18401
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: open ? "\u25BE " : "\u25B8 " }),
18402
+ /* @__PURE__ */ jsx21(Text17, { color: C.fg, bold: true, children: rig.name.padEnd(16) }),
18403
+ /* @__PURE__ */ jsx21(Text17, { color: l.color(), children: ` ${l.glyph} ${o.look[look]}`.padEnd(18) }),
18404
+ /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: o.seats(rig.running, rig.seats).padEnd(7) }),
18405
+ note ? /* @__PURE__ */ jsx21(Text17, { color: note.color, children: note.text.padEnd(34) }) : /* @__PURE__ */ jsx21(Text17, { children: "".padEnd(34) }),
18406
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: when })
18407
+ ] }) }),
18408
+ open ? rig.nodes.map((n) => /* @__PURE__ */ jsxs16(Text17, { wrap: "truncate", children: [
18409
+ /* @__PURE__ */ jsx21(Text17, { color: seatBusy(n) ? C.blue : C.faint, children: " \u25C6 " }),
18410
+ /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: n.logicalId.padEnd(18) }),
18411
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: [n.runtime, n.model].filter(Boolean).join(" \xB7 ").padEnd(26) }),
18412
+ /* @__PURE__ */ jsx21(Text17, { color: n.activity === "running" ? C.blue : C.dim, children: o.activity(n.activity).padEnd(12) }),
18413
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: o.work(n.work) })
18414
+ ] }, n.logicalId)) : null
18539
18415
  ] });
18540
- const detail = problems ? /* @__PURE__ */ jsx21(TitledBox, { title: t.catalog.problems, color: C.yellow, titleColor: C.yellow, flexGrow: 1, children: issues.length === 0 ? /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: t.catalog.noProblem }) : issues.map((p) => /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
18541
- /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", color: C.fg, children: p.path }),
18542
- /* @__PURE__ */ jsx21(Text17, { wrap: "wrap", color: C.dim, children: p.reason })
18543
- ] }, p.path)) }) : role ? /* @__PURE__ */ jsxs15(Fragment6, { children: [
18544
- /* @__PURE__ */ jsxs15(TitledBox, { title: `@${role.id} \xB7 ${role.label} \xB7 ${role.source === "repo" ? t.catalog.repo : t.catalog.home}`, color: C.coral, titleColor: C.coral, children: [
18545
- field(t.catalog.tool, /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: role.tool ? TOOL_NAME[role.tool] : t.catalog.toolDefault })),
18546
- field(t.catalog.model, /* @__PURE__ */ jsxs15(Text17, { children: [
18547
- /* @__PURE__ */ jsx21(Text17, { color: role.model ? C.fg : C.faint, children: role.model ?? t.catalog.toolDefault }),
18548
- " ",
18549
- /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: t.catalog.effort }),
18550
- " ",
18551
- /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: role.effort ?? "\u2014" })
18552
- ] })),
18553
- field(t.catalog.autonomy, /* @__PURE__ */ jsx21(Text17, { color: autonomyColor2(autonomy(role)), children: t.ask.autonomy[autonomy(role)] })),
18554
- field(t.catalog.file, /* @__PURE__ */ jsxs15(Text17, { color: C.dim, children: [
18555
- remote ? `${t.catalog.onMachine(machineLabel2)} ` : "",
18556
- role.path
18557
- ] })),
18558
- role.shadows ? field("", /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: t.catalog.replaces(role.shadows) })) : null,
18559
- field(t.catalog.usedIn, (() => {
18560
- const xs = usedIn(role.id, defs?.templates ?? []);
18561
- return xs.length ? /* @__PURE__ */ jsx21(Text17, { children: xs.map((x) => /* @__PURE__ */ jsxs15(Text17, { color: C.violet, children: [
18562
- g.team,
18416
+ }
18417
+ function ConfirmBox({ confirm }) {
18418
+ const { t } = useTui();
18419
+ const o = t.openrig;
18420
+ return /* @__PURE__ */ jsxs16(TitledBox, { title: o.confirmTitle, color: C.coral, titleColor: C.coral, titleBold: true, double: true, children: [
18421
+ /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", color: C.fg, bold: true, children: question(t, confirm.action) }),
18422
+ /* @__PURE__ */ jsx21(Text17, { wrap: "wrap", color: confirm.busy.length ? C.yellow : C.dim, children: o.busy(confirm.busy.join(", "), confirm.busy.length) }),
18423
+ confirm.commands.map((c) => /* @__PURE__ */ jsx21(Text17, { wrap: "truncate", color: C.faint, children: c }, c)),
18424
+ /* @__PURE__ */ jsxs16(Text17, { children: [
18425
+ /* @__PURE__ */ jsx21(Text17, { color: C.fg, bold: true, children: " y " }),
18426
+ /* @__PURE__ */ jsxs16(Text17, { color: C.dim, children: [
18427
+ o.yes,
18428
+ " "
18429
+ ] }),
18430
+ /* @__PURE__ */ jsx21(Text17, { color: C.fg, bold: true, children: " n " }),
18431
+ /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: o.no })
18432
+ ] })
18433
+ ] });
18434
+ }
18435
+ function operatorSession(state2, sessions, machine) {
18436
+ const seat = state2?.rigs.find((r) => r.name === "kernel")?.nodes.find((n) => n.logicalId === "operator.agent")?.session;
18437
+ return seat ? sessions.find((x) => x.rig?.seat === seat && x.state !== "exited" && (x.machineId ?? "local") === machine) : void 0;
18438
+ }
18439
+
18440
+ // src/tui/screens/RigLaunch.tsx
18441
+ import { useEffect as useEffect14, useMemo as useMemo6, useRef as useRef13, useState as useState15 } from "react";
18442
+ import { Box as Box17, Text as Text18, useInput as useInput7 } from "ink";
18443
+
18444
+ // src/tui/model/rigLaunch.ts
18445
+ var RUNTIMES = ["claude-code", "codex"];
18446
+ var orderSpecs = (specs) => [...specs].sort((a, b2) => a.source === b2.source ? a.name.localeCompare(b2.name) : a.source === "user" ? -1 : b2.source === "user" ? 1 : a.source.localeCompare(b2.source));
18447
+ var rolesOf = (spec) => (spec?.pods ?? []).flatMap((p) => p.members.map((m) => ({ role: `${p.id}.${m.id}`, pod: p.id, member: m.id, agent: m.agent, runtime: m.runtime, ...m.model ? { model: m.model } : {}, ...m.policy ? { policy: m.policy } : {} })));
18448
+ var emptyForm = (spec, cwd = "") => ({ spec, cwd, name: null, seats: {}, runtimes: {}, models: {}, delegation: "pairs", save: false, policy: null, policies: {}, nonInterruptive: false });
18449
+ var withSpec = (f, spec) => ({ ...f, spec, seats: {}, runtimes: {}, models: {}, policies: {} });
18450
+ function withPolicy(f, by) {
18451
+ const all2 = [null, ...RIG_POLICIES];
18452
+ return { ...f, policy: all2[(all2.indexOf(f.policy) + by + all2.length) % all2.length] };
18453
+ }
18454
+ function withRolePolicy(f, role) {
18455
+ const all2 = [void 0, ...RIG_POLICIES];
18456
+ const next3 = all2[(all2.indexOf(f.policies[role]) + 1) % all2.length];
18457
+ const { [role]: _gone, ...rest } = f.policies;
18458
+ return { ...f, policies: next3 ? { ...rest, [role]: next3 } : rest };
18459
+ }
18460
+ var yoloChosen = (f, roles) => f.policy === "yolo" || roles.some((r) => f.policies[r.role] === "yolo");
18461
+ var seatsOf = (f, role) => f.seats[role] ?? 1;
18462
+ var withSeats = (f, role, by) => ({ ...f, seats: { ...f.seats, [role]: Math.max(0, Math.min(9, seatsOf(f, role) + by)) } });
18463
+ var runtimeOf = (f, r) => f.runtimes[r.role] ?? r.runtime;
18464
+ function withRuntime(f, r, by) {
18465
+ const cur = runtimeOf(f, r), i = RUNTIMES.indexOf(cur);
18466
+ const next3 = RUNTIMES[((i < 0 ? by > 0 ? -1 : 0 : i) + by + RUNTIMES.length) % RUNTIMES.length];
18467
+ const { [r.role]: _gone, ...rest } = f.runtimes;
18468
+ return { ...f, runtimes: next3 === r.runtime ? rest : { ...rest, [r.role]: next3 } };
18469
+ }
18470
+ var modelOf = (f, r) => f.models[r.role] ?? r.model;
18471
+ function withModel(f, r, model) {
18472
+ const { [r.role]: _gone, ...rest } = f.models;
18473
+ const m = model.trim();
18474
+ return { ...f, models: m && m !== r.model ? { ...rest, [r.role]: m } : rest };
18475
+ }
18476
+ var totalSeats = (f, roles) => roles.reduce((n, r) => n + seatsOf(f, r.role), 0);
18477
+ var scaled = (f, roles) => roles.some((r) => seatsOf(f, r.role) > 1);
18478
+ var nameOf = (f) => f.name?.trim() || f.spec || "";
18479
+ function missingOf2(f, roles) {
18480
+ if (!f.spec) return "spec";
18481
+ if (!f.cwd.trim()) return "cwd";
18482
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(nameOf(f))) return "name";
18483
+ if (totalSeats(f, roles) === 0) return "seats";
18484
+ return null;
18485
+ }
18486
+ function launchOf(f, roles, plan) {
18487
+ const seats = Object.fromEntries(roles.filter((r) => seatsOf(f, r.role) !== 1).map((r) => [r.role, seatsOf(f, r.role)]));
18488
+ const runtimes = Object.fromEntries(roles.filter((r) => f.runtimes[r.role]).map((r) => [r.role, f.runtimes[r.role]]));
18489
+ const models = Object.fromEntries(roles.filter((r) => f.models[r.role]).map((r) => [r.role, f.models[r.role]]));
18490
+ const policies = Object.fromEntries(roles.filter((r) => f.policies[r.role]).map((r) => [r.role, f.policies[r.role]]));
18491
+ return {
18492
+ spec: f.spec,
18493
+ cwd: f.cwd.trim(),
18494
+ name: nameOf(f),
18495
+ ...Object.keys(seats).length ? { seats } : {},
18496
+ ...Object.keys(runtimes).length ? { runtimes } : {},
18497
+ ...Object.keys(models).length ? { models } : {},
18498
+ ...f.policy ? { policy: f.policy } : {},
18499
+ ...Object.keys(policies).length ? { policies } : {},
18500
+ ...f.nonInterruptive && yoloChosen(f, roles) ? { nonInterruptive: true } : {},
18501
+ ...scaled(f, roles) ? { delegation: f.delegation } : {},
18502
+ ...f.save ? { save: true } : {},
18503
+ ...plan ? { plan: true } : {}
18504
+ };
18505
+ }
18506
+ var folderChoices = (projects, machine) => (projects?.projects ?? []).flatMap((p) => p.repos.filter((r) => r.machine === machine).map((r) => ({ project: p.name, path: r.path })));
18507
+ function nextFolder(choices2, cwd, by) {
18508
+ if (!choices2.length) return cwd;
18509
+ const i = choices2.findIndex((c) => c.path === cwd);
18510
+ return choices2[i < 0 ? by > 0 ? 0 : choices2.length - 1 : (i + by + choices2.length) % choices2.length].path;
18511
+ }
18512
+ function warningLines(warnings) {
18513
+ const groups = /* @__PURE__ */ new Map();
18514
+ for (const w of warnings) {
18515
+ const m = /^([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+):\s*(.+)$/.exec(w.trim());
18516
+ const key = m ? m[2] : w.trim(), who = m ? [m[1]] : [];
18517
+ groups.set(key, [...groups.get(key) ?? [], ...who]);
18518
+ }
18519
+ return [...groups].map(([text5, seats]) => ({ text: text5, seats }));
18520
+ }
18521
+ var fieldsOf = (roles, yolo = false) => ["spec", "cwd", "name", ...roles.map((_, i) => `role:${i}`), "policy", ...yolo ? ["nonInterruptive"] : [], "delegation", "save"];
18522
+ var roleAt = (field) => field.startsWith("role:") ? Number(field.slice(5)) : null;
18523
+ function moveField(fields, cur, by) {
18524
+ const i = fields.indexOf(cur);
18525
+ return fields[i < 0 ? 0 : (i + by + fields.length) % fields.length];
18526
+ }
18527
+ var isText = (field) => field === "cwd" || field === "name";
18528
+
18529
+ // src/tui/screens/RigLaunch.tsx
18530
+ import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
18531
+ var SPEC_ROWS = 7;
18532
+ var specLine = (s) => {
18533
+ const members = s.pods.flatMap((p) => p.members.map((m) => m.id));
18534
+ return `${members.slice(0, 4).join(" + ")}${members.length > 4 ? "\u2026" : ""} \xB7 ${members.length}`;
18535
+ };
18536
+ function RigLaunchScreen() {
18537
+ const tui = useTui();
18538
+ const { t, g, machines, projects, client, toast, go, small } = tui;
18539
+ const l = t.rigLaunch;
18540
+ const machine = machines.find((m) => m.id === tui.kept.rigMachine) ?? machines[0] ?? null;
18541
+ const machineId = machine?.id ?? null;
18542
+ const specsView = useRigSpecs(client, machineId);
18543
+ const specs = useMemo6(() => orderSpecs(specsView.specs), [specsView.specs]);
18544
+ const folders = useMemo6(() => folderChoices(projects, machineId), [projects, machineId]);
18545
+ const [form, setFormState] = useState15(() => emptyForm(null, folders[0]?.path ?? ""));
18546
+ const [field, setField] = useState15("spec");
18547
+ const [modelAt, setModelAt] = useState15(null);
18548
+ const [modelDraft, setModelDraft] = useState15("");
18549
+ const [busy, setBusy] = useState15(null);
18550
+ const [result, setResult] = useState15(null);
18551
+ const alive = useRef13(true);
18552
+ useEffect14(() => () => {
18553
+ alive.current = false;
18554
+ }, []);
18555
+ useEditing(true);
18556
+ const spec = specs.find((s) => s.name === form.spec);
18557
+ const roles = rolesOf(spec);
18558
+ const yolo = yoloChosen(form, roles);
18559
+ const fields = fieldsOf(roles, yolo);
18560
+ useEffect14(() => {
18561
+ if (!form.spec && specs[0]) setFormState((f) => withSpec(f, specs[0].name));
18562
+ }, [specs]);
18563
+ useEffect14(() => {
18564
+ if (!form.cwd && folders[0]) setFormState((f) => ({ ...f, cwd: folders[0].path }));
18565
+ }, [folders]);
18566
+ const setForm = (fn) => {
18567
+ setFormState(fn);
18568
+ setResult(null);
18569
+ };
18570
+ const submit = (plan) => {
18571
+ if (busy || !machineId) return;
18572
+ const missing = missingOf2(form, roles);
18573
+ if (missing) {
18574
+ toast({ text: l.missing[missing], tone: "info" });
18575
+ return;
18576
+ }
18577
+ setBusy(plan ? "check" : "launch");
18578
+ setResult(null);
18579
+ const launch2 = launchOf(form, roles, plan);
18580
+ void launchRig(client, machineId, launch2).then((r) => {
18581
+ if (!alive.current) return;
18582
+ setBusy(null);
18583
+ setResult(r);
18584
+ if (!r.ok) {
18585
+ toast({ text: l.why[r.error ?? ""] ?? r.error ?? "", tone: "error" });
18586
+ return;
18587
+ }
18588
+ if (!plan) {
18589
+ toast({ text: l.launched(launch2.name, r.seats ?? totalSeats(form, roles)), tone: "ok" });
18590
+ go("openrig");
18591
+ }
18592
+ });
18593
+ };
18594
+ const role = roleAt(field) !== null ? roles[roleAt(field)] : void 0;
18595
+ useInput7((input, key) => {
18596
+ if (small && !key.escape) return;
18597
+ if (busy === "launch") return;
18598
+ if (modelAt !== null) {
18599
+ if (key.tab) {
18600
+ const r = roles[modelAt];
18601
+ if (r) setForm((f) => withModel(f, r, modelDraft));
18602
+ setModelAt(null);
18603
+ }
18604
+ return;
18605
+ }
18606
+ if (key.escape) {
18607
+ go("openrig");
18608
+ return;
18609
+ }
18610
+ if (key.ctrl && input === "s") {
18611
+ submit(false);
18612
+ return;
18613
+ }
18614
+ if (key.ctrl && input === "p") {
18615
+ submit(true);
18616
+ return;
18617
+ }
18618
+ if (key.tab || key.downArrow) {
18619
+ setField((f) => moveField(fields, f, key.shift ? -1 : 1));
18620
+ return;
18621
+ }
18622
+ if (key.upArrow) {
18623
+ setField((f) => moveField(fields, f, -1));
18624
+ return;
18625
+ }
18626
+ const by = key.rightArrow ? 1 : key.leftArrow ? -1 : 0;
18627
+ if (field === "cwd" && by) {
18628
+ setForm((f) => ({ ...f, cwd: nextFolder(folders, f.cwd, by) }));
18629
+ return;
18630
+ }
18631
+ if (isText(field)) return;
18632
+ if (input === "p") {
18633
+ submit(true);
18634
+ return;
18635
+ }
18636
+ if (field === "spec" && by && specs.length) {
18637
+ const i = Math.max(0, specs.findIndex((s) => s.name === form.spec));
18638
+ setForm((f) => withSpec(f, specs[(i + by + specs.length) % specs.length].name));
18639
+ return;
18640
+ }
18641
+ if (role) {
18642
+ if (by) setForm((f) => withRuntime(f, role, by));
18643
+ else if (input === "+" || input === "=") setForm((f) => withSeats(f, role.role, 1));
18644
+ else if (input === "-") setForm((f) => withSeats(f, role.role, -1));
18645
+ else if (input === "m") {
18646
+ setModelAt(roleAt(field));
18647
+ setModelDraft(modelOf(form, role) ?? "");
18648
+ } else if (input === "o") setForm((f) => withRolePolicy(f, role.role));
18649
+ return;
18650
+ }
18651
+ if (field === "policy" && by) {
18652
+ setForm((f) => withPolicy(f, by));
18653
+ return;
18654
+ }
18655
+ if (field === "nonInterruptive" && (by || input === " " || key.return)) {
18656
+ setForm((f) => ({ ...f, nonInterruptive: !f.nonInterruptive }));
18657
+ return;
18658
+ }
18659
+ if (field === "delegation" && (by || input === " ")) {
18660
+ setForm((f) => ({ ...f, delegation: f.delegation === "pairs" ? "all" : "pairs" }));
18661
+ return;
18662
+ }
18663
+ if (field === "save" && (by || input === " " || key.return)) {
18664
+ setForm((f) => ({ ...f, save: !f.save }));
18665
+ return;
18666
+ }
18667
+ });
18668
+ const row2 = (f, label, value, width = 9) => /* @__PURE__ */ jsxs17(Box17, { flexShrink: 0, backgroundColor: field === f ? C.sel : void 0, children: [
18669
+ /* @__PURE__ */ jsx22(Text18, { color: field === f ? C.coral : C.faint, children: field === f ? "\u258C" : " " }),
18670
+ /* @__PURE__ */ jsx22(Box17, { width, flexShrink: 0, children: /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: label }) }),
18671
+ /* @__PURE__ */ jsx22(Box17, { flexGrow: 1, overflow: "hidden", children: value })
18672
+ ] }, f);
18673
+ const chosen = (on, text5) => on ? /* @__PURE__ */ jsx22(Text18, { color: C.fg, bold: true, children: `${g.prev} ${text5} ${g.next}` }) : /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: text5 });
18674
+ const at = Math.max(0, specs.findIndex((s) => s.name === form.spec));
18675
+ const from = Math.max(0, Math.min(at - Math.floor(SPEC_ROWS / 2), specs.length - SPEC_ROWS));
18676
+ const folderOf2 = folders.find((f) => f.path === form.cwd);
18677
+ const seats = totalSeats(form, roles);
18678
+ const stopped = specsView.error === "daemon_stopped";
18679
+ const keys = modelAt !== null ? [["\u21B5", t.keys.apply], ["esc", t.keys.cancel]] : [["tab \u2191 \u2193", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], ["- +", l.count], ["m", l.modelKey], ["o", l.policyKey], ["p", l.check], ["ctrl+s", l.launch(seats)], ["esc", t.keys.cancel]];
18680
+ return /* @__PURE__ */ jsx22(Frame, { crumb: `${l.title} \xB7 ${machine?.label ?? "-"}`, crumbShort: l.title, keys, children: /* @__PURE__ */ jsxs17(Box17, { flexGrow: 1, children: [
18681
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", width: "55%", flexShrink: 0, children: [
18682
+ /* @__PURE__ */ jsx22(TitledBox, { title: l.spec, color: field === "spec" ? C.coral : C.line, titleColor: field === "spec" ? C.coral : C.dim, flexShrink: 0, height: (specs.length ? Math.min(SPEC_ROWS, specs.length) : 2) + 2, children: specsView.status === "loading" ? /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: l.loading }) : stopped ? /* @__PURE__ */ jsx22(Text18, { color: C.yellow, children: l.daemonStopped }) : specsView.error ? /* @__PURE__ */ jsx22(Text18, { color: C.yellow, children: l.why[specsView.error] ?? specsView.error }) : !specs.length ? /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: l.noSpecs }) : specs.slice(from, from + SPEC_ROWS).map((s) => {
18683
+ const on = s.name === form.spec;
18684
+ return /* @__PURE__ */ jsx22(Box17, { flexShrink: 0, backgroundColor: on && field === "spec" ? C.sel : void 0, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
18685
+ /* @__PURE__ */ jsxs17(Text18, { color: on ? C.coral : C.dim, children: [
18686
+ on ? g.radioOn : g.radioOff,
18687
+ " "
18688
+ ] }),
18689
+ /* @__PURE__ */ jsx22(Text18, { color: on ? C.fg : C.dim, bold: on, children: s.name.padEnd(16) }),
18690
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: specLine(s).padEnd(24) }),
18691
+ /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: l.source[s.source] ?? s.source })
18692
+ ] }) }, `${s.source}:${s.name}`);
18693
+ }) }),
18694
+ /* @__PURE__ */ jsxs17(TitledBox, { title: l.where, flexShrink: 0, height: folderOf2 ? 5 : 4, children: [
18695
+ row2("cwd", l.folder, /* @__PURE__ */ jsx22(Box17, { children: /* @__PURE__ */ jsx22(TextInput, { value: form.cwd, onChange: (v) => setForm((f) => ({ ...f, cwd: v })), active: field === "cwd" && modelAt === null, placeholder: l.folderHint }) })),
18696
+ folderOf2 ? /* @__PURE__ */ jsx22(Box17, { flexShrink: 0, children: /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", color: C.faint, children: [
18697
+ " ",
18698
+ g.prev,
18563
18699
  " ",
18564
- x.name,
18565
- " "
18566
- ] }, x.id)) }) : /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: t.catalog.noTemplate });
18567
- })()),
18568
- field(t.catalog.heldBy, (() => {
18569
- const xs = heldBy(role.id, d.sessions, teams, project, (s) => projects ? projectOf(s, projects) : null);
18570
- return xs.length ? /* @__PURE__ */ jsx21(Text17, { children: xs.map((x) => /* @__PURE__ */ jsxs15(Text17, { children: [
18571
- /* @__PURE__ */ jsx21(Text17, { color: C.fg, bold: true, children: x.name }),
18572
- x.team ? /* @__PURE__ */ jsxs15(Text17, { color: C.dim, children: [
18700
+ l.projectFolder(folderOf2.project),
18701
+ " ",
18702
+ g.next
18703
+ ] }) }) : null,
18704
+ row2("name", l.name, /* @__PURE__ */ jsx22(Box17, { children: /* @__PURE__ */ jsx22(TextInput, { value: form.name ?? "", onChange: (v) => setForm((f) => ({ ...f, name: v })), active: field === "name" && modelAt === null, placeholder: form.spec ?? "" }) }))
18705
+ ] }),
18706
+ /* @__PURE__ */ jsxs17(TitledBox, { title: l.roles, flexGrow: 1, flexShrink: 1, children: [
18707
+ roles.length ? /* @__PURE__ */ jsx22(Box17, { flexShrink: 0, children: /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: C.faint, children: l.rolesHint }) }) : null,
18708
+ roles.map((r, i) => {
18709
+ const f = `role:${i}`, on = field === f, n = seatsOf(form, r.role);
18710
+ const runtime = runtimeOf(form, r), model = modelOf(form, r);
18711
+ return /* @__PURE__ */ jsxs17(Box17, { flexShrink: 0, backgroundColor: on ? C.sel : void 0, children: [
18712
+ /* @__PURE__ */ jsx22(Text18, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
18713
+ /* @__PURE__ */ jsx22(Box17, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: C.fg, bold: true, children: r.role }) }),
18714
+ /* @__PURE__ */ jsx22(Box17, { width: 16, flexShrink: 0, children: /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", children: chosen(on, runtime) }) }),
18715
+ /* @__PURE__ */ jsx22(Box17, { width: 7, flexShrink: 0, children: /* @__PURE__ */ jsxs17(Text18, { children: [
18716
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: "\u2212 " }),
18717
+ /* @__PURE__ */ jsx22(Text18, { color: n === 1 ? C.fg : C.green, bold: true, children: n }),
18718
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: " +" })
18719
+ ] }) }),
18720
+ /* @__PURE__ */ jsx22(Box17, { width: 13, flexShrink: 0, children: /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: form.policies[r.role] === "yolo" ? C.red : form.policies[r.role] ? C.fg : C.faint, children: form.policies[r.role] ? l.policyOf[form.policies[r.role]] : l.asRig }) }),
18721
+ /* @__PURE__ */ jsx22(Box17, { flexGrow: 1, overflow: "hidden", children: modelAt === i ? /* @__PURE__ */ jsx22(TextInput, { value: modelDraft, onChange: setModelDraft, active: true, onSubmit: (v) => {
18722
+ setForm((x) => withModel(x, r, v));
18723
+ setModelAt(null);
18724
+ }, onEscape: () => setModelAt(null), placeholder: l.defaultModel }) : /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: model ? C.fg : C.faint, children: model ?? l.defaultModel }) })
18725
+ ] }, r.role);
18726
+ })
18727
+ ] }),
18728
+ /* @__PURE__ */ jsxs17(TitledBox, { title: l.permissions, flexShrink: 0, children: [
18729
+ row2("policy", "", /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
18730
+ chosen(field === "policy" || form.policy !== null, form.policy === null ? l.specPolicy(spec?.policy ?? l.noSpecPolicy) : l.policyOf[form.policy]),
18731
+ /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: ` ${[null, ...RIG_POLICIES].indexOf(form.policy) + 1}/${RIG_POLICIES.length + 1}` })
18732
+ ] }), 1),
18733
+ /* @__PURE__ */ jsx22(Box17, { flexShrink: 0, paddingLeft: 3, children: /* @__PURE__ */ jsx22(Text18, { wrap: "wrap", color: (form.policy ?? spec?.policy?.replace(/^builtin:/, "")) === "yolo" ? C.red : C.faint, children: l.policyHint[form.policy ?? spec?.policy?.replace(/^builtin:/, "") ?? "none"] ?? l.policyHint.none }) }),
18734
+ yolo ? row2("nonInterruptive", "", /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
18735
+ /* @__PURE__ */ jsx22(Text18, { color: form.nonInterruptive ? C.red : C.faint, children: form.nonInterruptive ? g.boxOn : g.boxOff }),
18736
+ /* @__PURE__ */ jsxs17(Text18, { color: C.dim, children: [
18573
18737
  " ",
18574
- x.team
18575
- ] }) : null,
18576
- " "
18577
- ] }, x.name)) }) : /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: t.catalog.nobody });
18578
- })())
18738
+ l.nonInterruptive
18739
+ ] })
18740
+ ] }), 1) : null
18741
+ ] }),
18742
+ /* @__PURE__ */ jsxs17(TitledBox, { title: l.delegation, flexShrink: 0, height: 4, children: [
18743
+ row2("delegation", "", /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", children: scaled(form, roles) ? /* @__PURE__ */ jsxs17(Fragment6, { children: [
18744
+ chosen(form.delegation === "pairs", l.pairs),
18745
+ /* @__PURE__ */ jsxs17(Text18, { color: C.faint, children: [
18746
+ " ",
18747
+ l.pairsHint,
18748
+ " "
18749
+ ] }),
18750
+ chosen(form.delegation === "all", l.all)
18751
+ ] }) : /* @__PURE__ */ jsx22(Text18, { color: C.faint, children: l.notScaled }) }), 1),
18752
+ row2("save", "", /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
18753
+ /* @__PURE__ */ jsx22(Text18, { color: form.save ? C.green : C.faint, children: form.save ? g.boxOn : g.boxOff }),
18754
+ /* @__PURE__ */ jsxs17(Text18, { color: C.dim, children: [
18755
+ " ",
18756
+ l.save,
18757
+ form.save ? ` : ${nameOf(form)}` : ""
18758
+ ] })
18759
+ ] }), 1)
18760
+ ] })
18579
18761
  ] }),
18580
- /* @__PURE__ */ jsx21(TitledBox, { title: t.catalog.instruction(role.prompt.length, ROLE_PROMPT_MAX), flexGrow: 1, children: /* @__PURE__ */ jsx21(Instruction, { text: role.prompt, empty: t.catalog.noInstruction }) })
18581
- ] }) : template ? /* @__PURE__ */ jsxs15(TitledBox, { title: `${g.team} ${template.name} \xB7 ${t.catalog.seats(template.seats.length)} \xB7 ${template.source === "repo" ? t.catalog.repo : t.catalog.home}`, color: C.coral, titleColor: C.coral, flexGrow: 1, children: [
18582
- template.seats.map((s) => {
18583
- const r = roleById(s.role);
18584
- const tool = s.tool ?? r?.tool;
18585
- return /* @__PURE__ */ jsxs15(Text17, { wrap: "truncate", children: [
18586
- /* @__PURE__ */ jsxs15(Text17, { color: C.violet, children: [
18587
- "@",
18588
- fit(s.role, 8)
18589
- ] }),
18590
- " ",
18591
- /* @__PURE__ */ jsx21(Text17, { color: C.fg, children: fit(r?.label ?? "?", 12) }),
18592
- " ",
18593
- /* @__PURE__ */ jsx21(Text17, { color: s.tool ? C.blue : C.dim, children: tool ? TOOL_NAME[tool] : t.catalog.toolDefault }),
18594
- s.model ? /* @__PURE__ */ jsxs15(Text17, { color: C.blue, children: [
18595
- " \xB7 ",
18596
- s.model
18597
- ] }) : r?.model ? /* @__PURE__ */ jsxs15(Text17, { color: C.dim, children: [
18598
- " \xB7 ",
18599
- r.model
18600
- ] }) : null,
18601
- " ",
18602
- /* @__PURE__ */ jsx21(Text17, { color: autonomyColor2(autonomy(r)), children: t.catalog.autonomyShort[autonomy(r)] })
18603
- ] }, s.role);
18604
- }),
18605
- /* @__PURE__ */ jsx21(Text17, { children: " " }),
18606
- (() => {
18607
- const auto = template.seats.filter((s) => roleById(s.role)?.autonomy === "auto").map((s) => `@${s.role}`);
18608
- return auto.length ? /* @__PURE__ */ jsxs15(Text17, { color: C.yellow, wrap: "truncate", children: [
18609
- "\u26A0 ",
18610
- t.ask.autoWarning(auto.join(", "))
18611
- ] }) : null;
18612
- })(),
18613
- field(t.catalog.file, /* @__PURE__ */ jsxs15(Text17, { color: C.dim, children: [
18614
- remote ? `${t.catalog.onMachine(machineLabel2)} ` : "",
18615
- template.path
18616
- ] })),
18617
- field(t.catalog.teams, (() => {
18618
- const xs = teams.filter((x) => x.template === template.id && x.state !== "dissolved");
18619
- return xs.length ? /* @__PURE__ */ jsx21(Text17, { children: xs.map((x) => /* @__PURE__ */ jsxs15(Text17, { children: [
18620
- /* @__PURE__ */ jsxs15(Text17, { color: C.violet, children: [
18621
- g.team,
18622
- " ",
18623
- x.name
18624
- ] }),
18625
- " ",
18626
- /* @__PURE__ */ jsx21(Text17, { color: C.dim, children: x.state === "active" ? t.catalog.active : t.catalog.resting }),
18627
- " "
18628
- ] }, x.id)) }) : /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: t.catalog.noTeam });
18629
- })())
18630
- ] }) : /* @__PURE__ */ jsx21(TitledBox, { title: t.tabs.teams, flexGrow: 1, children: /* @__PURE__ */ jsx21(Text17, { color: C.faint, children: defs === null ? silent ? "" : t.catalog.loading : defs.error ? "" : view === "roles" ? t.catalog.noRole : t.catalog.noTemplateHere }) });
18631
- const keys = [
18632
- ["j k", t.keys.navigate],
18633
- ["\u2190 \u2192", t.keys.rolesTemplates],
18634
- ["c", t.keys.folder],
18635
- ...reread ? [["r", t.keys.reread]] : [],
18636
- ...launchable ? [["\u21B5", view === "roles" ? t.keys.launchRole : t.keys.launchTeam]] : [],
18637
- ...current2 && !remote ? [["o", t.keys.openFile]] : [],
18638
- ...issues.length ? [["!", t.keys.ignored]] : [],
18639
- ["esc", t.keys.back]
18640
- ];
18641
- return /* @__PURE__ */ jsx21(Frame, { crumb: t.tabs.teams, keys, children: /* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, flexDirection: wide ? "row" : "column", children: [
18642
- /* @__PURE__ */ jsx21(Box16, { flexDirection: "column", width: wide ? left2 : void 0, flexShrink: 0, flexGrow: wide ? 0 : 1, children: listBox }),
18643
- /* @__PURE__ */ jsx21(Box16, { flexDirection: "column", flexGrow: 1, children: detail })
18762
+ /* @__PURE__ */ jsx22(Preview, { busy, result, seats })
18644
18763
  ] }) });
18645
18764
  }
18765
+ function Preview({ busy, result, seats }) {
18766
+ const { t } = useTui();
18767
+ const l = t.rigLaunch;
18768
+ const check = result?.check;
18769
+ return /* @__PURE__ */ jsx22(TitledBox, { title: l.preview, flexGrow: 1, color: C.coral, titleColor: C.coral, children: busy ? /* @__PURE__ */ jsx22(Text18, { color: C.blue, children: busy === "check" ? l.checking : l.launching }) : !result ? /* @__PURE__ */ jsx22(Text18, { wrap: "wrap", color: C.faint, children: l.noCheck }) : /* @__PURE__ */ jsxs17(Fragment6, { children: [
18770
+ check ? check.status === "planned" && !check.errors.length ? /* @__PURE__ */ jsx22(Text18, { color: C.green, bold: true, children: l.passes(result.seats ?? seats) }) : /* @__PURE__ */ jsx22(Text18, { color: check.errors.length ? C.red : C.yellow, bold: true, children: l.planStatus(check.status) }) : null,
18771
+ !result.ok ? /* @__PURE__ */ jsx22(Text18, { wrap: "wrap", color: C.red, children: l.why[result.error ?? ""] ?? result.error }) : null,
18772
+ check?.errors.map((e, i) => /* @__PURE__ */ jsx22(Text18, { wrap: "wrap", color: C.red, children: e }, `e${i}`)),
18773
+ warningLines(check?.warnings ?? []).map((w, i) => /* @__PURE__ */ jsx22(Text18, { wrap: "wrap", color: C.yellow, children: l.warning(w.text, w.seats.join(", ")) }, `w${i}`)),
18774
+ result.commands.map((c) => /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: C.faint, children: c }, c)),
18775
+ result.yaml ? /* @__PURE__ */ jsx22(Box17, { marginTop: 1, flexDirection: "column", children: result.yaml.split("\n").map((line, i) => /* @__PURE__ */ jsx22(YamlLine, { line }, i)) }) : null
18776
+ ] }) });
18777
+ }
18778
+ function YamlLine({ line }) {
18779
+ const m = /^(\s*-?\s*)([A-Za-z0-9_.-]+:)(.*)$/.exec(line);
18780
+ if (line.trim().startsWith("#")) return /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: C.faint, children: line || " " });
18781
+ if (!m) return /* @__PURE__ */ jsx22(Text18, { wrap: "truncate", color: C.dim, children: line || " " });
18782
+ return /* @__PURE__ */ jsxs17(Text18, { wrap: "truncate", children: [
18783
+ /* @__PURE__ */ jsx22(Text18, { color: C.dim, children: m[1] }),
18784
+ /* @__PURE__ */ jsx22(Text18, { color: C.blue, children: m[2] }),
18785
+ /* @__PURE__ */ jsx22(Text18, { color: C.green, children: m[3] })
18786
+ ] });
18787
+ }
18646
18788
 
18647
18789
  // src/tui/App.tsx
18648
- import { jsx as jsx22 } from "react/jsx-runtime";
18790
+ import { jsx as jsx23 } from "react/jsx-runtime";
18649
18791
  var TOAST_MS = 5e3;
18650
18792
  var QUIT_MS = 2e3;
18651
18793
  function App(props) {
18652
- const [lang, setLangState] = useState14(props.lang);
18653
- const [theme, setThemeState] = useState14(props.theme ?? "auto");
18794
+ const [lang, setLangState] = useState16(props.lang);
18795
+ const [theme, setThemeState] = useState16(props.theme ?? "auto");
18654
18796
  const tone2 = toneFor(theme, props.background ?? tone());
18655
18797
  if (tone2 !== tone()) setTone(tone2);
18656
- return /* @__PURE__ */ jsx22(I18nProvider, { lang, children: /* @__PURE__ */ jsx22(Shell, { ...props, lang, setLang: setLangState, theme, setThemeState, tone: tone2 }) });
18798
+ return /* @__PURE__ */ jsx23(I18nProvider, { lang, children: /* @__PURE__ */ jsx23(Shell, { ...props, lang, setLang: setLangState, theme, setThemeState, tone: tone2 }) });
18657
18799
  }
18658
18800
  function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeState, tone: tone2, background, saveTheme, notify, glyphs, env, run: run2, readImage, openFile }) {
18659
18801
  const { exit: exit3, suspendTerminal } = useApp();
18660
18802
  const { stdout } = useStdout();
18661
- const write = useCallback((seq) => {
18803
+ const write = useCallback2((seq) => {
18662
18804
  if (seq) stdout.write(seq);
18663
18805
  }, [stdout]);
18664
18806
  const d = useDaemon();
18665
18807
  const machines = useMachines(client);
18666
18808
  const projects = useProjects(client);
18667
- const teams = useTeams(client);
18668
18809
  const tasks = useTasks(client);
18669
18810
  const fleet = client.fleet;
18670
18811
  const t = MESSAGES[lang];
18671
- const [screen, setScreen] = useState14("overview");
18672
- const [back2, setBack] = useState14("overview");
18673
- const [selectedId, setSelectedId] = useState14(null);
18674
- const [lastToast, setToast] = useState14(null);
18675
- const [editing, setEditingState] = useState14(false);
18676
- const keys = useRef13(null);
18677
- const kept = useRef13({ drafts: /* @__PURE__ */ new Map(), sent: /* @__PURE__ */ new Map(), asked: [] }).current;
18678
- const armed = useRef13(0);
18679
- const catalog = useRef13({});
18680
- useEffect13(() => {
18812
+ const [screen, setScreen] = useState16("overview");
18813
+ const [back2, setBack] = useState16("overview");
18814
+ const [selectedId, setSelectedId] = useState16(null);
18815
+ const [lastToast, setToast] = useState16(null);
18816
+ const [editing, setEditingState] = useState16(false);
18817
+ const keys = useRef14(null);
18818
+ const kept = useRef14({ drafts: /* @__PURE__ */ new Map(), sent: /* @__PURE__ */ new Map(), asked: [] }).current;
18819
+ const armed = useRef14(0);
18820
+ useEffect15(() => {
18681
18821
  if (!lastToast) return;
18682
18822
  const timer2 = setTimeout(() => setToast(null), lastToast.ms ?? TOAST_MS);
18683
18823
  return () => clearTimeout(timer2);
18684
18824
  }, [lastToast]);
18685
- const toast = useCallback((x) => setToast(x), []);
18686
- const setEditing = useCallback((on) => setEditingState(on), []);
18687
- const view = useMemo8(() => overview(d.sessions, projects, teams ?? [], tasks), [d.sessions, projects, teams, tasks]);
18688
- const order2 = useRef13([]);
18689
- useEffect13(() => {
18825
+ const toast = useCallback2((x) => setToast(x), []);
18826
+ const setEditing = useCallback2((on) => setEditingState(on), []);
18827
+ const view = useMemo7(() => overview(d.sessions, projects), [d.sessions, projects]);
18828
+ const order2 = useRef14([]);
18829
+ useEffect15(() => {
18690
18830
  setSelectedId((cur) => cur === null && view.yours[0] ? view.yours[0].id : keepSelection(view.order, cur, order2.current));
18691
18831
  order2.current = view.order;
18692
18832
  }, [view]);
18693
18833
  const selected = (selectedId ? d.byId.get(selectedId) : void 0) ?? null;
18694
- const seen = useRef13(null);
18695
- const titled = useRef13("");
18696
- useEffect13(() => {
18834
+ const seen = useRef14(null);
18835
+ const titled = useRef14("");
18836
+ useEffect15(() => {
18697
18837
  if (seen.current) for (const text5 of asked(seen.current, d.sessions, t.notify)) write(notification(text5, notify, env));
18698
18838
  seen.current = d.byId;
18699
- const next2 = title(t.notify.title(listed(d.sessions).filter(isPending).length + openDecisions(tasks).length));
18700
- if (next2 !== titled.current) {
18701
- titled.current = next2;
18702
- write(next2);
18839
+ const next3 = title(t.notify.title(listed(d.sessions).filter(isPending).length + openDecisions(tasks).length));
18840
+ if (next3 !== titled.current) {
18841
+ titled.current = next3;
18842
+ write(next3);
18703
18843
  }
18704
18844
  }, [d.byId, tasks]);
18705
18845
  const actions = useActions({ client, sessions: d.sessions, errors: d.errors, t, toast });
18706
- const go = useCallback((next2) => {
18846
+ const go = useCallback2((next3) => {
18707
18847
  setScreen((cur) => {
18708
- if (next2 === "help" || next2 === "settings") setBack(cur === "help" || cur === "settings" ? "overview" : cur);
18709
- return next2;
18848
+ if (next3 === "help" || next3 === "settings") setBack(cur === "help" || cur === "settings" ? "overview" : cur);
18849
+ return next3;
18710
18850
  });
18711
18851
  }, []);
18712
- const setLang = useCallback((next2) => {
18713
- setLangState(next2);
18852
+ const setLang = useCallback2((next3) => {
18853
+ setLangState(next3);
18714
18854
  try {
18715
- saveLang(next2);
18716
- toast({ text: MESSAGES[next2].settings.saved, tone: "ok" });
18855
+ saveLang(next3);
18856
+ toast({ text: MESSAGES[next3].settings.saved, tone: "ok" });
18717
18857
  } catch (e) {
18718
- toast({ text: MESSAGES[next2].settings.saveFailed(e.message), tone: "error" });
18858
+ toast({ text: MESSAGES[next3].settings.saveFailed(e.message), tone: "error" });
18719
18859
  }
18720
18860
  }, [saveLang, setLangState, toast]);
18721
- const setTheme = useCallback((next2) => {
18722
- setThemeState(next2);
18861
+ const setTheme = useCallback2((next3) => {
18862
+ setThemeState(next3);
18723
18863
  try {
18724
- saveTheme?.(next2);
18864
+ saveTheme?.(next3);
18725
18865
  toast({ text: t.settings.saved, tone: "ok" });
18726
18866
  } catch (e) {
18727
18867
  toast({ text: t.settings.saveFailed(e.message), tone: "error" });
18728
18868
  }
18729
18869
  }, [saveTheme, setThemeState, toast, t]);
18730
- const attach = useCallback((s) => {
18870
+ const attach = useCallback2((s) => {
18731
18871
  const cmd = attachCommand(fleet, s);
18732
18872
  if ("why" in cmd) {
18733
18873
  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" });
@@ -18739,7 +18879,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
18739
18879
  if (code3 !== 0) toast({ text: t.toast.attachFailed(code3), tone: "error" });
18740
18880
  });
18741
18881
  }, [fleet, suspendTerminal, run2, t, toast, write]);
18742
- const runInTerminal = useCallback((argv) => {
18882
+ const runInTerminal = useCallback2((argv) => {
18743
18883
  void suspendTerminal(async () => {
18744
18884
  const code3 = await (run2 ?? runAttached)(argv);
18745
18885
  if (code3 !== 0) toast({ text: t.toast.exited(argv[0], code3), tone: "error" });
@@ -18774,13 +18914,12 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
18774
18914
  attach,
18775
18915
  runInTerminal,
18776
18916
  env,
18777
- catalog,
18778
18917
  keys,
18779
18918
  kept,
18780
18919
  readImage: readImage ?? (() => readClipboardImage(env)),
18781
18920
  openFile: openFile ?? ((file) => openImage(file, env))
18782
18921
  };
18783
- useInput7((input, key) => {
18922
+ useInput8((input, key) => {
18784
18923
  if (key.ctrl && input === "c") {
18785
18924
  if (keys.current?.(input, key)) return;
18786
18925
  const drafts = [...kept.drafts].some(([id, x]) => x.trim() && (id === "ask" || d.byId.has(id)));
@@ -18827,8 +18966,8 @@ function Shell({ client, lang, setLang: setLangState, saveLang, theme, setThemeS
18827
18966
  return;
18828
18967
  }
18829
18968
  });
18830
- const Body = { overview: Overview, session: SessionScreen, ask: Ask, launch: Launch, settings: Settings, teams: Teams, help: Help }[screen];
18831
- return /* @__PURE__ */ jsx22(TuiContext.Provider, { value: tui, children: /* @__PURE__ */ jsx22(Body, {}) });
18969
+ const Body2 = { overview: Overview, session: SessionScreen, ask: Ask, launch: Launch, settings: Settings, openrig: OpenRigScreen, rigLaunch: RigLaunchScreen, help: Help }[screen];
18970
+ return /* @__PURE__ */ jsx23(TuiContext.Provider, { value: tui, children: /* @__PURE__ */ jsx23(Body2, {}) });
18832
18971
  }
18833
18972
 
18834
18973
  // src/tui/fleet.ts
@@ -18883,9 +19022,6 @@ var UNICODE = {
18883
19022
  next: "\u25B8",
18884
19023
  ellipsis: "\u2026",
18885
19024
  bar: "\u2502",
18886
- /** A team's table and the common table (roles spec §3.9). */
18887
- team: "\u25A3",
18888
- common: "\u25AD",
18889
19025
  /** Markdown: a list's marks, a quote's bar, a rule, a code block's corners, a link's arrow, the end of a streamed answer. */
18890
19026
  bullet: "\u2022",
18891
19027
  subBullet: "\u25E6",
@@ -18903,7 +19039,7 @@ var UNICODE = {
18903
19039
  stale: "!",
18904
19040
  /** Links are clickable (OSC 8); without, their address follows them. */
18905
19041
  links: true,
18906
- /** A task handed between seats in « Échanges » (#92). */
19042
+ /** A task handed between sessions in « Échanges » (#92). */
18907
19043
  task: "\u25A4"
18908
19044
  };
18909
19045
  var ASCII = {
@@ -18937,8 +19073,6 @@ var ASCII = {
18937
19073
  next: ">",
18938
19074
  ellipsis: "...",
18939
19075
  bar: "|",
18940
- team: "[T]",
18941
- common: "[ ]",
18942
19076
  bullet: "-",
18943
19077
  subBullet: "o",
18944
19078
  quote: "|",
@@ -19136,7 +19270,7 @@ async function backgroundAtStart(theme, env, io) {
19136
19270
  }
19137
19271
 
19138
19272
  // src/tui/index.tsx
19139
- import { jsx as jsx23 } from "react/jsx-runtime";
19273
+ import { jsx as jsx24 } from "react/jsx-runtime";
19140
19274
  var TUI_USAGE = "ledge tui [--notify bell|osc9|off] [--here]";
19141
19275
  function tuiArgs(args) {
19142
19276
  let notify = "bell";
@@ -19175,15 +19309,15 @@ async function runTui(args) {
19175
19309
  const detach = attachClient(client);
19176
19310
  client.connect();
19177
19311
  const app = render(
19178
- /* @__PURE__ */ jsx23(
19312
+ /* @__PURE__ */ jsx24(
19179
19313
  App,
19180
19314
  {
19181
19315
  client,
19182
19316
  lang,
19183
- saveLang: (next2) => saveSettings({ ...loadSettings(), lang: next2 }),
19317
+ saveLang: (next3) => saveSettings({ ...loadSettings(), lang: next3 }),
19184
19318
  theme,
19185
19319
  background,
19186
- saveTheme: (next2) => saveSettings({ ...loadSettings(), theme: next2 }),
19320
+ saveTheme: (next3) => saveSettings({ ...loadSettings(), theme: next3 }),
19187
19321
  notify: parsed.notify,
19188
19322
  glyphs: glyphsFor(process.env),
19189
19323
  env: process.env