@paradow/ledge 0.28.0 → 0.29.0-snapshot.41297455946.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-MCR4BCSE.js} +5 -5
  3. package/dist/cli/ask-L3PSQZ2K.js +17 -0
  4. package/dist/cli/{attach-KXOSFUHO.js → attach-R7FT7LFH.js} +9 -14
  5. package/dist/cli/brain-BJTBN2ZK.js +20 -0
  6. package/dist/cli/{bridge-XCTXVWKH.js → bridge-YBHXFEFX.js} +2 -2
  7. package/dist/cli/{chunk-VKRFHDRK.js → chunk-2X56ZRK7.js} +1122 -1164
  8. package/dist/cli/{chunk-3NQNWGDX.js → chunk-6EW2FD2U.js} +3 -5
  9. package/dist/cli/chunk-DJMFCYF4.js +60 -0
  10. package/dist/cli/chunk-DLKJOK3B.js +6 -0
  11. package/dist/cli/{chunk-VMGACWNP.js → chunk-FWLT3SAN.js} +1 -1
  12. package/dist/cli/{chunk-6IYPRUQG.js → chunk-G3ST5WKR.js} +1 -1
  13. package/dist/cli/{chunk-ZI47U5CQ.js → chunk-GY35ZR5I.js} +102 -72
  14. package/dist/cli/{chunk-NLL2EQJ2.js → chunk-KBSXP63H.js} +22 -7
  15. package/dist/cli/chunk-LOXLGCGN.js +57 -0
  16. package/dist/cli/{chunk-LG3XFIPV.js → chunk-O4MUYFPH.js} +6 -7
  17. package/dist/cli/{chunk-JLUHY5AB.js → chunk-OR2NHT5O.js} +4 -4
  18. package/dist/cli/chunk-S6BZV7AY.js +57 -0
  19. package/dist/cli/{chunk-CR3LGTZQ.js → chunk-TCQ5PYIF.js} +3 -3
  20. package/dist/cli/{chunk-RYZ6FHYT.js → chunk-UMQGRTA4.js} +17 -39
  21. package/dist/cli/{chunk-3CF4FQWG.js → chunk-VQR5ME75.js} +4 -3
  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-XTDE2URL.js +365 -0
  25. package/dist/cli/daemon-ICC6LBCO.js +16562 -0
  26. package/dist/cli/{decide-OSSTI3FE.js → decide-JSY46QZC.js} +14 -16
  27. package/dist/cli/{handover-2G7OEWIG.js → handover-5RUSRVAD.js} +5 -5
  28. package/dist/cli/{hook-XY4E5H7O.js → hook-QQP3ENGA.js} +87 -81
  29. package/dist/cli/index.js +40 -28
  30. package/dist/cli/launch-RTBGUJSC.js +28 -0
  31. package/dist/cli/{send-RUHGKLMA.js → send-BIIH6SQW.js} +5 -5
  32. package/dist/cli/{setup-K42IVMHS.js → setup-OLVRJLJE.js} +4 -4
  33. package/dist/cli/{status-4Z2VJFID.js → status-Z3I33KZJ.js} +3 -3
  34. package/dist/cli/{tui-4IRYQXYU.js → tui-AIXCETOB.js} +6036 -3629
  35. package/dist/cli/{uninstall-4IMMR55T.js → uninstall-X65CWHIJ.js} +4 -4
  36. package/dist/cli/{version-L2Y7MRH6.js → version-QMHFVP3N.js} +1 -1
  37. package/dist/cli/who-C2VMEM35.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
@@ -1,31 +1,23 @@
1
1
  import {
2
- EFFORT_ARGS,
3
- MODEL_ARGS
4
- } from "./chunk-OYDTU6AP.js";
2
+ realTmux
3
+ } from "./chunk-KBSXP63H.js";
5
4
  import {
6
- findRole
7
- } from "./chunk-JHFKBKSO.js";
5
+ log
6
+ } from "./chunk-G3ST5WKR.js";
8
7
  import {
9
8
  exitFile,
10
9
  spawnSession
11
- } from "./chunk-LG3XFIPV.js";
10
+ } from "./chunk-O4MUYFPH.js";
12
11
  import {
13
12
  DaemonRefusedError,
14
13
  connect
15
- } from "./chunk-VMGACWNP.js";
14
+ } from "./chunk-FWLT3SAN.js";
16
15
  import {
17
- PROTOCOL_PORT,
18
- autonomyArgs
19
- } from "./chunk-VKRFHDRK.js";
20
- import {
21
- realTmux
22
- } from "./chunk-NLL2EQJ2.js";
23
- import {
24
- log
25
- } from "./chunk-6IYPRUQG.js";
16
+ PROTOCOL_PORT
17
+ } from "./chunk-2X56ZRK7.js";
26
18
  import {
27
19
  ensureLedgeDir
28
- } from "./chunk-HOOHS3MX.js";
20
+ } from "./chunk-W724IVDC.js";
29
21
 
30
22
  // src/cli/launch.ts
31
23
  import { spawn } from "child_process";
@@ -36,30 +28,19 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
36
28
  function parseLaunchArgs(argv) {
37
29
  const args = [];
38
30
  let name;
39
- let role;
40
31
  for (let i = 0; i < argv.length; i++) {
41
32
  const a = argv[i];
42
33
  if (a === "--name") {
43
34
  name = argv[++i];
44
35
  continue;
45
36
  }
46
- if (a === "--role") {
47
- role = argv[++i];
48
- continue;
49
- }
50
37
  if (a === "--") {
51
38
  args.push(...argv.slice(i + 1));
52
39
  break;
53
40
  }
54
41
  args.push(a);
55
42
  }
56
- return { name, ...role ? { role } : {}, args };
57
- }
58
- function roleArgs(tool, role, args) {
59
- const own = role.tool === void 0 || role.tool === tool;
60
- const hasModel = args.some((a) => a === "--model" || a === "-m" || a.startsWith("--model="));
61
- const hasEffort = args.some((a) => a === "--effort" || a.startsWith("--effort=") || a.includes("model_reasoning_effort"));
62
- return [...own && role.model && !hasModel ? MODEL_ARGS[tool](role.model) : [], ...own && role.effort && !hasEffort ? EFFORT_ARGS[tool]?.(role.effort) ?? [] : [], ...autonomyArgs(tool, role.autonomy)];
43
+ return { name, args };
63
44
  }
64
45
  function portOpen(port) {
65
46
  return new Promise((res) => {
@@ -137,8 +118,7 @@ async function createSession(opts) {
137
118
  ids.push(s.id);
138
119
  }
139
120
  }
140
- const { role, ...rest } = opts;
141
- const session = await spawnSession({ ...rest, ...role ? { args: [...roleArgs(opts.tool, role, opts.args), ...opts.args], role: { id: role.id, label: role.label }, ...role.autonomy ? { autonomy: role.autonomy } : {} } : {}, taken: { names, ids } });
121
+ const session = await spawnSession({ ...opts, taken: { names, ids } });
142
122
  try {
143
123
  opts.client?.send({ type: "session.register", session });
144
124
  } catch (e) {
@@ -175,21 +155,20 @@ var UNTRACKED = {
175
155
  unreachable: () => "ledge: daemon not running, session not tracked"
176
156
  };
177
157
  async function launch(tool, argv, opts = {}) {
158
+ if (argv.some((a) => a === "--role" || a.startsWith("--role="))) {
159
+ console.error("--role a \xE9t\xE9 retir\xE9 (les \xE9quipes et r\xF4les de Ledge n'existent plus)");
160
+ return 1;
161
+ }
178
162
  await ensureLedgeDir();
179
- const { name, role: roleId, args } = parseLaunchArgs(argv);
163
+ const { name, args } = parseLaunchArgs(argv);
180
164
  const port = opts.port ?? PROTOCOL_PORT;
181
165
  const socket = opts.tmuxSocket ?? TMUX_SOCKET;
182
166
  const tmux = realTmux(socket);
183
167
  const cwd = opts.cwd ?? process.cwd();
184
- const role = roleId ? findRole(roleId, cwd) : void 0;
185
- if (roleId && !role) {
186
- console.error(`ledge: r\xF4le inconnu : ${roleId} (aucun fichier .ledge/roles/${roleId}.md ni ~/.ledge/roles/${roleId}.md)`);
187
- return 1;
188
- }
189
168
  const daemon = await ensureDaemon({ port, spawnDaemon: opts.spawnDaemon, retryDelayMs: opts.retryDelayMs });
190
169
  let client = daemon.client;
191
170
  if (!daemon.client) console.error(UNTRACKED[daemon.reason](port));
192
- const create = (c) => createSession({ tool, args, command: opts.command, recordExit: true, name, ...role ? { role } : {}, cwd, tmux, client: c });
171
+ const create = (c) => createSession({ tool, args, command: opts.command, recordExit: true, name, cwd, tmux, client: c });
193
172
  let session;
194
173
  try {
195
174
  session = await create(client);
@@ -223,7 +202,6 @@ async function launch(tool, argv, opts = {}) {
223
202
 
224
203
  export {
225
204
  parseLaunchArgs,
226
- roleArgs,
227
205
  ensureDaemon,
228
206
  DaemonError,
229
207
  createSession,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  CODEX_HOOK_VAR
3
- } from "./chunk-LG3XFIPV.js";
3
+ } from "./chunk-O4MUYFPH.js";
4
4
  import {
5
5
  connect,
6
6
  sendMessageAndAwaitAck
7
- } from "./chunk-VMGACWNP.js";
7
+ } from "./chunk-FWLT3SAN.js";
8
8
 
9
9
  // src/cli/send.ts
10
10
  import { randomUUID } from "crypto";
@@ -130,8 +130,9 @@ function parseLedgeCommand(command) {
130
130
  if (words[1] === "who" && words.length === 2) return { cmd: "who" };
131
131
  if (words[1] === "send") return { cmd: "send", args: words.slice(2) };
132
132
  if (words[1] === "read") return { cmd: "read", args: words.slice(2) };
133
- if (words[1] === "task") return { cmd: "task", args: words.slice(2) };
133
+ if (words[1] === "ask") return { cmd: "ask", args: words.slice(2) };
134
134
  if (words[1] === "handover") return { cmd: "handover", args: words.slice(2) };
135
+ if (words[1] === "brain") return { cmd: "brain", args: words.slice(2) };
135
136
  return void 0;
136
137
  }
137
138
 
@@ -17,7 +17,7 @@ function ledgeDir() {
17
17
  }
18
18
  function paths() {
19
19
  const dir = ledgeDir();
20
- return { dir, tmuxConf: join(dir, "tmux.conf"), journal: join(dir, "sessions.json"), log: join(dir, "daemon.log"), pid: join(dir, "daemon.pid"), history: join(dir, "history"), mail: join(dir, "mail.json"), tasks: join(dir, "tasks.json"), teams: join(dir, "teams-live.json"), machines: join(dir, "machines.json"), projects: join(dir, "projects.json"), island: join(dir, "island.json"), token: join(dir, "token"), mobile: join(dir, "mobile.json"), mobileSession: join(dir, "mobile-session.json"), mobileKey: join(dir, "mobile-key"), devices: join(dir, "devices.json"), revocations: join(dir, "revocations.json"), attachments: join(dir, "attachments") };
20
+ return { dir, tmuxConf: join(dir, "tmux.conf"), journal: join(dir, "sessions.json"), log: join(dir, "daemon.log"), pid: join(dir, "daemon.pid"), history: join(dir, "history"), mail: join(dir, "mail.json"), asks: join(dir, "asks.json"), machines: join(dir, "machines.json"), projects: join(dir, "projects.json"), island: join(dir, "island.json"), token: join(dir, "token"), mobile: join(dir, "mobile.json"), mobileSession: join(dir, "mobile-session.json"), mobileKey: join(dir, "mobile-key"), devices: join(dir, "devices.json"), revocations: join(dir, "revocations.json"), attachments: join(dir, "attachments"), openrig: join(dir, "openrig.json"), ledgeThread: join(dir, "ledge-thread.jsonl"), rigs: join(dir, "rigs"), rigLaunches: join(dir, "rig-launches.json"), mandate: join(dir, "mandat.md"), memoryDir: join(dir, "memoire"), rules: join(dir, "memoire", "rules.json"), decisions: join(dir, "memoire", "decisions.jsonl"), brainSettings: join(dir, "brain.json"), brainDir: join(dir, "brain") };
21
21
  }
22
22
  async function ensureLedgeDir() {
23
23
  const p = paths();
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  run
3
- } from "./chunk-NLL2EQJ2.js";
3
+ } from "./chunk-KBSXP63H.js";
4
4
  import {
5
5
  ensureLedgeDir,
6
6
  paths
7
- } from "./chunk-HOOHS3MX.js";
7
+ } from "./chunk-W724IVDC.js";
8
8
 
9
9
  // src/cli/setup.ts
10
10
  import { readFile as readFile2, writeFile, mkdir, rename, realpath, rm, stat, lstat, readlink } from "fs/promises";
@@ -0,0 +1,365 @@
1
+ import {
2
+ PROJECT_COLORS,
3
+ RIG_NAME,
4
+ parseServerMessage
5
+ } from "./chunk-2X56ZRK7.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
+ };