@paradow/ledge 0.28.0 → 0.29.0-snapshot.41306023381.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +8 -0
  2. package/dist/cli/{action-NQRSZEC5.js → action-MXRYKL5N.js} +5 -5
  3. package/dist/cli/ask-HIKSAHRR.js +17 -0
  4. package/dist/cli/{attach-KXOSFUHO.js → attach-J65WTS3H.js} +9 -14
  5. package/dist/cli/brain-XOGS4OPT.js +20 -0
  6. package/dist/cli/{bridge-XCTXVWKH.js → bridge-YZL5NT7Q.js} +2 -2
  7. package/dist/cli/{chunk-3NQNWGDX.js → chunk-3U6TAEIO.js} +3 -5
  8. package/dist/cli/chunk-66ZLZ2OW.js +365 -0
  9. package/dist/cli/{chunk-JLUHY5AB.js → chunk-7XFJ5QCZ.js} +4 -4
  10. package/dist/cli/chunk-BP7NGWYD.js +57 -0
  11. package/dist/cli/{chunk-LG3XFIPV.js → chunk-DEOQT7IM.js} +24 -7
  12. package/dist/cli/chunk-DJMFCYF4.js +60 -0
  13. package/dist/cli/{chunk-6IYPRUQG.js → chunk-G3ST5WKR.js} +1 -1
  14. package/dist/cli/{chunk-VKRFHDRK.js → chunk-G6ESCKEX.js} +1124 -1164
  15. package/dist/cli/{chunk-3CF4FQWG.js → chunk-J67O4QQG.js} +4 -3
  16. package/dist/cli/{chunk-RYZ6FHYT.js → chunk-JFXRDHZR.js} +17 -39
  17. package/dist/cli/{chunk-NLL2EQJ2.js → chunk-KBSXP63H.js} +22 -7
  18. package/dist/cli/{chunk-CR3LGTZQ.js → chunk-LQCWBJYE.js} +3 -3
  19. package/dist/cli/{chunk-ZI47U5CQ.js → chunk-MJTMM6GC.js} +102 -72
  20. package/dist/cli/chunk-NEYGPVEZ.js +57 -0
  21. package/dist/cli/chunk-OFY6GEYS.js +6 -0
  22. package/dist/cli/{chunk-HOOHS3MX.js → chunk-W724IVDC.js} +1 -1
  23. package/dist/cli/{chunk-K7UEKSB2.js → chunk-WYRQZA4G.js} +2 -2
  24. package/dist/cli/{chunk-VMGACWNP.js → chunk-X6NV2IEM.js} +1 -1
  25. package/dist/cli/daemon-JPKL3IAU.js +16576 -0
  26. package/dist/cli/{decide-OSSTI3FE.js → decide-2LECM6BO.js} +14 -16
  27. package/dist/cli/{handover-2G7OEWIG.js → handover-ZFNRGTTV.js} +5 -5
  28. package/dist/cli/{hook-XY4E5H7O.js → hook-TZXMVYOG.js} +87 -81
  29. package/dist/cli/index.js +40 -28
  30. package/dist/cli/launch-HGECZQCL.js +28 -0
  31. package/dist/cli/{send-RUHGKLMA.js → send-4TPIJG7O.js} +5 -5
  32. package/dist/cli/{setup-K42IVMHS.js → setup-OLVRJLJE.js} +4 -4
  33. package/dist/cli/{status-4Z2VJFID.js → status-C6I23SKG.js} +3 -3
  34. package/dist/cli/{tui-4IRYQXYU.js → tui-RD5JZLSU.js} +6008 -3597
  35. package/dist/cli/{uninstall-4IMMR55T.js → uninstall-X65CWHIJ.js} +4 -4
  36. package/dist/cli/{version-L2Y7MRH6.js → version-SU3VF37X.js} +1 -1
  37. package/dist/cli/who-JXTN6IDB.js +12 -0
  38. package/package.json +21 -6
  39. package/dist/cli/chunk-CIQAFCX3.js +0 -118
  40. package/dist/cli/chunk-JHFKBKSO.js +0 -159
  41. package/dist/cli/chunk-OYDTU6AP.js +0 -9482
  42. package/dist/cli/chunk-QSRB3F5Y.js +0 -198
  43. package/dist/cli/chunk-UME2F7VM.js +0 -6
  44. package/dist/cli/daemon-ZUHJKLSL.js +0 -2322
  45. package/dist/cli/launch-7WOARLZM.js +0 -35
  46. package/dist/cli/task-2GESYTOL.js +0 -19
  47. package/dist/cli/team-MTSKNMAE.js +0 -205
  48. package/dist/cli/who-PSKHASMK.js +0 -12
package/README.md CHANGED
@@ -53,6 +53,14 @@ icon comes only from its profile, so Ledge installs a `Ledge` profile as a fragm
53
53
  `~/.ledge/tui.sh`, which each `ledge tui` rewrites with its node and CLI. `ledge tui --here`, tmux, SSH and any other
54
54
  terminal keep the TUI where it was started.
55
55
 
56
+ The TUI opens on a home with Ledge first: its conversation (decisions to settle with `1`-`9`, `@build` to write to an
57
+ OpenRig agent), then `À toi` (what waits for you), `Projets · sessions seules` (sessions outside a rig, as before) and
58
+ `Rigs` (OpenRig rigs and the OpenRig kernel). `2` opens a rig's room: its seats, their exchanges and a live, colour,
59
+ read-only view of the chosen seat's terminal (`a` attaches to type in it). A session driven by OMP shows its thread
60
+ drawn as a terminal instead. `+` launches a session, or a rig from a rig, the kernel or the Rigs tab (goal, plan, start;
61
+ `ctrl+t` switches between the two). Seats are restarted, stopped and resumed through OpenRig, never killed from Ledge.
62
+ `?` lists every key by screen.
63
+
56
64
  When a Claude it launched exits, Ledge appends a `custom-title` « name - topic » to its transcript, so `claude --resume` lists the conversation that way, unless the user named it (`claude -n`, `/rename`).
57
65
 
58
66
  `ledge setup` installs `ledge-daemon.service` as a systemd user unit, starts it and enables linger, so the daemon runs from WSL boot and restarts after a crash. Rerun it after a node upgrade or a repository move: the unit is rewritten and the service restarted. Useful commands:
@@ -8,11 +8,11 @@ import {
8
8
  resultLine,
9
9
  runClose,
10
10
  runOnClient
11
- } from "./chunk-CR3LGTZQ.js";
12
- import "./chunk-LG3XFIPV.js";
13
- import "./chunk-VMGACWNP.js";
14
- import "./chunk-VKRFHDRK.js";
15
- import "./chunk-HOOHS3MX.js";
11
+ } from "./chunk-LQCWBJYE.js";
12
+ import "./chunk-DEOQT7IM.js";
13
+ import "./chunk-X6NV2IEM.js";
14
+ import "./chunk-G6ESCKEX.js";
15
+ import "./chunk-W724IVDC.js";
16
16
  import "./chunk-U67V476Y.js";
17
17
  export {
18
18
  CLOSE_USAGE,
@@ -0,0 +1,17 @@
1
+ import {
2
+ ASK_USAGE,
3
+ askArgs,
4
+ askOnClient,
5
+ runAsk
6
+ } from "./chunk-BP7NGWYD.js";
7
+ import "./chunk-DEOQT7IM.js";
8
+ import "./chunk-X6NV2IEM.js";
9
+ import "./chunk-G6ESCKEX.js";
10
+ import "./chunk-W724IVDC.js";
11
+ import "./chunk-U67V476Y.js";
12
+ export {
13
+ ASK_USAGE,
14
+ askArgs,
15
+ askOnClient,
16
+ runAsk
17
+ };
@@ -1,24 +1,19 @@
1
1
  import {
2
2
  attachEnv
3
- } from "./chunk-RYZ6FHYT.js";
4
- import "./chunk-OYDTU6AP.js";
5
- import "./chunk-OLLQBK6N.js";
6
- import "./chunk-QSRB3F5Y.js";
7
- import "./chunk-ZI47U5CQ.js";
8
- import "./chunk-JHFKBKSO.js";
9
- import "./chunk-LG3XFIPV.js";
3
+ } from "./chunk-JFXRDHZR.js";
4
+ import {
5
+ realTmux
6
+ } from "./chunk-KBSXP63H.js";
7
+ import "./chunk-G3ST5WKR.js";
8
+ import "./chunk-DEOQT7IM.js";
10
9
  import {
11
10
  connect
12
- } from "./chunk-VMGACWNP.js";
11
+ } from "./chunk-X6NV2IEM.js";
13
12
  import {
14
13
  PROTOCOL_PORT,
15
14
  backendOf
16
- } from "./chunk-VKRFHDRK.js";
17
- import {
18
- realTmux
19
- } from "./chunk-NLL2EQJ2.js";
20
- import "./chunk-6IYPRUQG.js";
21
- import "./chunk-HOOHS3MX.js";
15
+ } from "./chunk-G6ESCKEX.js";
16
+ import "./chunk-W724IVDC.js";
22
17
  import "./chunk-U67V476Y.js";
23
18
 
24
19
  // src/cli/attach.ts
@@ -0,0 +1,20 @@
1
+ import {
2
+ BRAIN_MS,
3
+ BRAIN_USAGE,
4
+ NOT_BRAIN_LINE,
5
+ brainOnClient,
6
+ runBrain
7
+ } from "./chunk-NEYGPVEZ.js";
8
+ import "./chunk-LQCWBJYE.js";
9
+ import "./chunk-DEOQT7IM.js";
10
+ import "./chunk-X6NV2IEM.js";
11
+ import "./chunk-G6ESCKEX.js";
12
+ import "./chunk-W724IVDC.js";
13
+ import "./chunk-U67V476Y.js";
14
+ export {
15
+ BRAIN_MS,
16
+ BRAIN_USAGE,
17
+ NOT_BRAIN_LINE,
18
+ brainOnClient,
19
+ runBrain
20
+ };
@@ -7,8 +7,8 @@ import {
7
7
  parseClientMessage,
8
8
  parseServerMessage,
9
9
  readDaemonToken
10
- } from "./chunk-VKRFHDRK.js";
11
- import "./chunk-HOOHS3MX.js";
10
+ } from "./chunk-G6ESCKEX.js";
11
+ import "./chunk-W724IVDC.js";
12
12
  import "./chunk-U67V476Y.js";
13
13
 
14
14
  // src/cli/bridge.ts
@@ -1,27 +1,25 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-VMGACWNP.js";
3
+ } from "./chunk-X6NV2IEM.js";
4
4
  import {
5
5
  isArchived
6
- } from "./chunk-VKRFHDRK.js";
6
+ } from "./chunk-G6ESCKEX.js";
7
7
 
8
8
  // src/cli/who.ts
9
9
  import { basename } from "path";
10
10
  function formatWho(sessions, self) {
11
11
  const live = sessions.filter((s) => s.state !== "exited" && !isArchived(s));
12
12
  if (live.length === 0) return "aucune autre session";
13
- const roles = live.some((s) => s.role);
14
13
  const rows = live.map((s) => [
15
14
  s.id === self ? "*" : " ",
16
15
  s.name,
17
- ...roles ? [s.role ? `@${s.role}` : "-"] : [],
18
16
  s.tool,
19
17
  s.state,
20
18
  `${s.repo ?? s.project}${s.worktree ? ` (${basename(s.cwd)})` : ""}`,
21
19
  s.branch ?? "-",
22
20
  `${s.topic ?? "-"}${s.source === "hook_only" ? " \xB7 lecture seule" : ""}`
23
21
  ]);
24
- const head = [" ", "NOM", ...roles ? ["R\xD4LE"] : [], "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
22
+ const head = [" ", "NOM", "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
25
23
  const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
26
24
  return [head, ...rows].map((r) => r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i])).join(" ")).join("\n");
27
25
  }
@@ -0,0 +1,365 @@
1
+ import {
2
+ PROJECT_COLORS,
3
+ RIG_NAME,
4
+ parseServerMessage
5
+ } from "./chunk-G6ESCKEX.js";
6
+
7
+ // src/imageInfo.ts
8
+ var ok = (mime, width, height) => width > 0 && height > 0 ? { mime, width, height } : null;
9
+ function jpeg(b) {
10
+ let i = 2;
11
+ while (i + 4 <= b.length) {
12
+ if (b[i] !== 255) {
13
+ i++;
14
+ continue;
15
+ }
16
+ const m = b[i + 1];
17
+ if (m === 255) {
18
+ i++;
19
+ continue;
20
+ }
21
+ if (m === 216 || m === 1 || m >= 208 && m <= 215) {
22
+ i += 2;
23
+ continue;
24
+ }
25
+ const len = b.readUInt16BE(i + 2);
26
+ if (m >= 192 && m <= 207 && m !== 196 && m !== 200 && m !== 204) return i + 9 <= b.length ? ok("image/jpeg", b.readUInt16BE(i + 7), b.readUInt16BE(i + 5)) : null;
27
+ i += 2 + len;
28
+ }
29
+ return null;
30
+ }
31
+ function webp(b) {
32
+ const kind = b.toString("latin1", 12, 16);
33
+ if (kind === "VP8X") return b.length >= 30 ? ok("image/webp", b.readUIntLE(24, 3) + 1, b.readUIntLE(27, 3) + 1) : null;
34
+ if (kind === "VP8L") {
35
+ if (b.length < 25 || b[20] !== 47) return null;
36
+ const bits = b.readUInt32LE(21);
37
+ return ok("image/webp", (bits & 16383) + 1, (bits >>> 14 & 16383) + 1);
38
+ }
39
+ if (kind === "VP8 ") return b.length >= 30 && b[23] === 157 && b[24] === 1 && b[25] === 42 ? ok("image/webp", b.readUInt16LE(26) & 16383, b.readUInt16LE(28) & 16383) : null;
40
+ return null;
41
+ }
42
+ function imageInfo(b) {
43
+ try {
44
+ if (b.length >= 24 && b.readUInt32BE(0) === 2303741511 && b.readUInt32BE(4) === 218765834 && b.toString("latin1", 12, 16) === "IHDR") return ok("image/png", b.readUInt32BE(16), b.readUInt32BE(20));
45
+ if (b.length >= 10 && (b.toString("latin1", 0, 6) === "GIF87a" || b.toString("latin1", 0, 6) === "GIF89a")) return ok("image/gif", b.readUInt16LE(6), b.readUInt16LE(8));
46
+ if (b.length >= 4 && b[0] === 255 && b[1] === 216) return jpeg(b);
47
+ if (b.length >= 16 && b.toString("latin1", 0, 4) === "RIFF" && b.toString("latin1", 8, 12) === "WEBP") return webp(b);
48
+ } catch {
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // ../ui/src/rigs/model.ts
54
+ var rigKey = (r) => `${r.machine}/${r.id}`;
55
+ var COLOR_NAMES = Object.keys(PROJECT_COLORS);
56
+ function rigColor(r) {
57
+ let sum = 0;
58
+ for (let i = 0; i < r.name.length; i++) sum += r.name.charCodeAt(i);
59
+ return COLOR_NAMES[sum % COLOR_NAMES.length];
60
+ }
61
+ function kernelSplit(rig) {
62
+ const hidden = [], shown = [];
63
+ for (const s of rig.seats) (s.name.startsWith("advisor") || s.runtime === "terminal" ? hidden : shown).push(s);
64
+ return { shown, hidden };
65
+ }
66
+ var workingCount = (rig) => rig.seats.filter((s) => s.state === "working").length;
67
+ function rigPlace(rig, projects) {
68
+ if (rig.projectId) return { kind: "project", name: projects?.projects.find((p) => p.id === rig.projectId)?.name ?? rig.projectId };
69
+ if (rig.offProject) return { kind: "off", cwd: rig.cwd };
70
+ return { kind: "none" };
71
+ }
72
+ function isStandalone(s, rigs) {
73
+ if (s.brain) return false;
74
+ const r = s.rig;
75
+ if (!r) return true;
76
+ return !rigs.some((x) => x.machine === r.machine && x.seats.some((seat) => seat.sessionId === s.id && seat.state !== "stopped"));
77
+ }
78
+ var MENTION_RIG = /* @__PURE__ */ new Set(["running", "partial"]);
79
+ var MENTION_DOWN = /* @__PURE__ */ new Set(["stopped", "crashed", "attention"]);
80
+ function seatMentions(rigs, query) {
81
+ const live = rigs.filter((r) => r.machine === "local" && MENTION_RIG.has(r.lifecycle)).flatMap((rig) => rig.seats.filter((s) => !MENTION_DOWN.has(s.state)).map((seat) => ({ rig, seat })));
82
+ const holders = /* @__PURE__ */ new Map();
83
+ for (const { seat } of live) holders.set(seat.address, (holders.get(seat.address) ?? 0) + 1);
84
+ const q = query.toLowerCase();
85
+ return live.filter(({ rig, seat }) => !rig.kernel && seat.address.replace(/^@/, "").toLowerCase().startsWith(q)).sort((a, b) => a.seat.address.localeCompare(b.seat.address) || a.rig.name.localeCompare(b.rig.name)).map(({ rig, seat }) => {
86
+ const shared = (holders.get(seat.address) ?? 0) > 1;
87
+ const insert = shared ? `${seat.address}@${rig.name}` : seat.address;
88
+ return { key: `${rigKey(rig)}/${seat.ref}`, label: shared ? insert : `${seat.address} \xB7 ${rig.name}`, insert };
89
+ });
90
+ }
91
+ function ledgeLine(escalations, rigs, said) {
92
+ const open = escalations.filter((e) => e.intent === "decision" && (e.state === "pending" || e.state === "grace")).sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0);
93
+ const rigOf = (e) => {
94
+ const name = e?.rigId ? rigs.find((r) => r.machine === e.machine && r.id === e.rigId)?.name : void 0;
95
+ return name ? { rig: name } : {};
96
+ };
97
+ const pending = open.filter((e) => e.state === "pending");
98
+ if (pending.length) return { preview: { kind: "decision", count: pending.length, ...rigOf(pending[0]) }, at: pending[0].createdAt, unread: open.length };
99
+ const grace = open[0];
100
+ if (grace) return { preview: { kind: "grace", ...rigOf(grace) }, at: grace.createdAt, unread: open.length };
101
+ if (said) return { preview: { kind: "said", text: said.text }, at: said.at, unread: 0 };
102
+ return { preview: { kind: "calm", rigs: rigs.filter((r) => !r.kernel).length }, unread: 0 };
103
+ }
104
+ function shortCwd(cwd) {
105
+ const m = /^(\/home\/[^/]+|\/root)(?=\/|$)/.exec(cwd);
106
+ return m ? `~${cwd.slice(m[0].length)}` : cwd;
107
+ }
108
+ var RUNTIME_LABELS = { "claude-code": "Claude Code", codex: "Codex" };
109
+ var runtimeLabel = (runtime) => RUNTIME_LABELS[runtime] ?? runtime;
110
+
111
+ // ../fleet/src/token.ts
112
+ import { useSyncExternalStore } from "react";
113
+ var DAEMON_PROTOCOL_PREFIX = "ledge.";
114
+ var READ_TIMEOUT_MS = 1e4;
115
+ function createTokenSource(read) {
116
+ let cached = null, missing = false, inflight = null;
117
+ const fns = /* @__PURE__ */ new Set();
118
+ const setMissing = (m) => {
119
+ if (m !== missing) {
120
+ missing = m;
121
+ fns.forEach((f) => f(m));
122
+ }
123
+ };
124
+ const load = async () => {
125
+ let r, timer;
126
+ const unavailable = { kind: "unavailable" };
127
+ try {
128
+ const got = await Promise.race([read(), new Promise((res) => {
129
+ timer = setTimeout(() => res(unavailable), READ_TIMEOUT_MS);
130
+ })]);
131
+ r = got?.kind === "token" && typeof got.token === "string" || got?.kind === "absent" ? got : unavailable;
132
+ } catch {
133
+ r = unavailable;
134
+ } finally {
135
+ clearTimeout(timer);
136
+ }
137
+ if (r.kind === "token") cached = r.token;
138
+ else if (r.kind === "absent") cached = null;
139
+ setMissing(r.kind === "absent");
140
+ return r;
141
+ };
142
+ return {
143
+ get(fresh) {
144
+ if (!fresh && cached !== null) return Promise.resolve({ kind: "token", token: cached });
145
+ inflight ??= load().finally(() => {
146
+ inflight = null;
147
+ });
148
+ return inflight;
149
+ },
150
+ missing: () => missing,
151
+ onMissing(fn) {
152
+ fns.add(fn);
153
+ return () => {
154
+ fns.delete(fn);
155
+ };
156
+ }
157
+ };
158
+ }
159
+
160
+ // ../fleet/src/client.ts
161
+ function createDaemonClient(opts) {
162
+ const Ws = opts.WebSocketImpl ?? globalThis.WebSocket;
163
+ const min = opts.minBackoffMs ?? 2e3, max = opts.maxBackoffMs ?? 3e4, token = opts.token;
164
+ let ws = null, status = "connecting", backoff = min, timer = null, closed = false;
165
+ let failed = false;
166
+ let attempt = 0;
167
+ const msgFns = /* @__PURE__ */ new Set(), statusFns = /* @__PURE__ */ new Set();
168
+ const setStatus = (s) => {
169
+ if (s !== status) {
170
+ status = s;
171
+ statusFns.forEach((f) => f(s));
172
+ }
173
+ };
174
+ const schedule = () => {
175
+ if (closed) return;
176
+ timer = setTimeout(open, backoff);
177
+ backoff = Math.min(max, backoff * 2);
178
+ };
179
+ function open() {
180
+ if (closed) return;
181
+ setStatus(status === "open" ? "connecting" : status);
182
+ if (!token) {
183
+ dial(new Ws(opts.url));
184
+ return;
185
+ }
186
+ const mine = ++attempt;
187
+ void token.get(failed).then((r) => {
188
+ if (closed || mine !== attempt) return;
189
+ if (r.kind === "token") {
190
+ try {
191
+ dial(new Ws(opts.url, [DAEMON_PROTOCOL_PREFIX + r.token]));
192
+ return;
193
+ } catch {
194
+ }
195
+ }
196
+ failed = true;
197
+ setStatus("unreachable");
198
+ schedule();
199
+ });
200
+ }
201
+ function dial(sock) {
202
+ ws = sock;
203
+ let opened = false;
204
+ sock.onopen = () => {
205
+ if (ws !== sock) return;
206
+ opened = true;
207
+ setStatus("open");
208
+ };
209
+ sock.onmessage = (ev) => {
210
+ if (ws !== sock) return;
211
+ try {
212
+ const m = parseServerMessage(JSON.parse(String(ev.data)));
213
+ if (m.type === "snapshot") {
214
+ backoff = min;
215
+ failed = false;
216
+ }
217
+ msgFns.forEach((f) => f(m));
218
+ } catch {
219
+ }
220
+ };
221
+ sock.onerror = () => {
222
+ };
223
+ sock.onclose = () => {
224
+ if (ws !== sock) return;
225
+ ws = null;
226
+ if (!opened) failed = true;
227
+ setStatus("unreachable");
228
+ schedule();
229
+ };
230
+ }
231
+ return {
232
+ // An explicit connect (a « Reconnect ») says « connecting » at once, even after a drop: the automatic retries stay « unreachable ».
233
+ connect: () => {
234
+ closed = false;
235
+ if (timer) {
236
+ clearTimeout(timer);
237
+ timer = null;
238
+ }
239
+ const old = ws;
240
+ ws = null;
241
+ old?.close();
242
+ setStatus("connecting");
243
+ open();
244
+ },
245
+ close: () => {
246
+ closed = true;
247
+ attempt++;
248
+ if (timer) clearTimeout(timer);
249
+ const old = ws;
250
+ ws = null;
251
+ old?.close();
252
+ setStatus("connecting");
253
+ },
254
+ send: (m) => {
255
+ if (ws && ws.readyState === 1) ws.send(JSON.stringify(m));
256
+ },
257
+ status: () => status,
258
+ onMessage: (f) => {
259
+ msgFns.add(f);
260
+ return () => msgFns.delete(f);
261
+ },
262
+ onStatus: (f) => {
263
+ statusFns.add(f);
264
+ return () => statusFns.delete(f);
265
+ }
266
+ };
267
+ }
268
+
269
+ // ../ui/src/launch/rigModel.ts
270
+ var STOP = /* @__PURE__ */ new Set(["le", "la", "les", "de", "des", "du", "un", "une", "et", "\xE0", "au", "pour", "sur", "apr\xE8s", "avant"]);
271
+ var LONG_WORDS = ["jours", "dur\xE9e", "produit", "livrer", "plusieurs"];
272
+ var LONG_GOAL = 160;
273
+ var fold = (s) => s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "");
274
+ function slugOfGoal(goal) {
275
+ const words = goal.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((w) => w && !STOP.has(w)).map(fold).map((w) => w.replace(/[^a-z0-9]/g, "")).filter(Boolean);
276
+ const slug = words.slice(-2).join("-").slice(0, 40).replace(/-+$/, "");
277
+ return RIG_NAME.test(slug) ? slug : "rig";
278
+ }
279
+ function rigFormDefaults(p) {
280
+ const goal = p.wanted.goal ?? "";
281
+ const spec = p.wanted.spec ?? null;
282
+ const machine = p.machines.find((m) => m.id === p.wanted.machine)?.id ?? p.machines.find((m) => m.id === "local")?.id ?? p.machines[0]?.id ?? "local";
283
+ const project = p.projects.find((x) => x.id === p.wanted.project);
284
+ if (!project) return { goal, spec, machine, place: p.wanted.off ? { kind: "off", name: p.wanted.off, nameTouched: true } : { kind: "off", name: slugOfGoal(goal), nameTouched: false } };
285
+ const repo = project.repos.find((r) => r.machine === machine)?.path ?? null;
286
+ return { goal, spec, machine, place: { kind: "project", projectId: project.id, repo, branch: spec ? `feat/${spec}` : "", copy: true } };
287
+ }
288
+ function withGoal(f, goal) {
289
+ const place = f.place.kind === "off" && !f.place.nameTouched ? { ...f.place, name: slugOfGoal(goal) } : f.place;
290
+ return { ...f, goal, place };
291
+ }
292
+ function recommendSpec(goal, specs) {
293
+ const g = goal.toLowerCase();
294
+ const long = goal.length > LONG_GOAL || LONG_WORDS.some((w) => g.includes(w));
295
+ const spec = long ? "factory" : "starter";
296
+ return specs.some((s) => s.name === spec || s.id === spec) ? { spec, why: long ? "long" : "bounded" } : null;
297
+ }
298
+ function missingOf(f) {
299
+ const out = [];
300
+ if (!f.goal.trim()) out.push("goal");
301
+ if (!f.spec) out.push("spec");
302
+ if (f.place.kind === "project") {
303
+ if (!f.place.repo) out.push("repo");
304
+ const b = f.place.branch.trim();
305
+ if (!b || b.startsWith("-")) out.push("branch");
306
+ } else if (!RIG_NAME.test(f.place.name)) out.push("name");
307
+ return out;
308
+ }
309
+ function whereOf(f) {
310
+ const missing = missingOf(f);
311
+ if (f.place.kind === "project") {
312
+ if (missing.includes("repo") || missing.includes("branch") || !f.place.repo) return null;
313
+ return { kind: "project", projectId: f.place.projectId, repo: f.place.repo, branch: f.place.branch.trim(), copy: f.place.copy };
314
+ }
315
+ return missing.includes("name") ? null : { kind: "off-project", name: f.place.name };
316
+ }
317
+ var planRequired = (f) => f.place.kind === "project" && !f.place.copy;
318
+ var primaryAction = (f) => planRequired(f) ? "plan" : "launch";
319
+ function providers(seats) {
320
+ const counts = /* @__PURE__ */ new Map();
321
+ for (const s of seats) counts.set(s.runtime, (counts.get(s.runtime) ?? 0) + 1);
322
+ return [...counts].map(([runtime, count]) => ({ runtime, count }));
323
+ }
324
+ function launchability(m, status) {
325
+ if (m.status !== "open") return { ok: false, why: "offline" };
326
+ if (!status) return { ok: false, why: "unreachable" };
327
+ switch (status.state) {
328
+ case "running":
329
+ case "stopped":
330
+ case "starting":
331
+ return { ok: true };
332
+ case "absent":
333
+ return status.install ? { ok: false, why: "absent", install: status.install } : { ok: false, why: "absent" };
334
+ case "unsupported":
335
+ return { ok: false, why: "unsupported" };
336
+ default:
337
+ return { ok: false, why: "unreachable" };
338
+ }
339
+ }
340
+
341
+ export {
342
+ imageInfo,
343
+ rigKey,
344
+ rigColor,
345
+ kernelSplit,
346
+ workingCount,
347
+ rigPlace,
348
+ isStandalone,
349
+ seatMentions,
350
+ ledgeLine,
351
+ shortCwd,
352
+ runtimeLabel,
353
+ DAEMON_PROTOCOL_PREFIX,
354
+ createTokenSource,
355
+ createDaemonClient,
356
+ slugOfGoal,
357
+ rigFormDefaults,
358
+ withGoal,
359
+ recommendSpec,
360
+ missingOf,
361
+ whereOf,
362
+ primaryAction,
363
+ providers,
364
+ launchability
365
+ };
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  CODEX_HOOK_VAR
3
- } from "./chunk-LG3XFIPV.js";
3
+ } from "./chunk-DEOQT7IM.js";
4
4
  import {
5
5
  connect
6
- } from "./chunk-VMGACWNP.js";
6
+ } from "./chunk-X6NV2IEM.js";
7
7
  import {
8
8
  BRIEF_MAX
9
- } from "./chunk-VKRFHDRK.js";
9
+ } from "./chunk-G6ESCKEX.js";
10
10
 
11
11
  // src/cli/handover.ts
12
12
  import { randomUUID } from "crypto";
@@ -27,7 +27,7 @@ async function handoverOnClient(client, args, by, ms) {
27
27
  client.send(op.op === "show" ? { type: "handover.show", rid, by } : { type: "handover.brief", rid, by, text: op.text });
28
28
  const r = await client.next("handover.result", ms);
29
29
  if (r.reason !== void 0) return { line: `refus\xE9 : ${r.reason}`, code: 1 };
30
- return op.op === "show" ? { line: r.text ?? "", code: 0 } : { line: "bilan re\xE7u : Ledge lance la session qui prend ta rel\xE8ve et lui passe tes t\xE2ches. Arr\xEAte-toi l\xE0.", code: 0 };
30
+ return op.op === "show" ? { line: r.text ?? "", code: 0 } : { line: "bilan re\xE7u : Ledge lance la session qui prend ta rel\xE8ve. Arr\xEAte-toi l\xE0.", code: 0 };
31
31
  } catch (e) {
32
32
  return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
33
33
  }
@@ -0,0 +1,57 @@
1
+ import {
2
+ CODEX_HOOK_VAR
3
+ } from "./chunk-DEOQT7IM.js";
4
+ import {
5
+ connect
6
+ } from "./chunk-X6NV2IEM.js";
7
+ import {
8
+ ASK_OPTIONS_MAX,
9
+ ASK_QUESTION_MAX
10
+ } from "./chunk-G6ESCKEX.js";
11
+
12
+ // src/cli/ask.ts
13
+ import { randomUUID } from "crypto";
14
+ var ASK_USAGE = `ledge ask "<question>" "<options, ta recommandation>" une d\xE9cision pour l'utilisateur ; sa r\xE9ponse te r\xE9veille`;
15
+ var ASK_MS = 4e3;
16
+ function askArgs(args) {
17
+ const [question, options] = args;
18
+ return args.length === 2 && question.trim() && question.length <= ASK_QUESTION_MAX && options.length <= ASK_OPTIONS_MAX ? { question, options } : void 0;
19
+ }
20
+ async function askOnClient(client, args, by, ms) {
21
+ const parsed = askArgs(args);
22
+ if (!parsed) return { line: ASK_USAGE, code: 1 };
23
+ try {
24
+ client.send({ type: "ask.create", rid: randomUUID(), by, ...parsed });
25
+ const r = await client.next("ask.result", ms);
26
+ if (!r.ask) return { line: `refus\xE9 : ${r.reason ?? "raison inconnue"}`, code: 1 };
27
+ return { line: `d\xE9cision ${r.ask.id} pos\xE9e \xE0 l'utilisateur : sa r\xE9ponse te r\xE9veillera`, code: 0 };
28
+ } catch (e) {
29
+ return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
30
+ }
31
+ }
32
+ async function runAsk(args, opts = {}) {
33
+ if (!askArgs(args)) return { line: ASK_USAGE, code: 1 };
34
+ const env = opts.env ?? process.env;
35
+ const by = env[CODEX_HOOK_VAR] || env.LEDGE_SESSION;
36
+ if (!by) return { line: "ledge ask ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
37
+ const deadline = Date.now() + (opts.timeoutMs ?? ASK_MS);
38
+ const left = () => Math.max(0, deadline - Date.now());
39
+ let client;
40
+ try {
41
+ client = await connect({ port: opts.port, timeoutMs: left() });
42
+ } catch {
43
+ return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
44
+ }
45
+ try {
46
+ return await askOnClient(client, args, by, left());
47
+ } finally {
48
+ await client.close();
49
+ }
50
+ }
51
+
52
+ export {
53
+ ASK_USAGE,
54
+ askArgs,
55
+ askOnClient,
56
+ runAsk
57
+ };
@@ -3,10 +3,10 @@ import {
3
3
  SESSION_KEY_VAR,
4
4
  pickName,
5
5
  sessionKey
6
- } from "./chunk-VKRFHDRK.js";
6
+ } from "./chunk-G6ESCKEX.js";
7
7
  import {
8
8
  paths
9
- } from "./chunk-HOOHS3MX.js";
9
+ } from "./chunk-W724IVDC.js";
10
10
 
11
11
  // src/daemon/transcript.ts
12
12
  import { appendFileSync, closeSync, fstatSync, openSync, readFileSync, readSync, statSync } from "fs";
@@ -45,6 +45,23 @@ function tailLines(path, bytes = TAIL_BYTES) {
45
45
  }
46
46
  }
47
47
  var texts = (blocks, kind) => (Array.isArray(blocks) ? blocks : []).filter((b) => b?.type === kind && typeof b.text === "string").map((b) => b.text.trim()).filter(Boolean).join("\n\n");
48
+ function agentThought(path) {
49
+ let latest;
50
+ for (const line of tailLines(path, 64 * 1024)) {
51
+ let e;
52
+ try {
53
+ e = JSON.parse(line);
54
+ } catch {
55
+ continue;
56
+ }
57
+ if (e.type === "assistant" && e.isSidechain !== true) latest = texts(e.message?.content, "thinking") || latest;
58
+ if (e.type === "event_msg" && (e.payload?.type === "item_completed" || e.payload?.type === "item_updated")) {
59
+ const item = e.payload.item;
60
+ if (item?.type === "AgentReasoning") latest = (typeof item.text === "string" ? item.text : typeof item.summary === "string" ? item.summary : texts(item.content, "Text")) || latest;
61
+ }
62
+ }
63
+ return latest?.replace(/\s+/g, " ").trim().slice(0, 80) || void 0;
64
+ }
48
65
  function agentMessages(path, opts = {}) {
49
66
  const out = [];
50
67
  for (const line of tailLines(path, opts.bytes ?? TAIL_BYTES)) {
@@ -210,7 +227,7 @@ var PARENT_AGENT_VARS = [
210
227
  var PARENT_AGENT = new Set(PARENT_AGENT_VARS);
211
228
  function agentEnv(base, extra) {
212
229
  const env = {};
213
- for (const [k, v] of Object.entries(base)) if (v !== void 0 && k !== "TMUX" && !k.startsWith("TMUX_") && !PARENT_AGENT.has(k) && k !== "LEDGE_ROLE" && k !== "LEDGE_TEAM") env[k] = v;
230
+ for (const [k, v] of Object.entries(base)) if (v !== void 0 && k !== "TMUX" && !k.startsWith("TMUX_") && !PARENT_AGENT.has(k)) env[k] = v;
214
231
  return { ...env, ...extra };
215
232
  }
216
233
  async function spawnSession(opts) {
@@ -229,7 +246,7 @@ async function spawnSession(opts) {
229
246
  const transcriptPath = agentSessionId ? claudeTranscriptPath(opts.cwd, agentSessionId, env) : void 0;
230
247
  const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
231
248
  const key = sessionKey(id);
232
- await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(env, { LEDGE_SESSION: id, ...key ? { [SESSION_KEY_VAR]: key } : {}, LEDGE_NAME: name, ...opts.role ? { LEDGE_ROLE: opts.role.id } : {}, ...opts.team ? { LEDGE_TEAM: opts.team } : {}, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
249
+ await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(env, { LEDGE_SESSION: id, ...key ? { [SESSION_KEY_VAR]: key } : {}, LEDGE_NAME: name, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
233
250
  const at = now();
234
251
  return {
235
252
  id,
@@ -246,14 +263,14 @@ async function spawnSession(opts) {
246
263
  source: "launcher",
247
264
  ...agentSessionId ? { agentSessionId } : {},
248
265
  ...transcriptPath ? { transcriptPath } : {},
249
- ...opts.role ? { role: opts.role.id, roleLabel: opts.role.label } : {},
250
- ...opts.team ? { team: opts.team } : {},
251
- ...opts.autonomy && opts.autonomy !== "ask" ? { autonomy: opts.autonomy } : {}
266
+ ...opts.autonomy && opts.autonomy !== "ask" ? { autonomy: opts.autonomy } : {},
267
+ ...opts.brain ? { brain: true } : {}
252
268
  };
253
269
  }
254
270
 
255
271
  export {
256
272
  ledgeTitleRest,
273
+ agentThought,
257
274
  agentMessages,
258
275
  codexPlan,
259
276
  claudePlan,