@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.
- package/README.md +8 -0
- package/dist/cli/{action-NQRSZEC5.js → action-MXRYKL5N.js} +5 -5
- package/dist/cli/ask-HIKSAHRR.js +17 -0
- package/dist/cli/{attach-KXOSFUHO.js → attach-J65WTS3H.js} +9 -14
- package/dist/cli/brain-XOGS4OPT.js +20 -0
- package/dist/cli/{bridge-XCTXVWKH.js → bridge-YZL5NT7Q.js} +2 -2
- package/dist/cli/{chunk-3NQNWGDX.js → chunk-3U6TAEIO.js} +3 -5
- package/dist/cli/chunk-66ZLZ2OW.js +365 -0
- package/dist/cli/{chunk-JLUHY5AB.js → chunk-7XFJ5QCZ.js} +4 -4
- package/dist/cli/chunk-BP7NGWYD.js +57 -0
- package/dist/cli/{chunk-LG3XFIPV.js → chunk-DEOQT7IM.js} +24 -7
- package/dist/cli/chunk-DJMFCYF4.js +60 -0
- package/dist/cli/{chunk-6IYPRUQG.js → chunk-G3ST5WKR.js} +1 -1
- package/dist/cli/{chunk-VKRFHDRK.js → chunk-G6ESCKEX.js} +1124 -1164
- package/dist/cli/{chunk-3CF4FQWG.js → chunk-J67O4QQG.js} +4 -3
- package/dist/cli/{chunk-RYZ6FHYT.js → chunk-JFXRDHZR.js} +17 -39
- package/dist/cli/{chunk-NLL2EQJ2.js → chunk-KBSXP63H.js} +22 -7
- package/dist/cli/{chunk-CR3LGTZQ.js → chunk-LQCWBJYE.js} +3 -3
- package/dist/cli/{chunk-ZI47U5CQ.js → chunk-MJTMM6GC.js} +102 -72
- package/dist/cli/chunk-NEYGPVEZ.js +57 -0
- package/dist/cli/chunk-OFY6GEYS.js +6 -0
- package/dist/cli/{chunk-HOOHS3MX.js → chunk-W724IVDC.js} +1 -1
- package/dist/cli/{chunk-K7UEKSB2.js → chunk-WYRQZA4G.js} +2 -2
- package/dist/cli/{chunk-VMGACWNP.js → chunk-X6NV2IEM.js} +1 -1
- package/dist/cli/daemon-JPKL3IAU.js +16576 -0
- package/dist/cli/{decide-OSSTI3FE.js → decide-2LECM6BO.js} +14 -16
- package/dist/cli/{handover-2G7OEWIG.js → handover-ZFNRGTTV.js} +5 -5
- package/dist/cli/{hook-XY4E5H7O.js → hook-TZXMVYOG.js} +87 -81
- package/dist/cli/index.js +40 -28
- package/dist/cli/launch-HGECZQCL.js +28 -0
- package/dist/cli/{send-RUHGKLMA.js → send-4TPIJG7O.js} +5 -5
- package/dist/cli/{setup-K42IVMHS.js → setup-OLVRJLJE.js} +4 -4
- package/dist/cli/{status-4Z2VJFID.js → status-C6I23SKG.js} +3 -3
- package/dist/cli/{tui-4IRYQXYU.js → tui-RD5JZLSU.js} +6008 -3597
- package/dist/cli/{uninstall-4IMMR55T.js → uninstall-X65CWHIJ.js} +4 -4
- package/dist/cli/{version-L2Y7MRH6.js → version-SU3VF37X.js} +1 -1
- package/dist/cli/who-JXTN6IDB.js +12 -0
- package/package.json +21 -6
- package/dist/cli/chunk-CIQAFCX3.js +0 -118
- package/dist/cli/chunk-JHFKBKSO.js +0 -159
- package/dist/cli/chunk-OYDTU6AP.js +0 -9482
- package/dist/cli/chunk-QSRB3F5Y.js +0 -198
- package/dist/cli/chunk-UME2F7VM.js +0 -6
- package/dist/cli/daemon-ZUHJKLSL.js +0 -2322
- package/dist/cli/launch-7WOARLZM.js +0 -35
- package/dist/cli/task-2GESYTOL.js +0 -19
- package/dist/cli/team-MTSKNMAE.js +0 -205
- 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-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
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-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
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-
|
|
11
|
+
} from "./chunk-X6NV2IEM.js";
|
|
13
12
|
import {
|
|
14
13
|
PROTOCOL_PORT,
|
|
15
14
|
backendOf
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
11
|
-
import "./chunk-
|
|
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-
|
|
3
|
+
} from "./chunk-X6NV2IEM.js";
|
|
4
4
|
import {
|
|
5
5
|
isArchived
|
|
6
|
-
} from "./chunk-
|
|
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",
|
|
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-
|
|
3
|
+
} from "./chunk-DEOQT7IM.js";
|
|
4
4
|
import {
|
|
5
5
|
connect
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-X6NV2IEM.js";
|
|
7
7
|
import {
|
|
8
8
|
BRIEF_MAX
|
|
9
|
-
} from "./chunk-
|
|
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
|
|
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-
|
|
6
|
+
} from "./chunk-G6ESCKEX.js";
|
|
7
7
|
import {
|
|
8
8
|
paths
|
|
9
|
-
} from "./chunk-
|
|
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)
|
|
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.
|
|
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.
|
|
250
|
-
...opts.
|
|
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,
|