@paradow/ledge 0.5.1 → 0.5.2
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 +1 -1
- package/dist/cli/{attach-CUKFTN63.js → attach-QE22G2GY.js} +4 -4
- package/dist/cli/{bridge-R6DKTSYF.js → bridge-7RXMJSOP.js} +1 -1
- package/dist/cli/{chunk-Y4ODVTTN.js → chunk-E6ZQOGY4.js} +11 -1
- package/dist/cli/{chunk-5HVWJTTJ.js → chunk-IDGN7JGC.js} +1 -1
- package/dist/cli/{chunk-QHDPDZOJ.js → chunk-ISPFJBJM.js} +3 -3
- package/dist/cli/chunk-JB4FY43J.js +6 -0
- package/dist/cli/{chunk-SMA4Z3N2.js → chunk-NKBJ3LD4.js} +8 -5
- package/dist/cli/chunk-U4RU6V35.js +149 -0
- package/dist/cli/{daemon-RULSS2LK.js → daemon-J4WXI63H.js} +17 -60
- package/dist/cli/{hook-KFSK75QH.js → hook-ARQEXMYY.js} +18 -11
- package/dist/cli/index.js +7 -7
- package/dist/cli/{launch-YKYPT6BU.js → launch-GRWEWGSC.js} +4 -4
- package/dist/cli/{status-I7FHW6ZM.js → status-6OOVUG3V.js} +2 -2
- package/dist/cli/{version-X3IWEFL7.js → version-R7MKSJLO.js} +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-HUNVLIN7.js +0 -6
- package/dist/cli/chunk-U4A4XKDM.js +0 -48
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm i -g @paradow/ledge && ledge setup
|
|
|
12
12
|
|
|
13
13
|
## Update
|
|
14
14
|
|
|
15
|
-
From the Ledge island: the card in the list updates the daemon, then the Windows app. By hand:
|
|
15
|
+
From the Ledge island: the card in the list updates the connected servers' daemons, then the WSL daemon, then the Windows app. By hand, on each machine:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
npm i -g @paradow/ledge@latest && systemctl --user restart ledge-daemon
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-ISPFJBJM.js";
|
|
4
|
+
import "./chunk-U4RU6V35.js";
|
|
5
5
|
import {
|
|
6
6
|
realTmux
|
|
7
7
|
} from "./chunk-XZMLFV6H.js";
|
|
8
8
|
import "./chunk-P2SHPHAN.js";
|
|
9
9
|
import {
|
|
10
10
|
connect
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IDGN7JGC.js";
|
|
12
12
|
import {
|
|
13
13
|
PROTOCOL_PORT
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-E6ZQOGY4.js";
|
|
15
15
|
|
|
16
16
|
// src/cli/attach.ts
|
|
17
17
|
import { spawn } from "child_process";
|
|
@@ -8,6 +8,8 @@ var SessionStateSchema = z.enum(["starting", "working", "waiting_input", "waitin
|
|
|
8
8
|
var PendingSchema = z.object({
|
|
9
9
|
kind: z.enum(["question", "permission", "plan", "diff"]),
|
|
10
10
|
text: z.string(),
|
|
11
|
+
/** What to review in full before deciding, when `text` is only its summary: a plan's markdown. */
|
|
12
|
+
body: z.string().optional(),
|
|
11
13
|
tool: z.string().optional(),
|
|
12
14
|
options: z.array(z.string()).optional(),
|
|
13
15
|
/** Each option's description, by index ('' when it has none); left out when no option has one. */
|
|
@@ -20,7 +22,9 @@ var PendingSchema = z.object({
|
|
|
20
22
|
});
|
|
21
23
|
var ReplySchema = z.object({
|
|
22
24
|
text: z.string(),
|
|
23
|
-
at: z.string().datetime()
|
|
25
|
+
at: z.string().datetime(),
|
|
26
|
+
/** The reply is a plan the agent proposes (Codex Plan mode): nothing in it is done yet. */
|
|
27
|
+
plan: z.boolean().optional()
|
|
24
28
|
});
|
|
25
29
|
var HelperSchema = z.object({
|
|
26
30
|
id: z.string(),
|
|
@@ -56,6 +60,10 @@ var SessionSchema = z.object({
|
|
|
56
60
|
startedAt: z.string().datetime(),
|
|
57
61
|
lastActivityAt: z.string().datetime(),
|
|
58
62
|
lastHookAt: z.string().datetime().optional(),
|
|
63
|
+
/** The agent's own session id when Ledge chose it at launch (Claude Code `--session-id`). */
|
|
64
|
+
agentSessionId: z.string().optional(),
|
|
65
|
+
/** The agent's transcript: the latest one a hook named (it follows /clear and --resume), else the one the launch implies. */
|
|
66
|
+
transcriptPath: z.string().optional(),
|
|
59
67
|
exitCode: z.number().int().nullable().optional(),
|
|
60
68
|
exitedAt: z.string().datetime().optional(),
|
|
61
69
|
lastError: z.string().optional(),
|
|
@@ -195,6 +203,8 @@ var SessionEventSchema = z7.object({
|
|
|
195
203
|
/** Each option's description, by index ('' when it has none); left out when no option has one. */
|
|
196
204
|
optionDetails: z7.array(z7.string()).optional(),
|
|
197
205
|
kind: z7.enum(["question", "permission", "plan", "diff"]).optional(),
|
|
206
|
+
/** PanePrompt: what to review in full before deciding (a plan read in the terminal), when `text` is its summary. */
|
|
207
|
+
body: z7.string().optional(),
|
|
198
208
|
exitCode: z7.number().int().nullable().optional(),
|
|
199
209
|
helperId: z7.string().optional(),
|
|
200
210
|
/** The helper's description, once known (Claude Code: its `.meta.json`, written after SubagentStart). */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
exitFile,
|
|
3
3
|
spawnSession
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U4RU6V35.js";
|
|
5
5
|
import {
|
|
6
6
|
realTmux
|
|
7
7
|
} from "./chunk-XZMLFV6H.js";
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
} from "./chunk-P2SHPHAN.js";
|
|
12
12
|
import {
|
|
13
13
|
connect
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-IDGN7JGC.js";
|
|
15
15
|
import {
|
|
16
16
|
PROTOCOL_PORT
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-E6ZQOGY4.js";
|
|
18
18
|
|
|
19
19
|
// src/cli/launch.ts
|
|
20
20
|
import { spawn } from "child_process";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cleanTopic,
|
|
3
3
|
sessionTitle
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-E6ZQOGY4.js";
|
|
5
5
|
|
|
6
6
|
// src/daemon/state.ts
|
|
7
7
|
var HOOK_PRIORITY_MS = 2e3;
|
|
@@ -18,13 +18,14 @@ function pendingFor(ev) {
|
|
|
18
18
|
return { kind: "question", text: ev.text ?? "", options: ev.options, ...details(ev), source: "hook", ...ev.tool !== "AskUserQuestion" ? { tool: ev.tool } : {} };
|
|
19
19
|
}
|
|
20
20
|
if (ev.name === "PreToolUse" && ev.tool === "ExitPlanMode") {
|
|
21
|
-
|
|
21
|
+
const plan = ev.toolInput?.plan;
|
|
22
|
+
return { kind: "plan", text: ev.text ?? "", ...typeof plan === "string" && plan.trim() ? { body: plan } : {}, source: "hook" };
|
|
22
23
|
}
|
|
23
24
|
if (ev.name === "Notification" && ev.kind === "permission") {
|
|
24
25
|
return { kind: "permission", text: ev.text ?? "", source: "hook" };
|
|
25
26
|
}
|
|
26
27
|
if (ev.name === "PanePrompt") {
|
|
27
|
-
return { kind: ev.kind ?? "permission", text: ev.text ?? "", options: ev.options, ...details(ev), source: "pane" };
|
|
28
|
+
return { kind: ev.kind ?? "permission", text: ev.text ?? "", ...ev.body ? { body: ev.body } : {}, options: ev.options, ...details(ev), source: "pane" };
|
|
28
29
|
}
|
|
29
30
|
return void 0;
|
|
30
31
|
}
|
|
@@ -139,6 +140,7 @@ function reduce(s, ev) {
|
|
|
139
140
|
const lastHookAt = s.lastHookAt;
|
|
140
141
|
if (ev.name === "SubagentStart" || ev.name === "SubagentStop") return ev.helperId ? reduceHelper(s, { ...ev, helperId: ev.helperId }) : s;
|
|
141
142
|
if (ev.helperId && ev.source === "hook" && ev.name !== "SessionEnd") return reduceHelper(s, { ...ev, helperId: ev.helperId });
|
|
143
|
+
if (ev.source === "hook" && ev.transcriptPath && ev.transcriptPath !== s.transcriptPath) s = { ...s, transcriptPath: ev.transcriptPath };
|
|
142
144
|
switch (ev.name) {
|
|
143
145
|
case "SessionEnd":
|
|
144
146
|
return { ...s, state: "exited", pending: void 0, exitCode: ev.exitCode ?? null, exitedAt: s.exitedAt ?? at, lastActivityAt: at };
|
|
@@ -167,7 +169,7 @@ function reduce(s, ev) {
|
|
|
167
169
|
return { ...s, state: "working", pending: void 0, lastError: void 0, lastHookAt: at, lastActivityAt: at };
|
|
168
170
|
}
|
|
169
171
|
case "Stop": {
|
|
170
|
-
const lastReply = ev.text ? { text: ev.text, at } : s.lastReply;
|
|
172
|
+
const lastReply = ev.text ? { text: ev.text, at, ...ev.kind === "plan" ? { plan: true } : {} } : s.lastReply;
|
|
171
173
|
if (s.pending?.helperId) return underHelper(s, "idle", at, { lastReply });
|
|
172
174
|
return { ...s, state: "idle", pending: void 0, lastReply, lastHookAt: at, lastActivityAt: at };
|
|
173
175
|
}
|
|
@@ -187,7 +189,8 @@ function reduce(s, ev) {
|
|
|
187
189
|
case "PanePrompt": {
|
|
188
190
|
if (s.pending?.source === "hook") return s;
|
|
189
191
|
if (hooksReportPrompts(s) && lastHookAt && Date.parse(at) - Date.parse(lastHookAt) < HOOK_PRIORITY_MS) return s;
|
|
190
|
-
|
|
192
|
+
let pending = mergePending(s.pending, pendingFor(ev));
|
|
193
|
+
if (pending.kind === "plan" && !pending.body && s.tool === "codex" && s.lastReply) pending = { ...pending, body: s.lastReply.text };
|
|
191
194
|
if (s.pending && samePending(s.pending, pending)) return s;
|
|
192
195
|
const state = pending.kind === "question" ? "waiting_input" : "waiting_permission";
|
|
193
196
|
return { ...s, state, pending, lastActivityAt: at };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
paths
|
|
3
|
+
} from "./chunk-P2SHPHAN.js";
|
|
4
|
+
import {
|
|
5
|
+
pickName
|
|
6
|
+
} from "./chunk-E6ZQOGY4.js";
|
|
7
|
+
|
|
8
|
+
// src/daemon/transcript.ts
|
|
9
|
+
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
var TAIL_BYTES = 512 * 1024;
|
|
13
|
+
function tailLines(path, bytes = TAIL_BYTES) {
|
|
14
|
+
let fd;
|
|
15
|
+
try {
|
|
16
|
+
fd = openSync(path, "r");
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const size = fstatSync(fd).size;
|
|
22
|
+
const start = Math.max(0, size - bytes);
|
|
23
|
+
const buf = Buffer.alloc(size - start);
|
|
24
|
+
readSync(fd, buf, 0, buf.length, start);
|
|
25
|
+
const text = buf.toString("utf8");
|
|
26
|
+
const lines = text.split("\n");
|
|
27
|
+
if (start > 0) lines.shift();
|
|
28
|
+
if (!text.endsWith("\n")) lines.pop();
|
|
29
|
+
return lines.filter((l) => l.trim());
|
|
30
|
+
} finally {
|
|
31
|
+
closeSync(fd);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
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");
|
|
35
|
+
function agentMessages(path, opts = {}) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const line of tailLines(path, opts.bytes ?? TAIL_BYTES)) {
|
|
38
|
+
let e;
|
|
39
|
+
try {
|
|
40
|
+
e = JSON.parse(line);
|
|
41
|
+
} catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const at = typeof e.timestamp === "string" ? e.timestamp : void 0;
|
|
45
|
+
if (!at) continue;
|
|
46
|
+
if (e.type === "assistant" && (opts.sidechain || e.isSidechain !== true)) {
|
|
47
|
+
const text = texts(e.message?.content, "text");
|
|
48
|
+
if (text && typeof e.uuid === "string") out.push({ id: e.uuid, at, text });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (e.type === "event_msg") {
|
|
52
|
+
const p = e.payload;
|
|
53
|
+
const item = p?.type === "item_completed" ? p.item : void 0;
|
|
54
|
+
if (item?.type !== "AgentMessage" || typeof item.id !== "string") continue;
|
|
55
|
+
const text = texts(item.content, "Text");
|
|
56
|
+
if (text) out.push({ id: item.id, at, text });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function codexPlan(path, turnId) {
|
|
62
|
+
let plan;
|
|
63
|
+
for (const line of tailLines(path)) {
|
|
64
|
+
let e;
|
|
65
|
+
try {
|
|
66
|
+
e = JSON.parse(line);
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const p = e.payload;
|
|
71
|
+
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;
|
|
72
|
+
}
|
|
73
|
+
return plan;
|
|
74
|
+
}
|
|
75
|
+
var CLAUDE_DIR_MAX = 200;
|
|
76
|
+
function claudeTranscriptPath(cwd, id, env = process.env) {
|
|
77
|
+
const dir = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
78
|
+
if (dir.length > CLAUDE_DIR_MAX) return void 0;
|
|
79
|
+
return join(env.CLAUDE_CONFIG_DIR || join(env.HOME || homedir(), ".claude"), "projects", dir, `${id}.jsonl`);
|
|
80
|
+
}
|
|
81
|
+
function claudePlan(path) {
|
|
82
|
+
let plan;
|
|
83
|
+
for (const line of tailLines(path)) {
|
|
84
|
+
let e;
|
|
85
|
+
try {
|
|
86
|
+
e = JSON.parse(line);
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (e.isSidechain === true || !Array.isArray(e.message?.content)) continue;
|
|
91
|
+
for (const b of e.message.content) {
|
|
92
|
+
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() };
|
|
93
|
+
else if (e.type === "user" && b?.type === "tool_result" && plan && b.tool_use_id === plan.id) plan = void 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return plan?.text;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/spawn.ts
|
|
100
|
+
import { basename, join as join2 } from "path";
|
|
101
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
102
|
+
var AGENT_BIN = { claude: "claude", codex: "codex", cursor: "cursor-agent" };
|
|
103
|
+
var CODEX_ENV_VAR = "LEDGE_CODEX_SESSION";
|
|
104
|
+
var CODEX_HOOK_VAR = "LEDGE_CODEX_HOOK_SESSION";
|
|
105
|
+
function agentIdArgs(tool, id) {
|
|
106
|
+
return tool === "codex" ? ["-c", `shell_environment_policy.set.${CODEX_HOOK_VAR}="${id}"`] : [];
|
|
107
|
+
}
|
|
108
|
+
var CLAUDE_OWN_SESSION = /^(--resume|-r|--continue|-c|--session-id|--fork-session)(=|$)/;
|
|
109
|
+
function claudeSessionId(tool, args) {
|
|
110
|
+
return tool === "claude" && !args.some((a) => CLAUDE_OWN_SESSION.test(a)) ? randomUUID() : void 0;
|
|
111
|
+
}
|
|
112
|
+
function exitFile(id) {
|
|
113
|
+
return join2(paths().dir, `exit-${id}`);
|
|
114
|
+
}
|
|
115
|
+
function agentEnv(base, extra) {
|
|
116
|
+
const env = {};
|
|
117
|
+
for (const [k, v] of Object.entries(base)) if (v !== void 0 && k !== "TMUX" && !k.startsWith("TMUX_")) env[k] = v;
|
|
118
|
+
return { ...env, ...extra };
|
|
119
|
+
}
|
|
120
|
+
async function spawnSession(opts) {
|
|
121
|
+
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
122
|
+
const takenIds = new Set(opts.taken.ids);
|
|
123
|
+
let id;
|
|
124
|
+
do
|
|
125
|
+
id = randomBytes(2).toString("hex");
|
|
126
|
+
while (takenIds.has(id));
|
|
127
|
+
const name = pickName(opts.taken.names, opts.name);
|
|
128
|
+
const project = basename(opts.cwd) || opts.cwd;
|
|
129
|
+
const tmuxSession = `ledge-${id}`;
|
|
130
|
+
const agentSessionId = opts.command ? void 0 : claudeSessionId(opts.tool, opts.args);
|
|
131
|
+
const agent = opts.command ?? [AGENT_BIN[opts.tool], ...agentIdArgs(opts.tool, id), ...agentSessionId ? ["--session-id", agentSessionId] : [], ...opts.args];
|
|
132
|
+
const env = opts.env ?? process.env;
|
|
133
|
+
const transcriptPath = agentSessionId ? claudeTranscriptPath(opts.cwd, agentSessionId, env) : void 0;
|
|
134
|
+
const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
|
|
135
|
+
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 } : {} }), command });
|
|
136
|
+
const at = now();
|
|
137
|
+
return { id, name, tool: opts.tool, cwd: opts.cwd, project, title: `${name} \xB7 ${project}`, tmuxSession, state: "starting", helpers: [], startedAt: at, lastActivityAt: at, source: "launcher", ...agentSessionId ? { agentSessionId } : {}, ...transcriptPath ? { transcriptPath } : {} };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
agentMessages,
|
|
142
|
+
codexPlan,
|
|
143
|
+
claudePlan,
|
|
144
|
+
CODEX_ENV_VAR,
|
|
145
|
+
CODEX_HOOK_VAR,
|
|
146
|
+
exitFile,
|
|
147
|
+
agentEnv,
|
|
148
|
+
spawnSession
|
|
149
|
+
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
QUESTION_TOOLS,
|
|
3
3
|
reduce
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NKBJ3LD4.js";
|
|
5
5
|
import {
|
|
6
|
+
agentMessages,
|
|
7
|
+
claudePlan,
|
|
6
8
|
spawnSession
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-U4RU6V35.js";
|
|
8
10
|
import {
|
|
9
11
|
LEDGE_VERSION
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JB4FY43J.js";
|
|
11
13
|
import {
|
|
12
14
|
SERVICE,
|
|
13
15
|
inDaemonService,
|
|
@@ -39,7 +41,7 @@ import {
|
|
|
39
41
|
parseRelayBlock,
|
|
40
42
|
parseServerMessage,
|
|
41
43
|
pickName
|
|
42
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-E6ZQOGY4.js";
|
|
43
45
|
|
|
44
46
|
// src/daemon/index.ts
|
|
45
47
|
import { writeFile as writeFile5, rm as rm5 } from "fs/promises";
|
|
@@ -236,58 +238,6 @@ function filesOf(tool, input) {
|
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
// src/daemon/transcript.ts
|
|
240
|
-
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
241
|
-
var TAIL_BYTES = 512 * 1024;
|
|
242
|
-
function tailLines(path, bytes = TAIL_BYTES) {
|
|
243
|
-
let fd;
|
|
244
|
-
try {
|
|
245
|
-
fd = openSync(path, "r");
|
|
246
|
-
} catch {
|
|
247
|
-
return [];
|
|
248
|
-
}
|
|
249
|
-
try {
|
|
250
|
-
const size = fstatSync(fd).size;
|
|
251
|
-
const start = Math.max(0, size - bytes);
|
|
252
|
-
const buf = Buffer.alloc(size - start);
|
|
253
|
-
readSync(fd, buf, 0, buf.length, start);
|
|
254
|
-
const text = buf.toString("utf8");
|
|
255
|
-
const lines = text.split("\n");
|
|
256
|
-
if (start > 0) lines.shift();
|
|
257
|
-
if (!text.endsWith("\n")) lines.pop();
|
|
258
|
-
return lines.filter((l) => l.trim());
|
|
259
|
-
} finally {
|
|
260
|
-
closeSync(fd);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
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");
|
|
264
|
-
function agentMessages(path, opts = {}) {
|
|
265
|
-
const out = [];
|
|
266
|
-
for (const line of tailLines(path, opts.bytes ?? TAIL_BYTES)) {
|
|
267
|
-
let e;
|
|
268
|
-
try {
|
|
269
|
-
e = JSON.parse(line);
|
|
270
|
-
} catch {
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
const at = typeof e.timestamp === "string" ? e.timestamp : void 0;
|
|
274
|
-
if (!at) continue;
|
|
275
|
-
if (e.type === "assistant" && (opts.sidechain || e.isSidechain !== true)) {
|
|
276
|
-
const text = texts(e.message?.content, "text");
|
|
277
|
-
if (text && typeof e.uuid === "string") out.push({ id: e.uuid, at, text });
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (e.type === "event_msg") {
|
|
281
|
-
const p = e.payload;
|
|
282
|
-
const item = p?.type === "item_completed" ? p.item : void 0;
|
|
283
|
-
if (item?.type !== "AgentMessage" || typeof item.id !== "string") continue;
|
|
284
|
-
const text = texts(item.content, "Text");
|
|
285
|
-
if (text) out.push({ id: item.id, at, text });
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return out;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
241
|
// src/daemon/history.ts
|
|
292
242
|
var MAX_ITEMS = 400;
|
|
293
243
|
var SYNC_MS = 1e3;
|
|
@@ -953,6 +903,7 @@ function parseQuestion(lines) {
|
|
|
953
903
|
var PLAN_ASK = /^Ready to build\?$/;
|
|
954
904
|
var PLAN_OPTION = /^(?:→\s*)?\d+\.\s+(.+?)\s+\([^)]*\)$/;
|
|
955
905
|
var REVISE = /Describe how to revise the plan\b/;
|
|
906
|
+
var tidy = (lines) => lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
956
907
|
function parsePlan(lines) {
|
|
957
908
|
const box = squareBox(lines);
|
|
958
909
|
if (!box) return null;
|
|
@@ -962,7 +913,9 @@ function parsePlan(lines) {
|
|
|
962
913
|
if (!options.length) return null;
|
|
963
914
|
const head = box.findIndex((l) => l === "Suggested Plan");
|
|
964
915
|
const title = head < 0 ? "" : box.slice(head + 1).find((l) => l !== "") ?? "";
|
|
965
|
-
|
|
916
|
+
const end = head < 0 ? -1 : box.findIndex((l, i) => i > head && (/^Saved to /.test(l) || /^─{5,}/.test(l)));
|
|
917
|
+
const body = head < 0 ? "" : tidy(box.slice(head + 1, end < 0 ? ask : end));
|
|
918
|
+
return { title: title || "Ready to build?", options, ...body ? { body } : {} };
|
|
966
919
|
}
|
|
967
920
|
var TELL_INSTEAD = /Tell the agent what to do instead\b/;
|
|
968
921
|
var INPUT = /^\s{1,3}→ /;
|
|
@@ -983,7 +936,7 @@ function detectPrompt3(pane) {
|
|
|
983
936
|
const q = parseQuestion(lines);
|
|
984
937
|
if (q) return { kind: "question", text: q.question, options: q.options };
|
|
985
938
|
const plan = parsePlan(lines);
|
|
986
|
-
if (plan) return { kind: "plan", text: plan.title, options: plan.options };
|
|
939
|
+
if (plan) return { kind: "plan", text: plan.title, options: plan.options, ...plan.body ? { body: plan.body } : {} };
|
|
987
940
|
const d = parseDialog3(lines);
|
|
988
941
|
return d ? { kind: "permission", text: dialogText(d) } : null;
|
|
989
942
|
}
|
|
@@ -1224,7 +1177,7 @@ function startPoller(opts) {
|
|
|
1224
1177
|
const prev = hashes.get(s.id);
|
|
1225
1178
|
if (prev === h) {
|
|
1226
1179
|
const m2 = matches.get(s.id);
|
|
1227
|
-
if (m2 && !store.get(s.id)?.pending) store.apply(s.id, { name: "PanePrompt", at: now(), source: "pane", kind: m2.kind, text: m2.text, options: m2.options, ...m2.optionDetails ? { optionDetails: m2.optionDetails } : {} });
|
|
1180
|
+
if (m2 && !store.get(s.id)?.pending) store.apply(s.id, { name: "PanePrompt", at: now(), source: "pane", kind: m2.kind, text: m2.text, options: m2.options, ...m2.optionDetails ? { optionDetails: m2.optionDetails } : {}, ...withBody(s, m2) });
|
|
1228
1181
|
rested(s.id, t);
|
|
1229
1182
|
return;
|
|
1230
1183
|
}
|
|
@@ -1258,7 +1211,11 @@ function startPoller(opts) {
|
|
|
1258
1211
|
return;
|
|
1259
1212
|
}
|
|
1260
1213
|
matches.set(s.id, m);
|
|
1261
|
-
store.apply(s.id, { name: "PanePrompt", at: now(), source: "pane", kind: m.kind, text: m.text, options: m.options, ...m.optionDetails ? { optionDetails: m.optionDetails } : {} });
|
|
1214
|
+
store.apply(s.id, { name: "PanePrompt", at: now(), source: "pane", kind: m.kind, text: m.text, options: m.options, ...m.optionDetails ? { optionDetails: m.optionDetails } : {}, ...withBody(s, m) });
|
|
1215
|
+
}
|
|
1216
|
+
function withBody(s, m) {
|
|
1217
|
+
const body = m.body ?? (m.kind === "plan" && s.tool === "claude" && s.transcriptPath ? claudePlan(s.transcriptPath) : void 0);
|
|
1218
|
+
return body ? { body } : {};
|
|
1262
1219
|
}
|
|
1263
1220
|
function rested(id, t) {
|
|
1264
1221
|
const since = restSince.get(id);
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
QUESTION_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NKBJ3LD4.js";
|
|
4
4
|
import {
|
|
5
5
|
CODEX_ENV_VAR,
|
|
6
|
-
CODEX_HOOK_VAR
|
|
7
|
-
|
|
6
|
+
CODEX_HOOK_VAR,
|
|
7
|
+
codexPlan
|
|
8
|
+
} from "./chunk-U4RU6V35.js";
|
|
8
9
|
import {
|
|
9
10
|
log
|
|
10
11
|
} from "./chunk-P2SHPHAN.js";
|
|
11
12
|
import {
|
|
12
13
|
connect
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-IDGN7JGC.js";
|
|
15
|
+
import "./chunk-E6ZQOGY4.js";
|
|
15
16
|
|
|
16
17
|
// src/cli/hook.ts
|
|
17
18
|
import { readFileSync, openSync, fstatSync, readSync, closeSync } from "fs";
|
|
@@ -36,16 +37,18 @@ function describeTool(tool, input) {
|
|
|
36
37
|
}
|
|
37
38
|
var REPLY_MAX = 24e3;
|
|
38
39
|
function lastReply(p) {
|
|
39
|
-
|
|
40
|
+
return cap(str(p.last_assistant_message)?.trim());
|
|
41
|
+
}
|
|
42
|
+
function cap(text) {
|
|
40
43
|
if (!text) return void 0;
|
|
41
44
|
return text.length > REPLY_MAX ? `${text.slice(0, REPLY_MAX)}
|
|
42
45
|
|
|
43
46
|
[\u2026]` : text;
|
|
44
47
|
}
|
|
45
48
|
var INPUT_MAX = 8e3;
|
|
46
|
-
function trimInput(input) {
|
|
49
|
+
function trimInput(input, max = INPUT_MAX) {
|
|
47
50
|
if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
|
|
48
|
-
const trim = (v) => typeof v === "string" ? v.slice(0,
|
|
51
|
+
const trim = (v) => typeof v === "string" ? v.slice(0, max) : Array.isArray(v) ? v.map(trim) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, trim(x)])) : v;
|
|
49
52
|
return trim(input);
|
|
50
53
|
}
|
|
51
54
|
function toolOutput(response) {
|
|
@@ -115,7 +118,7 @@ function mapEvent(eventName, p, at, launched) {
|
|
|
115
118
|
const agentId = str(p.agent_id) || void 0;
|
|
116
119
|
const base = { at, source: "hook", ...agentId ? { helperId: agentId } : {} };
|
|
117
120
|
const toolUseId = str(p.tool_use_id) || void 0;
|
|
118
|
-
const toolInput = trimInput(p.tool_input);
|
|
121
|
+
const toolInput = trimInput(p.tool_input, tool === "ExitPlanMode" ? REPLY_MAX : INPUT_MAX);
|
|
119
122
|
const call = { ...toolUseId ? { toolUseId } : {}, ...toolInput ? { toolInput } : {} };
|
|
120
123
|
const transcript = str(p.transcript_path) ? { transcriptPath: str(p.transcript_path) } : {};
|
|
121
124
|
const duration = typeof p.duration_ms === "number" && p.duration_ms >= 0 ? { durationMs: p.duration_ms } : {};
|
|
@@ -151,8 +154,12 @@ function mapEvent(eventName, p, at, launched) {
|
|
|
151
154
|
case "UserPromptSubmit":
|
|
152
155
|
return { name: "UserPromptSubmit", ...base, ...promptText(p), ...transcript };
|
|
153
156
|
case "Stop": {
|
|
154
|
-
const
|
|
155
|
-
|
|
157
|
+
const turnId = str(p.turn_id);
|
|
158
|
+
const path = str(p.transcript_path);
|
|
159
|
+
const said = lastReply(p);
|
|
160
|
+
const plan = said ? void 0 : turnId && path ? cap(codexPlan(path, turnId)) : void 0;
|
|
161
|
+
const text = said ?? plan;
|
|
162
|
+
return { name: "Stop", ...base, ...plan ? { kind: "plan" } : {}, ...text ? { text } : {}, ...transcript };
|
|
156
163
|
}
|
|
157
164
|
case "SessionEnd":
|
|
158
165
|
if (launched) return null;
|
package/dist/cli/index.js
CHANGED
|
@@ -16,7 +16,7 @@ async function hookMain(args) {
|
|
|
16
16
|
try {
|
|
17
17
|
const chunks = [];
|
|
18
18
|
for await (const c of process.stdin) chunks.push(c);
|
|
19
|
-
const { runHook } = await import("./hook-
|
|
19
|
+
const { runHook } = await import("./hook-ARQEXMYY.js");
|
|
20
20
|
await runHook(event, Buffer.concat(chunks).toString("utf8"), { tool });
|
|
21
21
|
} catch {
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@ async function main(argv) {
|
|
|
26
26
|
const [cmd, ...rest] = argv;
|
|
27
27
|
switch (cmd) {
|
|
28
28
|
case "daemon": {
|
|
29
|
-
const { startDaemon } = await import("./daemon-
|
|
29
|
+
const { startDaemon } = await import("./daemon-J4WXI63H.js");
|
|
30
30
|
await startDaemon();
|
|
31
31
|
await new Promise(() => {
|
|
32
32
|
});
|
|
@@ -34,7 +34,7 @@ async function main(argv) {
|
|
|
34
34
|
}
|
|
35
35
|
case "--version":
|
|
36
36
|
case "version": {
|
|
37
|
-
const { LEDGE_VERSION } = await import("./version-
|
|
37
|
+
const { LEDGE_VERSION } = await import("./version-R7MKSJLO.js");
|
|
38
38
|
console.log(LEDGE_VERSION);
|
|
39
39
|
return 0;
|
|
40
40
|
}
|
|
@@ -50,23 +50,23 @@ async function main(argv) {
|
|
|
50
50
|
return r.ok ? 0 : 1;
|
|
51
51
|
}
|
|
52
52
|
case "status": {
|
|
53
|
-
const { printStatus } = await import("./status-
|
|
53
|
+
const { printStatus } = await import("./status-6OOVUG3V.js");
|
|
54
54
|
console.log(await printStatus());
|
|
55
55
|
return 0;
|
|
56
56
|
}
|
|
57
57
|
case "bridge": {
|
|
58
|
-
const { bridge } = await import("./bridge-
|
|
58
|
+
const { bridge } = await import("./bridge-7RXMJSOP.js");
|
|
59
59
|
await bridge();
|
|
60
60
|
return 0;
|
|
61
61
|
}
|
|
62
62
|
case "claude":
|
|
63
63
|
case "codex":
|
|
64
64
|
case "cursor": {
|
|
65
|
-
const { launch } = await import("./launch-
|
|
65
|
+
const { launch } = await import("./launch-GRWEWGSC.js");
|
|
66
66
|
return launch(cmd, rest);
|
|
67
67
|
}
|
|
68
68
|
case "attach": {
|
|
69
|
-
const { attach } = await import("./attach-
|
|
69
|
+
const { attach } = await import("./attach-QE22G2GY.js");
|
|
70
70
|
return attach(rest[0] ?? "");
|
|
71
71
|
}
|
|
72
72
|
default:
|
|
@@ -5,15 +5,15 @@ import {
|
|
|
5
5
|
ensureDaemon,
|
|
6
6
|
launch,
|
|
7
7
|
parseLaunchArgs
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ISPFJBJM.js";
|
|
9
9
|
import {
|
|
10
10
|
agentEnv,
|
|
11
11
|
exitFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-U4RU6V35.js";
|
|
13
13
|
import "./chunk-XZMLFV6H.js";
|
|
14
14
|
import "./chunk-P2SHPHAN.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-IDGN7JGC.js";
|
|
16
|
+
import "./chunk-E6ZQOGY4.js";
|
|
17
17
|
export {
|
|
18
18
|
DaemonError,
|
|
19
19
|
agentEnv,
|
package/package.json
CHANGED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
paths
|
|
3
|
-
} from "./chunk-P2SHPHAN.js";
|
|
4
|
-
import {
|
|
5
|
-
pickName
|
|
6
|
-
} from "./chunk-Y4ODVTTN.js";
|
|
7
|
-
|
|
8
|
-
// src/spawn.ts
|
|
9
|
-
import { basename, join } from "path";
|
|
10
|
-
import { randomBytes } from "crypto";
|
|
11
|
-
var AGENT_BIN = { claude: "claude", codex: "codex", cursor: "cursor-agent" };
|
|
12
|
-
var CODEX_ENV_VAR = "LEDGE_CODEX_SESSION";
|
|
13
|
-
var CODEX_HOOK_VAR = "LEDGE_CODEX_HOOK_SESSION";
|
|
14
|
-
function agentIdArgs(tool, id) {
|
|
15
|
-
return tool === "codex" ? ["-c", `shell_environment_policy.set.${CODEX_HOOK_VAR}="${id}"`] : [];
|
|
16
|
-
}
|
|
17
|
-
function exitFile(id) {
|
|
18
|
-
return join(paths().dir, `exit-${id}`);
|
|
19
|
-
}
|
|
20
|
-
function agentEnv(base, extra) {
|
|
21
|
-
const env = {};
|
|
22
|
-
for (const [k, v] of Object.entries(base)) if (v !== void 0 && k !== "TMUX" && !k.startsWith("TMUX_")) env[k] = v;
|
|
23
|
-
return { ...env, ...extra };
|
|
24
|
-
}
|
|
25
|
-
async function spawnSession(opts) {
|
|
26
|
-
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
27
|
-
const takenIds = new Set(opts.taken.ids);
|
|
28
|
-
let id;
|
|
29
|
-
do
|
|
30
|
-
id = randomBytes(2).toString("hex");
|
|
31
|
-
while (takenIds.has(id));
|
|
32
|
-
const name = pickName(opts.taken.names, opts.name);
|
|
33
|
-
const project = basename(opts.cwd) || opts.cwd;
|
|
34
|
-
const tmuxSession = `ledge-${id}`;
|
|
35
|
-
const agent = opts.command ?? [AGENT_BIN[opts.tool], ...agentIdArgs(opts.tool, id), ...opts.args];
|
|
36
|
-
const command = opts.recordExit ? ["sh", "-c", '"$@"; echo $? > "$0"', exitFile(id), ...agent] : agent;
|
|
37
|
-
await opts.tmux.newSession({ name: tmuxSession, cwd: opts.cwd, env: agentEnv(opts.env ?? process.env, { LEDGE_SESSION: id, LEDGE_NAME: name, ...opts.tool === "codex" ? { [CODEX_ENV_VAR]: id } : {} }), command });
|
|
38
|
-
const at = now();
|
|
39
|
-
return { id, name, tool: opts.tool, cwd: opts.cwd, project, title: `${name} \xB7 ${project}`, tmuxSession, state: "starting", helpers: [], startedAt: at, lastActivityAt: at, source: "launcher" };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export {
|
|
43
|
-
CODEX_ENV_VAR,
|
|
44
|
-
CODEX_HOOK_VAR,
|
|
45
|
-
exitFile,
|
|
46
|
-
agentEnv,
|
|
47
|
-
spawnSession
|
|
48
|
-
};
|