@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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CODEX_HOOK_VAR
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DEOQT7IM.js";
|
|
4
4
|
import {
|
|
5
5
|
connect,
|
|
6
6
|
sendMessageAndAwaitAck
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-X6NV2IEM.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] === "
|
|
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
|
|
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-OYDTU6AP.js";
|
|
2
|
+
realTmux
|
|
3
|
+
} from "./chunk-KBSXP63H.js";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
5
|
+
log
|
|
6
|
+
} from "./chunk-G3ST5WKR.js";
|
|
8
7
|
import {
|
|
9
8
|
exitFile,
|
|
10
9
|
spawnSession
|
|
11
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DEOQT7IM.js";
|
|
12
11
|
import {
|
|
13
12
|
DaemonRefusedError,
|
|
14
13
|
connect
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-X6NV2IEM.js";
|
|
16
15
|
import {
|
|
17
|
-
PROTOCOL_PORT
|
|
18
|
-
|
|
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-G6ESCKEX.js";
|
|
26
18
|
import {
|
|
27
19
|
ensureLedgeDir
|
|
28
|
-
} from "./chunk-
|
|
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,
|
|
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 {
|
|
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,
|
|
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,
|
|
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,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-G3ST5WKR.js";
|
|
4
4
|
import {
|
|
5
5
|
paths
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-W724IVDC.js";
|
|
7
7
|
|
|
8
8
|
// src/tmux.ts
|
|
9
9
|
import { execFile } from "child_process";
|
|
10
|
-
import { existsSync } from "fs";
|
|
10
|
+
import { accessSync, constants, existsSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
function runFile(file, args, env, opts) {
|
|
13
13
|
return new Promise((resolve) => {
|
|
@@ -18,6 +18,18 @@ function runFile(file, args, env, opts) {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
var run = (args, env) => runFile("tmux", args, env);
|
|
21
|
+
function daemonTmux() {
|
|
22
|
+
for (const dir of (process.env.PATH ?? "").split(":")) {
|
|
23
|
+
if (!dir) continue;
|
|
24
|
+
const candidate = join(dir, "tmux");
|
|
25
|
+
try {
|
|
26
|
+
accessSync(candidate, constants.X_OK);
|
|
27
|
+
return candidate;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return "tmux";
|
|
32
|
+
}
|
|
21
33
|
function systemdAvailable(env = process.env, exists = existsSync) {
|
|
22
34
|
return !!env.XDG_RUNTIME_DIR && exists(join(env.XDG_RUNTIME_DIR, "systemd", "private"));
|
|
23
35
|
}
|
|
@@ -38,10 +50,13 @@ function realTmux(socket = "ledge", opts = {}) {
|
|
|
38
50
|
const serverUp = (await t(["list-sessions"])).code === 0;
|
|
39
51
|
const scoped = !serverUp && await scope();
|
|
40
52
|
const { file, args: argv } = newSessionCommand(args, scoped, `ledge-tmux-${Date.now()}`);
|
|
41
|
-
|
|
53
|
+
const binary = daemonTmux();
|
|
54
|
+
const executable = file === "tmux" ? binary : file;
|
|
55
|
+
const commandArgs = file === "systemd-run" ? argv.map((a, i) => a === "tmux" && argv[i - 1] === "--" ? binary : a) : argv;
|
|
56
|
+
let r = await runFile(executable, commandArgs, clientEnv);
|
|
42
57
|
if (r.code !== 0 && scoped) {
|
|
43
58
|
log(`tmux: scoped start failed (${r.stderr.trim()}), starting unscoped`);
|
|
44
|
-
r = await runFile(
|
|
59
|
+
r = await runFile(binary, args, clientEnv);
|
|
45
60
|
}
|
|
46
61
|
if (r.code !== 0) throw new Error(`tmux new-session failed: ${r.stderr.trim()}`);
|
|
47
62
|
},
|
|
@@ -55,8 +70,8 @@ function realTmux(socket = "ledge", opts = {}) {
|
|
|
55
70
|
const r = await t(["send-keys", "-t", `=${name}:`, ...keys]);
|
|
56
71
|
if (r.code !== 0) throw new Error(`tmux send-keys failed: ${r.stderr.trim()}`);
|
|
57
72
|
},
|
|
58
|
-
async capture(name, lines) {
|
|
59
|
-
const r = await t(["capture-pane", "-p", "-J", "-t", `=${name}:`, "-S", String(-lines)]);
|
|
73
|
+
async capture(name, lines, o) {
|
|
74
|
+
const r = await t(["capture-pane", "-p", "-J", ...o?.color ? ["-e"] : [], "-t", `=${name}:`, "-S", String(-lines)]);
|
|
60
75
|
if (r.code !== 0) return null;
|
|
61
76
|
return r.stdout.replace(/\s+$/g, "").split("\n").slice(-lines).join("\n");
|
|
62
77
|
},
|
|
@@ -1,13 +1,13 @@
|
|
|
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
|
SESSION_KEY_VAR,
|
|
9
9
|
actionById
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-G6ESCKEX.js";
|
|
11
11
|
|
|
12
12
|
// src/cli/action.ts
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
@@ -1,47 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ledgeTitleRest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DEOQT7IM.js";
|
|
4
4
|
import {
|
|
5
5
|
cleanTopic,
|
|
6
|
-
|
|
6
|
+
mandateEntries,
|
|
7
7
|
sessionTitle
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
|
|
10
|
-
// src/daemon/seat.ts
|
|
11
|
-
function teammates(team, sessions) {
|
|
12
|
-
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
13
|
-
return team.seats.flatMap((seat) => {
|
|
14
|
-
const s = seat.sessionId ? byId.get(seat.sessionId) : void 0;
|
|
15
|
-
return s && s.state !== "exited" && !isArchived(s) ? [{ name: s.name, role: seat.role }] : [];
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function seatContext(o) {
|
|
19
|
-
const id = o.role?.id ?? o.roleId;
|
|
20
|
-
const label = o.role?.label ?? o.roleLabel ?? id;
|
|
21
|
-
const lines = [];
|
|
22
|
-
if (id) lines.push(`Ton r\xF4le : ${label} (${id})${o.team ? `, dans l'\xE9quipe ${o.team.name}` : ""}.`);
|
|
23
|
-
if (o.role?.prompt.trim()) lines.push(o.role.prompt.trim());
|
|
24
|
-
if (o.team) {
|
|
25
|
-
const members = o.team.members.map((m) => `${m.name} (${m.role})`).join(", ") || "personne d'autre pour l'instant";
|
|
26
|
-
lines.push(
|
|
27
|
-
`Ton \xE9quipe : ${members}. \xC9cris-leur par leur r\xF4le : ledge send @build "\u2026", ledge task give @qa "\u2026".`,
|
|
28
|
-
`L'op\xE9rateur est Ledge : il t'a confi\xE9 ou te confiera ton travail par une <ledge-task>. Travaille en autonomie avec ton \xE9quipe ; ne t'arr\xEAte pour l'utilisateur que pour une d\xE9cision qui lui revient. Termine toujours une t\xE2che re\xE7ue (ledge task done <id> "<r\xE9sultat>", ou refuse) : c'est ainsi que Ledge et l'utilisateur savent o\xF9 en est l'\xE9quipe.`
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return lines.join("\n");
|
|
32
|
-
}
|
|
33
|
-
function resolveRoleAddress(to, from, sessions, teams) {
|
|
34
|
-
if (!to.startsWith("@")) return { to };
|
|
35
|
-
const role = to.slice(1);
|
|
36
|
-
const team = teams.find((t) => t.state !== "dissolved" && t.seats.some((s) => s.sessionId === from));
|
|
37
|
-
if (!team) return { reason: `${to} n'existe que dans une \xE9quipe` };
|
|
38
|
-
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
39
|
-
const held = team.seats.filter((s) => s.role === role && s.sessionId).map((s) => byId.get(s.sessionId)).filter((s) => !!s && s.state !== "exited" && !isArchived(s));
|
|
40
|
-
if (held.length === 1) return { to: held[0].name };
|
|
41
|
-
if (held.length > 1) return { reason: `plusieurs sessions tiennent ${to} dans ${team.name} : ${held.map((s) => s.name).join(", ")}` };
|
|
42
|
-
const roles = teammates(team, sessions).map((m) => `@${m.role}`);
|
|
43
|
-
return { reason: `personne ne tient ${to} dans l'\xE9quipe ${team.name}${roles.length ? ` (r\xF4les tenus : ${roles.join(", ")})` : ""}` };
|
|
44
|
-
}
|
|
8
|
+
} from "./chunk-G6ESCKEX.js";
|
|
45
9
|
|
|
46
10
|
// src/daemon/crossSession.ts
|
|
47
11
|
import { createHash } from "crypto";
|
|
@@ -113,30 +77,14 @@ function formatLedgeNotice(n) {
|
|
|
113
77
|
const quote = ledgeBody(flat.length > NOTICE_QUOTE ? `${flat.slice(0, NOTICE_QUOTE)}\u2026` : flat);
|
|
114
78
|
return `<ledge-notice to="${to}">Ledge n'a pas pu remettre ton message \xE0 ${to} (\xAB ${quote}${NOTICE_REASON}${ledgeBody(n.reason)}</ledge-notice>`;
|
|
115
79
|
}
|
|
116
|
-
|
|
117
|
-
|
|
80
|
+
var ANSWER_BLOCK = /^<ledge-answer\b([^>]*)>([\s\S]*?)(?:<\/ledge-answer>|$)/;
|
|
81
|
+
function formatLedgeAnswer(a) {
|
|
82
|
+
return `<ledge-answer id="${attr(a.id)}">R\xE9ponse de l'utilisateur \xE0 \xAB ${ledgeBody(a.question)} \xBB : ${ledgeBody(a.text)}</ledge-answer>`;
|
|
118
83
|
}
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
function ledgeTeamNotice(text) {
|
|
124
|
-
const m = TEAM_NOTICE.exec(text.trimStart());
|
|
125
|
-
if (!m) return void 0;
|
|
126
|
-
const team = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]])).get("team");
|
|
127
|
-
return team ? { team, text: m[2].trim() } : void 0;
|
|
128
|
-
}
|
|
129
|
-
var TASK_BLOCK = /^<ledge-task\b([^>]*)>([\s\S]*?)(?:<\/ledge-task>|$)/;
|
|
130
|
-
function formatLedgeTask(t) {
|
|
131
|
-
return `<ledge-task id="${attr(t.id)}" from="${attr(t.from)}">${ledgeBody(t.text)}</ledge-task>`;
|
|
132
|
-
}
|
|
133
|
-
function ledgeTask(text) {
|
|
134
|
-
const m = TASK_BLOCK.exec(text.trimStart());
|
|
135
|
-
if (!m) return void 0;
|
|
136
|
-
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
137
|
-
const id = attrs.get("id");
|
|
138
|
-
const from = attrs.get("from");
|
|
139
|
-
return id ? { id, ...from ? { from } : {}, text: m[2].trim() } : void 0;
|
|
84
|
+
function ledgeAnswer(text) {
|
|
85
|
+
const m = ANSWER_BLOCK.exec(text.trimStart());
|
|
86
|
+
const id = m ? new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]])).get("id") : void 0;
|
|
87
|
+
return id ? { id, text: m[2].trim() } : void 0;
|
|
140
88
|
}
|
|
141
89
|
function ledgeNotice(text) {
|
|
142
90
|
const m = NOTICE_BLOCK.exec(text.trimStart());
|
|
@@ -491,7 +439,7 @@ function reduceEvent(s, ev) {
|
|
|
491
439
|
case "PreToolUse":
|
|
492
440
|
case "PermissionRequest":
|
|
493
441
|
case "Notification": {
|
|
494
|
-
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text)
|
|
442
|
+
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text));
|
|
495
443
|
if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !notice2 && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
|
|
496
444
|
if (s.pending?.source === "rpc" && ev.name === "PreToolUse") return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
497
445
|
const incoming = pendingFor(ev);
|
|
@@ -634,10 +582,89 @@ function takeImages(text, dir) {
|
|
|
634
582
|
return { text: rest.trim(), ids };
|
|
635
583
|
}
|
|
636
584
|
|
|
585
|
+
// src/daemon/brain/mandate.ts
|
|
586
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
587
|
+
import { dirname as dirname2 } from "path";
|
|
588
|
+
var DEFAULT_MANDATE = `# Le mandat de Ledge
|
|
589
|
+
|
|
590
|
+
Ledge tranche seul ce qui est dans la premi\xE8re liste. Tout le reste te revient.
|
|
591
|
+
Une ligne = une cat\xE9gorie : \`- <id> : <description>\`. Les cat\xE9gories design, architecture et critique te reviennent toujours, m\xEAme d\xE9plac\xE9es.
|
|
592
|
+
|
|
593
|
+
## Ledge tranche seul
|
|
594
|
+
- git : git et branches
|
|
595
|
+
- tests : tests et relectures
|
|
596
|
+
- context : contexte et rel\xE8ves
|
|
597
|
+
- code : petits choix de code
|
|
598
|
+
|
|
599
|
+
## Te revient toujours
|
|
600
|
+
- design : design (UI, copie)
|
|
601
|
+
- architecture : architecture (stockage, API)
|
|
602
|
+
- critical : critique (donn\xE9es, s\xE9curit\xE9, suppression, co\xFBts)
|
|
603
|
+
`;
|
|
604
|
+
var SELF_TITLE = "ledge tranche seul";
|
|
605
|
+
var USER_TITLE = "te revient toujours";
|
|
606
|
+
function escalateCategory(text, c) {
|
|
607
|
+
const lines = text.split("\n");
|
|
608
|
+
const isLine = (l) => new RegExp(`^\\s*[-*]\\s+${c}\\s+:`).test(l);
|
|
609
|
+
let list = null;
|
|
610
|
+
const moved = [];
|
|
611
|
+
let userHeadingAt = -1;
|
|
612
|
+
const kept = [];
|
|
613
|
+
for (const l of lines) {
|
|
614
|
+
if (l.trim().startsWith("#")) {
|
|
615
|
+
const title = l.replace(/^\s*#+\s*/, "").toLowerCase();
|
|
616
|
+
list = title.startsWith(SELF_TITLE) ? "self" : title.startsWith(USER_TITLE) ? "user" : null;
|
|
617
|
+
if (list === "user") userHeadingAt = kept.length;
|
|
618
|
+
} else if (list === "self" && isLine(l)) {
|
|
619
|
+
moved.push(l.trim());
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
kept.push(l);
|
|
623
|
+
}
|
|
624
|
+
if (moved.length === 0) return text;
|
|
625
|
+
if (userHeadingAt < 0) return `${kept.join("\n").trimEnd()}
|
|
626
|
+
|
|
627
|
+
## Te revient toujours
|
|
628
|
+
${moved[0]}
|
|
629
|
+
`;
|
|
630
|
+
let at = userHeadingAt + 1;
|
|
631
|
+
for (let i = at; i < kept.length && !kept[i].trim().startsWith("#"); i++) if (/^\s*[-*]\s+/.test(kept[i])) at = i + 1;
|
|
632
|
+
kept.splice(at, 0, moved[0]);
|
|
633
|
+
return kept.join("\n");
|
|
634
|
+
}
|
|
635
|
+
async function ensureMandate(path) {
|
|
636
|
+
try {
|
|
637
|
+
return await readFile(path, "utf8");
|
|
638
|
+
} catch (err) {
|
|
639
|
+
if (err.code !== "ENOENT") throw err;
|
|
640
|
+
}
|
|
641
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
642
|
+
await writeFile(path, DEFAULT_MANDATE);
|
|
643
|
+
return DEFAULT_MANDATE;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/daemon/openrig/seatContext.ts
|
|
647
|
+
var DEFAULT_SELF = "git et branches, tests et relectures, contexte et rel\xE8ves, petits choix de code";
|
|
648
|
+
var DEFAULT_USER = "design, architecture, critique (donn\xE9es, s\xE9curit\xE9, suppression, co\xFBts)";
|
|
649
|
+
var sentences = (self, user) => `Ledge tranche seul : ${typeof self === "string" ? self : self.join(", ")}.
|
|
650
|
+
Te reviennent toujours, via Ledge, \xE0 l'utilisateur : ${typeof user === "string" ? user : user.join(", ")}.`;
|
|
651
|
+
function mandateSummary(text) {
|
|
652
|
+
if (text === null) return sentences(DEFAULT_SELF, DEFAULT_USER);
|
|
653
|
+
const e = mandateEntries(text);
|
|
654
|
+
const toUser = [...e.user, ...e.always].map((x) => x.said);
|
|
655
|
+
if (e.self.length === 0) return "Ledge ne tranche rien seul : tout revient, via Ledge, \xE0 l'utilisateur.";
|
|
656
|
+
return sentences(e.self.map((x) => x.said), toUser.length ? toUser : DEFAULT_USER);
|
|
657
|
+
}
|
|
658
|
+
function rigSeatContext(p) {
|
|
659
|
+
return [
|
|
660
|
+
`Tu es ${p.seat}, si\xE8ge du rig ${p.rig}.`,
|
|
661
|
+
`Ton humain est Ledge (${p.human}) : il lit tes escalades et tranche pour l'utilisateur, ou les lui remonte.`,
|
|
662
|
+
`Pour une d\xE9cision qui ne t'appartient pas, escalade vers ${p.human} avec le skill messaging-the-human d'OpenRig (\`rig queue create --destination ${p.human} --human-intent decision \u2026\`).`,
|
|
663
|
+
p.mandate
|
|
664
|
+
].join("\n");
|
|
665
|
+
}
|
|
666
|
+
|
|
637
667
|
export {
|
|
638
|
-
teammates,
|
|
639
|
-
seatContext,
|
|
640
|
-
resolveRoleAddress,
|
|
641
668
|
crossSession,
|
|
642
669
|
messageNote,
|
|
643
670
|
stripNote,
|
|
@@ -647,11 +674,8 @@ export {
|
|
|
647
674
|
formatLedgeMessage,
|
|
648
675
|
ledgeMessages,
|
|
649
676
|
formatLedgeNotice,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
ledgeTeamNotice,
|
|
653
|
-
formatLedgeTask,
|
|
654
|
-
ledgeTask,
|
|
677
|
+
formatLedgeAnswer,
|
|
678
|
+
ledgeAnswer,
|
|
655
679
|
ledgeNotice,
|
|
656
680
|
crossSessionNotices,
|
|
657
681
|
Refusal,
|
|
@@ -668,6 +692,7 @@ export {
|
|
|
668
692
|
answeredBy,
|
|
669
693
|
reduce,
|
|
670
694
|
repositoryId,
|
|
695
|
+
execGit,
|
|
671
696
|
gitInfo,
|
|
672
697
|
gitSummary,
|
|
673
698
|
readPeers,
|
|
@@ -675,5 +700,10 @@ export {
|
|
|
675
700
|
ledgePeer,
|
|
676
701
|
resolveTarget,
|
|
677
702
|
withImages,
|
|
678
|
-
takeImages
|
|
703
|
+
takeImages,
|
|
704
|
+
DEFAULT_MANDATE,
|
|
705
|
+
escalateCategory,
|
|
706
|
+
ensureMandate,
|
|
707
|
+
mandateSummary,
|
|
708
|
+
rigSeatContext
|
|
679
709
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DAEMON_DOWN,
|
|
3
|
+
callerOf
|
|
4
|
+
} from "./chunk-LQCWBJYE.js";
|
|
5
|
+
import {
|
|
6
|
+
connect
|
|
7
|
+
} from "./chunk-X6NV2IEM.js";
|
|
8
|
+
import {
|
|
9
|
+
BRAIN_OPS
|
|
10
|
+
} from "./chunk-G6ESCKEX.js";
|
|
11
|
+
|
|
12
|
+
// src/cli/brain.ts
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
var NOT_BRAIN_LINE = "ledge brain ne marche que dans la session du cerveau de Ledge.";
|
|
15
|
+
var BRAIN_USAGE = "ledge brain <say|decide|ask|rig|send|propose|remember|who|rigs|escalations|rules|handover> \u2026 only from Ledge's brain session";
|
|
16
|
+
var isOp = (s) => BRAIN_OPS.includes(s ?? "");
|
|
17
|
+
async function brainOnClient(client, args, by, key, ms) {
|
|
18
|
+
const [op, ...rest] = args;
|
|
19
|
+
if (!isOp(op) || rest.length > 40) return { line: BRAIN_USAGE, code: 1 };
|
|
20
|
+
const rid = randomUUID();
|
|
21
|
+
const deadline = Date.now() + ms;
|
|
22
|
+
try {
|
|
23
|
+
client.send({ type: "brain.cmd", rid, by, key: key ?? "", op, args: rest });
|
|
24
|
+
for (; ; ) {
|
|
25
|
+
const r = await client.next("brain.result", Math.max(0, deadline - Date.now()));
|
|
26
|
+
if (r.rid === rid) return { line: r.line, code: r.ok ? 0 : 1 };
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var BRAIN_MS = 6e4;
|
|
33
|
+
async function runBrain(args, o = {}) {
|
|
34
|
+
const env = o.env ?? process.env;
|
|
35
|
+
const caller = callerOf(env);
|
|
36
|
+
if (!env.LEDGE_BRAIN || !caller) return { line: NOT_BRAIN_LINE, code: 1 };
|
|
37
|
+
if (!isOp(args[0]) || args.length > 41) return { line: BRAIN_USAGE, code: 1 };
|
|
38
|
+
let client;
|
|
39
|
+
try {
|
|
40
|
+
client = await connect({ port: o.port, timeoutMs: 1500 });
|
|
41
|
+
} catch {
|
|
42
|
+
return { line: DAEMON_DOWN, code: 1 };
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return await brainOnClient(client, args, caller.by, caller.key, BRAIN_MS);
|
|
46
|
+
} finally {
|
|
47
|
+
await client.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
NOT_BRAIN_LINE,
|
|
53
|
+
BRAIN_USAGE,
|
|
54
|
+
brainOnClient,
|
|
55
|
+
BRAIN_MS,
|
|
56
|
+
runBrain
|
|
57
|
+
};
|
|
@@ -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"),
|
|
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-
|
|
3
|
+
} from "./chunk-KBSXP63H.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureLedgeDir,
|
|
6
6
|
paths
|
|
7
|
-
} from "./chunk-
|
|
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";
|