@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,27 +1,25 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-VMGACWNP.js";
3
+ } from "./chunk-FWLT3SAN.js";
4
4
  import {
5
5
  isArchived
6
- } from "./chunk-VKRFHDRK.js";
6
+ } from "./chunk-2X56ZRK7.js";
7
7
 
8
8
  // src/cli/who.ts
9
9
  import { basename } from "path";
10
10
  function formatWho(sessions, self) {
11
11
  const live = sessions.filter((s) => s.state !== "exited" && !isArchived(s));
12
12
  if (live.length === 0) return "aucune autre session";
13
- const roles = live.some((s) => s.role);
14
13
  const rows = live.map((s) => [
15
14
  s.id === self ? "*" : " ",
16
15
  s.name,
17
- ...roles ? [s.role ? `@${s.role}` : "-"] : [],
18
16
  s.tool,
19
17
  s.state,
20
18
  `${s.repo ?? s.project}${s.worktree ? ` (${basename(s.cwd)})` : ""}`,
21
19
  s.branch ?? "-",
22
20
  `${s.topic ?? "-"}${s.source === "hook_only" ? " \xB7 lecture seule" : ""}`
23
21
  ]);
24
- const head = [" ", "NOM", ...roles ? ["R\xD4LE"] : [], "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
22
+ const head = [" ", "NOM", "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
25
23
  const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
26
24
  return [head, ...rows].map((r) => r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i])).join(" ")).join("\n");
27
25
  }
@@ -0,0 +1,60 @@
1
+ // src/daemon/openrig/facts.ts
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ var OPENRIG_MIN = "0.6.14";
5
+ var SUPPORTED_LINE = "0.6";
6
+ var DEFAULT_PORT = 7433;
7
+ var INSTALL_COMMAND = "npm install -g @openrig/cli@0.6.14 && rig setup";
8
+ var REGISTRATION = "external";
9
+ var HUMAN_ID = "ledge";
10
+ var HUMAN_ADDRESS = REGISTRATION === "external" ? "ledge@external" : "human-ledge@host";
11
+ var HUMAN_BINDING = `slack:ledge-decoy:file:${join(tmpdir(), "ledge-decoy-slack.env")}:primary`;
12
+ var OPERATOR_HUMAN_SESSIONS = ["operator-human@kernel"];
13
+ var HUMAN_GATE_TIER = "human-gate";
14
+ var ANSWER_PATH = "reply-and-approve";
15
+ var EVENTS = {
16
+ queueCreated: "queue.created",
17
+ queueUpdated: "queue.updated",
18
+ queueClaimed: "queue.claimed",
19
+ missionControlAction: "mission_control.action_executed",
20
+ rigImported: "rig.imported",
21
+ bootstrapCompleted: "bootstrap.completed",
22
+ nodeLaunched: "node.launched",
23
+ nodeStartupReady: "node.startup_ready",
24
+ seatActivity: "seat.activity_changed"
25
+ };
26
+ var EVENTS_UNOBSERVED = {
27
+ queueHandedOff: "queue.handed_off",
28
+ sessionDetached: "session.detached",
29
+ sessionStopped: "session.stopped",
30
+ rigDeleted: "rig.deleted",
31
+ rigStopped: "rig.stopped",
32
+ nodeAdded: "node.added",
33
+ nodeRemoved: "node.removed"
34
+ };
35
+ var OUTBOX_EVENT = null;
36
+ var OPEN_QUEUE_STATES = ["pending", "in-progress", "blocked"];
37
+ var QUEUE_ROW = { questions: "humanQuestions", intent: "humanIntent", evidence: "evidenceRef", replyTo: "replyTo", body: "body" };
38
+ function openrigBase(env) {
39
+ const n = Number(env.OPENRIG_PORT);
40
+ return `http://127.0.0.1:${Number.isInteger(n) && n > 0 && n < 65536 ? n : DEFAULT_PORT}`;
41
+ }
42
+
43
+ export {
44
+ OPENRIG_MIN,
45
+ SUPPORTED_LINE,
46
+ INSTALL_COMMAND,
47
+ REGISTRATION,
48
+ HUMAN_ID,
49
+ HUMAN_ADDRESS,
50
+ HUMAN_BINDING,
51
+ OPERATOR_HUMAN_SESSIONS,
52
+ HUMAN_GATE_TIER,
53
+ ANSWER_PATH,
54
+ EVENTS,
55
+ EVENTS_UNOBSERVED,
56
+ OUTBOX_EVENT,
57
+ OPEN_QUEUE_STATES,
58
+ QUEUE_ROW,
59
+ openrigBase
60
+ };
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.29.0-snapshot.41297455946.1" : "0.0.0-dev";
3
+
4
+ export {
5
+ LEDGE_VERSION
6
+ };
@@ -4,7 +4,7 @@ import {
4
4
  SUMMARY_MAX,
5
5
  parseServerMessage,
6
6
  readDaemonToken
7
- } from "./chunk-VKRFHDRK.js";
7
+ } from "./chunk-2X56ZRK7.js";
8
8
 
9
9
  // src/client.ts
10
10
  import WebSocket from "ws";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paths
3
- } from "./chunk-HOOHS3MX.js";
3
+ } from "./chunk-W724IVDC.js";
4
4
 
5
5
  // src/log.ts
6
6
  import { appendFileSync } from "fs";
@@ -1,47 +1,11 @@
1
1
  import {
2
2
  ledgeTitleRest
3
- } from "./chunk-LG3XFIPV.js";
3
+ } from "./chunk-O4MUYFPH.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-2X56ZRK7.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
  };
@@ -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
  },
@@ -0,0 +1,57 @@
1
+ import {
2
+ CODEX_HOOK_VAR
3
+ } from "./chunk-O4MUYFPH.js";
4
+ import {
5
+ connect
6
+ } from "./chunk-FWLT3SAN.js";
7
+ import {
8
+ ASK_OPTIONS_MAX,
9
+ ASK_QUESTION_MAX
10
+ } from "./chunk-2X56ZRK7.js";
11
+
12
+ // src/cli/ask.ts
13
+ import { randomUUID } from "crypto";
14
+ var ASK_USAGE = `ledge ask "<question>" "<options, ta recommandation>" une d\xE9cision pour l'utilisateur ; sa r\xE9ponse te r\xE9veille`;
15
+ var ASK_MS = 4e3;
16
+ function askArgs(args) {
17
+ const [question, options] = args;
18
+ return args.length === 2 && question.trim() && question.length <= ASK_QUESTION_MAX && options.length <= ASK_OPTIONS_MAX ? { question, options } : void 0;
19
+ }
20
+ async function askOnClient(client, args, by, ms) {
21
+ const parsed = askArgs(args);
22
+ if (!parsed) return { line: ASK_USAGE, code: 1 };
23
+ try {
24
+ client.send({ type: "ask.create", rid: randomUUID(), by, ...parsed });
25
+ const r = await client.next("ask.result", ms);
26
+ if (!r.ask) return { line: `refus\xE9 : ${r.reason ?? "raison inconnue"}`, code: 1 };
27
+ return { line: `d\xE9cision ${r.ask.id} pos\xE9e \xE0 l'utilisateur : sa r\xE9ponse te r\xE9veillera`, code: 0 };
28
+ } catch (e) {
29
+ return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
30
+ }
31
+ }
32
+ async function runAsk(args, opts = {}) {
33
+ if (!askArgs(args)) return { line: ASK_USAGE, code: 1 };
34
+ const env = opts.env ?? process.env;
35
+ const by = env[CODEX_HOOK_VAR] || env.LEDGE_SESSION;
36
+ if (!by) return { line: "ledge ask ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
37
+ const deadline = Date.now() + (opts.timeoutMs ?? ASK_MS);
38
+ const left = () => Math.max(0, deadline - Date.now());
39
+ let client;
40
+ try {
41
+ client = await connect({ port: opts.port, timeoutMs: left() });
42
+ } catch {
43
+ return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
44
+ }
45
+ try {
46
+ return await askOnClient(client, args, by, left());
47
+ } finally {
48
+ await client.close();
49
+ }
50
+ }
51
+
52
+ export {
53
+ ASK_USAGE,
54
+ askArgs,
55
+ askOnClient,
56
+ runAsk
57
+ };
@@ -3,10 +3,10 @@ import {
3
3
  SESSION_KEY_VAR,
4
4
  pickName,
5
5
  sessionKey
6
- } from "./chunk-VKRFHDRK.js";
6
+ } from "./chunk-2X56ZRK7.js";
7
7
  import {
8
8
  paths
9
- } from "./chunk-HOOHS3MX.js";
9
+ } from "./chunk-W724IVDC.js";
10
10
 
11
11
  // src/daemon/transcript.ts
12
12
  import { appendFileSync, closeSync, fstatSync, openSync, readFileSync, readSync, statSync } from "fs";
@@ -210,7 +210,7 @@ var PARENT_AGENT_VARS = [
210
210
  var PARENT_AGENT = new Set(PARENT_AGENT_VARS);
211
211
  function agentEnv(base, extra) {
212
212
  const env = {};
213
- for (const [k, v] of Object.entries(base)) if (v !== void 0 && k !== "TMUX" && !k.startsWith("TMUX_") && !PARENT_AGENT.has(k) && k !== "LEDGE_ROLE" && k !== "LEDGE_TEAM") env[k] = v;
213
+ 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
214
  return { ...env, ...extra };
215
215
  }
216
216
  async function spawnSession(opts) {
@@ -229,7 +229,7 @@ async function spawnSession(opts) {
229
229
  const transcriptPath = agentSessionId ? claudeTranscriptPath(opts.cwd, agentSessionId, env) : void 0;
230
230
  const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
231
231
  const key = sessionKey(id);
232
- await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(env, { LEDGE_SESSION: id, ...key ? { [SESSION_KEY_VAR]: key } : {}, LEDGE_NAME: name, ...opts.role ? { LEDGE_ROLE: opts.role.id } : {}, ...opts.team ? { LEDGE_TEAM: opts.team } : {}, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
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.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
233
233
  const at = now();
234
234
  return {
235
235
  id,
@@ -246,9 +246,8 @@ async function spawnSession(opts) {
246
246
  source: "launcher",
247
247
  ...agentSessionId ? { agentSessionId } : {},
248
248
  ...transcriptPath ? { transcriptPath } : {},
249
- ...opts.role ? { role: opts.role.id, roleLabel: opts.role.label } : {},
250
- ...opts.team ? { team: opts.team } : {},
251
- ...opts.autonomy && opts.autonomy !== "ask" ? { autonomy: opts.autonomy } : {}
249
+ ...opts.autonomy && opts.autonomy !== "ask" ? { autonomy: opts.autonomy } : {},
250
+ ...opts.brain ? { brain: true } : {}
252
251
  };
253
252
  }
254
253
 
@@ -1,12 +1,12 @@
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
- } from "./chunk-VMGACWNP.js";
6
+ } from "./chunk-FWLT3SAN.js";
7
7
  import {
8
8
  BRIEF_MAX
9
- } from "./chunk-VKRFHDRK.js";
9
+ } from "./chunk-2X56ZRK7.js";
10
10
 
11
11
  // src/cli/handover.ts
12
12
  import { randomUUID } from "crypto";
@@ -27,7 +27,7 @@ async function handoverOnClient(client, args, by, ms) {
27
27
  client.send(op.op === "show" ? { type: "handover.show", rid, by } : { type: "handover.brief", rid, by, text: op.text });
28
28
  const r = await client.next("handover.result", ms);
29
29
  if (r.reason !== void 0) return { line: `refus\xE9 : ${r.reason}`, code: 1 };
30
- return op.op === "show" ? { line: r.text ?? "", code: 0 } : { line: "bilan re\xE7u : Ledge lance la session qui prend ta rel\xE8ve et lui passe tes t\xE2ches. Arr\xEAte-toi l\xE0.", code: 0 };
30
+ return op.op === "show" ? { line: r.text ?? "", code: 0 } : { line: "bilan re\xE7u : Ledge lance la session qui prend ta rel\xE8ve. Arr\xEAte-toi l\xE0.", code: 0 };
31
31
  } catch (e) {
32
32
  return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
33
33
  }
@@ -0,0 +1,57 @@
1
+ import {
2
+ DAEMON_DOWN,
3
+ callerOf
4
+ } from "./chunk-TCQ5PYIF.js";
5
+ import {
6
+ connect
7
+ } from "./chunk-FWLT3SAN.js";
8
+ import {
9
+ BRAIN_OPS
10
+ } from "./chunk-2X56ZRK7.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
+ };
@@ -1,13 +1,13 @@
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
- } from "./chunk-VMGACWNP.js";
6
+ } from "./chunk-FWLT3SAN.js";
7
7
  import {
8
8
  SESSION_KEY_VAR,
9
9
  actionById
10
- } from "./chunk-VKRFHDRK.js";
10
+ } from "./chunk-2X56ZRK7.js";
11
11
 
12
12
  // src/cli/action.ts
13
13
  import { randomUUID } from "crypto";