@paradow/ledge 0.33.0 → 0.35.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/dist/cli/index.js +39003 -46
- package/dist/cli/reduceWorker.js +2037 -0
- package/dist/ledge-guide.md +26 -49
- package/package.json +5 -1
- package/dist/cli/action-QW2ZQQBN.js +0 -27
- package/dist/cli/attach-ULTYWCJ7.js +0 -74
- package/dist/cli/bridge-CSLF7EW6.js +0 -70
- package/dist/cli/chunk-3DB3VOMM.js +0 -723
- package/dist/cli/chunk-4HXJQEUN.js +0 -118
- package/dist/cli/chunk-4VNKZOC2.js +0 -160
- package/dist/cli/chunk-6IYPRUQG.js +0 -17
- package/dist/cli/chunk-ASYAWVFW.js +0 -95
- package/dist/cli/chunk-CCSUP5ZI.js +0 -45
- package/dist/cli/chunk-CPERVAPQ.js +0 -232
- package/dist/cli/chunk-CSLVBADC.js +0 -60
- package/dist/cli/chunk-E3LXONBI.js +0 -6
- package/dist/cli/chunk-GXL3X7M7.js +0 -144
- package/dist/cli/chunk-HOOHS3MX.js +0 -39
- package/dist/cli/chunk-JEGPTNWV.js +0 -270
- package/dist/cli/chunk-K7UEKSB2.js +0 -305
- package/dist/cli/chunk-NLL2EQJ2.js +0 -94
- package/dist/cli/chunk-NWIICISV.js +0 -290
- package/dist/cli/chunk-OLLQBK6N.js +0 -41
- package/dist/cli/chunk-PZ3F4QH3.js +0 -9758
- package/dist/cli/chunk-U67V476Y.js +0 -35
- package/dist/cli/chunk-WXE2NOE5.js +0 -2674
- package/dist/cli/chunk-XOZD4BP5.js +0 -85
- package/dist/cli/daemon-5GT545BB.js +0 -2322
- package/dist/cli/decide-PFHHOCFZ.js +0 -59
- package/dist/cli/handover-EMO6JT7O.js +0 -17
- package/dist/cli/hook-W2BU74H5.js +0 -523
- package/dist/cli/launch-LP7YJR5N.js +0 -36
- package/dist/cli/send-NEI4X33R.js +0 -19
- package/dist/cli/setup-K42IVMHS.js +0 -38
- package/dist/cli/status-U2HGGKHF.js +0 -28
- package/dist/cli/task-PDDCE23D.js +0 -19
- package/dist/cli/team-S3ASP22H.js +0 -205
- package/dist/cli/tui-UPMRMCPC.js +0 -19216
- package/dist/cli/uninstall-4IMMR55T.js +0 -162
- package/dist/cli/version-LUMK3Z3T.js +0 -7
- package/dist/cli/who-NM3GPAPJ.js +0 -12
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CODEX_HOOK_VAR
|
|
3
|
-
} from "./chunk-JEGPTNWV.js";
|
|
4
|
-
import {
|
|
5
|
-
connect
|
|
6
|
-
} from "./chunk-ASYAWVFW.js";
|
|
7
|
-
import {
|
|
8
|
-
BRIEF_MAX
|
|
9
|
-
} from "./chunk-WXE2NOE5.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,144 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CODEX_HOOK_VAR
|
|
3
|
-
} from "./chunk-JEGPTNWV.js";
|
|
4
|
-
import {
|
|
5
|
-
connect,
|
|
6
|
-
sendMessageAndAwaitAck
|
|
7
|
-
} from "./chunk-ASYAWVFW.js";
|
|
8
|
-
|
|
9
|
-
// src/cli/send.ts
|
|
10
|
-
import { randomUUID } from "crypto";
|
|
11
|
-
var USAGE = 'ledge send <nom> "<message>" [--summary <texte>] write to another Ledge session';
|
|
12
|
-
var SEND_MS = 4e3;
|
|
13
|
-
function sendUsage(args) {
|
|
14
|
-
const positional = [];
|
|
15
|
-
let summary;
|
|
16
|
-
for (let i = 0; i < args.length; i++) {
|
|
17
|
-
if (args[i] === "--summary") {
|
|
18
|
-
summary = args[++i];
|
|
19
|
-
if (summary === void 0) return void 0;
|
|
20
|
-
} else positional.push(args[i]);
|
|
21
|
-
}
|
|
22
|
-
const [to, text] = positional;
|
|
23
|
-
if (positional.length !== 2 || !to || !text) return void 0;
|
|
24
|
-
return { to, text, ...summary ? { summary } : {} };
|
|
25
|
-
}
|
|
26
|
-
async function sendOnClient(client, args, from, ms) {
|
|
27
|
-
const parsed = sendUsage(args);
|
|
28
|
-
if (!parsed) return { line: USAGE, code: 1 };
|
|
29
|
-
const { to, text, summary } = parsed;
|
|
30
|
-
let sent = false;
|
|
31
|
-
try {
|
|
32
|
-
const pending = sendMessageAndAwaitAck(client, { type: "session.message", rid: randomUUID(), from, to, text, ...summary ? { summary } : {} }, ms);
|
|
33
|
-
sent = true;
|
|
34
|
-
const ack = await pending;
|
|
35
|
-
if (ack.outcome === "accepted") return { line: `remis \xE0 ${to}`, code: 0 };
|
|
36
|
-
if (ack.outcome === "queued") return { line: `${to} est occup\xE9 : il le recevra d\xE8s qu'il sera libre`, code: 0 };
|
|
37
|
-
return { line: `refus\xE9 : ${ack.reason ?? "raison inconnue"}`, code: 1 };
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return { line: sent ? "pas de r\xE9ponse de Ledge : le message est peut-\xEAtre d\xE9j\xE0 parti" : `refus\xE9 : ${e.message}`, code: 1 };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function sendMessage(args, opts = {}) {
|
|
43
|
-
if (!sendUsage(args)) return { line: USAGE, code: 1 };
|
|
44
|
-
const env = opts.env ?? process.env;
|
|
45
|
-
const from = opts.from ?? (env[CODEX_HOOK_VAR] || env.LEDGE_SESSION);
|
|
46
|
-
if (!from) return { line: "ledge send ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
|
|
47
|
-
const deadline = Date.now() + (opts.timeoutMs ?? SEND_MS);
|
|
48
|
-
const left = () => Math.max(0, deadline - Date.now());
|
|
49
|
-
let client;
|
|
50
|
-
try {
|
|
51
|
-
client = await connect({ port: opts.port, timeoutMs: left() });
|
|
52
|
-
} catch {
|
|
53
|
-
return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
return await sendOnClient(client, args, from, left());
|
|
57
|
-
} finally {
|
|
58
|
-
await client.close();
|
|
59
|
-
}
|
|
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
|
-
}
|
|
95
|
-
function parseLedgeCommand(command) {
|
|
96
|
-
command = command.replace(/[\r\n\s]+$/, "");
|
|
97
|
-
const words = [];
|
|
98
|
-
let word = "";
|
|
99
|
-
let inWord = false;
|
|
100
|
-
let quote;
|
|
101
|
-
for (let i = 0; i < command.length; i++) {
|
|
102
|
-
const c = command[i];
|
|
103
|
-
if (quote === "'") {
|
|
104
|
-
if (c === "'") quote = void 0;
|
|
105
|
-
else word += c;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (quote === '"') {
|
|
109
|
-
if (c === '"') quote = void 0;
|
|
110
|
-
else if (c === "$" || c === "`") return void 0;
|
|
111
|
-
else if (c === "\\" && '"\\$`'.includes(command[i + 1] ?? "")) word += command[++i];
|
|
112
|
-
else word += c;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (c === "\n" || c === "\r") return void 0;
|
|
116
|
-
if (c === " " || c === " ") {
|
|
117
|
-
if (inWord) words.push(word);
|
|
118
|
-
word = "";
|
|
119
|
-
inWord = false;
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
if (";&|<>()$`\\*?[~{".includes(c) || c === "#" && !inWord) return void 0;
|
|
123
|
-
inWord = true;
|
|
124
|
-
if (c === '"' || c === "'") quote = c;
|
|
125
|
-
else word += c;
|
|
126
|
-
}
|
|
127
|
-
if (quote) return void 0;
|
|
128
|
-
if (inWord) words.push(word);
|
|
129
|
-
if (words[0] !== "ledge") return void 0;
|
|
130
|
-
if (words[1] === "who" && words.length === 2) return { cmd: "who" };
|
|
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) };
|
|
135
|
-
return void 0;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export {
|
|
139
|
-
sendOnClient,
|
|
140
|
-
sendMessage,
|
|
141
|
-
readOnClient,
|
|
142
|
-
readMessage,
|
|
143
|
-
parseLedgeCommand
|
|
144
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// src/paths.ts
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
5
|
-
var TITLE_LINES = `set -g set-titles on
|
|
6
|
-
set -g set-titles-string "#{@ledge_title}"
|
|
7
|
-
`;
|
|
8
|
-
var TMUX_CONF = `set -g status off
|
|
9
|
-
set -g mouse on
|
|
10
|
-
set -g default-terminal "tmux-256color"
|
|
11
|
-
set -sg escape-time 10
|
|
12
|
-
set -g history-limit 20000
|
|
13
|
-
set -g remain-on-exit off
|
|
14
|
-
${TITLE_LINES}`;
|
|
15
|
-
function ledgeDir() {
|
|
16
|
-
return process.env.LEDGE_HOME ?? join(homedir(), ".ledge");
|
|
17
|
-
}
|
|
18
|
-
function paths() {
|
|
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"), attachments: join(dir, "attachments") };
|
|
21
|
-
}
|
|
22
|
-
async function ensureLedgeDir() {
|
|
23
|
-
const p = paths();
|
|
24
|
-
await mkdir(p.dir, { recursive: true });
|
|
25
|
-
let conf = null;
|
|
26
|
-
try {
|
|
27
|
-
conf = await readFile(p.tmuxConf, "utf8");
|
|
28
|
-
} catch {
|
|
29
|
-
}
|
|
30
|
-
if (conf === null) await writeFile(p.tmuxConf, TMUX_CONF);
|
|
31
|
-
else if (!conf.includes("set-titles")) await writeFile(p.tmuxConf, `${conf.trimEnd()}
|
|
32
|
-
${TITLE_LINES}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export {
|
|
36
|
-
ledgeDir,
|
|
37
|
-
paths,
|
|
38
|
-
ensureLedgeDir
|
|
39
|
-
};
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NAMES,
|
|
3
|
-
SESSION_KEY_VAR,
|
|
4
|
-
pickName,
|
|
5
|
-
sessionKey
|
|
6
|
-
} from "./chunk-WXE2NOE5.js";
|
|
7
|
-
import {
|
|
8
|
-
paths
|
|
9
|
-
} from "./chunk-HOOHS3MX.js";
|
|
10
|
-
|
|
11
|
-
// src/daemon/transcript.ts
|
|
12
|
-
import { appendFileSync, closeSync, fstatSync, openSync, readFileSync, readSync, statSync } from "fs";
|
|
13
|
-
import { homedir } from "os";
|
|
14
|
-
import { basename, join } from "path";
|
|
15
|
-
|
|
16
|
-
// src/names.ts
|
|
17
|
-
var ledgeShaped = (n, own) => n === own || NAMES.includes(n.replace(/-\d+$/, ""));
|
|
18
|
-
function ledgeTitleRest(title, own) {
|
|
19
|
-
if (ledgeShaped(title, own)) return "";
|
|
20
|
-
const i = title.startsWith(`${own} - `) ? own.length : title.indexOf(" - ");
|
|
21
|
-
return i > 0 && ledgeShaped(title.slice(0, i), own) ? title.slice(i + 3).trim() : void 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// src/daemon/transcript.ts
|
|
25
|
-
var TAIL_BYTES = 512 * 1024;
|
|
26
|
-
function tailLines(path, bytes = TAIL_BYTES) {
|
|
27
|
-
let fd;
|
|
28
|
-
try {
|
|
29
|
-
fd = openSync(path, "r");
|
|
30
|
-
} catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const size = fstatSync(fd).size;
|
|
35
|
-
const start = Math.max(0, size - bytes);
|
|
36
|
-
const buf = Buffer.alloc(size - start);
|
|
37
|
-
readSync(fd, buf, 0, buf.length, start);
|
|
38
|
-
const text = buf.toString("utf8");
|
|
39
|
-
const lines = text.split("\n");
|
|
40
|
-
if (start > 0) lines.shift();
|
|
41
|
-
if (!text.endsWith("\n")) lines.pop();
|
|
42
|
-
return lines.filter((l) => l.trim());
|
|
43
|
-
} finally {
|
|
44
|
-
closeSync(fd);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
var texts = (blocks, kind) => (Array.isArray(blocks) ? blocks : []).filter((b) => b?.type === kind && typeof b.text === "string").map((b) => b.text.trim()).filter(Boolean).join("\n\n");
|
|
48
|
-
function agentMessages(path, opts = {}) {
|
|
49
|
-
const out = [];
|
|
50
|
-
for (const line of tailLines(path, opts.bytes ?? TAIL_BYTES)) {
|
|
51
|
-
let e;
|
|
52
|
-
try {
|
|
53
|
-
e = JSON.parse(line);
|
|
54
|
-
} catch {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const at = typeof e.timestamp === "string" ? e.timestamp : void 0;
|
|
58
|
-
if (!at) continue;
|
|
59
|
-
if (e.type === "assistant" && (opts.sidechain || e.isSidechain !== true)) {
|
|
60
|
-
const text = texts(e.message?.content, "text");
|
|
61
|
-
if (text && typeof e.uuid === "string") out.push({ id: e.uuid, at, text });
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (e.type === "event_msg") {
|
|
65
|
-
const p = e.payload;
|
|
66
|
-
const item = p?.type === "item_completed" ? p.item : void 0;
|
|
67
|
-
if (item?.type !== "AgentMessage" || typeof item.id !== "string") continue;
|
|
68
|
-
const text = texts(item.content, "Text");
|
|
69
|
-
if (text) out.push({ id: item.id, at, text });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return out;
|
|
73
|
-
}
|
|
74
|
-
function codexPlan(path, turnId) {
|
|
75
|
-
let plan;
|
|
76
|
-
for (const line of tailLines(path)) {
|
|
77
|
-
let e;
|
|
78
|
-
try {
|
|
79
|
-
e = JSON.parse(line);
|
|
80
|
-
} catch {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const p = e.payload;
|
|
84
|
-
if (e.type === "event_msg" && p?.type === "item_completed" && p.turn_id === turnId && p.item?.type === "Plan" && typeof p.item.text === "string") plan = p.item.text.trim() || plan;
|
|
85
|
-
}
|
|
86
|
-
return plan;
|
|
87
|
-
}
|
|
88
|
-
var CLAUDE_DIR_MAX = 200;
|
|
89
|
-
function claudeTranscriptPath(cwd, id, env = process.env) {
|
|
90
|
-
const dir = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
91
|
-
if (dir.length > CLAUDE_DIR_MAX) return void 0;
|
|
92
|
-
return join(env.CLAUDE_CONFIG_DIR || join(env.HOME || homedir(), ".claude"), "projects", dir, `${id}.jsonl`);
|
|
93
|
-
}
|
|
94
|
-
function claudePlan(path) {
|
|
95
|
-
let plan;
|
|
96
|
-
for (const line of tailLines(path)) {
|
|
97
|
-
let e;
|
|
98
|
-
try {
|
|
99
|
-
e = JSON.parse(line);
|
|
100
|
-
} catch {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (e.isSidechain === true || !Array.isArray(e.message?.content)) continue;
|
|
104
|
-
for (const b of e.message.content) {
|
|
105
|
-
if (e.type === "assistant" && b?.type === "tool_use" && b.name === "ExitPlanMode" && typeof b.id === "string" && typeof b.input?.plan === "string" && b.input.plan.trim()) plan = { id: b.id, text: b.input.plan.trim() };
|
|
106
|
-
else if (e.type === "user" && b?.type === "tool_result" && plan && b.tool_use_id === plan.id) plan = void 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return plan?.text;
|
|
110
|
-
}
|
|
111
|
-
function claudeCalls(path) {
|
|
112
|
-
const calls = [];
|
|
113
|
-
for (const line of tailLines(path)) {
|
|
114
|
-
let e;
|
|
115
|
-
try {
|
|
116
|
-
e = JSON.parse(line);
|
|
117
|
-
} catch {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (e.isSidechain === true || !Array.isArray(e.message?.content)) continue;
|
|
121
|
-
for (const b of e.message.content) {
|
|
122
|
-
if (e.type === "assistant" && b?.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string") calls.push({ id: b.id, name: b.name, answered: false });
|
|
123
|
-
else if (e.type === "user" && b?.type === "tool_result") {
|
|
124
|
-
const c = calls.find((c2) => c2.id === b.tool_use_id);
|
|
125
|
-
if (c) c.answered = true;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return calls;
|
|
130
|
-
}
|
|
131
|
-
function claudeOpenCall(path, tool) {
|
|
132
|
-
const last = claudeCalls(path).filter((c) => c.name === tool).at(-1);
|
|
133
|
-
return last && !last.answered ? last.id : void 0;
|
|
134
|
-
}
|
|
135
|
-
function claudeToolAnswered(path, id) {
|
|
136
|
-
return claudeCalls(path).some((c) => c.id === id && c.answered);
|
|
137
|
-
}
|
|
138
|
-
var LABEL_MAX = 80;
|
|
139
|
-
function resumeLabel(name, topic) {
|
|
140
|
-
const label = `${name} - ${topic}`.replace(/\s+/g, " ").trim();
|
|
141
|
-
return label.length > LABEL_MAX ? `${label.slice(0, LABEL_MAX - 1)}\u2026` : label;
|
|
142
|
-
}
|
|
143
|
-
function lastTitle(lines) {
|
|
144
|
-
let last;
|
|
145
|
-
for (const line of lines) {
|
|
146
|
-
if (!line.includes('"custom-title"')) continue;
|
|
147
|
-
try {
|
|
148
|
-
const e = JSON.parse(line);
|
|
149
|
-
if (e.type === "custom-title" && typeof e.customTitle === "string") last = { customTitle: e.customTitle, ledge: e.ledge };
|
|
150
|
-
} catch {
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return last;
|
|
154
|
-
}
|
|
155
|
-
var ledgeOwned = (t, name) => t.ledge === true || t.customTitle.startsWith(`${name} - `) || ledgeTitleRest(t.customTitle, name) === "";
|
|
156
|
-
function labelTranscript(path, name, label) {
|
|
157
|
-
try {
|
|
158
|
-
const last = lastTitle(tailLines(path)) ?? (statSync(path).size > TAIL_BYTES ? lastTitle(readFileSync(path, "utf8").split("\n")) : void 0);
|
|
159
|
-
if (last && (last.customTitle === label || !ledgeOwned(last, name))) return false;
|
|
160
|
-
const fd = openSync(path, "r");
|
|
161
|
-
let lead = "";
|
|
162
|
-
try {
|
|
163
|
-
const size = fstatSync(fd).size;
|
|
164
|
-
if (size > 0) {
|
|
165
|
-
const b = Buffer.alloc(1);
|
|
166
|
-
readSync(fd, b, 0, 1, size - 1);
|
|
167
|
-
if (b[0] !== 10) lead = "\n";
|
|
168
|
-
}
|
|
169
|
-
} finally {
|
|
170
|
-
closeSync(fd);
|
|
171
|
-
}
|
|
172
|
-
appendFileSync(path, `${lead}${JSON.stringify({ type: "custom-title", customTitle: label, sessionId: basename(path, ".jsonl"), ledge: true })}
|
|
173
|
-
`);
|
|
174
|
-
return true;
|
|
175
|
-
} catch (e) {
|
|
176
|
-
return e instanceof Error ? e : new Error(String(e));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// src/spawn.ts
|
|
181
|
-
import { basename as basename2, join as join2 } from "path";
|
|
182
|
-
import { randomBytes, randomUUID } from "crypto";
|
|
183
|
-
var AGENT_BIN = { claude: "claude", codex: "codex", cursor: "cursor-agent", omp: "omp" };
|
|
184
|
-
var CODEX_ENV_VAR = "LEDGE_CODEX_SESSION";
|
|
185
|
-
var CODEX_HOOK_VAR = "LEDGE_CODEX_HOOK_SESSION";
|
|
186
|
-
function agentIdArgs(tool, id) {
|
|
187
|
-
if (tool !== "codex") return [];
|
|
188
|
-
const key = sessionKey(id);
|
|
189
|
-
return ["-c", `shell_environment_policy.set.${CODEX_HOOK_VAR}="${id}"`, ...key ? ["-c", `shell_environment_policy.set.${SESSION_KEY_VAR}="${key}"`] : []];
|
|
190
|
-
}
|
|
191
|
-
var CLAUDE_OWN_SESSION = /^(--resume|-r|--continue|-c|--session-id|--fork-session)(=|$)/;
|
|
192
|
-
function claudeSessionId(tool, args) {
|
|
193
|
-
return tool === "claude" && !args.some((a) => CLAUDE_OWN_SESSION.test(a)) ? randomUUID() : void 0;
|
|
194
|
-
}
|
|
195
|
-
function exitFile(id) {
|
|
196
|
-
return join2(paths().dir, `exit-${id}`);
|
|
197
|
-
}
|
|
198
|
-
var PARENT_AGENT_VARS = [
|
|
199
|
-
"CLAUDECODE",
|
|
200
|
-
"CLAUDE_CODE_CHILD_SESSION",
|
|
201
|
-
"CLAUDE_CODE_SESSION_ID",
|
|
202
|
-
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
203
|
-
"CLAUDE_CODE_ENTRYPOINT",
|
|
204
|
-
"CLAUDE_CODE_EXECPATH",
|
|
205
|
-
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
206
|
-
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
207
|
-
"CLAUDE_PID",
|
|
208
|
-
"CLAUDE_EFFORT"
|
|
209
|
-
];
|
|
210
|
-
var PARENT_AGENT = new Set(PARENT_AGENT_VARS);
|
|
211
|
-
function agentEnv(base, extra) {
|
|
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) && k !== "LEDGE_ROLE" && k !== "LEDGE_TEAM") env[k] = v;
|
|
214
|
-
return { ...env, ...extra };
|
|
215
|
-
}
|
|
216
|
-
async function spawnSession(opts) {
|
|
217
|
-
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
218
|
-
const takenIds = new Set(opts.taken.ids);
|
|
219
|
-
let id = opts.id;
|
|
220
|
-
if (id === void 0) do
|
|
221
|
-
id = randomBytes(2).toString("hex");
|
|
222
|
-
while (takenIds.has(id));
|
|
223
|
-
const name = pickName(opts.taken.names, opts.name);
|
|
224
|
-
const project = basename2(opts.cwd) || opts.cwd;
|
|
225
|
-
const tmuxSession = `ledge-${id}`;
|
|
226
|
-
const agentSessionId = opts.command ? void 0 : claudeSessionId(opts.tool, opts.args);
|
|
227
|
-
const agent = opts.command ?? [AGENT_BIN[opts.tool], ...agentIdArgs(opts.tool, id), ...agentSessionId ? ["--session-id", agentSessionId] : [], ...opts.args];
|
|
228
|
-
const env = opts.env ?? process.env;
|
|
229
|
-
const transcriptPath = agentSessionId ? claudeTranscriptPath(opts.cwd, agentSessionId, env) : void 0;
|
|
230
|
-
const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
|
|
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.role ? { LEDGE_ROLE: opts.role.id } : {}, ...opts.team ? { LEDGE_TEAM: opts.team } : {}, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), unset: PARENT_AGENT_VARS, command });
|
|
233
|
-
const at = now();
|
|
234
|
-
return {
|
|
235
|
-
id,
|
|
236
|
-
name,
|
|
237
|
-
tool: opts.tool,
|
|
238
|
-
cwd: opts.cwd,
|
|
239
|
-
project,
|
|
240
|
-
title: `${name} \xB7 ${project}`,
|
|
241
|
-
tmuxSession,
|
|
242
|
-
state: "starting",
|
|
243
|
-
helpers: [],
|
|
244
|
-
startedAt: at,
|
|
245
|
-
lastActivityAt: at,
|
|
246
|
-
source: "launcher",
|
|
247
|
-
...agentSessionId ? { agentSessionId } : {},
|
|
248
|
-
...transcriptPath ? { transcriptPath } : {},
|
|
249
|
-
...opts.role ? { role: opts.role.id, roleLabel: opts.role.label } : {},
|
|
250
|
-
...opts.team ? { team: opts.team } : {},
|
|
251
|
-
...opts.autonomy && opts.autonomy !== "ask" ? { autonomy: opts.autonomy } : {}
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export {
|
|
256
|
-
ledgeTitleRest,
|
|
257
|
-
agentMessages,
|
|
258
|
-
codexPlan,
|
|
259
|
-
claudePlan,
|
|
260
|
-
claudeOpenCall,
|
|
261
|
-
claudeToolAnswered,
|
|
262
|
-
resumeLabel,
|
|
263
|
-
labelTranscript,
|
|
264
|
-
CODEX_ENV_VAR,
|
|
265
|
-
CODEX_HOOK_VAR,
|
|
266
|
-
exitFile,
|
|
267
|
-
PARENT_AGENT_VARS,
|
|
268
|
-
agentEnv,
|
|
269
|
-
spawnSession
|
|
270
|
-
};
|