@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
@@ -1,10 +1,10 @@
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
6
  sendMessageAndAwaitAck
7
- } from "./chunk-VMGACWNP.js";
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] === "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
 
@@ -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-DEOQT7IM.js";
12
11
  import {
13
12
  DaemonRefusedError,
14
13
  connect
15
- } from "./chunk-VMGACWNP.js";
14
+ } from "./chunk-X6NV2IEM.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-G6ESCKEX.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,13 +1,13 @@
1
1
  import {
2
2
  log
3
- } from "./chunk-6IYPRUQG.js";
3
+ } from "./chunk-G3ST5WKR.js";
4
4
  import {
5
5
  paths
6
- } from "./chunk-HOOHS3MX.js";
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
- let r = await runFile(file, argv, clientEnv);
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("tmux", args, clientEnv);
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-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
  SESSION_KEY_VAR,
9
9
  actionById
10
- } from "./chunk-VKRFHDRK.js";
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-LG3XFIPV.js";
3
+ } from "./chunk-DEOQT7IM.js";
4
4
  import {
5
5
  cleanTopic,
6
- isArchived,
6
+ mandateEntries,
7
7
  sessionTitle
8
- } from "./chunk-VKRFHDRK.js";
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
- function formatTeamNotice(n) {
117
- return `<ledge-notice team="${attr(n.team)}">${ledgeBody(n.text)}</ledge-notice>`;
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 formatRoleNotice(n) {
120
- return `<ledge-notice role="${attr(n.role)}">${ledgeBody(n.text)}</ledge-notice>`;
121
- }
122
- var TEAM_NOTICE = /^<ledge-notice\b([^>]*\bteam="[^"]*"[^>]*)>([\s\S]*?)(?:<\/ledge-notice>|$)/;
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) || !!ledgeTask(ev.text) || !!ledgeTeamNotice(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
- formatTeamNotice,
651
- formatRoleNotice,
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
+ };
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.29.0-snapshot.41306023381.1" : "0.0.0-dev";
3
+
4
+ export {
5
+ LEDGE_VERSION
6
+ };
@@ -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";
@@ -4,7 +4,7 @@ import {
4
4
  SUMMARY_MAX,
5
5
  parseServerMessage,
6
6
  readDaemonToken
7
- } from "./chunk-VKRFHDRK.js";
7
+ } from "./chunk-G6ESCKEX.js";
8
8
 
9
9
  // src/client.ts
10
10
  import WebSocket from "ws";