@paradow/ledge 0.17.0 → 0.19.0

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 (40) hide show
  1. package/README.md +7 -1
  2. package/assets/ledge.ico +0 -0
  3. package/dist/cli/{attach-EYIX7FZN.js → attach-3BKQSEWC.js} +12 -7
  4. package/dist/cli/{bridge-6UHCD76N.js → bridge-APNFBPNE.js} +2 -2
  5. package/dist/cli/chunk-3EIRKAX7.js +165 -0
  6. package/dist/cli/chunk-3HCID6NW.js +145 -0
  7. package/dist/cli/{chunk-JQZHXVIZ.js → chunk-43D6NMYH.js} +1 -1
  8. package/dist/cli/chunk-4RD7OJ2J.js +6 -0
  9. package/dist/cli/{chunk-ELLXOV4Q.js → chunk-7ZAA75HG.js} +1 -1
  10. package/dist/cli/{chunk-5ZCHFGCV.js → chunk-BVBX222F.js} +9 -7
  11. package/dist/cli/{chunk-OVJL2QTN.js → chunk-FQZ3IX6E.js} +2 -2
  12. package/dist/cli/{chunk-VVK4I6B5.js → chunk-GXFU4XAX.js} +1 -1
  13. package/dist/cli/chunk-HP3G2TYJ.js +1772 -0
  14. package/dist/cli/{chunk-NC5YAEJB.js → chunk-KKNE7BFZ.js} +55 -3
  15. package/dist/cli/{chunk-36KJUV62.js → chunk-LOOOHYZR.js} +38 -12
  16. package/dist/cli/{chunk-VE4LGNXX.js → chunk-TRTS24PF.js} +23 -5
  17. package/dist/cli/{chunk-Q7YNLP66.js → chunk-WCS24ICX.js} +5 -3
  18. package/dist/cli/{chunk-LAUNHDTX.js → chunk-WJ6YMZTT.js} +2 -2
  19. package/dist/cli/{daemon-QEJLAIBT.js → chunk-WP77O5LC.js} +6045 -7228
  20. package/dist/cli/chunk-Y5DJ5INP.js +60 -0
  21. package/dist/cli/{chunk-AQ25HFBP.js → chunk-ZUYOA55W.js} +5 -4
  22. package/dist/cli/daemon-66EUIZ3J.js +2240 -0
  23. package/dist/cli/handover-UXISKYPS.js +16 -0
  24. package/dist/cli/{hook-AE5HC6AS.js → hook-5UAARBWU.js} +86 -25
  25. package/dist/cli/index.js +30 -16
  26. package/dist/cli/launch-YQYCDZ7N.js +34 -0
  27. package/dist/cli/{send-2OZ3YZRV.js → send-NLVR6PL2.js} +5 -5
  28. package/dist/cli/{setup-2VM4GCDJ.js → setup-EEIXRAMU.js} +4 -4
  29. package/dist/cli/{status-3VJAO6UL.js → status-2OCYHOWZ.js} +3 -3
  30. package/dist/cli/{task-PMKNYP6M.js → task-AOOWHPMN.js} +5 -5
  31. package/dist/cli/team-TVNSVYWW.js +101 -0
  32. package/dist/cli/{tui-DWA2EDC2.js → tui-6JY7IS4D.js} +985 -125
  33. package/dist/cli/{uninstall-AKUWE33D.js → uninstall-UUN7LIIT.js} +4 -4
  34. package/dist/cli/{version-WOGCIZY3.js → version-XSEI3V2C.js} +1 -1
  35. package/dist/cli/who-52MENZTT.js +11 -0
  36. package/package.json +2 -2
  37. package/dist/cli/chunk-QD27W6W5.js +0 -1474
  38. package/dist/cli/chunk-V2H7UD2N.js +0 -6
  39. package/dist/cli/launch-B4G5OFBV.js +0 -27
  40. package/dist/cli/who-37TNDYVA.js +0 -11
@@ -1,10 +1,47 @@
1
1
  import {
2
2
  ledgeTitleRest
3
- } from "./chunk-VE4LGNXX.js";
3
+ } from "./chunk-TRTS24PF.js";
4
4
  import {
5
5
  cleanTopic,
6
+ isArchived,
6
7
  sessionTitle
7
- } from "./chunk-QD27W6W5.js";
8
+ } from "./chunk-HP3G2TYJ.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
45
 
9
46
  // src/daemon/crossSession.ts
10
47
  import { createHash } from "crypto";
@@ -71,6 +108,16 @@ function formatLedgeNotice(n) {
71
108
  const quote = ledgeBody(flat.length > NOTICE_QUOTE ? `${flat.slice(0, NOTICE_QUOTE)}\u2026` : flat);
72
109
  return `<ledge-notice to="${to}">Ledge n'a pas pu remettre ton message \xE0 ${to} (\xAB ${quote}${NOTICE_REASON}${ledgeBody(n.reason)}</ledge-notice>`;
73
110
  }
111
+ function formatTeamNotice(n) {
112
+ return `<ledge-notice team="${attr(n.team)}">${ledgeBody(n.text)}</ledge-notice>`;
113
+ }
114
+ var TEAM_NOTICE = /^<ledge-notice\b([^>]*\bteam="[^"]*"[^>]*)>([\s\S]*?)(?:<\/ledge-notice>|$)/;
115
+ function ledgeTeamNotice(text) {
116
+ const m = TEAM_NOTICE.exec(text.trimStart());
117
+ if (!m) return void 0;
118
+ const team = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]])).get("team");
119
+ return team ? { team, text: m[2].trim() } : void 0;
120
+ }
74
121
  var TASK_BLOCK = /^<ledge-task\b([^>]*)>([\s\S]*?)(?:<\/ledge-task>|$)/;
75
122
  function formatLedgeTask(t) {
76
123
  return `<ledge-task id="${attr(t.id)}" from="${attr(t.from)}">${ledgeBody(t.text)}</ledge-task>`;
@@ -436,7 +483,7 @@ function reduceEvent(s, ev) {
436
483
  case "PreToolUse":
437
484
  case "PermissionRequest":
438
485
  case "Notification": {
439
- const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text) || !!ledgeTask(ev.text));
486
+ const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text) || !!ledgeTask(ev.text) || !!ledgeTeamNotice(ev.text));
440
487
  if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !notice2 && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
441
488
  if (s.pending?.source === "rpc" && ev.name === "PreToolUse") return { ...s, lastHookAt: at, lastActivityAt: at };
442
489
  const incoming = pendingFor(ev);
@@ -563,6 +610,9 @@ function resolveTarget(to, sessions, peers) {
563
610
  }
564
611
 
565
612
  export {
613
+ teammates,
614
+ seatContext,
615
+ resolveRoleAddress,
566
616
  crossSession,
567
617
  messageNote,
568
618
  stripNote,
@@ -572,6 +622,8 @@ export {
572
622
  formatLedgeMessage,
573
623
  ledgeMessages,
574
624
  formatLedgeNotice,
625
+ formatTeamNotice,
626
+ ledgeTeamNotice,
575
627
  formatLedgeTask,
576
628
  ledgeTask,
577
629
  ledgeNotice,
@@ -1,23 +1,31 @@
1
1
  import {
2
- DaemonRefusedError,
3
- connect
4
- } from "./chunk-VVK4I6B5.js";
2
+ EFFORT_ARGS,
3
+ MODEL_ARGS
4
+ } from "./chunk-WP77O5LC.js";
5
+ import {
6
+ autonomyArgs,
7
+ findRole
8
+ } from "./chunk-3HCID6NW.js";
5
9
  import {
6
10
  exitFile,
7
11
  spawnSession
8
- } from "./chunk-VE4LGNXX.js";
12
+ } from "./chunk-TRTS24PF.js";
13
+ import {
14
+ DaemonRefusedError,
15
+ connect
16
+ } from "./chunk-GXFU4XAX.js";
9
17
  import {
10
18
  PROTOCOL_PORT
11
- } from "./chunk-QD27W6W5.js";
19
+ } from "./chunk-HP3G2TYJ.js";
12
20
  import {
13
21
  realTmux
14
- } from "./chunk-LAUNHDTX.js";
22
+ } from "./chunk-WJ6YMZTT.js";
15
23
  import {
16
24
  log
17
- } from "./chunk-JQZHXVIZ.js";
25
+ } from "./chunk-43D6NMYH.js";
18
26
  import {
19
27
  ensureLedgeDir
20
- } from "./chunk-ELLXOV4Q.js";
28
+ } from "./chunk-7ZAA75HG.js";
21
29
 
22
30
  // src/cli/launch.ts
23
31
  import { spawn } from "child_process";
@@ -28,19 +36,30 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
28
36
  function parseLaunchArgs(argv) {
29
37
  const args = [];
30
38
  let name;
39
+ let role;
31
40
  for (let i = 0; i < argv.length; i++) {
32
41
  const a = argv[i];
33
42
  if (a === "--name") {
34
43
  name = argv[++i];
35
44
  continue;
36
45
  }
46
+ if (a === "--role") {
47
+ role = argv[++i];
48
+ continue;
49
+ }
37
50
  if (a === "--") {
38
51
  args.push(...argv.slice(i + 1));
39
52
  break;
40
53
  }
41
54
  args.push(a);
42
55
  }
43
- return { name, args };
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)];
44
63
  }
45
64
  function portOpen(port) {
46
65
  return new Promise((res) => {
@@ -118,7 +137,8 @@ async function createSession(opts) {
118
137
  ids.push(s.id);
119
138
  }
120
139
  }
121
- const session = await spawnSession({ ...opts, taken: { names, ids } });
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 } });
122
142
  try {
123
143
  opts.client?.send({ type: "session.register", session });
124
144
  } catch (e) {
@@ -156,15 +176,20 @@ var UNTRACKED = {
156
176
  };
157
177
  async function launch(tool, argv, opts = {}) {
158
178
  await ensureLedgeDir();
159
- const { name, args } = parseLaunchArgs(argv);
179
+ const { name, role: roleId, args } = parseLaunchArgs(argv);
160
180
  const port = opts.port ?? PROTOCOL_PORT;
161
181
  const socket = opts.tmuxSocket ?? TMUX_SOCKET;
162
182
  const tmux = realTmux(socket);
163
183
  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
+ }
164
189
  const daemon = await ensureDaemon({ port, spawnDaemon: opts.spawnDaemon, retryDelayMs: opts.retryDelayMs });
165
190
  let client = daemon.client;
166
191
  if (!daemon.client) console.error(UNTRACKED[daemon.reason](port));
167
- const create = (c) => createSession({ tool, args, command: opts.command, recordExit: true, name, cwd, tmux, client: c });
192
+ const create = (c) => createSession({ tool, args, command: opts.command, recordExit: true, name, ...role ? { role } : {}, cwd, tmux, client: c });
168
193
  let session;
169
194
  try {
170
195
  session = await create(client);
@@ -198,6 +223,7 @@ async function launch(tool, argv, opts = {}) {
198
223
 
199
224
  export {
200
225
  parseLaunchArgs,
226
+ roleArgs,
201
227
  ensureDaemon,
202
228
  DaemonError,
203
229
  createSession,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  NAMES,
3
3
  pickName
4
- } from "./chunk-QD27W6W5.js";
4
+ } from "./chunk-HP3G2TYJ.js";
5
5
  import {
6
6
  paths
7
- } from "./chunk-ELLXOV4Q.js";
7
+ } from "./chunk-7ZAA75HG.js";
8
8
 
9
9
  // src/daemon/transcript.ts
10
10
  import { appendFileSync, closeSync, fstatSync, openSync, readFileSync, readSync, statSync } from "fs";
@@ -206,7 +206,7 @@ var PARENT_AGENT_VARS = [
206
206
  var PARENT_AGENT = new Set(PARENT_AGENT_VARS);
207
207
  function agentEnv(base, extra) {
208
208
  const env = {};
209
- for (const [k, v] of Object.entries(base)) if (v !== void 0 && k !== "TMUX" && !k.startsWith("TMUX_") && !PARENT_AGENT.has(k)) env[k] = v;
209
+ 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;
210
210
  return { ...env, ...extra };
211
211
  }
212
212
  async function spawnSession(opts) {
@@ -224,9 +224,27 @@ async function spawnSession(opts) {
224
224
  const env = opts.env ?? process.env;
225
225
  const transcriptPath = agentSessionId ? claudeTranscriptPath(opts.cwd, agentSessionId, env) : void 0;
226
226
  const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
227
- await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(env, { LEDGE_SESSION: id, LEDGE_NAME: name, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
227
+ await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(env, { LEDGE_SESSION: id, 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 });
228
228
  const at = now();
229
- return { id, name, tool: opts.tool, cwd: opts.cwd, project, title: `${name} \xB7 ${project}`, tmuxSession, state: "starting", helpers: [], startedAt: at, lastActivityAt: at, source: "launcher", ...agentSessionId ? { agentSessionId } : {}, ...transcriptPath ? { transcriptPath } : {} };
229
+ return {
230
+ id,
231
+ name,
232
+ tool: opts.tool,
233
+ cwd: opts.cwd,
234
+ project,
235
+ title: `${name} \xB7 ${project}`,
236
+ tmuxSession,
237
+ state: "starting",
238
+ helpers: [],
239
+ startedAt: at,
240
+ lastActivityAt: at,
241
+ source: "launcher",
242
+ ...agentSessionId ? { agentSessionId } : {},
243
+ ...transcriptPath ? { transcriptPath } : {},
244
+ ...opts.role ? { role: opts.role.id, roleLabel: opts.role.label } : {},
245
+ ...opts.team ? { team: opts.team } : {},
246
+ ...opts.autonomy && opts.autonomy !== "ask" ? { autonomy: opts.autonomy } : {}
247
+ };
230
248
  }
231
249
 
232
250
  export {
@@ -1,25 +1,27 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-VVK4I6B5.js";
3
+ } from "./chunk-GXFU4XAX.js";
4
4
  import {
5
5
  isArchived
6
- } from "./chunk-QD27W6W5.js";
6
+ } from "./chunk-HP3G2TYJ.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);
13
14
  const rows = live.map((s) => [
14
15
  s.id === self ? "*" : " ",
15
16
  s.name,
17
+ ...roles ? [s.role ? `@${s.role}` : "-"] : [],
16
18
  s.tool,
17
19
  s.state,
18
20
  `${s.repo ?? s.project}${s.worktree ? ` (${basename(s.cwd)})` : ""}`,
19
21
  s.branch ?? "-",
20
22
  `${s.topic ?? "-"}${s.source === "hook_only" ? " \xB7 lecture seule" : ""}`
21
23
  ]);
22
- const head = [" ", "NOM", "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
24
+ const head = [" ", "NOM", ...roles ? ["R\xD4LE"] : [], "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
23
25
  const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
24
26
  return [head, ...rows].map((r) => r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i])).join(" ")).join("\n");
25
27
  }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log
3
- } from "./chunk-JQZHXVIZ.js";
3
+ } from "./chunk-43D6NMYH.js";
4
4
  import {
5
5
  paths
6
- } from "./chunk-ELLXOV4Q.js";
6
+ } from "./chunk-7ZAA75HG.js";
7
7
 
8
8
  // src/tmux.ts
9
9
  import { execFile } from "child_process";