@paradow/ledge 0.16.1 → 0.17.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.
Files changed (32) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/{attach-5H727FCW.js → attach-EYIX7FZN.js} +7 -7
  3. package/dist/cli/{bridge-LXMOQN6Q.js → bridge-6UHCD76N.js} +2 -2
  4. package/dist/cli/{chunk-MQICEVKR.js → chunk-36KJUV62.js} +6 -6
  5. package/dist/cli/chunk-5ZCHFGCV.js +115 -0
  6. package/dist/cli/{chunk-QJVODYJ5.js → chunk-AQ25HFBP.js} +40 -2
  7. package/dist/cli/{chunk-R5462SQM.js → chunk-ELLXOV4Q.js} +2 -1
  8. package/dist/cli/{chunk-BNKPXSUY.js → chunk-JQZHXVIZ.js} +1 -1
  9. package/dist/cli/{chunk-2H5FJH4Q.js → chunk-LAUNHDTX.js} +2 -2
  10. package/dist/cli/{chunk-DBSCO3HI.js → chunk-NC5YAEJB.js} +56 -7
  11. package/dist/cli/{chunk-S4ANPQGA.js → chunk-OVJL2QTN.js} +2 -2
  12. package/dist/cli/{chunk-TB47EHML.js → chunk-Q7YNLP66.js} +2 -2
  13. package/dist/cli/{chunk-Q5WWH7FG.js → chunk-QD27W6W5.js} +681 -630
  14. package/dist/cli/chunk-V2H7UD2N.js +6 -0
  15. package/dist/cli/{chunk-H36JD4E3.js → chunk-VE4LGNXX.js} +2 -2
  16. package/dist/cli/{chunk-DEXDXDO4.js → chunk-VVK4I6B5.js} +1 -1
  17. package/dist/cli/{daemon-DKH63BGV.js → daemon-QEJLAIBT.js} +355 -40
  18. package/dist/cli/{hook-LY2YTK5U.js → hook-AE5HC6AS.js} +18 -15
  19. package/dist/cli/index.js +30 -11
  20. package/dist/cli/{launch-UQEFFPBN.js → launch-B4G5OFBV.js} +7 -7
  21. package/dist/cli/send-2OZ3YZRV.js +18 -0
  22. package/dist/cli/{setup-3N2EOJXS.js → setup-2VM4GCDJ.js} +4 -4
  23. package/dist/cli/{status-ZQSYXILE.js → status-3VJAO6UL.js} +3 -3
  24. package/dist/cli/task-PMKNYP6M.js +18 -0
  25. package/dist/cli/tui-DWA2EDC2.js +3561 -0
  26. package/dist/cli/{uninstall-W2OMKU3B.js → uninstall-AKUWE33D.js} +4 -4
  27. package/dist/cli/{version-6BMBIXHN.js → version-WOGCIZY3.js} +1 -1
  28. package/dist/cli/who-37TNDYVA.js +11 -0
  29. package/package.json +7 -1
  30. package/dist/cli/chunk-J4ICZEGP.js +0 -6
  31. package/dist/cli/send-EY63XQXI.js +0 -14
  32. package/dist/cli/who-VGIMGQNS.js +0 -11
@@ -1,6 +1,125 @@
1
1
  import {
2
2
  paths
3
- } from "./chunk-R5462SQM.js";
3
+ } from "./chunk-ELLXOV4Q.js";
4
+
5
+ // src/auth.ts
6
+ import { createHash, randomBytes, timingSafeEqual } from "crypto";
7
+ import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
8
+ import { readFileSync } from "fs";
9
+ import { dirname } from "path";
10
+ var TOKEN_RE = /^[0-9a-f]{64}$/;
11
+ var DAEMON_PROTOCOL_PREFIX = "ledge.";
12
+ function isDaemonToken(value) {
13
+ return TOKEN_RE.test(value);
14
+ }
15
+ function tokenEquals(presented, expected) {
16
+ const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
17
+ const b = createHash("sha256").update(expected, "utf8").digest();
18
+ return timingSafeEqual(a, b);
19
+ }
20
+ function hostAllowed(hostHeader) {
21
+ const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
22
+ if (!raw || raw.length > 255) return false;
23
+ const host = raw.trim().toLowerCase();
24
+ if (!host || host.includes(" ") || host.endsWith(".")) return false;
25
+ let name;
26
+ if (host.startsWith("[")) {
27
+ const end = host.indexOf("]");
28
+ if (end < 0) return false;
29
+ name = host.slice(1, end);
30
+ const rest = host.slice(end + 1);
31
+ if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
32
+ } else {
33
+ const colon = host.lastIndexOf(":");
34
+ if (colon >= 0) {
35
+ if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
36
+ name = host.slice(0, colon);
37
+ } else name = host;
38
+ }
39
+ return name === "localhost" || name === "127.0.0.1" || name === "::1";
40
+ }
41
+ function headerValue(value, max) {
42
+ const raw = Array.isArray(value) ? value.join(",") : value;
43
+ return !raw || raw.length > max ? "" : raw;
44
+ }
45
+ function bearerToken(authorization) {
46
+ if (!authorization || authorization.length > 512) return "";
47
+ return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
48
+ }
49
+ function protocolToken(header) {
50
+ const raw = headerValue(header, 512);
51
+ if (!raw) return "";
52
+ for (const part of raw.split(",").slice(0, 8)) {
53
+ const protocol = part.trim();
54
+ if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
55
+ }
56
+ return "";
57
+ }
58
+ function requestAuthorized(headers, expected) {
59
+ const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
60
+ const presented = protocolToken(headers["sec-websocket-protocol"]);
61
+ return bearerOk || presented !== "" && tokenEquals(presented, expected);
62
+ }
63
+ function selectDaemonProtocol(protocols, expected) {
64
+ for (const protocol of protocols) {
65
+ if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
66
+ }
67
+ return false;
68
+ }
69
+ async function readTokenFile(file) {
70
+ try {
71
+ const raw = (await readFile(file, "utf8")).trim();
72
+ return isDaemonToken(raw) ? raw : void 0;
73
+ } catch (e) {
74
+ if (e.code === "ENOENT") return void 0;
75
+ throw e;
76
+ }
77
+ }
78
+ async function lockTokenFile(file) {
79
+ await chmod(file, 384);
80
+ }
81
+ async function ensureDaemonToken() {
82
+ const file = paths().token;
83
+ await mkdir(dirname(file), { recursive: true });
84
+ const current = await readTokenFile(file);
85
+ if (current) {
86
+ await lockTokenFile(file);
87
+ return current;
88
+ }
89
+ const token = randomBytes(32).toString("hex");
90
+ const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
91
+ await writeFile(tmp, `${token}
92
+ `, { mode: 384, flag: "wx" });
93
+ await chmod(tmp, 384);
94
+ try {
95
+ await link(tmp, file);
96
+ } catch (e) {
97
+ await unlink(tmp).catch(() => {
98
+ });
99
+ if (e.code !== "EEXIST") throw e;
100
+ const raced = await readTokenFile(file);
101
+ if (raced) {
102
+ await lockTokenFile(file);
103
+ return raced;
104
+ }
105
+ await writeFile(file, `${token}
106
+ `, { mode: 384 });
107
+ }
108
+ await unlink(tmp).catch(() => {
109
+ });
110
+ await lockTokenFile(file);
111
+ const stored = await readTokenFile(file);
112
+ if (!stored) throw new Error("daemon token was not stored");
113
+ return stored;
114
+ }
115
+ function readDaemonToken() {
116
+ try {
117
+ const raw = readFileSync(paths().token, "utf8").trim();
118
+ return isDaemonToken(raw) ? raw : void 0;
119
+ } catch {
120
+ return void 0;
121
+ }
122
+ }
4
123
 
5
124
  // ../protocol/src/session.ts
6
125
  import { z } from "zod";
@@ -115,62 +234,100 @@ function isArchived(s) {
115
234
  return s.archivedAt !== void 0;
116
235
  }
117
236
 
237
+ // ../protocol/src/tasks.ts
238
+ import { z as z2 } from "zod";
239
+ var TASK_TITLE_MAX = 200;
240
+ var TASK_TEXT_MAX = 2e4;
241
+ var TaskSchema = z2.object({
242
+ id: z2.string(),
243
+ title: z2.string(),
244
+ text: z2.string().optional(),
245
+ giver: z2.string(),
246
+ giverName: z2.string(),
247
+ owner: z2.string(),
248
+ ownerName: z2.string(),
249
+ state: z2.enum(["open", "taken", "done", "refused", "dropped"]),
250
+ result: z2.string().optional(),
251
+ at: z2.string(),
252
+ updatedAt: z2.string(),
253
+ log: z2.array(z2.object({ at: z2.string(), by: z2.string(), what: z2.string() }))
254
+ });
255
+ var taskClosed = (t) => t.state === "done" || t.state === "refused" || t.state === "dropped";
256
+ var TaskCmdSchema = z2.object({
257
+ type: z2.literal("task.cmd"),
258
+ rid: z2.string().min(1),
259
+ by: z2.string().min(1),
260
+ op: z2.enum(["give", "take", "done", "refuse", "pass", "show", "list"]),
261
+ task: z2.string().min(1).max(40).optional(),
262
+ to: z2.string().min(1).optional(),
263
+ title: z2.string().min(1).max(TASK_TITLE_MAX).optional(),
264
+ text: z2.string().max(TASK_TEXT_MAX).optional(),
265
+ all: z2.boolean().optional()
266
+ });
267
+ var TaskResultSchema = z2.object({
268
+ type: z2.literal("task.result"),
269
+ rid: z2.string(),
270
+ task: TaskSchema.optional(),
271
+ tasks: z2.array(TaskSchema).optional(),
272
+ reason: z2.string().optional()
273
+ });
274
+
118
275
  // ../protocol/src/messages.ts
119
- import { z as z15 } from "zod";
276
+ import { z as z16 } from "zod";
120
277
 
121
278
  // ../protocol/src/chat.ts
122
- import { z as z2 } from "zod";
123
- var ChatStepSchema = z2.object({
124
- kind: z2.enum(["sessions", "pane", "reply", "git", "repo"]),
125
- id: z2.string().optional(),
126
- name: z2.string().optional(),
127
- path: z2.string().optional()
279
+ import { z as z3 } from "zod";
280
+ var ChatStepSchema = z3.object({
281
+ kind: z3.enum(["sessions", "pane", "reply", "git", "repo"]),
282
+ id: z3.string().optional(),
283
+ name: z3.string().optional(),
284
+ path: z3.string().optional()
128
285
  });
129
- var ChatProposalSchema = z2.object({
130
- cwd: z2.string().min(1),
131
- project: z2.string(),
132
- prompt: z2.string().optional(),
286
+ var ChatProposalSchema = z3.object({
287
+ cwd: z3.string().min(1),
288
+ project: z3.string(),
289
+ prompt: z3.string().optional(),
133
290
  tool: ToolSchema,
134
- launchedId: z2.string().optional(),
135
- error: z2.string().optional()
291
+ launchedId: z3.string().optional(),
292
+ error: z3.string().optional()
136
293
  });
137
294
  var MAX_PROPOSALS = 8;
138
- var ChatProposalsSchema = z2.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
139
- var ChatRelaySchema = z2.object({
140
- id: z2.string().min(1),
141
- name: z2.string(),
295
+ var ChatProposalsSchema = z3.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
296
+ var ChatRelaySchema = z3.object({
297
+ id: z3.string().min(1),
298
+ name: z3.string(),
142
299
  tool: ToolSchema,
143
- text: z2.string().min(1),
144
- status: z2.enum(["queued", "sent"]).optional(),
145
- sentAt: z2.string().datetime().optional(),
146
- error: z2.string().optional()
300
+ text: z3.string().min(1),
301
+ status: z3.enum(["queued", "sent"]).optional(),
302
+ sentAt: z3.string().datetime().optional(),
303
+ error: z3.string().optional()
147
304
  });
148
- var ChatCloseSchema = z2.object({
149
- id: z2.string().min(1),
150
- name: z2.string(),
305
+ var ChatCloseSchema = z3.object({
306
+ id: z3.string().min(1),
307
+ name: z3.string(),
151
308
  tool: ToolSchema,
152
- closedAt: z2.string().datetime().optional(),
153
- how: z2.enum(["exit", "killed"]).optional(),
154
- error: z2.string().optional()
309
+ closedAt: z3.string().datetime().optional(),
310
+ how: z3.enum(["exit", "killed"]).optional(),
311
+ error: z3.string().optional()
155
312
  });
156
- var ChatClosesSchema = z2.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
157
- var ChatProjectProposalSchema = z2.object({
158
- name: z2.string().min(1).max(80),
159
- description: z2.string().max(500).optional(),
160
- repos: z2.array(z2.object({ path: z2.string().min(1).max(4096), why: z2.string().min(1).max(200) })).min(1).max(50),
161
- addTo: z2.string().min(1).max(80).optional()
313
+ var ChatClosesSchema = z3.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
314
+ var ChatProjectProposalSchema = z3.object({
315
+ name: z3.string().min(1).max(80),
316
+ description: z3.string().max(500).optional(),
317
+ repos: z3.array(z3.object({ path: z3.string().min(1).max(4096), why: z3.string().min(1).max(200) })).min(1).max(50),
318
+ addTo: z3.string().min(1).max(80).optional()
162
319
  });
163
- var ChatTurnSchema = z2.object({
164
- machineId: z2.string().optional(),
165
- machineName: z2.string().optional(),
166
- choices: z2.array(z2.object({ id: z2.string(), label: z2.string() })).optional(),
167
- qid: z2.string().min(1),
168
- question: z2.string(),
169
- askedAt: z2.string().datetime(),
170
- steps: z2.array(ChatStepSchema),
171
- answer: z2.string().optional(),
172
- answeredAt: z2.string().datetime().optional(),
173
- error: z2.string().optional(),
320
+ var ChatTurnSchema = z3.object({
321
+ machineId: z3.string().optional(),
322
+ machineName: z3.string().optional(),
323
+ choices: z3.array(z3.object({ id: z3.string(), label: z3.string() })).optional(),
324
+ qid: z3.string().min(1),
325
+ question: z3.string(),
326
+ askedAt: z3.string().datetime(),
327
+ steps: z3.array(ChatStepSchema),
328
+ answer: z3.string().optional(),
329
+ answeredAt: z3.string().datetime().optional(),
330
+ error: z3.string().optional(),
174
331
  proposal: ChatProposalSchema.optional(),
175
332
  proposals: ChatProposalsSchema.optional(),
176
333
  relay: ChatRelaySchema.optional(),
@@ -180,70 +337,70 @@ var ChatTurnSchema = z2.object({
180
337
  var proposalRows = (t) => t?.proposals ?? (t?.proposal ? [t.proposal] : []);
181
338
 
182
339
  // ../protocol/src/repos.ts
183
- import { z as z3 } from "zod";
184
- var ReposInspectSchema = z3.object({
185
- type: z3.literal("repos.inspect"),
186
- requestId: z3.string().min(1).max(100),
187
- paths: z3.array(z3.string().min(1)).min(1).max(50),
188
- machineId: z3.string().optional()
340
+ import { z as z4 } from "zod";
341
+ var ReposInspectSchema = z4.object({
342
+ type: z4.literal("repos.inspect"),
343
+ requestId: z4.string().min(1).max(100),
344
+ paths: z4.array(z4.string().min(1)).min(1).max(50),
345
+ machineId: z4.string().optional()
189
346
  });
190
- var RepoInfoSchema = z3.object({ path: z3.string(), exists: z3.boolean(), branch: z3.string().nullable() });
191
- var ReposInfoSchema = z3.object({ type: z3.literal("repos.info"), requestId: z3.string(), repos: z3.array(RepoInfoSchema) });
347
+ var RepoInfoSchema = z4.object({ path: z4.string(), exists: z4.boolean(), branch: z4.string().nullable() });
348
+ var ReposInfoSchema = z4.object({ type: z4.literal("repos.info"), requestId: z4.string(), repos: z4.array(RepoInfoSchema) });
192
349
 
193
350
  // ../protocol/src/models.ts
194
- import { z as z4 } from "zod";
195
- var ModelIdSchema = z4.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
196
- var EffortSchema = z4.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
197
- var ModelChoiceSchema = z4.object({
351
+ import { z as z5 } from "zod";
352
+ var ModelIdSchema = z5.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
353
+ var EffortSchema = z5.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
354
+ var ModelChoiceSchema = z5.object({
198
355
  id: ModelIdSchema,
199
- label: z4.string(),
200
- description: z4.string().optional(),
201
- efforts: z4.array(EffortSchema).optional(),
356
+ label: z5.string(),
357
+ description: z5.string().optional(),
358
+ efforts: z5.array(EffortSchema).optional(),
202
359
  defaultEffort: EffortSchema.optional(),
203
- isDefault: z4.boolean().optional()
360
+ isDefault: z5.boolean().optional()
204
361
  });
205
- var ModelsListSchema = z4.object({
206
- type: z4.literal("models.list"),
207
- requestId: z4.string().min(1).max(100),
362
+ var ModelsListSchema = z5.object({
363
+ type: z5.literal("models.list"),
364
+ requestId: z5.string().min(1).max(100),
208
365
  tool: ToolSchema,
209
- machineId: z4.string().optional()
366
+ machineId: z5.string().optional()
210
367
  });
211
- var ModelsListingSchema = z4.object({
212
- type: z4.literal("models.listing"),
213
- requestId: z4.string(),
368
+ var ModelsListingSchema = z5.object({
369
+ type: z5.literal("models.listing"),
370
+ requestId: z5.string(),
214
371
  tool: ToolSchema,
215
- models: z4.array(ModelChoiceSchema),
216
- error: z4.string().optional()
372
+ models: z5.array(ModelChoiceSchema),
373
+ error: z5.string().optional()
217
374
  });
218
375
 
219
376
  // ../protocol/src/scan.ts
220
- import { z as z5 } from "zod";
221
- var ReposScanSchema = z5.object({ type: z5.literal("repos.scan"), requestId: z5.string().min(1).max(100), machineId: z5.string().optional() });
222
- var FoundRepoSchema = z5.object({ path: z5.string(), branch: z5.string().nullable(), remote: z5.string().optional(), manifest: z5.string().max(200).optional() });
223
- var ReposFoundSchema = z5.object({ type: z5.literal("repos.found"), requestId: z5.string(), repos: z5.array(FoundRepoSchema).max(500), truncated: z5.boolean(), error: z5.string().optional() });
377
+ import { z as z6 } from "zod";
378
+ var ReposScanSchema = z6.object({ type: z6.literal("repos.scan"), requestId: z6.string().min(1).max(100), machineId: z6.string().optional() });
379
+ var FoundRepoSchema = z6.object({ path: z6.string(), branch: z6.string().nullable(), remote: z6.string().optional(), manifest: z6.string().max(200).optional() });
380
+ var ReposFoundSchema = z6.object({ type: z6.literal("repos.found"), requestId: z6.string(), repos: z6.array(FoundRepoSchema).max(500), truncated: z6.boolean(), error: z6.string().optional() });
224
381
 
225
382
  // ../protocol/src/documents.ts
226
- import { z as z6 } from "zod";
383
+ import { z as z7 } from "zod";
227
384
  var DOCUMENT_MAX_BYTES = 1048576;
228
- var path = z6.string().min(1).max(4096);
229
- var requestId = z6.string().min(1);
230
- var DocumentReadSchema = z6.object({
231
- type: z6.literal("document.read"),
385
+ var path = z7.string().min(1).max(4096);
386
+ var requestId = z7.string().min(1);
387
+ var DocumentReadSchema = z7.object({
388
+ type: z7.literal("document.read"),
232
389
  requestId,
233
390
  path,
234
- machineId: z6.string().optional(),
235
- sessionId: z6.string().min(1).optional(),
391
+ machineId: z7.string().optional(),
392
+ sessionId: z7.string().min(1).optional(),
236
393
  basePath: path.optional()
237
394
  });
238
- var DocumentContentSchema = z6.object({
239
- type: z6.literal("document.content"),
395
+ var DocumentContentSchema = z7.object({
396
+ type: z7.literal("document.content"),
240
397
  requestId,
241
398
  path,
242
- text: z6.string(),
243
- bytes: z6.number().int().nonnegative(),
244
- modifiedAt: z6.string().datetime()
399
+ text: z7.string(),
400
+ bytes: z7.number().int().nonnegative(),
401
+ modifiedAt: z7.string().datetime()
245
402
  });
246
- var DocumentErrorCodeSchema = z6.enum([
403
+ var DocumentErrorCodeSchema = z7.enum([
247
404
  "not_found",
248
405
  "not_file",
249
406
  "unsupported_path",
@@ -255,47 +412,47 @@ var DocumentErrorCodeSchema = z6.enum([
255
412
  "session_missing",
256
413
  "read_failed"
257
414
  ]);
258
- var DocumentErrorSchema = z6.object({
259
- type: z6.literal("document.error"),
415
+ var DocumentErrorSchema = z7.object({
416
+ type: z7.literal("document.error"),
260
417
  requestId,
261
418
  path,
262
419
  code: DocumentErrorCodeSchema
263
420
  });
264
421
 
265
422
  // ../protocol/src/routing.ts
266
- import { z as z7 } from "zod";
267
- var MachineInventorySchema = z7.object({
268
- id: z7.string().min(1).max(80),
269
- label: z7.string().min(1).max(120),
270
- online: z7.boolean(),
271
- local: z7.boolean(),
272
- sessions: z7.array(z7.object({ id: z7.string(), name: z7.string(), project: z7.string(), cwd: z7.string(), topic: z7.string().optional(), state: z7.string(), readOnly: z7.boolean() })).max(500),
273
- folders: z7.array(z7.object({ project: z7.string(), cwd: z7.string() })).max(100)
423
+ import { z as z8 } from "zod";
424
+ var MachineInventorySchema = z8.object({
425
+ id: z8.string().min(1).max(80),
426
+ label: z8.string().min(1).max(120),
427
+ online: z8.boolean(),
428
+ local: z8.boolean(),
429
+ sessions: z8.array(z8.object({ id: z8.string(), name: z8.string(), project: z8.string(), cwd: z8.string(), topic: z8.string().optional(), state: z8.string(), readOnly: z8.boolean() })).max(500),
430
+ folders: z8.array(z8.object({ project: z8.string(), cwd: z8.string() })).max(100)
274
431
  });
275
- var RoutingContextSchema = z7.object({
276
- machines: z7.array(MachineInventorySchema).min(1).max(20),
277
- history: z7.array(z7.object({ question: z7.string().max(2e4), answer: z7.string().max(2e4).optional(), machineId: z7.string().optional() })).max(50)
432
+ var RoutingContextSchema = z8.object({
433
+ machines: z8.array(MachineInventorySchema).min(1).max(20),
434
+ history: z8.array(z8.object({ question: z8.string().max(2e4), answer: z8.string().max(2e4).optional(), machineId: z8.string().optional() })).max(50)
278
435
  });
279
- var RouteResultSchema = z7.object({
436
+ var RouteResultSchema = z8.object({
280
437
  /** Null means an answer or a clarification, never an implicit local destination. */
281
- machineId: z7.string().nullable(),
282
- text: z7.string().min(1).max(2e4),
283
- choices: z7.array(z7.string()).max(20).optional()
438
+ machineId: z8.string().nullable(),
439
+ text: z8.string().min(1).max(2e4),
440
+ choices: z8.array(z8.string()).max(20).optional()
284
441
  });
285
442
 
286
443
  // ../protocol/src/ssh.ts
287
- import { z as z8 } from "zod";
288
- var SshAliasSchema = z8.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
444
+ import { z as z9 } from "zod";
445
+ var SshAliasSchema = z9.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
289
446
 
290
447
  // ../protocol/src/machines.ts
291
- import { z as z9 } from "zod";
292
- var MachineIdSchema = z9.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
293
- var MachineSchema = z9.object({
448
+ import { z as z10 } from "zod";
449
+ var MachineIdSchema = z10.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
450
+ var MachineSchema = z10.object({
294
451
  id: MachineIdSchema,
295
- label: z9.string().trim().min(1).max(80),
452
+ label: z10.string().trim().min(1).max(80),
296
453
  ssh: SshAliasSchema.optional()
297
454
  });
298
- var MachinesSchema = z9.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
455
+ var MachinesSchema = z10.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
299
456
  if (list[0]?.id !== "local" || list[0].ssh !== void 0) ctx.addIssue({ code: "custom", message: "the first machine is local, without ssh", path: [0] });
300
457
  const ids = /* @__PURE__ */ new Set(), aliases = /* @__PURE__ */ new Set();
301
458
  list.forEach((m, i) => {
@@ -310,52 +467,53 @@ var MachinesSchema = z9.array(MachineSchema).min(1).max(20).superRefine((list, c
310
467
  });
311
468
 
312
469
  // ../protocol/src/island.ts
313
- import { z as z10 } from "zod";
314
- var IslandScaleSchema = z10.union([z10.literal("auto"), z10.literal(1), z10.literal(1.25), z10.literal(1.5), z10.literal(2)]);
315
- var IslandPrefsSchema = z10.object({
470
+ import { z as z11 } from "zod";
471
+ var IslandScaleSchema = z11.union([z11.literal("auto"), z11.literal(1), z11.literal(1.25), z11.literal(1.5), z11.literal(2)]);
472
+ var IslandPrefsSchema = z11.object({
316
473
  /** Shown at the top of the screen; hidden, Desktop still notifies. */
317
- visible: z10.boolean(),
474
+ visible: z11.boolean(),
318
475
  /** `primary` (whichever it is after a reconfiguration), or a monitor's id as the shell lists it (`list_monitors`: the OS
319
476
  * name, `#index` without one). `second`, written by older fronts, is the first monitor that is not the primary. */
320
- screen: z10.string().trim().min(1).max(200),
321
- position: z10.enum(["center", "left", "right"]),
477
+ screen: z11.string().trim().min(1).max(200),
478
+ position: z11.enum(["center", "left", "right"]),
322
479
  /** A strip under a maximised window. */
323
- discreet: z10.boolean(),
480
+ discreet: z11.boolean(),
324
481
  islandScale: IslandScaleSchema,
325
482
  panelScale: IslandScaleSchema,
326
483
  /** What the island shows besides asks and questions, which it always shows. */
327
- shows: z10.object({
484
+ shows: z11.object({
328
485
  /** Allow, deny or answer in a line from the island; off, its cards only say « Open in Ledge ». */
329
- quick: z10.boolean(),
486
+ quick: z11.boolean(),
330
487
  /** The 6 s bubble when an agent finishes. */
331
- finished: z10.boolean(),
488
+ finished: z11.boolean(),
332
489
  /** An agent's sentence in a bubble. */
333
- say: z10.boolean(),
490
+ say: z11.boolean(),
334
491
  /** The sub-agents next to their main agent. */
335
- helpers: z10.boolean()
492
+ helpers: z11.boolean()
336
493
  }),
337
494
  /** The global shortcut that opens the island, as shown (`Ctrl Alt L`). */
338
- shortcut: z10.string().trim().min(1).max(40)
495
+ shortcut: z11.string().trim().min(1).max(40)
339
496
  });
340
497
 
341
498
  // ../protocol/src/projects.ts
342
- import { z as z11 } from "zod";
343
- var ProjectColorSchema = z11.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
344
- var ProjectIdSchema = z11.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
499
+ import { z as z12 } from "zod";
500
+ var PROJECT_COLORS = { blue: "#3b82f6", green: "#10b981", amber: "#f59e0b", violet: "#8b5cf6", teal: "#14b8a6", pink: "#ec4899", red: "#ef4444", slate: "#64748b" };
501
+ var ProjectColorSchema = z12.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
502
+ var ProjectIdSchema = z12.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
345
503
  function normalizePath(raw) {
346
504
  const p = raw.trim().replace(/\/+/g, "/");
347
505
  return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
348
506
  }
349
- var PathSchema = z11.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
350
- var RepoSchema = z11.object({ machine: MachineIdSchema, path: PathSchema });
351
- var ProjectSchema = z11.object({
507
+ var PathSchema = z12.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
508
+ var RepoSchema = z12.object({ machine: MachineIdSchema, path: PathSchema });
509
+ var ProjectSchema = z12.object({
352
510
  id: ProjectIdSchema,
353
- name: z11.string().trim().min(1).max(80),
511
+ name: z12.string().trim().min(1).max(80),
354
512
  color: ProjectColorSchema,
355
- description: z11.string().max(500).default(""),
356
- repos: z11.array(RepoSchema).max(50)
513
+ description: z12.string().max(500).default(""),
514
+ repos: z12.array(RepoSchema).max(50)
357
515
  });
358
- var RuleSchema = z11.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
516
+ var RuleSchema = z12.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
359
517
  function projectsProblems(p) {
360
518
  const out = [];
361
519
  const ids = /* @__PURE__ */ new Set(), names = /* @__PURE__ */ new Set(), repos = /* @__PURE__ */ new Set(), rules = /* @__PURE__ */ new Set();
@@ -381,7 +539,7 @@ ${rule.path}`;
381
539
  });
382
540
  return out;
383
541
  }
384
- var ProjectsSchema = z11.object({ projects: z11.array(ProjectSchema).max(100), rules: z11.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
542
+ var ProjectsSchema = z12.object({ projects: z12.array(ProjectSchema).max(100), rules: z12.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
385
543
  for (const problem of projectsProblems(p)) ctx.addIssue({ code: "custom", message: problem.code, path: problem.path });
386
544
  });
387
545
  var DEFAULT_PROJECTS = { projects: [], rules: [] };
@@ -399,47 +557,47 @@ function projectOf(session, state) {
399
557
  }
400
558
 
401
559
  // ../protocol/src/thread.ts
402
- import { z as z12 } from "zod";
403
- var ToolStatusSchema = z12.enum(["running", "success", "failed", "unknown"]);
404
- var FileChangeSchema = z12.object({ path: z12.string().min(1), from: z12.string().min(1).optional(), change: z12.enum(["add", "edit", "delete", "write"]), diff: z12.string().optional() });
405
- var base = { id: z12.string().min(1), at: z12.string().datetime(), turn: z12.number().int().nonnegative(), helperId: z12.string().optional() };
406
- var ThreadItemSchema = z12.discriminatedUnion("kind", [
407
- z12.object({ kind: z12.literal("prompt"), ...base, text: z12.string() }),
408
- z12.object({ kind: z12.literal("message"), ...base, text: z12.string() }),
560
+ import { z as z13 } from "zod";
561
+ var ToolStatusSchema = z13.enum(["running", "success", "failed", "unknown"]);
562
+ var FileChangeSchema = z13.object({ path: z13.string().min(1), from: z13.string().min(1).optional(), change: z13.enum(["add", "edit", "delete", "write"]), diff: z13.string().optional() });
563
+ var base = { id: z13.string().min(1), at: z13.string().datetime(), turn: z13.number().int().nonnegative(), helperId: z13.string().optional() };
564
+ var ThreadItemSchema = z13.discriminatedUnion("kind", [
565
+ z13.object({ kind: z13.literal("prompt"), ...base, text: z13.string() }),
566
+ z13.object({ kind: z13.literal("message"), ...base, text: z13.string() }),
409
567
  /** `id` is the harness's tool_use_id when it gives one, so the result updates the same row; `byClose`: a question
410
568
  * dismissed by Ledge closing the session. */
411
- z12.object({ kind: z12.literal("tool"), ...base, tool: z12.string(), label: z12.string(), input: z12.string().optional(), output: z12.string().optional(), error: z12.string().optional(), status: ToolStatusSchema, durationMs: z12.number().nonnegative().optional(), files: z12.array(FileChangeSchema).default([]), byClose: z12.literal(true).optional() }),
569
+ z13.object({ kind: z13.literal("tool"), ...base, tool: z13.string(), label: z13.string(), input: z13.string().optional(), output: z13.string().optional(), error: z13.string().optional(), status: ToolStatusSchema, durationMs: z13.number().nonnegative().optional(), files: z13.array(FileChangeSchema).default([]), byClose: z13.literal(true).optional() }),
412
570
  /** `unknown`: answered in the terminal, the daemon only saw the prompt go away; `always`: allowed and not asked again;
413
571
  * `byClose`: denied by Ledge closing the session. */
414
- z12.object({ kind: z12.literal("permission"), ...base, tool: z12.string(), text: z12.string(), decision: z12.enum(["accept", "deny", "always", "unknown"]).optional(), byClose: z12.literal(true).optional() }),
415
- z12.object({ kind: z12.literal("reply"), ...base, text: z12.string() }),
416
- z12.object({ kind: z12.literal("helper"), ...base, task: z12.string(), agentType: z12.string().optional(), state: z12.enum(["working", "exited"]) }),
572
+ z13.object({ kind: z13.literal("permission"), ...base, tool: z13.string(), text: z13.string(), decision: z13.enum(["accept", "deny", "always", "unknown"]).optional(), byClose: z13.literal(true).optional() }),
573
+ z13.object({ kind: z13.literal("reply"), ...base, text: z13.string() }),
574
+ z13.object({ kind: z13.literal("helper"), ...base, task: z13.string(), agentType: z13.string().optional(), state: z13.enum(["working", "exited"]) }),
417
575
  /** 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, `via` set when Ledge's mailbox carries it. */
418
- z12.object({ kind: z12.literal("sent"), ...base, to: z12.string(), peer: z12.string().optional(), summary: z12.string().optional(), text: z12.string(), status: z12.enum(["sending", "delivered", "read", "failed"]), received: z12.string().optional(), via: z12.literal("ledge").optional() }),
576
+ z13.object({ kind: z13.literal("sent"), ...base, to: z13.string(), peer: z13.string().optional(), summary: z13.string().optional(), text: z13.string(), status: z13.enum(["sending", "delivered", "read", "failed"]), received: z13.string().optional(), via: z13.literal("ledge").optional() }),
419
577
  /** 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. */
420
- z12.object({ kind: z12.literal("received"), ...base, from: z12.string(), peer: z12.string().optional(), text: z12.string(), sent: z12.string().optional() }),
578
+ z13.object({ kind: z13.literal("received"), ...base, from: z13.string(), peer: z13.string().optional(), text: z13.string(), sent: z13.string().optional() }),
421
579
  /** An automated notice of the agent's harness about messages between sessions (Claude Code's `[Cross-session idle
422
580
  * notice]` / `[Cross-session delivery notice]`): a system event, never the user's words. `name` is the session it is
423
581
  * about as quoted, `peer` its Ledge session id; a delivery notice says the outcome of `count` messages to `recipients`. */
424
- z12.object({
425
- kind: z12.literal("notice"),
582
+ z13.object({
583
+ kind: z13.literal("notice"),
426
584
  ...base,
427
- notice: z12.enum(["idle", "exited", "unavailable", "expired", "delivery", "other"]),
428
- name: z12.string().optional(),
429
- peer: z12.string().optional(),
430
- time: z12.string().optional(),
431
- summary: z12.string().optional(),
432
- status: z12.enum(["held", "denied", "expired", "delivered", "refused", "dropped", "unknown"]).optional(),
433
- count: z12.number().int().positive().optional(),
434
- recipients: z12.array(z12.string()).optional(),
435
- reason: z12.string().optional(),
436
- text: z12.string()
585
+ notice: z13.enum(["idle", "exited", "unavailable", "expired", "delivery", "other"]),
586
+ name: z13.string().optional(),
587
+ peer: z13.string().optional(),
588
+ time: z13.string().optional(),
589
+ summary: z13.string().optional(),
590
+ status: z13.enum(["held", "denied", "expired", "delivered", "refused", "dropped", "unknown"]).optional(),
591
+ count: z13.number().int().positive().optional(),
592
+ recipients: z13.array(z13.string()).optional(),
593
+ reason: z13.string().optional(),
594
+ text: z13.string()
437
595
  })
438
596
  ]);
439
597
 
440
598
  // ../protocol/src/decisions.ts
441
- import { z as z13 } from "zod";
442
- var DecisionSchema = z13.enum(["accept", "deny", "always"]);
599
+ import { z as z14 } from "zod";
600
+ var DecisionSchema = z14.enum(["accept", "deny", "always"]);
443
601
  var NOTE_MAX = 4e3;
444
602
  var NOTE_NOT_TYPED = "note_not_typed";
445
603
  var DECISION_OFFERS = {
@@ -469,12 +627,12 @@ function pendingKey(p) {
469
627
  }
470
628
 
471
629
  // ../protocol/src/mobile.ts
472
- import { z as z14 } from "zod";
630
+ import { z as z15 } from "zod";
473
631
  var DEFAULT_RELAY_URL = "https://app.kl-ledge.dev";
474
- var RelayUrlSchema = z14.string().transform((v, ctx) => {
632
+ var RelayUrlSchema = z15.string().transform((v, ctx) => {
475
633
  const bad = () => {
476
- ctx.addIssue({ code: z14.ZodIssueCode.custom, message: "invalid relay URL" });
477
- return z14.NEVER;
634
+ ctx.addIssue({ code: z15.ZodIssueCode.custom, message: "invalid relay URL" });
635
+ return z15.NEVER;
478
636
  };
479
637
  let u;
480
638
  try {
@@ -487,108 +645,108 @@ var RelayUrlSchema = z14.string().transform((v, ctx) => {
487
645
  if (u.username || u.password || u.pathname !== "/" || u.search || u.hash) return bad();
488
646
  return u.origin;
489
647
  });
490
- var MobileConfigSchema = z14.object({ enabled: z14.boolean(), relayUrl: RelayUrlSchema });
491
- var MobileLinkStateSchema = z14.discriminatedUnion("state", [
492
- z14.object({ state: z14.literal("off") }),
493
- z14.object({ state: z14.literal("connecting") }),
494
- z14.object({ state: z14.literal("online") }),
495
- z14.object({ state: z14.literal("offline"), retryAt: z14.string().datetime(), error: z14.string() }),
496
- z14.object({ state: z14.literal("signed_out"), reason: z14.enum(["other_pc", "session_invalid"]) }),
648
+ var MobileConfigSchema = z15.object({ enabled: z15.boolean(), relayUrl: RelayUrlSchema });
649
+ var MobileLinkStateSchema = z15.discriminatedUnion("state", [
650
+ z15.object({ state: z15.literal("off") }),
651
+ z15.object({ state: z15.literal("connecting") }),
652
+ z15.object({ state: z15.literal("online") }),
653
+ z15.object({ state: z15.literal("offline"), retryAt: z15.string().datetime(), error: z15.string() }),
654
+ z15.object({ state: z15.literal("signed_out"), reason: z15.enum(["other_pc", "session_invalid"]) }),
497
655
  /** The mobile service did not start (a broken mobile.json, a missing key store): nothing is linked until the daemon restarts. */
498
- z14.object({ state: z14.literal("unavailable"), error: z14.string().max(200) })
656
+ z15.object({ state: z15.literal("unavailable"), error: z15.string().max(200) })
499
657
  ]);
500
- var MobilePairingSchema = z14.object({
501
- id: z14.string(),
502
- label: z14.string(),
503
- code: z14.string().nullable(),
504
- expiresAt: z14.string().datetime(),
505
- state: z14.enum(["waiting", "confirm", "approved", "linked", "expired", "refused"])
658
+ var MobilePairingSchema = z15.object({
659
+ id: z15.string(),
660
+ label: z15.string(),
661
+ code: z15.string().nullable(),
662
+ expiresAt: z15.string().datetime(),
663
+ state: z15.enum(["waiting", "confirm", "approved", "linked", "expired", "refused"])
506
664
  });
507
- var MobileStateSchema = z14.object({
508
- enabled: z14.boolean(),
509
- relayUrl: z14.string(),
510
- account: z14.object({ login: z14.string(), accountId: z14.string() }).nullable(),
511
- signin: z14.object({ userCode: z14.string(), verificationUri: z14.string(), expiresAt: z14.string().datetime() }).nullable(),
665
+ var MobileStateSchema = z15.object({
666
+ enabled: z15.boolean(),
667
+ relayUrl: z15.string(),
668
+ account: z15.object({ login: z15.string(), accountId: z15.string() }).nullable(),
669
+ signin: z15.object({ userCode: z15.string(), verificationUri: z15.string(), expiresAt: z15.string().datetime() }).nullable(),
512
670
  /** The last sign-in that did not end with a session; cleared by the next one. */
513
- signinError: z14.enum(["expired", "denied", "not_allowed", "failed"]).nullable(),
671
+ signinError: z15.enum(["expired", "denied", "not_allowed", "failed"]).nullable(),
514
672
  link: MobileLinkStateSchema,
515
- devices: z14.array(z14.object({ id: z14.string(), label: z14.string(), approvedAt: z14.string(), online: z14.boolean(), lastSeenAt: z14.string().optional() })),
516
- pairings: z14.array(MobilePairingSchema)
673
+ devices: z15.array(z15.object({ id: z15.string(), label: z15.string(), approvedAt: z15.string(), online: z15.boolean(), lastSeenAt: z15.string().optional() })),
674
+ pairings: z15.array(MobilePairingSchema)
517
675
  });
518
676
 
519
677
  // ../protocol/src/messages.ts
520
678
  var PROTOCOL_PORT = 47831;
521
- var RefusalCodeSchema = z15.enum(["stale", "read_only", "no_prompt", "not_taken", "busy", "unknown_session", "nothing_to_decide", "not_offered", "free_text", "note_not_typed", "unavailable", "invalid"]);
522
- var SessionEventSchema = z15.object({
523
- name: z15.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed", "NativeRequest", "AgentMessage", "QueueUpdate"]),
524
- at: z15.string().datetime(),
525
- source: z15.enum(["hook", "pane", "daemon"]),
526
- tool: z15.string().optional(),
527
- text: z15.string().optional(),
528
- options: z15.array(z15.string()).optional(),
679
+ var RefusalCodeSchema = z16.enum(["stale", "read_only", "no_prompt", "not_taken", "busy", "unknown_session", "nothing_to_decide", "not_offered", "free_text", "note_not_typed", "unavailable", "invalid"]);
680
+ var SessionEventSchema = z16.object({
681
+ name: z16.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed", "NativeRequest", "AgentMessage", "QueueUpdate"]),
682
+ at: z16.string().datetime(),
683
+ source: z16.enum(["hook", "pane", "daemon"]),
684
+ tool: z16.string().optional(),
685
+ text: z16.string().optional(),
686
+ options: z16.array(z16.string()).optional(),
529
687
  /** Each option's description, by index ('' when it has none); left out when no option has one. */
530
- optionDetails: z15.array(z15.string()).optional(),
531
- kind: z15.enum(["question", "permission", "plan", "diff"]).optional(),
688
+ optionDetails: z16.array(z16.string()).optional(),
689
+ kind: z16.enum(["question", "permission", "plan", "diff"]).optional(),
532
690
  /** PanePrompt: what to review in full before deciding (a plan read in the terminal), when `text` is its summary. */
533
- body: z15.string().optional(),
534
- exitCode: z15.number().int().nullable().optional(),
535
- helperId: z15.string().optional(),
691
+ body: z16.string().optional(),
692
+ exitCode: z16.number().int().nullable().optional(),
693
+ helperId: z16.string().optional(),
536
694
  /** The helper's description, once known (Claude Code: its `.meta.json`, written after SubagentStart). */
537
- task: z15.string().optional(),
695
+ task: z16.string().optional(),
538
696
  /** PostToolUseFailure: the user interrupted the turn (Esc); Claude then waits for input with no Stop. */
539
- interrupt: z15.boolean().optional(),
697
+ interrupt: z16.boolean().optional(),
540
698
  /** QueueUpdate: OMP's queue as it stands. */
541
- queue: z15.array(z15.object({ text: z15.string(), mode: z15.enum(["steer", "follow_up"]) })).optional(),
699
+ queue: z16.array(z16.object({ text: z16.string(), mode: z16.enum(["steer", "follow_up"]) })).optional(),
542
700
  /** AgentMessage: the harness's own message, its text so far (`streaming`) or whole. `messageId` is unique for the session's life. */
543
- messageId: z15.string().optional(),
544
- streaming: z15.boolean().optional(),
701
+ messageId: z16.string().optional(),
702
+ streaming: z16.boolean().optional(),
545
703
  /** NativeRequest: the dialog's id, and OMP's `ask` questions when it is one. */
546
- requestId: z15.string().optional(),
547
- questions: z15.array(AskQuestionSchema).optional(),
704
+ requestId: z16.string().optional(),
705
+ questions: z16.array(AskQuestionSchema).optional(),
548
706
  /** Stop: its text is already in the thread as AgentMessage cards, which the reply card must not repeat. */
549
- streamed: z15.boolean().optional(),
707
+ streamed: z16.boolean().optional(),
550
708
  /** Native tracking (spec 2026-10-13 §2): the harness's id of the tool call, so its result lands on the same row. */
551
- toolUseId: z15.string().optional(),
709
+ toolUseId: z16.string().optional(),
552
710
  /** The tool's input as the harness gave it, every string capped by the hook. */
553
- toolInput: z15.record(z15.string(), z15.unknown()).optional(),
711
+ toolInput: z16.record(z16.string(), z16.unknown()).optional(),
554
712
  /** PostToolUse: the result as text; SubagentStop: nothing (the report travels in `text`). */
555
- output: z15.string().optional(),
556
- durationMs: z15.number().nonnegative().optional(),
713
+ output: z16.string().optional(),
714
+ durationMs: z16.number().nonnegative().optional(),
557
715
  /** PostToolUseFailure: the harness's error message. */
558
- error: z15.string().optional(),
716
+ error: z16.string().optional(),
559
717
  /** The session's transcript, or the helper's on SubagentStop; the daemon reads its tail for the agent's messages. */
560
- transcriptPath: z15.string().optional(),
718
+ transcriptPath: z16.string().optional(),
561
719
  /** Decided: what was chosen when the daemon knows it. */
562
720
  decision: DecisionSchema.optional(),
563
721
  /** Decided: the request was refused by Ledge closing the session (its interrupt keys), not by the user's answer. */
564
- byClose: z15.literal(true).optional(),
722
+ byClose: z16.literal(true).optional(),
565
723
  /** The agent's own session id when the hook names it, so the daemon can resume that conversation. */
566
- agentSessionId: z15.string().optional()
724
+ agentSessionId: z16.string().optional()
567
725
  });
568
- var RecentSchema = z15.object({ cwd: z15.string().min(1), project: z15.string().min(1), lastAt: z15.string().datetime() });
726
+ var RecentSchema = z16.object({ cwd: z16.string().min(1), project: z16.string().min(1), lastAt: z16.string().datetime() });
569
727
  var SEMVER = /^\d+\.\d+\.\d+$/;
570
- var DaemonInfoSchema = z15.object({
571
- version: z15.string(),
572
- install: z15.enum(["npm", "git", "unknown"]),
573
- systemd: z15.boolean(),
574
- manual: z15.string().optional(),
575
- routing: z15.boolean().optional(),
576
- archive: z15.boolean().optional(),
728
+ var DaemonInfoSchema = z16.object({
729
+ version: z16.string(),
730
+ install: z16.enum(["npm", "git", "unknown"]),
731
+ systemd: z16.boolean(),
732
+ manual: z16.string().optional(),
733
+ routing: z16.boolean().optional(),
734
+ archive: z16.boolean().optional(),
577
735
  /** Takes `model` and `effort` on `session.launch` and answers `models.list` (#58). */
578
- models: z15.boolean().optional(),
736
+ models: z16.boolean().optional(),
579
737
  /** Drives OMP over its RPC: launching `omp` sessions is possible when the binary is there (`models.list` says so). */
580
- omp: z15.boolean().optional(),
738
+ omp: z16.boolean().optional(),
581
739
  /** Takes `session.model`: a live session's model and effort changed (#58, lot 2). */
582
- modelSwitch: z15.boolean().optional(),
740
+ modelSwitch: z16.boolean().optional(),
583
741
  /** Takes `approval` on an OMP `session.launch` (auto mode). */
584
- ompApproval: z15.boolean().optional(),
742
+ ompApproval: z16.boolean().optional(),
585
743
  /** Its home folder, so a client shows the folders under it as `~` (absent from older daemons). */
586
- home: z15.string().optional(),
587
- mobile: z15.boolean().optional()
744
+ home: z16.string().optional(),
745
+ mobile: z16.boolean().optional()
588
746
  });
589
- var UpdateFailureSchema = z15.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
590
- var ServerMessageSchema = z15.discriminatedUnion("type", [
591
- z15.object({ type: z15.literal("chat.routed"), qid: z15.string(), result: RouteResultSchema }),
747
+ var UpdateFailureSchema = z16.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
748
+ var ServerMessageSchema = z16.discriminatedUnion("type", [
749
+ z16.object({ type: z16.literal("chat.routed"), qid: z16.string(), result: RouteResultSchema }),
592
750
  DocumentContentSchema,
593
751
  DocumentErrorSchema,
594
752
  ReposInfoSchema,
@@ -597,69 +755,72 @@ var ServerMessageSchema = z15.discriminatedUnion("type", [
597
755
  /** `recents`: the folders of past sessions, most recent first. `launcher`: the command that runs `ledge` inside WSL
598
756
  * (`[node, cli]`), so the overlay can attach a terminal to a session with `<launcher> attach <id>`. `daemon`: absent
599
757
  * from daemons older than the auto-update. */
600
- z15.object({ type: z15.literal("snapshot"), sessions: z15.array(SessionSchema), recents: z15.array(RecentSchema).optional(), launcher: z15.array(z15.string()).optional(), daemon: DaemonInfoSchema.optional(), sshAlias: SshAliasSchema.optional() }),
601
- z15.object({ type: z15.literal("session.updated"), session: SessionSchema }),
758
+ z16.object({ type: z16.literal("snapshot"), sessions: z16.array(SessionSchema), recents: z16.array(RecentSchema).optional(), launcher: z16.array(z16.string()).optional(), daemon: DaemonInfoSchema.optional(), sshAlias: SshAliasSchema.optional() }),
759
+ z16.object({ type: z16.literal("session.updated"), session: SessionSchema }),
602
760
  /** OMP: the instructions taken back from its queue by an interrupt that restores it; the asker puts them back in its draft. */
603
- z15.object({ type: z15.literal("session.queue.restored"), id: z15.string(), texts: z15.array(z15.string()) }),
604
- z15.object({ type: z15.literal("session.removed"), id: z15.string() }),
605
- z15.object({ type: z15.literal("session.preview"), id: z15.string(), lines: z15.array(z15.string()) }),
761
+ z16.object({ type: z16.literal("session.queue.restored"), id: z16.string(), texts: z16.array(z16.string()) }),
762
+ z16.object({ type: z16.literal("session.removed"), id: z16.string() }),
763
+ z16.object({ type: z16.literal("session.preview"), id: z16.string(), lines: z16.array(z16.string()) }),
606
764
  /** A session's thread, on request (spec 2026-10-13 §5); then each created or updated item, broadcast. */
607
- z15.object({ type: z15.literal("session.thread"), id: z15.string(), items: z15.array(ThreadItemSchema) }),
608
- z15.object({ type: z15.literal("session.thread.item"), id: z15.string(), item: ThreadItemSchema }),
765
+ z16.object({ type: z16.literal("session.thread"), id: z16.string(), items: z16.array(ThreadItemSchema) }),
766
+ z16.object({ type: z16.literal("session.thread.item"), id: z16.string(), item: ThreadItemSchema }),
609
767
  /** Answer to the requester of a `session.launch`: the session exists (its `session.updated` is broadcast as usual). */
610
- z15.object({ type: z15.literal("session.launched"), id: z15.string() }),
768
+ z16.object({ type: z16.literal("session.launched"), id: z16.string() }),
611
769
  /** Answer to the requester of a `session.close`, once per row closed: the session `id` and how it ended. */
612
- z15.object({ type: z15.literal("session.closed"), qid: z15.string(), row: z15.number().int().min(0), id: z15.string(), how: z15.enum(["exit", "killed"]) }),
613
- z15.object({ type: z15.literal("recents"), recents: z15.array(RecentSchema) }),
770
+ z16.object({ type: z16.literal("session.closed"), qid: z16.string(), row: z16.number().int().min(0), id: z16.string(), how: z16.enum(["exit", "killed"]) }),
771
+ z16.object({ type: z16.literal("recents"), recents: z16.array(RecentSchema) }),
614
772
  /** Answer to the requester of a `folder.list` or `folder.create`: `path` resolved (`~` expanded), `parent` null at the
615
773
  * root, `entries` its subfolders by name (hidden ones left out). A failure keeps the asked path and says why in `error`,
616
774
  * never in an `error` message, which the launch form reads as a failed launch. */
617
- z15.object({
618
- type: z15.literal("folder.listing"),
619
- path: z15.string(),
620
- parent: z15.string().nullable(),
621
- entries: z15.array(z15.string()),
622
- error: z15.string().optional(),
775
+ z16.object({
776
+ type: z16.literal("folder.listing"),
777
+ path: z16.string(),
778
+ parent: z16.string().nullable(),
779
+ entries: z16.array(z16.string()),
780
+ error: z16.string().optional(),
623
781
  /** The request this answers, when it carried one. */
624
- requestId: z15.string().optional(),
782
+ requestId: z16.string().optional(),
625
783
  /** Those `entries` that are Git repositories, with their branch (the short commit when HEAD is detached). */
626
- repos: z15.array(z15.object({ name: z15.string(), branch: z15.string() })).optional()
784
+ repos: z16.array(z16.object({ name: z16.string(), branch: z16.string() })).optional()
627
785
  }),
628
786
  /** The chat (spec 2026-10-11 §5): what Ledge reads, its words as they come, the final text, or why it stopped; the day's turns after each snapshot. */
629
- z15.object({ type: z15.literal("chat.step"), qid: z15.string(), step: ChatStepSchema }),
630
- z15.object({ type: z15.literal("chat.delta"), qid: z15.string(), text: z15.string() }),
631
- z15.object({ type: z15.literal("chat.done"), qid: z15.string(), text: z15.string(), at: z15.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), closes: ChatClosesSchema.optional(), projectProposal: ChatProjectProposalSchema.optional() }),
632
- z15.object({ type: z15.literal("chat.error"), qid: z15.string(), message: z15.string() }),
633
- z15.object({ type: z15.literal("chat.history"), turns: z15.array(ChatTurnSchema) }),
787
+ z16.object({ type: z16.literal("chat.step"), qid: z16.string(), step: ChatStepSchema }),
788
+ z16.object({ type: z16.literal("chat.delta"), qid: z16.string(), text: z16.string() }),
789
+ z16.object({ type: z16.literal("chat.done"), qid: z16.string(), text: z16.string(), at: z16.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), closes: ChatClosesSchema.optional(), projectProposal: ChatProjectProposalSchema.optional() }),
790
+ z16.object({ type: z16.literal("chat.error"), qid: z16.string(), message: z16.string() }),
791
+ z16.object({ type: z16.literal("chat.history"), turns: z16.array(ChatTurnSchema) }),
634
792
  /** The daemon updating itself: npm is running, then the service restarts (the socket closes right after). */
635
- z15.object({ type: z15.literal("update.progress"), version: z15.string(), step: z15.enum(["installing", "restarting"]) }),
793
+ z16.object({ type: z16.literal("update.progress"), version: z16.string(), step: z16.enum(["installing", "restarting"]) }),
636
794
  /** Nothing was changed. `detail`: the useful end of npm's output; `command`: what the person can run instead. */
637
- z15.object({ type: z15.literal("update.failed"), version: z15.string(), reason: UpdateFailureSchema, detail: z15.string().optional(), command: z15.string().optional() }),
795
+ z16.object({ type: z16.literal("update.failed"), version: z16.string(), reason: UpdateFailureSchema, detail: z16.string().optional(), command: z16.string().optional() }),
638
796
  /** The shared machines list (spec 2026-10-14 §5), sent to local clients only. */
639
- z15.object({ type: z15.literal("machines"), machines: MachinesSchema }),
797
+ z16.object({ type: z16.literal("machines"), machines: MachinesSchema }),
640
798
  /** The local daemon's projects and folder rules (spec 2026-10-16 build §5), sent to local clients only. */
641
- z15.object({ type: z15.literal("projects"), projects: ProjectsSchema }),
799
+ z16.object({ type: z16.literal("projects"), projects: ProjectsSchema }),
642
800
  /** The island's settings shared by the local fronts; null until a front sets them (the island then sends its own). Local clients only. */
643
- z15.object({ type: z15.literal("island"), prefs: IslandPrefsSchema.nullable() }),
801
+ z16.object({ type: z16.literal("island"), prefs: IslandPrefsSchema.nullable() }),
644
802
  /** Mobile access (lot 3), after the island in each snapshot and on every change. Local clients only. */
645
- z15.object({ type: z15.literal("mobile"), state: MobileStateSchema }),
803
+ z16.object({ type: z16.literal("mobile"), state: MobileStateSchema }),
646
804
  /** The daemon's immediate answer to a `session.message`: delivered (`accepted`), held for later (`queued`), left to Claude's own SendMessage (`native`), or refused with a `reason`. */
647
- z15.object({ type: z15.literal("message.ack"), rid: z15.string(), outcome: z15.enum(["accepted", "queued", "native", "refused"]), reason: z15.string().optional() }),
648
- z15.object({
649
- type: z15.literal("error"),
650
- message: z15.string(),
651
- id: z15.string().optional(),
805
+ z16.object({ type: z16.literal("message.ack"), rid: z16.string(), outcome: z16.enum(["accepted", "queued", "native", "refused"]), reason: z16.string().optional() }),
806
+ /** The answer to a `message.read`: the body with its sender, target and time, or the refusal's `reason`. */
807
+ z16.object({ type: z16.literal("message.body"), id: z16.string(), from: z16.string().optional(), to: z16.string().optional(), at: z16.string().optional(), text: z16.string().optional(), reason: z16.string().optional() }),
808
+ TaskResultSchema,
809
+ z16.object({
810
+ type: z16.literal("error"),
811
+ message: z16.string(),
812
+ id: z16.string().optional(),
652
813
  /** Le type du message client refusé. */
653
- request: z15.string().optional(),
814
+ request: z16.string().optional(),
654
815
  /** Ce que le front doit reconnaître au-delà du texte (NOTE_NOT_TYPED) ; une chaîne libre, qu'un front plus ancien ignore. */
655
- code: z15.string().optional()
816
+ code: z16.string().optional()
656
817
  })
657
818
  ]);
658
819
  var MESSAGE_MAX = 2e4;
659
820
  var SUMMARY_MAX = 500;
660
- var launchRows = z15.array(z15.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
661
- var ClientMessageSchema = z15.discriminatedUnion("type", [
662
- z15.object({ type: z15.literal("chat.route"), qid: z15.string().min(1).max(100), text: z15.string().min(1).max(2e4), tool: ToolSchema.optional(), context: RoutingContextSchema }),
821
+ var launchRows = z16.array(z16.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
822
+ var ClientMessageSchema = z16.discriminatedUnion("type", [
823
+ z16.object({ type: z16.literal("chat.route"), qid: z16.string().min(1).max(100), text: z16.string().min(1).max(2e4), tool: ToolSchema.optional(), context: RoutingContextSchema }),
663
824
  DocumentReadSchema,
664
825
  ReposInspectSchema,
665
826
  ReposScanSchema,
@@ -668,114 +829,117 @@ var ClientMessageSchema = z15.discriminatedUnion("type", [
668
829
  * free one otherwise), `prompt` the agent's first instruction, `continue` resumes the folder's last conversation.
669
830
  * With `qid` it is the chat card of that turn: the daemon launches the turn's proposal and ignores the other fields
670
831
  * but `rows`, `model` and `effort`. */
671
- z15.object({
672
- type: z15.literal("session.launch"),
832
+ z16.object({
833
+ type: z16.literal("session.launch"),
673
834
  tool: ToolSchema,
674
- cwd: z15.string().min(1),
675
- name: z15.string().optional(),
676
- prompt: z15.string().optional(),
677
- continue: z15.boolean().optional(),
678
- qid: z15.string().min(1).optional(),
835
+ cwd: z16.string().min(1),
836
+ name: z16.string().optional(),
837
+ prompt: z16.string().optional(),
838
+ continue: z16.boolean().optional(),
839
+ qid: z16.string().min(1).optional(),
679
840
  /** With `qid`: the card's rows to launch (#24), indexes into the turn's `proposals`, each once; the first row without it. */
680
841
  rows: launchRows.optional(),
681
842
  /** Folders in addition to `cwd` (Claude: `--add-dir`); resolved like `cwd`. */
682
- dirs: z15.array(z15.string().min(1)).max(20).optional(),
843
+ dirs: z16.array(z16.string().min(1)).max(20).optional(),
683
844
  /** The agent's model and effort (#58), the agent's own default when left out; with `qid`, for every row launched. */
684
845
  model: ModelIdSchema.optional(),
685
846
  effort: EffortSchema.optional(),
686
847
  /** OMP only: its approval mode, `always-ask` when left out. */
687
848
  approval: OmpApprovalSchema.optional(),
688
849
  /** Routing by the fleet, removed before the message reaches the daemon. */
689
- machineId: z15.string().optional()
850
+ machineId: z16.string().optional()
690
851
  }),
691
852
  /** The launch form's folder browser: list a folder's subfolders (home when no `path`), or make `name` inside `parent`
692
853
  * and list it. Both are answered with a `folder.listing`. */
693
- z15.object({
694
- type: z15.literal("folder.list"),
695
- path: z15.string().optional(),
854
+ z16.object({
855
+ type: z16.literal("folder.list"),
856
+ path: z16.string().optional(),
696
857
  /** Routing by the fleet, removed before the message reaches the daemon. */
697
- machineId: z15.string().optional(),
858
+ machineId: z16.string().optional(),
698
859
  /** Copied into the `folder.listing` that answers; the fleet relays only that listing, from the machine asked. */
699
- requestId: z15.string().min(1).max(100).optional()
860
+ requestId: z16.string().min(1).max(100).optional()
700
861
  }),
701
- z15.object({
702
- type: z15.literal("folder.create"),
703
- parent: z15.string().min(1),
704
- name: z15.string().min(1),
862
+ z16.object({
863
+ type: z16.literal("folder.create"),
864
+ parent: z16.string().min(1),
865
+ name: z16.string().min(1),
705
866
  /** As for `folder.list`: routing by the fleet, and the request its `folder.listing` answers. */
706
- machineId: z15.string().optional(),
707
- requestId: z15.string().min(1).max(100).optional()
867
+ machineId: z16.string().optional(),
868
+ requestId: z16.string().min(1).max(100).optional()
708
869
  }),
709
870
  /** `steer`: OMP only, redirect the turn running now instead of queueing the text after it. */
710
871
  /** OMP's `ask`: the answers to every question of the dialog `requestId`, in order (`selected` labels, and/or free `text`); `cancel` declines it. */
711
- z15.object({ type: z15.literal("session.answer"), id: z15.string(), requestId: z15.string().min(1).max(200), answers: z15.array(z15.object({ id: z15.string(), selected: z15.array(z15.string().max(2e3)).max(100), text: z15.string().max(2e4).optional() })).max(50), cancel: z15.boolean().optional() }),
712
- z15.object({ type: z15.literal("session.reply"), id: z15.string(), text: z15.string().min(1), steer: z15.boolean().optional() }),
872
+ z16.object({ type: z16.literal("session.answer"), id: z16.string(), requestId: z16.string().min(1).max(200), answers: z16.array(z16.object({ id: z16.string(), selected: z16.array(z16.string().max(2e3)).max(100), text: z16.string().max(2e4).optional() })).max(50), cancel: z16.boolean().optional() }),
873
+ z16.object({ type: z16.literal("session.reply"), id: z16.string(), text: z16.string().min(1), steer: z16.boolean().optional() }),
713
874
  /** The chat card's instruction (spec 2026-10-12 relay §4): sent now if the session is free, else queued until it is. */
714
- z15.object({ type: z15.literal("session.relay"), qid: z15.string().min(1) }),
875
+ z16.object({ type: z16.literal("session.relay"), qid: z16.string().min(1) }),
715
876
  /** Takes the card's queued instruction back out of the queue. */
716
- z15.object({ type: z15.literal("session.relay.cancel"), qid: z15.string().min(1) }),
877
+ z16.object({ type: z16.literal("session.relay.cancel"), qid: z16.string().min(1) }),
717
878
  /** The chat's close card: closes the turn's sessions, `rows` (indexes into its `closes`) or all of them. */
718
- z15.object({ type: z15.literal("session.close"), qid: z15.string().min(1), rows: launchRows.optional() }),
879
+ z16.object({ type: z16.literal("session.close"), qid: z16.string().min(1), rows: launchRows.optional() }),
719
880
  /** `always` and `note` only where the harness offers them (`offers`); a note goes with `deny`, for the agent to read. */
720
- z15.object({
721
- type: z15.literal("session.decide"),
722
- id: z15.string(),
881
+ z16.object({
882
+ type: z16.literal("session.decide"),
883
+ id: z16.string(),
723
884
  decision: DecisionSchema,
724
- note: z15.string().trim().min(1).max(NOTE_MAX).optional(),
885
+ note: z16.string().trim().min(1).max(NOTE_MAX).optional(),
725
886
  /** The request the user saw when deciding: refused (the request changed) unless it is still the pending one. Left out by older fronts and the island.
726
887
  * `key` (pendingKey) covers its whole content; older fronts send kind and text only. */
727
- expect: PendingSchema.pick({ kind: true, text: true }).extend({ key: z15.string().max(64).optional() }).optional()
888
+ expect: PendingSchema.pick({ kind: true, text: true }).extend({ key: z16.string().max(64).optional() }).optional()
728
889
  }),
729
- z15.object({ type: z15.literal("session.focus"), id: z15.string() }),
730
- z15.object({ type: z15.literal("session.kill"), id: z15.string() }),
890
+ z16.object({ type: z16.literal("session.focus"), id: z16.string() }),
891
+ z16.object({ type: z16.literal("session.kill"), id: z16.string() }),
731
892
  /** `restoreQueue` (OMP): also take back what waits in its queue, returned as `session.queue.restored` to put in the draft. */
732
- z15.object({ type: z15.literal("session.interrupt"), id: z15.string(), restoreQueue: z15.boolean().optional() }),
893
+ z16.object({ type: z16.literal("session.interrupt"), id: z16.string(), restoreQueue: z16.boolean().optional() }),
733
894
  /** Removes a finished (exited) or read-only (hook_only) session from the list; refused for a live launcher session. */
734
- z15.object({ type: z15.literal("session.dismiss"), id: z15.string() }),
895
+ z16.object({ type: z16.literal("session.dismiss"), id: z16.string() }),
735
896
  /** Puts a session in the archive: it leaves the live list but is kept. */
736
- z15.object({ type: z15.literal("session.archive"), id: z15.string() }),
897
+ z16.object({ type: z16.literal("session.archive"), id: z16.string() }),
737
898
  /** Takes an archived session back out of the archive, as it was. */
738
- z15.object({ type: z15.literal("session.restore"), id: z15.string() }),
899
+ z16.object({ type: z16.literal("session.restore"), id: z16.string() }),
739
900
  /** Starts the agent again on an archived session's conversation. */
740
- z15.object({ type: z15.literal("session.resume"), id: z15.string() }),
901
+ z16.object({ type: z16.literal("session.resume"), id: z16.string() }),
741
902
  /** Changes a live session's model and effort (#58, lot 2), `null` for the agent's own default: the daemon restarts the
742
903
  * agent on the same conversation with them, at once when the session is free, else at its next free moment. */
743
- z15.object({ type: z15.literal("session.model"), id: z15.string(), model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
904
+ z16.object({ type: z16.literal("session.model"), id: z16.string(), model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
744
905
  /** Deletes a session for good, archived or not. */
745
- z15.object({ type: z15.literal("session.delete"), id: z15.string() }),
906
+ z16.object({ type: z16.literal("session.delete"), id: z16.string() }),
746
907
  /** 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. */
747
- z15.object({ type: z15.literal("session.rename"), id: z15.string(), topic: z15.string().max(500) }),
748
- z15.object({ type: z15.literal("session.thread"), id: z15.string() }),
749
- z15.object({ type: z15.literal("session.register"), session: SessionSchema }),
908
+ z16.object({ type: z16.literal("session.rename"), id: z16.string(), topic: z16.string().max(500) }),
909
+ z16.object({ type: z16.literal("session.thread"), id: z16.string() }),
910
+ z16.object({ type: z16.literal("session.register"), session: SessionSchema }),
750
911
  /** `agent` and `cwd` describe the session when the daemon does not know it yet (a hook from an agent started without ledge). */
751
- z15.object({ type: z15.literal("session.event"), id: z15.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z15.string().optional() }),
752
- z15.object({ type: z15.literal("session.unregister"), id: z15.string(), exitCode: z15.number().int().nullable() }),
753
- z15.object({ type: z15.literal("snapshot.request") }),
754
- z15.object({ type: z15.literal("chat.ask"), qid: z15.string().min(1), text: z15.string().min(1), tool: ToolSchema.optional() }),
755
- z15.object({ type: z15.literal("chat.cancel"), qid: z15.string().min(1) }),
912
+ z16.object({ type: z16.literal("session.event"), id: z16.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z16.string().optional() }),
913
+ z16.object({ type: z16.literal("session.unregister"), id: z16.string(), exitCode: z16.number().int().nullable() }),
914
+ z16.object({ type: z16.literal("snapshot.request") }),
915
+ z16.object({ type: z16.literal("chat.ask"), qid: z16.string().min(1), text: z16.string().min(1), tool: ToolSchema.optional() }),
916
+ z16.object({ type: z16.literal("chat.cancel"), qid: z16.string().min(1) }),
756
917
  /** Starts a new conversation with Ledge: the day's turns are cleared and the next question opens a new harness session. */
757
- z15.object({ type: z15.literal("chat.reset") }),
918
+ z16.object({ type: z16.literal("chat.reset") }),
758
919
  /** Installs `@paradow/ledge@version` and restarts the service (spec 2026-10-12 auto-update §6). */
759
- z15.object({ type: z15.literal("update.daemon"), version: z15.string().regex(SEMVER) }),
920
+ z16.object({ type: z16.literal("update.daemon"), version: z16.string().regex(SEMVER) }),
760
921
  /** Replaces the daemon's machines list (first connected front wins); refused unless valid. */
761
- z15.object({ type: z15.literal("machines.set"), machines: MachinesSchema }),
922
+ z16.object({ type: z16.literal("machines.set"), machines: MachinesSchema }),
762
923
  /** Replaces the local daemon's projects and rules as a whole; refused unless valid. */
763
- z15.object({ type: z15.literal("projects.set"), projects: ProjectsSchema }),
924
+ z16.object({ type: z16.literal("projects.set"), projects: ProjectsSchema }),
764
925
  /** Replaces the island's settings as a whole; refused unless valid. */
765
- z15.object({ type: z15.literal("island.set"), prefs: IslandPrefsSchema }),
926
+ z16.object({ type: z16.literal("island.set"), prefs: IslandPrefsSchema }),
766
927
  /** Mobile access (lot 3), local fronts only. A changed `relayUrl` drops the relay session. */
767
- z15.object({ type: z15.literal("mobile.set"), enabled: z15.boolean(), relayUrl: RelayUrlSchema }),
768
- z15.object({ type: z15.literal("mobile.signin") }),
769
- z15.object({ type: z15.literal("mobile.signin.cancel") }),
770
- z15.object({ type: z15.literal("mobile.signout") }),
928
+ z16.object({ type: z16.literal("mobile.set"), enabled: z16.boolean(), relayUrl: RelayUrlSchema }),
929
+ z16.object({ type: z16.literal("mobile.signin") }),
930
+ z16.object({ type: z16.literal("mobile.signin.cancel") }),
931
+ z16.object({ type: z16.literal("mobile.signout") }),
771
932
  /** While the link is offline: try again now. */
772
- z15.object({ type: z15.literal("mobile.retry") }),
773
- z15.object({ type: z15.literal("mobile.pairing.approve"), id: z15.string().min(1).max(100) }),
774
- z15.object({ type: z15.literal("mobile.pairing.deny"), id: z15.string().min(1).max(100) }),
933
+ z16.object({ type: z16.literal("mobile.retry") }),
934
+ z16.object({ type: z16.literal("mobile.pairing.approve"), id: z16.string().min(1).max(100) }),
935
+ z16.object({ type: z16.literal("mobile.pairing.deny"), id: z16.string().min(1).max(100) }),
775
936
  /** `id`: the device's fingerprint. */
776
- z15.object({ type: z15.literal("mobile.device.revoke"), id: z15.string().min(1).max(100) }),
937
+ z16.object({ type: z16.literal("mobile.device.revoke"), id: z16.string().min(1).max(100) }),
777
938
  /** One session writing to another by Ledge name (`to` may end in ` [ref]`); `intercept` (the Claude hook): answer `native` unless `to` is a live non-Claude session. */
778
- z15.object({ type: z15.literal("session.message"), rid: z15.string().min(1), from: z15.string().min(1), to: z15.string().min(1), text: z15.string().min(1).max(MESSAGE_MAX), summary: z15.string().max(SUMMARY_MAX).optional(), intercept: z15.boolean().optional() })
939
+ z16.object({ type: z16.literal("session.message"), rid: z16.string().min(1), from: z16.string().min(1), to: z16.string().min(1), text: z16.string().min(1).max(MESSAGE_MAX), summary: z16.string().max(SUMMARY_MAX).optional(), intercept: z16.boolean().optional() }),
940
+ /** `ledge read <id>`: a message's whole body, for its receiver or its sender (`by`, a session id). */
941
+ z16.object({ type: z16.literal("message.read"), id: z16.string().min(1).max(40), by: z16.string().min(1) }),
942
+ TaskCmdSchema
779
943
  ]);
780
944
  function parseClientMessage(raw) {
781
945
  return ClientMessageSchema.parse(raw);
@@ -867,16 +1031,16 @@ function pickName(taken, preferred) {
867
1031
  }
868
1032
 
869
1033
  // ../protocol/src/proposal.ts
870
- import { z as z16 } from "zod";
871
- var ProposalBlockSchema = z16.object({ cwd: z16.string().trim().min(1), prompt: z16.string().optional() });
872
- var RelayBlockSchema = z16.object({ session: z16.string().trim().min(1), text: z16.string().trim().min(1) });
873
- var ProjectBlockSchema = z16.object({
874
- name: z16.string().trim().min(1).max(80),
875
- description: z16.string().max(500).optional(),
876
- repos: z16.array(z16.object({ path: z16.string().min(1).max(4096), why: z16.string().trim().min(1).max(200) })).min(1).max(50),
877
- addTo: z16.string().trim().max(80).optional()
1034
+ import { z as z17 } from "zod";
1035
+ var ProposalBlockSchema = z17.object({ cwd: z17.string().trim().min(1), prompt: z17.string().optional() });
1036
+ var RelayBlockSchema = z17.object({ session: z17.string().trim().min(1), text: z17.string().trim().min(1) });
1037
+ var ProjectBlockSchema = z17.object({
1038
+ name: z17.string().trim().min(1).max(80),
1039
+ description: z17.string().max(500).optional(),
1040
+ repos: z17.array(z17.object({ path: z17.string().min(1).max(4096), why: z17.string().trim().min(1).max(200) })).min(1).max(50),
1041
+ addTo: z17.string().trim().max(80).optional()
878
1042
  });
879
- var CloseBlockSchema = z16.object({ session: z16.string().trim().min(1) });
1043
+ var CloseBlockSchema = z17.object({ session: z17.string().trim().min(1) });
880
1044
  var BLOCK = /```ledge-(session|relay|project|close)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
881
1045
  function extractProposal(answer) {
882
1046
  const found = [];
@@ -932,80 +1096,80 @@ function sessionTitle(s) {
932
1096
  }
933
1097
 
934
1098
  // ../protocol/src/relay.ts
935
- import { z as z17 } from "zod";
1099
+ import { z as z18 } from "zod";
936
1100
  var SEALED_MAX = 1048576;
937
- var RelayPeerSchema = z17.union([z17.literal("pc"), z17.string().regex(/^d_[A-Za-z0-9_-]{16,40}$/)]);
938
- var RelayClientMessageSchema = z17.object({
939
- type: z17.literal("send"),
1101
+ var RelayPeerSchema = z18.union([z18.literal("pc"), z18.string().regex(/^d_[A-Za-z0-9_-]{16,40}$/)]);
1102
+ var RelayClientMessageSchema = z18.object({
1103
+ type: z18.literal("send"),
940
1104
  to: RelayPeerSchema,
941
- body: z17.string().max(SEALED_MAX)
1105
+ body: z18.string().max(SEALED_MAX)
942
1106
  });
943
- var RelayServerMessageSchema = z17.discriminatedUnion("type", [
944
- z17.object({ type: z17.literal("welcome"), accountId: z17.string(), self: RelayPeerSchema, online: z17.array(RelayPeerSchema) }),
945
- z17.object({ type: z17.literal("deliver"), from: RelayPeerSchema, body: z17.string() }),
946
- z17.object({ type: z17.literal("presence"), peer: RelayPeerSchema, online: z17.boolean() }),
1107
+ var RelayServerMessageSchema = z18.discriminatedUnion("type", [
1108
+ z18.object({ type: z18.literal("welcome"), accountId: z18.string(), self: RelayPeerSchema, online: z18.array(RelayPeerSchema) }),
1109
+ z18.object({ type: z18.literal("deliver"), from: RelayPeerSchema, body: z18.string() }),
1110
+ z18.object({ type: z18.literal("presence"), peer: RelayPeerSchema, online: z18.boolean() }),
947
1111
  /**
948
1112
  * A frame was refused; `to` names the target when it is known.
949
1113
  * Contract: an error concerning a sealed body (`offline`, `too_large`, or a socket closed with 4008
950
1114
  * `rate_limited`) means that body was not delivered, so the sequence on that channel is lost: the
951
1115
  * sender must re-handshake with that peer before sending to it again.
952
1116
  */
953
- z17.object({
954
- type: z17.literal("error"),
955
- code: z17.enum(["bad_frame", "too_large", "bad_target", "offline", "rate_limited"]),
1117
+ z18.object({
1118
+ type: z18.literal("error"),
1119
+ code: z18.enum(["bad_frame", "too_large", "bad_target", "offline", "rate_limited"]),
956
1120
  to: RelayPeerSchema.optional()
957
1121
  })
958
1122
  ]);
959
1123
  var PAIR_LABEL_MAX = 40;
960
- var KeyB64 = z17.string().regex(/^[A-Za-z0-9_-]{87}$/);
961
- var Nonce = z17.string().regex(/^[A-Za-z0-9_-]{43}$/);
962
- var Fingerprint = z17.string().regex(/^[A-Za-z0-9_-]{22}$/);
963
- var SealedFrameSchema = z17.discriminatedUnion("k", [
1124
+ var KeyB64 = z18.string().regex(/^[A-Za-z0-9_-]{87}$/);
1125
+ var Nonce = z18.string().regex(/^[A-Za-z0-9_-]{43}$/);
1126
+ var Fingerprint = z18.string().regex(/^[A-Za-z0-9_-]{22}$/);
1127
+ var SealedFrameSchema = z18.discriminatedUnion("k", [
964
1128
  /** `e` is the ephemeral key, `f` the fingerprint of the sender's static key. */
965
- z17.object({ k: z17.literal("hello"), e: KeyB64, f: Fingerprint }),
966
- z17.object({ k: z17.literal("msg"), s: z17.string() }),
967
- z17.object({ k: z17.literal("pair_commit"), c: Nonce, label: z17.string().trim().min(1).max(PAIR_LABEL_MAX) }),
968
- z17.object({ k: z17.literal("pair_offer"), pc: KeyB64, n: Nonce }),
969
- z17.object({ k: z17.literal("pair_reveal"), d: KeyB64, n: Nonce }),
970
- z17.object({ k: z17.literal("pair_ok") }),
971
- z17.object({ k: z17.literal("pair_no") }),
972
- z17.object({ k: z17.literal("denied") }),
973
- z17.object({ k: z17.literal("rekey") })
1129
+ z18.object({ k: z18.literal("hello"), e: KeyB64, f: Fingerprint }),
1130
+ z18.object({ k: z18.literal("msg"), s: z18.string() }),
1131
+ z18.object({ k: z18.literal("pair_commit"), c: Nonce, label: z18.string().trim().min(1).max(PAIR_LABEL_MAX) }),
1132
+ z18.object({ k: z18.literal("pair_offer"), pc: KeyB64, n: Nonce }),
1133
+ z18.object({ k: z18.literal("pair_reveal"), d: KeyB64, n: Nonce }),
1134
+ z18.object({ k: z18.literal("pair_ok") }),
1135
+ z18.object({ k: z18.literal("pair_no") }),
1136
+ z18.object({ k: z18.literal("denied") }),
1137
+ z18.object({ k: z18.literal("rekey") })
974
1138
  ]);
975
1139
 
976
1140
  // ../protocol/src/relayAuth.ts
977
- import { z as z18 } from "zod";
1141
+ import { z as z19 } from "zod";
978
1142
  var RELAY_CLOSE_SIGNED_OUT = 4001;
979
- var DeviceStartResponseSchema = z18.object({
980
- deviceCode: z18.string().min(1),
981
- userCode: z18.string().min(1),
982
- verificationUri: z18.string().min(1),
983
- expiresIn: z18.number().int().positive(),
984
- interval: z18.number().int().positive()
1143
+ var DeviceStartResponseSchema = z19.object({
1144
+ deviceCode: z19.string().min(1),
1145
+ userCode: z19.string().min(1),
1146
+ verificationUri: z19.string().min(1),
1147
+ expiresIn: z19.number().int().positive(),
1148
+ interval: z19.number().int().positive()
985
1149
  });
986
- var DevicePollResponseSchema = z18.discriminatedUnion("status", [
987
- z18.object({ status: z18.literal("pending") }),
988
- z18.object({ status: z18.literal("slow_down"), interval: z18.number().int().positive() }),
1150
+ var DevicePollResponseSchema = z19.discriminatedUnion("status", [
1151
+ z19.object({ status: z19.literal("pending") }),
1152
+ z19.object({ status: z19.literal("slow_down"), interval: z19.number().int().positive() }),
989
1153
  /** `token` is the relay session token, shown this once only. */
990
- z18.object({ status: z18.literal("ok"), token: z18.string().min(1), accountId: z18.string().min(1), login: z18.string().min(1) }),
991
- z18.object({ status: z18.literal("expired") }),
992
- z18.object({ status: z18.literal("denied") }),
993
- z18.object({ status: z18.literal("not_allowed") })
1154
+ z19.object({ status: z19.literal("ok"), token: z19.string().min(1), accountId: z19.string().min(1), login: z19.string().min(1) }),
1155
+ z19.object({ status: z19.literal("expired") }),
1156
+ z19.object({ status: z19.literal("denied") }),
1157
+ z19.object({ status: z19.literal("not_allowed") })
994
1158
  ]);
995
- var DeviceRevokeResponseSchema = z18.object({ revoked: z18.number().int().min(0) });
996
- var MeResponseSchema = z18.object({
997
- accountId: z18.string().min(1),
998
- login: z18.string().min(1),
1159
+ var DeviceRevokeResponseSchema = z19.object({ revoked: z19.number().int().min(0) });
1160
+ var MeResponseSchema = z19.object({
1161
+ accountId: z19.string().min(1),
1162
+ login: z19.string().min(1),
999
1163
  peer: RelayPeerSchema,
1000
- kind: z18.enum(["pc", "device"])
1164
+ kind: z19.enum(["pc", "device"])
1001
1165
  });
1002
1166
 
1003
1167
  // ../protocol/src/phone.ts
1004
- import { z as z20 } from "zod";
1168
+ import { z as z21 } from "zod";
1005
1169
 
1006
1170
  // ../protocol/src/push.ts
1007
- import { z as z19 } from "zod";
1008
- var B64U = z19.string().regex(/^[A-Za-z0-9_-]*$/);
1171
+ import { z as z20 } from "zod";
1172
+ var B64U = z20.string().regex(/^[A-Za-z0-9_-]*$/);
1009
1173
  var PUSH_HOSTS = ["*.push.apple.com", "fcm.googleapis.com", "updates.push.services.mozilla.com", "*.notify.windows.com"];
1010
1174
  function pushEndpointAllowed(url) {
1011
1175
  let u;
@@ -1022,29 +1186,29 @@ function pushEndpointAllowed(url) {
1022
1186
  return host.length > suffix.length && host.endsWith(suffix);
1023
1187
  });
1024
1188
  }
1025
- var PushSendSchema = z19.object({
1026
- endpoint: z19.string().url().max(1024),
1189
+ var PushSendSchema = z20.object({
1190
+ endpoint: z20.string().url().max(1024),
1027
1191
  body: B64U.max(5462),
1028
- ttl: z19.number().int().min(0).max(86400),
1029
- urgency: z19.enum(["normal", "high"]),
1030
- topic: z19.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1192
+ ttl: z20.number().int().min(0).max(86400),
1193
+ urgency: z20.enum(["normal", "high"]),
1194
+ topic: z20.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1031
1195
  });
1032
- var PushSendResultSchema = z19.object({
1033
- result: z19.enum(["sent", "gone", "failed"]),
1034
- status: z19.number().int().optional()
1196
+ var PushSendResultSchema = z20.object({
1197
+ result: z20.enum(["sent", "gone", "failed"]),
1198
+ status: z20.number().int().optional()
1035
1199
  });
1036
- var PushSubscriptionSchema = z19.object({
1037
- endpoint: z19.string().url().max(1024).refine(pushEndpointAllowed),
1038
- keys: z19.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1200
+ var PushSubscriptionSchema = z20.object({
1201
+ endpoint: z20.string().url().max(1024).refine(pushEndpointAllowed),
1202
+ keys: z20.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1039
1203
  });
1040
- var PushPayloadSchema = z19.object({
1041
- v: z19.literal(1),
1042
- kind: z19.enum(["request", "question", "finished", "chat"]),
1043
- title: z19.string().max(80),
1044
- body: z19.string().max(120),
1045
- url: z19.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
1046
- badge: z19.number().int().min(0).max(999),
1047
- tag: z19.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1204
+ var PushPayloadSchema = z20.object({
1205
+ v: z20.literal(1),
1206
+ kind: z20.enum(["request", "question", "finished", "chat"]),
1207
+ title: z20.string().max(80),
1208
+ body: z20.string().max(120),
1209
+ url: z20.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
1210
+ badge: z20.number().int().min(0).max(999),
1211
+ tag: z20.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1048
1212
  });
1049
1213
 
1050
1214
  // ../protocol/src/phone.ts
@@ -1065,22 +1229,22 @@ var THREAD_TEXT_MAX = 8192;
1065
1229
  var TOOL_TEXT_MAX = 2048;
1066
1230
  var PENDING_BODY_MAX = 65536;
1067
1231
  var SNAPSHOT_BODY_MAX = 2048;
1068
- var RiskSchema = z20.enum(["low", "high"]);
1069
- var RiskReasonSchema = z20.enum(["question", "read_only_command", "edit_inside_folder", "pushes", "destroys", "deploys", "publishes", "pipes_to_shell", "network", "writes_outside_folder", "sensitive_path", "unsafe_flag", "shell_syntax", "script", "plan", "unknown"]);
1070
- var OperationIdSchema = z20.string().uuid();
1071
- var OperationStatusSchema = z20.enum(["received", "applied", "rejected", "failed"]);
1072
- var OperationReasonSchema = z20.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired"]);
1073
- var CapabilitiesSchema = z20.object({
1232
+ var RiskSchema = z21.enum(["low", "high"]);
1233
+ var RiskReasonSchema = z21.enum(["question", "read_only_command", "edit_inside_folder", "pushes", "destroys", "deploys", "publishes", "pipes_to_shell", "network", "writes_outside_folder", "sensitive_path", "unsafe_flag", "shell_syntax", "script", "plan", "unknown"]);
1234
+ var OperationIdSchema = z21.string().uuid();
1235
+ var OperationStatusSchema = z21.enum(["received", "applied", "rejected", "failed"]);
1236
+ var OperationReasonSchema = z21.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired"]);
1237
+ var CapabilitiesSchema = z21.object({
1074
1238
  /** A question can be answered in free text. */
1075
- freeTextAnswer: z20.boolean(),
1239
+ freeTextAnswer: z21.boolean(),
1076
1240
  /** The session's accept/deny can be typed; absent = true. */
1077
- decide: z20.boolean().optional(),
1241
+ decide: z21.boolean().optional(),
1078
1242
  /** OMP's dialog questions can be answered from the phone; absent = false. */
1079
- questions: z20.boolean().optional()
1243
+ questions: z21.boolean().optional()
1080
1244
  });
1081
1245
  var UNTYPABLE = /[\u0000-\u001f\u007f-\u009f\u200b\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
1082
- var CapabilitiesByToolSchema = z20.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
1083
- var PhonePendingSchema = PendingSchema.extend({ key: z20.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z20.literal(true).optional() });
1246
+ var CapabilitiesByToolSchema = z21.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
1247
+ var PhonePendingSchema = PendingSchema.extend({ key: z21.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z21.literal(true).optional() });
1084
1248
  var PhoneSessionSchema = SessionSchema.pick({
1085
1249
  id: true,
1086
1250
  name: true,
@@ -1096,272 +1260,160 @@ var PhoneSessionSchema = SessionSchema.pick({
1096
1260
  lastReply: true,
1097
1261
  startedAt: true,
1098
1262
  lastActivityAt: true
1099
- }).extend({ pending: PhonePendingSchema.optional(), unread: z20.boolean() });
1100
- var B64U2 = z20.string().regex(/^[A-Za-z0-9_-]*$/);
1101
- var CredentialSchema = z20.object({
1263
+ }).extend({ pending: PhonePendingSchema.optional(), unread: z21.boolean() });
1264
+ var B64U2 = z21.string().regex(/^[A-Za-z0-9_-]*$/);
1265
+ var CredentialSchema = z21.object({
1102
1266
  id: B64U2.max(1366),
1103
1267
  publicKey: B64U2.max(2048),
1104
- alg: z20.union([z20.literal(-7), z20.literal(-257)]),
1268
+ alg: z21.union([z21.literal(-7), z21.literal(-257)]),
1105
1269
  clientDataJSON: B64U2.max(4096),
1106
1270
  authenticatorData: B64U2.max(4096),
1107
- transports: z20.array(z20.string().max(20)).max(8).optional()
1271
+ transports: z21.array(z21.string().max(20)).max(8).optional()
1108
1272
  });
1109
- var AssertionSchema = z20.object({
1273
+ var AssertionSchema = z21.object({
1110
1274
  id: B64U2.max(1366),
1111
1275
  clientDataJSON: B64U2.max(4096),
1112
1276
  authenticatorData: B64U2.max(4096),
1113
1277
  signature: B64U2.max(1024)
1114
1278
  });
1115
- var ConfirmPurposeSchema = z20.enum(["register", "decide", "launch", "close"]);
1279
+ var ConfirmPurposeSchema = z21.enum(["register", "decide", "launch", "close"]);
1116
1280
  var PhoneChatStepSchema = ChatStepSchema.pick({ kind: true, id: true, name: true }).strict();
1117
- var PhoneChatProposalSchema = z20.object({
1118
- project: z20.string().max(80),
1281
+ var PhoneChatProposalSchema = z21.object({
1282
+ project: z21.string().max(80),
1119
1283
  tool: ToolSchema,
1120
- prompt: z20.string().max(TOOL_TEXT_MAX).optional(),
1121
- launchedId: z20.string().optional(),
1122
- failed: z20.literal(true).optional()
1284
+ prompt: z21.string().max(TOOL_TEXT_MAX).optional(),
1285
+ launchedId: z21.string().optional(),
1286
+ failed: z21.literal(true).optional()
1123
1287
  }).strict();
1124
- var PhoneChatRelaySchema = z20.object({
1125
- id: z20.string().min(1),
1126
- name: z20.string(),
1288
+ var PhoneChatRelaySchema = z21.object({
1289
+ id: z21.string().min(1),
1290
+ name: z21.string(),
1127
1291
  tool: ToolSchema,
1128
- text: z20.string().max(TOOL_TEXT_MAX),
1129
- status: z20.enum(["queued", "sent"]).optional(),
1130
- sentAt: z20.string().datetime().optional(),
1131
- failed: z20.literal(true).optional()
1292
+ text: z21.string().max(TOOL_TEXT_MAX),
1293
+ status: z21.enum(["queued", "sent"]).optional(),
1294
+ sentAt: z21.string().datetime().optional(),
1295
+ failed: z21.literal(true).optional()
1132
1296
  }).strict();
1133
- var PhoneChatCloseSchema = z20.object({
1134
- id: z20.string().min(1),
1135
- name: z20.string(),
1297
+ var PhoneChatCloseSchema = z21.object({
1298
+ id: z21.string().min(1),
1299
+ name: z21.string(),
1136
1300
  tool: ToolSchema,
1137
- how: z20.enum(["exit", "killed"]).optional(),
1138
- closedAt: z20.string().datetime().optional(),
1139
- failed: z20.literal(true).optional()
1301
+ how: z21.enum(["exit", "killed"]).optional(),
1302
+ closedAt: z21.string().datetime().optional(),
1303
+ failed: z21.literal(true).optional()
1140
1304
  }).strict();
1141
- var ChatErrorCodeSchema = z20.enum(["busy", "timeout", "cancelled", "failed"]);
1142
- var PhoneChatTurnSchema = z20.object({
1305
+ var ChatErrorCodeSchema = z21.enum(["busy", "timeout", "cancelled", "failed"]);
1306
+ var PhoneChatTurnSchema = z21.object({
1143
1307
  qid: OperationIdSchema,
1144
- question: z20.string().max(MESSAGE_MAX),
1145
- askedAt: z20.string().datetime(),
1146
- steps: z20.array(PhoneChatStepSchema).max(50),
1147
- answer: z20.string().max(THREAD_TEXT_MAX).optional(),
1148
- answeredAt: z20.string().datetime().optional(),
1308
+ question: z21.string().max(MESSAGE_MAX),
1309
+ askedAt: z21.string().datetime(),
1310
+ steps: z21.array(PhoneChatStepSchema).max(50),
1311
+ answer: z21.string().max(THREAD_TEXT_MAX).optional(),
1312
+ answeredAt: z21.string().datetime().optional(),
1149
1313
  error: ChatErrorCodeSchema.optional(),
1150
- proposals: z20.array(PhoneChatProposalSchema).max(MAX_PROPOSALS).optional(),
1314
+ proposals: z21.array(PhoneChatProposalSchema).max(MAX_PROPOSALS).optional(),
1151
1315
  relay: PhoneChatRelaySchema.optional(),
1152
- closes: z20.array(PhoneChatCloseSchema).min(1).max(MAX_PROPOSALS).optional(),
1153
- cut: z20.literal(true).optional()
1316
+ closes: z21.array(PhoneChatCloseSchema).min(1).max(MAX_PROPOSALS).optional(),
1317
+ cut: z21.literal(true).optional()
1154
1318
  }).strict();
1155
1319
  var CHAT_HISTORY_BUDGET = 393216;
1156
- var sessionId = z20.string().min(1).max(100);
1157
- var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z20.string().min(1).max(64) });
1158
- var CardRowsSchema = z20.array(z20.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
1159
- var PhoneClientMessageSchema = z20.discriminatedUnion("type", [
1160
- z20.object({ type: z20.literal("snapshot.request") }),
1161
- z20.object({ type: z20.literal("session.thread"), id: sessionId }),
1320
+ var sessionId = z21.string().min(1).max(100);
1321
+ var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z21.string().min(1).max(64) });
1322
+ var CardRowsSchema = z21.array(z21.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
1323
+ var PhoneClientMessageSchema = z21.discriminatedUnion("type", [
1324
+ z21.object({ type: z21.literal("snapshot.request") }),
1325
+ z21.object({ type: z21.literal("session.thread"), id: sessionId }),
1162
1326
  /** The session's request with its whole body (up to `PENDING_BODY_MAX`). */
1163
- z20.object({ type: z20.literal("session.pending"), id: sessionId }),
1327
+ z21.object({ type: z21.literal("session.pending"), id: sessionId }),
1164
1328
  /** The session whose terminal the phone shows; null when it shows none. */
1165
- z20.object({ type: z20.literal("session.watch"), id: sessionId.nullable() }),
1166
- z20.object({ type: z20.literal("session.read"), id: sessionId, at: z20.string().datetime() }),
1329
+ z21.object({ type: z21.literal("session.watch"), id: sessionId.nullable() }),
1330
+ z21.object({ type: z21.literal("session.read"), id: sessionId, at: z21.string().datetime() }),
1167
1331
  /** Asks for a challenge. One object for both purposes: the gateway refuses a `decide` without `id` and `expect`. */
1168
- z20.object({ type: z20.literal("confirm.prepare"), purpose: ConfirmPurposeSchema, id: sessionId.optional(), expect: PhoneExpectSchema.optional(), qid: OperationIdSchema.optional() }),
1169
- z20.object({ type: z20.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
1170
- z20.object({
1171
- type: z20.literal("session.decide"),
1332
+ z21.object({ type: z21.literal("confirm.prepare"), purpose: ConfirmPurposeSchema, id: sessionId.optional(), expect: PhoneExpectSchema.optional(), qid: OperationIdSchema.optional() }),
1333
+ z21.object({ type: z21.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
1334
+ z21.object({
1335
+ type: z21.literal("session.decide"),
1172
1336
  op: OperationIdSchema,
1173
1337
  id: sessionId,
1174
1338
  decision: DecisionSchema,
1175
- note: z20.string().trim().min(1).max(NOTE_MAX).optional(),
1339
+ note: z21.string().trim().min(1).max(NOTE_MAX).optional(),
1176
1340
  /** The request the phone showed: refused unless it is still the pending one. */
1177
1341
  expect: PhoneExpectSchema,
1178
1342
  /** The device check over the challenge of `confirm.prepare`; required by the daemon for a high-risk accept. */
1179
1343
  assertion: AssertionSchema.optional()
1180
1344
  }),
1181
- z20.object({
1182
- type: z20.literal("session.reply"),
1345
+ z21.object({
1346
+ type: z21.literal("session.reply"),
1183
1347
  op: OperationIdSchema,
1184
1348
  id: sessionId,
1185
- text: z20.string().min(1).max(MESSAGE_MAX),
1349
+ text: z21.string().min(1).max(MESSAGE_MAX),
1186
1350
  /** The request the reply answers, or null when the phone showed none: refused if the session's request is no longer that one. */
1187
1351
  expect: PhoneExpectSchema.nullable(),
1188
1352
  assertion: AssertionSchema.optional()
1189
1353
  }),
1190
1354
  /** OMP's `ask`: the answers to every question of the dialog `requestId`, in order; `cancel` declines it. Bounds as the local `session.answer`. */
1191
- z20.object({
1192
- type: z20.literal("session.answer"),
1355
+ z21.object({
1356
+ type: z21.literal("session.answer"),
1193
1357
  op: OperationIdSchema,
1194
1358
  id: sessionId,
1195
- requestId: z20.string().min(1).max(200),
1196
- answers: z20.array(z20.object({ id: z20.string().max(200), selected: z20.array(z20.string().max(2e3)).max(100), text: z20.string().max(2e4).optional() })).max(50),
1197
- cancel: z20.literal(true).optional(),
1359
+ requestId: z21.string().min(1).max(200),
1360
+ answers: z21.array(z21.object({ id: z21.string().max(200), selected: z21.array(z21.string().max(2e3)).max(100), text: z21.string().max(2e4).optional() })).max(50),
1361
+ cancel: z21.literal(true).optional(),
1198
1362
  expect: PhoneExpectSchema
1199
1363
  }),
1200
- z20.object({ type: z20.literal("session.interrupt"), op: OperationIdSchema, id: sessionId }),
1364
+ z21.object({ type: z21.literal("session.interrupt"), op: OperationIdSchema, id: sessionId }),
1201
1365
  /** The phone drops its passkey from the daemon. */
1202
- z20.object({ type: z20.literal("device.forget"), op: OperationIdSchema }),
1366
+ z21.object({ type: z21.literal("device.forget"), op: OperationIdSchema }),
1203
1367
  /** The phone's push subscription, with the VAPID key it was made with (so a key change is seen). */
1204
- z20.object({ type: z20.literal("push.subscribe"), op: OperationIdSchema, subscription: PushSubscriptionSchema, key: B64U2.length(87) }),
1205
- z20.object({ type: z20.literal("push.unsubscribe"), op: OperationIdSchema }),
1368
+ z21.object({ type: z21.literal("push.subscribe"), op: OperationIdSchema, subscription: PushSubscriptionSchema, key: B64U2.length(87) }),
1369
+ z21.object({ type: z21.literal("push.unsubscribe"), op: OperationIdSchema }),
1206
1370
  /** The phone's own chat with Ledge. */
1207
- z20.object({ type: z20.literal("chat.ask"), qid: OperationIdSchema, text: z20.string().trim().min(1).max(MESSAGE_MAX) }),
1208
- z20.object({ type: z20.literal("chat.cancel"), qid: OperationIdSchema }),
1209
- z20.object({ type: z20.literal("chat.reset") }),
1210
- z20.object({ type: z20.literal("chat.history") }),
1211
- z20.object({ type: z20.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
1212
- z20.object({ type: z20.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
1213
- z20.object({ type: z20.literal("chat.close"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
1371
+ z21.object({ type: z21.literal("chat.ask"), qid: OperationIdSchema, text: z21.string().trim().min(1).max(MESSAGE_MAX) }),
1372
+ z21.object({ type: z21.literal("chat.cancel"), qid: OperationIdSchema }),
1373
+ z21.object({ type: z21.literal("chat.reset") }),
1374
+ z21.object({ type: z21.literal("chat.history") }),
1375
+ z21.object({ type: z21.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
1376
+ z21.object({ type: z21.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
1377
+ z21.object({ type: z21.literal("chat.close"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
1214
1378
  /** Whether the phone's app is in front: a device that is visible gets no push. */
1215
- z20.object({ type: z20.literal("presence"), visible: z20.boolean() })
1379
+ z21.object({ type: z21.literal("presence"), visible: z21.boolean() })
1216
1380
  ]);
1217
- var PhoneServerMessageSchema = z20.discriminatedUnion("type", [
1218
- z20.object({ type: z20.literal("snapshot"), protocol: z20.literal(PHONE_PROTOCOL), sessions: z20.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z20.object({ version: z20.string(), name: z20.string().max(40), boot: z20.string().uuid().optional() }), device: z20.object({ confirm: z20.boolean(), push: z20.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z20.literal(true).optional() }).optional() }),
1219
- z20.object({ type: z20.literal("confirm.challenge"), purpose: ConfirmPurposeSchema, challenge: B64U2.max(64), rpId: z20.string().max(253), credentialId: B64U2.max(1366).optional(), expiresInMs: z20.number().int().positive() }),
1220
- z20.object({ type: z20.literal("session.updated"), session: PhoneSessionSchema }),
1221
- z20.object({ type: z20.literal("session.removed"), id: z20.string() }),
1381
+ var PhoneServerMessageSchema = z21.discriminatedUnion("type", [
1382
+ z21.object({ type: z21.literal("snapshot"), protocol: z21.literal(PHONE_PROTOCOL), sessions: z21.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z21.object({ version: z21.string(), name: z21.string().max(40), boot: z21.string().uuid().optional() }), device: z21.object({ confirm: z21.boolean(), push: z21.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z21.literal(true).optional() }).optional() }),
1383
+ z21.object({ type: z21.literal("confirm.challenge"), purpose: ConfirmPurposeSchema, challenge: B64U2.max(64), rpId: z21.string().max(253), credentialId: B64U2.max(1366).optional(), expiresInMs: z21.number().int().positive() }),
1384
+ z21.object({ type: z21.literal("session.updated"), session: PhoneSessionSchema }),
1385
+ z21.object({ type: z21.literal("session.removed"), id: z21.string() }),
1222
1386
  /** `pending.cut` is set when even this body was shortened. */
1223
- z20.object({ type: z20.literal("session.pending"), id: z20.string(), pending: PhonePendingSchema }),
1224
- z20.object({ type: z20.literal("session.preview"), id: z20.string(), lines: z20.array(z20.string()) }),
1387
+ z21.object({ type: z21.literal("session.pending"), id: z21.string(), pending: PhonePendingSchema }),
1388
+ z21.object({ type: z21.literal("session.preview"), id: z21.string(), lines: z21.array(z21.string()) }),
1225
1389
  /** `cut`: the ids of the items shortened; `more`: older items were left out. */
1226
- z20.object({ type: z20.literal("session.thread"), id: z20.string(), items: z20.array(ThreadItemSchema), cut: z20.array(z20.string()), more: z20.boolean() }),
1227
- z20.object({ type: z20.literal("session.thread.item"), id: z20.string(), item: ThreadItemSchema, cut: z20.literal(true).optional() }),
1228
- z20.object({ type: z20.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional() }),
1229
- z20.object({ type: z20.literal("chat.history"), turns: z20.array(PhoneChatTurnSchema).max(50) }),
1230
- z20.object({ type: z20.literal("chat.step"), qid: OperationIdSchema, step: PhoneChatStepSchema }),
1390
+ z21.object({ type: z21.literal("session.thread"), id: z21.string(), items: z21.array(ThreadItemSchema), cut: z21.array(z21.string()), more: z21.boolean() }),
1391
+ z21.object({ type: z21.literal("session.thread.item"), id: z21.string(), item: ThreadItemSchema, cut: z21.literal(true).optional() }),
1392
+ z21.object({ type: z21.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional() }),
1393
+ z21.object({ type: z21.literal("chat.history"), turns: z21.array(PhoneChatTurnSchema).max(50) }),
1394
+ z21.object({ type: z21.literal("chat.step"), qid: OperationIdSchema, step: PhoneChatStepSchema }),
1231
1395
  /** The whole text so far, not an increment. */
1232
- z20.object({ type: z20.literal("chat.delta"), qid: OperationIdSchema, text: z20.string().max(THREAD_TEXT_MAX) }),
1233
- z20.object({ type: z20.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
1234
- z20.object({ type: z20.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
1396
+ z21.object({ type: z21.literal("chat.delta"), qid: OperationIdSchema, text: z21.string().max(THREAD_TEXT_MAX) }),
1397
+ z21.object({ type: z21.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
1398
+ z21.object({ type: z21.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
1235
1399
  /** Never carries free text. */
1236
- z20.object({ type: z20.literal("error"), code: z20.enum(["invalid", "unknown_session", "too_large"]), request: z20.string().max(40).optional() })
1400
+ z21.object({ type: z21.literal("error"), code: z21.enum(["invalid", "unknown_session", "too_large"]), request: z21.string().max(40).optional() })
1237
1401
  ]);
1238
1402
 
1239
- // src/auth.ts
1240
- import { createHash, randomBytes, timingSafeEqual } from "crypto";
1241
- import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
1242
- import { readFileSync } from "fs";
1243
- import { dirname } from "path";
1244
- var TOKEN_RE = /^[0-9a-f]{64}$/;
1245
- var DAEMON_PROTOCOL_PREFIX = "ledge.";
1246
- function isDaemonToken(value) {
1247
- return TOKEN_RE.test(value);
1248
- }
1249
- function tokenEquals(presented, expected) {
1250
- const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
1251
- const b = createHash("sha256").update(expected, "utf8").digest();
1252
- return timingSafeEqual(a, b);
1253
- }
1254
- function hostAllowed(hostHeader) {
1255
- const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
1256
- if (!raw || raw.length > 255) return false;
1257
- const host = raw.trim().toLowerCase();
1258
- if (!host || host.includes(" ") || host.endsWith(".")) return false;
1259
- let name;
1260
- if (host.startsWith("[")) {
1261
- const end = host.indexOf("]");
1262
- if (end < 0) return false;
1263
- name = host.slice(1, end);
1264
- const rest = host.slice(end + 1);
1265
- if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
1266
- } else {
1267
- const colon = host.lastIndexOf(":");
1268
- if (colon >= 0) {
1269
- if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
1270
- name = host.slice(0, colon);
1271
- } else name = host;
1272
- }
1273
- return name === "localhost" || name === "127.0.0.1" || name === "::1";
1274
- }
1275
- function headerValue(value, max) {
1276
- const raw = Array.isArray(value) ? value.join(",") : value;
1277
- return !raw || raw.length > max ? "" : raw;
1278
- }
1279
- function bearerToken(authorization) {
1280
- if (!authorization || authorization.length > 512) return "";
1281
- return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
1282
- }
1283
- function protocolToken(header) {
1284
- const raw = headerValue(header, 512);
1285
- if (!raw) return "";
1286
- for (const part of raw.split(",").slice(0, 8)) {
1287
- const protocol = part.trim();
1288
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
1289
- }
1290
- return "";
1291
- }
1292
- function requestAuthorized(headers, expected) {
1293
- const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
1294
- const presented = protocolToken(headers["sec-websocket-protocol"]);
1295
- return bearerOk || presented !== "" && tokenEquals(presented, expected);
1296
- }
1297
- function selectDaemonProtocol(protocols, expected) {
1298
- for (const protocol of protocols) {
1299
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
1300
- }
1301
- return false;
1302
- }
1303
- async function readTokenFile(file) {
1304
- try {
1305
- const raw = (await readFile(file, "utf8")).trim();
1306
- return isDaemonToken(raw) ? raw : void 0;
1307
- } catch (e) {
1308
- if (e.code === "ENOENT") return void 0;
1309
- throw e;
1310
- }
1311
- }
1312
- async function lockTokenFile(file) {
1313
- await chmod(file, 384);
1314
- }
1315
- async function ensureDaemonToken() {
1316
- const file = paths().token;
1317
- await mkdir(dirname(file), { recursive: true });
1318
- const current = await readTokenFile(file);
1319
- if (current) {
1320
- await lockTokenFile(file);
1321
- return current;
1322
- }
1323
- const token = randomBytes(32).toString("hex");
1324
- const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
1325
- await writeFile(tmp, `${token}
1326
- `, { mode: 384, flag: "wx" });
1327
- await chmod(tmp, 384);
1328
- try {
1329
- await link(tmp, file);
1330
- } catch (e) {
1331
- await unlink(tmp).catch(() => {
1332
- });
1333
- if (e.code !== "EEXIST") throw e;
1334
- const raced = await readTokenFile(file);
1335
- if (raced) {
1336
- await lockTokenFile(file);
1337
- return raced;
1338
- }
1339
- await writeFile(file, `${token}
1340
- `, { mode: 384 });
1341
- }
1342
- await unlink(tmp).catch(() => {
1343
- });
1344
- await lockTokenFile(file);
1345
- const stored = await readTokenFile(file);
1346
- if (!stored) throw new Error("daemon token was not stored");
1347
- return stored;
1348
- }
1349
- function readDaemonToken() {
1350
- try {
1351
- const raw = readFileSync(paths().token, "utf8").trim();
1352
- return isDaemonToken(raw) ? raw : void 0;
1353
- } catch {
1354
- return void 0;
1355
- }
1356
- }
1357
-
1358
1403
  export {
1404
+ hostAllowed,
1405
+ requestAuthorized,
1406
+ selectDaemonProtocol,
1407
+ ensureDaemonToken,
1408
+ readDaemonToken,
1359
1409
  ToolSchema,
1360
1410
  SessionSchema,
1361
1411
  backendOf,
1362
1412
  isArchived,
1363
1413
  MAX_PROPOSALS,
1414
+ ChatTurnSchema,
1364
1415
  proposalRows,
1416
+ NOTE_MAX,
1365
1417
  NOTE_NOT_TYPED,
1366
1418
  offers,
1367
1419
  pendingKey,
@@ -1369,6 +1421,7 @@ export {
1369
1421
  SshAliasSchema,
1370
1422
  MachinesSchema,
1371
1423
  IslandPrefsSchema,
1424
+ PROJECT_COLORS,
1372
1425
  ProjectsSchema,
1373
1426
  DEFAULT_PROJECTS,
1374
1427
  projectOf,
@@ -1377,6 +1430,9 @@ export {
1377
1430
  RouteResultSchema,
1378
1431
  DEFAULT_RELAY_URL,
1379
1432
  MobileConfigSchema,
1433
+ TASK_TITLE_MAX,
1434
+ TASK_TEXT_MAX,
1435
+ taskClosed,
1380
1436
  PROTOCOL_PORT,
1381
1437
  RefusalCodeSchema,
1382
1438
  RecentSchema,
@@ -1414,10 +1470,5 @@ export {
1414
1470
  OperationIdSchema,
1415
1471
  UNTYPABLE,
1416
1472
  CHAT_HISTORY_BUDGET,
1417
- PhoneClientMessageSchema,
1418
- hostAllowed,
1419
- requestAuthorized,
1420
- selectDaemonProtocol,
1421
- ensureDaemonToken,
1422
- readDaemonToken
1473
+ PhoneClientMessageSchema
1423
1474
  };