@paradow/ledge 0.34.0 → 0.35.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 (36) hide show
  1. package/dist/cli/index.js +39003 -39
  2. package/dist/cli/{chunk-IWY7DSSO.js → reduceWorker.js} +749 -1188
  3. package/package.json +4 -1
  4. package/dist/cli/action-K42H5BDA.js +0 -27
  5. package/dist/cli/attach-KEE4A5YP.js +0 -87
  6. package/dist/cli/bridge-X327AZY4.js +0 -70
  7. package/dist/cli/chunk-25RVT5MR.js +0 -623
  8. package/dist/cli/chunk-3OJCWJAA.js +0 -144
  9. package/dist/cli/chunk-4OYVM4MT.js +0 -49
  10. package/dist/cli/chunk-4TCSADFR.js +0 -252
  11. package/dist/cli/chunk-53ISXX2R.js +0 -39
  12. package/dist/cli/chunk-54KF5RBY.js +0 -17
  13. package/dist/cli/chunk-7NHQGP6J.js +0 -95
  14. package/dist/cli/chunk-G7XPEVCG.js +0 -118
  15. package/dist/cli/chunk-KPDTYX7F.js +0 -60
  16. package/dist/cli/chunk-OLLQBK6N.js +0 -41
  17. package/dist/cli/chunk-RPH773TY.js +0 -6
  18. package/dist/cli/chunk-RX5CJ7YH.js +0 -43
  19. package/dist/cli/chunk-T547FLQN.js +0 -128
  20. package/dist/cli/chunk-U67V476Y.js +0 -35
  21. package/dist/cli/chunk-UJ2WBGLW.js +0 -305
  22. package/dist/cli/chunk-ULOMKEB6.js +0 -85
  23. package/dist/cli/chunk-V4XKKL6K.js +0 -205
  24. package/dist/cli/daemon-RO43S3S5.js +0 -11449
  25. package/dist/cli/decide-KZFYL3HY.js +0 -59
  26. package/dist/cli/handover-BWNMI2RA.js +0 -17
  27. package/dist/cli/hook-ZLWXI5EQ.js +0 -473
  28. package/dist/cli/launch-GCX7TXOH.js +0 -28
  29. package/dist/cli/send-SXZAPVRH.js +0 -19
  30. package/dist/cli/setup-TJ32FMVJ.js +0 -38
  31. package/dist/cli/status-YDVRES4E.js +0 -28
  32. package/dist/cli/task-TUSOVJK7.js +0 -19
  33. package/dist/cli/tui-TCVIRZCQ.js +0 -19348
  34. package/dist/cli/uninstall-QO7YMSLA.js +0 -162
  35. package/dist/cli/version-BAYSYWFP.js +0 -7
  36. package/dist/cli/who-6AZYSP7T.js +0 -12
@@ -1,126 +1,168 @@
1
- import {
2
- paths
3
- } from "./chunk-53ISXX2R.js";
1
+ // src/daemon/reduceWorker.ts
2
+ import { parentPort } from "worker_threads";
4
3
 
5
- // ../protocol/src/handover.ts
4
+ // src/daemon/attachments.ts
5
+ import { createHash, randomUUID } from "crypto";
6
+ import { mkdir, readdir, readFile, rename, stat, unlink, utimes, writeFile } from "fs/promises";
7
+ import { join } from "path";
8
+
9
+ // ../protocol/src/attachments.ts
6
10
  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;
11
+ var ATTACHMENT_ID = /^[0-9a-f]{64}$/;
12
+ var AttachmentIdSchema = z.string().regex(ATTACHMENT_ID);
13
+ var IMAGE_MIMES = ["image/webp", "image/jpeg", "image/png", "image/gif"];
14
+ var ImageMimeSchema = z.enum(IMAGE_MIMES);
15
+ var ATTACHMENT_MAX = 4e5;
16
+ var ATTACHMENT_LOCAL_MAX = 2e6;
17
+ var PREVIEW_MAX = 4e4;
18
+ var CAPTURE_RAW_MAX = 28e5;
19
+ var ATTACHMENTS_MAX = 5;
20
+ var IMAGE_LONG_SIDE = 1600;
21
+ var PREVIEW_LONG_SIDE = 480;
22
+ var b64Max = (bytes) => Math.ceil(bytes / 3) * 4;
23
+ var AttachmentRefSchema = z.object({
24
+ id: AttachmentIdSchema,
25
+ mime: ImageMimeSchema,
26
+ width: z.number().int().positive(),
27
+ height: z.number().int().positive(),
28
+ bytes: z.number().int().positive(),
29
+ preview: z.boolean()
30
+ });
31
+ var ImageUploadSchema = z.object({
32
+ mime: ImageMimeSchema,
33
+ data: z.string().min(1).max(b64Max(ATTACHMENT_LOCAL_MAX)),
34
+ preview: z.object({ mime: ImageMimeSchema, data: z.string().min(1).max(b64Max(PREVIEW_MAX)) }).optional()
35
+ });
36
+ var AttachmentErrorReasonSchema = z.enum(["too_large", "not_image", "unreadable", "not_found", "offline"]);
37
+ var requestId = z.string().min(1).max(100);
38
+ var AttachmentPutSchema = z.object({ type: z.literal("attachment.put"), requestId, id: z.string().optional(), image: ImageUploadSchema });
39
+ var AttachmentGetSchema = z.object({ type: z.literal("attachment.get"), requestId, attachment: AttachmentIdSchema, preview: z.boolean().optional(), id: z.string().optional(), machine: z.string().optional() });
40
+ var AttachmentCaptureSchema = z.object({ type: z.literal("attachment.capture"), requestId, image: z.object({ mime: ImageMimeSchema, data: z.string().min(1).max(b64Max(CAPTURE_RAW_MAX)) }) });
41
+ var AttachmentStoredSchema = z.object({ type: z.literal("attachment.stored"), requestId, ref: AttachmentRefSchema });
42
+ var AttachmentDataSchema = z.object({ type: z.literal("attachment.data"), requestId, attachment: AttachmentIdSchema, mime: ImageMimeSchema, data: z.string() });
43
+ var AttachmentErrorSchema = z.object({ type: z.literal("attachment.error"), requestId, reason: AttachmentErrorReasonSchema });
44
+
45
+ // ../protocol/src/chat.ts
46
+ import { z as z9 } from "zod";
47
+
48
+ // ../protocol/src/session.ts
49
+ import { z as z3 } from "zod";
50
+
51
+ // ../protocol/src/handover.ts
52
+ import { z as z2 } from "zod";
53
+ var ContextSchema = z2.object({ used: z2.number().int().min(0), window: z2.number().int().positive(), at: z2.string().datetime() });
9
54
  var CONTEXT_NEAR_MIN = 0.5;
10
55
  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({
56
+ var ContextPrefsSchema = z2.object({ near: z2.number().min(CONTEXT_NEAR_MIN).max(CONTEXT_NEAR_MAX) });
57
+ var HandoverStateSchema = z2.enum(["briefing", "launching", "done", "failed"]);
58
+ var HandoverSchema = z2.object({
16
59
  state: HandoverStateSchema,
17
- at: z.string().datetime(),
60
+ at: z2.string().datetime(),
18
61
  /** The relief, once launched. */
19
- to: z.string().optional(),
20
- toName: z.string().optional(),
21
- reason: z.string().optional(),
62
+ to: z2.string().optional(),
63
+ toName: z2.string().optional(),
64
+ reason: z2.string().optional(),
22
65
  /** Started by Ledge itself (a session working without asking), not by the user. */
23
- auto: z.literal(true).optional()
66
+ auto: z2.literal(true).optional()
24
67
  });
25
- var RelievesSchema = z.object({ id: z.string(), name: z.string(), at: z.string().datetime() });
68
+ var RelievesSchema = z2.object({ id: z2.string(), name: z2.string(), at: z2.string().datetime() });
26
69
  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() });
70
+ var ContextSetSchema = z2.object({ type: z2.literal("context.set"), prefs: ContextPrefsSchema });
71
+ var ContextPrefsMessageSchema = z2.object({ type: z2.literal("context.prefs"), prefs: ContextPrefsSchema });
72
+ var SessionHandoverSchema = z2.object({ type: z2.literal("session.handover"), rid: z2.string().min(1).max(100), id: z2.string().min(1) });
73
+ var HandoverBriefSchema = z2.object({ type: z2.literal("handover.brief"), rid: z2.string().min(1).max(100), by: z2.string().min(1), text: z2.string().min(1).max(BRIEF_MAX) });
74
+ var HandoverShowSchema = z2.object({ type: z2.literal("handover.show"), rid: z2.string().min(1).max(100), by: z2.string().min(1) });
75
+ var SessionHandoverResultSchema = z2.object({ type: z2.literal("session.handover.result"), rid: z2.string(), ok: z2.boolean(), reason: z2.string().optional() });
76
+ var HandoverResultSchema = z2.object({ type: z2.literal("handover.result"), rid: z2.string(), text: z2.string().optional(), reason: z2.string().optional() });
34
77
 
35
78
  // ../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 BackendSchema = z2.enum(["tmux", "omp-rpc"]);
40
- var SessionStateSchema = z2.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
41
- var AskQuestionSchema = z2.object({
42
- id: z2.string(),
43
- question: z2.string(),
44
- header: z2.string().optional(),
45
- multi: z2.boolean().optional(),
46
- options: z2.array(z2.object({ label: z2.string(), description: z2.string().optional() }))
47
- });
48
- var PendingSchema = z2.object({
49
- kind: z2.enum(["question", "permission", "plan", "diff"]),
50
- text: z2.string(),
79
+ var ToolSchema = z3.enum(["claude", "codex", "cursor", "omp"]);
80
+ var OmpApprovalSchema = z3.enum(["always-ask", "write", "yolo"]);
81
+ var BackendSchema = z3.enum(["tmux", "omp-rpc"]);
82
+ var SessionStateSchema = z3.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
83
+ var AskQuestionSchema = z3.object({
84
+ id: z3.string(),
85
+ question: z3.string(),
86
+ header: z3.string().optional(),
87
+ multi: z3.boolean().optional(),
88
+ options: z3.array(z3.object({ label: z3.string(), description: z3.string().optional() }))
89
+ });
90
+ var PendingSchema = z3.object({
91
+ kind: z3.enum(["question", "permission", "plan", "diff"]),
92
+ text: z3.string(),
51
93
  /** What to review in full before deciding, when `text` is only its summary: a plan's markdown. */
52
- body: z2.string().optional(),
53
- tool: z2.string().optional(),
54
- options: z2.array(z2.string()).optional(),
94
+ body: z3.string().optional(),
95
+ tool: z3.string().optional(),
96
+ options: z3.array(z3.string()).optional(),
55
97
  /** Each option's description, by index ('' when it has none); left out when no option has one. */
56
- optionDetails: z2.array(z2.string()).optional(),
98
+ optionDetails: z3.array(z3.string()).optional(),
57
99
  /** `rpc`: the harness's own protocol asked (OMP): answered on its request id, never by typing. */
58
- source: z2.enum(["hook", "pane", "rpc"]),
100
+ source: z3.enum(["hook", "pane", "rpc"]),
59
101
  /** OMP: every question of one `ask` call, answered together with `session.answer`; `requestId` is the dialog it answers. */
60
- questions: z2.array(AskQuestionSchema).optional(),
61
- requestId: z2.string().optional(),
102
+ questions: z3.array(AskQuestionSchema).optional(),
103
+ requestId: z3.string().optional(),
62
104
  /** Set when the request comes from a helper (subagent): it is routed through its parent. */
63
- helperId: z2.string().optional(),
105
+ helperId: z3.string().optional(),
64
106
  /** For a helper-routed request: the state the parent returns to once it is answered (main thread may stop meanwhile). */
65
107
  parentState: SessionStateSchema.optional()
66
108
  });
67
- var ReplySchema = z2.object({
68
- text: z2.string(),
69
- at: z2.string().datetime(),
109
+ var ReplySchema = z3.object({
110
+ text: z3.string(),
111
+ at: z3.string().datetime(),
70
112
  /** The reply is a plan the agent proposes (Codex Plan mode): nothing in it is done yet. */
71
- plan: z2.boolean().optional()
113
+ plan: z3.boolean().optional()
72
114
  });
73
- var HelperSchema = z2.object({
74
- id: z2.string(),
75
- kind: z2.string(),
76
- task: z2.string(),
115
+ var HelperSchema = z3.object({
116
+ id: z3.string(),
117
+ kind: z3.string(),
118
+ task: z3.string(),
77
119
  state: SessionStateSchema,
78
120
  /** When this helper finished; retained across daemon restarts until its five-minute expiry. */
79
- finishedAt: z2.string().datetime().optional()
121
+ finishedAt: z3.string().datetime().optional()
80
122
  });
81
- var TopicSourceSchema = z2.enum(["user", "pane", "prompt"]);
82
- var SessionSchema = z2.object({
123
+ var TopicSourceSchema = z3.enum(["user", "pane", "prompt"]);
124
+ var SessionSchema = z3.object({
83
125
  /** Origin is annotated by the overlay, never used as an unqualified session id on the wire. */
84
- machineId: z2.string().optional(),
85
- machineName: z2.string().optional(),
86
- repoId: z2.string().optional(),
126
+ machineId: z3.string().optional(),
127
+ machineName: z3.string().optional(),
128
+ repoId: z3.string().optional(),
87
129
  /** Annotated by the fleet when its machine is unreachable and the last snapshot is kept (keepStale, spec 2026-10-14 §6). */
88
- offline: z2.boolean().optional(),
89
- id: z2.string().min(1),
90
- name: z2.string().min(1),
130
+ offline: z3.boolean().optional(),
131
+ id: z3.string().min(1),
132
+ name: z3.string().min(1),
91
133
  tool: ToolSchema,
92
- cwd: z2.string(),
93
- project: z2.string(),
94
- title: z2.string(),
134
+ cwd: z3.string(),
135
+ project: z3.string(),
136
+ title: z3.string(),
95
137
  /** One line saying what the session is about (spec 2026-10-11 §3). */
96
- topic: z2.string().optional(),
138
+ topic: z3.string().optional(),
97
139
  topicSource: TopicSourceSchema.optional(),
98
140
  /** The repository (its main worktree's folder name and absolute path), the checked-out branch, and whether `cwd` is a linked worktree (§4). */
99
- repo: z2.string().optional(),
100
- repoPath: z2.string().optional(),
101
- branch: z2.string().optional(),
102
- worktree: z2.boolean().optional(),
141
+ repo: z3.string().optional(),
142
+ repoPath: z3.string().optional(),
143
+ branch: z3.string().optional(),
144
+ worktree: z3.boolean().optional(),
103
145
  /** The folder the agent works in now, as its last hook reported it: `cwd` stays where it started, this follows a worktree it entered. */
104
- workdir: z2.string().optional(),
146
+ workdir: z3.string().optional(),
105
147
  /** The OpenRig seat this agent is: its session name (`dev-build@starter`), its rig, and its role (`dev.build`) once OpenRig named it. */
106
- rig: z2.object({ seat: z2.string(), name: z2.string(), role: z2.string().optional() }).optional(),
107
- tmuxSession: z2.string().nullable(),
148
+ rig: z3.object({ seat: z3.string(), name: z3.string(), role: z3.string().optional() }).optional(),
149
+ tmuxSession: z3.string().nullable(),
108
150
  /** Left out for a tmux session. `omp-rpc`: no pane, nothing to attach to; the daemon holds the process (see `agentSessionFile`). */
109
151
  backend: BackendSchema.optional(),
110
152
  state: SessionStateSchema,
111
153
  pending: PendingSchema.optional(),
112
- helpers: z2.array(HelperSchema).default([]),
154
+ helpers: z3.array(HelperSchema).default([]),
113
155
  /** OMP: the instructions waiting in its own queue (typed while it works), as it reports them; left out when none. */
114
- queue: z2.array(z2.object({ text: z2.string(), mode: z2.enum(["steer", "follow_up"]) })).optional(),
156
+ queue: z3.array(z3.object({ text: z3.string(), mode: z3.enum(["steer", "follow_up"]) })).optional(),
115
157
  lastReply: ReplySchema.optional(),
116
158
  /** 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. */
117
- lastMessage: z2.object({ id: z2.string(), to: z2.string(), peer: z2.string().optional(), summary: z2.string(), at: z2.string().datetime() }).optional(),
118
- startedAt: z2.string().datetime(),
119
- lastActivityAt: z2.string().datetime(),
120
- lastHookAt: z2.string().datetime().optional(),
159
+ lastMessage: z3.object({ id: z3.string(), to: z3.string(), peer: z3.string().optional(), summary: z3.string(), at: z3.string().datetime() }).optional(),
160
+ startedAt: z3.string().datetime(),
161
+ lastActivityAt: z3.string().datetime(),
162
+ lastHookAt: z3.string().datetime().optional(),
121
163
  /** The model and effort Ledge launched the agent with (#58); left out, the agent's own default. */
122
- model: z2.string().optional(),
123
- effort: z2.string().optional(),
164
+ model: z3.string().optional(),
165
+ effort: z3.string().optional(),
124
166
  /** OMP: the approval mode it was launched with, kept by a resume; left out, `always-ask`. */
125
167
  approval: OmpApprovalSchema.optional(),
126
168
  /** Its context as its agent last reported it, after a turn (context handover spec §3.1); left out, not measured. */
@@ -130,218 +172,136 @@ var SessionSchema = z2.object({
130
172
  /** The session it relieves, when it is a relief. */
131
173
  relieves: RelievesSchema.optional(),
132
174
  /** 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. */
133
- modelNext: z2.object({ model: z2.string().optional(), effort: z2.string().optional() }).optional(),
175
+ modelNext: z3.object({ model: z3.string().optional(), effort: z3.string().optional() }).optional(),
134
176
  /** The agent's own session id when Ledge chose it at launch (Claude Code `--session-id`). */
135
- agentSessionId: z2.string().optional(),
177
+ agentSessionId: z3.string().optional(),
136
178
  /** OMP: the exact journal file of the conversation, what a resume names (never "the latest of the folder"). */
137
- agentSessionFile: z2.string().optional(),
179
+ agentSessionFile: z3.string().optional(),
138
180
  /** The agent's transcript: the latest one a hook named (it follows /clear and --resume), else the one the launch implies. */
139
- transcriptPath: z2.string().optional(),
140
- exitCode: z2.number().int().nullable().optional(),
141
- exitedAt: z2.string().datetime().optional(),
142
- lastError: z2.string().optional(),
181
+ transcriptPath: z3.string().optional(),
182
+ exitCode: z3.number().int().nullable().optional(),
183
+ exitedAt: z3.string().datetime().optional(),
184
+ lastError: z3.string().optional(),
143
185
  /** `launcher`: Ledge launched it; `hook_only`: only its hooks are known (read-only); `rig`: an OpenRig seat Ledge follows through
144
186
  * its tmux pane on the user's default server, answered like its own, whose life stays OpenRig's (no close, kill, resume, model). */
145
187
  // A kind a newer daemon adds reads as read-only here, never as a snapshot thrown away whole (« hors ligne »).
146
- source: z2.enum(["launcher", "hook_only", "rig"]).catch("hook_only"),
188
+ source: z3.enum(["launcher", "hook_only", "rig"]).catch("hook_only"),
147
189
  /** When the session was put in the archive (session management): set, it leaves the live list and waits to be restored, resumed or deleted. */
148
- archivedAt: z2.string().datetime().optional(),
190
+ archivedAt: z3.string().datetime().optional(),
149
191
  /** When the session was last taken out of the archive: the poller's retirement clock restarts from it, not from `exitedAt`. */
150
- restoredAt: z2.string().datetime().optional()
151
- });
152
- function backendOf(s) {
153
- return s.backend ?? "tmux";
154
- }
155
- function isArchived(s) {
156
- return s.archivedAt !== void 0;
157
- }
158
-
159
- // ../protocol/src/tasks.ts
160
- import { z as z3 } from "zod";
161
- var TASK_TITLE_MAX = 200;
162
- var TASK_TEXT_MAX = 2e4;
163
- var LEDGE_GIVER = "ledge";
164
- var TASK_OPS = ["give", "take", "pass", "done", "refuse", "cancel", "drop"];
165
- var TRANSMISSION_OPS = ["give", "pass", "done", "refuse", "cancel"];
166
- var TaskLogEntrySchema = z3.object({
167
- at: z3.string(),
168
- by: z3.string(),
169
- what: z3.string(),
170
- op: z3.enum(TASK_OPS).optional(),
171
- from: z3.string().optional(),
172
- to: z3.string().optional(),
173
- fromName: z3.string().optional(),
174
- toName: z3.string().optional(),
175
- note: z3.string().optional()
176
- });
177
- var TaskSchema = z3.object({
178
- id: z3.string(),
179
- title: z3.string(),
180
- text: z3.string().optional(),
181
- giver: z3.string(),
182
- giverName: z3.string(),
183
- owner: z3.string(),
184
- ownerName: z3.string(),
185
- state: z3.enum(["open", "taken", "done", "refused", "dropped"]),
186
- result: z3.string().optional(),
187
- /** A task Ledge gave and the user put away from « À toi » once closed. */
188
- seen: z3.boolean().optional(),
189
- at: z3.string(),
190
- updatedAt: z3.string(),
191
- log: z3.array(TaskLogEntrySchema)
192
- });
193
- var handOver = (op) => TRANSMISSION_OPS.includes(op ?? "");
194
- function transmissions(t) {
195
- 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 } : {} }] : []);
196
- }
197
- var givenByLedge = (t) => t.giver === LEDGE_GIVER;
198
- var taskClosed = (t) => t.state === "done" || t.state === "refused" || t.state === "dropped";
199
- var openDecision = (t) => t.owner === LEDGE_GIVER && !taskClosed(t);
200
- var TaskCmdSchema = z3.object({
201
- type: z3.literal("task.cmd"),
202
- rid: z3.string().min(1),
203
- by: z3.string().min(1),
204
- op: z3.enum(["give", "take", "done", "refuse", "pass", "show", "list", "seen", "cancel"]),
205
- task: z3.string().min(1).max(40).optional(),
206
- to: z3.string().min(1).optional(),
207
- title: z3.string().min(1).max(TASK_TITLE_MAX).optional(),
208
- text: z3.string().max(TASK_TEXT_MAX).optional(),
209
- all: z3.boolean().optional(),
210
- qid: z3.string().min(1).optional(),
211
- row: z3.number().int().min(0).max(7).optional()
212
- });
213
- var TaskUpdatedSchema = z3.object({ type: z3.literal("task.updated"), task: TaskSchema });
214
- var TaskResultSchema = z3.object({
215
- type: z3.literal("task.result"),
216
- rid: z3.string(),
217
- task: TaskSchema.optional(),
218
- tasks: z3.array(TaskSchema).optional(),
219
- reason: z3.string().optional()
192
+ restoredAt: z3.string().datetime().optional()
220
193
  });
221
194
 
222
- // ../protocol/src/actions.ts
223
- import { z as z7 } from "zod";
224
-
225
- // ../protocol/src/proposal.ts
226
- import { z as z4 } from "zod";
227
- var ProposalBlockSchema = z4.object({ cwd: z4.string().trim().min(1), prompt: z4.string().optional() });
228
- var RelayBlockSchema = z4.object({ session: z4.string().trim().min(1), text: z4.string().trim().min(1) });
229
- var ProjectBlockSchema = z4.object({
230
- name: z4.string().trim().min(1).max(80),
231
- description: z4.string().max(500).optional(),
232
- repos: z4.array(z4.object({ path: z4.string().min(1).max(4096), why: z4.string().trim().min(1).max(200) })).min(1).max(50),
233
- addTo: z4.string().trim().max(80).optional()
234
- });
235
- var CloseBlockSchema = z4.object({ session: z4.string().trim().min(1) });
236
- var TaskBlockSchema = z4.object({
237
- to: z4.string().trim().min(1).max(80),
238
- title: z4.string().trim().min(1).max(200),
239
- text: z4.string().max(2e4).optional()
240
- });
241
- var BLOCK = /```ledge-(session|relay|project|close|task|action)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
242
- function extractProposal(answer) {
243
- const found = [];
244
- const text2 = answer.replace(BLOCK, (_m, kind, body) => {
245
- found.push({ kind, raw: body.trim() });
246
- return "";
247
- });
248
- const last = found.at(-1);
249
- return last ? { text: text2.replace(/\n{3,}/g, "\n\n").trim(), kind: last.kind, raw: last.raw, blocks: found } : { text: answer, kind: null, raw: null, blocks: [] };
250
- }
251
- function parseRelayBlock(raw) {
252
- try {
253
- const r = RelayBlockSchema.safeParse(JSON.parse(raw));
254
- return r.success ? r.data : null;
255
- } catch {
256
- return null;
257
- }
258
- }
259
- function parseCloseBlock(raw) {
260
- try {
261
- const r = CloseBlockSchema.safeParse(JSON.parse(raw));
262
- return r.success ? r.data : null;
263
- } catch {
264
- return null;
265
- }
266
- }
267
- function parseTaskBlock(raw) {
268
- try {
269
- const r = TaskBlockSchema.safeParse(JSON.parse(raw));
270
- return r.success ? r.data : null;
271
- } catch {
272
- return null;
273
- }
274
- }
275
- function parseProjectBlock(raw) {
276
- try {
277
- const r = ProjectBlockSchema.safeParse(JSON.parse(raw));
278
- return r.success ? r.data : null;
279
- } catch {
280
- return null;
281
- }
282
- }
283
-
284
195
  // ../protocol/src/models.ts
285
- import { z as z5 } from "zod";
286
- var ModelIdSchema = z5.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
287
- var EffortSchema = z5.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
288
- var ModelChoiceSchema = z5.object({
196
+ import { z as z4 } from "zod";
197
+ var ModelIdSchema = z4.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
198
+ var EffortSchema = z4.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
199
+ var ModelChoiceSchema = z4.object({
289
200
  id: ModelIdSchema,
290
- label: z5.string(),
291
- description: z5.string().optional(),
292
- efforts: z5.array(EffortSchema).optional(),
201
+ label: z4.string(),
202
+ description: z4.string().optional(),
203
+ efforts: z4.array(EffortSchema).optional(),
293
204
  defaultEffort: EffortSchema.optional(),
294
- isDefault: z5.boolean().optional()
205
+ isDefault: z4.boolean().optional()
295
206
  });
296
- var ModelsListSchema = z5.object({
297
- type: z5.literal("models.list"),
298
- requestId: z5.string().min(1).max(100),
207
+ var ModelsListSchema = z4.object({
208
+ type: z4.literal("models.list"),
209
+ requestId: z4.string().min(1).max(100),
299
210
  tool: ToolSchema,
300
- machineId: z5.string().optional()
211
+ machineId: z4.string().optional()
301
212
  });
302
- var ModelsListingSchema = z5.object({
303
- type: z5.literal("models.listing"),
304
- requestId: z5.string(),
213
+ var ModelsListingSchema = z4.object({
214
+ type: z4.literal("models.listing"),
215
+ requestId: z4.string(),
305
216
  tool: ToolSchema,
306
- models: z5.array(ModelChoiceSchema),
307
- error: z5.string().optional()
217
+ models: z4.array(ModelChoiceSchema),
218
+ error: z4.string().optional()
219
+ });
220
+ var ModelRefusalSchema = z4.enum(["not_launched", "ended", "no_conversation", "not_running", "effort_in_model", "restart_failed", "refused"]);
221
+ var SessionModelAcceptedSchema = z4.object({ type: z4.literal("session.model.accepted"), id: z4.string(), when: z4.enum(["now", "turn", "none"]) });
222
+
223
+ // ../protocol/src/actions.ts
224
+ import { z as z8 } from "zod";
225
+
226
+ // ../protocol/src/proposal.ts
227
+ import { z as z5 } from "zod";
228
+ var ProposalBlockSchema = z5.object({ cwd: z5.string().trim().min(1), prompt: z5.string().optional() });
229
+ var RelayBlockSchema = z5.object({ session: z5.string().trim().min(1), text: z5.string().trim().min(1) });
230
+ var ProjectBlockSchema = z5.object({
231
+ name: z5.string().trim().min(1).max(80),
232
+ description: z5.string().max(500).optional(),
233
+ repos: z5.array(z5.object({ path: z5.string().min(1).max(4096), why: z5.string().trim().min(1).max(200) })).min(1).max(50),
234
+ addTo: z5.string().trim().max(80).optional()
235
+ });
236
+ var CloseBlockSchema = z5.object({ session: z5.string().trim().min(1) });
237
+ var TaskBlockSchema = z5.object({
238
+ to: z5.string().trim().min(1).max(80),
239
+ title: z5.string().trim().min(1).max(200),
240
+ text: z5.string().max(2e4).optional()
308
241
  });
309
- var ModelRefusalSchema = z5.enum(["not_launched", "ended", "no_conversation", "not_running", "effort_in_model", "restart_failed", "refused"]);
310
- var SessionModelAcceptedSchema = z5.object({ type: z5.literal("session.model.accepted"), id: z5.string(), when: z5.enum(["now", "turn", "none"]) });
311
242
 
312
243
  // ../protocol/src/decisions.ts
313
244
  import { z as z6 } from "zod";
314
245
  var DecisionSchema = z6.enum(["accept", "deny", "always"]);
315
246
  var NOTE_MAX = 4e3;
316
- var NOTE_NOT_TYPED = "note_not_typed";
317
- var DECISION_OFFERS = {
318
- claude: { always: ["permission", "diff", "plan"], note: ["permission", "diff", "plan"] },
319
- codex: { always: ["permission", "diff"], note: ["permission", "diff", "plan"] },
320
- cursor: { always: [], note: ["permission", "plan"] },
321
- /** OMP answers its own native requests (spec 2026-10-20 §3.3): no « always », no note until those are typed. */
322
- omp: { always: [], note: [] }
323
- };
324
- function offers(tool, kind, what) {
325
- return DECISION_OFFERS[tool][what].includes(kind);
326
- }
327
- function cyrb53(str, seed) {
328
- let h1 = 3735928559 ^ seed, h2 = 1103547991 ^ seed;
329
- for (let i = 0; i < str.length; i++) {
330
- const ch = str.charCodeAt(i);
331
- h1 = Math.imul(h1 ^ ch, 2654435761);
332
- h2 = Math.imul(h2 ^ ch, 1597334677);
333
- }
334
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
335
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
336
- return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0");
337
- }
338
- function pendingKey(p) {
339
- const s = JSON.stringify([p.kind, p.text, p.body ?? null, p.tool ?? null, p.options ?? null, p.optionDetails ?? null, p.helperId ?? null]);
340
- return cyrb53(s, 0) + cyrb53(s, 1);
341
- }
247
+
248
+ // ../protocol/src/tasks.ts
249
+ import { z as z7 } from "zod";
250
+ var TASK_TITLE_MAX = 200;
251
+ var TASK_TEXT_MAX = 2e4;
252
+ var TASK_OPS = ["give", "take", "pass", "done", "refuse", "cancel", "drop"];
253
+ var TRANSMISSION_OPS = ["give", "pass", "done", "refuse", "cancel"];
254
+ var TaskLogEntrySchema = z7.object({
255
+ at: z7.string(),
256
+ by: z7.string(),
257
+ what: z7.string(),
258
+ op: z7.enum(TASK_OPS).optional(),
259
+ from: z7.string().optional(),
260
+ to: z7.string().optional(),
261
+ fromName: z7.string().optional(),
262
+ toName: z7.string().optional(),
263
+ note: z7.string().optional()
264
+ });
265
+ var TaskSchema = z7.object({
266
+ id: z7.string(),
267
+ title: z7.string(),
268
+ text: z7.string().optional(),
269
+ giver: z7.string(),
270
+ giverName: z7.string(),
271
+ owner: z7.string(),
272
+ ownerName: z7.string(),
273
+ state: z7.enum(["open", "taken", "done", "refused", "dropped"]),
274
+ result: z7.string().optional(),
275
+ /** A task Ledge gave and the user put away from « À toi » once closed. */
276
+ seen: z7.boolean().optional(),
277
+ at: z7.string(),
278
+ updatedAt: z7.string(),
279
+ log: z7.array(TaskLogEntrySchema)
280
+ });
281
+ var TaskCmdSchema = z7.object({
282
+ type: z7.literal("task.cmd"),
283
+ rid: z7.string().min(1),
284
+ by: z7.string().min(1),
285
+ op: z7.enum(["give", "take", "done", "refuse", "pass", "show", "list", "seen", "cancel"]),
286
+ task: z7.string().min(1).max(40).optional(),
287
+ to: z7.string().min(1).optional(),
288
+ title: z7.string().min(1).max(TASK_TITLE_MAX).optional(),
289
+ text: z7.string().max(TASK_TEXT_MAX).optional(),
290
+ all: z7.boolean().optional(),
291
+ qid: z7.string().min(1).optional(),
292
+ row: z7.number().int().min(0).max(7).optional()
293
+ });
294
+ var TaskUpdatedSchema = z7.object({ type: z7.literal("task.updated"), task: TaskSchema });
295
+ var TaskResultSchema = z7.object({
296
+ type: z7.literal("task.result"),
297
+ rid: z7.string(),
298
+ task: TaskSchema.optional(),
299
+ tasks: z7.array(TaskSchema).optional(),
300
+ reason: z7.string().optional()
301
+ });
342
302
 
343
303
  // ../protocol/src/actions.ts
344
- var ActionRiskSchema = z7.enum(["read", "reversible", "destructive"]);
304
+ var ActionRiskSchema = z8.enum(["read", "reversible", "destructive"]);
345
305
  var ACTION_IDS = [
346
306
  "session.launch",
347
307
  "session.relay",
@@ -358,10 +318,10 @@ var ACTION_IDS = [
358
318
  "task.pass",
359
319
  "task.answer"
360
320
  ];
361
- var ActionIdSchema = z7.enum(ACTION_IDS);
321
+ var ActionIdSchema = z8.enum(ACTION_IDS);
362
322
  var def = (d) => d;
363
- var SessionRef = z7.string().trim().min(1).max(80);
364
- var TaskRef = z7.string().trim().min(1).max(40);
323
+ var SessionRef = z8.string().trim().min(1).max(80);
324
+ var TaskRef = z8.string().trim().min(1).max(40);
365
325
  var LaunchArgs = ProposalBlockSchema.extend({ model: ModelIdSchema.optional(), effort: EffortSchema.optional() });
366
326
  var ACTIONS = [
367
327
  def({
@@ -427,14 +387,14 @@ var ACTIONS = [
427
387
  phone: null,
428
388
  risk: "reversible",
429
389
  max: 8,
430
- args: z7.object({ session: SessionRef, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
390
+ args: z8.object({ session: SessionRef, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
431
391
  summary: "changer le mod\xE8le et l'effort d'une session en cours (null : le d\xE9faut de l'agent) ; elle red\xE9marre sur la m\xEAme conversation, \xE0 son prochain moment libre",
432
392
  when: "quand l'utilisateur demande de passer une session sur un autre mod\xE8le ou un autre effort",
433
393
  example: { session: "Nova", model: "claude-opus-5-5", effort: "high" }
434
394
  }),
435
395
  def({
436
396
  id: "session.interrupt",
437
- args: z7.object({ session: SessionRef }),
397
+ args: z8.object({ session: SessionRef }),
438
398
  card: "generic",
439
399
  phone: null,
440
400
  risk: "reversible",
@@ -445,7 +405,7 @@ var ACTIONS = [
445
405
  }),
446
406
  def({
447
407
  id: "session.archive",
448
- args: z7.object({ session: SessionRef }),
408
+ args: z8.object({ session: SessionRef }),
449
409
  card: "generic",
450
410
  phone: null,
451
411
  risk: "reversible",
@@ -456,7 +416,7 @@ var ACTIONS = [
456
416
  }),
457
417
  def({
458
418
  id: "session.restore",
459
- args: z7.object({ session: SessionRef }),
419
+ args: z8.object({ session: SessionRef }),
460
420
  card: "generic",
461
421
  phone: null,
462
422
  risk: "reversible",
@@ -467,7 +427,7 @@ var ACTIONS = [
467
427
  }),
468
428
  def({
469
429
  id: "session.resume",
470
- args: z7.object({ session: SessionRef }),
430
+ args: z8.object({ session: SessionRef }),
471
431
  card: "generic",
472
432
  phone: "act",
473
433
  risk: "reversible",
@@ -481,7 +441,7 @@ var ACTIONS = [
481
441
  card: "generic",
482
442
  phone: "act",
483
443
  max: 1,
484
- args: z7.object({ session: SessionRef, decision: z7.enum(["accept", "deny"]), note: z7.string().trim().min(1).max(NOTE_MAX).optional() }),
444
+ args: z8.object({ session: SessionRef, decision: z8.enum(["accept", "deny"]), note: z8.string().trim().min(1).max(NOTE_MAX).optional() }),
485
445
  risk: (a) => a.decision === "accept" ? "destructive" : "reversible",
486
446
  summary: "accepter ou refuser la demande en attente d'une session (permission, diff ou plan), note possible avec un refus ; jamais \xAB toujours \xBB",
487
447
  when: "seulement quand l'utilisateur demande explicitement d'accepter ou de refuser la demande d'une session pr\xE9cise, et que sessions.md montre une demande en attente pour elle ; cite la demande telle quelle dans ta r\xE9ponse",
@@ -489,7 +449,7 @@ var ACTIONS = [
489
449
  }),
490
450
  def({
491
451
  id: "task.cancel",
492
- args: z7.object({ task: TaskRef, reason: z7.string().trim().min(1).max(500).optional() }),
452
+ args: z8.object({ task: TaskRef, reason: z8.string().trim().min(1).max(500).optional() }),
493
453
  card: "generic",
494
454
  phone: "act",
495
455
  risk: "destructive",
@@ -500,7 +460,7 @@ var ACTIONS = [
500
460
  }),
501
461
  def({
502
462
  id: "task.pass",
503
- args: z7.object({ task: TaskRef, to: SessionRef, note: z7.string().trim().min(1).max(TASK_TEXT_MAX).optional() }),
463
+ args: z8.object({ task: TaskRef, to: SessionRef, note: z8.string().trim().min(1).max(TASK_TEXT_MAX).optional() }),
504
464
  card: "generic",
505
465
  phone: null,
506
466
  risk: "reversible",
@@ -511,7 +471,7 @@ var ACTIONS = [
511
471
  }),
512
472
  def({
513
473
  id: "task.answer",
514
- args: z7.object({ task: TaskRef, answer: z7.string().trim().min(1).max(TASK_TEXT_MAX) }),
474
+ args: z8.object({ task: TaskRef, answer: z8.string().trim().min(1).max(TASK_TEXT_MAX) }),
515
475
  card: "generic",
516
476
  phone: "act",
517
477
  risk: "destructive",
@@ -522,436 +482,95 @@ var ACTIONS = [
522
482
  })
523
483
  ];
524
484
  var byId = new Map(ACTIONS.map((a) => [a.id, a]));
525
- var actionById = (id) => byId.get(id);
526
- var riskOf = (a, args) => typeof a.risk === "function" ? a.risk(args) : a.risk;
527
- var ActionBlockSchema = z7.object({ action: z7.string().trim().min(1).max(40), args: z7.record(z7.unknown()).default({}) });
528
- var LEGACY_FENCE_ACTION = {
529
- session: "session.launch",
530
- relay: "session.relay",
531
- close: "session.close",
532
- project: "project.propose",
533
- task: "task.give"
534
- };
535
- function parseActionBlock(kind, raw) {
536
- let json;
537
- try {
538
- json = JSON.parse(raw);
539
- } catch {
540
- return { error: "invalid JSON" };
541
- }
542
- let id;
543
- let args;
544
- if (kind === "action") {
545
- const b = ActionBlockSchema.safeParse(json);
546
- if (!b.success) return { error: "not an {action, args} object" };
547
- id = b.data.action;
548
- args = b.data.args;
549
- } else {
550
- id = LEGACY_FENCE_ACTION[kind];
551
- args = json;
552
- }
553
- const action = actionById(id);
554
- if (!action) return { error: `unknown action ${id}` };
555
- const parsed = action.args.safeParse(args);
556
- if (!parsed.success) return { error: `invalid args for ${id}: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join("; ")}` };
557
- return { action, args: parsed.data };
558
- }
559
- var NEVER_PROPOSED = [
560
- "tuer (kill) ou supprimer une session, la retirer de la liste",
561
- "mettre \xE0 jour le daemon",
562
- "changer les machines, les r\xE9glages, l'acc\xE8s mobile",
563
- "modifier les projets autrement que par la carte de projet",
564
- "cr\xE9er un dossier",
565
- "r\xE9pondre \xAB toujours \xBB \xE0 une demande, ou r\xE9pondre aux questions \xE0 choix d'OMP"
566
- ];
567
-
568
- // ../protocol/src/openrig.ts
569
- import { z as z8 } from "zod";
570
- var RigNameSchema = z8.string().min(1).max(100).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "not a rig name");
571
- var RigSeatNameSchema = z8.string().min(3).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*@[A-Za-z0-9][A-Za-z0-9._-]*$/, "not a seat");
572
- var SeatPermissionsSchema = z8.enum(["inherit", "floor", "auto", "full_bypass"]);
573
- var KERNEL_RIG = "kernel";
574
- var RigSeatSchema = z8.object({
575
- logicalId: z8.string(),
576
- session: z8.string(),
577
- runtime: z8.string(),
578
- model: z8.string().nullable(),
579
- sessionStatus: z8.string(),
580
- lifecycleState: z8.string(),
581
- activity: z8.string(),
582
- stopped: z8.boolean(),
583
- work: z8.number().int().min(0)
584
- });
585
- var RigSchema = z8.object({
586
- id: z8.string(),
587
- name: z8.string(),
588
- status: z8.string(),
589
- lifecycleState: z8.string(),
590
- seats: z8.number().int().min(0),
591
- running: z8.number().int().min(0),
592
- attention: z8.number().int().min(0),
593
- uptime: z8.string().nullable(),
594
- snapshot: z8.string().nullable(),
595
- nodes: z8.array(RigSeatSchema)
596
- });
597
- var RigDaemonStateSchema = z8.enum(["running", "stopped", "stale", "unknown"]);
598
- var RigKernelStateSchema = z8.enum(["ready", "starting", "stopped", "skipped", "absent", "unknown"]);
599
- var OpenRigStateSchema = z8.object({
600
- installed: z8.boolean(),
601
- version: z8.string().optional(),
602
- daemon: z8.object({ state: RigDaemonStateSchema, pid: z8.number().int().optional(), port: z8.number().int().optional(), message: z8.string().optional() }),
603
- kernel: z8.object({ state: RigKernelStateSchema }),
604
- rigs: z8.array(RigSchema),
605
- /** When it was read (ISO). */
606
- at: z8.string()
607
- });
608
- var OpenRigActionSchema = z8.discriminatedUnion("kind", [
609
- /** `restore` `last`: `rig start --last` (the rigs that were running come back); `none`: `rig daemon start`. */
610
- z8.object({ kind: z8.literal("daemon.start"), restore: z8.enum(["last", "none"]).optional(), noKernel: z8.boolean().optional() }),
611
- z8.object({ kind: z8.literal("daemon.stop") }),
612
- z8.object({ kind: z8.literal("daemon.restart"), restore: z8.enum(["last", "none"]).optional() }),
613
- z8.object({ kind: z8.literal("kernel.start") }),
614
- z8.object({ kind: z8.literal("kernel.stop") }),
615
- z8.object({ kind: z8.literal("kernel.restart") }),
616
- z8.object({ kind: z8.literal("rig.start"), rig: RigNameSchema }),
617
- z8.object({ kind: z8.literal("rig.stop"), rig: RigNameSchema, snapshot: z8.boolean().optional(), force: z8.boolean().optional(), delete: z8.boolean().optional() }),
618
- z8.object({ kind: z8.literal("rig.restart"), rig: RigNameSchema }),
619
- /** A seat's settings (docs/mockups/ledge-openrig-groups.html, view 2). `runtime`: moves it now to the other harness (a successor
620
- * takes its context, `rig seat set-runtime`), `model` and `effort` going with it; without it, `model`, `effort` and `permissions`
621
- * apply at its next resume. `dryRun`: the move's plan only. */
622
- z8.object({
623
- kind: z8.literal("seat.set"),
624
- seat: RigSeatNameSchema,
625
- runtime: z8.enum(["claude-code", "codex"]).optional(),
626
- model: ModelIdSchema.optional(),
627
- effort: EffortSchema.optional(),
628
- permissions: SeatPermissionsSchema.optional(),
629
- dryRun: z8.boolean().optional()
630
- })
631
- ]);
632
- var OpenRigStatusSchema = z8.object({ type: z8.literal("openrig.status"), requestId: z8.string().min(1).max(100), machineId: z8.string().optional() });
633
- var OpenRigActSchema = z8.object({ type: z8.literal("openrig.act"), requestId: z8.string().min(1).max(100), action: OpenRigActionSchema, machineId: z8.string().optional() });
634
- var OpenRigUnreachableSchema = z8.enum(["offline", "outdated"]);
635
- var OpenRigStateMessageSchema = z8.object({
636
- type: z8.literal("openrig.state"),
637
- requestId: z8.string().optional(),
638
- machineId: z8.string().optional(),
639
- state: OpenRigStateSchema.optional(),
640
- error: OpenRigUnreachableSchema.optional()
641
- });
642
- var OpenRigDoneSchema = z8.object({
643
- type: z8.literal("openrig.done"),
644
- requestId: z8.string(),
645
- machineId: z8.string().optional(),
646
- ok: z8.boolean(),
647
- commands: z8.array(z8.string()),
648
- output: z8.string().optional(),
649
- error: z8.string().optional()
650
- });
651
- function rigCommands(a) {
652
- const start = (restore, noKernel) => restore === "last" ? ["start", "--last", "--json"] : ["daemon", "start", ...noKernel ? ["--no-kernel"] : []];
653
- const up = (rig) => ["up", rig, "--existing", "--yes", "--json"];
654
- const down = (rig, o = {}) => ["down", rig, ...o.snapshot ? ["--snapshot"] : [], ...o.force ? ["--force"] : [], ...o.delete ? ["--delete"] : [], "--json"];
655
- switch (a.kind) {
656
- case "daemon.start":
657
- return [start(a.restore, a.noKernel)];
658
- case "daemon.stop":
659
- return [["daemon", "stop"]];
660
- case "daemon.restart":
661
- return [["daemon", "stop"], start(a.restore)];
662
- case "kernel.start":
663
- return [up(KERNEL_RIG)];
664
- case "kernel.stop":
665
- return [down(KERNEL_RIG)];
666
- case "kernel.restart":
667
- return [down(KERNEL_RIG, { snapshot: true }), up(KERNEL_RIG)];
668
- case "rig.start":
669
- return [up(a.rig)];
670
- case "rig.stop":
671
- return [down(a.rig, a)];
672
- case "rig.restart":
673
- return [down(a.rig, { snapshot: true }), up(a.rig)];
674
- case "seat.set": {
675
- const why = ["--reason", "Ledge"];
676
- if (a.runtime) return [[
677
- "seat",
678
- "set-runtime",
679
- a.seat,
680
- "--runtime",
681
- a.runtime,
682
- ...a.model ? ["--model", a.model] : [],
683
- ...a.effort ? ["--effort", a.effort] : [],
684
- ...a.permissions && a.permissions !== "inherit" ? ["--permission-mode", a.permissions] : [],
685
- ...a.dryRun ? ["--dry-run"] : [],
686
- ...why
687
- ]];
688
- return [
689
- ...a.model ? [["seat", "set-model", a.seat, "--model", a.model, ...why, "--json"]] : [],
690
- ...a.effort ? [["seat", "set-effort", a.seat, "--effort", a.effort, ...why, "--json"]] : [],
691
- ...a.permissions ? [["seat", "set-permissions", a.seat, "--mode", a.permissions, ...why, "--json"]] : []
692
- ];
693
- }
694
- }
695
- }
696
- var showRigCommand = (args) => ["rig", ...args.filter((x) => x !== "--json")].join(" ");
697
- var RigSpecMemberSchema = z8.object({
698
- id: z8.string(),
699
- runtime: z8.string(),
700
- model: z8.string().optional(),
701
- agent: z8.string(),
702
- /** Its `permission_policy`, as written. */
703
- policy: z8.string().optional()
704
- });
705
- var RigSpecPodSchema = z8.object({ id: z8.string(), label: z8.string().optional(), members: z8.array(RigSpecMemberSchema) });
706
- var RigSpecSchema = z8.object({
707
- name: z8.string(),
708
- source: z8.string(),
709
- summary: z8.string().optional(),
710
- path: z8.string(),
711
- /** The rig's `permission_policy`, as written (`builtin:auto`, a file…). */
712
- policy: z8.string().optional(),
713
- pods: z8.array(RigSpecPodSchema),
714
- edges: z8.number().int().min(0)
715
- });
716
- var OpenRigSpecsSchema = z8.object({ type: z8.literal("openrig.specs"), requestId: z8.string().min(1).max(100), machineId: z8.string().optional() });
717
- var OpenRigSpecListSchema = z8.object({
718
- type: z8.literal("openrig.specList"),
719
- requestId: z8.string(),
720
- machineId: z8.string().optional(),
721
- specs: z8.array(RigSpecSchema),
722
- error: z8.string().optional()
723
- });
724
- var RigRoleSchema = z8.string().min(3).max(201).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*\.[A-Za-z0-9][A-Za-z0-9_-]*$/, "not a role");
725
- var RuntimeSchema = z8.enum(["claude-code", "codex"]);
726
- var RigPolicySchema = z8.enum(["auto", "standard", "open", "yolo", "locked"]);
727
- var RIG_POLICIES = RigPolicySchema.options;
728
- var RigLaunchSchema = z8.object({
729
- spec: RigNameSchema,
730
- cwd: z8.string().min(1).max(4096),
731
- name: RigNameSchema,
732
- seats: z8.record(RigRoleSchema, z8.number().int().min(0).max(9)).optional(),
733
- runtimes: z8.record(RigRoleSchema, RuntimeSchema).optional(),
734
- models: z8.record(RigRoleSchema, z8.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/)).optional(),
735
- delegation: z8.enum(["pairs", "all"]).optional(),
736
- save: z8.boolean().optional(),
737
- plan: z8.boolean().optional(),
738
- /** The rig's permission policy, in place of the spec's; `policies`: a role's, over the rig's. */
739
- policy: RigPolicySchema.optional(),
740
- policies: z8.record(RigRoleSchema, RigPolicySchema).optional(),
741
- /** Accept the harnesses' first-launch warnings (`rig up --non-interruptive`), so a bypassing seat does not stop on Claude's dialog. */
742
- nonInterruptive: z8.boolean().optional()
743
- });
744
- var OpenRigLaunchSchema = z8.object({ type: z8.literal("openrig.launch"), requestId: z8.string().min(1).max(100), launch: RigLaunchSchema, machineId: z8.string().optional() });
745
- var OpenRigLaunchedSchema = z8.object({
746
- type: z8.literal("openrig.launched"),
747
- requestId: z8.string(),
748
- machineId: z8.string().optional(),
749
- ok: z8.boolean(),
750
- plan: z8.boolean(),
751
- commands: z8.array(z8.string()),
752
- yaml: z8.string().optional(),
753
- seats: z8.number().int().optional(),
754
- output: z8.string().optional(),
755
- error: z8.string().optional(),
756
- /** What `rig up --plan` concluded: its status (`planned`…), its errors and its warnings (one per seat without a permission policy…). */
757
- check: z8.object({ status: z8.string(), errors: z8.array(z8.string()), warnings: z8.array(z8.string()) }).optional()
758
- });
759
- var roleOf = (logicalId) => logicalId.replace(/-\d+$/, "");
760
- var copyOf = (logicalId) => {
761
- const m = /-(\d+)$/.exec(logicalId);
762
- return m ? Number(m[1]) : 1;
763
- };
764
- function seatsByRole(nodes) {
765
- const out = /* @__PURE__ */ new Map();
766
- for (const n of nodes) out.set(roleOf(n.logicalId), [...out.get(roleOf(n.logicalId)) ?? [], n.logicalId]);
767
- for (const [role, ids] of out) out.set(role, ids.sort((a, b) => copyOf(a) - copyOf(b)));
768
- return out;
769
- }
770
- function seatPlan(nodes, seats) {
771
- const by = seatsByRole(nodes);
772
- const add = [], remove = [];
773
- for (const [role, want] of Object.entries(seats)) {
774
- const have = by.get(role) ?? [];
775
- if (have.length === 0) continue;
776
- if (want < have.length) {
777
- remove.push(...have.slice(want).reverse());
778
- continue;
779
- }
780
- const [pod, member] = role.split(".");
781
- let next = Math.max(...have.map(copyOf));
782
- for (let i = have.length; i < want; i++) add.push({ role, pod, id: `${member}-${++next}`, from: have[0] });
783
- }
784
- return { add, remove };
785
- }
786
- var OpenRigSeatsSchema = z8.object({
787
- type: z8.literal("openrig.seats"),
788
- requestId: z8.string().min(1).max(100),
789
- machineId: z8.string().optional(),
790
- rig: RigNameSchema,
791
- seats: z8.record(RigRoleSchema, z8.number().int().min(0).max(9)),
792
- /** How the added seats are linked, where OpenRig can add links to a running rig (0.7+): like a launch's. */
793
- delegation: z8.enum(["pairs", "all"]).optional()
794
- });
795
- var OpenRigReseatedSchema = z8.object({
796
- type: z8.literal("openrig.reseated"),
797
- requestId: z8.string(),
798
- machineId: z8.string().optional(),
799
- ok: z8.boolean(),
800
- commands: z8.array(z8.string()),
801
- added: z8.array(z8.string()),
802
- removed: z8.array(z8.string()),
803
- output: z8.string().optional(),
804
- error: z8.string().optional(),
805
- /** The links ensured between the seats; `linksUnsupported`: this OpenRig cannot add links to a running rig (before 0.7). */
806
- links: z8.number().int().optional(),
807
- linksUnsupported: z8.boolean().optional()
808
- });
809
-
810
- // ../protocol/src/messages.ts
811
- import { z as z21 } from "zod";
485
+ var ActionBlockSchema = z8.object({ action: z8.string().trim().min(1).max(40), args: z8.record(z8.unknown()).default({}) });
812
486
 
813
487
  // ../protocol/src/chat.ts
814
- import { z as z10 } from "zod";
815
-
816
- // ../protocol/src/attachments.ts
817
- import { z as z9 } from "zod";
818
- var ATTACHMENT_ID = /^[0-9a-f]{64}$/;
819
- var AttachmentIdSchema = z9.string().regex(ATTACHMENT_ID);
820
- var IMAGE_MIMES = ["image/webp", "image/jpeg", "image/png", "image/gif"];
821
- var ImageMimeSchema = z9.enum(IMAGE_MIMES);
822
- var ATTACHMENT_MAX = 4e5;
823
- var ATTACHMENT_LOCAL_MAX = 2e6;
824
- var PREVIEW_MAX = 4e4;
825
- var ATTACHMENTS_MAX = 5;
826
- var b64Max = (bytes) => Math.ceil(bytes / 3) * 4;
827
- var AttachmentRefSchema = z9.object({
828
- id: AttachmentIdSchema,
829
- mime: ImageMimeSchema,
830
- width: z9.number().int().positive(),
831
- height: z9.number().int().positive(),
832
- bytes: z9.number().int().positive(),
833
- preview: z9.boolean()
834
- });
835
- var ImageUploadSchema = z9.object({
836
- mime: ImageMimeSchema,
837
- data: z9.string().min(1).max(b64Max(ATTACHMENT_LOCAL_MAX)),
838
- preview: z9.object({ mime: ImageMimeSchema, data: z9.string().min(1).max(b64Max(PREVIEW_MAX)) }).optional()
839
- });
840
- var AttachmentErrorReasonSchema = z9.enum(["too_large", "not_image", "unreadable", "not_found", "offline"]);
841
- var requestId = z9.string().min(1).max(100);
842
- var AttachmentPutSchema = z9.object({ type: z9.literal("attachment.put"), requestId, id: z9.string().optional(), image: ImageUploadSchema });
843
- var AttachmentGetSchema = z9.object({ type: z9.literal("attachment.get"), requestId, attachment: AttachmentIdSchema, preview: z9.boolean().optional(), id: z9.string().optional(), machine: z9.string().optional() });
844
- var AttachmentStoredSchema = z9.object({ type: z9.literal("attachment.stored"), requestId, ref: AttachmentRefSchema });
845
- var AttachmentDataSchema = z9.object({ type: z9.literal("attachment.data"), requestId, attachment: AttachmentIdSchema, mime: ImageMimeSchema, data: z9.string() });
846
- var AttachmentErrorSchema = z9.object({ type: z9.literal("attachment.error"), requestId, reason: AttachmentErrorReasonSchema });
847
-
848
- // ../protocol/src/chat.ts
849
- var ChatStepSchema = z10.object({
850
- kind: z10.enum(["sessions", "pane", "reply", "git", "repo"]),
851
- id: z10.string().optional(),
852
- name: z10.string().optional(),
853
- path: z10.string().optional()
854
- });
855
- var ChatProposalSchema = z10.object({
856
- cwd: z10.string().min(1),
857
- project: z10.string(),
858
- prompt: z10.string().optional(),
488
+ var ChatStepSchema = z9.object({
489
+ kind: z9.enum(["sessions", "pane", "reply", "git", "repo"]),
490
+ id: z9.string().optional(),
491
+ name: z9.string().optional(),
492
+ path: z9.string().optional()
493
+ });
494
+ var ChatProposalSchema = z9.object({
495
+ cwd: z9.string().min(1),
496
+ project: z9.string(),
497
+ prompt: z9.string().optional(),
859
498
  tool: ToolSchema,
860
499
  /** What Ledge proposed on the card's picker (spec 2026-10-26 action registry §4), the agent's own default when left out. */
861
500
  model: ModelIdSchema.optional(),
862
501
  effort: EffortSchema.optional(),
863
- launchedId: z10.string().optional(),
864
- error: z10.string().optional()
502
+ launchedId: z9.string().optional(),
503
+ error: z9.string().optional()
865
504
  });
866
505
  var MAX_PROPOSALS = 8;
867
- var ChatProposalsSchema = z10.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
868
- var ChatRelaySchema = z10.object({
869
- id: z10.string().min(1),
870
- name: z10.string(),
506
+ var ChatProposalsSchema = z9.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
507
+ var ChatRelaySchema = z9.object({
508
+ id: z9.string().min(1),
509
+ name: z9.string(),
871
510
  tool: ToolSchema,
872
- text: z10.string().min(1),
873
- status: z10.enum(["queued", "sent"]).optional(),
874
- sentAt: z10.string().datetime().optional(),
875
- error: z10.string().optional(),
511
+ text: z9.string().min(1),
512
+ status: z9.enum(["queued", "sent"]).optional(),
513
+ sentAt: z9.string().datetime().optional(),
514
+ error: z9.string().optional(),
876
515
  /** Why it failed, as a code a front words itself (a RefusalCode, `ended`, or `failed` for the machinery); goes with `error`. */
877
- code: z10.string().max(40).optional()
516
+ code: z9.string().max(40).optional()
878
517
  });
879
- var ChatCloseSchema = z10.object({
880
- id: z10.string().min(1),
881
- name: z10.string(),
518
+ var ChatCloseSchema = z9.object({
519
+ id: z9.string().min(1),
520
+ name: z9.string(),
882
521
  tool: ToolSchema,
883
- closedAt: z10.string().datetime().optional(),
884
- how: z10.enum(["exit", "killed"]).optional(),
885
- error: z10.string().optional()
886
- });
887
- var ChatClosesSchema = z10.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
888
- var ChatProjectProposalSchema = z10.object({
889
- name: z10.string().min(1).max(80),
890
- description: z10.string().max(500).optional(),
891
- repos: z10.array(z10.object({ path: z10.string().min(1).max(4096), why: z10.string().min(1).max(200) })).min(1).max(50),
892
- addTo: z10.string().min(1).max(80).optional()
893
- });
894
- var ChatTaskProposalSchema = z10.object({
895
- to: z10.string().min(1),
896
- toName: z10.string().optional(),
897
- title: z10.string().min(1).max(200),
898
- text: z10.string().max(2e4).optional(),
899
- taskId: z10.string().optional(),
900
- ownerName: z10.string().optional(),
901
- state: z10.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
902
- givenAt: z10.string().datetime().optional(),
903
- error: z10.string().optional()
904
- });
905
- var ChatTasksSchema = z10.array(ChatTaskProposalSchema).min(1).max(MAX_PROPOSALS);
906
- var ActionVarsSchema = z10.record(z10.string().max(40), z10.string().max(300));
522
+ closedAt: z9.string().datetime().optional(),
523
+ how: z9.enum(["exit", "killed"]).optional(),
524
+ error: z9.string().optional()
525
+ });
526
+ var ChatClosesSchema = z9.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
527
+ var ChatProjectProposalSchema = z9.object({
528
+ name: z9.string().min(1).max(80),
529
+ description: z9.string().max(500).optional(),
530
+ repos: z9.array(z9.object({ path: z9.string().min(1).max(4096), why: z9.string().min(1).max(200) })).min(1).max(50),
531
+ addTo: z9.string().min(1).max(80).optional()
532
+ });
533
+ var ChatTaskProposalSchema = z9.object({
534
+ to: z9.string().min(1),
535
+ toName: z9.string().optional(),
536
+ title: z9.string().min(1).max(200),
537
+ text: z9.string().max(2e4).optional(),
538
+ taskId: z9.string().optional(),
539
+ ownerName: z9.string().optional(),
540
+ state: z9.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
541
+ givenAt: z9.string().datetime().optional(),
542
+ error: z9.string().optional()
543
+ });
544
+ var ChatTasksSchema = z9.array(ChatTaskProposalSchema).min(1).max(MAX_PROPOSALS);
545
+ var ActionVarsSchema = z9.record(z9.string().max(40), z9.string().max(300));
907
546
  var DETAIL_MAX = 4e3;
908
- var ACTION_ERROR_CODES = [
909
- "unknown",
910
- "ambiguous",
911
- "read_only",
912
- "exited",
913
- "running",
914
- "archived",
915
- "not_archived",
916
- "no_terminal",
917
- "same_model",
918
- "no_request",
919
- "in_session",
920
- "no_note",
921
- "closed",
922
- "holder",
923
- "changed",
924
- "cut",
925
- "not_decision",
926
- "failed"
927
- ];
928
- var ChatActionStateSchema = z10.enum(["running", "done", "denied", "failed", "refused"]);
929
- var ChatActionSchema = z10.object({
930
- action: z10.string().min(1).max(40),
931
- args: z10.record(z10.unknown()),
932
- summary: z10.string().min(1).max(300),
547
+ var ChatActionStateSchema = z9.enum(["running", "done", "denied", "failed", "refused"]);
548
+ var ChatActionSchema = z9.object({
549
+ action: z9.string().min(1).max(40),
550
+ args: z9.record(z9.unknown()),
551
+ summary: z9.string().min(1).max(300),
933
552
  vars: ActionVarsSchema.optional(),
934
- detail: z10.string().max(DETAIL_MAX).optional(),
935
- detailCut: z10.literal(true).optional(),
553
+ detail: z9.string().max(DETAIL_MAX).optional(),
554
+ detailCut: z9.literal(true).optional(),
936
555
  risk: ActionRiskSchema,
937
- session: z10.string().optional(),
556
+ session: z9.string().optional(),
938
557
  state: ChatActionStateSchema.optional(),
939
- doneAt: z10.string().datetime().optional(),
940
- error: z10.string().optional(),
941
- code: z10.string().max(40).optional()
942
- });
943
- var ChatActionsSchema = z10.array(ChatActionSchema).min(1).max(MAX_PROPOSALS);
944
- var ChatTurnSchema = z10.object({
945
- machineId: z10.string().optional(),
946
- machineName: z10.string().optional(),
947
- choices: z10.array(z10.object({ id: z10.string(), label: z10.string() })).optional(),
948
- qid: z10.string().min(1),
949
- question: z10.string(),
950
- askedAt: z10.string().datetime(),
951
- steps: z10.array(ChatStepSchema),
952
- answer: z10.string().optional(),
953
- answeredAt: z10.string().datetime().optional(),
954
- error: z10.string().optional(),
558
+ doneAt: z9.string().datetime().optional(),
559
+ error: z9.string().optional(),
560
+ code: z9.string().max(40).optional()
561
+ });
562
+ var ChatActionsSchema = z9.array(ChatActionSchema).min(1).max(MAX_PROPOSALS);
563
+ var ChatTurnSchema = z9.object({
564
+ machineId: z9.string().optional(),
565
+ machineName: z9.string().optional(),
566
+ choices: z9.array(z9.object({ id: z9.string(), label: z9.string() })).optional(),
567
+ qid: z9.string().min(1),
568
+ question: z9.string(),
569
+ askedAt: z9.string().datetime(),
570
+ steps: z9.array(ChatStepSchema),
571
+ answer: z9.string().optional(),
572
+ answeredAt: z9.string().datetime().optional(),
573
+ error: z9.string().optional(),
955
574
  proposal: ChatProposalSchema.optional(),
956
575
  proposals: ChatProposalsSchema.optional(),
957
576
  relay: ChatRelaySchema.optional(),
@@ -959,50 +578,31 @@ var ChatTurnSchema = z10.object({
959
578
  projectProposal: ChatProjectProposalSchema.optional(),
960
579
  tasks: ChatTasksSchema.optional(),
961
580
  actions: ChatActionsSchema.optional(),
962
- ignoredAt: z10.string().datetime().optional(),
963
- attachments: z10.array(AttachmentRefSchema).optional()
581
+ ignoredAt: z9.string().datetime().optional(),
582
+ attachments: z9.array(AttachmentRefSchema).optional()
964
583
  });
965
- var proposalRows = (t) => t?.proposals ?? (t?.proposal ? [t.proposal] : []);
966
-
967
- // ../protocol/src/repos.ts
968
- import { z as z11 } from "zod";
969
- var ReposInspectSchema = z11.object({
970
- type: z11.literal("repos.inspect"),
971
- requestId: z11.string().min(1).max(100),
972
- paths: z11.array(z11.string().min(1)).min(1).max(50),
973
- machineId: z11.string().optional()
974
- });
975
- var RepoInfoSchema = z11.object({ path: z11.string(), exists: z11.boolean(), branch: z11.string().nullable() });
976
- var ReposInfoSchema = z11.object({ type: z11.literal("repos.info"), requestId: z11.string(), repos: z11.array(RepoInfoSchema) });
977
-
978
- // ../protocol/src/scan.ts
979
- import { z as z12 } from "zod";
980
- var ReposScanSchema = z12.object({ type: z12.literal("repos.scan"), requestId: z12.string().min(1).max(100), machineId: z12.string().optional() });
981
- var FoundRepoSchema = z12.object({ path: z12.string(), branch: z12.string().nullable(), remote: z12.string().optional(), manifest: z12.string().max(200).optional() });
982
- var ReposFoundSchema = z12.object({ type: z12.literal("repos.found"), requestId: z12.string(), repos: z12.array(FoundRepoSchema).max(500), truncated: z12.boolean(), error: z12.string().optional() });
983
584
 
984
585
  // ../protocol/src/documents.ts
985
- import { z as z13 } from "zod";
986
- var DOCUMENT_MAX_BYTES = 1048576;
987
- var path = z13.string().min(1).max(4096);
988
- var requestId2 = z13.string().min(1);
989
- var DocumentReadSchema = z13.object({
990
- type: z13.literal("document.read"),
586
+ import { z as z10 } from "zod";
587
+ var path = z10.string().min(1).max(4096);
588
+ var requestId2 = z10.string().min(1);
589
+ var DocumentReadSchema = z10.object({
590
+ type: z10.literal("document.read"),
991
591
  requestId: requestId2,
992
592
  path,
993
- machineId: z13.string().optional(),
994
- sessionId: z13.string().min(1).optional(),
593
+ machineId: z10.string().optional(),
594
+ sessionId: z10.string().min(1).optional(),
995
595
  basePath: path.optional()
996
596
  });
997
- var DocumentContentSchema = z13.object({
998
- type: z13.literal("document.content"),
597
+ var DocumentContentSchema = z10.object({
598
+ type: z10.literal("document.content"),
999
599
  requestId: requestId2,
1000
600
  path,
1001
- text: z13.string(),
1002
- bytes: z13.number().int().nonnegative(),
1003
- modifiedAt: z13.string().datetime()
601
+ text: z10.string(),
602
+ bytes: z10.number().int().nonnegative(),
603
+ modifiedAt: z10.string().datetime()
1004
604
  });
1005
- var DocumentErrorCodeSchema = z13.enum([
605
+ var DocumentErrorCodeSchema = z10.enum([
1006
606
  "not_found",
1007
607
  "not_file",
1008
608
  "unsupported_path",
@@ -1014,49 +614,30 @@ var DocumentErrorCodeSchema = z13.enum([
1014
614
  "session_missing",
1015
615
  "read_failed"
1016
616
  ]);
1017
- var DocumentErrorSchema = z13.object({
1018
- type: z13.literal("document.error"),
617
+ var DocumentErrorSchema = z10.object({
618
+ type: z10.literal("document.error"),
1019
619
  requestId: requestId2,
1020
620
  path,
1021
621
  code: DocumentErrorCodeSchema
1022
622
  });
1023
623
 
1024
- // ../protocol/src/routing.ts
1025
- import { z as z14 } from "zod";
1026
- var MachineInventorySchema = z14.object({
1027
- id: z14.string().min(1).max(80),
1028
- label: z14.string().min(1).max(120),
1029
- online: z14.boolean(),
1030
- local: z14.boolean(),
1031
- sessions: z14.array(z14.object({ id: z14.string(), name: z14.string(), project: z14.string(), cwd: z14.string(), topic: z14.string().optional(), state: z14.string(), readOnly: z14.boolean() })).max(500),
1032
- folders: z14.array(z14.object({ project: z14.string(), cwd: z14.string() })).max(100)
1033
- });
1034
- var RoutingContextSchema = z14.object({
1035
- machines: z14.array(MachineInventorySchema).min(1).max(20),
1036
- history: z14.array(z14.object({ question: z14.string().max(2e4), answer: z14.string().max(2e4).optional(), machineId: z14.string().optional() })).max(50)
1037
- });
1038
- var RouteResultSchema = z14.object({
1039
- /** Null means an answer or a clarification, never an implicit local destination. */
1040
- machineId: z14.string().nullable(),
1041
- text: z14.string().min(1).max(2e4),
1042
- choices: z14.array(z14.string()).max(20).optional()
1043
- });
624
+ // ../protocol/src/machines.ts
625
+ import { z as z12 } from "zod";
1044
626
 
1045
627
  // ../protocol/src/ssh.ts
1046
- import { z as z15 } from "zod";
1047
- var SshAliasSchema = z15.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
628
+ import { z as z11 } from "zod";
629
+ var SshAliasSchema = z11.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
1048
630
 
1049
631
  // ../protocol/src/machines.ts
1050
- import { z as z16 } from "zod";
1051
- var MachineIdSchema = z16.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
1052
- var MachineSchema = z16.object({
632
+ var MachineIdSchema = z12.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
633
+ var MachineSchema = z12.object({
1053
634
  id: MachineIdSchema,
1054
- label: z16.string().trim().min(1).max(80),
635
+ label: z12.string().trim().min(1).max(80),
1055
636
  ssh: SshAliasSchema.optional(),
1056
637
  /** Kept in the list but never connected to: no peer, no retry, no session shown until it is enabled again. */
1057
- disabled: z16.boolean().optional()
638
+ disabled: z12.boolean().optional()
1058
639
  });
1059
- var MachinesSchema = z16.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
640
+ var MachinesSchema = z12.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
1060
641
  if (list[0]?.id !== "local" || list[0].ssh !== void 0) ctx.addIssue({ code: "custom", message: "the first machine is local, without ssh", path: [0] });
1061
642
  const ids = /* @__PURE__ */ new Set(), aliases = /* @__PURE__ */ new Set();
1062
643
  list.forEach((m, i) => {
@@ -1072,53 +653,52 @@ var MachinesSchema = z16.array(MachineSchema).min(1).max(20).superRefine((list,
1072
653
  });
1073
654
 
1074
655
  // ../protocol/src/island.ts
1075
- import { z as z17 } from "zod";
1076
- var IslandScaleSchema = z17.union([z17.literal("auto"), z17.literal(1), z17.literal(1.25), z17.literal(1.5), z17.literal(2)]);
1077
- var IslandPrefsSchema = z17.object({
656
+ import { z as z13 } from "zod";
657
+ var IslandScaleSchema = z13.union([z13.literal("auto"), z13.literal(1), z13.literal(1.25), z13.literal(1.5), z13.literal(2)]);
658
+ var IslandPrefsSchema = z13.object({
1078
659
  /** Shown at the top of the screen; hidden, Desktop still notifies. */
1079
- visible: z17.boolean(),
660
+ visible: z13.boolean(),
1080
661
  /** `primary` (whichever it is after a reconfiguration), or a monitor's id as the shell lists it (`list_monitors`: the OS
1081
662
  * name, `#index` without one). `second`, written by older fronts, is the first monitor that is not the primary. */
1082
- screen: z17.string().trim().min(1).max(200),
1083
- position: z17.enum(["center", "left", "right"]),
663
+ screen: z13.string().trim().min(1).max(200),
664
+ position: z13.enum(["center", "left", "right"]),
1084
665
  /** A strip under a maximised window. */
1085
- discreet: z17.boolean(),
666
+ discreet: z13.boolean(),
1086
667
  islandScale: IslandScaleSchema,
1087
668
  panelScale: IslandScaleSchema,
1088
669
  /** What the island shows besides asks and questions, which it always shows. */
1089
- shows: z17.object({
670
+ shows: z13.object({
1090
671
  /** Allow, deny or answer in a line from the island; off, its cards only say « Open in Ledge ». */
1091
- quick: z17.boolean(),
672
+ quick: z13.boolean(),
1092
673
  /** The 6 s bubble when an agent finishes. */
1093
- finished: z17.boolean(),
674
+ finished: z13.boolean(),
1094
675
  /** An agent's sentence in a bubble. */
1095
- say: z17.boolean(),
676
+ say: z13.boolean(),
1096
677
  /** The sub-agents next to their main agent. */
1097
- helpers: z17.boolean()
678
+ helpers: z13.boolean()
1098
679
  }),
1099
680
  /** The global shortcut that opens the island, as shown (`Ctrl Alt L`). */
1100
- shortcut: z17.string().trim().min(1).max(40)
681
+ shortcut: z13.string().trim().min(1).max(40)
1101
682
  });
1102
683
 
1103
684
  // ../protocol/src/projects.ts
1104
- import { z as z18 } from "zod";
1105
- var PROJECT_COLORS = { blue: "#3b82f6", green: "#10b981", amber: "#f59e0b", violet: "#8b5cf6", teal: "#14b8a6", pink: "#ec4899", red: "#ef4444", slate: "#64748b" };
1106
- var ProjectColorSchema = z18.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
1107
- var ProjectIdSchema = z18.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
685
+ import { z as z14 } from "zod";
686
+ var ProjectColorSchema = z14.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
687
+ var ProjectIdSchema = z14.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
1108
688
  function normalizePath(raw) {
1109
689
  const p = raw.trim().replace(/\/+/g, "/");
1110
690
  return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
1111
691
  }
1112
- var PathSchema = z18.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
1113
- var RepoSchema = z18.object({ machine: MachineIdSchema, path: PathSchema });
1114
- var ProjectSchema = z18.object({
692
+ var PathSchema = z14.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
693
+ var RepoSchema = z14.object({ machine: MachineIdSchema, path: PathSchema });
694
+ var ProjectSchema = z14.object({
1115
695
  id: ProjectIdSchema,
1116
- name: z18.string().trim().min(1).max(80),
696
+ name: z14.string().trim().min(1).max(80),
1117
697
  color: ProjectColorSchema,
1118
- description: z18.string().max(500).default(""),
1119
- repos: z18.array(RepoSchema).max(50)
698
+ description: z14.string().max(500).default(""),
699
+ repos: z14.array(RepoSchema).max(50)
1120
700
  });
1121
- var RuleSchema = z18.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
701
+ var RuleSchema = z14.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
1122
702
  function projectsProblems(p) {
1123
703
  const out = [];
1124
704
  const ids = /* @__PURE__ */ new Set(), names = /* @__PURE__ */ new Set(), repos = /* @__PURE__ */ new Set(), rules = /* @__PURE__ */ new Set();
@@ -1144,22 +724,218 @@ ${rule.path}`;
1144
724
  });
1145
725
  return out;
1146
726
  }
1147
- var ProjectsSchema = z18.object({ projects: z18.array(ProjectSchema).max(100), rules: z18.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
727
+ var ProjectsSchema = z14.object({ projects: z14.array(ProjectSchema).max(100), rules: z14.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
1148
728
  for (const problem of projectsProblems(p)) ctx.addIssue({ code: "custom", message: problem.code, path: problem.path });
1149
729
  });
1150
- var DEFAULT_PROJECTS = { projects: [], rules: [] };
1151
- var under = (cwd, path2) => path2 === "/" || cwd === path2 || cwd.startsWith(`${path2}/`);
1152
- function projectOf(session, state) {
1153
- const machine = session.machineId ?? "local";
1154
- const cwd = normalizePath(session.cwd);
1155
- const known = new Set(state.projects.map((p) => p.id));
1156
- const candidates = [
1157
- ...state.projects.flatMap((p) => p.repos.filter((r) => r.machine === machine).map((r) => ({ project: p.id, path: r.path, rule: false }))),
1158
- ...state.rules.filter((r) => r.machine === machine && known.has(r.project)).map((r) => ({ project: r.project, path: r.path, rule: true }))
1159
- ].filter((c) => under(cwd, c.path));
1160
- candidates.sort((x, y) => y.path.length - x.path.length || Number(y.rule) - Number(x.rule));
1161
- return candidates[0]?.project ?? null;
1162
- }
730
+
731
+ // ../protocol/src/messages.ts
732
+ import { z as z21 } from "zod";
733
+
734
+ // ../protocol/src/repos.ts
735
+ import { z as z15 } from "zod";
736
+ var ReposInspectSchema = z15.object({
737
+ type: z15.literal("repos.inspect"),
738
+ requestId: z15.string().min(1).max(100),
739
+ paths: z15.array(z15.string().min(1)).min(1).max(50),
740
+ machineId: z15.string().optional()
741
+ });
742
+ var RepoInfoSchema = z15.object({ path: z15.string(), exists: z15.boolean(), branch: z15.string().nullable() });
743
+ var ReposInfoSchema = z15.object({ type: z15.literal("repos.info"), requestId: z15.string(), repos: z15.array(RepoInfoSchema) });
744
+
745
+ // ../protocol/src/scan.ts
746
+ import { z as z16 } from "zod";
747
+ var ReposScanSchema = z16.object({ type: z16.literal("repos.scan"), requestId: z16.string().min(1).max(100), machineId: z16.string().optional() });
748
+ var FoundRepoSchema = z16.object({ path: z16.string(), branch: z16.string().nullable(), remote: z16.string().optional(), manifest: z16.string().max(200).optional() });
749
+ var ReposFoundSchema = z16.object({ type: z16.literal("repos.found"), requestId: z16.string(), repos: z16.array(FoundRepoSchema).max(500), truncated: z16.boolean(), error: z16.string().optional() });
750
+
751
+ // ../protocol/src/openrig.ts
752
+ import { z as z17 } from "zod";
753
+ var RigNameSchema = z17.string().min(1).max(100).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "not a rig name");
754
+ var RigSeatNameSchema = z17.string().min(3).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*@[A-Za-z0-9][A-Za-z0-9._-]*$/, "not a seat");
755
+ var SeatPermissionsSchema = z17.enum(["inherit", "floor", "auto", "full_bypass"]);
756
+ var RigSeatSchema = z17.object({
757
+ logicalId: z17.string(),
758
+ session: z17.string(),
759
+ runtime: z17.string(),
760
+ model: z17.string().nullable(),
761
+ sessionStatus: z17.string(),
762
+ lifecycleState: z17.string(),
763
+ activity: z17.string(),
764
+ stopped: z17.boolean(),
765
+ work: z17.number().int().min(0)
766
+ });
767
+ var RigSchema = z17.object({
768
+ id: z17.string(),
769
+ name: z17.string(),
770
+ status: z17.string(),
771
+ lifecycleState: z17.string(),
772
+ seats: z17.number().int().min(0),
773
+ running: z17.number().int().min(0),
774
+ attention: z17.number().int().min(0),
775
+ uptime: z17.string().nullable(),
776
+ snapshot: z17.string().nullable(),
777
+ nodes: z17.array(RigSeatSchema)
778
+ });
779
+ var RigDaemonStateSchema = z17.enum(["running", "stopped", "stale", "unknown"]);
780
+ var RigKernelStateSchema = z17.enum(["ready", "starting", "stopped", "skipped", "absent", "unknown"]);
781
+ var OpenRigStateSchema = z17.object({
782
+ installed: z17.boolean(),
783
+ version: z17.string().optional(),
784
+ daemon: z17.object({ state: RigDaemonStateSchema, pid: z17.number().int().optional(), port: z17.number().int().optional(), message: z17.string().optional() }),
785
+ kernel: z17.object({ state: RigKernelStateSchema }),
786
+ rigs: z17.array(RigSchema),
787
+ /** When it was read (ISO). */
788
+ at: z17.string()
789
+ });
790
+ var OpenRigActionSchema = z17.discriminatedUnion("kind", [
791
+ /** `restore` `last`: `rig start --last` (the rigs that were running come back); `none`: `rig daemon start`. */
792
+ z17.object({ kind: z17.literal("daemon.start"), restore: z17.enum(["last", "none"]).optional(), noKernel: z17.boolean().optional() }),
793
+ z17.object({ kind: z17.literal("daemon.stop") }),
794
+ z17.object({ kind: z17.literal("daemon.restart"), restore: z17.enum(["last", "none"]).optional() }),
795
+ z17.object({ kind: z17.literal("kernel.start") }),
796
+ z17.object({ kind: z17.literal("kernel.stop") }),
797
+ z17.object({ kind: z17.literal("kernel.restart") }),
798
+ z17.object({ kind: z17.literal("rig.start"), rig: RigNameSchema }),
799
+ z17.object({ kind: z17.literal("rig.stop"), rig: RigNameSchema, snapshot: z17.boolean().optional(), force: z17.boolean().optional(), delete: z17.boolean().optional() }),
800
+ z17.object({ kind: z17.literal("rig.restart"), rig: RigNameSchema }),
801
+ /** A seat's settings (docs/mockups/ledge-openrig-groups.html, view 2). `runtime`: moves it now to the other harness (a successor
802
+ * takes its context, `rig seat set-runtime`), `model` and `effort` going with it; without it, `model`, `effort` and `permissions`
803
+ * apply at its next resume. `dryRun`: the move's plan only. */
804
+ z17.object({
805
+ kind: z17.literal("seat.set"),
806
+ seat: RigSeatNameSchema,
807
+ runtime: z17.enum(["claude-code", "codex"]).optional(),
808
+ model: ModelIdSchema.optional(),
809
+ effort: EffortSchema.optional(),
810
+ permissions: SeatPermissionsSchema.optional(),
811
+ dryRun: z17.boolean().optional()
812
+ })
813
+ ]);
814
+ var OpenRigStatusSchema = z17.object({ type: z17.literal("openrig.status"), requestId: z17.string().min(1).max(100), machineId: z17.string().optional() });
815
+ var OpenRigActSchema = z17.object({ type: z17.literal("openrig.act"), requestId: z17.string().min(1).max(100), action: OpenRigActionSchema, machineId: z17.string().optional() });
816
+ var OpenRigUnreachableSchema = z17.enum(["offline", "outdated"]);
817
+ var OpenRigStateMessageSchema = z17.object({
818
+ type: z17.literal("openrig.state"),
819
+ requestId: z17.string().optional(),
820
+ machineId: z17.string().optional(),
821
+ state: OpenRigStateSchema.optional(),
822
+ error: OpenRigUnreachableSchema.optional()
823
+ });
824
+ var OpenRigDoneSchema = z17.object({
825
+ type: z17.literal("openrig.done"),
826
+ requestId: z17.string(),
827
+ machineId: z17.string().optional(),
828
+ ok: z17.boolean(),
829
+ commands: z17.array(z17.string()),
830
+ output: z17.string().optional(),
831
+ error: z17.string().optional()
832
+ });
833
+ var RigSpecMemberSchema = z17.object({
834
+ id: z17.string(),
835
+ runtime: z17.string(),
836
+ model: z17.string().optional(),
837
+ agent: z17.string(),
838
+ /** Its `permission_policy`, as written. */
839
+ policy: z17.string().optional()
840
+ });
841
+ var RigSpecPodSchema = z17.object({ id: z17.string(), label: z17.string().optional(), members: z17.array(RigSpecMemberSchema) });
842
+ var RigSpecSchema = z17.object({
843
+ name: z17.string(),
844
+ source: z17.string(),
845
+ summary: z17.string().optional(),
846
+ path: z17.string(),
847
+ /** The rig's `permission_policy`, as written (`builtin:auto`, a file…). */
848
+ policy: z17.string().optional(),
849
+ pods: z17.array(RigSpecPodSchema),
850
+ edges: z17.number().int().min(0)
851
+ });
852
+ var OpenRigSpecsSchema = z17.object({ type: z17.literal("openrig.specs"), requestId: z17.string().min(1).max(100), machineId: z17.string().optional() });
853
+ var OpenRigSpecListSchema = z17.object({
854
+ type: z17.literal("openrig.specList"),
855
+ requestId: z17.string(),
856
+ machineId: z17.string().optional(),
857
+ specs: z17.array(RigSpecSchema),
858
+ error: z17.string().optional()
859
+ });
860
+ var RigRoleSchema = z17.string().min(3).max(201).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*\.[A-Za-z0-9][A-Za-z0-9_-]*$/, "not a role");
861
+ var RuntimeSchema = z17.enum(["claude-code", "codex"]);
862
+ var RigPolicySchema = z17.enum(["auto", "standard", "open", "yolo", "locked"]);
863
+ var RIG_POLICIES = RigPolicySchema.options;
864
+ var RigLaunchSchema = z17.object({
865
+ spec: RigNameSchema,
866
+ cwd: z17.string().min(1).max(4096),
867
+ name: RigNameSchema,
868
+ seats: z17.record(RigRoleSchema, z17.number().int().min(0).max(9)).optional(),
869
+ runtimes: z17.record(RigRoleSchema, RuntimeSchema).optional(),
870
+ models: z17.record(RigRoleSchema, z17.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/)).optional(),
871
+ delegation: z17.enum(["pairs", "all"]).optional(),
872
+ save: z17.boolean().optional(),
873
+ plan: z17.boolean().optional(),
874
+ /** The rig's permission policy, in place of the spec's; `policies`: a role's, over the rig's. */
875
+ policy: RigPolicySchema.optional(),
876
+ policies: z17.record(RigRoleSchema, RigPolicySchema).optional(),
877
+ /** Accept the harnesses' first-launch warnings (`rig up --non-interruptive`), so a bypassing seat does not stop on Claude's dialog. */
878
+ nonInterruptive: z17.boolean().optional()
879
+ });
880
+ var OpenRigLaunchSchema = z17.object({ type: z17.literal("openrig.launch"), requestId: z17.string().min(1).max(100), launch: RigLaunchSchema, machineId: z17.string().optional() });
881
+ var OpenRigLaunchedSchema = z17.object({
882
+ type: z17.literal("openrig.launched"),
883
+ requestId: z17.string(),
884
+ machineId: z17.string().optional(),
885
+ ok: z17.boolean(),
886
+ plan: z17.boolean(),
887
+ commands: z17.array(z17.string()),
888
+ yaml: z17.string().optional(),
889
+ seats: z17.number().int().optional(),
890
+ output: z17.string().optional(),
891
+ error: z17.string().optional(),
892
+ /** What `rig up --plan` concluded: its status (`planned`…), its errors and its warnings (one per seat without a permission policy…). */
893
+ check: z17.object({ status: z17.string(), errors: z17.array(z17.string()), warnings: z17.array(z17.string()) }).optional()
894
+ });
895
+ var OpenRigSeatsSchema = z17.object({
896
+ type: z17.literal("openrig.seats"),
897
+ requestId: z17.string().min(1).max(100),
898
+ machineId: z17.string().optional(),
899
+ rig: RigNameSchema,
900
+ seats: z17.record(RigRoleSchema, z17.number().int().min(0).max(9)),
901
+ /** How the added seats are linked, where OpenRig can add links to a running rig (0.7+): like a launch's. */
902
+ delegation: z17.enum(["pairs", "all"]).optional()
903
+ });
904
+ var OpenRigReseatedSchema = z17.object({
905
+ type: z17.literal("openrig.reseated"),
906
+ requestId: z17.string(),
907
+ machineId: z17.string().optional(),
908
+ ok: z17.boolean(),
909
+ commands: z17.array(z17.string()),
910
+ added: z17.array(z17.string()),
911
+ removed: z17.array(z17.string()),
912
+ output: z17.string().optional(),
913
+ error: z17.string().optional(),
914
+ /** The links ensured between the seats; `linksUnsupported`: this OpenRig cannot add links to a running rig (before 0.7). */
915
+ links: z17.number().int().optional(),
916
+ linksUnsupported: z17.boolean().optional()
917
+ });
918
+
919
+ // ../protocol/src/routing.ts
920
+ import { z as z18 } from "zod";
921
+ var MachineInventorySchema = z18.object({
922
+ id: z18.string().min(1).max(80),
923
+ label: z18.string().min(1).max(120),
924
+ online: z18.boolean(),
925
+ local: z18.boolean(),
926
+ sessions: z18.array(z18.object({ id: z18.string(), name: z18.string(), project: z18.string(), cwd: z18.string(), topic: z18.string().optional(), state: z18.string(), readOnly: z18.boolean() })).max(500),
927
+ folders: z18.array(z18.object({ project: z18.string(), cwd: z18.string() })).max(100)
928
+ });
929
+ var RoutingContextSchema = z18.object({
930
+ machines: z18.array(MachineInventorySchema).min(1).max(20),
931
+ history: z18.array(z18.object({ question: z18.string().max(2e4), answer: z18.string().max(2e4).optional(), machineId: z18.string().optional() })).max(50)
932
+ });
933
+ var RouteResultSchema = z18.object({
934
+ /** Null means an answer or a clarification, never an implicit local destination. */
935
+ machineId: z18.string().nullable(),
936
+ text: z18.string().min(1).max(2e4),
937
+ choices: z18.array(z18.string()).max(20).optional()
938
+ });
1163
939
 
1164
940
  // ../protocol/src/thread.ts
1165
941
  import { z as z19 } from "zod";
@@ -1171,7 +947,22 @@ var ThreadItemSchema = z19.discriminatedUnion("kind", [
1171
947
  z19.object({ kind: z19.literal("message"), ...base, text: z19.string() }),
1172
948
  /** `id` is the harness's tool_use_id when it gives one, so the result updates the same row; `byClose`: a question
1173
949
  * dismissed by Ledge closing the session. */
1174
- z19.object({ kind: z19.literal("tool"), ...base, tool: z19.string(), label: z19.string(), input: z19.string().optional(), output: z19.string().optional(), error: z19.string().optional(), status: ToolStatusSchema, durationMs: z19.number().nonnegative().optional(), files: z19.array(FileChangeSchema).default([]), byClose: z19.literal(true).optional() }),
950
+ z19.object({
951
+ kind: z19.literal("tool"),
952
+ ...base,
953
+ tool: z19.string(),
954
+ label: z19.string(),
955
+ input: z19.string().optional(),
956
+ output: z19.string().optional(),
957
+ error: z19.string().optional(),
958
+ status: ToolStatusSchema,
959
+ durationMs: z19.number().nonnegative().optional(),
960
+ files: z19.array(FileChangeSchema).default([]),
961
+ byClose: z19.literal(true).optional(),
962
+ /** Images the tool returned (a browser screenshot), in order; `imagesDropped`: some were not kept. */
963
+ images: z19.array(AttachmentRefSchema).max(ATTACHMENTS_MAX).optional(),
964
+ imagesDropped: z19.literal(true).optional()
965
+ }),
1175
966
  /** `unknown`: answered in the terminal, the daemon only saw the prompt go away; `always`: allowed and not asked again;
1176
967
  * `byClose`: denied by Ledge closing the session. */
1177
968
  z19.object({ kind: z19.literal("permission"), ...base, tool: z19.string(), text: z19.string(), decision: z19.enum(["accept", "deny", "always", "unknown"]).optional(), byClose: z19.literal(true).optional() }),
@@ -1207,7 +998,6 @@ var ThreadItemSchema = z19.discriminatedUnion("kind", [
1207
998
 
1208
999
  // ../protocol/src/mobile.ts
1209
1000
  import { z as z20 } from "zod";
1210
- var DEFAULT_RELAY_URL = "https://app.kl-ledge.dev";
1211
1001
  var RelayUrlSchema = z20.string().transform((v, ctx) => {
1212
1002
  const bad = () => {
1213
1003
  ctx.addIssue({ code: z20.ZodIssueCode.custom, message: "invalid relay URL" });
@@ -1254,7 +1044,6 @@ var MobileStateSchema = z20.object({
1254
1044
  });
1255
1045
 
1256
1046
  // ../protocol/src/messages.ts
1257
- var PROTOCOL_PORT = 47831;
1258
1047
  var RefusalCodeSchema = z21.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"]);
1259
1048
  var SessionEventSchema = z21.object({
1260
1049
  name: z21.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed", "NativeRequest", "AgentMessage", "QueueUpdate"]),
@@ -1274,8 +1063,10 @@ var SessionEventSchema = z21.object({
1274
1063
  task: z21.string().optional(),
1275
1064
  /** PostToolUseFailure: the user interrupted the turn (Esc); Claude then waits for input with no Stop. */
1276
1065
  interrupt: z21.boolean().optional(),
1277
- /** UserPromptSubmit: the images the prompt carried, by attachment id. */
1066
+ /** UserPromptSubmit, PostToolUse: the images the prompt or the tool result carried, by attachment id. */
1278
1067
  attachments: z21.array(AttachmentIdSchema).max(ATTACHMENTS_MAX).optional(),
1068
+ /** PostToolUse: some of the result's images were not kept. */
1069
+ imagesDropped: z21.boolean().optional(),
1279
1070
  /** QueueUpdate: OMP's queue as it stands. */
1280
1071
  queue: z21.array(z21.object({ text: z21.string(), mode: z21.enum(["steer", "follow_up"]) })).optional(),
1281
1072
  /** AgentMessage: the harness's own message, its text so far (`streaming`) or whole. `messageId` is unique for the session's life. */
@@ -1437,6 +1228,7 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1437
1228
  z21.object({ type: z21.literal("chat.route"), qid: z21.string().min(1).max(100), text: z21.string().min(1).max(2e4), tool: ToolSchema.optional(), context: RoutingContextSchema }),
1438
1229
  DocumentReadSchema,
1439
1230
  AttachmentPutSchema,
1231
+ AttachmentCaptureSchema,
1440
1232
  AttachmentGetSchema,
1441
1233
  ReposInspectSchema,
1442
1234
  ReposScanSchema,
@@ -1601,106 +1393,6 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1601
1393
  HandoverBriefSchema,
1602
1394
  HandoverShowSchema
1603
1395
  ]);
1604
- function parseClientMessage(raw) {
1605
- return ClientMessageSchema.parse(raw);
1606
- }
1607
- function parseServerMessage(raw) {
1608
- return ServerMessageSchema.parse(raw);
1609
- }
1610
-
1611
- // ../protocol/src/names.ts
1612
- var NAMES = [
1613
- "Otto",
1614
- "Pim",
1615
- "Nova",
1616
- "Bix",
1617
- "Lu",
1618
- "Max",
1619
- "Ida",
1620
- "Rex",
1621
- "Zoe",
1622
- "Kit",
1623
- "Ari",
1624
- "Bo",
1625
- "Cal",
1626
- "Dee",
1627
- "Eli",
1628
- "Fay",
1629
- "Gus",
1630
- "Hal",
1631
- "Ivy",
1632
- "Jax",
1633
- "Kai",
1634
- "Lio",
1635
- "Mo",
1636
- "Nia",
1637
- "Oz",
1638
- "Pax",
1639
- "Quin",
1640
- "Rae",
1641
- "Sol",
1642
- "Tad",
1643
- "Uma",
1644
- "Vic",
1645
- "Wyn",
1646
- "Xen",
1647
- "Yan",
1648
- "Zia",
1649
- "Abe",
1650
- "Bea",
1651
- "Cy",
1652
- "Dot",
1653
- "Eva",
1654
- "Fin",
1655
- "Gil",
1656
- "Hen",
1657
- "Iro",
1658
- "Jo",
1659
- "Kip",
1660
- "Lex",
1661
- "Mia",
1662
- "Ned",
1663
- "Ola",
1664
- "Pat",
1665
- "Rio",
1666
- "Sam",
1667
- "Tia",
1668
- "Ulf",
1669
- "Val",
1670
- "Wes",
1671
- "Yui",
1672
- "Zed",
1673
- "Ash",
1674
- "Bry",
1675
- "Cleo",
1676
- "Dax"
1677
- ];
1678
- var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
1679
- function pickName(taken, preferred) {
1680
- const used = new Set(taken);
1681
- const free = (n) => !used.has(n);
1682
- const wanted = preferred?.trim();
1683
- if (wanted) {
1684
- const base2 = cap(wanted);
1685
- if (free(base2)) return base2;
1686
- for (let i = 2; ; i++) if (free(`${base2}-${i}`)) return `${base2}-${i}`;
1687
- }
1688
- const first = NAMES.find(free);
1689
- if (first) return first;
1690
- for (let i = 2; ; i++) if (free(`${NAMES[0]}-${i}`)) return `${NAMES[0]}-${i}`;
1691
- }
1692
- var namesInUse = (sessions) => [...sessions].filter((s) => s.state !== "exited" && !s.archivedAt).map((s) => s.name);
1693
-
1694
- // ../protocol/src/topic.ts
1695
- var TOPIC_MAX = 80;
1696
- function cleanTopic(text2) {
1697
- const line = text2.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
1698
- const flat = line.replace(/\s+/g, " ");
1699
- return flat.length > TOPIC_MAX ? `${flat.slice(0, TOPIC_MAX - 1).trimEnd()}\u2026` : flat;
1700
- }
1701
- function sessionTitle(s) {
1702
- return `${s.name} \xB7 ${s.topic || s.project}`;
1703
- }
1704
1396
 
1705
1397
  // ../protocol/src/relay.ts
1706
1398
  import { z as z22 } from "zod";
@@ -1746,7 +1438,6 @@ var SealedFrameSchema = z22.discriminatedUnion("k", [
1746
1438
 
1747
1439
  // ../protocol/src/relayAuth.ts
1748
1440
  import { z as z23 } from "zod";
1749
- var RELAY_CLOSE_SIGNED_OUT = 4001;
1750
1441
  var DeviceStartResponseSchema = z23.object({
1751
1442
  deviceCode: z23.string().min(1),
1752
1443
  userCode: z23.string().min(1),
@@ -1820,18 +1511,7 @@ var PushPayloadSchema = z24.object({
1820
1511
 
1821
1512
  // ../protocol/src/phone.ts
1822
1513
  var PHONE_PROTOCOL = 1;
1823
- var PAD_TIERS = [1024, 4096, 16384, 65536, 262144, 655360];
1824
- var PLAIN_MAX = 655360;
1825
1514
  var utf8 = new TextEncoder();
1826
- function padPlain(json) {
1827
- const bytes = utf8.encode(json).length;
1828
- if (bytes > PLAIN_MAX) return null;
1829
- const tier = PAD_TIERS.find((t) => t >= bytes);
1830
- if (tier === void 0) return null;
1831
- return json + " ".repeat(tier - bytes);
1832
- }
1833
- var THREAD_ITEMS_MAX = 200;
1834
- var THREAD_BUDGET = 524288;
1835
1515
  var THREAD_TEXT_MAX = 8192;
1836
1516
  var TOOL_TEXT_MAX = 2048;
1837
1517
  var CHECKLIST_TASKS_MAX = 50;
@@ -1849,8 +1529,6 @@ var PhoneChecklistSummarySchema = z25.object({
1849
1529
  current: z25.string().max(CHECKLIST_TEXT_MAX).optional(),
1850
1530
  cut: z25.literal(true).optional()
1851
1531
  });
1852
- var PENDING_BODY_MAX = 65536;
1853
- var SNAPSHOT_BODY_MAX = 2048;
1854
1532
  var RiskSchema = z25.enum(["low", "high"]);
1855
1533
  var RiskReasonSchema = z25.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"]);
1856
1534
  var OperationIdSchema = z25.string().uuid();
@@ -1864,7 +1542,6 @@ var CapabilitiesSchema = z25.object({
1864
1542
  /** OMP's dialog questions can be answered from the phone; absent = false. */
1865
1543
  questions: z25.boolean().optional()
1866
1544
  });
1867
- var UNTYPABLE = /[\u0000-\u001f\u007f-\u009f\u200b\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
1868
1545
  var CapabilitiesByToolSchema = z25.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
1869
1546
  var PhonePendingSchema = PendingSchema.extend({ key: z25.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z25.literal(true).optional() });
1870
1547
  var SwitchableSessionSchema = SessionSchema.extend({ canSwitch: z25.literal(true).optional() });
@@ -2002,13 +1679,11 @@ var PhoneChatTurnSchema = z25.object({
2002
1679
  ignoredAt: z25.string().datetime().optional(),
2003
1680
  attachments: z25.array(AttachmentRefSchema).max(ATTACHMENTS_MAX).optional()
2004
1681
  }).strict();
2005
- var CHAT_HISTORY_BUDGET = 393216;
2006
1682
  var sessionId = z25.string().min(1).max(100);
2007
1683
  var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z25.string().min(1).max(64) });
2008
1684
  var CardRowsSchema = z25.array(z25.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
2009
1685
  var pcOp = { op: OperationIdSchema, assertion: AssertionSchema.optional() };
2010
1686
  var PcActionSchema = z25.enum(["update", "restart"]);
2011
- var pcSubject = (action, version) => action === "update" ? `update@${version ?? ""}` : "restart";
2012
1687
  var PhoneClientMessageSchema = z25.discriminatedUnion("type", [
2013
1688
  z25.object({ type: z25.literal("snapshot.request") }),
2014
1689
  z25.object({ type: z25.literal("session.thread"), id: sessionId }),
@@ -2181,296 +1856,182 @@ var PhoneServerMessageSchema = z25.discriminatedUnion("type", [
2181
1856
  z25.object({ type: z25.literal("error"), code: z25.enum(["invalid", "unknown_session", "too_large"]), request: z25.string().max(40).optional() })
2182
1857
  ]);
2183
1858
 
2184
- // ../protocol/src/todos.ts
2185
- var TODO_TOOLS = /* @__PURE__ */ new Set(["TodoWrite", "update_plan", "todo"]);
2186
- var STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
2187
- var statusOf = (s) => typeof s === "string" && STATUSES.has(s) ? s : "pending";
2188
- var text = (s) => typeof s === "string" && s.trim() !== "" ? s : null;
2189
- var record = (v) => v && typeof v === "object" ? v : {};
2190
- function omp(input) {
2191
- if (!Array.isArray(input.phases)) return void 0;
2192
- const phases = input.phases.map(record);
2193
- const out = [];
2194
- for (const ph of phases) {
2195
- for (const raw of Array.isArray(ph.tasks) ? ph.tasks : []) {
2196
- const e = record(raw);
2197
- const said = text(e.content);
2198
- if (said === null) continue;
2199
- const blocked = e.status === "blocked";
2200
- const name = phases.length > 1 ? text(ph.name) : null;
2201
- out.push({ text: `${name ? `${name} \xB7 ` : ""}${said}${blocked && text(e.blocker) ? ` (blocked: ${e.blocker})` : ""}`, status: blocked ? "pending" : statusOf(e.status) });
1859
+ // src/imageInfo.ts
1860
+ var ok = (mime, width, height) => width > 0 && height > 0 ? { mime, width, height } : null;
1861
+ function jpeg(b) {
1862
+ let i = 2;
1863
+ while (i + 4 <= b.length) {
1864
+ if (b[i] !== 255) {
1865
+ i++;
1866
+ continue;
2202
1867
  }
1868
+ const m = b[i + 1];
1869
+ if (m === 255) {
1870
+ i++;
1871
+ continue;
1872
+ }
1873
+ if (m === 216 || m === 1 || m >= 208 && m <= 215) {
1874
+ i += 2;
1875
+ continue;
1876
+ }
1877
+ const len = b.readUInt16BE(i + 2);
1878
+ if (m >= 192 && m <= 207 && m !== 196 && m !== 200 && m !== 204) return i + 9 <= b.length ? ok("image/jpeg", b.readUInt16BE(i + 7), b.readUInt16BE(i + 5)) : null;
1879
+ i += 2 + len;
2203
1880
  }
2204
- return out;
1881
+ return null;
2205
1882
  }
2206
- function listOf(t) {
2207
- if (!TODO_TOOLS.has(t.tool) || t.input === void 0) return void 0;
2208
- let input;
2209
- try {
2210
- input = record(JSON.parse(t.input));
2211
- } catch {
2212
- return void 0;
1883
+ function webp(b) {
1884
+ const kind = b.toString("latin1", 12, 16);
1885
+ if (kind === "VP8X") return b.length >= 30 ? ok("image/webp", b.readUIntLE(24, 3) + 1, b.readUIntLE(27, 3) + 1) : null;
1886
+ if (kind === "VP8L") {
1887
+ if (b.length < 25 || b[20] !== 47) return null;
1888
+ const bits = b.readUInt32LE(21);
1889
+ return ok("image/webp", (bits & 16383) + 1, (bits >>> 14 & 16383) + 1);
2213
1890
  }
2214
- if (t.tool === "todo") return omp(input);
2215
- const claude = t.tool === "TodoWrite";
2216
- const list = claude ? input.todos : input.plan;
2217
- if (!Array.isArray(list)) return void 0;
2218
- const out = [];
2219
- for (const raw of list) {
2220
- const e = record(raw);
2221
- const status = statusOf(e.status);
2222
- const said = claude ? status === "in_progress" ? text(e.activeForm) ?? text(e.content) : text(e.content) : text(e.step);
2223
- if (said !== null) out.push({ text: said, status });
2224
- }
2225
- return out;
1891
+ if (kind === "VP8 ") return b.length >= 30 && b[23] === 157 && b[24] === 1 && b[25] === 42 ? ok("image/webp", b.readUInt16LE(26) & 16383, b.readUInt16LE(28) & 16383) : null;
1892
+ return null;
2226
1893
  }
2227
- function latestTodos(items, helperId) {
2228
- for (let i = items.length - 1; i >= 0; i--) {
2229
- const it = items[i];
2230
- if (it.kind !== "tool" || it.helperId !== helperId || !TODO_TOOLS.has(it.tool)) continue;
2231
- const list = listOf(it);
2232
- if (list) return list.length > 0 ? list : null;
1894
+ function imageInfo(b) {
1895
+ try {
1896
+ if (b.length >= 24 && b.readUInt32BE(0) === 2303741511 && b.readUInt32BE(4) === 218765834 && b.toString("latin1", 12, 16) === "IHDR") return ok("image/png", b.readUInt32BE(16), b.readUInt32BE(20));
1897
+ if (b.length >= 10 && (b.toString("latin1", 0, 6) === "GIF87a" || b.toString("latin1", 0, 6) === "GIF89a")) return ok("image/gif", b.readUInt16LE(6), b.readUInt16LE(8));
1898
+ if (b.length >= 4 && b[0] === 255 && b[1] === 216) return jpeg(b);
1899
+ if (b.length >= 16 && b.toString("latin1", 0, 4) === "RIFF" && b.toString("latin1", 8, 12) === "WEBP") return webp(b);
1900
+ } catch {
2233
1901
  }
2234
1902
  return null;
2235
1903
  }
2236
- var doneCount = (todos) => todos.filter((x) => x.status === "completed").length;
2237
1904
 
2238
- // src/auth.ts
2239
- import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
2240
- import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
2241
- import { readFileSync } from "fs";
2242
- import { dirname } from "path";
2243
- var TOKEN_RE = /^[0-9a-f]{64}$/;
2244
- var DAEMON_PROTOCOL_PREFIX = "ledge.";
2245
- function isDaemonToken(value) {
2246
- return TOKEN_RE.test(value);
2247
- }
2248
- function tokenEquals(presented, expected) {
2249
- const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
2250
- const b = createHash("sha256").update(expected, "utf8").digest();
2251
- return timingSafeEqual(a, b);
2252
- }
2253
- function hostAllowed(hostHeader) {
2254
- const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
2255
- if (!raw || raw.length > 255) return false;
2256
- const host = raw.trim().toLowerCase();
2257
- if (!host || host.includes(" ") || host.endsWith(".")) return false;
2258
- let name;
2259
- if (host.startsWith("[")) {
2260
- const end = host.indexOf("]");
2261
- if (end < 0) return false;
2262
- name = host.slice(1, end);
2263
- const rest = host.slice(end + 1);
2264
- if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
2265
- } else {
2266
- const colon = host.lastIndexOf(":");
2267
- if (colon >= 0) {
2268
- if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
2269
- name = host.slice(0, colon);
2270
- } else name = host;
1905
+ // src/daemon/attachments.ts
1906
+ var AttachmentError = class extends Error {
1907
+ constructor(reason) {
1908
+ super(`attachment: ${reason}`);
1909
+ this.reason = reason;
1910
+ this.name = "AttachmentError";
2271
1911
  }
2272
- return name === "localhost" || name === "127.0.0.1" || name === "::1";
2273
- }
2274
- function headerValue(value, max) {
2275
- const raw = Array.isArray(value) ? value.join(",") : value;
2276
- return !raw || raw.length > max ? "" : raw;
2277
- }
2278
- function bearerToken(authorization) {
2279
- if (!authorization || authorization.length > 512) return "";
2280
- return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
2281
- }
2282
- function protocolToken(header) {
2283
- const raw = headerValue(header, 512);
2284
- if (!raw) return "";
2285
- for (const part of raw.split(",").slice(0, 8)) {
2286
- const protocol = part.trim();
2287
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
1912
+ reason;
1913
+ };
1914
+ var MAX_AGE_MS = 24 * 36e5;
1915
+
1916
+ // src/daemon/reduce.ts
1917
+ import { decode, encode } from "jpeg-js";
1918
+ import { PNG } from "pngjs";
1919
+ var DECODE_MAX_PIXELS = 2e7;
1920
+ var MIN_SIDE = 16;
1921
+ function flattenOnWhite(data) {
1922
+ for (let i = 0; i < data.length; i += 4) {
1923
+ const a = data[i + 3];
1924
+ if (a === 255) continue;
1925
+ for (let c = 0; c < 3; c++) data[i + c] = Math.round((data[i + c] * a + 255 * (255 - a)) / 255);
1926
+ data[i + 3] = 255;
2288
1927
  }
2289
- return "";
1928
+ return data;
2290
1929
  }
2291
- function requestAuthorized(headers, expected) {
2292
- const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
2293
- const presented = protocolToken(headers["sec-websocket-protocol"]);
2294
- return bearerOk || presented !== "" && tokenEquals(presented, expected);
2295
- }
2296
- function selectDaemonProtocol(protocols, expected) {
2297
- for (const protocol of protocols) {
2298
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
1930
+ function decodeRaster(raw, mime) {
1931
+ try {
1932
+ if (mime === "image/png") {
1933
+ const p = PNG.sync.read(raw);
1934
+ return { width: p.width, height: p.height, data: flattenOnWhite(p.data) };
1935
+ }
1936
+ const j = decode(raw, { useTArray: true, formatAsRGBA: true });
1937
+ return { width: j.width, height: j.height, data: j.data };
1938
+ } catch {
1939
+ throw new AttachmentError("not_image");
2299
1940
  }
2300
- return false;
2301
1941
  }
2302
- async function readTokenFile(file) {
2303
- try {
2304
- const raw = (await readFile(file, "utf8")).trim();
2305
- return isDaemonToken(raw) ? raw : void 0;
2306
- } catch (e) {
2307
- if (e.code === "ENOENT") return void 0;
2308
- throw e;
1942
+ function resize(src, width, height) {
1943
+ const { width: sw, height: sh, data } = src;
1944
+ const sx = sw / width, sy = sh / height;
1945
+ const out = Buffer.alloc(width * height * 4, 255);
1946
+ const rowCache = /* @__PURE__ */ new Map();
1947
+ const reduceRow = (y) => {
1948
+ const cached = rowCache.get(y);
1949
+ if (cached) return cached;
1950
+ const row = new Float32Array(width * 3);
1951
+ for (let ox = 0; ox < width; ox++) {
1952
+ const x0 = ox * sx, x1 = (ox + 1) * sx;
1953
+ let r = 0, g = 0, b = 0;
1954
+ for (let x = Math.floor(x0); x < Math.min(sw, Math.ceil(x1)); x++) {
1955
+ const w = Math.min(x + 1, x1) - Math.max(x, x0);
1956
+ const i = (y * sw + x) * 4;
1957
+ r += w * data[i];
1958
+ g += w * data[i + 1];
1959
+ b += w * data[i + 2];
1960
+ }
1961
+ row[ox * 3] = r / sx;
1962
+ row[ox * 3 + 1] = g / sx;
1963
+ row[ox * 3 + 2] = b / sx;
1964
+ }
1965
+ rowCache.set(y, row);
1966
+ return row;
1967
+ };
1968
+ for (let oy = 0; oy < height; oy++) {
1969
+ const y0 = oy * sy, y1 = (oy + 1) * sy;
1970
+ const acc = new Float32Array(width * 3);
1971
+ for (let y = Math.floor(y0); y < Math.min(sh, Math.ceil(y1)); y++) {
1972
+ const w = Math.min(y + 1, y1) - Math.max(y, y0);
1973
+ const row = reduceRow(y);
1974
+ for (let k = 0; k < acc.length; k++) acc[k] += w * row[k];
1975
+ }
1976
+ for (let ox = 0; ox < width; ox++) {
1977
+ const o = (oy * width + ox) * 4;
1978
+ out[o] = Math.round(acc[ox * 3] / sy);
1979
+ out[o + 1] = Math.round(acc[ox * 3 + 1] / sy);
1980
+ out[o + 2] = Math.round(acc[ox * 3 + 2] / sy);
1981
+ }
1982
+ for (const k of rowCache.keys()) if (k < Math.floor(y1) - 1) rowCache.delete(k);
2309
1983
  }
1984
+ return { width, height, data: out };
2310
1985
  }
2311
- async function lockTokenFile(file) {
2312
- await chmod(file, 384);
1986
+ var qualityShare = (q, from) => 1 - (from - q) * 0.0115;
1987
+ function toJpeg(src, long, max, from, floor) {
1988
+ const srcLong = Math.max(src.width, src.height);
1989
+ const encodeAt = (side2, q2) => {
1990
+ if (side2 < MIN_SIDE) throw new AttachmentError("too_large");
1991
+ const scale = side2 / srcLong;
1992
+ const r = scale < 1 ? resize(src, Math.max(1, Math.round(src.width * scale)), Math.max(1, Math.round(src.height * scale))) : src;
1993
+ return encode({ data: r.data, width: r.width, height: r.height }, q2).data;
1994
+ };
1995
+ let side = Math.min(long, srcLong);
1996
+ let jpg = encodeAt(side, from);
1997
+ if (jpg.length <= max) return jpg;
1998
+ const ratio = max * 0.85 / jpg.length;
1999
+ let q = from;
2000
+ while (q > floor && qualityShare(q, from) > ratio) q = Math.max(floor, q - 5);
2001
+ if (qualityShare(q, from) > ratio) side = Math.floor(side * Math.sqrt(ratio / qualityShare(q, from)));
2002
+ jpg = encodeAt(side, q);
2003
+ if (jpg.length <= max) return jpg;
2004
+ jpg = encodeAt(Math.floor(side * Math.sqrt(max * 0.7 / jpg.length)), floor);
2005
+ if (jpg.length <= max) return jpg;
2006
+ throw new AttachmentError("too_large");
2313
2007
  }
2314
- async function ensureDaemonToken() {
2315
- const file = paths().token;
2316
- await mkdir(dirname(file), { recursive: true });
2317
- const current = await readTokenFile(file);
2318
- if (current) {
2319
- await lockTokenFile(file);
2320
- return current;
2008
+ function reduceCapture(raw) {
2009
+ if (raw.length > CAPTURE_RAW_MAX) throw new AttachmentError("too_large");
2010
+ const info = imageInfo(raw);
2011
+ if (!info) throw new AttachmentError("not_image");
2012
+ const within = Math.max(info.width, info.height) <= IMAGE_LONG_SIDE && raw.length <= ATTACHMENT_MAX;
2013
+ if (info.mime === "image/webp" || info.mime === "image/gif") {
2014
+ if (!within) throw new AttachmentError("too_large");
2015
+ return { image: raw };
2321
2016
  }
2322
- const token = randomBytes(32).toString("hex");
2323
- const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
2324
- await writeFile(tmp, `${token}
2325
- `, { mode: 384, flag: "wx" });
2326
- await chmod(tmp, 384);
2017
+ if (info.width * info.height > DECODE_MAX_PIXELS) throw new AttachmentError("too_large");
2018
+ let src;
2327
2019
  try {
2328
- await link(tmp, file);
2020
+ src = decodeRaster(raw, info.mime);
2329
2021
  } catch (e) {
2330
- await unlink(tmp).catch(() => {
2331
- });
2332
- if (e.code !== "EEXIST") throw e;
2333
- const raced = await readTokenFile(file);
2334
- if (raced) {
2335
- await lockTokenFile(file);
2336
- return raced;
2337
- }
2338
- await writeFile(file, `${token}
2339
- `, { mode: 384 });
2022
+ if (within) return { image: raw };
2023
+ throw e;
2340
2024
  }
2341
- await unlink(tmp).catch(() => {
2342
- });
2343
- await lockTokenFile(file);
2344
- const stored = await readTokenFile(file);
2345
- if (!stored) throw new Error("daemon token was not stored");
2346
- return stored;
2347
- }
2348
- var SESSION_KEY_VAR = "LEDGE_SESSION_KEY";
2349
- function sessionKey(id, token = readDaemonToken()) {
2350
- return token ? createHmac("sha256", token).update(`ledge-session:${id}`).digest("hex") : void 0;
2025
+ const image = within ? raw : toJpeg(src, IMAGE_LONG_SIDE, ATTACHMENT_MAX, 85, 45);
2026
+ return { image, preview: toJpeg(src, PREVIEW_LONG_SIDE, PREVIEW_MAX, 70, 40) };
2351
2027
  }
2352
- function readDaemonToken() {
2028
+
2029
+ // src/daemon/reduceWorker.ts
2030
+ parentPort.on("message", (raw) => {
2353
2031
  try {
2354
- const raw = readFileSync(paths().token, "utf8").trim();
2355
- return isDaemonToken(raw) ? raw : void 0;
2356
- } catch {
2357
- return void 0;
2032
+ parentPort.postMessage(reduceCapture(Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength)));
2033
+ } catch (e) {
2034
+ if (!(e instanceof AttachmentError)) throw e;
2035
+ parentPort.postMessage({ reason: e.reason });
2358
2036
  }
2359
- }
2360
-
2361
- export {
2362
- ATTACHMENT_ID,
2363
- ATTACHMENT_MAX,
2364
- ATTACHMENT_LOCAL_MAX,
2365
- PREVIEW_MAX,
2366
- ATTACHMENTS_MAX,
2367
- AttachmentRefSchema,
2368
- CONTEXT_NEAR,
2369
- CONTEXT_NEAR_MIN,
2370
- CONTEXT_NEAR_MAX,
2371
- ContextPrefsSchema,
2372
- contextNear,
2373
- BRIEF_MAX,
2374
- ToolSchema,
2375
- SessionSchema,
2376
- backendOf,
2377
- isArchived,
2378
- ModelIdSchema,
2379
- EffortSchema,
2380
- ModelRefusalSchema,
2381
- extractProposal,
2382
- parseRelayBlock,
2383
- parseCloseBlock,
2384
- parseTaskBlock,
2385
- parseProjectBlock,
2386
- NOTE_MAX,
2387
- NOTE_NOT_TYPED,
2388
- offers,
2389
- pendingKey,
2390
- TASK_TITLE_MAX,
2391
- TASK_TEXT_MAX,
2392
- LEDGE_GIVER,
2393
- transmissions,
2394
- givenByLedge,
2395
- taskClosed,
2396
- openDecision,
2397
- ACTIONS,
2398
- actionById,
2399
- riskOf,
2400
- LEGACY_FENCE_ACTION,
2401
- parseActionBlock,
2402
- NEVER_PROPOSED,
2403
- MAX_PROPOSALS,
2404
- DETAIL_MAX,
2405
- ACTION_ERROR_CODES,
2406
- ChatTurnSchema,
2407
- proposalRows,
2408
- DOCUMENT_MAX_BYTES,
2409
- SshAliasSchema,
2410
- MachinesSchema,
2411
- IslandPrefsSchema,
2412
- PROJECT_COLORS,
2413
- ProjectsSchema,
2414
- DEFAULT_PROJECTS,
2415
- projectOf,
2416
- RigSeatNameSchema,
2417
- KERNEL_RIG,
2418
- rigCommands,
2419
- showRigCommand,
2420
- RIG_POLICIES,
2421
- seatsByRole,
2422
- seatPlan,
2423
- RouteResultSchema,
2424
- DEFAULT_RELAY_URL,
2425
- MobileConfigSchema,
2426
- PROTOCOL_PORT,
2427
- RefusalCodeSchema,
2428
- RecentSchema,
2429
- SEMVER,
2430
- MESSAGE_MAX,
2431
- SUMMARY_MAX,
2432
- parseClientMessage,
2433
- parseServerMessage,
2434
- NAMES,
2435
- pickName,
2436
- namesInUse,
2437
- TOPIC_MAX,
2438
- cleanTopic,
2439
- sessionTitle,
2440
- RelayServerMessageSchema,
2441
- PAIR_LABEL_MAX,
2442
- SealedFrameSchema,
2443
- RELAY_CLOSE_SIGNED_OUT,
2444
- DeviceStartResponseSchema,
2445
- DevicePollResponseSchema,
2446
- pushEndpointAllowed,
2447
- PushSendResultSchema,
2448
- PushPayloadSchema,
2449
- PHONE_PROTOCOL,
2450
- padPlain,
2451
- THREAD_ITEMS_MAX,
2452
- THREAD_BUDGET,
2453
- THREAD_TEXT_MAX,
2454
- TOOL_TEXT_MAX,
2455
- CHECKLIST_TASKS_MAX,
2456
- CHECKLIST_TEXT_MAX,
2457
- PENDING_BODY_MAX,
2458
- SNAPSHOT_BODY_MAX,
2459
- OperationIdSchema,
2460
- UNTYPABLE,
2461
- CHAT_HISTORY_BUDGET,
2462
- pcSubject,
2463
- PhoneClientMessageSchema,
2464
- PhoneModelChoiceSchema,
2465
- TODO_TOOLS,
2466
- latestTodos,
2467
- doneCount,
2468
- tokenEquals,
2469
- hostAllowed,
2470
- requestAuthorized,
2471
- selectDaemonProtocol,
2472
- ensureDaemonToken,
2473
- SESSION_KEY_VAR,
2474
- sessionKey,
2475
- readDaemonToken
2476
- };
2037
+ });