@paradow/ledge 0.6.0 → 0.8.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.
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  attachEnv
3
- } from "./chunk-QJG7M6BJ.js";
4
- import "./chunk-HYB7ZZN4.js";
3
+ } from "./chunk-7AKUPFMS.js";
4
+ import "./chunk-ZPANTZKC.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-Z4YH2DLP.js";
11
+ } from "./chunk-6ZKNEJND.js";
12
12
  import {
13
13
  PROTOCOL_PORT
14
- } from "./chunk-AEV7ODMZ.js";
14
+ } from "./chunk-KJWMK65Y.js";
15
15
 
16
16
  // src/cli/attach.ts
17
17
  import { spawn } from "child_process";
@@ -6,7 +6,7 @@ import {
6
6
  PROTOCOL_PORT,
7
7
  parseClientMessage,
8
8
  parseServerMessage
9
- } from "./chunk-AEV7ODMZ.js";
9
+ } from "./chunk-KJWMK65Y.js";
10
10
 
11
11
  // src/cli/bridge.ts
12
12
  import WebSocket from "ws";
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.8.0" : "0.0.0-dev";
3
+
4
+ export {
5
+ LEDGE_VERSION
6
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PROTOCOL_PORT,
3
3
  parseServerMessage
4
- } from "./chunk-AEV7ODMZ.js";
4
+ } from "./chunk-KJWMK65Y.js";
5
5
 
6
6
  // src/client.ts
7
7
  import WebSocket from "ws";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  exitFile,
3
3
  spawnSession
4
- } from "./chunk-HYB7ZZN4.js";
4
+ } from "./chunk-ZPANTZKC.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-Z4YH2DLP.js";
14
+ } from "./chunk-6ZKNEJND.js";
15
15
  import {
16
16
  PROTOCOL_PORT
17
- } from "./chunk-AEV7ODMZ.js";
17
+ } from "./chunk-KJWMK65Y.js";
18
18
 
19
19
  // src/cli/launch.ts
20
20
  import { spawn } from "child_process";
@@ -30,7 +30,9 @@ var HelperSchema = z.object({
30
30
  id: z.string(),
31
31
  kind: z.string(),
32
32
  task: z.string(),
33
- state: SessionStateSchema
33
+ state: SessionStateSchema,
34
+ /** When this helper finished; retained across daemon restarts until its five-minute expiry. */
35
+ finishedAt: z.string().datetime().optional()
34
36
  });
35
37
  var TopicSourceSchema = z.enum(["user", "pane", "prompt"]);
36
38
  var SessionSchema = z.object({
@@ -59,6 +61,8 @@ var SessionSchema = z.object({
59
61
  pending: PendingSchema.optional(),
60
62
  helpers: z.array(HelperSchema).default([]),
61
63
  lastReply: ReplySchema.optional(),
64
+ /** 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. */
65
+ lastMessage: z.object({ id: z.string(), to: z.string(), peer: z.string().optional(), summary: z.string(), at: z.string().datetime() }).optional(),
62
66
  startedAt: z.string().datetime(),
63
67
  lastActivityAt: z.string().datetime(),
64
68
  lastHookAt: z.string().datetime().optional(),
@@ -69,8 +73,15 @@ var SessionSchema = z.object({
69
73
  exitCode: z.number().int().nullable().optional(),
70
74
  exitedAt: z.string().datetime().optional(),
71
75
  lastError: z.string().optional(),
72
- source: z.enum(["launcher", "hook_only"])
76
+ source: z.enum(["launcher", "hook_only"]),
77
+ /** When the session was put in the archive (session management): set, it leaves the live list and waits to be restored, resumed or deleted. */
78
+ archivedAt: z.string().datetime().optional(),
79
+ /** When the session was last taken out of the archive: the poller's retirement clock restarts from it, not from `exitedAt`. */
80
+ restoredAt: z.string().datetime().optional()
73
81
  });
82
+ function isArchived(s) {
83
+ return s.archivedAt !== void 0;
84
+ }
74
85
 
75
86
  // ../protocol/src/chat.ts
76
87
  import { z as z2 } from "zod";
@@ -324,7 +335,11 @@ var ThreadItemSchema = z11.discriminatedUnion("kind", [
324
335
  /** `unknown`: answered in the terminal, the daemon only saw the prompt go away; `always`: allowed and not asked again. */
325
336
  z11.object({ kind: z11.literal("permission"), ...base, tool: z11.string(), text: z11.string(), decision: z11.enum(["accept", "deny", "always", "unknown"]).optional() }),
326
337
  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"]) })
338
+ z11.object({ kind: z11.literal("helper"), ...base, task: z11.string(), agentType: z11.string().optional(), state: z11.enum(["working", "exited"]) }),
339
+ /** 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. */
340
+ 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() }),
341
+ /** 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. */
342
+ z11.object({ kind: z11.literal("received"), ...base, from: z11.string(), peer: z11.string().optional(), text: z11.string(), sent: z11.string().optional() })
328
343
  ]);
329
344
 
330
345
  // ../protocol/src/decisions.ts
@@ -388,11 +403,13 @@ var SessionEventSchema = z13.object({
388
403
  /** The session's transcript, or the helper's on SubagentStop; the daemon reads its tail for the agent's messages. */
389
404
  transcriptPath: z13.string().optional(),
390
405
  /** Decided: what was chosen when the daemon knows it. */
391
- decision: DecisionSchema.optional()
406
+ decision: DecisionSchema.optional(),
407
+ /** The agent's own session id when the hook names it, so the daemon can resume that conversation. */
408
+ agentSessionId: z13.string().optional()
392
409
  });
393
410
  var RecentSchema = z13.object({ cwd: z13.string().min(1), project: z13.string().min(1), lastAt: z13.string().datetime() });
394
411
  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() });
412
+ 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
413
  var UpdateFailureSchema = z13.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
397
414
  var ServerMessageSchema = z13.discriminatedUnion("type", [
398
415
  z13.object({ type: z13.literal("chat.routed"), qid: z13.string(), result: RouteResultSchema }),
@@ -505,6 +522,14 @@ var ClientMessageSchema = z13.discriminatedUnion("type", [
505
522
  z13.object({ type: z13.literal("session.interrupt"), id: z13.string() }),
506
523
  /** Removes a finished (exited) or read-only (hook_only) session from the list; refused for a live launcher session. */
507
524
  z13.object({ type: z13.literal("session.dismiss"), id: z13.string() }),
525
+ /** Puts a session in the archive: it leaves the live list but is kept. */
526
+ z13.object({ type: z13.literal("session.archive"), id: z13.string() }),
527
+ /** Takes an archived session back out of the archive, as it was. */
528
+ z13.object({ type: z13.literal("session.restore"), id: z13.string() }),
529
+ /** Starts the agent again on an archived session's conversation. */
530
+ z13.object({ type: z13.literal("session.resume"), id: z13.string() }),
531
+ /** Deletes a session for good, archived or not. */
532
+ z13.object({ type: z13.literal("session.delete"), id: z13.string() }),
508
533
  /** 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
534
  z13.object({ type: z13.literal("session.rename"), id: z13.string(), topic: z13.string().max(500) }),
510
535
  z13.object({ type: z13.literal("session.thread"), id: z13.string() }),
@@ -673,6 +698,7 @@ function sessionTitle(s) {
673
698
 
674
699
  export {
675
700
  SessionSchema,
701
+ isArchived,
676
702
  NOTE_NOT_TYPED,
677
703
  offers,
678
704
  pendingKey,
@@ -1,7 +1,24 @@
1
1
  import {
2
2
  cleanTopic,
3
3
  sessionTitle
4
- } from "./chunk-AEV7ODMZ.js";
4
+ } from "./chunk-KJWMK65Y.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,24 @@ function withTopic(s, text, source) {
134
151
  return { ...next, title: sessionTitle(next) };
135
152
  }
136
153
  function reduce(s, ev) {
154
+ const next = reduceEvent(s, ev);
155
+ if (next === s) return s;
156
+ let changed = false;
157
+ const helpers = next.helpers.map((h) => {
158
+ if (h.state === "exited" || next.state === "exited") {
159
+ if (h.state === "exited" && h.finishedAt) return h;
160
+ changed = true;
161
+ return { ...h, state: "exited", finishedAt: h.finishedAt ?? next.exitedAt ?? ev.at };
162
+ }
163
+ if (!h.finishedAt) return h;
164
+ changed = true;
165
+ const { finishedAt: _gone, ...active } = h;
166
+ return active;
167
+ });
168
+ return changed ? { ...next, helpers } : next;
169
+ }
170
+ function reduceEvent(s, ev) {
171
+ if (ev.agentSessionId && !ev.helperId && ev.agentSessionId !== s.agentSessionId) s = { ...s, agentSessionId: ev.agentSessionId };
137
172
  if (s.state === "exited") return s;
138
173
  s = endCursorHelpers(s, ev);
139
174
  const at = ev.at;
@@ -200,7 +235,7 @@ function reduce(s, ev) {
200
235
  case "PreToolUse":
201
236
  case "PermissionRequest":
202
237
  case "Notification": {
203
- if (ev.name === "UserPromptSubmit" && ev.text && !s.topic) s = withTopic(s, ev.text, "prompt");
238
+ if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
204
239
  const incoming = pendingFor(ev);
205
240
  if (incoming) {
206
241
  if (ev.name === "Notification" && s.pending?.source === "hook") return { ...s, lastHookAt: at, lastActivityAt: at };
@@ -218,6 +253,7 @@ function reduce(s, ev) {
218
253
  }
219
254
 
220
255
  export {
256
+ crossSession,
221
257
  QUESTION_TOOLS,
222
258
  reduce
223
259
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-L6IGH4LF.js";
4
4
  import {
5
5
  pickName
6
- } from "./chunk-AEV7ODMZ.js";
6
+ } from "./chunk-KJWMK65Y.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);
@@ -1,15 +1,16 @@
1
1
  import {
2
2
  QUESTION_TOOLS,
3
+ crossSession,
3
4
  reduce
4
- } from "./chunk-VK2IBMW7.js";
5
+ } from "./chunk-Y5TCZTFL.js";
5
6
  import {
6
7
  agentMessages,
7
8
  claudePlan,
8
9
  spawnSession
9
- } from "./chunk-HYB7ZZN4.js";
10
+ } from "./chunk-ZPANTZKC.js";
10
11
  import {
11
12
  LEDGE_VERSION
12
- } from "./chunk-FFVZIXKG.js";
13
+ } from "./chunk-3KKTSYHP.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-AEV7ODMZ.js";
55
+ } from "./chunk-KJWMK65Y.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
- if (!raw.trim()) return;
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 log(`journal entry dropped: ${r.error.issues[0]?.message ?? "invalid"}`);
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
- const cur = this.sessions.get(id);
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) {
@@ -1258,6 +1341,7 @@ async function gitSummary(cwd, exec = execGit) {
1258
1341
  // src/daemon/poller.ts
1259
1342
  var DETECT_LINES = 60;
1260
1343
  var REST_MS = 2e3;
1344
+ var HELPER_RETENTION_MS = 5 * 60 * 1e3;
1261
1345
  function startPoller(opts) {
1262
1346
  const { store, tmux } = opts;
1263
1347
  const adapterFor2 = opts.adapterFor ?? adapterFor;
@@ -1289,7 +1373,9 @@ function startPoller(opts) {
1289
1373
  const deadCheck = t - lastDeadCheck >= deadCheckMs;
1290
1374
  if (deadCheck) lastDeadCheck = t;
1291
1375
  for (const id of previews.keys()) if (!store.get(id)) previews.delete(id);
1292
- for (const s of store.all()) {
1376
+ for (const snapshot of store.all()) {
1377
+ const s = store.get(snapshot.id);
1378
+ if (!s) continue;
1293
1379
  try {
1294
1380
  await pollOne(s, t, deadCheck);
1295
1381
  } catch (e) {
@@ -1301,9 +1387,22 @@ function startPoller(opts) {
1301
1387
  }
1302
1388
  }
1303
1389
  async function pollOne(s, t, deadCheck) {
1390
+ const helpers = s.helpers.flatMap((h2) => {
1391
+ if (s.pending?.helperId === h2.id || h2.state !== "exited" && s.state !== "exited") return [h2];
1392
+ const finishedAt = h2.finishedAt ?? s.exitedAt ?? s.lastActivityAt;
1393
+ if (t - Date.parse(finishedAt) >= HELPER_RETENTION_MS) return [];
1394
+ return h2.finishedAt && h2.state === "exited" ? [h2] : [{ ...h2, state: "exited", finishedAt }];
1395
+ });
1396
+ if (helpers.length !== s.helpers.length || helpers.some((h2, i) => h2 !== s.helpers[i])) {
1397
+ s = { ...s, helpers };
1398
+ store.upsert(s);
1399
+ }
1304
1400
  if (s.state === "exited") {
1305
1401
  forget(s.id);
1306
- if (s.exitedAt && t - Date.parse(s.exitedAt) > retireMs) store.remove(s.id);
1402
+ if (!s.archivedAt && s.exitedAt) {
1403
+ const since = Math.max(Date.parse(s.exitedAt), s.restoredAt ? Date.parse(s.restoredAt) : -Infinity);
1404
+ if (t - since > retireMs) store.archive(s.id, now());
1405
+ }
1307
1406
  return;
1308
1407
  }
1309
1408
  if ((deadCheck || !gitSeen.has(s.id)) && s.cwd) {
@@ -2127,7 +2226,7 @@ function createChat(deps) {
2127
2226
  }
2128
2227
  }
2129
2228
  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) });
2229
+ 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
2230
  if (mine.cancelled) return CANCELLED;
2132
2231
  const { sessionId, resume } = await daySession(fresh, tool);
2133
2232
  const label2 = TOOL_LABEL[tool];
@@ -2606,6 +2705,11 @@ function serveSsh(ws, alias, opts = {}) {
2606
2705
 
2607
2706
  // src/daemon/server.ts
2608
2707
  var CONTINUE_ARGS = { claude: ["--continue"], codex: ["resume", "--last"], cursor: ["--continue"] };
2708
+ var RESUME_ARGS = { claude: (id) => ["--resume", id], codex: (id) => ["resume", id], cursor: (id) => ["--resume", id] };
2709
+ function resumeKey(s) {
2710
+ if (s.agentSessionId) return s.agentSessionId;
2711
+ return s.tool === "claude" && s.transcriptPath ? basename4(s.transcriptPath, ".jsonl") : void 0;
2712
+ }
2609
2713
  var MULTI_DIR_ARGS = { claude: (dirs) => ["--add-dir", ...dirs] };
2610
2714
  function launchArgs(msg) {
2611
2715
  const prompt = msg.prompt?.trim();
@@ -2617,6 +2721,7 @@ var NoteNotTyped = class extends Error {
2617
2721
  super("denied, but the note could not be typed");
2618
2722
  }
2619
2723
  };
2724
+ var DELETED_TTL_MS = 5 * 60 * 1e3;
2620
2725
  var ALLOWED_ORIGINS = [
2621
2726
  "tauri://localhost",
2622
2727
  "http://tauri.localhost",
@@ -2680,7 +2785,13 @@ async function startServer(opts) {
2680
2785
  };
2681
2786
  const history = opts.history;
2682
2787
  const unsubscribeHistory = history ? [
2683
- history.onItem((id, item) => broadcast({ type: "session.thread.item", id, item })),
2788
+ history.onItem((id, item) => {
2789
+ broadcast({ type: "session.thread.item", id, item });
2790
+ const cur = store.get(id)?.lastMessage;
2791
+ if (item.kind === "sent" && item.status !== "read" && item.status !== "failed" && cur?.id !== item.id && (!cur || item.at >= cur.at)) {
2792
+ 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 } });
2793
+ }
2794
+ }),
2684
2795
  store.onEvent((id, ev) => history.record(id, ev)),
2685
2796
  store.onChange((c) => {
2686
2797
  if (c.type === "removed") history.forget(c.id);
@@ -2702,7 +2813,7 @@ async function startServer(opts) {
2702
2813
  broadcast(c.type === "updated" ? { type: "session.updated", session: c.session } : { type: "session.removed", id: c.id });
2703
2814
  });
2704
2815
  const sendSnapshot = (ws) => {
2705
- send(ws, { type: "snapshot", sessions: store.all(), recents: store.recents(), launcher, ...opts.daemon ? { daemon: { ...opts.daemon, routing: true } } : {} });
2816
+ send(ws, { type: "snapshot", sessions: store.all(), recents: store.recents(), launcher, ...opts.daemon ? { daemon: { ...opts.daemon, routing: true, archive: true } } : {} });
2706
2817
  send(ws, machinesMsg());
2707
2818
  send(ws, projectsMsg());
2708
2819
  send(ws, islandMsg());
@@ -2791,6 +2902,11 @@ async function startServer(opts) {
2791
2902
  const NOT_TAKEN = "the terminal did not take it";
2792
2903
  const FREE_TEXT = "free-text answers are not supported yet, answer in the terminal";
2793
2904
  const NOT_OFFERED = "this answer is not offered for this request, answer in the terminal";
2905
+ async function endSession(s) {
2906
+ if (s.source !== "launcher" || s.state === "exited") return;
2907
+ if (s.tmuxSession) await tmux.killSession(s.tmuxSession).catch((e) => log(`kill of ${s.tmuxSession} failed: ${e.message}`));
2908
+ store.apply(s.id, { name: "PaneGone", at: now(), source: "daemon" });
2909
+ }
2794
2910
  async function replyTo(id, text) {
2795
2911
  const s = store.get(id);
2796
2912
  if (!s) return "unknown session";
@@ -2803,6 +2919,19 @@ async function startServer(opts) {
2803
2919
  await injectAndVerify(id, (pane) => adapter.replyPlan(text, pending, pane || void 0), !!pending);
2804
2920
  return null;
2805
2921
  }
2922
+ const deleted = /* @__PURE__ */ new Map();
2923
+ function markDeleted(id) {
2924
+ const t = Date.now();
2925
+ for (const [k, at] of deleted) if (t - at > DELETED_TTL_MS) deleted.delete(k);
2926
+ deleted.set(id, t);
2927
+ }
2928
+ function recentlyDeleted(id) {
2929
+ const at = deleted.get(id);
2930
+ if (at === void 0) return false;
2931
+ if (Date.now() - at <= DELETED_TTL_MS) return true;
2932
+ deleted.delete(id);
2933
+ return false;
2934
+ }
2806
2935
  const chatLaunches = /* @__PURE__ */ new Set();
2807
2936
  async function launch(req) {
2808
2937
  if (req.dirs?.length && !MULTI_DIR_ARGS[req.tool]) throw new Error(`launch: ${req.tool} takes one folder`);
@@ -2917,6 +3046,7 @@ async function startServer(opts) {
2917
3046
  }
2918
3047
  case "session.event": {
2919
3048
  const ev = msg.event;
3049
+ if (recentlyDeleted(msg.id)) return;
2920
3050
  const ownTurn = !ev.helperId && (ev.name === "PreToolUse" || ev.name === "PermissionRequest" || ev.name === "Stop");
2921
3051
  const helperEnd = ev.name === "SubagentStop" && !!ev.helperId;
2922
3052
  if (history && ev.transcriptPath && (ownTurn || helperEnd)) {
@@ -2926,7 +3056,11 @@ async function startServer(opts) {
2926
3056
  log(`transcript not read for ${msg.id}: ${e.message}`);
2927
3057
  }
2928
3058
  }
3059
+ const terminal = ev.name === "SessionEnd" || ev.name === "PaneGone";
3060
+ const stored = store.get(msg.id);
3061
+ if (stored?.archivedAt && stored.state !== "exited" && !terminal) store.unarchive(msg.id);
2929
3062
  const before = store.get(msg.id);
3063
+ if (!before && terminal) return;
2930
3064
  if (!before) {
2931
3065
  const taken = store.all().map((s) => s.name);
2932
3066
  const cwd = msg.cwd ?? "";
@@ -2988,6 +3122,54 @@ async function startServer(opts) {
2988
3122
  store.remove(msg.id);
2989
3123
  return;
2990
3124
  }
3125
+ case "session.archive": {
3126
+ const s = store.get(msg.id);
3127
+ if (!s) return refuse("unknown session", msg.id);
3128
+ await endSession(s);
3129
+ store.archive(msg.id, now());
3130
+ return;
3131
+ }
3132
+ case "session.restore": {
3133
+ if (!store.get(msg.id)) return refuse("unknown session", msg.id);
3134
+ store.unarchive(msg.id, now());
3135
+ return;
3136
+ }
3137
+ case "session.resume": {
3138
+ const s = store.get(msg.id);
3139
+ if (!s) return refuse("unknown session", msg.id);
3140
+ if (s.state !== "exited") return refuse("the session is still running", msg.id);
3141
+ const key = resumeKey(s);
3142
+ if (!key) return refuse("no conversation to resume", msg.id);
3143
+ const { archivedAt: _a, exitedAt: _e, exitCode: _c, ...rest } = s;
3144
+ store.upsert({ ...rest, state: "starting", source: "launcher", agentSessionId: key });
3145
+ try {
3146
+ const cwd = await resolveFolder(s.cwd, opts.home);
3147
+ const env = await agentEnv();
3148
+ 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: [] } });
3149
+ const cur = store.get(s.id);
3150
+ if (!cur || cur.archivedAt !== void 0) {
3151
+ if (spawned.tmuxSession) await tmux.killSession(spawned.tmuxSession).catch((e) => log(`kill of ${spawned.tmuxSession} failed: ${e.message}`));
3152
+ log(`resume of ${s.id} dropped: the session was ${cur ? "archived" : "deleted"} meanwhile`);
3153
+ return;
3154
+ }
3155
+ const { topic, topicSource, repo, repoPath, branch, worktree, repoId, project, transcriptPath } = s;
3156
+ store.upsert({ ...spawned, agentSessionId: key, ...transcriptPath ? { transcriptPath } : {}, ...topic ? { topic } : {}, ...topicSource ? { topicSource } : {}, ...repo ? { repo } : {}, ...repoPath ? { repoPath } : {}, ...branch ? { branch } : {}, ...worktree !== void 0 ? { worktree } : {}, ...repoId ? { repoId } : {}, project });
3157
+ log(`resumed ${spawned.name} (${spawned.tool}) in ${spawned.cwd} as ${spawned.id}`);
3158
+ } catch (e) {
3159
+ const cur = store.get(s.id);
3160
+ if (cur && cur.archivedAt === void 0 && cur.state === "starting") store.upsert(s);
3161
+ return refuse(e.message, msg.id);
3162
+ }
3163
+ return;
3164
+ }
3165
+ case "session.delete": {
3166
+ const s = store.get(msg.id);
3167
+ if (!s) return refuse("unknown session", msg.id);
3168
+ if (s.source === "launcher" || s.state === "exited") markDeleted(msg.id);
3169
+ await endSession(s);
3170
+ store.remove(msg.id);
3171
+ return;
3172
+ }
2991
3173
  // A chat card's instruction: a refusal reaches the requester as an `error`, the card's state everyone through chat.history.
2992
3174
  case "session.relay":
2993
3175
  return relay.request(msg.qid);
@@ -3166,11 +3348,49 @@ ${output}` } : { code: r.code, output };
3166
3348
  };
3167
3349
  }
3168
3350
 
3351
+ // src/daemon/peers.ts
3352
+ import { readdirSync, readFileSync as readFileSync4 } from "fs";
3353
+ import { homedir as homedir6 } from "os";
3354
+ import { basename as basename5, join as join10 } from "path";
3355
+ var defaultDir = () => join10(process.env.CLAUDE_CONFIG_DIR ?? join10(homedir6(), ".claude"), "sessions");
3356
+ function parsePeer(path) {
3357
+ try {
3358
+ const j = JSON.parse(readFileSync4(path, "utf8"));
3359
+ if (typeof j.pid !== "number" || typeof j.sessionId !== "string" || typeof j.name !== "string" || typeof j.messagingSocketPath !== "string") return void 0;
3360
+ return { pid: j.pid, sessionId: j.sessionId, name: j.name, address: `uds:${j.messagingSocketPath}` };
3361
+ } catch {
3362
+ return void 0;
3363
+ }
3364
+ }
3365
+ function readPeers(dir = defaultDir()) {
3366
+ let names;
3367
+ try {
3368
+ names = readdirSync(dir);
3369
+ } catch {
3370
+ return [];
3371
+ }
3372
+ return names.filter((n) => n.endsWith(".json")).flatMap((n) => parsePeer(join10(dir, n)) ?? []);
3373
+ }
3374
+ function peerFor(target, peers, sessions, dir = defaultDir()) {
3375
+ const name = target.replace(/ \[[^\]]*\]$/, "");
3376
+ const found = peers.filter((p) => p.name === name || p.address === name);
3377
+ if (!found.length && name.startsWith("uds:") && name.endsWith(".sock")) {
3378
+ const pid = basename5(name, ".sock");
3379
+ const direct = /^\d+$/.test(pid) ? parsePeer(join10(dir, `${pid}.json`)) : void 0;
3380
+ if (direct) found.push(direct);
3381
+ }
3382
+ for (const { sessionId: id } of found) {
3383
+ const s = sessions.find((s2) => s2.agentSessionId === id || s2.transcriptPath?.endsWith(`/${id}.jsonl`));
3384
+ if (s) return s;
3385
+ }
3386
+ return void 0;
3387
+ }
3388
+
3169
3389
  // src/daemon/index.ts
3170
3390
  async function startDaemon(opts = {}) {
3171
3391
  await ensureLedgeDir();
3172
3392
  const store = new Store();
3173
- await store.load();
3393
+ const loaded = await store.load();
3174
3394
  const tmux = opts.tmux ?? realTmux(opts.tmuxSocket ?? "ledge");
3175
3395
  let live2 = null;
3176
3396
  try {
@@ -3179,17 +3399,19 @@ async function startDaemon(opts = {}) {
3179
3399
  log(`startup prune skipped, tmux unavailable: ${e.message}`);
3180
3400
  }
3181
3401
  if (live2) for (const s of store.all()) {
3182
- if (s.tmuxSession && !live2.has(s.tmuxSession)) store.remove(s.id);
3402
+ if (s.tmuxSession && !live2.has(s.tmuxSession)) store.apply(s.id, { name: "PaneGone", at: (/* @__PURE__ */ new Date()).toISOString(), source: "daemon" });
3183
3403
  }
3184
3404
  const daemon = await daemonInfo();
3185
3405
  const updater = createDaemonUpdater(realUpdaterDeps({ info: daemon, save: () => store.save() }));
3186
- const history = new History();
3406
+ const history = new History({ peerOf: (t) => peerFor(t, readPeers(), store.all())?.id });
3187
3407
  try {
3188
3408
  await history.load();
3189
3409
  } catch (e) {
3190
3410
  log(`history not loaded: ${e.message}`);
3191
3411
  }
3192
- for (const id of history.ids()) if (!store.get(id)) history.forget(id);
3412
+ if (loaded.clean) {
3413
+ for (const id of history.ids()) if (!store.get(id)) history.forget(id);
3414
+ } else log(`history cleanup skipped: ${loaded.reason}`);
3193
3415
  const server = await startServer({ store, tmux, port: opts.port, daemon, updater, history });
3194
3416
  const poller = startPoller({ store, tmux, broadcastPreview: server.preview });
3195
3417
  await writeFile5(paths().pid, String(process.pid));
@@ -1,33 +1,34 @@
1
1
  import {
2
2
  QUESTION_TOOLS
3
- } from "./chunk-VK2IBMW7.js";
3
+ } from "./chunk-Y5TCZTFL.js";
4
4
  import {
5
5
  CODEX_ENV_VAR,
6
6
  CODEX_HOOK_VAR,
7
7
  codexPlan
8
- } from "./chunk-HYB7ZZN4.js";
8
+ } from "./chunk-ZPANTZKC.js";
9
9
  import {
10
10
  log
11
11
  } from "./chunk-L6IGH4LF.js";
12
12
  import {
13
13
  connect
14
- } from "./chunk-Z4YH2DLP.js";
15
- import "./chunk-AEV7ODMZ.js";
14
+ } from "./chunk-6ZKNEJND.js";
15
+ import "./chunk-KJWMK65Y.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 handBack = (t) => t.startsWith('<agent-message from="');
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, handBack(t) ? INPUT_MAX : PROMPT_MAX) } : {};
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
- return { id, event: task ? { ...event, task } : event, agent, ...cwd ? { cwd } : {} };
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-CT5OKGHQ.js");
19
+ const { runHook } = await import("./hook-BBVUY4RK.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-SKI3UKOJ.js");
29
+ const { startDaemon } = await import("./daemon-SRNTDIZW.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-3CHJ23IV.js");
37
+ const { LEDGE_VERSION } = await import("./version-M6IZZGUV.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-TACDR6DZ.js");
53
+ const { printStatus } = await import("./status-L2GROM4J.js");
54
54
  console.log(await printStatus());
55
55
  return 0;
56
56
  }
57
57
  case "bridge": {
58
- const { bridge } = await import("./bridge-AFAK3FU5.js");
58
+ const { bridge } = await import("./bridge-QKQW7UD6.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-LVMFX3KF.js");
65
+ const { launch } = await import("./launch-RWT4EWWI.js");
66
66
  return launch(cmd, rest);
67
67
  }
68
68
  case "attach": {
69
- const { attach } = await import("./attach-FDMNVXYR.js");
69
+ const { attach } = await import("./attach-KTZOG7E5.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-QJG7M6BJ.js";
8
+ } from "./chunk-7AKUPFMS.js";
9
9
  import {
10
10
  agentEnv,
11
11
  exitFile
12
- } from "./chunk-HYB7ZZN4.js";
12
+ } from "./chunk-ZPANTZKC.js";
13
13
  import "./chunk-3TNPEAMR.js";
14
14
  import "./chunk-L6IGH4LF.js";
15
- import "./chunk-Z4YH2DLP.js";
16
- import "./chunk-AEV7ODMZ.js";
15
+ import "./chunk-6ZKNEJND.js";
16
+ import "./chunk-KJWMK65Y.js";
17
17
  export {
18
18
  DaemonError,
19
19
  agentEnv,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-Z4YH2DLP.js";
4
- import "./chunk-AEV7ODMZ.js";
3
+ } from "./chunk-6ZKNEJND.js";
4
+ import "./chunk-KJWMK65Y.js";
5
5
 
6
6
  // src/cli/status.ts
7
7
  async function printStatus(opts = {}) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LEDGE_VERSION
3
- } from "./chunk-FFVZIXKG.js";
3
+ } from "./chunk-3KKTSYHP.js";
4
4
  export {
5
5
  LEDGE_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paradow/ledge",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Follow and answer your WSL agent sessions (Claude Code, Codex, Cursor Agent) from the Ledge island on Windows.",
5
5
  "type": "module",
6
6
  "bin": { "ledge": "dist/cli/index.js" },
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var LEDGE_VERSION = true ? "0.6.0" : "0.0.0-dev";
3
-
4
- export {
5
- LEDGE_VERSION
6
- };