@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.
- package/README.md +7 -1
- package/assets/ledge.ico +0 -0
- package/dist/cli/{attach-EYIX7FZN.js → attach-3BKQSEWC.js} +12 -7
- package/dist/cli/{bridge-6UHCD76N.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-JQZHXVIZ.js → chunk-43D6NMYH.js} +1 -1
- package/dist/cli/chunk-4RD7OJ2J.js +6 -0
- package/dist/cli/{chunk-ELLXOV4Q.js → chunk-7ZAA75HG.js} +1 -1
- package/dist/cli/{chunk-5ZCHFGCV.js → chunk-BVBX222F.js} +9 -7
- package/dist/cli/{chunk-OVJL2QTN.js → chunk-FQZ3IX6E.js} +2 -2
- package/dist/cli/{chunk-VVK4I6B5.js → chunk-GXFU4XAX.js} +1 -1
- package/dist/cli/chunk-HP3G2TYJ.js +1772 -0
- package/dist/cli/{chunk-NC5YAEJB.js → chunk-KKNE7BFZ.js} +55 -3
- package/dist/cli/{chunk-36KJUV62.js → chunk-LOOOHYZR.js} +38 -12
- package/dist/cli/{chunk-VE4LGNXX.js → chunk-TRTS24PF.js} +23 -5
- package/dist/cli/{chunk-Q7YNLP66.js → chunk-WCS24ICX.js} +5 -3
- package/dist/cli/{chunk-LAUNHDTX.js → chunk-WJ6YMZTT.js} +2 -2
- package/dist/cli/{daemon-QEJLAIBT.js → chunk-WP77O5LC.js} +6045 -7228
- package/dist/cli/chunk-Y5DJ5INP.js +60 -0
- package/dist/cli/{chunk-AQ25HFBP.js → chunk-ZUYOA55W.js} +5 -4
- package/dist/cli/daemon-66EUIZ3J.js +2240 -0
- package/dist/cli/handover-UXISKYPS.js +16 -0
- package/dist/cli/{hook-AE5HC6AS.js → hook-5UAARBWU.js} +86 -25
- package/dist/cli/index.js +30 -16
- package/dist/cli/launch-YQYCDZ7N.js +34 -0
- package/dist/cli/{send-2OZ3YZRV.js → send-NLVR6PL2.js} +5 -5
- package/dist/cli/{setup-2VM4GCDJ.js → setup-EEIXRAMU.js} +4 -4
- package/dist/cli/{status-3VJAO6UL.js → status-2OCYHOWZ.js} +3 -3
- package/dist/cli/{task-PMKNYP6M.js → task-AOOWHPMN.js} +5 -5
- package/dist/cli/team-TVNSVYWW.js +101 -0
- package/dist/cli/{tui-DWA2EDC2.js → tui-6JY7IS4D.js} +985 -125
- package/dist/cli/{uninstall-AKUWE33D.js → uninstall-UUN7LIIT.js} +4 -4
- package/dist/cli/{version-WOGCIZY3.js → version-XSEI3V2C.js} +1 -1
- package/dist/cli/who-52MENZTT.js +11 -0
- package/package.json +2 -2
- package/dist/cli/chunk-QD27W6W5.js +0 -1474
- package/dist/cli/chunk-V2H7UD2N.js +0 -6
- package/dist/cli/launch-B4G5OFBV.js +0 -27
- package/dist/cli/who-37TNDYVA.js +0 -11
|
@@ -1,10 +1,47 @@
|
|
|
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
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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
|
}
|