@paradow/ledge 0.6.0 → 0.7.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/{attach-FDMNVXYR.js → attach-P6CWCJ2R.js} +4 -4
- package/dist/cli/{bridge-AFAK3FU5.js → bridge-ZYR3IBVM.js} +1 -1
- package/dist/cli/{chunk-HYB7ZZN4.js → chunk-3TXM4DM7.js} +3 -3
- package/dist/cli/{chunk-AEV7ODMZ.js → chunk-4BBKLTDB.js} +28 -4
- package/dist/cli/{chunk-VK2IBMW7.js → chunk-GZFSUQZJ.js} +21 -2
- package/dist/cli/{chunk-QJG7M6BJ.js → chunk-HR6VWBLT.js} +3 -3
- package/dist/cli/chunk-KD7LKXIF.js +6 -0
- package/dist/cli/{chunk-Z4YH2DLP.js → chunk-MK7NEXY7.js} +1 -1
- package/dist/cli/{daemon-SKI3UKOJ.js → daemon-GREPJUOS.js} +230 -21
- package/dist/cli/{hook-CT5OKGHQ.js → hook-ZVXQ3MGS.js} +10 -8
- package/dist/cli/index.js +7 -7
- package/dist/cli/{launch-LVMFX3KF.js → launch-PIFVOOJV.js} +4 -4
- package/dist/cli/{status-TACDR6DZ.js → status-RVD4U2XH.js} +2 -2
- package/dist/cli/{version-3CHJ23IV.js → version-X3LYAGKA.js} +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-FFVZIXKG.js +0 -6
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-HR6VWBLT.js";
|
|
4
|
+
import "./chunk-3TXM4DM7.js";
|
|
5
5
|
import {
|
|
6
6
|
realTmux
|
|
7
7
|
} from "./chunk-3TNPEAMR.js";
|
|
8
8
|
import "./chunk-L6IGH4LF.js";
|
|
9
9
|
import {
|
|
10
10
|
connect
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MK7NEXY7.js";
|
|
12
12
|
import {
|
|
13
13
|
PROTOCOL_PORT
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-4BBKLTDB.js";
|
|
15
15
|
|
|
16
16
|
// src/cli/attach.ts
|
|
17
17
|
import { spawn } from "child_process";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-L6IGH4LF.js";
|
|
4
4
|
import {
|
|
5
5
|
pickName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-4BBKLTDB.js";
|
|
7
7
|
|
|
8
8
|
// src/daemon/transcript.ts
|
|
9
9
|
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
@@ -120,8 +120,8 @@ function agentEnv(base, extra) {
|
|
|
120
120
|
async function spawnSession(opts) {
|
|
121
121
|
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
122
122
|
const takenIds = new Set(opts.taken.ids);
|
|
123
|
-
let id;
|
|
124
|
-
do
|
|
123
|
+
let id = opts.id;
|
|
124
|
+
if (id === void 0) do
|
|
125
125
|
id = randomBytes(2).toString("hex");
|
|
126
126
|
while (takenIds.has(id));
|
|
127
127
|
const name = pickName(opts.taken.names, opts.name);
|
|
@@ -59,6 +59,8 @@ var SessionSchema = z.object({
|
|
|
59
59
|
pending: PendingSchema.optional(),
|
|
60
60
|
helpers: z.array(HelperSchema).default([]),
|
|
61
61
|
lastReply: ReplySchema.optional(),
|
|
62
|
+
/** The last message this session sent to another agent: its tool_use_id, the address, the peer's Ledge session id, a one-line summary. */
|
|
63
|
+
lastMessage: z.object({ id: z.string(), to: z.string(), peer: z.string().optional(), summary: z.string(), at: z.string().datetime() }).optional(),
|
|
62
64
|
startedAt: z.string().datetime(),
|
|
63
65
|
lastActivityAt: z.string().datetime(),
|
|
64
66
|
lastHookAt: z.string().datetime().optional(),
|
|
@@ -69,8 +71,15 @@ var SessionSchema = z.object({
|
|
|
69
71
|
exitCode: z.number().int().nullable().optional(),
|
|
70
72
|
exitedAt: z.string().datetime().optional(),
|
|
71
73
|
lastError: z.string().optional(),
|
|
72
|
-
source: z.enum(["launcher", "hook_only"])
|
|
74
|
+
source: z.enum(["launcher", "hook_only"]),
|
|
75
|
+
/** When the session was put in the archive (session management): set, it leaves the live list and waits to be restored, resumed or deleted. */
|
|
76
|
+
archivedAt: z.string().datetime().optional(),
|
|
77
|
+
/** When the session was last taken out of the archive: the poller's retirement clock restarts from it, not from `exitedAt`. */
|
|
78
|
+
restoredAt: z.string().datetime().optional()
|
|
73
79
|
});
|
|
80
|
+
function isArchived(s) {
|
|
81
|
+
return s.archivedAt !== void 0;
|
|
82
|
+
}
|
|
74
83
|
|
|
75
84
|
// ../protocol/src/chat.ts
|
|
76
85
|
import { z as z2 } from "zod";
|
|
@@ -324,7 +333,11 @@ var ThreadItemSchema = z11.discriminatedUnion("kind", [
|
|
|
324
333
|
/** `unknown`: answered in the terminal, the daemon only saw the prompt go away; `always`: allowed and not asked again. */
|
|
325
334
|
z11.object({ kind: z11.literal("permission"), ...base, tool: z11.string(), text: z11.string(), decision: z11.enum(["accept", "deny", "always", "unknown"]).optional() }),
|
|
326
335
|
z11.object({ kind: z11.literal("reply"), ...base, text: z11.string() }),
|
|
327
|
-
z11.object({ kind: z11.literal("helper"), ...base, task: z11.string(), agentType: z11.string().optional(), state: z11.enum(["working", "exited"]) })
|
|
336
|
+
z11.object({ kind: z11.literal("helper"), ...base, task: z11.string(), agentType: z11.string().optional(), state: z11.enum(["working", "exited"]) }),
|
|
337
|
+
/** A message this session sent to another agent; `id` is the tool_use_id, `to` the address it used, `peer` the Ledge session id, `received` the paired item's id in the peer's thread. */
|
|
338
|
+
z11.object({ kind: z11.literal("sent"), ...base, to: z11.string(), peer: z11.string().optional(), summary: z11.string().optional(), text: z11.string(), status: z11.enum(["sending", "delivered", "read", "failed"]), received: z11.string().optional() }),
|
|
339
|
+
/** A message another agent sent to this session; `from` is its from-name (else its address), `sent` the paired item's id in the peer's thread. */
|
|
340
|
+
z11.object({ kind: z11.literal("received"), ...base, from: z11.string(), peer: z11.string().optional(), text: z11.string(), sent: z11.string().optional() })
|
|
328
341
|
]);
|
|
329
342
|
|
|
330
343
|
// ../protocol/src/decisions.ts
|
|
@@ -388,11 +401,13 @@ var SessionEventSchema = z13.object({
|
|
|
388
401
|
/** The session's transcript, or the helper's on SubagentStop; the daemon reads its tail for the agent's messages. */
|
|
389
402
|
transcriptPath: z13.string().optional(),
|
|
390
403
|
/** Decided: what was chosen when the daemon knows it. */
|
|
391
|
-
decision: DecisionSchema.optional()
|
|
404
|
+
decision: DecisionSchema.optional(),
|
|
405
|
+
/** The agent's own session id when the hook names it, so the daemon can resume that conversation. */
|
|
406
|
+
agentSessionId: z13.string().optional()
|
|
392
407
|
});
|
|
393
408
|
var RecentSchema = z13.object({ cwd: z13.string().min(1), project: z13.string().min(1), lastAt: z13.string().datetime() });
|
|
394
409
|
var SEMVER = /^\d+\.\d+\.\d+$/;
|
|
395
|
-
var DaemonInfoSchema = z13.object({ version: z13.string(), install: z13.enum(["npm", "git", "unknown"]), systemd: z13.boolean(), manual: z13.string().optional(), routing: z13.boolean().optional() });
|
|
410
|
+
var DaemonInfoSchema = z13.object({ version: z13.string(), install: z13.enum(["npm", "git", "unknown"]), systemd: z13.boolean(), manual: z13.string().optional(), routing: z13.boolean().optional(), archive: z13.boolean().optional() });
|
|
396
411
|
var UpdateFailureSchema = z13.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
|
|
397
412
|
var ServerMessageSchema = z13.discriminatedUnion("type", [
|
|
398
413
|
z13.object({ type: z13.literal("chat.routed"), qid: z13.string(), result: RouteResultSchema }),
|
|
@@ -505,6 +520,14 @@ var ClientMessageSchema = z13.discriminatedUnion("type", [
|
|
|
505
520
|
z13.object({ type: z13.literal("session.interrupt"), id: z13.string() }),
|
|
506
521
|
/** Removes a finished (exited) or read-only (hook_only) session from the list; refused for a live launcher session. */
|
|
507
522
|
z13.object({ type: z13.literal("session.dismiss"), id: z13.string() }),
|
|
523
|
+
/** Puts a session in the archive: it leaves the live list but is kept. */
|
|
524
|
+
z13.object({ type: z13.literal("session.archive"), id: z13.string() }),
|
|
525
|
+
/** Takes an archived session back out of the archive, as it was. */
|
|
526
|
+
z13.object({ type: z13.literal("session.restore"), id: z13.string() }),
|
|
527
|
+
/** Starts the agent again on an archived session's conversation. */
|
|
528
|
+
z13.object({ type: z13.literal("session.resume"), id: z13.string() }),
|
|
529
|
+
/** Deletes a session for good, archived or not. */
|
|
530
|
+
z13.object({ type: z13.literal("session.delete"), id: z13.string() }),
|
|
508
531
|
/** The user names the session's topic from the overlay (500 characters at most, the hook's prompt cap); an empty topic hands back to the automatic sources. */
|
|
509
532
|
z13.object({ type: z13.literal("session.rename"), id: z13.string(), topic: z13.string().max(500) }),
|
|
510
533
|
z13.object({ type: z13.literal("session.thread"), id: z13.string() }),
|
|
@@ -673,6 +696,7 @@ function sessionTitle(s) {
|
|
|
673
696
|
|
|
674
697
|
export {
|
|
675
698
|
SessionSchema,
|
|
699
|
+
isArchived,
|
|
676
700
|
NOTE_NOT_TYPED,
|
|
677
701
|
offers,
|
|
678
702
|
pendingKey,
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cleanTopic,
|
|
3
3
|
sessionTitle
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4BBKLTDB.js";
|
|
5
|
+
|
|
6
|
+
// src/daemon/crossSession.ts
|
|
7
|
+
var FIRST_LINE = /^Another Claude session sent a message:\s*\n/;
|
|
8
|
+
var BLOCK = /<cross-session-message\b([^>]*)>([\s\S]*?)(?:<\/cross-session-message>|(?=\n[ \t]*<cross-session-message\b)|$)/g;
|
|
9
|
+
var ATTR = /([\w-]+)="([^"]*)"/g;
|
|
10
|
+
function crossSession(text) {
|
|
11
|
+
const body = text.trimStart().replace(FIRST_LINE, "");
|
|
12
|
+
if (!body.startsWith("<cross-session-message")) return void 0;
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const m of body.matchAll(BLOCK)) {
|
|
15
|
+
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
16
|
+
const from = attrs.get("from");
|
|
17
|
+
const name = attrs.get("from-name");
|
|
18
|
+
if (from) out.push({ from, ...name ? { name } : {}, text: m[2].trim() });
|
|
19
|
+
}
|
|
20
|
+
return out.length ? out : void 0;
|
|
21
|
+
}
|
|
5
22
|
|
|
6
23
|
// src/daemon/state.ts
|
|
7
24
|
var HOOK_PRIORITY_MS = 2e3;
|
|
@@ -134,6 +151,7 @@ function withTopic(s, text, source) {
|
|
|
134
151
|
return { ...next, title: sessionTitle(next) };
|
|
135
152
|
}
|
|
136
153
|
function reduce(s, ev) {
|
|
154
|
+
if (ev.agentSessionId && !ev.helperId && ev.agentSessionId !== s.agentSessionId) s = { ...s, agentSessionId: ev.agentSessionId };
|
|
137
155
|
if (s.state === "exited") return s;
|
|
138
156
|
s = endCursorHelpers(s, ev);
|
|
139
157
|
const at = ev.at;
|
|
@@ -200,7 +218,7 @@ function reduce(s, ev) {
|
|
|
200
218
|
case "PreToolUse":
|
|
201
219
|
case "PermissionRequest":
|
|
202
220
|
case "Notification": {
|
|
203
|
-
if (ev.name === "UserPromptSubmit" && ev.text && !s.topic) s = withTopic(s, ev.text, "prompt");
|
|
221
|
+
if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
|
|
204
222
|
const incoming = pendingFor(ev);
|
|
205
223
|
if (incoming) {
|
|
206
224
|
if (ev.name === "Notification" && s.pending?.source === "hook") return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
@@ -218,6 +236,7 @@ function reduce(s, ev) {
|
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
export {
|
|
239
|
+
crossSession,
|
|
221
240
|
QUESTION_TOOLS,
|
|
222
241
|
reduce
|
|
223
242
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
exitFile,
|
|
3
3
|
spawnSession
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3TXM4DM7.js";
|
|
5
5
|
import {
|
|
6
6
|
realTmux
|
|
7
7
|
} from "./chunk-3TNPEAMR.js";
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
} from "./chunk-L6IGH4LF.js";
|
|
12
12
|
import {
|
|
13
13
|
connect
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-MK7NEXY7.js";
|
|
15
15
|
import {
|
|
16
16
|
PROTOCOL_PORT
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4BBKLTDB.js";
|
|
18
18
|
|
|
19
19
|
// src/cli/launch.ts
|
|
20
20
|
import { spawn } from "child_process";
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
QUESTION_TOOLS,
|
|
3
|
+
crossSession,
|
|
3
4
|
reduce
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GZFSUQZJ.js";
|
|
5
6
|
import {
|
|
6
7
|
agentMessages,
|
|
7
8
|
claudePlan,
|
|
8
9
|
spawnSession
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3TXM4DM7.js";
|
|
10
11
|
import {
|
|
11
12
|
LEDGE_VERSION
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-KD7LKXIF.js";
|
|
13
14
|
import {
|
|
14
15
|
SERVICE,
|
|
15
16
|
inDaemonService,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
SessionSchema,
|
|
42
43
|
SshAliasSchema,
|
|
43
44
|
extractProposal,
|
|
45
|
+
isArchived,
|
|
44
46
|
offers,
|
|
45
47
|
parseClientMessage,
|
|
46
48
|
parseProjectBlock,
|
|
@@ -50,7 +52,7 @@ import {
|
|
|
50
52
|
pendingKey,
|
|
51
53
|
pickName,
|
|
52
54
|
projectOf
|
|
53
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-4BBKLTDB.js";
|
|
54
56
|
|
|
55
57
|
// src/daemon/index.ts
|
|
56
58
|
import { writeFile as writeFile5, rm as rm5 } from "fs/promises";
|
|
@@ -59,6 +61,7 @@ import { writeFile as writeFile5, rm as rm5 } from "fs/promises";
|
|
|
59
61
|
import { readFile, writeFile, rename, mkdir } from "fs/promises";
|
|
60
62
|
import { dirname } from "path";
|
|
61
63
|
var MAX_RECENTS = 12;
|
|
64
|
+
var ARCHIVE_MAX = 200;
|
|
62
65
|
var Store = class {
|
|
63
66
|
sessions = /* @__PURE__ */ new Map();
|
|
64
67
|
recentList = [];
|
|
@@ -67,33 +70,49 @@ var Store = class {
|
|
|
67
70
|
timer = null;
|
|
68
71
|
saving = Promise.resolve();
|
|
69
72
|
journalPath;
|
|
73
|
+
/** Set by an unclean load and saved with the journal: a later clean-looking load must not be trusted for cleanups. */
|
|
74
|
+
incomplete = false;
|
|
70
75
|
constructor(opts = {}) {
|
|
71
76
|
this.journalPath = opts.journalPath ?? paths().journal;
|
|
72
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Reads the journal. `clean` is false when it was missing, unreadable, or an entry was dropped by the schema, or when
|
|
80
|
+
* an earlier load was so (the mark is saved with the journal): what the store holds may then not be every session,
|
|
81
|
+
* and nothing may be cleaned up on the strength of it (the threads of the sessions it misses).
|
|
82
|
+
*/
|
|
73
83
|
async load() {
|
|
74
84
|
let raw;
|
|
75
85
|
try {
|
|
76
86
|
raw = await readFile(this.journalPath, "utf8");
|
|
77
87
|
} catch {
|
|
78
|
-
return;
|
|
88
|
+
return { clean: false, reason: "no journal" };
|
|
79
89
|
}
|
|
80
|
-
|
|
90
|
+
const bad = async (reason) => {
|
|
91
|
+
this.incomplete = true;
|
|
92
|
+
await writeFile(`${this.journalPath}.incomplete`, raw, { flag: "wx" }).catch(() => void 0);
|
|
93
|
+
return { clean: false, reason };
|
|
94
|
+
};
|
|
95
|
+
if (!raw.trim()) return bad("empty journal");
|
|
81
96
|
let parsed;
|
|
82
97
|
try {
|
|
83
98
|
parsed = JSON.parse(raw);
|
|
84
99
|
} catch (e) {
|
|
85
100
|
log(`journal unreadable, starting empty: ${e.message}`);
|
|
86
|
-
return;
|
|
101
|
+
return bad("unreadable journal");
|
|
87
102
|
}
|
|
88
103
|
const list = parsed?.sessions;
|
|
89
104
|
if (!Array.isArray(list)) {
|
|
90
105
|
log("journal unreadable, starting empty: no sessions array");
|
|
91
|
-
return;
|
|
106
|
+
return bad("no sessions array");
|
|
92
107
|
}
|
|
108
|
+
let dropped = 0;
|
|
93
109
|
for (const item of list) {
|
|
94
110
|
const r = SessionSchema.safeParse(item);
|
|
95
111
|
if (r.success) this.sessions.set(r.data.id, r.data);
|
|
96
|
-
else
|
|
112
|
+
else {
|
|
113
|
+
dropped++;
|
|
114
|
+
log(`journal entry dropped: ${r.error.issues[0]?.message ?? "invalid"}`);
|
|
115
|
+
}
|
|
97
116
|
}
|
|
98
117
|
const recents = parsed?.recents;
|
|
99
118
|
if (Array.isArray(recents)) {
|
|
@@ -103,6 +122,12 @@ var Store = class {
|
|
|
103
122
|
}
|
|
104
123
|
this.recentList = this.recentList.slice(0, MAX_RECENTS);
|
|
105
124
|
}
|
|
125
|
+
if (dropped) return bad(`${dropped} journal ${dropped === 1 ? "entry" : "entries"} dropped`);
|
|
126
|
+
if (parsed.incomplete === true) {
|
|
127
|
+
this.incomplete = true;
|
|
128
|
+
return { clean: false, reason: "an earlier load was incomplete" };
|
|
129
|
+
}
|
|
130
|
+
return { clean: true };
|
|
106
131
|
}
|
|
107
132
|
all() {
|
|
108
133
|
return [...this.sessions.values()];
|
|
@@ -130,14 +155,33 @@ var Store = class {
|
|
|
130
155
|
if (Object.keys(fields).every((k) => cur[k] === fields[k])) return;
|
|
131
156
|
this.upsert({ ...cur, ...fields });
|
|
132
157
|
}
|
|
158
|
+
/** Sets the archive date (kept if already set), then drops the oldest archives past `ARCHIVE_MAX`. */
|
|
159
|
+
archive(id, at) {
|
|
160
|
+
const cur = this.sessions.get(id);
|
|
161
|
+
if (!cur) return void 0;
|
|
162
|
+
const next = cur.archivedAt === void 0 ? { ...cur, archivedAt: at } : cur;
|
|
163
|
+
if (next !== cur) this.upsert(next);
|
|
164
|
+
const archived = this.all().filter((s) => s.archivedAt !== void 0).sort((a, b) => a.archivedAt.localeCompare(b.archivedAt));
|
|
165
|
+
for (const old of archived.slice(0, Math.max(0, archived.length - ARCHIVE_MAX))) this.remove(old.id);
|
|
166
|
+
return this.sessions.get(id);
|
|
167
|
+
}
|
|
168
|
+
/** Takes the session out of the archive; `at` (a restore by the user) restarts the retirement clock from then. */
|
|
169
|
+
unarchive(id, at) {
|
|
170
|
+
const cur = this.sessions.get(id);
|
|
171
|
+
if (!cur) return void 0;
|
|
172
|
+
if (cur.archivedAt === void 0) return cur;
|
|
173
|
+
const { archivedAt: _gone, ...rest } = cur;
|
|
174
|
+
const next = at !== void 0 ? { ...rest, restoredAt: at } : rest;
|
|
175
|
+
this.upsert(next);
|
|
176
|
+
return next;
|
|
177
|
+
}
|
|
133
178
|
/** Every event applied to a known session, before the reducer decides whether it changes anything. */
|
|
134
179
|
onEvent(listener) {
|
|
135
180
|
this.eventListeners.add(listener);
|
|
136
181
|
return () => this.eventListeners.delete(listener);
|
|
137
182
|
}
|
|
138
183
|
apply(id, event) {
|
|
139
|
-
|
|
140
|
-
if (!cur) return void 0;
|
|
184
|
+
if (!this.sessions.has(id)) return void 0;
|
|
141
185
|
for (const l of this.eventListeners) {
|
|
142
186
|
try {
|
|
143
187
|
l(id, event);
|
|
@@ -145,6 +189,8 @@ var Store = class {
|
|
|
145
189
|
log(`event listener failed: ${e.message}`);
|
|
146
190
|
}
|
|
147
191
|
}
|
|
192
|
+
const cur = this.sessions.get(id);
|
|
193
|
+
if (!cur) return void 0;
|
|
148
194
|
const next = reduce(cur, event);
|
|
149
195
|
if (next === cur) return cur;
|
|
150
196
|
this.sessions.set(id, next);
|
|
@@ -171,7 +217,7 @@ var Store = class {
|
|
|
171
217
|
async write() {
|
|
172
218
|
await mkdir(dirname(this.journalPath), { recursive: true });
|
|
173
219
|
const tmp = `${this.journalPath}.tmp`;
|
|
174
|
-
await writeFile(tmp, JSON.stringify({ savedAt: (/* @__PURE__ */ new Date()).toISOString(), sessions: this.all(), recents: this.recentList }, null, 2));
|
|
220
|
+
await writeFile(tmp, JSON.stringify({ savedAt: (/* @__PURE__ */ new Date()).toISOString(), ...this.incomplete ? { incomplete: true } : {}, sessions: this.all(), recents: this.recentList }, null, 2));
|
|
175
221
|
await rename(tmp, this.journalPath);
|
|
176
222
|
}
|
|
177
223
|
emit(c) {
|
|
@@ -282,8 +328,11 @@ var History = class {
|
|
|
282
328
|
listeners = /* @__PURE__ */ new Set();
|
|
283
329
|
writes = Promise.resolve();
|
|
284
330
|
dir;
|
|
331
|
+
/** The Ledge session a SendMessage address or a sender's name is, when it is one (peers.ts). */
|
|
332
|
+
peerOf;
|
|
285
333
|
constructor(opts = {}) {
|
|
286
334
|
this.dir = opts.dir ?? paths().history;
|
|
335
|
+
this.peerOf = opts.peerOf ?? (() => void 0);
|
|
287
336
|
}
|
|
288
337
|
items(id) {
|
|
289
338
|
return this.threads.get(id) ?? [];
|
|
@@ -355,6 +404,15 @@ var History = class {
|
|
|
355
404
|
if (helper?.kind === "helper") this.settle(id, (i) => i.helperId === back.helperId);
|
|
356
405
|
return back.report ? this.put(id, { kind: "reply", id: `report-${back.helperId}`, at: ev.at, turn: helper?.turn ?? turn, helperId: back.helperId, text: back.report }) : void 0;
|
|
357
406
|
}
|
|
407
|
+
const messages = crossSession(ev.text);
|
|
408
|
+
if (messages) {
|
|
409
|
+
this.turns.set(id, turn + 1);
|
|
410
|
+
let last;
|
|
411
|
+
messages.forEach((m, i) => {
|
|
412
|
+
last = this.receive(id, { kind: "received", id: `recv-${turn + 1}-${i}`, at: ev.at, turn: turn + 1, from: m.name ?? m.from, text: m.text }, m);
|
|
413
|
+
});
|
|
414
|
+
return last;
|
|
415
|
+
}
|
|
358
416
|
this.turns.set(id, turn + 1);
|
|
359
417
|
return this.put(id, { kind: "prompt", id: `prompt-${turn + 1}`, at: ev.at, turn: turn + 1, text: ev.text });
|
|
360
418
|
}
|
|
@@ -362,6 +420,7 @@ var History = class {
|
|
|
362
420
|
if (!ev.tool) return void 0;
|
|
363
421
|
const existing = ev.toolUseId && this.items(id).find((i) => i.id === ev.toolUseId);
|
|
364
422
|
if (existing) return existing;
|
|
423
|
+
if (ev.tool === "SendMessage") return this.put(id, this.sentOf(ev, common, "sending"));
|
|
365
424
|
const input = inputText(ev.toolInput);
|
|
366
425
|
return this.put(id, { kind: "tool", id: ev.toolUseId ?? `tool-${ev.at}-${this.items(id).length}`, ...common, tool: ev.tool, label: ev.text ?? ev.tool, ...input ? { input } : {}, status: "running", files: filesOf(ev.tool, ev.toolInput) });
|
|
367
426
|
}
|
|
@@ -369,6 +428,11 @@ var History = class {
|
|
|
369
428
|
case "PostToolUseFailure": {
|
|
370
429
|
if (!ev.tool) return void 0;
|
|
371
430
|
const failed = ev.name === "PostToolUseFailure";
|
|
431
|
+
if (ev.tool === "SendMessage") {
|
|
432
|
+
const sent = this.items(id).find((i) => i.kind === "sent" && i.id === ev.toolUseId);
|
|
433
|
+
if (sent?.status === "read") return sent;
|
|
434
|
+
return this.put(id, sent ? { ...sent, status: failed ? "failed" : "delivered" } : this.sentOf(ev, common, failed ? "failed" : "delivered"));
|
|
435
|
+
}
|
|
372
436
|
const row = this.callFor(id, ev);
|
|
373
437
|
const base = row ?? { kind: "tool", id: ev.toolUseId ?? `tool-${ev.at}-${this.items(id).length}`, ...common, tool: ev.tool, label: ev.text ?? ev.tool, status: "running", files: filesOf(ev.tool, ev.toolInput) };
|
|
374
438
|
const error = failed ? ev.error ?? (ev.interrupt ? "interrupted" : "failed") : void 0;
|
|
@@ -419,7 +483,7 @@ var History = class {
|
|
|
419
483
|
if (!opts.force && now - (this.synced.get(key) ?? 0) < SYNC_MS) return [];
|
|
420
484
|
this.synced.set(key, now);
|
|
421
485
|
const items = this.items(id);
|
|
422
|
-
const since = opts.helperId ? items.find((i) => i.id === opts.helperId)?.at : [...items].reverse().find((i) => i.kind === "prompt")?.at;
|
|
486
|
+
const since = opts.helperId ? items.find((i) => i.id === opts.helperId)?.at : [...items].reverse().find((i) => i.kind === "prompt" || i.kind === "received")?.at;
|
|
423
487
|
if (!since) return [];
|
|
424
488
|
const have = new Set(items.map((i) => i.id));
|
|
425
489
|
const turn = this.turns.get(id) ?? 0;
|
|
@@ -430,6 +494,25 @@ var History = class {
|
|
|
430
494
|
}
|
|
431
495
|
return added;
|
|
432
496
|
}
|
|
497
|
+
/** A SendMessage's card, from its input: `to` the address used, `peer` the Ledge session that address is. */
|
|
498
|
+
sentOf(ev, common, status) {
|
|
499
|
+
const input = ev.toolInput ?? {};
|
|
500
|
+
const to = typeof input.to === "string" ? input.to : "";
|
|
501
|
+
const peer = to ? this.peerOf(to) : void 0;
|
|
502
|
+
return { kind: "sent", id: ev.toolUseId ?? `sent-${ev.at}`, ...common, to, ...peer ? { peer } : {}, ...typeof input.summary === "string" && input.summary ? { summary: input.summary } : {}, text: typeof input.message === "string" ? input.message : "", status };
|
|
503
|
+
}
|
|
504
|
+
/** Puts a received card, paired with the sender's sent one when the sender is a Ledge session: the last of its
|
|
505
|
+
* messages to this session not read yet, the one whose text starts like the body if any (the hook may have cut
|
|
506
|
+
* either). That sent one becomes read, in the sender's thread. */
|
|
507
|
+
receive(id, recv, m) {
|
|
508
|
+
const peer = this.peerOf(m.from) ?? (m.name ? this.peerOf(m.name) : void 0);
|
|
509
|
+
if (!peer) return this.put(id, recv);
|
|
510
|
+
const head = (t) => t.trim().slice(0, 200);
|
|
511
|
+
const open3 = this.items(peer).filter((i) => i.kind === "sent" && i.peer === id && i.status !== "read");
|
|
512
|
+
const sent = [...open3].reverse().find((i) => head(i.text) === head(recv.text)) ?? open3.at(-1);
|
|
513
|
+
if (sent) this.put(peer, { ...sent, status: "read", received: recv.id });
|
|
514
|
+
return this.put(id, { ...recv, peer, ...sent ? { sent: sent.id } : {} });
|
|
515
|
+
}
|
|
433
516
|
/** The turn (or the helper) is over: an action still running will not get its result from this side. It shows as
|
|
434
517
|
* unknown, not as waiting forever; a result that arrives later still settles it (callFor finds it by id). */
|
|
435
518
|
settle(id, mine) {
|
|
@@ -1303,7 +1386,10 @@ function startPoller(opts) {
|
|
|
1303
1386
|
async function pollOne(s, t, deadCheck) {
|
|
1304
1387
|
if (s.state === "exited") {
|
|
1305
1388
|
forget(s.id);
|
|
1306
|
-
if (s.
|
|
1389
|
+
if (!s.archivedAt && s.exitedAt) {
|
|
1390
|
+
const since = Math.max(Date.parse(s.exitedAt), s.restoredAt ? Date.parse(s.restoredAt) : -Infinity);
|
|
1391
|
+
if (t - since > retireMs) store.archive(s.id, now());
|
|
1392
|
+
}
|
|
1307
1393
|
return;
|
|
1308
1394
|
}
|
|
1309
1395
|
if ((deadCheck || !gitSeen.has(s.id)) && s.cwd) {
|
|
@@ -2127,7 +2213,7 @@ function createChat(deps) {
|
|
|
2127
2213
|
}
|
|
2128
2214
|
}
|
|
2129
2215
|
async function run2(turn, emit, fresh, mine, tool) {
|
|
2130
|
-
const ctx = await writeContext(contextDir, { sessions: deps.store.all(), recents: deps.store.recents(), projects: deps.projects?.(), now: now(), capture, git: (cwd) => gitSummary(cwd) });
|
|
2216
|
+
const ctx = await writeContext(contextDir, { sessions: deps.store.all().filter((s) => !isArchived(s)), recents: deps.store.recents(), projects: deps.projects?.(), now: now(), capture, git: (cwd) => gitSummary(cwd) });
|
|
2131
2217
|
if (mine.cancelled) return CANCELLED;
|
|
2132
2218
|
const { sessionId, resume } = await daySession(fresh, tool);
|
|
2133
2219
|
const label2 = TOOL_LABEL[tool];
|
|
@@ -2606,6 +2692,11 @@ function serveSsh(ws, alias, opts = {}) {
|
|
|
2606
2692
|
|
|
2607
2693
|
// src/daemon/server.ts
|
|
2608
2694
|
var CONTINUE_ARGS = { claude: ["--continue"], codex: ["resume", "--last"], cursor: ["--continue"] };
|
|
2695
|
+
var RESUME_ARGS = { claude: (id) => ["--resume", id], codex: (id) => ["resume", id], cursor: (id) => ["--resume", id] };
|
|
2696
|
+
function resumeKey(s) {
|
|
2697
|
+
if (s.agentSessionId) return s.agentSessionId;
|
|
2698
|
+
return s.tool === "claude" && s.transcriptPath ? basename4(s.transcriptPath, ".jsonl") : void 0;
|
|
2699
|
+
}
|
|
2609
2700
|
var MULTI_DIR_ARGS = { claude: (dirs) => ["--add-dir", ...dirs] };
|
|
2610
2701
|
function launchArgs(msg) {
|
|
2611
2702
|
const prompt = msg.prompt?.trim();
|
|
@@ -2617,6 +2708,7 @@ var NoteNotTyped = class extends Error {
|
|
|
2617
2708
|
super("denied, but the note could not be typed");
|
|
2618
2709
|
}
|
|
2619
2710
|
};
|
|
2711
|
+
var DELETED_TTL_MS = 5 * 60 * 1e3;
|
|
2620
2712
|
var ALLOWED_ORIGINS = [
|
|
2621
2713
|
"tauri://localhost",
|
|
2622
2714
|
"http://tauri.localhost",
|
|
@@ -2680,7 +2772,13 @@ async function startServer(opts) {
|
|
|
2680
2772
|
};
|
|
2681
2773
|
const history = opts.history;
|
|
2682
2774
|
const unsubscribeHistory = history ? [
|
|
2683
|
-
history.onItem((id, item) =>
|
|
2775
|
+
history.onItem((id, item) => {
|
|
2776
|
+
broadcast({ type: "session.thread.item", id, item });
|
|
2777
|
+
const cur = store.get(id)?.lastMessage;
|
|
2778
|
+
if (item.kind === "sent" && item.status !== "read" && item.status !== "failed" && cur?.id !== item.id && (!cur || item.at >= cur.at)) {
|
|
2779
|
+
store.patch(id, { lastMessage: { id: item.id, to: item.to, ...item.peer ? { peer: item.peer } : {}, summary: item.summary ?? item.text.split("\n")[0].slice(0, 120), at: item.at } });
|
|
2780
|
+
}
|
|
2781
|
+
}),
|
|
2684
2782
|
store.onEvent((id, ev) => history.record(id, ev)),
|
|
2685
2783
|
store.onChange((c) => {
|
|
2686
2784
|
if (c.type === "removed") history.forget(c.id);
|
|
@@ -2702,7 +2800,7 @@ async function startServer(opts) {
|
|
|
2702
2800
|
broadcast(c.type === "updated" ? { type: "session.updated", session: c.session } : { type: "session.removed", id: c.id });
|
|
2703
2801
|
});
|
|
2704
2802
|
const sendSnapshot = (ws) => {
|
|
2705
|
-
send(ws, { type: "snapshot", sessions: store.all(), recents: store.recents(), launcher, ...opts.daemon ? { daemon: { ...opts.daemon, routing: true } } : {} });
|
|
2803
|
+
send(ws, { type: "snapshot", sessions: store.all(), recents: store.recents(), launcher, ...opts.daemon ? { daemon: { ...opts.daemon, routing: true, archive: true } } : {} });
|
|
2706
2804
|
send(ws, machinesMsg());
|
|
2707
2805
|
send(ws, projectsMsg());
|
|
2708
2806
|
send(ws, islandMsg());
|
|
@@ -2791,6 +2889,11 @@ async function startServer(opts) {
|
|
|
2791
2889
|
const NOT_TAKEN = "the terminal did not take it";
|
|
2792
2890
|
const FREE_TEXT = "free-text answers are not supported yet, answer in the terminal";
|
|
2793
2891
|
const NOT_OFFERED = "this answer is not offered for this request, answer in the terminal";
|
|
2892
|
+
async function endSession(s) {
|
|
2893
|
+
if (s.source !== "launcher" || s.state === "exited") return;
|
|
2894
|
+
if (s.tmuxSession) await tmux.killSession(s.tmuxSession).catch((e) => log(`kill of ${s.tmuxSession} failed: ${e.message}`));
|
|
2895
|
+
store.apply(s.id, { name: "PaneGone", at: now(), source: "daemon" });
|
|
2896
|
+
}
|
|
2794
2897
|
async function replyTo(id, text) {
|
|
2795
2898
|
const s = store.get(id);
|
|
2796
2899
|
if (!s) return "unknown session";
|
|
@@ -2803,6 +2906,19 @@ async function startServer(opts) {
|
|
|
2803
2906
|
await injectAndVerify(id, (pane) => adapter.replyPlan(text, pending, pane || void 0), !!pending);
|
|
2804
2907
|
return null;
|
|
2805
2908
|
}
|
|
2909
|
+
const deleted = /* @__PURE__ */ new Map();
|
|
2910
|
+
function markDeleted(id) {
|
|
2911
|
+
const t = Date.now();
|
|
2912
|
+
for (const [k, at] of deleted) if (t - at > DELETED_TTL_MS) deleted.delete(k);
|
|
2913
|
+
deleted.set(id, t);
|
|
2914
|
+
}
|
|
2915
|
+
function recentlyDeleted(id) {
|
|
2916
|
+
const at = deleted.get(id);
|
|
2917
|
+
if (at === void 0) return false;
|
|
2918
|
+
if (Date.now() - at <= DELETED_TTL_MS) return true;
|
|
2919
|
+
deleted.delete(id);
|
|
2920
|
+
return false;
|
|
2921
|
+
}
|
|
2806
2922
|
const chatLaunches = /* @__PURE__ */ new Set();
|
|
2807
2923
|
async function launch(req) {
|
|
2808
2924
|
if (req.dirs?.length && !MULTI_DIR_ARGS[req.tool]) throw new Error(`launch: ${req.tool} takes one folder`);
|
|
@@ -2917,6 +3033,7 @@ async function startServer(opts) {
|
|
|
2917
3033
|
}
|
|
2918
3034
|
case "session.event": {
|
|
2919
3035
|
const ev = msg.event;
|
|
3036
|
+
if (recentlyDeleted(msg.id)) return;
|
|
2920
3037
|
const ownTurn = !ev.helperId && (ev.name === "PreToolUse" || ev.name === "PermissionRequest" || ev.name === "Stop");
|
|
2921
3038
|
const helperEnd = ev.name === "SubagentStop" && !!ev.helperId;
|
|
2922
3039
|
if (history && ev.transcriptPath && (ownTurn || helperEnd)) {
|
|
@@ -2926,7 +3043,11 @@ async function startServer(opts) {
|
|
|
2926
3043
|
log(`transcript not read for ${msg.id}: ${e.message}`);
|
|
2927
3044
|
}
|
|
2928
3045
|
}
|
|
3046
|
+
const terminal = ev.name === "SessionEnd" || ev.name === "PaneGone";
|
|
3047
|
+
const stored = store.get(msg.id);
|
|
3048
|
+
if (stored?.archivedAt && stored.state !== "exited" && !terminal) store.unarchive(msg.id);
|
|
2929
3049
|
const before = store.get(msg.id);
|
|
3050
|
+
if (!before && terminal) return;
|
|
2930
3051
|
if (!before) {
|
|
2931
3052
|
const taken = store.all().map((s) => s.name);
|
|
2932
3053
|
const cwd = msg.cwd ?? "";
|
|
@@ -2988,6 +3109,54 @@ async function startServer(opts) {
|
|
|
2988
3109
|
store.remove(msg.id);
|
|
2989
3110
|
return;
|
|
2990
3111
|
}
|
|
3112
|
+
case "session.archive": {
|
|
3113
|
+
const s = store.get(msg.id);
|
|
3114
|
+
if (!s) return refuse("unknown session", msg.id);
|
|
3115
|
+
await endSession(s);
|
|
3116
|
+
store.archive(msg.id, now());
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
3119
|
+
case "session.restore": {
|
|
3120
|
+
if (!store.get(msg.id)) return refuse("unknown session", msg.id);
|
|
3121
|
+
store.unarchive(msg.id, now());
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
case "session.resume": {
|
|
3125
|
+
const s = store.get(msg.id);
|
|
3126
|
+
if (!s) return refuse("unknown session", msg.id);
|
|
3127
|
+
if (s.state !== "exited") return refuse("the session is still running", msg.id);
|
|
3128
|
+
const key = resumeKey(s);
|
|
3129
|
+
if (!key) return refuse("no conversation to resume", msg.id);
|
|
3130
|
+
const { archivedAt: _a, exitedAt: _e, exitCode: _c, ...rest } = s;
|
|
3131
|
+
store.upsert({ ...rest, state: "starting", source: "launcher", agentSessionId: key });
|
|
3132
|
+
try {
|
|
3133
|
+
const cwd = await resolveFolder(s.cwd, opts.home);
|
|
3134
|
+
const env = await agentEnv();
|
|
3135
|
+
const spawned = await spawnSession({ tool: s.tool, id: s.id, name: s.name, args: RESUME_ARGS[s.tool](key), cwd, tmux, now, env, taken: { names: store.all().filter((o) => o.id !== s.id && o.state !== "exited").map((o) => o.name), ids: [] } });
|
|
3136
|
+
const cur = store.get(s.id);
|
|
3137
|
+
if (!cur || cur.archivedAt !== void 0) {
|
|
3138
|
+
if (spawned.tmuxSession) await tmux.killSession(spawned.tmuxSession).catch((e) => log(`kill of ${spawned.tmuxSession} failed: ${e.message}`));
|
|
3139
|
+
log(`resume of ${s.id} dropped: the session was ${cur ? "archived" : "deleted"} meanwhile`);
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
const { topic, topicSource, repo, repoPath, branch, worktree, repoId, project, transcriptPath } = s;
|
|
3143
|
+
store.upsert({ ...spawned, agentSessionId: key, ...transcriptPath ? { transcriptPath } : {}, ...topic ? { topic } : {}, ...topicSource ? { topicSource } : {}, ...repo ? { repo } : {}, ...repoPath ? { repoPath } : {}, ...branch ? { branch } : {}, ...worktree !== void 0 ? { worktree } : {}, ...repoId ? { repoId } : {}, project });
|
|
3144
|
+
log(`resumed ${spawned.name} (${spawned.tool}) in ${spawned.cwd} as ${spawned.id}`);
|
|
3145
|
+
} catch (e) {
|
|
3146
|
+
const cur = store.get(s.id);
|
|
3147
|
+
if (cur && cur.archivedAt === void 0 && cur.state === "starting") store.upsert(s);
|
|
3148
|
+
return refuse(e.message, msg.id);
|
|
3149
|
+
}
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
case "session.delete": {
|
|
3153
|
+
const s = store.get(msg.id);
|
|
3154
|
+
if (!s) return refuse("unknown session", msg.id);
|
|
3155
|
+
if (s.source === "launcher" || s.state === "exited") markDeleted(msg.id);
|
|
3156
|
+
await endSession(s);
|
|
3157
|
+
store.remove(msg.id);
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
2991
3160
|
// A chat card's instruction: a refusal reaches the requester as an `error`, the card's state everyone through chat.history.
|
|
2992
3161
|
case "session.relay":
|
|
2993
3162
|
return relay.request(msg.qid);
|
|
@@ -3166,11 +3335,49 @@ ${output}` } : { code: r.code, output };
|
|
|
3166
3335
|
};
|
|
3167
3336
|
}
|
|
3168
3337
|
|
|
3338
|
+
// src/daemon/peers.ts
|
|
3339
|
+
import { readdirSync, readFileSync as readFileSync4 } from "fs";
|
|
3340
|
+
import { homedir as homedir6 } from "os";
|
|
3341
|
+
import { basename as basename5, join as join10 } from "path";
|
|
3342
|
+
var defaultDir = () => join10(process.env.CLAUDE_CONFIG_DIR ?? join10(homedir6(), ".claude"), "sessions");
|
|
3343
|
+
function parsePeer(path) {
|
|
3344
|
+
try {
|
|
3345
|
+
const j = JSON.parse(readFileSync4(path, "utf8"));
|
|
3346
|
+
if (typeof j.pid !== "number" || typeof j.sessionId !== "string" || typeof j.name !== "string" || typeof j.messagingSocketPath !== "string") return void 0;
|
|
3347
|
+
return { pid: j.pid, sessionId: j.sessionId, name: j.name, address: `uds:${j.messagingSocketPath}` };
|
|
3348
|
+
} catch {
|
|
3349
|
+
return void 0;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function readPeers(dir = defaultDir()) {
|
|
3353
|
+
let names;
|
|
3354
|
+
try {
|
|
3355
|
+
names = readdirSync(dir);
|
|
3356
|
+
} catch {
|
|
3357
|
+
return [];
|
|
3358
|
+
}
|
|
3359
|
+
return names.filter((n) => n.endsWith(".json")).flatMap((n) => parsePeer(join10(dir, n)) ?? []);
|
|
3360
|
+
}
|
|
3361
|
+
function peerFor(target, peers, sessions, dir = defaultDir()) {
|
|
3362
|
+
const name = target.replace(/ \[[^\]]*\]$/, "");
|
|
3363
|
+
const found = peers.filter((p) => p.name === name || p.address === name);
|
|
3364
|
+
if (!found.length && name.startsWith("uds:") && name.endsWith(".sock")) {
|
|
3365
|
+
const pid = basename5(name, ".sock");
|
|
3366
|
+
const direct = /^\d+$/.test(pid) ? parsePeer(join10(dir, `${pid}.json`)) : void 0;
|
|
3367
|
+
if (direct) found.push(direct);
|
|
3368
|
+
}
|
|
3369
|
+
for (const { sessionId: id } of found) {
|
|
3370
|
+
const s = sessions.find((s2) => s2.agentSessionId === id || s2.transcriptPath?.endsWith(`/${id}.jsonl`));
|
|
3371
|
+
if (s) return s;
|
|
3372
|
+
}
|
|
3373
|
+
return void 0;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3169
3376
|
// src/daemon/index.ts
|
|
3170
3377
|
async function startDaemon(opts = {}) {
|
|
3171
3378
|
await ensureLedgeDir();
|
|
3172
3379
|
const store = new Store();
|
|
3173
|
-
await store.load();
|
|
3380
|
+
const loaded = await store.load();
|
|
3174
3381
|
const tmux = opts.tmux ?? realTmux(opts.tmuxSocket ?? "ledge");
|
|
3175
3382
|
let live2 = null;
|
|
3176
3383
|
try {
|
|
@@ -3179,17 +3386,19 @@ async function startDaemon(opts = {}) {
|
|
|
3179
3386
|
log(`startup prune skipped, tmux unavailable: ${e.message}`);
|
|
3180
3387
|
}
|
|
3181
3388
|
if (live2) for (const s of store.all()) {
|
|
3182
|
-
if (s.tmuxSession && !live2.has(s.tmuxSession)) store.
|
|
3389
|
+
if (s.tmuxSession && !live2.has(s.tmuxSession)) store.apply(s.id, { name: "PaneGone", at: (/* @__PURE__ */ new Date()).toISOString(), source: "daemon" });
|
|
3183
3390
|
}
|
|
3184
3391
|
const daemon = await daemonInfo();
|
|
3185
3392
|
const updater = createDaemonUpdater(realUpdaterDeps({ info: daemon, save: () => store.save() }));
|
|
3186
|
-
const history = new History();
|
|
3393
|
+
const history = new History({ peerOf: (t) => peerFor(t, readPeers(), store.all())?.id });
|
|
3187
3394
|
try {
|
|
3188
3395
|
await history.load();
|
|
3189
3396
|
} catch (e) {
|
|
3190
3397
|
log(`history not loaded: ${e.message}`);
|
|
3191
3398
|
}
|
|
3192
|
-
|
|
3399
|
+
if (loaded.clean) {
|
|
3400
|
+
for (const id of history.ids()) if (!store.get(id)) history.forget(id);
|
|
3401
|
+
} else log(`history cleanup skipped: ${loaded.reason}`);
|
|
3193
3402
|
const server = await startServer({ store, tmux, port: opts.port, daemon, updater, history });
|
|
3194
3403
|
const poller = startPoller({ store, tmux, broadcastPreview: server.preview });
|
|
3195
3404
|
await writeFile5(paths().pid, String(process.pid));
|
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
QUESTION_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GZFSUQZJ.js";
|
|
4
4
|
import {
|
|
5
5
|
CODEX_ENV_VAR,
|
|
6
6
|
CODEX_HOOK_VAR,
|
|
7
7
|
codexPlan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3TXM4DM7.js";
|
|
9
9
|
import {
|
|
10
10
|
log
|
|
11
11
|
} from "./chunk-L6IGH4LF.js";
|
|
12
12
|
import {
|
|
13
13
|
connect
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-MK7NEXY7.js";
|
|
15
|
+
import "./chunk-4BBKLTDB.js";
|
|
16
16
|
|
|
17
17
|
// src/cli/hook.ts
|
|
18
18
|
import { readFileSync, openSync, fstatSync, readSync, closeSync } from "fs";
|
|
19
19
|
import { basename, join } from "path";
|
|
20
20
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
21
21
|
var PROMPT_MAX = 500;
|
|
22
|
-
var
|
|
22
|
+
var fromAgent = (t) => t.startsWith('<agent-message from="') || t.startsWith("<cross-session-message ");
|
|
23
23
|
var promptText = (p) => {
|
|
24
24
|
const t = str(p.prompt)?.trim();
|
|
25
|
-
return t ? { text: t.slice(0,
|
|
25
|
+
return t ? { text: t.slice(0, fromAgent(t) ? INPUT_MAX : PROMPT_MAX) } : {};
|
|
26
26
|
};
|
|
27
27
|
function describeTool(tool, input) {
|
|
28
28
|
const i = input && typeof input === "object" ? input : {};
|
|
29
29
|
if (tool === "Bash") return str(i.command) ?? "";
|
|
30
30
|
if (tool === "Edit" || tool === "Write" || tool === "MultiEdit" || tool === "Read") return str(i.file_path) ?? "";
|
|
31
|
+
if (tool === "SendMessage") return str(i.summary) || `\u2192 ${str(i.to) ?? ""}`;
|
|
31
32
|
if (tool === "ExitPlanMode") return str(i.plan)?.split("\n")[0] ?? "plan";
|
|
32
33
|
if (tool === "apply_patch") {
|
|
33
34
|
const files = [...(str(i.command) ?? "").matchAll(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm)].map((m) => m[1].trim());
|
|
@@ -102,7 +103,8 @@ function mapHook(agent, eventName, payload, at) {
|
|
|
102
103
|
const event = mapEvent(eventName, p, at, launched);
|
|
103
104
|
if (!event) return null;
|
|
104
105
|
const task = agent === "claude" && event.helperId ? helperDescription(str(p.transcript_path), event.helperId) : void 0;
|
|
105
|
-
|
|
106
|
+
const agentSessionId = !event.helperId ? str(p.session_id) : void 0;
|
|
107
|
+
return { id, event: { ...event, ...task ? { task } : {}, ...agentSessionId ? { agentSessionId } : {} }, agent, ...cwd ? { cwd } : {} };
|
|
106
108
|
}
|
|
107
109
|
function helperDescription(transcriptPath, agentId) {
|
|
108
110
|
if (!transcriptPath?.endsWith(".jsonl") || !/^[\w-]+$/.test(agentId)) return void 0;
|
|
@@ -246,7 +248,7 @@ function mapCursorHook(eventName, payload, at, env = process.env) {
|
|
|
246
248
|
const roots = Array.isArray(p.workspace_roots) ? p.workspace_roots : [];
|
|
247
249
|
const cwd = str(roots[0]) || void 0;
|
|
248
250
|
const event = helper ? mapCursorHelperEvent(eventName, p, at, sessionId) : mapCursorEvent(eventName, p, at, !!env.LEDGE_SESSION, transcript);
|
|
249
|
-
return event ? { id, event, agent: "cursor", ...cwd ? { cwd } : {} } : null;
|
|
251
|
+
return event ? { id, event: helper || event.helperId ? event : { ...event, agentSessionId: sessionId }, agent: "cursor", ...cwd ? { cwd } : {} } : null;
|
|
250
252
|
}
|
|
251
253
|
function mapCursorHelperEvent(eventName, p, at, helperId) {
|
|
252
254
|
const base = { at, source: "hook", helperId };
|
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-ZVXQ3MGS.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-GREPJUOS.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-X3LYAGKA.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-RVD4U2XH.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-ZYR3IBVM.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-PIFVOOJV.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-P6CWCJ2R.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-HR6VWBLT.js";
|
|
9
9
|
import {
|
|
10
10
|
agentEnv,
|
|
11
11
|
exitFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-3TXM4DM7.js";
|
|
13
13
|
import "./chunk-3TNPEAMR.js";
|
|
14
14
|
import "./chunk-L6IGH4LF.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-MK7NEXY7.js";
|
|
16
|
+
import "./chunk-4BBKLTDB.js";
|
|
17
17
|
export {
|
|
18
18
|
DaemonError,
|
|
19
19
|
agentEnv,
|
package/package.json
CHANGED