@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.
- package/README.md +8 -0
- package/dist/cli/{action-NQRSZEC5.js → action-MCR4BCSE.js} +5 -5
- package/dist/cli/ask-L3PSQZ2K.js +17 -0
- package/dist/cli/{attach-KXOSFUHO.js → attach-R7FT7LFH.js} +9 -14
- package/dist/cli/brain-BJTBN2ZK.js +20 -0
- package/dist/cli/{bridge-XCTXVWKH.js → bridge-YBHXFEFX.js} +2 -2
- package/dist/cli/{chunk-VKRFHDRK.js → chunk-2X56ZRK7.js} +1122 -1164
- package/dist/cli/{chunk-3NQNWGDX.js → chunk-6EW2FD2U.js} +3 -5
- package/dist/cli/chunk-DJMFCYF4.js +60 -0
- package/dist/cli/chunk-DLKJOK3B.js +6 -0
- package/dist/cli/{chunk-VMGACWNP.js → chunk-FWLT3SAN.js} +1 -1
- package/dist/cli/{chunk-6IYPRUQG.js → chunk-G3ST5WKR.js} +1 -1
- package/dist/cli/{chunk-ZI47U5CQ.js → chunk-GY35ZR5I.js} +102 -72
- package/dist/cli/{chunk-NLL2EQJ2.js → chunk-KBSXP63H.js} +22 -7
- package/dist/cli/chunk-LOXLGCGN.js +57 -0
- package/dist/cli/{chunk-LG3XFIPV.js → chunk-O4MUYFPH.js} +6 -7
- package/dist/cli/{chunk-JLUHY5AB.js → chunk-OR2NHT5O.js} +4 -4
- package/dist/cli/chunk-S6BZV7AY.js +57 -0
- package/dist/cli/{chunk-CR3LGTZQ.js → chunk-TCQ5PYIF.js} +3 -3
- package/dist/cli/{chunk-RYZ6FHYT.js → chunk-UMQGRTA4.js} +17 -39
- package/dist/cli/{chunk-3CF4FQWG.js → chunk-VQR5ME75.js} +4 -3
- package/dist/cli/{chunk-HOOHS3MX.js → chunk-W724IVDC.js} +1 -1
- package/dist/cli/{chunk-K7UEKSB2.js → chunk-WYRQZA4G.js} +2 -2
- package/dist/cli/chunk-XTDE2URL.js +365 -0
- package/dist/cli/daemon-ICC6LBCO.js +16562 -0
- package/dist/cli/{decide-OSSTI3FE.js → decide-JSY46QZC.js} +14 -16
- package/dist/cli/{handover-2G7OEWIG.js → handover-5RUSRVAD.js} +5 -5
- package/dist/cli/{hook-XY4E5H7O.js → hook-QQP3ENGA.js} +87 -81
- package/dist/cli/index.js +40 -28
- package/dist/cli/launch-RTBGUJSC.js +28 -0
- package/dist/cli/{send-RUHGKLMA.js → send-BIIH6SQW.js} +5 -5
- package/dist/cli/{setup-K42IVMHS.js → setup-OLVRJLJE.js} +4 -4
- package/dist/cli/{status-4Z2VJFID.js → status-Z3I33KZJ.js} +3 -3
- package/dist/cli/{tui-4IRYQXYU.js → tui-AIXCETOB.js} +6036 -3629
- package/dist/cli/{uninstall-4IMMR55T.js → uninstall-X65CWHIJ.js} +4 -4
- package/dist/cli/{version-L2Y7MRH6.js → version-QMHFVP3N.js} +1 -1
- package/dist/cli/who-C2VMEM35.js +12 -0
- package/package.json +21 -6
- package/dist/cli/chunk-CIQAFCX3.js +0 -118
- package/dist/cli/chunk-JHFKBKSO.js +0 -159
- package/dist/cli/chunk-OYDTU6AP.js +0 -9482
- package/dist/cli/chunk-QSRB3F5Y.js +0 -198
- package/dist/cli/chunk-UME2F7VM.js +0 -6
- package/dist/cli/daemon-ZUHJKLSL.js +0 -2322
- package/dist/cli/launch-7WOARLZM.js +0 -35
- package/dist/cli/task-2GESYTOL.js +0 -19
- package/dist/cli/team-MTSKNMAE.js +0 -205
- package/dist/cli/who-PSKHASMK.js +0 -12
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connect
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FWLT3SAN.js";
|
|
4
4
|
import {
|
|
5
5
|
isArchived
|
|
6
|
-
} from "./chunk-
|
|
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",
|
|
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
|
+
};
|
|
@@ -1,47 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ledgeTitleRest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-O4MUYFPH.js";
|
|
4
4
|
import {
|
|
5
5
|
cleanTopic,
|
|
6
|
-
|
|
6
|
+
mandateEntries,
|
|
7
7
|
sessionTitle
|
|
8
|
-
} from "./chunk-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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)
|
|
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
|
-
|
|
651
|
-
|
|
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-
|
|
3
|
+
} from "./chunk-G3ST5WKR.js";
|
|
4
4
|
import {
|
|
5
5
|
paths
|
|
6
|
-
} from "./chunk-
|
|
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
|
-
|
|
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(
|
|
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-
|
|
6
|
+
} from "./chunk-2X56ZRK7.js";
|
|
7
7
|
import {
|
|
8
8
|
paths
|
|
9
|
-
} from "./chunk-
|
|
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)
|
|
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.
|
|
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.
|
|
250
|
-
...opts.
|
|
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-
|
|
3
|
+
} from "./chunk-O4MUYFPH.js";
|
|
4
4
|
import {
|
|
5
5
|
connect
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FWLT3SAN.js";
|
|
7
7
|
import {
|
|
8
8
|
BRIEF_MAX
|
|
9
|
-
} from "./chunk-
|
|
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
|
|
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-
|
|
3
|
+
} from "./chunk-O4MUYFPH.js";
|
|
4
4
|
import {
|
|
5
5
|
connect
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FWLT3SAN.js";
|
|
7
7
|
import {
|
|
8
8
|
SESSION_KEY_VAR,
|
|
9
9
|
actionById
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2X56ZRK7.js";
|
|
11
11
|
|
|
12
12
|
// src/cli/action.ts
|
|
13
13
|
import { randomUUID } from "crypto";
|