@paradow/ledge 0.16.1 → 0.18.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.
- package/README.md +2 -0
- package/dist/cli/{attach-5H727FCW.js → attach-I6TTCGNV.js} +12 -7
- package/dist/cli/{bridge-LXMOQN6Q.js → bridge-APNFBPNE.js} +2 -2
- package/dist/cli/chunk-3EIRKAX7.js +165 -0
- package/dist/cli/chunk-3HCID6NW.js +145 -0
- package/dist/cli/{chunk-BNKPXSUY.js → chunk-43D6NMYH.js} +1 -1
- package/dist/cli/{daemon-DKH63BGV.js → chunk-7IQ6FV2F.js} +6117 -6985
- package/dist/cli/{chunk-R5462SQM.js → chunk-7ZAA75HG.js} +2 -1
- package/dist/cli/chunk-BVBX222F.js +117 -0
- package/dist/cli/{chunk-S4ANPQGA.js → chunk-FQZ3IX6E.js} +2 -2
- package/dist/cli/chunk-G6K2NSQS.js +6 -0
- package/dist/cli/{chunk-DEXDXDO4.js → chunk-GXFU4XAX.js} +1 -1
- package/dist/cli/chunk-HP3G2TYJ.js +1772 -0
- package/dist/cli/{chunk-DBSCO3HI.js → chunk-KKNE7BFZ.js} +108 -7
- package/dist/cli/{chunk-MQICEVKR.js → chunk-QQZALYNI.js} +38 -12
- package/dist/cli/{chunk-H36JD4E3.js → chunk-TRTS24PF.js} +23 -5
- package/dist/cli/{chunk-TB47EHML.js → chunk-WCS24ICX.js} +5 -3
- package/dist/cli/{chunk-2H5FJH4Q.js → chunk-WJ6YMZTT.js} +2 -2
- package/dist/cli/chunk-Y5DJ5INP.js +60 -0
- package/dist/cli/{chunk-QJVODYJ5.js → chunk-ZUYOA55W.js} +43 -4
- package/dist/cli/daemon-SKC7VKYQ.js +2240 -0
- package/dist/cli/handover-UXISKYPS.js +16 -0
- package/dist/cli/{hook-LY2YTK5U.js → hook-5UAARBWU.js} +93 -29
- package/dist/cli/index.js +45 -12
- package/dist/cli/launch-T5FJ42XS.js +34 -0
- package/dist/cli/send-NLVR6PL2.js +18 -0
- package/dist/cli/{setup-3N2EOJXS.js → setup-EEIXRAMU.js} +4 -4
- package/dist/cli/{status-ZQSYXILE.js → status-2OCYHOWZ.js} +3 -3
- package/dist/cli/task-AOOWHPMN.js +18 -0
- package/dist/cli/team-TVNSVYWW.js +101 -0
- package/dist/cli/tui-MQLMUUJB.js +4314 -0
- package/dist/cli/{uninstall-W2OMKU3B.js → uninstall-UUN7LIIT.js} +4 -4
- package/dist/cli/{version-6BMBIXHN.js → version-AEN2CRT2.js} +1 -1
- package/dist/cli/who-52MENZTT.js +11 -0
- package/package.json +7 -1
- package/dist/cli/chunk-J4ICZEGP.js +0 -6
- package/dist/cli/chunk-Q5WWH7FG.js +0 -1423
- package/dist/cli/launch-UQEFFPBN.js +0 -27
- package/dist/cli/send-EY63XQXI.js +0 -14
- package/dist/cli/who-VGIMGQNS.js +0 -11
|
@@ -1,12 +1,50 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ledgeTitleRest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TRTS24PF.js";
|
|
4
4
|
import {
|
|
5
5
|
cleanTopic,
|
|
6
|
+
isArchived,
|
|
6
7
|
sessionTitle
|
|
7
|
-
} from "./chunk-
|
|
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
|
|
47
|
+
import { createHash } from "crypto";
|
|
10
48
|
var FIRST_LINE = /^Another Claude session sent a message:\s*\n/;
|
|
11
49
|
var BLOCK = /<cross-session-message\b([^>]*)>([\s\S]*?)(?:<\/cross-session-message>|(?=\n[ \t]*<cross-session-message\b)|$)/g;
|
|
12
50
|
var ATTR = /([\w-]+)="([^"]*)"/g;
|
|
@@ -24,7 +62,7 @@ function crossSession(text) {
|
|
|
24
62
|
}
|
|
25
63
|
var LEDGE_CLOSE = "</ledge-message>";
|
|
26
64
|
var LEDGE_BLOCK = /^<ledge-message\b([^>]*)>([\s\S]*?)(?:<\/ledge-message>|$)/;
|
|
27
|
-
var messageNote = (from, to) => `(message de la session ${from}, pas de l'utilisateur ; pour r\xE9pondre : ${to === "
|
|
65
|
+
var messageNote = (from, to) => `(message de la session ${from}, pas de l'utilisateur ; pour r\xE9pondre : ${to === "claude" ? `SendMessage vers ${from}` : `ledge send ${from} "\u2026"`})`;
|
|
28
66
|
var NOTE_SENDER = /^\(message de la session (.+?), pas de l'utilisateur ; /;
|
|
29
67
|
function stripNote(text) {
|
|
30
68
|
const body = text.trimStart();
|
|
@@ -36,9 +74,20 @@ var ESCAPES = /\x1b(?:\[[0-?]*[ -/]*[@-~]|[\]PX^_][^\x07\x1b]*(?:\x07|\x1b\\)|[
|
|
|
36
74
|
var CONTROLS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
37
75
|
var typable = (text, newline) => text.replace(ESCAPES, "").replace(/\r\n|\r|\n/g, newline).replace(CONTROLS, " ");
|
|
38
76
|
var ledgeBody = (text) => typable(text.replaceAll("<", "\u2039"), " \u23CE ");
|
|
77
|
+
var INLINE_MAX = 800;
|
|
78
|
+
var INLINE_LINES = 8;
|
|
79
|
+
var POINTER_HEAD = 200;
|
|
80
|
+
var lineCount = (text) => text.split(/\r\n|\r|\n/).length;
|
|
81
|
+
var mailId = (rid, length = 6) => `m${createHash("sha1").update(rid).digest("hex").slice(0, length)}`;
|
|
82
|
+
var inline = (text) => text.length <= INLINE_MAX && lineCount(text) <= INLINE_LINES;
|
|
83
|
+
var attr = (s) => typable(s, " ").replace(/["<>]/g, "").trim();
|
|
39
84
|
function formatLedgeMessage(m) {
|
|
40
|
-
const from =
|
|
41
|
-
|
|
85
|
+
const from = attr(m.from);
|
|
86
|
+
const open = `<ledge-message from="${from}" tool="${m.tool}"${m.id ? ` id="${attr(m.id)}"` : ""}>${messageNote(from, m.to)} `;
|
|
87
|
+
if (!m.id || inline(m.text)) return `${open}${ledgeBody(m.text)}${LEDGE_CLOSE}`;
|
|
88
|
+
const first = m.summary?.trim() || m.text.trim().split(/\r\n|\r|\n/)[0].trim();
|
|
89
|
+
const head = ledgeBody(first.length > POINTER_HEAD ? `${first.slice(0, POINTER_HEAD)}\u2026` : first);
|
|
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}`;
|
|
42
91
|
}
|
|
43
92
|
function ledgeMessages(text) {
|
|
44
93
|
const m = LEDGE_BLOCK.exec(text.trimStart());
|
|
@@ -46,8 +95,49 @@ function ledgeMessages(text) {
|
|
|
46
95
|
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
47
96
|
const from = attrs.get("from");
|
|
48
97
|
const tool = attrs.get("tool");
|
|
98
|
+
const id = attrs.get("id");
|
|
49
99
|
if (!from) return void 0;
|
|
50
|
-
return [{ from, ...tool ? { tool } : {}, text: stripNote(m[2]).text.trim() }];
|
|
100
|
+
return [{ from, ...tool ? { tool } : {}, ...id ? { id } : {}, text: stripNote(m[2]).text.trim() }];
|
|
101
|
+
}
|
|
102
|
+
var NOTICE_BLOCK = /^<ledge-notice\b([^>]*)>([\s\S]*?)(?:<\/ledge-notice>|$)/;
|
|
103
|
+
var NOTICE_QUOTE = 80;
|
|
104
|
+
var NOTICE_REASON = " \xBB) : ";
|
|
105
|
+
function formatLedgeNotice(n) {
|
|
106
|
+
const to = attr(n.to);
|
|
107
|
+
const flat = typable(n.text, " ").trim();
|
|
108
|
+
const quote = ledgeBody(flat.length > NOTICE_QUOTE ? `${flat.slice(0, NOTICE_QUOTE)}\u2026` : flat);
|
|
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>`;
|
|
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
|
+
}
|
|
121
|
+
var TASK_BLOCK = /^<ledge-task\b([^>]*)>([\s\S]*?)(?:<\/ledge-task>|$)/;
|
|
122
|
+
function formatLedgeTask(t) {
|
|
123
|
+
return `<ledge-task id="${attr(t.id)}" from="${attr(t.from)}">${ledgeBody(t.text)}</ledge-task>`;
|
|
124
|
+
}
|
|
125
|
+
function ledgeTask(text) {
|
|
126
|
+
const m = TASK_BLOCK.exec(text.trimStart());
|
|
127
|
+
if (!m) return void 0;
|
|
128
|
+
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
129
|
+
const id = attrs.get("id");
|
|
130
|
+
const from = attrs.get("from");
|
|
131
|
+
return id ? { id, ...from ? { from } : {}, text: m[2].trim() } : void 0;
|
|
132
|
+
}
|
|
133
|
+
function ledgeNotice(text) {
|
|
134
|
+
const m = NOTICE_BLOCK.exec(text.trimStart());
|
|
135
|
+
if (!m) return void 0;
|
|
136
|
+
const to = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]])).get("to");
|
|
137
|
+
if (!to) return void 0;
|
|
138
|
+
const body = m[2].trim();
|
|
139
|
+
const at = body.lastIndexOf(NOTICE_REASON);
|
|
140
|
+
return { to, ...at >= 0 ? { reason: body.slice(at + NOTICE_REASON.length) } : {}, text: body };
|
|
51
141
|
}
|
|
52
142
|
var NOTICE_HEAD = /^\[Cross-session ([\w -]+?) notice\]/;
|
|
53
143
|
var NOTICE_SPLIT = /\n\s*(?=\[Cross-session [\w -]+? notice\])/;
|
|
@@ -393,7 +483,7 @@ function reduceEvent(s, ev) {
|
|
|
393
483
|
case "PreToolUse":
|
|
394
484
|
case "PermissionRequest":
|
|
395
485
|
case "Notification": {
|
|
396
|
-
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && !!crossSessionNotices(ev.text);
|
|
486
|
+
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text) || !!ledgeTask(ev.text) || !!ledgeTeamNotice(ev.text));
|
|
397
487
|
if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !notice2 && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
|
|
398
488
|
if (s.pending?.source === "rpc" && ev.name === "PreToolUse") return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
399
489
|
const incoming = pendingFor(ev);
|
|
@@ -520,12 +610,23 @@ function resolveTarget(to, sessions, peers) {
|
|
|
520
610
|
}
|
|
521
611
|
|
|
522
612
|
export {
|
|
613
|
+
teammates,
|
|
614
|
+
seatContext,
|
|
615
|
+
resolveRoleAddress,
|
|
523
616
|
crossSession,
|
|
524
617
|
messageNote,
|
|
525
618
|
stripNote,
|
|
526
619
|
ledgeBody,
|
|
620
|
+
mailId,
|
|
621
|
+
inline,
|
|
527
622
|
formatLedgeMessage,
|
|
528
623
|
ledgeMessages,
|
|
624
|
+
formatLedgeNotice,
|
|
625
|
+
formatTeamNotice,
|
|
626
|
+
ledgeTeamNotice,
|
|
627
|
+
formatLedgeTask,
|
|
628
|
+
ledgeTask,
|
|
629
|
+
ledgeNotice,
|
|
529
630
|
crossSessionNotices,
|
|
530
631
|
Refusal,
|
|
531
632
|
NO_ALWAYS,
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
2
|
+
EFFORT_ARGS,
|
|
3
|
+
MODEL_ARGS
|
|
4
|
+
} from "./chunk-7IQ6FV2F.js";
|
|
5
|
+
import {
|
|
6
|
+
autonomyArgs,
|
|
7
|
+
findRole
|
|
8
|
+
} from "./chunk-3HCID6NW.js";
|
|
5
9
|
import {
|
|
6
10
|
exitFile,
|
|
7
11
|
spawnSession
|
|
8
|
-
} from "./chunk-
|
|
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-
|
|
19
|
+
} from "./chunk-HP3G2TYJ.js";
|
|
12
20
|
import {
|
|
13
21
|
realTmux
|
|
14
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WJ6YMZTT.js";
|
|
15
23
|
import {
|
|
16
24
|
log
|
|
17
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-43D6NMYH.js";
|
|
18
26
|
import {
|
|
19
27
|
ensureLedgeDir
|
|
20
|
-
} from "./chunk-
|
|
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
|
|
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-
|
|
4
|
+
} from "./chunk-HP3G2TYJ.js";
|
|
5
5
|
import {
|
|
6
6
|
paths
|
|
7
|
-
} from "./chunk-
|
|
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 {
|
|
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-
|
|
3
|
+
} from "./chunk-GXFU4XAX.js";
|
|
4
4
|
import {
|
|
5
5
|
isArchived
|
|
6
|
-
} from "./chunk-
|
|
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
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODEX_HOOK_VAR
|
|
3
|
+
} from "./chunk-TRTS24PF.js";
|
|
4
|
+
import {
|
|
5
|
+
connect
|
|
6
|
+
} from "./chunk-GXFU4XAX.js";
|
|
7
|
+
import {
|
|
8
|
+
BRIEF_MAX
|
|
9
|
+
} from "./chunk-HP3G2TYJ.js";
|
|
10
|
+
|
|
11
|
+
// src/cli/handover.ts
|
|
12
|
+
import { randomUUID } from "crypto";
|
|
13
|
+
var HANDOVER_USAGE = `ledge handover "<bilan>" ton bilan de rel\xE8ve, quand Ledge te le demande (fait, en cours, suite, pi\xE8ges, fichiers cl\xE9s)
|
|
14
|
+
ledge handover show relire le bilan de la session dont tu as pris la rel\xE8ve`;
|
|
15
|
+
var HANDOVER_MS = 4e3;
|
|
16
|
+
function handoverArgs(args) {
|
|
17
|
+
if (args.length === 1 && args[0] === "show") return { op: "show" };
|
|
18
|
+
const text = args.join(" ").trim();
|
|
19
|
+
if (!args.length || !text || text.length > BRIEF_MAX) return void 0;
|
|
20
|
+
return { op: "brief", text };
|
|
21
|
+
}
|
|
22
|
+
async function handoverOnClient(client, args, by, ms) {
|
|
23
|
+
const op = handoverArgs(args);
|
|
24
|
+
if (!op) return { line: HANDOVER_USAGE, code: 1 };
|
|
25
|
+
try {
|
|
26
|
+
const rid = randomUUID();
|
|
27
|
+
client.send(op.op === "show" ? { type: "handover.show", rid, by } : { type: "handover.brief", rid, by, text: op.text });
|
|
28
|
+
const r = await client.next("handover.result", ms);
|
|
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 };
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function runHandover(args, opts = {}) {
|
|
36
|
+
if (!handoverArgs(args)) return { line: HANDOVER_USAGE, code: args.length ? 1 : 0 };
|
|
37
|
+
const env = opts.env ?? process.env;
|
|
38
|
+
const by = env[CODEX_HOOK_VAR] || env.LEDGE_SESSION;
|
|
39
|
+
if (!by) return { line: "ledge handover ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
|
|
40
|
+
const deadline = Date.now() + (opts.timeoutMs ?? HANDOVER_MS);
|
|
41
|
+
const left = () => Math.max(0, deadline - Date.now());
|
|
42
|
+
let client;
|
|
43
|
+
try {
|
|
44
|
+
client = await connect({ port: opts.port, timeoutMs: left() });
|
|
45
|
+
} catch {
|
|
46
|
+
return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return await handoverOnClient(client, args, by, left());
|
|
50
|
+
} finally {
|
|
51
|
+
await client.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
HANDOVER_USAGE,
|
|
57
|
+
handoverArgs,
|
|
58
|
+
handoverOnClient,
|
|
59
|
+
runHandover
|
|
60
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODEX_HOOK_VAR
|
|
3
|
+
} from "./chunk-TRTS24PF.js";
|
|
1
4
|
import {
|
|
2
5
|
connect,
|
|
3
6
|
sendMessageAndAwaitAck
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
CODEX_HOOK_VAR
|
|
7
|
-
} from "./chunk-H36JD4E3.js";
|
|
7
|
+
} from "./chunk-GXFU4XAX.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/send.ts
|
|
10
10
|
import { randomUUID } from "crypto";
|
|
@@ -58,6 +58,40 @@ async function sendMessage(args, opts = {}) {
|
|
|
58
58
|
await client.close();
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
var READ_USAGE = "ledge read <id> print a long message whole (its id is in the pointer you received)";
|
|
62
|
+
var MAIL_ID = /^m[0-9a-f]{6,40}$/;
|
|
63
|
+
async function readOnClient(client, args, by, ms) {
|
|
64
|
+
const [id] = args;
|
|
65
|
+
if (args.length !== 1 || !id || !MAIL_ID.test(id)) return { line: READ_USAGE, code: 1 };
|
|
66
|
+
try {
|
|
67
|
+
client.send({ type: "message.read", id, by });
|
|
68
|
+
const body = await client.next("message.body", ms);
|
|
69
|
+
if (body.text === void 0) return { line: `introuvable : ${body.reason ?? "raison inconnue"}`, code: 1 };
|
|
70
|
+
return { line: `De ${body.from} \xE0 ${body.to}, ${body.at} :
|
|
71
|
+
${body.text}`, code: 0 };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function readMessage(args, opts = {}) {
|
|
77
|
+
if (args.length !== 1 || !MAIL_ID.test(args[0])) return { line: READ_USAGE, code: 1 };
|
|
78
|
+
const env = opts.env ?? process.env;
|
|
79
|
+
const by = env[CODEX_HOOK_VAR] || env.LEDGE_SESSION;
|
|
80
|
+
if (!by) return { line: "ledge read ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
|
|
81
|
+
const deadline = Date.now() + (opts.timeoutMs ?? SEND_MS);
|
|
82
|
+
const left = () => Math.max(0, deadline - Date.now());
|
|
83
|
+
let client;
|
|
84
|
+
try {
|
|
85
|
+
client = await connect({ port: opts.port, timeoutMs: left() });
|
|
86
|
+
} catch {
|
|
87
|
+
return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return await readOnClient(client, args, by, left());
|
|
91
|
+
} finally {
|
|
92
|
+
await client.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
61
95
|
function parseLedgeCommand(command) {
|
|
62
96
|
command = command.replace(/[\r\n\s]+$/, "");
|
|
63
97
|
const words = [];
|
|
@@ -95,11 +129,16 @@ function parseLedgeCommand(command) {
|
|
|
95
129
|
if (words[0] !== "ledge") return void 0;
|
|
96
130
|
if (words[1] === "who" && words.length === 2) return { cmd: "who" };
|
|
97
131
|
if (words[1] === "send") return { cmd: "send", args: words.slice(2) };
|
|
132
|
+
if (words[1] === "read") return { cmd: "read", args: words.slice(2) };
|
|
133
|
+
if (words[1] === "task") return { cmd: "task", args: words.slice(2) };
|
|
134
|
+
if (words[1] === "handover") return { cmd: "handover", args: words.slice(2) };
|
|
98
135
|
return void 0;
|
|
99
136
|
}
|
|
100
137
|
|
|
101
138
|
export {
|
|
102
139
|
sendOnClient,
|
|
103
140
|
sendMessage,
|
|
141
|
+
readOnClient,
|
|
142
|
+
readMessage,
|
|
104
143
|
parseLedgeCommand
|
|
105
144
|
};
|