@paradow/ledge 0.19.1 → 0.21.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 (38) hide show
  1. package/dist/cli/{attach-3BKQSEWC.js → attach-IVICSJYB.js} +11 -11
  2. package/dist/cli/{bridge-APNFBPNE.js → bridge-H3ZPGIUY.js} +2 -2
  3. package/dist/cli/{chunk-3HCID6NW.js → chunk-24AHC55Y.js} +3 -3
  4. package/dist/cli/{chunk-3EIRKAX7.js → chunk-2VSEIOGA.js} +4 -4
  5. package/dist/cli/{chunk-TRTS24PF.js → chunk-35BPMR6P.js} +2 -2
  6. package/dist/cli/{chunk-43D6NMYH.js → chunk-6IYPRUQG.js} +1 -1
  7. package/dist/cli/{chunk-ZUYOA55W.js → chunk-AJFC33TH.js} +2 -2
  8. package/dist/cli/{chunk-LOOOHYZR.js → chunk-ALEZJFJ3.js} +8 -8
  9. package/dist/cli/{chunk-BVBX222F.js → chunk-EBBAOQ2Z.js} +7 -6
  10. package/dist/cli/{chunk-WP77O5LC.js → chunk-EKXZ45MQ.js} +1182 -621
  11. package/dist/cli/{chunk-HP3G2TYJ.js → chunk-EX66KH66.js} +730 -646
  12. package/dist/cli/{chunk-7ZAA75HG.js → chunk-HOOHS3MX.js} +1 -1
  13. package/dist/cli/{chunk-FQZ3IX6E.js → chunk-K7UEKSB2.js} +2 -2
  14. package/dist/cli/{chunk-GXFU4XAX.js → chunk-KACRWFN3.js} +1 -1
  15. package/dist/cli/chunk-NFXFY5CT.js +6 -0
  16. package/dist/cli/{chunk-WJ6YMZTT.js → chunk-NLL2EQJ2.js} +2 -2
  17. package/dist/cli/{chunk-KKNE7BFZ.js → chunk-WRE3PUTV.js} +35 -11
  18. package/dist/cli/{chunk-WCS24ICX.js → chunk-ZQON4EV7.js} +2 -2
  19. package/dist/cli/{chunk-Y5DJ5INP.js → chunk-ZZMZDAF6.js} +3 -3
  20. package/dist/cli/{daemon-YHRAZSRS.js → daemon-5FPZG5R3.js} +87 -25
  21. package/dist/cli/decide-2ZKSX7O5.js +56 -0
  22. package/dist/cli/{handover-UXISKYPS.js → handover-AZ35QZRK.js} +5 -5
  23. package/dist/cli/{hook-5UAARBWU.js → hook-KANGO46Y.js} +21 -14
  24. package/dist/cli/index.js +23 -16
  25. package/dist/cli/launch-DEKJ5HI4.js +34 -0
  26. package/dist/cli/{send-NLVR6PL2.js → send-BFPJBXXH.js} +5 -5
  27. package/dist/cli/{setup-EEIXRAMU.js → setup-47ODKJBM.js} +4 -4
  28. package/dist/cli/{status-2OCYHOWZ.js → status-JPF4NZNI.js} +3 -3
  29. package/dist/cli/{task-AOOWHPMN.js → task-UTRG4KA5.js} +5 -5
  30. package/dist/cli/{team-TVNSVYWW.js → team-SZJHGA7D.js} +6 -6
  31. package/dist/cli/{tui-5XPPX4GR.js → tui-GEJHBWP5.js} +817 -367
  32. package/dist/cli/{uninstall-UUN7LIIT.js → uninstall-QNWRD27D.js} +4 -4
  33. package/dist/cli/{version-HB3S4IVG.js → version-YUO6SLAF.js} +1 -1
  34. package/dist/cli/who-NYPVBO64.js +11 -0
  35. package/package.json +1 -1
  36. package/dist/cli/chunk-TVIAHOOD.js +0 -6
  37. package/dist/cli/launch-YQYCDZ7N.js +0 -34
  38. package/dist/cli/who-52MENZTT.js +0 -11
@@ -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") };
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") };
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-WJ6YMZTT.js";
3
+ } from "./chunk-NLL2EQJ2.js";
4
4
  import {
5
5
  ensureLedgeDir,
6
6
  paths
7
- } from "./chunk-7ZAA75HG.js";
7
+ } from "./chunk-HOOHS3MX.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-HP3G2TYJ.js";
7
+ } from "./chunk-EX66KH66.js";
8
8
 
9
9
  // src/client.ts
10
10
  import WebSocket from "ws";
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.21.0" : "0.0.0-dev";
3
+
4
+ export {
5
+ LEDGE_VERSION
6
+ };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log
3
- } from "./chunk-43D6NMYH.js";
3
+ } from "./chunk-6IYPRUQG.js";
4
4
  import {
5
5
  paths
6
- } from "./chunk-7ZAA75HG.js";
6
+ } from "./chunk-HOOHS3MX.js";
7
7
 
8
8
  // src/tmux.ts
9
9
  import { execFile } from "child_process";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  ledgeTitleRest
3
- } from "./chunk-TRTS24PF.js";
3
+ } from "./chunk-35BPMR6P.js";
4
4
  import {
5
5
  cleanTopic,
6
6
  isArchived,
7
7
  sessionTitle
8
- } from "./chunk-HP3G2TYJ.js";
8
+ } from "./chunk-EX66KH66.js";
9
9
 
10
10
  // src/daemon/seat.ts
11
11
  function teammates(team, sessions) {
@@ -90,14 +90,19 @@ function formatLedgeMessage(m) {
90
90
  return `${open}${head} \u2026 [message de ${m.text.length} caract\xE8res, ${lineCount(m.text)} lignes : lis-le en entier avec ledge read ${attr(m.id)}]${LEDGE_CLOSE}`;
91
91
  }
92
92
  function ledgeMessages(text) {
93
- const m = LEDGE_BLOCK.exec(text.trimStart());
94
- if (!m) return void 0;
95
- const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
96
- const from = attrs.get("from");
97
- const tool = attrs.get("tool");
98
- const id = attrs.get("id");
99
- if (!from) return void 0;
100
- return [{ from, ...tool ? { tool } : {}, ...id ? { id } : {}, text: stripNote(m[2]).text.trim() }];
93
+ const out = [];
94
+ let rest = text.trimStart();
95
+ let m;
96
+ while (rest && (m = LEDGE_BLOCK.exec(rest))) {
97
+ const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
98
+ const from = attrs.get("from");
99
+ const tool = attrs.get("tool");
100
+ const id = attrs.get("id");
101
+ if (!from) break;
102
+ out.push({ from, ...tool ? { tool } : {}, ...id ? { id } : {}, text: stripNote(m[2]).text.trim() });
103
+ rest = rest.slice(m[0].length).trimStart();
104
+ }
105
+ return out.length ? out : void 0;
101
106
  }
102
107
  var NOTICE_BLOCK = /^<ledge-notice\b([^>]*)>([\s\S]*?)(?:<\/ledge-notice>|$)/;
103
108
  var NOTICE_QUOTE = 80;
@@ -609,6 +614,23 @@ function resolveTarget(to, sessions, peers) {
609
614
  return address ? { outcome: "native", target: t, address } : { outcome: "refused", reason: `${t.name} n'est pas joignable` };
610
615
  }
611
616
 
617
+ // src/attachmentText.ts
618
+ var imageBracket = (path) => `[Image attached: ${path}]`;
619
+ var withImages = (text, paths) => [text, ...paths.map(imageBracket)].join(" ").trim();
620
+ var BRACKET = /[ \t]*\[Image attached: ([^\]\n]*)\]/g;
621
+ function takeImages(text, dir) {
622
+ const prefix = dir.replace(/\/+$/, "") + "/";
623
+ const ids = [];
624
+ const rest = text.replace(BRACKET, (whole, path) => {
625
+ if (!path.startsWith(prefix)) return whole;
626
+ const m = /^([0-9a-f]{64})\.(?:webp|jpg|png|gif)$/.exec(path.slice(prefix.length));
627
+ if (!m) return whole;
628
+ ids.push(m[1]);
629
+ return "";
630
+ });
631
+ return { text: rest.trim(), ids };
632
+ }
633
+
612
634
  export {
613
635
  teammates,
614
636
  seatContext,
@@ -647,5 +669,7 @@ export {
647
669
  readPeers,
648
670
  bareName,
649
671
  ledgePeer,
650
- resolveTarget
672
+ resolveTarget,
673
+ withImages,
674
+ takeImages
651
675
  };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-GXFU4XAX.js";
3
+ } from "./chunk-KACRWFN3.js";
4
4
  import {
5
5
  isArchived
6
- } from "./chunk-HP3G2TYJ.js";
6
+ } from "./chunk-EX66KH66.js";
7
7
 
8
8
  // src/cli/who.ts
9
9
  import { basename } from "path";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  CODEX_HOOK_VAR
3
- } from "./chunk-TRTS24PF.js";
3
+ } from "./chunk-35BPMR6P.js";
4
4
  import {
5
5
  connect
6
- } from "./chunk-GXFU4XAX.js";
6
+ } from "./chunk-KACRWFN3.js";
7
7
  import {
8
8
  BRIEF_MAX
9
- } from "./chunk-HP3G2TYJ.js";
9
+ } from "./chunk-EX66KH66.js";
10
10
 
11
11
  // src/cli/handover.ts
12
12
  import { randomUUID } from "crypto";
@@ -1,14 +1,16 @@
1
1
  import {
2
+ AttachmentStore,
2
3
  fileJournal,
3
4
  loginShellEnv,
5
+ quote,
4
6
  reapOwner,
5
7
  rpOf,
6
8
  startPoller,
7
9
  startServer,
8
10
  tasksJournal,
9
11
  teamsJournal
10
- } from "./chunk-WP77O5LC.js";
11
- import "./chunk-3EIRKAX7.js";
12
+ } from "./chunk-EKXZ45MQ.js";
13
+ import "./chunk-2VSEIOGA.js";
12
14
  import "./chunk-OLLQBK6N.js";
13
15
  import {
14
16
  QUESTION_TOOLS,
@@ -24,11 +26,11 @@ import {
24
26
  readPeers,
25
27
  reduce,
26
28
  stripNote
27
- } from "./chunk-KKNE7BFZ.js";
28
- import "./chunk-3HCID6NW.js";
29
+ } from "./chunk-WRE3PUTV.js";
30
+ import "./chunk-24AHC55Y.js";
29
31
  import {
30
32
  agentMessages
31
- } from "./chunk-TRTS24PF.js";
33
+ } from "./chunk-35BPMR6P.js";
32
34
  import {
33
35
  DEFAULT_RELAY_URL,
34
36
  DevicePollResponseSchema,
@@ -42,26 +44,26 @@ import {
42
44
  SealedFrameSchema,
43
45
  SessionSchema,
44
46
  backendOf
45
- } from "./chunk-HP3G2TYJ.js";
47
+ } from "./chunk-EX66KH66.js";
46
48
  import {
47
49
  LEDGE_VERSION
48
- } from "./chunk-TVIAHOOD.js";
50
+ } from "./chunk-NFXFY5CT.js";
49
51
  import {
50
52
  SERVICE,
51
53
  inDaemonService,
52
54
  tmuxServerAtRisk
53
- } from "./chunk-FQZ3IX6E.js";
55
+ } from "./chunk-K7UEKSB2.js";
54
56
  import {
55
57
  realTmux,
56
58
  runFile
57
- } from "./chunk-WJ6YMZTT.js";
59
+ } from "./chunk-NLL2EQJ2.js";
58
60
  import {
59
61
  log
60
- } from "./chunk-43D6NMYH.js";
62
+ } from "./chunk-6IYPRUQG.js";
61
63
  import {
62
64
  ensureLedgeDir,
63
65
  paths
64
- } from "./chunk-7ZAA75HG.js";
66
+ } from "./chunk-HOOHS3MX.js";
65
67
 
66
68
  // src/daemon/index.ts
67
69
  import { writeFile as writeFile3, rm as rm2 } from "fs/promises";
@@ -367,9 +369,12 @@ var History = class {
367
369
  dir;
368
370
  /** The Ledge session a SendMessage address or a sender's name is, when it is one (peers.ts). */
369
371
  peerOf;
372
+ /** The stored image an attachment id is, when the store still holds it. */
373
+ refOf;
370
374
  constructor(opts = {}) {
371
375
  this.dir = opts.dir ?? paths().history;
372
376
  this.peerOf = opts.peerOf ?? (() => void 0);
377
+ this.refOf = opts.refOf ?? (() => null);
373
378
  }
374
379
  items(id) {
375
380
  return this.threads.get(id) ?? [];
@@ -425,6 +430,35 @@ var History = class {
425
430
  if (lines > items.length || lost) this.compact(id, items);
426
431
  }
427
432
  }
433
+ /** The ids of the images the journaled prompts refer to, read from every `<dir>/*.jsonl` once the pending writes landed. */
434
+ async attachmentIds() {
435
+ await this.writes;
436
+ const ids = /* @__PURE__ */ new Set();
437
+ let names;
438
+ try {
439
+ names = await readdir(this.dir);
440
+ } catch {
441
+ return ids;
442
+ }
443
+ for (const name of names) {
444
+ if (!name.endsWith(".jsonl")) continue;
445
+ let text;
446
+ try {
447
+ text = await readFile2(join(this.dir, name), "utf8");
448
+ } catch (e) {
449
+ log(`history not read for ${name}: ${e.message}`);
450
+ continue;
451
+ }
452
+ for (const line of text.split("\n")) {
453
+ if (!line.includes('"images"')) continue;
454
+ try {
455
+ for (const img of JSON.parse(line).images ?? []) if (typeof img?.id === "string") ids.add(img.id);
456
+ } catch {
457
+ }
458
+ }
459
+ }
460
+ return ids;
461
+ }
428
462
  /** The ids of the threads held, loaded or recorded. */
429
463
  ids() {
430
464
  return [...this.threads.keys()];
@@ -443,25 +477,27 @@ var History = class {
443
477
  }
444
478
  switch (ev.name) {
445
479
  case "UserPromptSubmit": {
446
- if (!ev.text) return void 0;
447
- const back = handBack(ev.text);
480
+ const images = (ev.attachments ?? []).flatMap((a) => this.refOf(a) ?? []);
481
+ if (!ev.text && images.length === 0) return void 0;
482
+ const text = ev.text ?? "";
483
+ const back = handBack(text);
448
484
  if (back) {
449
485
  const helper = this.items(id).find((i) => i.id === back.helperId);
450
486
  if (helper?.kind === "helper" && helper.state !== "exited") this.put(id, { ...helper, state: "exited" });
451
487
  if (helper?.kind === "helper") this.settle(id, (i) => i.helperId === back.helperId);
452
488
  return back.report ? this.put(id, { kind: "reply", id: `report-${back.helperId}`, at: ev.at, turn: helper?.turn ?? turn, helperId: back.helperId, text: back.report }) : void 0;
453
489
  }
454
- const messages = crossSession(ev.text);
490
+ const messages = crossSession(text);
455
491
  if (messages) {
456
492
  this.turns.set(id, turn + 1);
457
493
  let last;
458
494
  messages.forEach((m, i) => {
459
- const { sender, text } = stripNote(m.text);
460
- last = this.receive(id, { kind: "received", id: `recv-${turn + 1}-${i}`, at: ev.at, turn: turn + 1, from: sender ?? m.name ?? m.from, text }, { ...m, ...sender ? { sender } : {} });
495
+ const { sender, text: text2 } = stripNote(m.text);
496
+ last = this.receive(id, { kind: "received", id: `recv-${turn + 1}-${i}`, at: ev.at, turn: turn + 1, from: sender ?? m.name ?? m.from, text: text2 }, { ...m, ...sender ? { sender } : {} });
461
497
  });
462
498
  return last;
463
499
  }
464
- const ledge = ledgeMessages(ev.text);
500
+ const ledge = ledgeMessages(text);
465
501
  if (ledge) {
466
502
  this.turns.set(id, turn + 1);
467
503
  let last;
@@ -470,7 +506,7 @@ var History = class {
470
506
  });
471
507
  return last;
472
508
  }
473
- const notices = crossSessionNotices(ev.text);
509
+ const notices = crossSessionNotices(text);
474
510
  if (notices) {
475
511
  this.turns.set(id, turn + 1);
476
512
  let last;
@@ -479,7 +515,7 @@ var History = class {
479
515
  });
480
516
  return last;
481
517
  }
482
- const failed = ledgeNotice(ev.text);
518
+ const failed = ledgeNotice(text);
483
519
  if (failed) {
484
520
  this.turns.set(id, turn + 1);
485
521
  const peer = this.peerOf(failed.to);
@@ -498,7 +534,7 @@ var History = class {
498
534
  text: failed.text
499
535
  });
500
536
  }
501
- const task = ledgeTask(ev.text);
537
+ const task = ledgeTask(text);
502
538
  if (task) {
503
539
  this.turns.set(id, turn + 1);
504
540
  const peer = task.from ? this.peerOf(task.from) : void 0;
@@ -515,13 +551,13 @@ var History = class {
515
551
  text: task.text
516
552
  });
517
553
  }
518
- const joined = ledgeTeamNotice(ev.text);
554
+ const joined = ledgeTeamNotice(text);
519
555
  if (joined) {
520
556
  this.turns.set(id, turn + 1);
521
557
  return this.put(id, { kind: "notice", id: `notice-${turn + 1}-0`, at: ev.at, turn: turn + 1, notice: "other", team: joined.team, summary: joined.text.replaceAll(" \u23CE ", "\n"), text: joined.text });
522
558
  }
523
559
  this.turns.set(id, turn + 1);
524
- return this.put(id, { kind: "prompt", id: `prompt-${turn + 1}`, at: ev.at, turn: turn + 1, text: ev.text });
560
+ return this.put(id, { kind: "prompt", id: `prompt-${turn + 1}`, at: ev.at, turn: turn + 1, text, ...images.length ? { images } : {} });
525
561
  }
526
562
  case "PreToolUse": {
527
563
  if (!ev.tool) return void 0;
@@ -604,7 +640,7 @@ var History = class {
604
640
  if (!opts.force && now - (this.synced.get(key) ?? 0) < SYNC_MS) return [];
605
641
  this.synced.set(key, now);
606
642
  const items = this.items(id);
607
- const since = opts.helperId ? items.find((i) => i.id === opts.helperId)?.at : [...items].reverse().find((i) => i.kind === "prompt" || i.kind === "received" || i.kind === "notice")?.at;
643
+ const since = opts.helperId ? items.find((i) => i.id === opts.helperId)?.at : [...items].reverse().find((i) => i.kind === "prompt" || i.kind === "received" || i.kind === "notice" && !i.outgoing)?.at;
608
644
  if (!since) return [];
609
645
  const have = new Set(items.map((i) => i.id));
610
646
  const turn = this.turns.get(id) ?? 0;
@@ -622,6 +658,30 @@ var History = class {
622
658
  if (existing) return this.put(senderId, { ...existing, ...m.peer ? { peer: m.peer } : {}, via: "ledge" });
623
659
  return this.put(senderId, { kind: "sent", id: m.rid, at: m.at, turn: this.turns.get(senderId) ?? 0, to: m.to, ...m.peer ? { peer: m.peer } : {}, ...m.summary ? { summary: m.summary } : {}, text: m.text, status: "sending", via: "ledge" });
624
660
  }
661
+ /**
662
+ * A task the session handed over (issue #92): its card in the sender's thread, in the turn it is in (not a prompt).
663
+ * One card per transmission: the same one again replaces it.
664
+ */
665
+ recordTask(senderId, n) {
666
+ const { task, tr } = n;
667
+ const what = { give: "confi\xE9e \xE0", pass: "pass\xE9e \xE0", done: "rendue \xE0", refuse: "refus\xE9e, rendue \xE0", cancel: "annul\xE9e pour" }[tr.op];
668
+ const said = tr.op === "pass" ? tr.note : task.result;
669
+ return this.put(senderId, {
670
+ kind: "notice",
671
+ id: `task-${task.id}-${tr.index}`,
672
+ at: tr.at,
673
+ turn: this.turns.get(senderId) ?? 0,
674
+ notice: "other",
675
+ task: task.id,
676
+ taskOp: tr.op,
677
+ outgoing: true,
678
+ name: tr.toName,
679
+ ...n.peer ? { peer: n.peer } : {},
680
+ summary: said ? `${task.title}
681
+ ${quote(said)}` : task.title,
682
+ text: `T\xE2che \xAB ${task.title} \xBB ${what} ${tr.toName}${tr.op === "done" ? " (termin\xE9e)" : ""}`
683
+ });
684
+ }
625
685
  /** The mailbox's outcome for a card; a `read` card stays read. */
626
686
  markSent(senderId, rid, status) {
627
687
  const sent = this.items(senderId).find((i) => i.kind === "sent" && i.id === rid);
@@ -2188,7 +2248,9 @@ async function startDaemon(opts = {}) {
2188
2248
  const daemon = await daemonInfo();
2189
2249
  let ompLive = () => 0;
2190
2250
  const updater = createDaemonUpdater(realUpdaterDeps({ info: daemon, save: () => store.save(), ownedLive: () => ompLive() }));
2191
- const history = new History({ peerOf: (t) => ledgePeer(t, readPeers(), store.all())?.id });
2251
+ const attachments = new AttachmentStore(paths().attachments);
2252
+ await attachments.load().catch((e) => log(`attachments not loaded: ${e.message}`));
2253
+ const history = new History({ peerOf: (t) => ledgePeer(t, readPeers(), store.all())?.id, refOf: (id) => attachments.ref(id) });
2192
2254
  try {
2193
2255
  await history.load();
2194
2256
  } catch (e) {
@@ -2208,7 +2270,7 @@ async function startDaemon(opts = {}) {
2208
2270
  const mail = fileJournal(paths().mail);
2209
2271
  const taskJournal = tasksJournal(paths().tasks);
2210
2272
  const teamJournal = teamsJournal(paths().teams);
2211
- const server = await startServer({ store, tmux, port: opts.port, daemon, updater, history, mobile, mobileUnavailable, mail, tasks: taskJournal, teams: teamJournal });
2273
+ const server = await startServer({ store, tmux, port: opts.port, daemon, updater, history, mobile, mobileUnavailable, mail, tasks: taskJournal, teams: teamJournal, attachments });
2212
2274
  ompLive = () => server.omp.liveCount();
2213
2275
  const poller = startPoller({ store, tmux, broadcastPreview: server.preview, labelExited: server.labelExited });
2214
2276
  await writeFile3(paths().pid, String(process.pid));
@@ -0,0 +1,56 @@
1
+ import {
2
+ CODEX_HOOK_VAR
3
+ } from "./chunk-35BPMR6P.js";
4
+ import {
5
+ connect
6
+ } from "./chunk-KACRWFN3.js";
7
+ import {
8
+ LEDGE_GIVER,
9
+ TASK_TEXT_MAX
10
+ } from "./chunk-EX66KH66.js";
11
+ import "./chunk-HOOHS3MX.js";
12
+
13
+ // src/cli/decide.ts
14
+ import { randomUUID } from "crypto";
15
+ var DECIDE_USAGE = `ledge decide les d\xE9cisions que les sessions attendent de toi
16
+ ledge decide <id> "<r\xE9ponse>" y r\xE9pondre : la session qui l'a demand\xE9e reprend`;
17
+ var ID = /^t[0-9a-f]{6}$/;
18
+ var DECIDE_MS = 4e3;
19
+ function decideArgs(args) {
20
+ if (!args.length) return { op: "list" };
21
+ const [id, text] = args;
22
+ return args.length === 2 && ID.test(id) && text.trim() && text.length <= TASK_TEXT_MAX ? { op: "done", task: id, text } : void 0;
23
+ }
24
+ async function runDecide(args, opts = {}) {
25
+ const op = decideArgs(args);
26
+ if (!op) return { line: DECIDE_USAGE, code: 1 };
27
+ const env = opts.env ?? process.env;
28
+ if (env.LEDGE_SESSION || env[CODEX_HOOK_VAR]) return { line: "refus\xE9 : ledge decide est la r\xE9ponse de l'utilisateur, pas celle d'une session", code: 1 };
29
+ let client;
30
+ try {
31
+ client = await connect({ port: opts.port, timeoutMs: DECIDE_MS });
32
+ } catch {
33
+ return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
34
+ }
35
+ try {
36
+ if (op.op === "list") {
37
+ client.send({ type: "task.cmd", rid: randomUUID(), by: LEDGE_GIVER, op: "list" });
38
+ const asked = ((await client.next("task.result", DECIDE_MS)).tasks ?? []).filter((t) => t.owner === LEDGE_GIVER);
39
+ if (!asked.length) return { line: "aucune d\xE9cision en attente", code: 0 };
40
+ return { line: asked.map((t) => [`${t.id} de ${t.giverName} ${t.title}`, ...t.text ? t.text.split("\n").map((l) => ` ${l}`) : []].join("\n")).join("\n\n"), code: 0 };
41
+ }
42
+ client.send({ type: "task.cmd", rid: randomUUID(), by: LEDGE_GIVER, op: "done", task: op.task, text: op.text });
43
+ const r = await client.next("task.result", DECIDE_MS);
44
+ if (r.reason !== void 0) return { line: `refus\xE9 : ${r.reason}`, code: 1 };
45
+ return { line: `r\xE9ponse envoy\xE9e \xE0 ${r.task.giverName}`, code: 0 };
46
+ } catch (e) {
47
+ return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
48
+ } finally {
49
+ await client.close();
50
+ }
51
+ }
52
+ export {
53
+ DECIDE_USAGE,
54
+ decideArgs,
55
+ runDecide
56
+ };
@@ -3,11 +3,11 @@ import {
3
3
  handoverArgs,
4
4
  handoverOnClient,
5
5
  runHandover
6
- } from "./chunk-Y5DJ5INP.js";
7
- import "./chunk-TRTS24PF.js";
8
- import "./chunk-GXFU4XAX.js";
9
- import "./chunk-HP3G2TYJ.js";
10
- import "./chunk-7ZAA75HG.js";
6
+ } from "./chunk-ZZMZDAF6.js";
7
+ import "./chunk-35BPMR6P.js";
8
+ import "./chunk-KACRWFN3.js";
9
+ import "./chunk-EX66KH66.js";
10
+ import "./chunk-HOOHS3MX.js";
11
11
  export {
12
12
  HANDOVER_USAGE,
13
13
  handoverArgs,
@@ -6,41 +6,44 @@ import {
6
6
  resolveRoleAddress,
7
7
  resolveTarget,
8
8
  seatContext,
9
+ takeImages,
9
10
  teammates
10
- } from "./chunk-KKNE7BFZ.js";
11
+ } from "./chunk-WRE3PUTV.js";
11
12
  import {
12
13
  findRole
13
- } from "./chunk-3HCID6NW.js";
14
+ } from "./chunk-24AHC55Y.js";
14
15
  import {
15
16
  parseLedgeCommand,
16
17
  readOnClient,
17
18
  sendOnClient
18
- } from "./chunk-ZUYOA55W.js";
19
+ } from "./chunk-AJFC33TH.js";
19
20
  import {
20
21
  taskOnClient
21
- } from "./chunk-BVBX222F.js";
22
+ } from "./chunk-EBBAOQ2Z.js";
22
23
  import {
23
24
  handoverOnClient
24
- } from "./chunk-Y5DJ5INP.js";
25
+ } from "./chunk-ZZMZDAF6.js";
25
26
  import {
26
27
  CODEX_ENV_VAR,
27
28
  CODEX_HOOK_VAR,
28
29
  codexPlan
29
- } from "./chunk-TRTS24PF.js";
30
+ } from "./chunk-35BPMR6P.js";
30
31
  import {
31
32
  formatWho
32
- } from "./chunk-WCS24ICX.js";
33
+ } from "./chunk-ZQON4EV7.js";
33
34
  import {
34
35
  connect,
35
36
  sendMessageAndAwaitAck
36
- } from "./chunk-GXFU4XAX.js";
37
+ } from "./chunk-KACRWFN3.js";
37
38
  import {
38
39
  taskClosed
39
- } from "./chunk-HP3G2TYJ.js";
40
+ } from "./chunk-EX66KH66.js";
40
41
  import {
41
42
  log
42
- } from "./chunk-43D6NMYH.js";
43
- import "./chunk-7ZAA75HG.js";
43
+ } from "./chunk-6IYPRUQG.js";
44
+ import {
45
+ paths
46
+ } from "./chunk-HOOHS3MX.js";
44
47
 
45
48
  // src/cli/hook.ts
46
49
  import { randomUUID } from "crypto";
@@ -50,8 +53,12 @@ var str = (v) => typeof v === "string" ? v : void 0;
50
53
  var PROMPT_MAX = 500;
51
54
  var fromAgent = (t) => t.startsWith('<agent-message from="') || t.startsWith("<cross-session-message ") || t.startsWith("[Cross-session ");
52
55
  var promptText = (p) => {
53
- const t = str(p.prompt)?.trim();
54
- return t ? { text: t.slice(0, fromAgent(t) ? INPUT_MAX : PROMPT_MAX) } : {};
56
+ const raw = str(p.prompt)?.trim();
57
+ if (!raw) return {};
58
+ const taken = takeImages(raw, paths().attachments);
59
+ const ids = taken.ids;
60
+ const t = ids.length ? taken.text : raw;
61
+ return { ...t ? { text: t.slice(0, fromAgent(t) ? INPUT_MAX : PROMPT_MAX) } : {}, ...ids.length ? { attachments: ids } : {} };
55
62
  };
56
63
  function describeTool(tool, input) {
57
64
  const i = input && typeof input === "object" ? input : {};
@@ -348,7 +355,7 @@ function identityContext(s) {
348
355
  "Pour voir les autres sessions (nom, outil, \xE9tat, projet, branche, sujet), lance `ledge who`.",
349
356
  s.tool === "claude" ? "Pour \xE9crire \xE0 une autre session Ledge, Claude ou Codex, utilise son nom Ledge (donn\xE9 par `ledge who`) comme adresse SendMessage : Ledge le transmet. `ListAgents` montre les noms internes de Claude, pas les noms Ledge." : "Pour \xE9crire \xE0 une autre session Ledge, lance `ledge send <nom> \"<message>\"` (seul, sans && ni |). Ledge ex\xE9cute lui-m\xEAme `ledge send`, `ledge read`, `ledge task` et `ledge who` : leur r\xE9sultat t'arrive comme \xAB Command blocked by PreToolUse hook: \u2026 \xBB, c'est la r\xE9ponse, pas une erreur.",
350
357
  "Un long message d'une autre session t'arrive en aper\xE7u avec son id : lance `ledge read <id>` pour le lire en entier. Une `<ledge-notice>` te dit qu'un de tes messages n'a pas pu \xEAtre remis.",
351
- 'Un message sert \xE0 pr\xE9venir ; pour confier un travail qui ne doit pas se perdre, lance plut\xF4t `ledge task give <nom> "<titre>" "<d\xE9tail>"` (`ledge task` pour le reste). Une `<ledge-task>` t\'en confie une, ou te dit comment une des tiennes a fini : quand on t\'en confie une, prends-la (`ledge task take <id>`) et termine-la toujours (`ledge task done <id> "<r\xE9sultat>"`, ou `refuse`).',
358
+ 'Un message sert \xE0 pr\xE9venir ; pour confier un travail qui ne doit pas se perdre, lance plut\xF4t `ledge task give <nom> "<titre>" "<d\xE9tail>"` (`ledge task` pour le reste). Une `<ledge-task>` t\'en confie une, ou te dit comment une des tiennes a fini : quand on t\'en confie une, prends-la (`ledge task take <id>`) et termine-la toujours (`ledge task done <id> "<r\xE9sultat>"`, ou `refuse`). Une d\xE9cision qui revient \xE0 l\'utilisateur se pose avec `ledge task give ledge "<question>" "<options, ta recommandation>"` : sa r\xE9ponse te r\xE9veille, inutile d\'attendre la fin pour la poser.',
352
359
  ...s.seat ? [s.seat] : [],
353
360
  ...s.relieves ? [`Tu as pris la rel\xE8ve de ${s.relieves.name}, dont le contexte \xE9tait plein : relis son bilan avec \`ledge handover show\`.`] : [],
354
361
  ...s.tasks ? [tasksContext(s.tasks) ?? ""].filter(Boolean) : []