@paradow/ledge 0.20.0 → 0.22.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 (40) hide show
  1. package/dist/cli/action-WNQ3NJGD.js +27 -0
  2. package/dist/cli/{attach-JNECAG6L.js → attach-UHM7QQ4W.js} +9 -8
  3. package/dist/cli/{bridge-H3ZPGIUY.js → bridge-R37AHVXS.js} +2 -1
  4. package/dist/cli/chunk-4DEVJE4H.js +6 -0
  5. package/dist/cli/{chunk-ZQON4EV7.js → chunk-5VFFJ3ZC.js} +2 -2
  6. package/dist/cli/{chunk-CDME3WRV.js → chunk-7XMGYJTN.js} +7 -7
  7. package/dist/cli/chunk-ARB4BKZJ.js +2302 -0
  8. package/dist/cli/{chunk-24AHC55Y.js → chunk-AVKFQSRP.js} +7 -21
  9. package/dist/cli/{chunk-2VSEIOGA.js → chunk-JNLARCUC.js} +7 -4
  10. package/dist/cli/{chunk-NVSBPIXY.js → chunk-KRFXWD5S.js} +1147 -406
  11. package/dist/cli/chunk-O6FTIYJV.js +85 -0
  12. package/dist/cli/{chunk-ERN736NQ.js → chunk-PUJLMKZC.js} +7 -6
  13. package/dist/cli/{chunk-ZZMZDAF6.js → chunk-RKV5KYKL.js} +3 -3
  14. package/dist/cli/{chunk-XR42PIDL.js → chunk-RYUDIFSI.js} +15 -10
  15. package/dist/cli/{chunk-AJFC33TH.js → chunk-TOFVC6IV.js} +2 -2
  16. package/dist/cli/{chunk-KACRWFN3.js → chunk-TZBI2RVU.js} +1 -1
  17. package/dist/cli/chunk-U67V476Y.js +35 -0
  18. package/dist/cli/{chunk-35BPMR6P.js → chunk-ZDEEHFU3.js} +9 -4
  19. package/dist/cli/{daemon-DVPDCY3V.js → daemon-7IGQIDWB.js} +8 -7
  20. package/dist/cli/decide-UWJMKDR4.js +57 -0
  21. package/dist/cli/{handover-AZ35QZRK.js → handover-5JHFA5EF.js} +5 -4
  22. package/dist/cli/{hook-ERYA6BKY.js → hook-XBSMUMZ7.js} +11 -10
  23. package/dist/cli/index.js +31 -17
  24. package/dist/cli/{launch-LAER5DI6.js → launch-OVB66BZJ.js} +9 -8
  25. package/dist/cli/{send-BFPJBXXH.js → send-Y2C3BIGK.js} +5 -4
  26. package/dist/cli/{setup-47ODKJBM.js → setup-K42IVMHS.js} +1 -0
  27. package/dist/cli/{status-JPF4NZNI.js → status-5YEORNTH.js} +3 -2
  28. package/dist/cli/{task-K43WJIUX.js → task-R44VWZHR.js} +5 -4
  29. package/dist/cli/team-F7B3IG3I.js +205 -0
  30. package/dist/cli/tui-ZJDNIBIB.js +19086 -0
  31. package/dist/cli/{uninstall-QNWRD27D.js → uninstall-4IMMR55T.js} +1 -0
  32. package/dist/cli/version-EHETEZAK.js +7 -0
  33. package/dist/cli/who-YMSQI64I.js +12 -0
  34. package/package.json +2 -1
  35. package/dist/cli/chunk-EX66KH66.js +0 -1856
  36. package/dist/cli/chunk-PFBZUINN.js +0 -6
  37. package/dist/cli/team-SZJHGA7D.js +0 -101
  38. package/dist/cli/tui-AMERXSL7.js +0 -4873
  39. package/dist/cli/version-BFNXT3H4.js +0 -6
  40. package/dist/cli/who-NYPVBO64.js +0 -11
@@ -1,1856 +0,0 @@
1
- import {
2
- paths
3
- } from "./chunk-HOOHS3MX.js";
4
-
5
- // ../protocol/src/handover.ts
6
- import { z } from "zod";
7
- var ContextSchema = z.object({ used: z.number().int().min(0), window: z.number().int().positive(), at: z.string().datetime() });
8
- var CONTEXT_NEAR = 0.8;
9
- var CONTEXT_NEAR_MIN = 0.5;
10
- var CONTEXT_NEAR_MAX = 0.95;
11
- var ContextPrefsSchema = z.object({ near: z.number().min(CONTEXT_NEAR_MIN).max(CONTEXT_NEAR_MAX) });
12
- var contextShare = (c) => c ? Math.min(1, c.used / c.window) : void 0;
13
- var contextNear = (c, near = CONTEXT_NEAR) => (contextShare(c) ?? 0) >= near;
14
- var HandoverStateSchema = z.enum(["briefing", "launching", "done", "failed"]);
15
- var HandoverSchema = z.object({
16
- state: HandoverStateSchema,
17
- at: z.string().datetime(),
18
- /** The relief, once launched. */
19
- to: z.string().optional(),
20
- toName: z.string().optional(),
21
- reason: z.string().optional(),
22
- /** Started by Ledge itself (a seat working without asking), not by the user. */
23
- auto: z.literal(true).optional()
24
- });
25
- var RelievesSchema = z.object({ id: z.string(), name: z.string(), at: z.string().datetime() });
26
- var BRIEF_MAX = 2e4;
27
- var ContextSetSchema = z.object({ type: z.literal("context.set"), prefs: ContextPrefsSchema });
28
- var ContextPrefsMessageSchema = z.object({ type: z.literal("context.prefs"), prefs: ContextPrefsSchema });
29
- var SessionHandoverSchema = z.object({ type: z.literal("session.handover"), rid: z.string().min(1).max(100), id: z.string().min(1) });
30
- var HandoverBriefSchema = z.object({ type: z.literal("handover.brief"), rid: z.string().min(1).max(100), by: z.string().min(1), text: z.string().min(1).max(BRIEF_MAX) });
31
- var HandoverShowSchema = z.object({ type: z.literal("handover.show"), rid: z.string().min(1).max(100), by: z.string().min(1) });
32
- var SessionHandoverResultSchema = z.object({ type: z.literal("session.handover.result"), rid: z.string(), ok: z.boolean(), reason: z.string().optional() });
33
- var HandoverResultSchema = z.object({ type: z.literal("handover.result"), rid: z.string(), text: z.string().optional(), reason: z.string().optional() });
34
-
35
- // ../protocol/src/session.ts
36
- import { z as z2 } from "zod";
37
- var ToolSchema = z2.enum(["claude", "codex", "cursor", "omp"]);
38
- var OmpApprovalSchema = z2.enum(["always-ask", "write", "yolo"]);
39
- var AutonomySchema = z2.enum(["ask", "edits", "auto"]);
40
- var BackendSchema = z2.enum(["tmux", "omp-rpc"]);
41
- var SessionStateSchema = z2.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
42
- var AskQuestionSchema = z2.object({
43
- id: z2.string(),
44
- question: z2.string(),
45
- header: z2.string().optional(),
46
- multi: z2.boolean().optional(),
47
- options: z2.array(z2.object({ label: z2.string(), description: z2.string().optional() }))
48
- });
49
- var PendingSchema = z2.object({
50
- kind: z2.enum(["question", "permission", "plan", "diff"]),
51
- text: z2.string(),
52
- /** What to review in full before deciding, when `text` is only its summary: a plan's markdown. */
53
- body: z2.string().optional(),
54
- tool: z2.string().optional(),
55
- options: z2.array(z2.string()).optional(),
56
- /** Each option's description, by index ('' when it has none); left out when no option has one. */
57
- optionDetails: z2.array(z2.string()).optional(),
58
- /** `rpc`: the harness's own protocol asked (OMP): answered on its request id, never by typing. */
59
- source: z2.enum(["hook", "pane", "rpc"]),
60
- /** OMP: every question of one `ask` call, answered together with `session.answer`; `requestId` is the dialog it answers. */
61
- questions: z2.array(AskQuestionSchema).optional(),
62
- requestId: z2.string().optional(),
63
- /** Set when the request comes from a helper (subagent): it is routed through its parent. */
64
- helperId: z2.string().optional(),
65
- /** For a helper-routed request: the state the parent returns to once it is answered (main thread may stop meanwhile). */
66
- parentState: SessionStateSchema.optional()
67
- });
68
- var ReplySchema = z2.object({
69
- text: z2.string(),
70
- at: z2.string().datetime(),
71
- /** The reply is a plan the agent proposes (Codex Plan mode): nothing in it is done yet. */
72
- plan: z2.boolean().optional()
73
- });
74
- var HelperSchema = z2.object({
75
- id: z2.string(),
76
- kind: z2.string(),
77
- task: z2.string(),
78
- state: SessionStateSchema,
79
- /** When this helper finished; retained across daemon restarts until its five-minute expiry. */
80
- finishedAt: z2.string().datetime().optional()
81
- });
82
- var TopicSourceSchema = z2.enum(["user", "pane", "prompt"]);
83
- var SessionSchema = z2.object({
84
- /** Origin is annotated by the overlay, never used as an unqualified session id on the wire. */
85
- machineId: z2.string().optional(),
86
- machineName: z2.string().optional(),
87
- repoId: z2.string().optional(),
88
- /** Annotated by the fleet when its machine is unreachable and the last snapshot is kept (keepStale, spec 2026-10-14 §6). */
89
- offline: z2.boolean().optional(),
90
- id: z2.string().min(1),
91
- name: z2.string().min(1),
92
- tool: ToolSchema,
93
- cwd: z2.string(),
94
- project: z2.string(),
95
- title: z2.string(),
96
- /** One line saying what the session is about (spec 2026-10-11 §3). */
97
- topic: z2.string().optional(),
98
- topicSource: TopicSourceSchema.optional(),
99
- /** The repository (its main worktree's folder name and absolute path), the checked-out branch, and whether `cwd` is a linked worktree (§4). */
100
- repo: z2.string().optional(),
101
- repoPath: z2.string().optional(),
102
- branch: z2.string().optional(),
103
- worktree: z2.boolean().optional(),
104
- /** The folder the agent works in now, as its last hook reported it: `cwd` stays where it started, this follows a worktree it entered. */
105
- workdir: z2.string().optional(),
106
- tmuxSession: z2.string().nullable(),
107
- /** Left out for a tmux session. `omp-rpc`: no pane, nothing to attach to; the daemon holds the process (see `agentSessionFile`). */
108
- backend: BackendSchema.optional(),
109
- state: SessionStateSchema,
110
- pending: PendingSchema.optional(),
111
- helpers: z2.array(HelperSchema).default([]),
112
- /** OMP: the instructions waiting in its own queue (typed while it works), as it reports them; left out when none. */
113
- queue: z2.array(z2.object({ text: z2.string(), mode: z2.enum(["steer", "follow_up"]) })).optional(),
114
- lastReply: ReplySchema.optional(),
115
- /** The last message this session sent to another agent: its tool_use_id, the address, the peer's Ledge session id, a one-line summary. */
116
- lastMessage: z2.object({ id: z2.string(), to: z2.string(), peer: z2.string().optional(), summary: z2.string(), at: z2.string().datetime() }).optional(),
117
- startedAt: z2.string().datetime(),
118
- lastActivityAt: z2.string().datetime(),
119
- lastHookAt: z2.string().datetime().optional(),
120
- /** The model and effort Ledge launched the agent with (#58); left out, the agent's own default. */
121
- model: z2.string().optional(),
122
- effort: z2.string().optional(),
123
- /** OMP: the approval mode it was launched with, kept by a resume; left out, `always-ask`. */
124
- approval: OmpApprovalSchema.optional(),
125
- /** The role it was launched with or took in a team (spec 2026-10-25 roles §3.3): its id, its label as it was then. */
126
- role: z2.string().optional(),
127
- roleLabel: z2.string().optional(),
128
- /** The live team it sits in (`team-…`); left out, the project's common table. */
129
- team: z2.string().optional(),
130
- /** Its role's autonomy, as launched (kept by a resume); left out, `ask`. */
131
- autonomy: AutonomySchema.optional(),
132
- /** Its context as its agent last reported it, after a turn (context handover spec §3.1); left out, not measured. */
133
- context: ContextSchema.optional(),
134
- /** Its relief by a fresh session (§3.3): asked, launched, done or failed. */
135
- handover: HandoverSchema.optional(),
136
- /** The session it relieves, when it is a relief. */
137
- relieves: RelievesSchema.optional(),
138
- /** A model change asked while the agent worked (`session.model`), applied at its next free moment; a field left out is the agent's default. */
139
- modelNext: z2.object({ model: z2.string().optional(), effort: z2.string().optional() }).optional(),
140
- /** The agent's own session id when Ledge chose it at launch (Claude Code `--session-id`). */
141
- agentSessionId: z2.string().optional(),
142
- /** OMP: the exact journal file of the conversation, what a resume names (never "the latest of the folder"). */
143
- agentSessionFile: z2.string().optional(),
144
- /** The agent's transcript: the latest one a hook named (it follows /clear and --resume), else the one the launch implies. */
145
- transcriptPath: z2.string().optional(),
146
- exitCode: z2.number().int().nullable().optional(),
147
- exitedAt: z2.string().datetime().optional(),
148
- lastError: z2.string().optional(),
149
- source: z2.enum(["launcher", "hook_only"]),
150
- /** When the session was put in the archive (session management): set, it leaves the live list and waits to be restored, resumed or deleted. */
151
- archivedAt: z2.string().datetime().optional(),
152
- /** When the session was last taken out of the archive: the poller's retirement clock restarts from it, not from `exitedAt`. */
153
- restoredAt: z2.string().datetime().optional()
154
- });
155
- function backendOf(s) {
156
- return s.backend ?? "tmux";
157
- }
158
- function isArchived(s) {
159
- return s.archivedAt !== void 0;
160
- }
161
-
162
- // ../protocol/src/tasks.ts
163
- import { z as z3 } from "zod";
164
- var TASK_TITLE_MAX = 200;
165
- var TASK_TEXT_MAX = 2e4;
166
- var LEDGE_GIVER = "ledge";
167
- var TASK_OPS = ["give", "take", "pass", "done", "refuse", "cancel", "drop"];
168
- var TRANSMISSION_OPS = ["give", "pass", "done", "refuse", "cancel"];
169
- var TaskLogEntrySchema = z3.object({
170
- at: z3.string(),
171
- by: z3.string(),
172
- what: z3.string(),
173
- op: z3.enum(TASK_OPS).optional(),
174
- from: z3.string().optional(),
175
- to: z3.string().optional(),
176
- fromName: z3.string().optional(),
177
- toName: z3.string().optional(),
178
- note: z3.string().optional()
179
- });
180
- var TaskSchema = z3.object({
181
- id: z3.string(),
182
- title: z3.string(),
183
- text: z3.string().optional(),
184
- giver: z3.string(),
185
- giverName: z3.string(),
186
- owner: z3.string(),
187
- ownerName: z3.string(),
188
- state: z3.enum(["open", "taken", "done", "refused", "dropped"]),
189
- result: z3.string().optional(),
190
- /** The live team its owner sat in when it was given (or passed), so a team's tasks can be listed. */
191
- team: z3.string().optional(),
192
- /** A task Ledge gave and the user put away from « À toi » once closed. */
193
- seen: z3.boolean().optional(),
194
- at: z3.string(),
195
- updatedAt: z3.string(),
196
- log: z3.array(TaskLogEntrySchema)
197
- });
198
- var handOver = (op) => TRANSMISSION_OPS.includes(op ?? "");
199
- function transmissions(t) {
200
- return t.log.flatMap((e, index) => handOver(e.op) && e.from !== void 0 && e.to !== void 0 ? [{ index, at: e.at, op: e.op, from: e.from, to: e.to, fromName: e.fromName ?? "", toName: e.toName ?? "", ...e.note !== void 0 ? { note: e.note } : {} }] : []);
201
- }
202
- var givenByLedge = (t) => t.giver === LEDGE_GIVER;
203
- var taskClosed = (t) => t.state === "done" || t.state === "refused" || t.state === "dropped";
204
- var TaskCmdSchema = z3.object({
205
- type: z3.literal("task.cmd"),
206
- rid: z3.string().min(1),
207
- by: z3.string().min(1),
208
- op: z3.enum(["give", "take", "done", "refuse", "pass", "show", "list", "seen"]),
209
- task: z3.string().min(1).max(40).optional(),
210
- to: z3.string().min(1).optional(),
211
- title: z3.string().min(1).max(TASK_TITLE_MAX).optional(),
212
- text: z3.string().max(TASK_TEXT_MAX).optional(),
213
- all: z3.boolean().optional(),
214
- team: z3.string().min(1).max(40).optional(),
215
- qid: z3.string().min(1).optional(),
216
- row: z3.number().int().min(0).max(7).optional()
217
- });
218
- var TaskUpdatedSchema = z3.object({ type: z3.literal("task.updated"), task: TaskSchema });
219
- var TaskResultSchema = z3.object({
220
- type: z3.literal("task.result"),
221
- rid: z3.string(),
222
- task: TaskSchema.optional(),
223
- tasks: z3.array(TaskSchema).optional(),
224
- reason: z3.string().optional()
225
- });
226
-
227
- // ../protocol/src/messages.ts
228
- import { z as z19 } from "zod";
229
-
230
- // ../protocol/src/chat.ts
231
- import { z as z5 } from "zod";
232
-
233
- // ../protocol/src/attachments.ts
234
- import { z as z4 } from "zod";
235
- var ATTACHMENT_ID = /^[0-9a-f]{64}$/;
236
- var AttachmentIdSchema = z4.string().regex(ATTACHMENT_ID);
237
- var IMAGE_MIMES = ["image/webp", "image/jpeg", "image/png", "image/gif"];
238
- var ImageMimeSchema = z4.enum(IMAGE_MIMES);
239
- var ATTACHMENT_MAX = 4e5;
240
- var ATTACHMENT_LOCAL_MAX = 2e6;
241
- var PREVIEW_MAX = 4e4;
242
- var ATTACHMENTS_MAX = 5;
243
- var b64Max = (bytes) => Math.ceil(bytes / 3) * 4;
244
- var AttachmentRefSchema = z4.object({
245
- id: AttachmentIdSchema,
246
- mime: ImageMimeSchema,
247
- width: z4.number().int().positive(),
248
- height: z4.number().int().positive(),
249
- bytes: z4.number().int().positive(),
250
- preview: z4.boolean()
251
- });
252
- var ImageUploadSchema = z4.object({
253
- mime: ImageMimeSchema,
254
- data: z4.string().min(1).max(b64Max(ATTACHMENT_LOCAL_MAX)),
255
- preview: z4.object({ mime: ImageMimeSchema, data: z4.string().min(1).max(b64Max(PREVIEW_MAX)) }).optional()
256
- });
257
- var AttachmentErrorReasonSchema = z4.enum(["too_large", "not_image", "unreadable", "not_found", "offline"]);
258
- var requestId = z4.string().min(1).max(100);
259
- var AttachmentPutSchema = z4.object({ type: z4.literal("attachment.put"), requestId, id: z4.string().optional(), image: ImageUploadSchema });
260
- var AttachmentGetSchema = z4.object({ type: z4.literal("attachment.get"), requestId, attachment: AttachmentIdSchema, preview: z4.boolean().optional(), id: z4.string().optional(), machine: z4.string().optional() });
261
- var AttachmentStoredSchema = z4.object({ type: z4.literal("attachment.stored"), requestId, ref: AttachmentRefSchema });
262
- var AttachmentDataSchema = z4.object({ type: z4.literal("attachment.data"), requestId, attachment: AttachmentIdSchema, mime: ImageMimeSchema, data: z4.string() });
263
- var AttachmentErrorSchema = z4.object({ type: z4.literal("attachment.error"), requestId, reason: AttachmentErrorReasonSchema });
264
-
265
- // ../protocol/src/chat.ts
266
- var ChatStepSchema = z5.object({
267
- kind: z5.enum(["sessions", "pane", "reply", "git", "repo"]),
268
- id: z5.string().optional(),
269
- name: z5.string().optional(),
270
- path: z5.string().optional()
271
- });
272
- var ChatProposalSchema = z5.object({
273
- cwd: z5.string().min(1),
274
- project: z5.string(),
275
- prompt: z5.string().optional(),
276
- tool: ToolSchema,
277
- launchedId: z5.string().optional(),
278
- error: z5.string().optional()
279
- });
280
- var MAX_PROPOSALS = 8;
281
- var ChatProposalsSchema = z5.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
282
- var ChatRelaySchema = z5.object({
283
- id: z5.string().min(1),
284
- name: z5.string(),
285
- tool: ToolSchema,
286
- text: z5.string().min(1),
287
- status: z5.enum(["queued", "sent"]).optional(),
288
- sentAt: z5.string().datetime().optional(),
289
- error: z5.string().optional()
290
- });
291
- var ChatCloseSchema = z5.object({
292
- id: z5.string().min(1),
293
- name: z5.string(),
294
- tool: ToolSchema,
295
- closedAt: z5.string().datetime().optional(),
296
- how: z5.enum(["exit", "killed"]).optional(),
297
- error: z5.string().optional()
298
- });
299
- var ChatClosesSchema = z5.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
300
- var ChatProjectProposalSchema = z5.object({
301
- name: z5.string().min(1).max(80),
302
- description: z5.string().max(500).optional(),
303
- repos: z5.array(z5.object({ path: z5.string().min(1).max(4096), why: z5.string().min(1).max(200) })).min(1).max(50),
304
- addTo: z5.string().min(1).max(80).optional()
305
- });
306
- var ChatTeamSeatSchema = z5.object({
307
- role: z5.string().min(1),
308
- label: z5.string(),
309
- tool: ToolSchema,
310
- model: z5.string().optional(),
311
- effort: z5.string().optional(),
312
- autonomy: AutonomySchema.optional(),
313
- name: z5.string().optional()
314
- });
315
- var ChatTeamProposalSchema = z5.object({
316
- cwd: z5.string().min(1),
317
- project: z5.string(),
318
- projectName: z5.string().optional(),
319
- name: z5.string().min(1),
320
- template: z5.string().optional(),
321
- seats: z5.array(ChatTeamSeatSchema).min(1).max(8),
322
- teamId: z5.string().optional(),
323
- launchedAt: z5.string().datetime().optional(),
324
- error: z5.string().optional(),
325
- savedPath: z5.string().optional()
326
- });
327
- var ChatTaskProposalSchema = z5.object({
328
- to: z5.string().min(1),
329
- toName: z5.string().optional(),
330
- team: z5.string().optional(),
331
- title: z5.string().min(1).max(200),
332
- text: z5.string().max(2e4).optional(),
333
- taskId: z5.string().optional(),
334
- ownerName: z5.string().optional(),
335
- state: z5.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
336
- givenAt: z5.string().datetime().optional(),
337
- error: z5.string().optional()
338
- });
339
- var ChatTasksSchema = z5.array(ChatTaskProposalSchema).min(1).max(MAX_PROPOSALS);
340
- var ChatTurnSchema = z5.object({
341
- machineId: z5.string().optional(),
342
- machineName: z5.string().optional(),
343
- choices: z5.array(z5.object({ id: z5.string(), label: z5.string() })).optional(),
344
- qid: z5.string().min(1),
345
- question: z5.string(),
346
- askedAt: z5.string().datetime(),
347
- steps: z5.array(ChatStepSchema),
348
- answer: z5.string().optional(),
349
- answeredAt: z5.string().datetime().optional(),
350
- error: z5.string().optional(),
351
- proposal: ChatProposalSchema.optional(),
352
- proposals: ChatProposalsSchema.optional(),
353
- relay: ChatRelaySchema.optional(),
354
- closes: ChatClosesSchema.optional(),
355
- projectProposal: ChatProjectProposalSchema.optional(),
356
- team: ChatTeamProposalSchema.optional(),
357
- tasks: ChatTasksSchema.optional(),
358
- attachments: z5.array(AttachmentRefSchema).optional()
359
- });
360
- var proposalRows = (t) => t?.proposals ?? (t?.proposal ? [t.proposal] : []);
361
-
362
- // ../protocol/src/repos.ts
363
- import { z as z6 } from "zod";
364
- var ReposInspectSchema = z6.object({
365
- type: z6.literal("repos.inspect"),
366
- requestId: z6.string().min(1).max(100),
367
- paths: z6.array(z6.string().min(1)).min(1).max(50),
368
- machineId: z6.string().optional()
369
- });
370
- var RepoInfoSchema = z6.object({ path: z6.string(), exists: z6.boolean(), branch: z6.string().nullable() });
371
- var ReposInfoSchema = z6.object({ type: z6.literal("repos.info"), requestId: z6.string(), repos: z6.array(RepoInfoSchema) });
372
-
373
- // ../protocol/src/models.ts
374
- import { z as z7 } from "zod";
375
- var ModelIdSchema = z7.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
376
- var EffortSchema = z7.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
377
- var ModelChoiceSchema = z7.object({
378
- id: ModelIdSchema,
379
- label: z7.string(),
380
- description: z7.string().optional(),
381
- efforts: z7.array(EffortSchema).optional(),
382
- defaultEffort: EffortSchema.optional(),
383
- isDefault: z7.boolean().optional()
384
- });
385
- var ModelsListSchema = z7.object({
386
- type: z7.literal("models.list"),
387
- requestId: z7.string().min(1).max(100),
388
- tool: ToolSchema,
389
- machineId: z7.string().optional()
390
- });
391
- var ModelsListingSchema = z7.object({
392
- type: z7.literal("models.listing"),
393
- requestId: z7.string(),
394
- tool: ToolSchema,
395
- models: z7.array(ModelChoiceSchema),
396
- error: z7.string().optional()
397
- });
398
-
399
- // ../protocol/src/scan.ts
400
- import { z as z8 } from "zod";
401
- var ReposScanSchema = z8.object({ type: z8.literal("repos.scan"), requestId: z8.string().min(1).max(100), machineId: z8.string().optional() });
402
- var FoundRepoSchema = z8.object({ path: z8.string(), branch: z8.string().nullable(), remote: z8.string().optional(), manifest: z8.string().max(200).optional() });
403
- var ReposFoundSchema = z8.object({ type: z8.literal("repos.found"), requestId: z8.string(), repos: z8.array(FoundRepoSchema).max(500), truncated: z8.boolean(), error: z8.string().optional() });
404
-
405
- // ../protocol/src/documents.ts
406
- import { z as z9 } from "zod";
407
- var DOCUMENT_MAX_BYTES = 1048576;
408
- var path = z9.string().min(1).max(4096);
409
- var requestId2 = z9.string().min(1);
410
- var DocumentReadSchema = z9.object({
411
- type: z9.literal("document.read"),
412
- requestId: requestId2,
413
- path,
414
- machineId: z9.string().optional(),
415
- sessionId: z9.string().min(1).optional(),
416
- basePath: path.optional()
417
- });
418
- var DocumentContentSchema = z9.object({
419
- type: z9.literal("document.content"),
420
- requestId: requestId2,
421
- path,
422
- text: z9.string(),
423
- bytes: z9.number().int().nonnegative(),
424
- modifiedAt: z9.string().datetime()
425
- });
426
- var DocumentErrorCodeSchema = z9.enum([
427
- "not_found",
428
- "not_file",
429
- "unsupported_path",
430
- "unsupported_type",
431
- "permission_denied",
432
- "too_large",
433
- "invalid_utf8",
434
- "ambiguous_path",
435
- "session_missing",
436
- "read_failed"
437
- ]);
438
- var DocumentErrorSchema = z9.object({
439
- type: z9.literal("document.error"),
440
- requestId: requestId2,
441
- path,
442
- code: DocumentErrorCodeSchema
443
- });
444
-
445
- // ../protocol/src/routing.ts
446
- import { z as z10 } from "zod";
447
- var MachineInventorySchema = z10.object({
448
- id: z10.string().min(1).max(80),
449
- label: z10.string().min(1).max(120),
450
- online: z10.boolean(),
451
- local: z10.boolean(),
452
- sessions: z10.array(z10.object({ id: z10.string(), name: z10.string(), project: z10.string(), cwd: z10.string(), topic: z10.string().optional(), state: z10.string(), readOnly: z10.boolean() })).max(500),
453
- folders: z10.array(z10.object({ project: z10.string(), cwd: z10.string() })).max(100)
454
- });
455
- var RoutingContextSchema = z10.object({
456
- machines: z10.array(MachineInventorySchema).min(1).max(20),
457
- history: z10.array(z10.object({ question: z10.string().max(2e4), answer: z10.string().max(2e4).optional(), machineId: z10.string().optional() })).max(50)
458
- });
459
- var RouteResultSchema = z10.object({
460
- /** Null means an answer or a clarification, never an implicit local destination. */
461
- machineId: z10.string().nullable(),
462
- text: z10.string().min(1).max(2e4),
463
- choices: z10.array(z10.string()).max(20).optional()
464
- });
465
-
466
- // ../protocol/src/ssh.ts
467
- import { z as z11 } from "zod";
468
- var SshAliasSchema = z11.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
469
-
470
- // ../protocol/src/machines.ts
471
- import { z as z12 } from "zod";
472
- var MachineIdSchema = z12.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
473
- var MachineSchema = z12.object({
474
- id: MachineIdSchema,
475
- label: z12.string().trim().min(1).max(80),
476
- ssh: SshAliasSchema.optional()
477
- });
478
- var MachinesSchema = z12.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
479
- if (list[0]?.id !== "local" || list[0].ssh !== void 0) ctx.addIssue({ code: "custom", message: "the first machine is local, without ssh", path: [0] });
480
- const ids = /* @__PURE__ */ new Set(), aliases = /* @__PURE__ */ new Set();
481
- list.forEach((m, i) => {
482
- if (i > 0 && !m.ssh) ctx.addIssue({ code: "custom", message: "a remote machine needs an ssh alias", path: [i, "ssh"] });
483
- if (ids.has(m.id)) ctx.addIssue({ code: "custom", message: `duplicate id ${m.id}`, path: [i, "id"] });
484
- ids.add(m.id);
485
- if (m.ssh) {
486
- if (aliases.has(m.ssh)) ctx.addIssue({ code: "custom", message: `duplicate ssh alias ${m.ssh}`, path: [i, "ssh"] });
487
- aliases.add(m.ssh);
488
- }
489
- });
490
- });
491
-
492
- // ../protocol/src/island.ts
493
- import { z as z13 } from "zod";
494
- var IslandScaleSchema = z13.union([z13.literal("auto"), z13.literal(1), z13.literal(1.25), z13.literal(1.5), z13.literal(2)]);
495
- var IslandPrefsSchema = z13.object({
496
- /** Shown at the top of the screen; hidden, Desktop still notifies. */
497
- visible: z13.boolean(),
498
- /** `primary` (whichever it is after a reconfiguration), or a monitor's id as the shell lists it (`list_monitors`: the OS
499
- * name, `#index` without one). `second`, written by older fronts, is the first monitor that is not the primary. */
500
- screen: z13.string().trim().min(1).max(200),
501
- position: z13.enum(["center", "left", "right"]),
502
- /** A strip under a maximised window. */
503
- discreet: z13.boolean(),
504
- islandScale: IslandScaleSchema,
505
- panelScale: IslandScaleSchema,
506
- /** What the island shows besides asks and questions, which it always shows. */
507
- shows: z13.object({
508
- /** Allow, deny or answer in a line from the island; off, its cards only say « Open in Ledge ». */
509
- quick: z13.boolean(),
510
- /** The 6 s bubble when an agent finishes. */
511
- finished: z13.boolean(),
512
- /** An agent's sentence in a bubble. */
513
- say: z13.boolean(),
514
- /** The sub-agents next to their main agent. */
515
- helpers: z13.boolean()
516
- }),
517
- /** The global shortcut that opens the island, as shown (`Ctrl Alt L`). */
518
- shortcut: z13.string().trim().min(1).max(40)
519
- });
520
-
521
- // ../protocol/src/projects.ts
522
- import { z as z14 } from "zod";
523
- var PROJECT_COLORS = { blue: "#3b82f6", green: "#10b981", amber: "#f59e0b", violet: "#8b5cf6", teal: "#14b8a6", pink: "#ec4899", red: "#ef4444", slate: "#64748b" };
524
- var ProjectColorSchema = z14.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
525
- var ProjectIdSchema = z14.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
526
- function normalizePath(raw) {
527
- const p = raw.trim().replace(/\/+/g, "/");
528
- return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
529
- }
530
- var PathSchema = z14.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
531
- var RepoSchema = z14.object({ machine: MachineIdSchema, path: PathSchema });
532
- var ProjectSchema = z14.object({
533
- id: ProjectIdSchema,
534
- name: z14.string().trim().min(1).max(80),
535
- color: ProjectColorSchema,
536
- description: z14.string().max(500).default(""),
537
- repos: z14.array(RepoSchema).max(50)
538
- });
539
- var RuleSchema = z14.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
540
- function projectsProblems(p) {
541
- const out = [];
542
- const ids = /* @__PURE__ */ new Set(), names = /* @__PURE__ */ new Set(), repos = /* @__PURE__ */ new Set(), rules = /* @__PURE__ */ new Set();
543
- p.projects.forEach((project, i) => {
544
- if (ids.has(project.id)) out.push({ code: "duplicate_id", path: ["projects", i, "id"] });
545
- ids.add(project.id);
546
- const name = project.name.trim().toLocaleLowerCase();
547
- if (names.has(name)) out.push({ code: "duplicate_name", path: ["projects", i, "name"] });
548
- names.add(name);
549
- project.repos.forEach((repo, j) => {
550
- const key = `${repo.machine}
551
- ${repo.path}`;
552
- if (repos.has(key)) out.push({ code: "duplicate_repo", path: ["projects", i, "repos", j] });
553
- repos.add(key);
554
- });
555
- });
556
- p.rules.forEach((rule, i) => {
557
- const key = `${rule.machine}
558
- ${rule.path}`;
559
- if (rules.has(key)) out.push({ code: "duplicate_rule", path: ["rules", i] });
560
- rules.add(key);
561
- if (!ids.has(rule.project)) out.push({ code: "unknown_project", path: ["rules", i, "project"] });
562
- });
563
- return out;
564
- }
565
- var ProjectsSchema = z14.object({ projects: z14.array(ProjectSchema).max(100), rules: z14.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
566
- for (const problem of projectsProblems(p)) ctx.addIssue({ code: "custom", message: problem.code, path: problem.path });
567
- });
568
- var DEFAULT_PROJECTS = { projects: [], rules: [] };
569
- var under = (cwd, path2) => path2 === "/" || cwd === path2 || cwd.startsWith(`${path2}/`);
570
- function projectOf(session, state) {
571
- const machine = session.machineId ?? "local";
572
- const cwd = normalizePath(session.cwd);
573
- const known = new Set(state.projects.map((p) => p.id));
574
- const candidates = [
575
- ...state.projects.flatMap((p) => p.repos.filter((r) => r.machine === machine).map((r) => ({ project: p.id, path: r.path, rule: false }))),
576
- ...state.rules.filter((r) => r.machine === machine && known.has(r.project)).map((r) => ({ project: r.project, path: r.path, rule: true }))
577
- ].filter((c) => under(cwd, c.path));
578
- candidates.sort((x, y) => y.path.length - x.path.length || Number(y.rule) - Number(x.rule));
579
- return candidates[0]?.project ?? null;
580
- }
581
-
582
- // ../protocol/src/thread.ts
583
- import { z as z15 } from "zod";
584
- var ToolStatusSchema = z15.enum(["running", "success", "failed", "unknown"]);
585
- var FileChangeSchema = z15.object({ path: z15.string().min(1), from: z15.string().min(1).optional(), change: z15.enum(["add", "edit", "delete", "write"]), diff: z15.string().optional() });
586
- var base = { id: z15.string().min(1), at: z15.string().datetime(), turn: z15.number().int().nonnegative(), helperId: z15.string().optional() };
587
- var ThreadItemSchema = z15.discriminatedUnion("kind", [
588
- z15.object({ kind: z15.literal("prompt"), ...base, text: z15.string(), images: z15.array(AttachmentRefSchema).optional() }),
589
- z15.object({ kind: z15.literal("message"), ...base, text: z15.string() }),
590
- /** `id` is the harness's tool_use_id when it gives one, so the result updates the same row; `byClose`: a question
591
- * dismissed by Ledge closing the session. */
592
- z15.object({ kind: z15.literal("tool"), ...base, tool: z15.string(), label: z15.string(), input: z15.string().optional(), output: z15.string().optional(), error: z15.string().optional(), status: ToolStatusSchema, durationMs: z15.number().nonnegative().optional(), files: z15.array(FileChangeSchema).default([]), byClose: z15.literal(true).optional() }),
593
- /** `unknown`: answered in the terminal, the daemon only saw the prompt go away; `always`: allowed and not asked again;
594
- * `byClose`: denied by Ledge closing the session. */
595
- z15.object({ kind: z15.literal("permission"), ...base, tool: z15.string(), text: z15.string(), decision: z15.enum(["accept", "deny", "always", "unknown"]).optional(), byClose: z15.literal(true).optional() }),
596
- z15.object({ kind: z15.literal("reply"), ...base, text: z15.string() }),
597
- z15.object({ kind: z15.literal("helper"), ...base, task: z15.string(), agentType: z15.string().optional(), state: z15.enum(["working", "exited"]) }),
598
- /** 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. */
599
- z15.object({ kind: z15.literal("sent"), ...base, to: z15.string(), peer: z15.string().optional(), summary: z15.string().optional(), text: z15.string(), status: z15.enum(["sending", "delivered", "read", "failed"]), received: z15.string().optional(), via: z15.literal("ledge").optional() }),
600
- /** 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. */
601
- z15.object({ kind: z15.literal("received"), ...base, from: z15.string(), peer: z15.string().optional(), text: z15.string(), sent: z15.string().optional() }),
602
- /** An automated notice of the agent's harness about messages between sessions (Claude Code's `[Cross-session idle
603
- * notice]` / `[Cross-session delivery notice]`): a system event, never the user's words. `name` is the session it is
604
- * about as quoted, `peer` its Ledge session id; a delivery notice says the outcome of `count` messages to `recipients`. */
605
- z15.object({
606
- kind: z15.literal("notice"),
607
- ...base,
608
- notice: z15.enum(["idle", "exited", "unavailable", "expired", "delivery", "other"]),
609
- /** An `other` notice from Ledge: `task`, the task a `<ledge-task>` is about; `team`, the team a `<ledge-notice team>` seated the
610
- * session in (roles spec §3.3). An older front, which knows neither, shows it as any other notice. */
611
- task: z15.string().optional(),
612
- team: z15.string().optional(),
613
- /** A task this session handed over (`outgoing`), and how (issue #92). */
614
- taskOp: z15.enum(TRANSMISSION_OPS).optional(),
615
- outgoing: z15.literal(true).optional(),
616
- name: z15.string().optional(),
617
- peer: z15.string().optional(),
618
- time: z15.string().optional(),
619
- summary: z15.string().optional(),
620
- status: z15.enum(["held", "denied", "expired", "delivered", "refused", "dropped", "unknown"]).optional(),
621
- count: z15.number().int().positive().optional(),
622
- recipients: z15.array(z15.string()).optional(),
623
- reason: z15.string().optional(),
624
- text: z15.string()
625
- })
626
- ]);
627
-
628
- // ../protocol/src/decisions.ts
629
- import { z as z16 } from "zod";
630
- var DecisionSchema = z16.enum(["accept", "deny", "always"]);
631
- var NOTE_MAX = 4e3;
632
- var NOTE_NOT_TYPED = "note_not_typed";
633
- var DECISION_OFFERS = {
634
- claude: { always: ["permission", "diff", "plan"], note: ["permission", "diff", "plan"] },
635
- codex: { always: ["permission", "diff"], note: ["permission", "diff", "plan"] },
636
- cursor: { always: [], note: ["permission", "plan"] },
637
- /** OMP answers its own native requests (spec 2026-10-20 §3.3): no « always », no note until those are typed. */
638
- omp: { always: [], note: [] }
639
- };
640
- function offers(tool, kind, what) {
641
- return DECISION_OFFERS[tool][what].includes(kind);
642
- }
643
- function cyrb53(str, seed) {
644
- let h1 = 3735928559 ^ seed, h2 = 1103547991 ^ seed;
645
- for (let i = 0; i < str.length; i++) {
646
- const ch = str.charCodeAt(i);
647
- h1 = Math.imul(h1 ^ ch, 2654435761);
648
- h2 = Math.imul(h2 ^ ch, 1597334677);
649
- }
650
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
651
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
652
- return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0");
653
- }
654
- function pendingKey(p) {
655
- const s = JSON.stringify([p.kind, p.text, p.body ?? null, p.tool ?? null, p.options ?? null, p.optionDetails ?? null, p.helperId ?? null]);
656
- return cyrb53(s, 0) + cyrb53(s, 1);
657
- }
658
-
659
- // ../protocol/src/mobile.ts
660
- import { z as z17 } from "zod";
661
- var DEFAULT_RELAY_URL = "https://app.kl-ledge.dev";
662
- var RelayUrlSchema = z17.string().transform((v, ctx) => {
663
- const bad = () => {
664
- ctx.addIssue({ code: z17.ZodIssueCode.custom, message: "invalid relay URL" });
665
- return z17.NEVER;
666
- };
667
- let u;
668
- try {
669
- u = new URL(v.replace(/\/$/, ""));
670
- } catch {
671
- return bad();
672
- }
673
- const local = u.hostname === "localhost" || u.hostname === "127.0.0.1";
674
- if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) return bad();
675
- if (u.username || u.password || u.pathname !== "/" || u.search || u.hash) return bad();
676
- return u.origin;
677
- });
678
- var MobileConfigSchema = z17.object({ enabled: z17.boolean(), relayUrl: RelayUrlSchema });
679
- var MobileLinkStateSchema = z17.discriminatedUnion("state", [
680
- z17.object({ state: z17.literal("off") }),
681
- z17.object({ state: z17.literal("connecting") }),
682
- z17.object({ state: z17.literal("online") }),
683
- z17.object({ state: z17.literal("offline"), retryAt: z17.string().datetime(), error: z17.string() }),
684
- z17.object({ state: z17.literal("signed_out"), reason: z17.enum(["other_pc", "session_invalid"]) }),
685
- /** The mobile service did not start (a broken mobile.json, a missing key store): nothing is linked until the daemon restarts. */
686
- z17.object({ state: z17.literal("unavailable"), error: z17.string().max(200) })
687
- ]);
688
- var MobilePairingSchema = z17.object({
689
- id: z17.string(),
690
- label: z17.string(),
691
- code: z17.string().nullable(),
692
- expiresAt: z17.string().datetime(),
693
- state: z17.enum(["waiting", "confirm", "approved", "linked", "expired", "refused"])
694
- });
695
- var MobileStateSchema = z17.object({
696
- enabled: z17.boolean(),
697
- relayUrl: z17.string(),
698
- account: z17.object({ login: z17.string(), accountId: z17.string() }).nullable(),
699
- signin: z17.object({ userCode: z17.string(), verificationUri: z17.string(), expiresAt: z17.string().datetime() }).nullable(),
700
- /** The last sign-in that did not end with a session; cleared by the next one. */
701
- signinError: z17.enum(["expired", "denied", "not_allowed", "failed"]).nullable(),
702
- link: MobileLinkStateSchema,
703
- devices: z17.array(z17.object({ id: z17.string(), label: z17.string(), approvedAt: z17.string(), online: z17.boolean(), lastSeenAt: z17.string().optional() })),
704
- pairings: z17.array(MobilePairingSchema)
705
- });
706
-
707
- // ../protocol/src/teams.ts
708
- import { z as z18 } from "zod";
709
- var RoleIdSchema = z18.string().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/);
710
- var ROLE_LABEL_MAX = 40;
711
- var ROLE_PROMPT_MAX = 8e3;
712
- var RoleSchema = z18.object({
713
- id: RoleIdSchema,
714
- label: z18.string().min(1).max(ROLE_LABEL_MAX),
715
- prompt: z18.string().max(ROLE_PROMPT_MAX),
716
- tool: ToolSchema.optional(),
717
- model: ModelIdSchema.optional(),
718
- effort: EffortSchema.optional(),
719
- autonomy: AutonomySchema.optional(),
720
- source: z18.enum(["repo", "home"]),
721
- path: z18.string()
722
- });
723
- var DefinitionProblemSchema = z18.object({ path: z18.string(), reason: z18.string() });
724
- var TEAM_SEATS_MAX = 8;
725
- var SeatSchema = z18.object({
726
- role: RoleIdSchema,
727
- tool: ToolSchema.optional(),
728
- model: ModelIdSchema.optional(),
729
- effort: EffortSchema.optional(),
730
- name: z18.string().min(1).max(40).optional()
731
- });
732
- var uniqueRoles = (seats) => new Set(seats.map((s) => s.role)).size === seats.length;
733
- var SeatsSchema = z18.array(SeatSchema).min(1).max(TEAM_SEATS_MAX).refine(uniqueRoles, "a role twice in one team");
734
- var TeamNameSchema = z18.string().trim().min(1).max(60);
735
- var TeamTemplateSchema = z18.object({ id: z18.string().min(1).max(64), name: TeamNameSchema, seats: SeatsSchema, source: z18.enum(["repo", "home"]), path: z18.string() });
736
- var TeamIdSchema = z18.string().regex(/^team-[0-9a-f]{4,8}$/);
737
- var TeamStateSchema = z18.enum(["active", "resting", "dissolved"]);
738
- var TeamSchema = z18.object({
739
- id: TeamIdSchema,
740
- name: TeamNameSchema,
741
- template: z18.string().optional(),
742
- project: z18.string().nullable(),
743
- machine: z18.string(),
744
- cwd: z18.string(),
745
- state: TeamStateSchema,
746
- seats: z18.array(z18.object({ role: RoleIdSchema, sessionId: z18.string().nullable() })).max(TEAM_SEATS_MAX * 2),
747
- createdAt: z18.string(),
748
- updatedAt: z18.string(),
749
- restingAt: z18.string().optional(),
750
- dissolvedAt: z18.string().optional()
751
- });
752
- var RolesListSchema = z18.object({ type: z18.literal("roles.list"), cwd: z18.string().min(1).optional(), requestId: z18.string().max(100).optional(), machineId: z18.string().optional() });
753
- var RolesListingSchema = z18.object({ type: z18.literal("roles.listing"), requestId: z18.string().optional(), roles: z18.array(RoleSchema), templates: z18.array(TeamTemplateSchema).default([]), problems: z18.array(DefinitionProblemSchema) });
754
- var TeamLaunchSchema = z18.object({
755
- type: z18.literal("team.launch"),
756
- rid: z18.string().min(1).max(100),
757
- cwd: z18.string().min(1).optional(),
758
- template: z18.string().min(1).max(64).optional(),
759
- seats: SeatsSchema.optional(),
760
- name: TeamNameSchema.optional(),
761
- qid: z18.string().min(1).optional(),
762
- machineId: z18.string().optional()
763
- });
764
- var TeamJoinSchema = z18.object({ type: z18.literal("team.join"), team: TeamIdSchema, session: z18.string().min(1), role: RoleIdSchema });
765
- var TeamLeaveSchema = z18.object({ type: z18.literal("team.leave"), team: TeamIdSchema, session: z18.string().min(1) });
766
- var TeamResumeSchema = z18.object({ type: z18.literal("team.resume"), team: TeamIdSchema });
767
- var TeamDissolveSchema = z18.object({ type: z18.literal("team.dissolve"), team: TeamIdSchema });
768
- var TeamSaveSchema = z18.object({ type: z18.literal("team.save"), rid: z18.string().min(1).max(100), name: TeamNameSchema, seats: SeatsSchema, qid: z18.string().min(1).optional() });
769
- var TeamsMessageSchema = z18.object({ type: z18.literal("teams"), teams: z18.array(TeamSchema) });
770
- var TeamUpdatedSchema = z18.object({ type: z18.literal("team.updated"), team: TeamSchema });
771
- var TeamLaunchedSchema = z18.object({ type: z18.literal("team.launched"), rid: z18.string(), team: TeamSchema });
772
- var TeamFailedSchema = z18.object({ type: z18.literal("team.failed"), rid: z18.string(), reason: z18.string(), team: TeamSchema.optional() });
773
- var TeamSavedSchema = z18.object({ type: z18.literal("team.saved"), rid: z18.string(), path: z18.string().optional(), reason: z18.string().optional() });
774
-
775
- // ../protocol/src/messages.ts
776
- var PROTOCOL_PORT = 47831;
777
- var RefusalCodeSchema = z19.enum(["stale", "read_only", "no_prompt", "not_taken", "busy", "unknown_session", "nothing_to_decide", "not_offered", "free_text", "note_not_typed", "unavailable", "invalid", "attachment_missing", "attachments_not_accepted"]);
778
- var SessionEventSchema = z19.object({
779
- name: z19.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed", "NativeRequest", "AgentMessage", "QueueUpdate"]),
780
- at: z19.string().datetime(),
781
- source: z19.enum(["hook", "pane", "daemon"]),
782
- tool: z19.string().optional(),
783
- text: z19.string().optional(),
784
- options: z19.array(z19.string()).optional(),
785
- /** Each option's description, by index ('' when it has none); left out when no option has one. */
786
- optionDetails: z19.array(z19.string()).optional(),
787
- kind: z19.enum(["question", "permission", "plan", "diff"]).optional(),
788
- /** PanePrompt: what to review in full before deciding (a plan read in the terminal), when `text` is its summary. */
789
- body: z19.string().optional(),
790
- exitCode: z19.number().int().nullable().optional(),
791
- helperId: z19.string().optional(),
792
- /** The helper's description, once known (Claude Code: its `.meta.json`, written after SubagentStart). */
793
- task: z19.string().optional(),
794
- /** PostToolUseFailure: the user interrupted the turn (Esc); Claude then waits for input with no Stop. */
795
- interrupt: z19.boolean().optional(),
796
- /** UserPromptSubmit: the images the prompt carried, by attachment id. */
797
- attachments: z19.array(AttachmentIdSchema).max(ATTACHMENTS_MAX).optional(),
798
- /** QueueUpdate: OMP's queue as it stands. */
799
- queue: z19.array(z19.object({ text: z19.string(), mode: z19.enum(["steer", "follow_up"]) })).optional(),
800
- /** AgentMessage: the harness's own message, its text so far (`streaming`) or whole. `messageId` is unique for the session's life. */
801
- messageId: z19.string().optional(),
802
- streaming: z19.boolean().optional(),
803
- /** NativeRequest: the dialog's id, and OMP's `ask` questions when it is one. */
804
- requestId: z19.string().optional(),
805
- questions: z19.array(AskQuestionSchema).optional(),
806
- /** Stop: its text is already in the thread as AgentMessage cards, which the reply card must not repeat. */
807
- streamed: z19.boolean().optional(),
808
- /** Native tracking (spec 2026-10-13 §2): the harness's id of the tool call, so its result lands on the same row. */
809
- toolUseId: z19.string().optional(),
810
- /** The tool's input as the harness gave it, every string capped by the hook. */
811
- toolInput: z19.record(z19.string(), z19.unknown()).optional(),
812
- /** PostToolUse: the result as text; SubagentStop: nothing (the report travels in `text`). */
813
- output: z19.string().optional(),
814
- durationMs: z19.number().nonnegative().optional(),
815
- /** PostToolUseFailure: the harness's error message. */
816
- error: z19.string().optional(),
817
- /** The session's transcript, or the helper's on SubagentStop; the daemon reads its tail for the agent's messages. */
818
- transcriptPath: z19.string().optional(),
819
- /** Decided: what was chosen when the daemon knows it. */
820
- decision: DecisionSchema.optional(),
821
- /** Decided: the request was refused by Ledge closing the session (its interrupt keys), not by the user's answer. */
822
- byClose: z19.literal(true).optional(),
823
- /** The agent's own session id when the hook names it, so the daemon can resume that conversation. */
824
- agentSessionId: z19.string().optional()
825
- });
826
- var RecentSchema = z19.object({ cwd: z19.string().min(1), project: z19.string().min(1), lastAt: z19.string().datetime() });
827
- var SEMVER = /^\d+\.\d+\.\d+$/;
828
- var DaemonInfoSchema = z19.object({
829
- version: z19.string(),
830
- install: z19.enum(["npm", "git", "unknown"]),
831
- systemd: z19.boolean(),
832
- manual: z19.string().optional(),
833
- routing: z19.boolean().optional(),
834
- archive: z19.boolean().optional(),
835
- /** Takes `model` and `effort` on `session.launch` and answers `models.list` (#58). */
836
- models: z19.boolean().optional(),
837
- /** Drives OMP over its RPC: launching `omp` sessions is possible when the binary is there (`models.list` says so). */
838
- omp: z19.boolean().optional(),
839
- /** Takes `session.model`: a live session's model and effort changed (#58, lot 2). */
840
- modelSwitch: z19.boolean().optional(),
841
- /** Takes `approval` on an OMP `session.launch` (auto mode). */
842
- ompApproval: z19.boolean().optional(),
843
- /** Its home folder, so a client shows the folders under it as `~` (absent from older daemons). */
844
- home: z19.string().optional(),
845
- mobile: z19.boolean().optional(),
846
- /** Knows roles, teams and tasks given by Ledge (spec 2026-10-25 roles). */
847
- teams: z19.boolean().optional()
848
- });
849
- var UpdateFailureSchema = z19.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
850
- var ServerMessageSchema = z19.discriminatedUnion("type", [
851
- z19.object({ type: z19.literal("chat.routed"), qid: z19.string(), result: RouteResultSchema }),
852
- DocumentContentSchema,
853
- DocumentErrorSchema,
854
- AttachmentStoredSchema,
855
- AttachmentDataSchema,
856
- AttachmentErrorSchema,
857
- ReposInfoSchema,
858
- ReposFoundSchema,
859
- ModelsListingSchema,
860
- /** `recents`: the folders of past sessions, most recent first. `launcher`: the command that runs `ledge` inside WSL
861
- * (`[node, cli]`), so the overlay can attach a terminal to a session with `<launcher> attach <id>`. `daemon`: absent
862
- * from daemons older than the auto-update. */
863
- z19.object({ type: z19.literal("snapshot"), sessions: z19.array(SessionSchema), recents: z19.array(RecentSchema).optional(), launcher: z19.array(z19.string()).optional(), daemon: DaemonInfoSchema.optional(), sshAlias: SshAliasSchema.optional() }),
864
- z19.object({ type: z19.literal("session.updated"), session: SessionSchema }),
865
- /** OMP: the instructions taken back from its queue by an interrupt that restores it; the asker puts them back in its draft. */
866
- z19.object({
867
- type: z19.literal("session.queue.restored"),
868
- id: z19.string(),
869
- texts: z19.array(z19.string()),
870
- /** The same instructions with the images they carried; `imagesDropped`: some images could not be put back. */
871
- entries: z19.array(z19.object({ text: z19.string(), attachments: z19.array(AttachmentIdSchema).optional() })).optional(),
872
- imagesDropped: z19.boolean().optional()
873
- }),
874
- z19.object({ type: z19.literal("session.removed"), id: z19.string() }),
875
- z19.object({ type: z19.literal("session.preview"), id: z19.string(), lines: z19.array(z19.string()) }),
876
- /** A session's thread, on request (spec 2026-10-13 §5); then each created or updated item, broadcast. */
877
- z19.object({ type: z19.literal("session.thread"), id: z19.string(), items: z19.array(ThreadItemSchema) }),
878
- z19.object({ type: z19.literal("session.thread.item"), id: z19.string(), item: ThreadItemSchema }),
879
- /** Answer to the requester of a `session.launch`: the session exists (its `session.updated` is broadcast as usual). */
880
- z19.object({ type: z19.literal("session.launched"), id: z19.string() }),
881
- /** Answer to the requester of a `session.close`, once per row closed: the session `id` and how it ended. */
882
- z19.object({ type: z19.literal("session.closed"), qid: z19.string(), row: z19.number().int().min(0), id: z19.string(), how: z19.enum(["exit", "killed"]) }),
883
- z19.object({ type: z19.literal("recents"), recents: z19.array(RecentSchema) }),
884
- /** Answer to the requester of a `folder.list` or `folder.create`: `path` resolved (`~` expanded), `parent` null at the
885
- * root, `entries` its subfolders by name (hidden ones left out). A failure keeps the asked path and says why in `error`,
886
- * never in an `error` message, which the launch form reads as a failed launch. */
887
- z19.object({
888
- type: z19.literal("folder.listing"),
889
- path: z19.string(),
890
- parent: z19.string().nullable(),
891
- entries: z19.array(z19.string()),
892
- error: z19.string().optional(),
893
- /** The request this answers, when it carried one. */
894
- requestId: z19.string().optional(),
895
- /** Those `entries` that are Git repositories, with their branch (the short commit when HEAD is detached). */
896
- repos: z19.array(z19.object({ name: z19.string(), branch: z19.string() })).optional()
897
- }),
898
- /** 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. */
899
- z19.object({ type: z19.literal("chat.step"), qid: z19.string(), step: ChatStepSchema }),
900
- z19.object({ type: z19.literal("chat.delta"), qid: z19.string(), text: z19.string() }),
901
- z19.object({ type: z19.literal("chat.done"), qid: z19.string(), text: z19.string(), at: z19.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), closes: ChatClosesSchema.optional(), projectProposal: ChatProjectProposalSchema.optional(), team: ChatTeamProposalSchema.optional(), tasks: ChatTasksSchema.optional() }),
902
- z19.object({ type: z19.literal("chat.error"), qid: z19.string(), message: z19.string() }),
903
- z19.object({ type: z19.literal("chat.history"), turns: z19.array(ChatTurnSchema) }),
904
- /** The daemon updating itself: npm is running, then the service restarts (the socket closes right after). */
905
- z19.object({ type: z19.literal("update.progress"), version: z19.string(), step: z19.enum(["installing", "restarting"]) }),
906
- /** Nothing was changed. `detail`: the useful end of npm's output; `command`: what the person can run instead. */
907
- z19.object({ type: z19.literal("update.failed"), version: z19.string(), reason: UpdateFailureSchema, detail: z19.string().optional(), command: z19.string().optional() }),
908
- /** The shared machines list (spec 2026-10-14 §5), sent to local clients only. */
909
- z19.object({ type: z19.literal("machines"), machines: MachinesSchema }),
910
- /** The local daemon's projects and folder rules (spec 2026-10-16 build §5), sent to local clients only. */
911
- z19.object({ type: z19.literal("projects"), projects: ProjectsSchema }),
912
- /** The island's settings shared by the local fronts; null until a front sets them (the island then sends its own). Local clients only. */
913
- z19.object({ type: z19.literal("island"), prefs: IslandPrefsSchema.nullable() }),
914
- /** Mobile access (lot 3), after the island in each snapshot and on every change. Local clients only. */
915
- z19.object({ type: z19.literal("mobile"), state: MobileStateSchema }),
916
- /** 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`. */
917
- z19.object({ type: z19.literal("message.ack"), rid: z19.string(), outcome: z19.enum(["accepted", "queued", "native", "refused"]), reason: z19.string().optional() }),
918
- /** The answer to a `message.read`: the body with its sender, target and time, or the refusal's `reason`. */
919
- z19.object({ type: z19.literal("message.body"), id: z19.string(), from: z19.string().optional(), to: z19.string().optional(), at: z19.string().optional(), text: z19.string().optional(), reason: z19.string().optional() }),
920
- TaskResultSchema,
921
- SessionHandoverResultSchema,
922
- ContextPrefsMessageSchema,
923
- HandoverResultSchema,
924
- TaskUpdatedSchema,
925
- RolesListingSchema,
926
- TeamsMessageSchema,
927
- TeamUpdatedSchema,
928
- TeamLaunchedSchema,
929
- TeamFailedSchema,
930
- TeamSavedSchema,
931
- z19.object({
932
- type: z19.literal("error"),
933
- message: z19.string(),
934
- id: z19.string().optional(),
935
- /** Le type du message client refusé. */
936
- request: z19.string().optional(),
937
- /** Ce que le front doit reconnaître au-delà du texte (NOTE_NOT_TYPED) ; une chaîne libre, qu'un front plus ancien ignore. */
938
- code: z19.string().optional()
939
- })
940
- ]);
941
- var MESSAGE_MAX = 2e4;
942
- var SUMMARY_MAX = 500;
943
- var launchRows = z19.array(z19.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
944
- var ClientMessageSchema = z19.discriminatedUnion("type", [
945
- z19.object({ type: z19.literal("chat.route"), qid: z19.string().min(1).max(100), text: z19.string().min(1).max(2e4), tool: ToolSchema.optional(), context: RoutingContextSchema }),
946
- DocumentReadSchema,
947
- AttachmentPutSchema,
948
- AttachmentGetSchema,
949
- ReposInspectSchema,
950
- ReposScanSchema,
951
- ModelsListSchema,
952
- /** The overlay starts a session: `cwd` is a WSL path (`~` allowed), `name` a preferred bot name (the daemon picks a
953
- * free one otherwise), `prompt` the agent's first instruction, `continue` resumes the folder's last conversation.
954
- * With `qid` it is the chat card of that turn: the daemon launches the turn's proposal and ignores the other fields
955
- * but `rows`, `model` and `effort`. */
956
- z19.object({
957
- type: z19.literal("session.launch"),
958
- tool: ToolSchema,
959
- cwd: z19.string().min(1),
960
- name: z19.string().optional(),
961
- prompt: z19.string().optional(),
962
- continue: z19.boolean().optional(),
963
- qid: z19.string().min(1).optional(),
964
- /** With `qid`: the card's rows to launch (#24), indexes into the turn's `proposals`, each once; the first row without it. */
965
- rows: launchRows.optional(),
966
- /** Folders in addition to `cwd` (Claude: `--add-dir`); resolved like `cwd`. */
967
- dirs: z19.array(z19.string().min(1)).max(20).optional(),
968
- /** The agent's model and effort (#58), the agent's own default when left out; with `qid`, for every row launched. */
969
- model: ModelIdSchema.optional(),
970
- effort: EffortSchema.optional(),
971
- /** OMP only: its approval mode, `always-ask` when left out. */
972
- approval: OmpApprovalSchema.optional(),
973
- /** A role (spec 2026-10-25 roles §3.4): its prompt reaches the agent at every start, its defaults fill what is left out. */
974
- role: RoleIdSchema.optional(),
975
- /** A live team the new session joins at once, in `role`. */
976
- team: TeamIdSchema.optional(),
977
- /** Routing by the fleet, removed before the message reaches the daemon. */
978
- machineId: z19.string().optional()
979
- }),
980
- /** The launch form's folder browser: list a folder's subfolders (home when no `path`), or make `name` inside `parent`
981
- * and list it. Both are answered with a `folder.listing`. */
982
- z19.object({
983
- type: z19.literal("folder.list"),
984
- path: z19.string().optional(),
985
- /** Routing by the fleet, removed before the message reaches the daemon. */
986
- machineId: z19.string().optional(),
987
- /** Copied into the `folder.listing` that answers; the fleet relays only that listing, from the machine asked. */
988
- requestId: z19.string().min(1).max(100).optional()
989
- }),
990
- z19.object({
991
- type: z19.literal("folder.create"),
992
- parent: z19.string().min(1),
993
- name: z19.string().min(1),
994
- /** As for `folder.list`: routing by the fleet, and the request its `folder.listing` answers. */
995
- machineId: z19.string().optional(),
996
- requestId: z19.string().min(1).max(100).optional()
997
- }),
998
- /** `steer`: OMP only, redirect the turn running now instead of queueing the text after it. */
999
- /** OMP's `ask`: the answers to every question of the dialog `requestId`, in order (`selected` labels, and/or free `text`); `cancel` declines it. */
1000
- z19.object({ type: z19.literal("session.answer"), id: z19.string(), requestId: z19.string().min(1).max(200), answers: z19.array(z19.object({ id: z19.string(), selected: z19.array(z19.string().max(2e3)).max(100), text: z19.string().max(2e4).optional() })).max(50), cancel: z19.boolean().optional() }),
1001
- z19.object({ type: z19.literal("session.reply"), id: z19.string(), text: z19.string(), attachments: z19.array(AttachmentIdSchema).max(ATTACHMENTS_MAX).optional(), steer: z19.boolean().optional() }),
1002
- /** The chat card's instruction (spec 2026-10-12 relay §4): sent now if the session is free, else queued until it is. */
1003
- z19.object({ type: z19.literal("session.relay"), qid: z19.string().min(1) }),
1004
- /** Takes the card's queued instruction back out of the queue. */
1005
- z19.object({ type: z19.literal("session.relay.cancel"), qid: z19.string().min(1) }),
1006
- /** The chat's close card: closes the turn's sessions, `rows` (indexes into its `closes`) or all of them. */
1007
- z19.object({ type: z19.literal("session.close"), qid: z19.string().min(1), rows: launchRows.optional() }),
1008
- /** `always` and `note` only where the harness offers them (`offers`); a note goes with `deny`, for the agent to read. */
1009
- z19.object({
1010
- type: z19.literal("session.decide"),
1011
- id: z19.string(),
1012
- decision: DecisionSchema,
1013
- note: z19.string().trim().min(1).max(NOTE_MAX).optional(),
1014
- /** 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.
1015
- * `key` (pendingKey) covers its whole content; older fronts send kind and text only. */
1016
- expect: PendingSchema.pick({ kind: true, text: true }).extend({ key: z19.string().max(64).optional() }).optional()
1017
- }),
1018
- z19.object({ type: z19.literal("session.focus"), id: z19.string() }),
1019
- z19.object({ type: z19.literal("session.kill"), id: z19.string() }),
1020
- /** `restoreQueue` (OMP): also take back what waits in its queue, returned as `session.queue.restored` to put in the draft. */
1021
- z19.object({ type: z19.literal("session.interrupt"), id: z19.string(), restoreQueue: z19.boolean().optional() }),
1022
- /** Removes a finished (exited) or read-only (hook_only) session from the list; refused for a live launcher session. */
1023
- z19.object({ type: z19.literal("session.dismiss"), id: z19.string() }),
1024
- /** Puts a session in the archive: it leaves the live list but is kept. */
1025
- z19.object({ type: z19.literal("session.archive"), id: z19.string() }),
1026
- /** Takes an archived session back out of the archive, as it was. */
1027
- z19.object({ type: z19.literal("session.restore"), id: z19.string() }),
1028
- /** Starts the agent again on an archived session's conversation. */
1029
- z19.object({ type: z19.literal("session.resume"), id: z19.string() }),
1030
- /** Changes a live session's model and effort (#58, lot 2), `null` for the agent's own default: the daemon restarts the
1031
- * agent on the same conversation with them, at once when the session is free, else at its next free moment. */
1032
- z19.object({ type: z19.literal("session.model"), id: z19.string(), model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
1033
- /** Deletes a session for good, archived or not. */
1034
- z19.object({ type: z19.literal("session.delete"), id: z19.string() }),
1035
- /** 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. */
1036
- z19.object({ type: z19.literal("session.rename"), id: z19.string(), topic: z19.string().max(500) }),
1037
- z19.object({ type: z19.literal("session.thread"), id: z19.string() }),
1038
- z19.object({ type: z19.literal("session.register"), session: SessionSchema }),
1039
- /** `agent` and `cwd` describe the session when the daemon does not know it yet (a hook from an agent started without ledge). */
1040
- z19.object({ type: z19.literal("session.event"), id: z19.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z19.string().optional() }),
1041
- z19.object({ type: z19.literal("session.unregister"), id: z19.string(), exitCode: z19.number().int().nullable() }),
1042
- z19.object({ type: z19.literal("snapshot.request") }),
1043
- z19.object({ type: z19.literal("chat.ask"), qid: z19.string().min(1), text: z19.string(), images: z19.array(ImageUploadSchema).max(ATTACHMENTS_MAX).optional(), tool: ToolSchema.optional() }),
1044
- z19.object({ type: z19.literal("chat.cancel"), qid: z19.string().min(1) }),
1045
- /** Starts a new conversation with Ledge: the day's turns are cleared and the next question opens a new harness session. */
1046
- z19.object({ type: z19.literal("chat.reset") }),
1047
- /** Installs `@paradow/ledge@version` and restarts the service (spec 2026-10-12 auto-update §6). */
1048
- z19.object({ type: z19.literal("update.daemon"), version: z19.string().regex(SEMVER) }),
1049
- /** Replaces the daemon's machines list (first connected front wins); refused unless valid. */
1050
- z19.object({ type: z19.literal("machines.set"), machines: MachinesSchema }),
1051
- /** Replaces the local daemon's projects and rules as a whole; refused unless valid. */
1052
- z19.object({ type: z19.literal("projects.set"), projects: ProjectsSchema }),
1053
- /** Replaces the island's settings as a whole; refused unless valid. */
1054
- z19.object({ type: z19.literal("island.set"), prefs: IslandPrefsSchema }),
1055
- /** Mobile access (lot 3), local fronts only. A changed `relayUrl` drops the relay session. */
1056
- z19.object({ type: z19.literal("mobile.set"), enabled: z19.boolean(), relayUrl: RelayUrlSchema }),
1057
- z19.object({ type: z19.literal("mobile.signin") }),
1058
- z19.object({ type: z19.literal("mobile.signin.cancel") }),
1059
- z19.object({ type: z19.literal("mobile.signout") }),
1060
- /** While the link is offline: try again now. */
1061
- z19.object({ type: z19.literal("mobile.retry") }),
1062
- z19.object({ type: z19.literal("mobile.pairing.approve"), id: z19.string().min(1).max(100) }),
1063
- z19.object({ type: z19.literal("mobile.pairing.deny"), id: z19.string().min(1).max(100) }),
1064
- /** `id`: the device's fingerprint. */
1065
- z19.object({ type: z19.literal("mobile.device.revoke"), id: z19.string().min(1).max(100) }),
1066
- /** 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. */
1067
- z19.object({ type: z19.literal("session.message"), rid: z19.string().min(1), from: z19.string().min(1), to: z19.string().min(1), text: z19.string().min(1).max(MESSAGE_MAX), summary: z19.string().max(SUMMARY_MAX).optional(), intercept: z19.boolean().optional() }),
1068
- /** `ledge read <id>`: a message's whole body, for its receiver or its sender (`by`, a session id). */
1069
- z19.object({ type: z19.literal("message.read"), id: z19.string().min(1).max(40), by: z19.string().min(1) }),
1070
- TaskCmdSchema,
1071
- SessionHandoverSchema,
1072
- ContextSetSchema,
1073
- HandoverBriefSchema,
1074
- HandoverShowSchema,
1075
- RolesListSchema,
1076
- TeamLaunchSchema,
1077
- TeamJoinSchema,
1078
- TeamLeaveSchema,
1079
- TeamResumeSchema,
1080
- TeamDissolveSchema,
1081
- TeamSaveSchema
1082
- ]);
1083
- function parseClientMessage(raw) {
1084
- return ClientMessageSchema.parse(raw);
1085
- }
1086
- function parseServerMessage(raw) {
1087
- return ServerMessageSchema.parse(raw);
1088
- }
1089
-
1090
- // ../protocol/src/names.ts
1091
- var NAMES = [
1092
- "Otto",
1093
- "Pim",
1094
- "Nova",
1095
- "Bix",
1096
- "Lu",
1097
- "Max",
1098
- "Ida",
1099
- "Rex",
1100
- "Zoe",
1101
- "Kit",
1102
- "Ari",
1103
- "Bo",
1104
- "Cal",
1105
- "Dee",
1106
- "Eli",
1107
- "Fay",
1108
- "Gus",
1109
- "Hal",
1110
- "Ivy",
1111
- "Jax",
1112
- "Kai",
1113
- "Lio",
1114
- "Mo",
1115
- "Nia",
1116
- "Oz",
1117
- "Pax",
1118
- "Quin",
1119
- "Rae",
1120
- "Sol",
1121
- "Tad",
1122
- "Uma",
1123
- "Vic",
1124
- "Wyn",
1125
- "Xen",
1126
- "Yan",
1127
- "Zia",
1128
- "Abe",
1129
- "Bea",
1130
- "Cy",
1131
- "Dot",
1132
- "Eva",
1133
- "Fin",
1134
- "Gil",
1135
- "Hen",
1136
- "Iro",
1137
- "Jo",
1138
- "Kip",
1139
- "Lex",
1140
- "Mia",
1141
- "Ned",
1142
- "Ola",
1143
- "Pat",
1144
- "Rio",
1145
- "Sam",
1146
- "Tia",
1147
- "Ulf",
1148
- "Val",
1149
- "Wes",
1150
- "Yui",
1151
- "Zed",
1152
- "Ash",
1153
- "Bry",
1154
- "Cleo",
1155
- "Dax"
1156
- ];
1157
- var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
1158
- function pickName(taken, preferred) {
1159
- const used = new Set(taken);
1160
- const free = (n) => !used.has(n);
1161
- const wanted = preferred?.trim();
1162
- if (wanted) {
1163
- const base2 = cap(wanted);
1164
- if (free(base2)) return base2;
1165
- for (let i = 2; ; i++) if (free(`${base2}-${i}`)) return `${base2}-${i}`;
1166
- }
1167
- const first = NAMES.find(free);
1168
- if (first) return first;
1169
- for (let i = 2; ; i++) if (free(`${NAMES[0]}-${i}`)) return `${NAMES[0]}-${i}`;
1170
- }
1171
-
1172
- // ../protocol/src/proposal.ts
1173
- import { z as z20 } from "zod";
1174
- var ProposalBlockSchema = z20.object({ cwd: z20.string().trim().min(1), prompt: z20.string().optional() });
1175
- var RelayBlockSchema = z20.object({ session: z20.string().trim().min(1), text: z20.string().trim().min(1) });
1176
- var ProjectBlockSchema = z20.object({
1177
- name: z20.string().trim().min(1).max(80),
1178
- description: z20.string().max(500).optional(),
1179
- repos: z20.array(z20.object({ path: z20.string().min(1).max(4096), why: z20.string().trim().min(1).max(200) })).min(1).max(50),
1180
- addTo: z20.string().trim().max(80).optional()
1181
- });
1182
- var CloseBlockSchema = z20.object({ session: z20.string().trim().min(1) });
1183
- var TeamBlockSchema = z20.object({
1184
- cwd: z20.string().trim().min(1),
1185
- template: z20.string().trim().min(1).max(64).optional(),
1186
- name: z20.string().trim().min(1).max(60).optional(),
1187
- seats: z20.array(z20.object({ role: z20.string().trim().min(1).max(40), tool: z20.enum(["claude", "codex", "cursor", "omp"]).optional(), model: z20.string().max(200).optional(), effort: z20.string().max(20).optional(), name: z20.string().max(40).optional() })).min(1).max(8).optional()
1188
- }).refine((b) => !!b.template || !!b.seats?.length, "a template or seats");
1189
- var TaskBlockSchema = z20.object({
1190
- to: z20.string().trim().min(1).max(80),
1191
- team: z20.string().trim().max(40).optional(),
1192
- title: z20.string().trim().min(1).max(200),
1193
- text: z20.string().max(2e4).optional()
1194
- });
1195
- var BLOCK = /```ledge-(session|relay|project|close|team|task)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
1196
- function extractProposal(answer) {
1197
- const found = [];
1198
- const text = answer.replace(BLOCK, (_m, kind, body) => {
1199
- found.push({ kind, raw: body.trim() });
1200
- return "";
1201
- });
1202
- const last = found.at(-1);
1203
- return last ? { text: text.replace(/\n{3,}/g, "\n\n").trim(), kind: last.kind, raw: last.raw, blocks: found } : { text: answer, kind: null, raw: null, blocks: [] };
1204
- }
1205
- function parseProposalBlock(raw) {
1206
- try {
1207
- const r = ProposalBlockSchema.safeParse(JSON.parse(raw));
1208
- return r.success ? r.data : null;
1209
- } catch {
1210
- return null;
1211
- }
1212
- }
1213
- function parseRelayBlock(raw) {
1214
- try {
1215
- const r = RelayBlockSchema.safeParse(JSON.parse(raw));
1216
- return r.success ? r.data : null;
1217
- } catch {
1218
- return null;
1219
- }
1220
- }
1221
- function parseCloseBlock(raw) {
1222
- try {
1223
- const r = CloseBlockSchema.safeParse(JSON.parse(raw));
1224
- return r.success ? r.data : null;
1225
- } catch {
1226
- return null;
1227
- }
1228
- }
1229
- function parseTeamBlock(raw) {
1230
- try {
1231
- const r = TeamBlockSchema.safeParse(JSON.parse(raw));
1232
- return r.success ? r.data : null;
1233
- } catch {
1234
- return null;
1235
- }
1236
- }
1237
- function parseTaskBlock(raw) {
1238
- try {
1239
- const r = TaskBlockSchema.safeParse(JSON.parse(raw));
1240
- return r.success ? r.data : null;
1241
- } catch {
1242
- return null;
1243
- }
1244
- }
1245
- function parseProjectBlock(raw) {
1246
- try {
1247
- const r = ProjectBlockSchema.safeParse(JSON.parse(raw));
1248
- return r.success ? r.data : null;
1249
- } catch {
1250
- return null;
1251
- }
1252
- }
1253
-
1254
- // ../protocol/src/topic.ts
1255
- var TOPIC_MAX = 80;
1256
- function cleanTopic(text) {
1257
- const line = text.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
1258
- const flat = line.replace(/\s+/g, " ");
1259
- return flat.length > TOPIC_MAX ? `${flat.slice(0, TOPIC_MAX - 1).trimEnd()}\u2026` : flat;
1260
- }
1261
- function sessionTitle(s) {
1262
- return `${s.name} \xB7 ${s.topic || s.project}`;
1263
- }
1264
-
1265
- // ../protocol/src/relay.ts
1266
- import { z as z21 } from "zod";
1267
- var SEALED_MAX = 1048576;
1268
- var RelayPeerSchema = z21.union([z21.literal("pc"), z21.string().regex(/^d_[A-Za-z0-9_-]{16,40}$/)]);
1269
- var RelayClientMessageSchema = z21.object({
1270
- type: z21.literal("send"),
1271
- to: RelayPeerSchema,
1272
- body: z21.string().max(SEALED_MAX)
1273
- });
1274
- var RelayServerMessageSchema = z21.discriminatedUnion("type", [
1275
- z21.object({ type: z21.literal("welcome"), accountId: z21.string(), self: RelayPeerSchema, online: z21.array(RelayPeerSchema) }),
1276
- z21.object({ type: z21.literal("deliver"), from: RelayPeerSchema, body: z21.string() }),
1277
- z21.object({ type: z21.literal("presence"), peer: RelayPeerSchema, online: z21.boolean() }),
1278
- /**
1279
- * A frame was refused; `to` names the target when it is known.
1280
- * Contract: an error concerning a sealed body (`offline`, `too_large`, or a socket closed with 4008
1281
- * `rate_limited`) means that body was not delivered, so the sequence on that channel is lost: the
1282
- * sender must re-handshake with that peer before sending to it again.
1283
- */
1284
- z21.object({
1285
- type: z21.literal("error"),
1286
- code: z21.enum(["bad_frame", "too_large", "bad_target", "offline", "rate_limited"]),
1287
- to: RelayPeerSchema.optional()
1288
- })
1289
- ]);
1290
- var PAIR_LABEL_MAX = 40;
1291
- var KeyB64 = z21.string().regex(/^[A-Za-z0-9_-]{87}$/);
1292
- var Nonce = z21.string().regex(/^[A-Za-z0-9_-]{43}$/);
1293
- var Fingerprint = z21.string().regex(/^[A-Za-z0-9_-]{22}$/);
1294
- var SealedFrameSchema = z21.discriminatedUnion("k", [
1295
- /** `e` is the ephemeral key, `f` the fingerprint of the sender's static key. */
1296
- z21.object({ k: z21.literal("hello"), e: KeyB64, f: Fingerprint }),
1297
- z21.object({ k: z21.literal("msg"), s: z21.string() }),
1298
- z21.object({ k: z21.literal("pair_commit"), c: Nonce, label: z21.string().trim().min(1).max(PAIR_LABEL_MAX) }),
1299
- z21.object({ k: z21.literal("pair_offer"), pc: KeyB64, n: Nonce }),
1300
- z21.object({ k: z21.literal("pair_reveal"), d: KeyB64, n: Nonce }),
1301
- z21.object({ k: z21.literal("pair_ok") }),
1302
- z21.object({ k: z21.literal("pair_no") }),
1303
- z21.object({ k: z21.literal("denied") }),
1304
- z21.object({ k: z21.literal("rekey") })
1305
- ]);
1306
-
1307
- // ../protocol/src/relayAuth.ts
1308
- import { z as z22 } from "zod";
1309
- var RELAY_CLOSE_SIGNED_OUT = 4001;
1310
- var DeviceStartResponseSchema = z22.object({
1311
- deviceCode: z22.string().min(1),
1312
- userCode: z22.string().min(1),
1313
- verificationUri: z22.string().min(1),
1314
- expiresIn: z22.number().int().positive(),
1315
- interval: z22.number().int().positive()
1316
- });
1317
- var DevicePollResponseSchema = z22.discriminatedUnion("status", [
1318
- z22.object({ status: z22.literal("pending") }),
1319
- z22.object({ status: z22.literal("slow_down"), interval: z22.number().int().positive() }),
1320
- /** `token` is the relay session token, shown this once only. */
1321
- z22.object({ status: z22.literal("ok"), token: z22.string().min(1), accountId: z22.string().min(1), login: z22.string().min(1) }),
1322
- z22.object({ status: z22.literal("expired") }),
1323
- z22.object({ status: z22.literal("denied") }),
1324
- z22.object({ status: z22.literal("not_allowed") })
1325
- ]);
1326
- var DeviceRevokeResponseSchema = z22.object({ revoked: z22.number().int().min(0) });
1327
- var MeResponseSchema = z22.object({
1328
- accountId: z22.string().min(1),
1329
- login: z22.string().min(1),
1330
- peer: RelayPeerSchema,
1331
- kind: z22.enum(["pc", "device"])
1332
- });
1333
-
1334
- // ../protocol/src/phone.ts
1335
- import { z as z24 } from "zod";
1336
-
1337
- // ../protocol/src/push.ts
1338
- import { z as z23 } from "zod";
1339
- var B64U = z23.string().regex(/^[A-Za-z0-9_-]*$/);
1340
- var PUSH_HOSTS = ["*.push.apple.com", "fcm.googleapis.com", "updates.push.services.mozilla.com", "*.notify.windows.com"];
1341
- function pushEndpointAllowed(url) {
1342
- let u;
1343
- try {
1344
- u = new URL(url);
1345
- } catch {
1346
- return false;
1347
- }
1348
- if (u.protocol !== "https:" || u.port !== "" || u.username !== "" || u.password !== "") return false;
1349
- const host = u.hostname.toLowerCase();
1350
- return PUSH_HOSTS.some((h) => {
1351
- if (!h.startsWith("*.")) return host === h;
1352
- const suffix = h.slice(1);
1353
- return host.length > suffix.length && host.endsWith(suffix);
1354
- });
1355
- }
1356
- var PushSendSchema = z23.object({
1357
- endpoint: z23.string().url().max(1024),
1358
- body: B64U.max(5462),
1359
- ttl: z23.number().int().min(0).max(86400),
1360
- urgency: z23.enum(["normal", "high"]),
1361
- topic: z23.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1362
- });
1363
- var PushSendResultSchema = z23.object({
1364
- result: z23.enum(["sent", "gone", "failed"]),
1365
- status: z23.number().int().optional()
1366
- });
1367
- var PushSubscriptionSchema = z23.object({
1368
- endpoint: z23.string().url().max(1024).refine(pushEndpointAllowed),
1369
- keys: z23.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1370
- });
1371
- var PushPayloadSchema = z23.object({
1372
- v: z23.literal(1),
1373
- kind: z23.enum(["request", "question", "finished", "chat"]),
1374
- title: z23.string().max(80),
1375
- body: z23.string().max(120),
1376
- url: z23.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
1377
- badge: z23.number().int().min(0).max(999),
1378
- tag: z23.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1379
- });
1380
-
1381
- // ../protocol/src/phone.ts
1382
- var PHONE_PROTOCOL = 1;
1383
- var PAD_TIERS = [1024, 4096, 16384, 65536, 262144, 655360];
1384
- var PLAIN_MAX = 655360;
1385
- var utf8 = new TextEncoder();
1386
- function padPlain(json) {
1387
- const bytes = utf8.encode(json).length;
1388
- if (bytes > PLAIN_MAX) return null;
1389
- const tier = PAD_TIERS.find((t) => t >= bytes);
1390
- if (tier === void 0) return null;
1391
- return json + " ".repeat(tier - bytes);
1392
- }
1393
- var THREAD_ITEMS_MAX = 200;
1394
- var THREAD_BUDGET = 524288;
1395
- var THREAD_TEXT_MAX = 8192;
1396
- var TOOL_TEXT_MAX = 2048;
1397
- var PENDING_BODY_MAX = 65536;
1398
- var SNAPSHOT_BODY_MAX = 2048;
1399
- var RiskSchema = z24.enum(["low", "high"]);
1400
- var RiskReasonSchema = z24.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"]);
1401
- var OperationIdSchema = z24.string().uuid();
1402
- var OperationStatusSchema = z24.enum(["received", "applied", "rejected", "failed"]);
1403
- var OperationReasonSchema = z24.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired"]);
1404
- var CapabilitiesSchema = z24.object({
1405
- /** A question can be answered in free text. */
1406
- freeTextAnswer: z24.boolean(),
1407
- /** The session's accept/deny can be typed; absent = true. */
1408
- decide: z24.boolean().optional(),
1409
- /** OMP's dialog questions can be answered from the phone; absent = false. */
1410
- questions: z24.boolean().optional()
1411
- });
1412
- var UNTYPABLE = /[\u0000-\u001f\u007f-\u009f\u200b\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
1413
- var CapabilitiesByToolSchema = z24.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
1414
- var PhonePendingSchema = PendingSchema.extend({ key: z24.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z24.literal(true).optional() });
1415
- var PhoneSessionSchema = SessionSchema.pick({
1416
- id: true,
1417
- name: true,
1418
- tool: true,
1419
- project: true,
1420
- title: true,
1421
- topic: true,
1422
- repo: true,
1423
- branch: true,
1424
- worktree: true,
1425
- state: true,
1426
- helpers: true,
1427
- lastReply: true,
1428
- startedAt: true,
1429
- lastActivityAt: true,
1430
- /** Its role and its live team (ledge home spec §2.4): the agents screen groups a project's sessions by table. */
1431
- role: true,
1432
- roleLabel: true,
1433
- team: true,
1434
- /** Its context and its relief (context handover spec §3.2): the phone shows the gauge; the relief is started from the PC. */
1435
- context: true,
1436
- relieves: true
1437
- }).extend({ pending: PhonePendingSchema.optional(), unread: z24.boolean(), handover: z24.object({ state: HandoverStateSchema, toName: z24.string().max(80).optional() }).strict().optional() });
1438
- var B64U2 = z24.string().regex(/^[A-Za-z0-9_-]*$/);
1439
- var CredentialSchema = z24.object({
1440
- id: B64U2.max(1366),
1441
- publicKey: B64U2.max(2048),
1442
- alg: z24.union([z24.literal(-7), z24.literal(-257)]),
1443
- clientDataJSON: B64U2.max(4096),
1444
- authenticatorData: B64U2.max(4096),
1445
- transports: z24.array(z24.string().max(20)).max(8).optional()
1446
- });
1447
- var AssertionSchema = z24.object({
1448
- id: B64U2.max(1366),
1449
- clientDataJSON: B64U2.max(4096),
1450
- authenticatorData: B64U2.max(4096),
1451
- signature: B64U2.max(1024)
1452
- });
1453
- var ConfirmPurposeSchema = z24.enum(["register", "decide", "launch", "close"]);
1454
- var PhoneChatStepSchema = ChatStepSchema.pick({ kind: true, id: true, name: true }).strict();
1455
- var PhoneChatProposalSchema = z24.object({
1456
- project: z24.string().max(80),
1457
- tool: ToolSchema,
1458
- prompt: z24.string().max(TOOL_TEXT_MAX).optional(),
1459
- launchedId: z24.string().optional(),
1460
- failed: z24.literal(true).optional()
1461
- }).strict();
1462
- var PhoneChatRelaySchema = z24.object({
1463
- id: z24.string().min(1),
1464
- name: z24.string(),
1465
- tool: ToolSchema,
1466
- text: z24.string().max(TOOL_TEXT_MAX),
1467
- status: z24.enum(["queued", "sent"]).optional(),
1468
- sentAt: z24.string().datetime().optional(),
1469
- failed: z24.literal(true).optional()
1470
- }).strict();
1471
- var PhoneChatCloseSchema = z24.object({
1472
- id: z24.string().min(1),
1473
- name: z24.string(),
1474
- tool: ToolSchema,
1475
- how: z24.enum(["exit", "killed"]).optional(),
1476
- closedAt: z24.string().datetime().optional(),
1477
- failed: z24.literal(true).optional()
1478
- }).strict();
1479
- var PhoneChatTeamSchema = z24.object({
1480
- project: z24.string().max(80),
1481
- name: z24.string().max(60),
1482
- template: z24.string().max(64).optional(),
1483
- seats: z24.array(z24.object({ role: z24.string().max(40), label: z24.string().max(40), tool: ToolSchema, model: z24.string().max(200).optional(), effort: z24.string().max(20).optional(), autonomy: AutonomySchema.optional() }).strict()).min(1).max(8),
1484
- teamId: z24.string().max(40).optional(),
1485
- launchedAt: z24.string().datetime().optional(),
1486
- failed: z24.literal(true).optional()
1487
- }).strict();
1488
- var PhoneChatTaskSchema = z24.object({
1489
- to: z24.string().max(80),
1490
- toName: z24.string().max(80).optional(),
1491
- title: z24.string().max(200),
1492
- text: z24.string().max(TOOL_TEXT_MAX).optional(),
1493
- taskId: z24.string().max(40).optional(),
1494
- ownerName: z24.string().max(80).optional(),
1495
- state: z24.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
1496
- failed: z24.literal(true).optional()
1497
- }).strict();
1498
- var PhoneTeamSchema = z24.object({
1499
- id: z24.string().max(40),
1500
- name: z24.string().max(60),
1501
- project: z24.string().max(80).nullable(),
1502
- state: z24.enum(["active", "resting"]),
1503
- seats: z24.array(z24.object({ role: z24.string().max(40), sessionId: z24.string().max(100).nullable() }).strict()).max(16),
1504
- done: z24.number().int().min(0),
1505
- given: z24.number().int().min(0)
1506
- }).strict();
1507
- var PhoneTaskSchema = z24.object({
1508
- id: z24.string().max(40),
1509
- title: z24.string().max(200),
1510
- owner: z24.string().max(100),
1511
- ownerName: z24.string().max(80),
1512
- ownerRole: z24.string().max(40).optional(),
1513
- team: z24.string().max(40).optional(),
1514
- teamName: z24.string().max(60).optional(),
1515
- state: z24.enum(["open", "taken", "done", "refused", "dropped"]),
1516
- result: z24.string().max(TOOL_TEXT_MAX).optional(),
1517
- cut: z24.literal(true).optional(),
1518
- at: z24.string(),
1519
- updatedAt: z24.string(),
1520
- seen: z24.literal(true).optional()
1521
- }).strict();
1522
- var ChatErrorCodeSchema = z24.enum(["busy", "timeout", "cancelled", "failed"]);
1523
- var PhoneChatTurnSchema = z24.object({
1524
- qid: OperationIdSchema,
1525
- question: z24.string().max(MESSAGE_MAX),
1526
- askedAt: z24.string().datetime(),
1527
- steps: z24.array(PhoneChatStepSchema).max(50),
1528
- answer: z24.string().max(THREAD_TEXT_MAX).optional(),
1529
- answeredAt: z24.string().datetime().optional(),
1530
- error: ChatErrorCodeSchema.optional(),
1531
- proposals: z24.array(PhoneChatProposalSchema).max(MAX_PROPOSALS).optional(),
1532
- relay: PhoneChatRelaySchema.optional(),
1533
- closes: z24.array(PhoneChatCloseSchema).min(1).max(MAX_PROPOSALS).optional(),
1534
- cut: z24.literal(true).optional(),
1535
- team: PhoneChatTeamSchema.optional(),
1536
- tasks: z24.array(PhoneChatTaskSchema).min(1).max(MAX_PROPOSALS).optional(),
1537
- attachments: z24.array(AttachmentRefSchema).max(ATTACHMENTS_MAX).optional()
1538
- }).strict();
1539
- var CHAT_HISTORY_BUDGET = 393216;
1540
- var sessionId = z24.string().min(1).max(100);
1541
- var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z24.string().min(1).max(64) });
1542
- var CardRowsSchema = z24.array(z24.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
1543
- var PhoneClientMessageSchema = z24.discriminatedUnion("type", [
1544
- z24.object({ type: z24.literal("snapshot.request") }),
1545
- z24.object({ type: z24.literal("session.thread"), id: sessionId }),
1546
- /** The session's request with its whole body (up to `PENDING_BODY_MAX`). */
1547
- z24.object({ type: z24.literal("session.pending"), id: sessionId }),
1548
- /** The session whose terminal the phone shows; null when it shows none. */
1549
- z24.object({ type: z24.literal("session.watch"), id: sessionId.nullable() }),
1550
- z24.object({ type: z24.literal("session.read"), id: sessionId, at: z24.string().datetime() }),
1551
- /** Asks for a challenge. One object for both purposes: the gateway refuses a `decide` without `id` and `expect`. */
1552
- z24.object({ type: z24.literal("confirm.prepare"), purpose: ConfirmPurposeSchema, id: sessionId.optional(), expect: PhoneExpectSchema.optional(), qid: OperationIdSchema.optional() }),
1553
- z24.object({ type: z24.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
1554
- z24.object({
1555
- type: z24.literal("session.decide"),
1556
- op: OperationIdSchema,
1557
- id: sessionId,
1558
- decision: DecisionSchema,
1559
- note: z24.string().trim().min(1).max(NOTE_MAX).optional(),
1560
- /** The request the phone showed: refused unless it is still the pending one. */
1561
- expect: PhoneExpectSchema,
1562
- /** The device check over the challenge of `confirm.prepare`; required by the daemon for a high-risk accept. */
1563
- assertion: AssertionSchema.optional()
1564
- }),
1565
- z24.object({
1566
- type: z24.literal("session.reply"),
1567
- op: OperationIdSchema,
1568
- id: sessionId,
1569
- text: z24.string().max(MESSAGE_MAX),
1570
- attachments: z24.array(AttachmentIdSchema).max(ATTACHMENTS_MAX).optional(),
1571
- /** The request the reply answers, or null when the phone showed none: refused if the session's request is no longer that one. */
1572
- expect: PhoneExpectSchema.nullable(),
1573
- assertion: AssertionSchema.optional()
1574
- }),
1575
- /** OMP's `ask`: the answers to every question of the dialog `requestId`, in order; `cancel` declines it. Bounds as the local `session.answer`. */
1576
- z24.object({
1577
- type: z24.literal("session.answer"),
1578
- op: OperationIdSchema,
1579
- id: sessionId,
1580
- requestId: z24.string().min(1).max(200),
1581
- answers: z24.array(z24.object({ id: z24.string().max(200), selected: z24.array(z24.string().max(2e3)).max(100), text: z24.string().max(2e4).optional() })).max(50),
1582
- cancel: z24.literal(true).optional(),
1583
- expect: PhoneExpectSchema
1584
- }),
1585
- z24.object({ type: z24.literal("session.interrupt"), op: OperationIdSchema, id: sessionId }),
1586
- /** The phone drops its passkey from the daemon. */
1587
- z24.object({ type: z24.literal("device.forget"), op: OperationIdSchema }),
1588
- /** The phone's push subscription, with the VAPID key it was made with (so a key change is seen). */
1589
- z24.object({ type: z24.literal("push.subscribe"), op: OperationIdSchema, subscription: PushSubscriptionSchema, key: B64U2.length(87) }),
1590
- z24.object({ type: z24.literal("push.unsubscribe"), op: OperationIdSchema }),
1591
- /** The phone's own chat with Ledge. */
1592
- z24.object({ type: z24.literal("chat.ask"), qid: OperationIdSchema, text: z24.string().trim().max(MESSAGE_MAX), attachments: z24.array(AttachmentIdSchema).max(ATTACHMENTS_MAX).optional() }),
1593
- z24.object({ type: z24.literal("chat.cancel"), qid: OperationIdSchema }),
1594
- z24.object({ type: z24.literal("chat.reset") }),
1595
- z24.object({ type: z24.literal("chat.history") }),
1596
- /** An image to store, sent alone before the message that names it; no `id`, and at most `ATTACHMENT_MAX` bytes. */
1597
- AttachmentPutSchema.pick({ type: true, requestId: true }).extend({ image: ImageUploadSchema.extend({ data: z24.string().min(1).max(b64Max(ATTACHMENT_MAX)) }) }),
1598
- AttachmentGetSchema.pick({ type: true, requestId: true, attachment: true, preview: true }),
1599
- z24.object({ type: z24.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
1600
- z24.object({ type: z24.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
1601
- z24.object({ type: z24.literal("chat.close"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
1602
- /** The turn's team card (roles spec §3.8): launched like a session card, with the device check (purpose `launch`, the turn). */
1603
- z24.object({ type: z24.literal("chat.team"), op: OperationIdSchema, qid: OperationIdSchema, assertion: AssertionSchema.optional() }),
1604
- /** A row of the turn's task card: given by Ledge. */
1605
- z24.object({ type: z24.literal("chat.task"), op: OperationIdSchema, qid: OperationIdSchema, row: z24.number().int().min(0).max(MAX_PROPOSALS - 1) }),
1606
- /** « Reprendre » a resting team. */
1607
- z24.object({ type: z24.literal("team.resume"), op: OperationIdSchema, team: z24.string().min(1).max(40) }),
1608
- /** Puts a closed task Ledge gave away from « À traiter ». */
1609
- z24.object({ type: z24.literal("task.seen"), op: OperationIdSchema, task: z24.string().min(1).max(40) }),
1610
- /** Whether the phone's app is in front: a device that is visible gets no push. */
1611
- z24.object({ type: z24.literal("presence"), visible: z24.boolean() })
1612
- ]);
1613
- var PhoneServerMessageSchema = z24.discriminatedUnion("type", [
1614
- z24.object({ type: z24.literal("snapshot"), protocol: z24.literal(PHONE_PROTOCOL), sessions: z24.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z24.object({ version: z24.string(), name: z24.string().max(40), boot: z24.string().uuid().optional() }), device: z24.object({ confirm: z24.boolean(), push: z24.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z24.literal(true).optional(), contextNear: ContextPrefsSchema.shape.near.optional() }).optional() }),
1615
- z24.object({ type: z24.literal("confirm.challenge"), purpose: ConfirmPurposeSchema, challenge: B64U2.max(64), rpId: z24.string().max(253), credentialId: B64U2.max(1366).optional(), expiresInMs: z24.number().int().positive() }),
1616
- z24.object({ type: z24.literal("session.updated"), session: PhoneSessionSchema }),
1617
- z24.object({ type: z24.literal("session.removed"), id: z24.string() }),
1618
- /** `pending.cut` is set when even this body was shortened. */
1619
- z24.object({ type: z24.literal("session.pending"), id: z24.string(), pending: PhonePendingSchema }),
1620
- z24.object({ type: z24.literal("session.preview"), id: z24.string(), lines: z24.array(z24.string()) }),
1621
- /** `cut`: the ids of the items shortened; `more`: older items were left out. */
1622
- z24.object({ type: z24.literal("session.thread"), id: z24.string(), items: z24.array(ThreadItemSchema), cut: z24.array(z24.string()), more: z24.boolean() }),
1623
- z24.object({ type: z24.literal("session.thread.item"), id: z24.string(), item: ThreadItemSchema, cut: z24.literal(true).optional() }),
1624
- z24.object({ type: z24.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional() }),
1625
- z24.object({ type: z24.literal("chat.history"), turns: z24.array(PhoneChatTurnSchema).max(50) }),
1626
- z24.object({ type: z24.literal("chat.step"), qid: OperationIdSchema, step: PhoneChatStepSchema }),
1627
- /** The whole text so far, not an increment. */
1628
- z24.object({ type: z24.literal("chat.delta"), qid: OperationIdSchema, text: z24.string().max(THREAD_TEXT_MAX) }),
1629
- z24.object({ type: z24.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
1630
- z24.object({ type: z24.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
1631
- /** The live teams, after each snapshot and on every change. */
1632
- z24.object({ type: z24.literal("teams"), teams: z24.array(PhoneTeamSchema).max(50) }),
1633
- /** The tasks Ledge gave, open or closed lately, after each snapshot; then each change. */
1634
- z24.object({ type: z24.literal("tasks"), tasks: z24.array(PhoneTaskSchema).max(200) }),
1635
- z24.object({ type: z24.literal("task.updated"), task: PhoneTaskSchema }),
1636
- AttachmentStoredSchema,
1637
- AttachmentDataSchema,
1638
- AttachmentErrorSchema,
1639
- /** Never carries free text. */
1640
- z24.object({ type: z24.literal("error"), code: z24.enum(["invalid", "unknown_session", "too_large"]), request: z24.string().max(40).optional() })
1641
- ]);
1642
-
1643
- // src/auth.ts
1644
- import { createHash, randomBytes, timingSafeEqual } from "crypto";
1645
- import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
1646
- import { readFileSync } from "fs";
1647
- import { dirname } from "path";
1648
- var TOKEN_RE = /^[0-9a-f]{64}$/;
1649
- var DAEMON_PROTOCOL_PREFIX = "ledge.";
1650
- function isDaemonToken(value) {
1651
- return TOKEN_RE.test(value);
1652
- }
1653
- function tokenEquals(presented, expected) {
1654
- const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
1655
- const b = createHash("sha256").update(expected, "utf8").digest();
1656
- return timingSafeEqual(a, b);
1657
- }
1658
- function hostAllowed(hostHeader) {
1659
- const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
1660
- if (!raw || raw.length > 255) return false;
1661
- const host = raw.trim().toLowerCase();
1662
- if (!host || host.includes(" ") || host.endsWith(".")) return false;
1663
- let name;
1664
- if (host.startsWith("[")) {
1665
- const end = host.indexOf("]");
1666
- if (end < 0) return false;
1667
- name = host.slice(1, end);
1668
- const rest = host.slice(end + 1);
1669
- if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
1670
- } else {
1671
- const colon = host.lastIndexOf(":");
1672
- if (colon >= 0) {
1673
- if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
1674
- name = host.slice(0, colon);
1675
- } else name = host;
1676
- }
1677
- return name === "localhost" || name === "127.0.0.1" || name === "::1";
1678
- }
1679
- function headerValue(value, max) {
1680
- const raw = Array.isArray(value) ? value.join(",") : value;
1681
- return !raw || raw.length > max ? "" : raw;
1682
- }
1683
- function bearerToken(authorization) {
1684
- if (!authorization || authorization.length > 512) return "";
1685
- return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
1686
- }
1687
- function protocolToken(header) {
1688
- const raw = headerValue(header, 512);
1689
- if (!raw) return "";
1690
- for (const part of raw.split(",").slice(0, 8)) {
1691
- const protocol = part.trim();
1692
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
1693
- }
1694
- return "";
1695
- }
1696
- function requestAuthorized(headers, expected) {
1697
- const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
1698
- const presented = protocolToken(headers["sec-websocket-protocol"]);
1699
- return bearerOk || presented !== "" && tokenEquals(presented, expected);
1700
- }
1701
- function selectDaemonProtocol(protocols, expected) {
1702
- for (const protocol of protocols) {
1703
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
1704
- }
1705
- return false;
1706
- }
1707
- async function readTokenFile(file) {
1708
- try {
1709
- const raw = (await readFile(file, "utf8")).trim();
1710
- return isDaemonToken(raw) ? raw : void 0;
1711
- } catch (e) {
1712
- if (e.code === "ENOENT") return void 0;
1713
- throw e;
1714
- }
1715
- }
1716
- async function lockTokenFile(file) {
1717
- await chmod(file, 384);
1718
- }
1719
- async function ensureDaemonToken() {
1720
- const file = paths().token;
1721
- await mkdir(dirname(file), { recursive: true });
1722
- const current = await readTokenFile(file);
1723
- if (current) {
1724
- await lockTokenFile(file);
1725
- return current;
1726
- }
1727
- const token = randomBytes(32).toString("hex");
1728
- const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
1729
- await writeFile(tmp, `${token}
1730
- `, { mode: 384, flag: "wx" });
1731
- await chmod(tmp, 384);
1732
- try {
1733
- await link(tmp, file);
1734
- } catch (e) {
1735
- await unlink(tmp).catch(() => {
1736
- });
1737
- if (e.code !== "EEXIST") throw e;
1738
- const raced = await readTokenFile(file);
1739
- if (raced) {
1740
- await lockTokenFile(file);
1741
- return raced;
1742
- }
1743
- await writeFile(file, `${token}
1744
- `, { mode: 384 });
1745
- }
1746
- await unlink(tmp).catch(() => {
1747
- });
1748
- await lockTokenFile(file);
1749
- const stored = await readTokenFile(file);
1750
- if (!stored) throw new Error("daemon token was not stored");
1751
- return stored;
1752
- }
1753
- function readDaemonToken() {
1754
- try {
1755
- const raw = readFileSync(paths().token, "utf8").trim();
1756
- return isDaemonToken(raw) ? raw : void 0;
1757
- } catch {
1758
- return void 0;
1759
- }
1760
- }
1761
-
1762
- export {
1763
- ATTACHMENT_ID,
1764
- ATTACHMENT_MAX,
1765
- ATTACHMENT_LOCAL_MAX,
1766
- PREVIEW_MAX,
1767
- ATTACHMENTS_MAX,
1768
- AttachmentRefSchema,
1769
- CONTEXT_NEAR,
1770
- CONTEXT_NEAR_MIN,
1771
- CONTEXT_NEAR_MAX,
1772
- ContextPrefsSchema,
1773
- contextNear,
1774
- BRIEF_MAX,
1775
- ToolSchema,
1776
- SessionSchema,
1777
- backendOf,
1778
- isArchived,
1779
- MAX_PROPOSALS,
1780
- ChatTurnSchema,
1781
- proposalRows,
1782
- NOTE_MAX,
1783
- NOTE_NOT_TYPED,
1784
- offers,
1785
- pendingKey,
1786
- DOCUMENT_MAX_BYTES,
1787
- SshAliasSchema,
1788
- MachinesSchema,
1789
- IslandPrefsSchema,
1790
- PROJECT_COLORS,
1791
- ProjectsSchema,
1792
- DEFAULT_PROJECTS,
1793
- projectOf,
1794
- ModelIdSchema,
1795
- EffortSchema,
1796
- RouteResultSchema,
1797
- TASK_TITLE_MAX,
1798
- TASK_TEXT_MAX,
1799
- LEDGE_GIVER,
1800
- transmissions,
1801
- givenByLedge,
1802
- taskClosed,
1803
- DEFAULT_RELAY_URL,
1804
- MobileConfigSchema,
1805
- ROLE_LABEL_MAX,
1806
- ROLE_PROMPT_MAX,
1807
- RoleSchema,
1808
- SeatsSchema,
1809
- TeamNameSchema,
1810
- TeamSchema,
1811
- PROTOCOL_PORT,
1812
- RefusalCodeSchema,
1813
- RecentSchema,
1814
- MESSAGE_MAX,
1815
- SUMMARY_MAX,
1816
- parseClientMessage,
1817
- parseServerMessage,
1818
- NAMES,
1819
- pickName,
1820
- extractProposal,
1821
- parseProposalBlock,
1822
- parseRelayBlock,
1823
- parseCloseBlock,
1824
- parseTeamBlock,
1825
- parseTaskBlock,
1826
- parseProjectBlock,
1827
- TOPIC_MAX,
1828
- cleanTopic,
1829
- sessionTitle,
1830
- RelayServerMessageSchema,
1831
- PAIR_LABEL_MAX,
1832
- SealedFrameSchema,
1833
- RELAY_CLOSE_SIGNED_OUT,
1834
- DeviceStartResponseSchema,
1835
- DevicePollResponseSchema,
1836
- pushEndpointAllowed,
1837
- PushSendResultSchema,
1838
- PushPayloadSchema,
1839
- PHONE_PROTOCOL,
1840
- padPlain,
1841
- THREAD_ITEMS_MAX,
1842
- THREAD_BUDGET,
1843
- THREAD_TEXT_MAX,
1844
- TOOL_TEXT_MAX,
1845
- PENDING_BODY_MAX,
1846
- SNAPSHOT_BODY_MAX,
1847
- OperationIdSchema,
1848
- UNTYPABLE,
1849
- CHAT_HISTORY_BUDGET,
1850
- PhoneClientMessageSchema,
1851
- hostAllowed,
1852
- requestAuthorized,
1853
- selectDaemonProtocol,
1854
- ensureDaemonToken,
1855
- readDaemonToken
1856
- };