@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.
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  attachEnv
3
- } from "./chunk-QJG7M6BJ.js";
4
- import "./chunk-HYB7ZZN4.js";
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-Z4YH2DLP.js";
11
+ } from "./chunk-MK7NEXY7.js";
12
12
  import {
13
13
  PROTOCOL_PORT
14
- } from "./chunk-AEV7ODMZ.js";
14
+ } from "./chunk-4BBKLTDB.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-4BBKLTDB.js";
10
10
 
11
11
  // src/cli/bridge.ts
12
12
  import WebSocket from "ws";
@@ -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-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-AEV7ODMZ.js";
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-HYB7ZZN4.js";
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-Z4YH2DLP.js";
14
+ } from "./chunk-MK7NEXY7.js";
15
15
  import {
16
16
  PROTOCOL_PORT
17
- } from "./chunk-AEV7ODMZ.js";
17
+ } from "./chunk-4BBKLTDB.js";
18
18
 
19
19
  // src/cli/launch.ts
20
20
  import { spawn } from "child_process";
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.7.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-4BBKLTDB.js";
5
5
 
6
6
  // src/client.ts
7
7
  import WebSocket from "ws";
@@ -1,15 +1,16 @@
1
1
  import {
2
2
  QUESTION_TOOLS,
3
+ crossSession,
3
4
  reduce
4
- } from "./chunk-VK2IBMW7.js";
5
+ } from "./chunk-GZFSUQZJ.js";
5
6
  import {
6
7
  agentMessages,
7
8
  claudePlan,
8
9
  spawnSession
9
- } from "./chunk-HYB7ZZN4.js";
10
+ } from "./chunk-3TXM4DM7.js";
10
11
  import {
11
12
  LEDGE_VERSION
12
- } from "./chunk-FFVZIXKG.js";
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-AEV7ODMZ.js";
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
- 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) {
@@ -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.exitedAt && t - Date.parse(s.exitedAt) > retireMs) store.remove(s.id);
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) => broadcast({ type: "session.thread.item", 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.remove(s.id);
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
- for (const id of history.ids()) if (!store.get(id)) history.forget(id);
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-VK2IBMW7.js";
3
+ } from "./chunk-GZFSUQZJ.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-3TXM4DM7.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-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 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-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-SKI3UKOJ.js");
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-3CHJ23IV.js");
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-TACDR6DZ.js");
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-AFAK3FU5.js");
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-LVMFX3KF.js");
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-FDMNVXYR.js");
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-QJG7M6BJ.js";
8
+ } from "./chunk-HR6VWBLT.js";
9
9
  import {
10
10
  agentEnv,
11
11
  exitFile
12
- } from "./chunk-HYB7ZZN4.js";
12
+ } from "./chunk-3TXM4DM7.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-MK7NEXY7.js";
16
+ import "./chunk-4BBKLTDB.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-MK7NEXY7.js";
4
+ import "./chunk-4BBKLTDB.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-KD7LKXIF.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.7.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
- };