@paradow/ledge 0.27.0 → 0.29.0-snapshot.41297455946.1

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 (48) hide show
  1. package/README.md +8 -0
  2. package/dist/cli/{action-7SJJTIEA.js → action-MCR4BCSE.js} +5 -5
  3. package/dist/cli/ask-L3PSQZ2K.js +17 -0
  4. package/dist/cli/{attach-V7UPHVB2.js → attach-R7FT7LFH.js} +9 -14
  5. package/dist/cli/brain-BJTBN2ZK.js +20 -0
  6. package/dist/cli/{bridge-JK3PMG7T.js → bridge-YBHXFEFX.js} +2 -2
  7. package/dist/cli/{chunk-37G5F75H.js → chunk-2X56ZRK7.js} +1229 -1156
  8. package/dist/cli/{chunk-7OGFH6KJ.js → chunk-6EW2FD2U.js} +3 -5
  9. package/dist/cli/chunk-DJMFCYF4.js +60 -0
  10. package/dist/cli/chunk-DLKJOK3B.js +6 -0
  11. package/dist/cli/{chunk-7GIUG73R.js → chunk-FWLT3SAN.js} +1 -1
  12. package/dist/cli/{chunk-6IYPRUQG.js → chunk-G3ST5WKR.js} +1 -1
  13. package/dist/cli/{chunk-7JRQCKBP.js → chunk-GY35ZR5I.js} +102 -72
  14. package/dist/cli/{chunk-NLL2EQJ2.js → chunk-KBSXP63H.js} +22 -7
  15. package/dist/cli/chunk-LOXLGCGN.js +57 -0
  16. package/dist/cli/{chunk-ADAGKOFO.js → chunk-O4MUYFPH.js} +6 -7
  17. package/dist/cli/{chunk-2N7PSP7C.js → chunk-OR2NHT5O.js} +4 -4
  18. package/dist/cli/chunk-S6BZV7AY.js +57 -0
  19. package/dist/cli/{chunk-IGAGJVRZ.js → chunk-TCQ5PYIF.js} +3 -3
  20. package/dist/cli/{chunk-ISNHG5SJ.js → chunk-UMQGRTA4.js} +17 -39
  21. package/dist/cli/{chunk-RA3OIXEQ.js → chunk-VQR5ME75.js} +4 -3
  22. package/dist/cli/{chunk-HOOHS3MX.js → chunk-W724IVDC.js} +1 -1
  23. package/dist/cli/{chunk-K7UEKSB2.js → chunk-WYRQZA4G.js} +2 -2
  24. package/dist/cli/chunk-XTDE2URL.js +365 -0
  25. package/dist/cli/daemon-ICC6LBCO.js +16562 -0
  26. package/dist/cli/{decide-HFI2WQXI.js → decide-JSY46QZC.js} +14 -16
  27. package/dist/cli/{handover-GKORMXZ2.js → handover-5RUSRVAD.js} +5 -5
  28. package/dist/cli/{hook-TJJEFV54.js → hook-QQP3ENGA.js} +87 -81
  29. package/dist/cli/index.js +40 -28
  30. package/dist/cli/launch-RTBGUJSC.js +28 -0
  31. package/dist/cli/{send-RI7JOO5K.js → send-BIIH6SQW.js} +5 -5
  32. package/dist/cli/{setup-K42IVMHS.js → setup-OLVRJLJE.js} +4 -4
  33. package/dist/cli/{status-RQRS26S2.js → status-Z3I33KZJ.js} +3 -3
  34. package/dist/cli/{tui-UWR7SDYF.js → tui-AIXCETOB.js} +6036 -3629
  35. package/dist/cli/{uninstall-4IMMR55T.js → uninstall-X65CWHIJ.js} +4 -4
  36. package/dist/cli/{version-YYNMMWKJ.js → version-QMHFVP3N.js} +1 -1
  37. package/dist/cli/who-C2VMEM35.js +12 -0
  38. package/package.json +21 -6
  39. package/dist/cli/chunk-CVHX7GUB.js +0 -9226
  40. package/dist/cli/chunk-HMTOPOJG.js +0 -6
  41. package/dist/cli/chunk-TOSPL5H2.js +0 -118
  42. package/dist/cli/chunk-UPKJO3BZ.js +0 -159
  43. package/dist/cli/chunk-YLN3VOGV.js +0 -198
  44. package/dist/cli/daemon-YUPB5NUG.js +0 -2322
  45. package/dist/cli/launch-QKQZZSIM.js +0 -35
  46. package/dist/cli/task-U2XHBUKW.js +0 -19
  47. package/dist/cli/team-NFC4XEIA.js +0 -205
  48. package/dist/cli/who-WW6IL4KD.js +0 -12
@@ -1,42 +1,208 @@
1
1
  import {
2
2
  paths
3
- } from "./chunk-HOOHS3MX.js";
3
+ } from "./chunk-W724IVDC.js";
4
4
 
5
- // ../protocol/src/handover.ts
5
+ // src/auth.ts
6
+ import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
7
+ import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
8
+ import { readFileSync } from "fs";
9
+ import { dirname } from "path";
10
+ var TOKEN_RE = /^[0-9a-f]{64}$/;
11
+ var DAEMON_PROTOCOL_PREFIX = "ledge.";
12
+ function isDaemonToken(value) {
13
+ return TOKEN_RE.test(value);
14
+ }
15
+ function tokenEquals(presented, expected) {
16
+ const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
17
+ const b = createHash("sha256").update(expected, "utf8").digest();
18
+ return timingSafeEqual(a, b);
19
+ }
20
+ function hostAllowed(hostHeader) {
21
+ const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
22
+ if (!raw || raw.length > 255) return false;
23
+ const host = raw.trim().toLowerCase();
24
+ if (!host || host.includes(" ") || host.endsWith(".")) return false;
25
+ let name;
26
+ if (host.startsWith("[")) {
27
+ const end = host.indexOf("]");
28
+ if (end < 0) return false;
29
+ name = host.slice(1, end);
30
+ const rest = host.slice(end + 1);
31
+ if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
32
+ } else {
33
+ const colon = host.lastIndexOf(":");
34
+ if (colon >= 0) {
35
+ if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
36
+ name = host.slice(0, colon);
37
+ } else name = host;
38
+ }
39
+ return name === "localhost" || name === "127.0.0.1" || name === "::1";
40
+ }
41
+ function headerValue(value, max) {
42
+ const raw = Array.isArray(value) ? value.join(",") : value;
43
+ return !raw || raw.length > max ? "" : raw;
44
+ }
45
+ function bearerToken(authorization) {
46
+ if (!authorization || authorization.length > 512) return "";
47
+ return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
48
+ }
49
+ function protocolToken(header) {
50
+ const raw = headerValue(header, 512);
51
+ if (!raw) return "";
52
+ for (const part of raw.split(",").slice(0, 8)) {
53
+ const protocol = part.trim();
54
+ if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
55
+ }
56
+ return "";
57
+ }
58
+ function requestAuthorized(headers, expected) {
59
+ const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
60
+ const presented = protocolToken(headers["sec-websocket-protocol"]);
61
+ return bearerOk || presented !== "" && tokenEquals(presented, expected);
62
+ }
63
+ function selectDaemonProtocol(protocols, expected) {
64
+ for (const protocol of protocols) {
65
+ if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
66
+ }
67
+ return false;
68
+ }
69
+ async function readTokenFile(file) {
70
+ try {
71
+ const raw = (await readFile(file, "utf8")).trim();
72
+ return isDaemonToken(raw) ? raw : void 0;
73
+ } catch (e) {
74
+ if (e.code === "ENOENT") return void 0;
75
+ throw e;
76
+ }
77
+ }
78
+ async function lockTokenFile(file) {
79
+ await chmod(file, 384);
80
+ }
81
+ async function ensureDaemonToken() {
82
+ const file = paths().token;
83
+ await mkdir(dirname(file), { recursive: true });
84
+ const current = await readTokenFile(file);
85
+ if (current) {
86
+ await lockTokenFile(file);
87
+ return current;
88
+ }
89
+ const token = randomBytes(32).toString("hex");
90
+ const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
91
+ await writeFile(tmp, `${token}
92
+ `, { mode: 384, flag: "wx" });
93
+ await chmod(tmp, 384);
94
+ try {
95
+ await link(tmp, file);
96
+ } catch (e) {
97
+ await unlink(tmp).catch(() => {
98
+ });
99
+ if (e.code !== "EEXIST") throw e;
100
+ const raced = await readTokenFile(file);
101
+ if (raced) {
102
+ await lockTokenFile(file);
103
+ return raced;
104
+ }
105
+ await writeFile(file, `${token}
106
+ `, { mode: 384 });
107
+ }
108
+ await unlink(tmp).catch(() => {
109
+ });
110
+ await lockTokenFile(file);
111
+ const stored = await readTokenFile(file);
112
+ if (!stored) throw new Error("daemon token was not stored");
113
+ return stored;
114
+ }
115
+ var SESSION_KEY_VAR = "LEDGE_SESSION_KEY";
116
+ function sessionKey(id2, token = readDaemonToken()) {
117
+ return token ? createHmac("sha256", token).update(`ledge-session:${id2}`).digest("hex") : void 0;
118
+ }
119
+ function readDaemonToken() {
120
+ try {
121
+ const raw = readFileSync(paths().token, "utf8").trim();
122
+ return isDaemonToken(raw) ? raw : void 0;
123
+ } catch {
124
+ return void 0;
125
+ }
126
+ }
127
+
128
+ // ../protocol/src/asks.ts
6
129
  import { z } from "zod";
7
- var ContextSchema = z.object({ used: z.number().int().min(0), window: z.number().int().positive(), at: z.string().datetime() });
130
+ var ASK_QUESTION_MAX = 2e3;
131
+ var ASK_OPTIONS_MAX = 4e3;
132
+ var ASK_ANSWER_MAX = 4e3;
133
+ var AskIdSchema = z.string().regex(/^a[0-9a-f]{6}$/);
134
+ var AskSchema = z.object({
135
+ id: AskIdSchema,
136
+ from: z.string().min(1),
137
+ fromName: z.string(),
138
+ question: z.string().min(1).max(ASK_QUESTION_MAX),
139
+ options: z.string().max(ASK_OPTIONS_MAX),
140
+ at: z.string().datetime(),
141
+ state: z.enum(["open", "answered", "dropped"]),
142
+ answer: z.object({ text: z.string().min(1).max(ASK_ANSWER_MAX), at: z.string().datetime() }).optional(),
143
+ closedAt: z.string().datetime().optional()
144
+ });
145
+ var AskRefusalSchema = z.enum(["unknown", "closed", "dropped", "not_session", "invalid"]);
146
+ var AskCreateSchema = z.object({
147
+ type: z.literal("ask.create"),
148
+ rid: z.string().min(1).max(100),
149
+ by: z.string().min(1),
150
+ question: z.string().trim().min(1).max(ASK_QUESTION_MAX),
151
+ options: AskSchema.shape.options
152
+ });
153
+ var AskAnswerSchema = z.object({
154
+ type: z.literal("ask.answer"),
155
+ rid: z.string().min(1).max(100),
156
+ id: AskIdSchema,
157
+ text: z.string().trim().min(1).max(ASK_ANSWER_MAX),
158
+ machineId: z.string().optional()
159
+ });
160
+ var AsksMessageSchema = z.object({ type: z.literal("asks"), asks: z.array(AskSchema) });
161
+ var AskUpdatedSchema = z.object({ type: z.literal("ask.updated"), ask: AskSchema });
162
+ var AskResultSchema = z.object({
163
+ type: z.literal("ask.result"),
164
+ rid: z.string(),
165
+ ask: AskSchema.optional(),
166
+ code: AskRefusalSchema.optional(),
167
+ reason: z.string().optional()
168
+ });
169
+ var askShown = (a) => a.state === "open";
170
+
171
+ // ../protocol/src/handover.ts
172
+ import { z as z2 } from "zod";
173
+ var ContextSchema = z2.object({ used: z2.number().int().min(0), window: z2.number().int().positive(), at: z2.string().datetime() });
8
174
  var CONTEXT_NEAR = 0.8;
9
175
  var CONTEXT_NEAR_MIN = 0.5;
10
176
  var CONTEXT_NEAR_MAX = 0.95;
11
- var ContextPrefsSchema = z.object({ near: z.number().min(CONTEXT_NEAR_MIN).max(CONTEXT_NEAR_MAX) });
177
+ var ContextPrefsSchema = z2.object({ near: z2.number().min(CONTEXT_NEAR_MIN).max(CONTEXT_NEAR_MAX) });
12
178
  var contextShare = (c) => c ? Math.min(1, c.used / c.window) : void 0;
13
179
  var contextNear = (c, near = CONTEXT_NEAR) => (contextShare(c) ?? 0) >= near;
14
- var HandoverStateSchema = z.enum(["briefing", "launching", "done", "failed"]);
15
- var HandoverSchema = z.object({
180
+ var HandoverStateSchema = z2.enum(["briefing", "launching", "done", "failed"]);
181
+ var HandoverSchema = z2.object({
16
182
  state: HandoverStateSchema,
17
- at: z.string().datetime(),
183
+ at: z2.string().datetime(),
18
184
  /** The relief, once launched. */
19
- to: z.string().optional(),
20
- toName: z.string().optional(),
21
- reason: z.string().optional(),
185
+ to: z2.string().optional(),
186
+ toName: z2.string().optional(),
187
+ reason: z2.string().optional(),
22
188
  /** Started by Ledge itself (a seat working without asking), not by the user. */
23
- auto: z.literal(true).optional()
189
+ auto: z2.literal(true).optional()
24
190
  });
25
- var RelievesSchema = z.object({ id: z.string(), name: z.string(), at: z.string().datetime() });
191
+ var RelievesSchema = z2.object({ id: z2.string(), name: z2.string(), at: z2.string().datetime() });
26
192
  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() });
193
+ var ContextSetSchema = z2.object({ type: z2.literal("context.set"), prefs: ContextPrefsSchema });
194
+ var ContextPrefsMessageSchema = z2.object({ type: z2.literal("context.prefs"), prefs: ContextPrefsSchema });
195
+ var SessionHandoverSchema = z2.object({ type: z2.literal("session.handover"), rid: z2.string().min(1).max(100), id: z2.string().min(1) });
196
+ 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) });
197
+ var HandoverShowSchema = z2.object({ type: z2.literal("handover.show"), rid: z2.string().min(1).max(100), by: z2.string().min(1) });
198
+ var SessionHandoverResultSchema = z2.object({ type: z2.literal("session.handover.result"), rid: z2.string(), ok: z2.boolean(), reason: z2.string().optional() });
199
+ var HandoverResultSchema = z2.object({ type: z2.literal("handover.result"), rid: z2.string(), text: z2.string().optional(), reason: z2.string().optional() });
34
200
 
35
201
  // ../protocol/src/session.ts
36
- import { z as z2 } from "zod";
37
- var ToolSchema = z2.enum(["claude", "codex", "cursor", "omp"]);
38
- var OmpApprovalSchema = z2.enum(["always-ask", "write", "yolo"]);
39
- var AutonomySchema = z2.enum(["ask", "edits", "auto"]);
202
+ import { z as z3 } from "zod";
203
+ var ToolSchema = z3.enum(["claude", "codex", "cursor", "omp"]);
204
+ var OmpApprovalSchema = z3.enum(["always-ask", "write", "yolo"]);
205
+ var AutonomySchema = z3.enum(["ask", "edits", "auto"]);
40
206
  function autonomyArgs(tool, autonomy) {
41
207
  if (!autonomy || autonomy === "ask") return [];
42
208
  switch (tool) {
@@ -53,97 +219,95 @@ function autonomyArgs(tool, autonomy) {
53
219
  function ompApprovalFor(autonomy) {
54
220
  return autonomy === "auto" ? "yolo" : autonomy ? "always-ask" : void 0;
55
221
  }
56
- var BackendSchema = z2.enum(["tmux", "omp-rpc"]);
57
- var SessionStateSchema = z2.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
58
- var AskQuestionSchema = z2.object({
59
- id: z2.string(),
60
- question: z2.string(),
61
- header: z2.string().optional(),
62
- multi: z2.boolean().optional(),
63
- options: z2.array(z2.object({ label: z2.string(), description: z2.string().optional() }))
64
- });
65
- var PendingSchema = z2.object({
66
- kind: z2.enum(["question", "permission", "plan", "diff"]),
67
- text: z2.string(),
222
+ var BackendSchema = z3.enum(["tmux", "omp-rpc"]);
223
+ var SessionStateSchema = z3.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
224
+ var AskQuestionSchema = z3.object({
225
+ id: z3.string(),
226
+ question: z3.string(),
227
+ header: z3.string().optional(),
228
+ multi: z3.boolean().optional(),
229
+ options: z3.array(z3.object({ label: z3.string(), description: z3.string().optional() }))
230
+ });
231
+ var PendingSchema = z3.object({
232
+ kind: z3.enum(["question", "permission", "plan", "diff"]),
233
+ text: z3.string(),
68
234
  /** What to review in full before deciding, when `text` is only its summary: a plan's markdown. */
69
- body: z2.string().optional(),
70
- tool: z2.string().optional(),
71
- options: z2.array(z2.string()).optional(),
235
+ body: z3.string().optional(),
236
+ tool: z3.string().optional(),
237
+ options: z3.array(z3.string()).optional(),
72
238
  /** Each option's description, by index ('' when it has none); left out when no option has one. */
73
- optionDetails: z2.array(z2.string()).optional(),
239
+ optionDetails: z3.array(z3.string()).optional(),
74
240
  /** `rpc`: the harness's own protocol asked (OMP): answered on its request id, never by typing. */
75
- source: z2.enum(["hook", "pane", "rpc"]),
241
+ source: z3.enum(["hook", "pane", "rpc"]),
76
242
  /** OMP: every question of one `ask` call, answered together with `session.answer`; `requestId` is the dialog it answers. */
77
- questions: z2.array(AskQuestionSchema).optional(),
78
- requestId: z2.string().optional(),
243
+ questions: z3.array(AskQuestionSchema).optional(),
244
+ requestId: z3.string().optional(),
79
245
  /** Set when the request comes from a helper (subagent): it is routed through its parent. */
80
- helperId: z2.string().optional(),
246
+ helperId: z3.string().optional(),
81
247
  /** For a helper-routed request: the state the parent returns to once it is answered (main thread may stop meanwhile). */
82
248
  parentState: SessionStateSchema.optional()
83
249
  });
84
- var ReplySchema = z2.object({
85
- text: z2.string(),
86
- at: z2.string().datetime(),
250
+ var ReplySchema = z3.object({
251
+ text: z3.string(),
252
+ at: z3.string().datetime(),
87
253
  /** The reply is a plan the agent proposes (Codex Plan mode): nothing in it is done yet. */
88
- plan: z2.boolean().optional()
254
+ plan: z3.boolean().optional()
89
255
  });
90
- var HelperSchema = z2.object({
91
- id: z2.string(),
92
- kind: z2.string(),
93
- task: z2.string(),
256
+ var HelperSchema = z3.object({
257
+ id: z3.string(),
258
+ kind: z3.string(),
259
+ task: z3.string(),
94
260
  state: SessionStateSchema,
95
261
  /** When this helper finished; retained across daemon restarts until its five-minute expiry. */
96
- finishedAt: z2.string().datetime().optional()
262
+ finishedAt: z3.string().datetime().optional()
97
263
  });
98
- var TopicSourceSchema = z2.enum(["user", "pane", "prompt"]);
99
- var SessionSchema = z2.object({
264
+ var TopicSourceSchema = z3.enum(["user", "pane", "prompt"]);
265
+ var SessionRigSchema = z3.object({ machine: z3.string(), rigId: z3.string(), rigName: z3.string(), seat: z3.string(), pod: z3.string(), member: z3.string() });
266
+ var SessionSchema = z3.object({
100
267
  /** Origin is annotated by the overlay, never used as an unqualified session id on the wire. */
101
- machineId: z2.string().optional(),
102
- machineName: z2.string().optional(),
103
- repoId: z2.string().optional(),
268
+ machineId: z3.string().optional(),
269
+ machineName: z3.string().optional(),
270
+ repoId: z3.string().optional(),
104
271
  /** Annotated by the fleet when its machine is unreachable and the last snapshot is kept (keepStale, spec 2026-10-14 §6). */
105
- offline: z2.boolean().optional(),
106
- id: z2.string().min(1),
107
- name: z2.string().min(1),
272
+ offline: z3.boolean().optional(),
273
+ /** The permanent Ledge brain session (Lot 7). */
274
+ brain: z3.literal(true).optional(),
275
+ id: z3.string().min(1),
276
+ name: z3.string().min(1),
108
277
  tool: ToolSchema,
109
- cwd: z2.string(),
110
- project: z2.string(),
111
- title: z2.string(),
278
+ cwd: z3.string(),
279
+ project: z3.string(),
280
+ title: z3.string(),
112
281
  /** One line saying what the session is about (spec 2026-10-11 §3). */
113
- topic: z2.string().optional(),
282
+ topic: z3.string().optional(),
114
283
  topicSource: TopicSourceSchema.optional(),
115
284
  /** The repository (its main worktree's folder name and absolute path), the checked-out branch, and whether `cwd` is a linked worktree (§4). */
116
- repo: z2.string().optional(),
117
- repoPath: z2.string().optional(),
118
- branch: z2.string().optional(),
119
- worktree: z2.boolean().optional(),
285
+ repo: z3.string().optional(),
286
+ repoPath: z3.string().optional(),
287
+ branch: z3.string().optional(),
288
+ worktree: z3.boolean().optional(),
120
289
  /** The folder the agent works in now, as its last hook reported it: `cwd` stays where it started, this follows a worktree it entered. */
121
- workdir: z2.string().optional(),
122
- tmuxSession: z2.string().nullable(),
290
+ workdir: z3.string().optional(),
291
+ tmuxSession: z3.string().nullable(),
123
292
  /** Left out for a tmux session. `omp-rpc`: no pane, nothing to attach to; the daemon holds the process (see `agentSessionFile`). */
124
293
  backend: BackendSchema.optional(),
125
294
  state: SessionStateSchema,
126
295
  pending: PendingSchema.optional(),
127
- helpers: z2.array(HelperSchema).default([]),
296
+ helpers: z3.array(HelperSchema).default([]),
128
297
  /** OMP: the instructions waiting in its own queue (typed while it works), as it reports them; left out when none. */
129
- queue: z2.array(z2.object({ text: z2.string(), mode: z2.enum(["steer", "follow_up"]) })).optional(),
298
+ queue: z3.array(z3.object({ text: z3.string(), mode: z3.enum(["steer", "follow_up"]) })).optional(),
130
299
  lastReply: ReplySchema.optional(),
131
300
  /** 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. */
132
- lastMessage: z2.object({ id: z2.string(), to: z2.string(), peer: z2.string().optional(), summary: z2.string(), at: z2.string().datetime() }).optional(),
133
- startedAt: z2.string().datetime(),
134
- lastActivityAt: z2.string().datetime(),
135
- lastHookAt: z2.string().datetime().optional(),
301
+ lastMessage: z3.object({ id: z3.string(), to: z3.string(), peer: z3.string().optional(), summary: z3.string(), at: z3.string().datetime() }).optional(),
302
+ startedAt: z3.string().datetime(),
303
+ lastActivityAt: z3.string().datetime(),
304
+ lastHookAt: z3.string().datetime().optional(),
136
305
  /** The model and effort Ledge launched the agent with (#58); left out, the agent's own default. */
137
- model: z2.string().optional(),
138
- effort: z2.string().optional(),
306
+ model: z3.string().optional(),
307
+ effort: z3.string().optional(),
139
308
  /** OMP: the approval mode it was launched with, kept by a resume; left out, `always-ask`. */
140
309
  approval: OmpApprovalSchema.optional(),
141
- /** The role it was launched with or took in a team (spec 2026-10-25 roles §3.3): its id, its label as it was then. */
142
- role: z2.string().optional(),
143
- roleLabel: z2.string().optional(),
144
- /** The live team it sits in (`team-…`); left out, the project's common table. */
145
- team: z2.string().optional(),
146
- /** Its role's autonomy, as launched (kept by a resume); left out, `ask`. */
310
+ /** Its autonomy, as launched (kept by a resume); left out, `ask`. */
147
311
  autonomy: AutonomySchema.optional(),
148
312
  /** Its context as its agent last reported it, after a turn (context handover spec §3.1); left out, not measured. */
149
313
  context: ContextSchema.optional(),
@@ -152,21 +316,23 @@ var SessionSchema = z2.object({
152
316
  /** The session it relieves, when it is a relief. */
153
317
  relieves: RelievesSchema.optional(),
154
318
  /** 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. */
155
- modelNext: z2.object({ model: z2.string().optional(), effort: z2.string().optional() }).optional(),
319
+ modelNext: z3.object({ model: z3.string().optional(), effort: z3.string().optional() }).optional(),
156
320
  /** The agent's own session id when Ledge chose it at launch (Claude Code `--session-id`). */
157
- agentSessionId: z2.string().optional(),
321
+ agentSessionId: z3.string().optional(),
158
322
  /** OMP: the exact journal file of the conversation, what a resume names (never "the latest of the folder"). */
159
- agentSessionFile: z2.string().optional(),
323
+ agentSessionFile: z3.string().optional(),
160
324
  /** The agent's transcript: the latest one a hook named (it follows /clear and --resume), else the one the launch implies. */
161
- transcriptPath: z2.string().optional(),
162
- exitCode: z2.number().int().nullable().optional(),
163
- exitedAt: z2.string().datetime().optional(),
164
- lastError: z2.string().optional(),
165
- source: z2.enum(["launcher", "hook_only"]),
325
+ transcriptPath: z3.string().optional(),
326
+ exitCode: z3.number().int().nullable().optional(),
327
+ exitedAt: z3.string().datetime().optional(),
328
+ lastError: z3.string().optional(),
329
+ source: z3.enum(["launcher", "hook_only"]),
166
330
  /** When the session was put in the archive (session management): set, it leaves the live list and waits to be restored, resumed or deleted. */
167
- archivedAt: z2.string().datetime().optional(),
331
+ archivedAt: z3.string().datetime().optional(),
168
332
  /** When the session was last taken out of the archive: the poller's retirement clock restarts from it, not from `exitedAt`. */
169
- restoredAt: z2.string().datetime().optional()
333
+ restoredAt: z3.string().datetime().optional(),
334
+ /** The OpenRig seat this session runs in (paired from the hook's `openrig.sessionName`). */
335
+ rig: SessionRigSchema.optional()
170
336
  });
171
337
  function backendOf(s) {
172
338
  return s.backend ?? "tmux";
@@ -175,351 +341,29 @@ function isArchived(s) {
175
341
  return s.archivedAt !== void 0;
176
342
  }
177
343
 
178
- // ../protocol/src/teams.ts
344
+ // ../protocol/src/actions.ts
179
345
  import { z as z7 } from "zod";
180
346
 
181
- // ../protocol/src/models.ts
182
- import { z as z3 } from "zod";
183
- var ModelIdSchema = z3.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
184
- var EffortSchema = z3.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
185
- var ModelChoiceSchema = z3.object({
186
- id: ModelIdSchema,
187
- label: z3.string(),
188
- description: z3.string().optional(),
189
- efforts: z3.array(EffortSchema).optional(),
190
- defaultEffort: EffortSchema.optional(),
191
- isDefault: z3.boolean().optional()
192
- });
193
- var ModelsListSchema = z3.object({
194
- type: z3.literal("models.list"),
195
- requestId: z3.string().min(1).max(100),
196
- tool: ToolSchema,
197
- machineId: z3.string().optional()
198
- });
199
- var ModelsListingSchema = z3.object({
200
- type: z3.literal("models.listing"),
201
- requestId: z3.string(),
202
- tool: ToolSchema,
203
- models: z3.array(ModelChoiceSchema),
204
- error: z3.string().optional()
205
- });
206
-
207
- // ../protocol/src/projects.ts
208
- import { z as z6 } from "zod";
209
-
210
- // ../protocol/src/machines.ts
211
- import { z as z5 } from "zod";
212
-
213
- // ../protocol/src/ssh.ts
347
+ // ../protocol/src/proposal.ts
214
348
  import { z as z4 } from "zod";
215
- var SshAliasSchema = z4.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
216
-
217
- // ../protocol/src/machines.ts
218
- var MachineIdSchema = z5.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
219
- var MachineSchema = z5.object({
220
- id: MachineIdSchema,
221
- label: z5.string().trim().min(1).max(80),
222
- ssh: SshAliasSchema.optional()
223
- });
224
- var MachinesSchema = z5.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
225
- if (list[0]?.id !== "local" || list[0].ssh !== void 0) ctx.addIssue({ code: "custom", message: "the first machine is local, without ssh", path: [0] });
226
- const ids = /* @__PURE__ */ new Set(), aliases = /* @__PURE__ */ new Set();
227
- list.forEach((m, i) => {
228
- if (i > 0 && !m.ssh) ctx.addIssue({ code: "custom", message: "a remote machine needs an ssh alias", path: [i, "ssh"] });
229
- if (ids.has(m.id)) ctx.addIssue({ code: "custom", message: `duplicate id ${m.id}`, path: [i, "id"] });
230
- ids.add(m.id);
231
- if (m.ssh) {
232
- if (aliases.has(m.ssh)) ctx.addIssue({ code: "custom", message: `duplicate ssh alias ${m.ssh}`, path: [i, "ssh"] });
233
- aliases.add(m.ssh);
234
- }
235
- });
349
+ var ProposalBlockSchema = z4.object({ cwd: z4.string().trim().min(1), prompt: z4.string().optional() });
350
+ var RelayBlockSchema = z4.object({ session: z4.string().trim().min(1), text: z4.string().trim().min(1) });
351
+ var ProjectBlockSchema = z4.object({
352
+ name: z4.string().trim().min(1).max(80),
353
+ description: z4.string().max(500).optional(),
354
+ repos: z4.array(z4.object({ path: z4.string().min(1).max(4096), why: z4.string().trim().min(1).max(200) })).min(1).max(50),
355
+ addTo: z4.string().trim().max(80).optional()
236
356
  });
237
-
238
- // ../protocol/src/projects.ts
239
- var PROJECT_COLORS = { blue: "#3b82f6", green: "#10b981", amber: "#f59e0b", violet: "#8b5cf6", teal: "#14b8a6", pink: "#ec4899", red: "#ef4444", slate: "#64748b" };
240
- var ProjectColorSchema = z6.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
241
- var ProjectIdSchema = z6.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
242
- function normalizePath(raw) {
243
- const p = raw.trim().replace(/\/+/g, "/");
244
- return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
245
- }
246
- var PathSchema = z6.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
247
- var RepoSchema = z6.object({ machine: MachineIdSchema, path: PathSchema });
248
- var ProjectSchema = z6.object({
249
- id: ProjectIdSchema,
250
- name: z6.string().trim().min(1).max(80),
251
- color: ProjectColorSchema,
252
- description: z6.string().max(500).default(""),
253
- repos: z6.array(RepoSchema).max(50)
254
- });
255
- var RuleSchema = z6.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
256
- function projectsProblems(p) {
257
- const out = [];
258
- const ids = /* @__PURE__ */ new Set(), names = /* @__PURE__ */ new Set(), repos = /* @__PURE__ */ new Set(), rules = /* @__PURE__ */ new Set();
259
- p.projects.forEach((project, i) => {
260
- if (ids.has(project.id)) out.push({ code: "duplicate_id", path: ["projects", i, "id"] });
261
- ids.add(project.id);
262
- const name = project.name.trim().toLocaleLowerCase();
263
- if (names.has(name)) out.push({ code: "duplicate_name", path: ["projects", i, "name"] });
264
- names.add(name);
265
- project.repos.forEach((repo, j) => {
266
- const key = `${repo.machine}
267
- ${repo.path}`;
268
- if (repos.has(key)) out.push({ code: "duplicate_repo", path: ["projects", i, "repos", j] });
269
- repos.add(key);
270
- });
271
- });
272
- p.rules.forEach((rule, i) => {
273
- const key = `${rule.machine}
274
- ${rule.path}`;
275
- if (rules.has(key)) out.push({ code: "duplicate_rule", path: ["rules", i] });
276
- rules.add(key);
277
- if (!ids.has(rule.project)) out.push({ code: "unknown_project", path: ["rules", i, "project"] });
278
- });
279
- return out;
280
- }
281
- var ProjectsSchema = z6.object({ projects: z6.array(ProjectSchema).max(100), rules: z6.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
282
- for (const problem of projectsProblems(p)) ctx.addIssue({ code: "custom", message: problem.code, path: problem.path });
283
- });
284
- var DEFAULT_PROJECTS = { projects: [], rules: [] };
285
- var under = (cwd, path2) => path2 === "/" || cwd === path2 || cwd.startsWith(`${path2}/`);
286
- function projectOf(session, state) {
287
- const machine = session.machineId ?? "local";
288
- const cwd = normalizePath(session.cwd);
289
- const known = new Set(state.projects.map((p) => p.id));
290
- const candidates = [
291
- ...state.projects.flatMap((p) => p.repos.filter((r) => r.machine === machine).map((r) => ({ project: p.id, path: r.path, rule: false }))),
292
- ...state.rules.filter((r) => r.machine === machine && known.has(r.project)).map((r) => ({ project: r.project, path: r.path, rule: true }))
293
- ].filter((c) => under(cwd, c.path));
294
- candidates.sort((x, y) => y.path.length - x.path.length || Number(y.rule) - Number(x.rule));
295
- return candidates[0]?.project ?? null;
296
- }
297
-
298
- // ../protocol/src/teams.ts
299
- var RoleIdSchema = z7.string().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/);
300
- var ROLE_LABEL_MAX = 40;
301
- var ROLE_PROMPT_MAX = 8e3;
302
- var DefinitionHashSchema = z7.string().regex(/^[0-9a-f]{16}$/);
303
- var RoleSchema = z7.object({
304
- id: RoleIdSchema,
305
- label: z7.string().min(1).max(ROLE_LABEL_MAX),
306
- prompt: z7.string().max(ROLE_PROMPT_MAX),
307
- tool: ToolSchema.optional(),
308
- model: ModelIdSchema.optional(),
309
- effort: EffortSchema.optional(),
310
- autonomy: AutonomySchema.optional(),
311
- source: z7.enum(["repo", "home"]),
312
- path: z7.string(),
313
- shadows: z7.string().optional(),
314
- /** 16 hex chars of the file's text: what a write or delete must name as its base (absent from older daemons). */
315
- hash: DefinitionHashSchema.optional()
316
- });
317
- var DefinitionProblemSchema = z7.object({ path: z7.string(), reason: z7.string() });
318
- var TEAM_SEATS_MAX = 8;
319
- var SeatSchema = z7.object({
320
- role: RoleIdSchema,
321
- tool: ToolSchema.optional(),
322
- model: ModelIdSchema.optional(),
323
- effort: EffortSchema.optional(),
324
- name: z7.string().min(1).max(40).optional()
325
- });
326
- var uniqueRoles = (seats) => new Set(seats.map((s) => s.role)).size === seats.length;
327
- var SeatsSchema = z7.array(SeatSchema).min(1).max(TEAM_SEATS_MAX).refine(uniqueRoles, "a role twice in one team");
328
- var TeamNameSchema = z7.string().trim().min(1).max(60);
329
- var TemplateIdSchema = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/);
330
- var TeamTemplateSchema = z7.object({ id: z7.string().min(1).max(64), name: TeamNameSchema, seats: SeatsSchema, source: z7.enum(["repo", "home"]), path: z7.string(), hash: DefinitionHashSchema.optional() });
331
- var TeamIdSchema = z7.string().regex(/^team-[0-9a-f]{4,8}$/);
332
- var TeamStateSchema = z7.enum(["active", "resting", "dissolved"]);
333
- var TeamSchema = z7.object({
334
- id: TeamIdSchema,
335
- name: TeamNameSchema,
336
- template: z7.string().optional(),
337
- project: z7.string().nullable(),
338
- machine: z7.string(),
339
- cwd: z7.string(),
340
- state: TeamStateSchema,
341
- /** A live seat: `tool`, `model` and `effort` are what it was composed with over the role's defaults; `leaving` marks a seat removed
342
- * from the composition that finishes its turn and then is closed. */
343
- seats: z7.array(z7.object({
344
- role: RoleIdSchema,
345
- sessionId: z7.string().nullable(),
346
- tool: ToolSchema.optional(),
347
- model: ModelIdSchema.optional(),
348
- effort: EffortSchema.optional(),
349
- leaving: z7.literal(true).optional()
350
- })).max(TEAM_SEATS_MAX * 2),
351
- createdAt: z7.string(),
352
- updatedAt: z7.string(),
353
- restingAt: z7.string().optional(),
354
- dissolvedAt: z7.string().optional()
355
- });
356
- var RolesListSchema = z7.object({ type: z7.literal("roles.list"), cwd: z7.string().min(1).optional(), requestId: z7.string().max(100).optional(), machineId: z7.string().optional() });
357
- var RolesListingSchema = z7.object({
358
- type: z7.literal("roles.listing"),
359
- requestId: z7.string().optional(),
360
- roles: z7.array(RoleSchema),
361
- templates: z7.array(TeamTemplateSchema).default([]),
362
- problems: z7.array(DefinitionProblemSchema),
363
- /** Set by the fleet when the project's machine could not answer (#99): 'offline', or 'outdated' for a daemon without roles.list. */
364
- error: z7.string().optional()
365
- });
366
- var TeamLaunchSchema = z7.object({
367
- type: z7.literal("team.launch"),
368
- rid: z7.string().min(1).max(100),
369
- cwd: z7.string().min(1).optional(),
370
- template: z7.string().min(1).max(64).optional(),
371
- seats: SeatsSchema.optional(),
372
- name: TeamNameSchema.optional(),
373
- qid: z7.string().min(1).optional(),
374
- machineId: z7.string().optional(),
375
- /** The project's name, for a client that holds no path (the phone): the daemon resolves its folder. */
376
- project: ProjectIdSchema.optional()
377
- });
378
- var TeamJoinSchema = z7.object({ type: z7.literal("team.join"), team: TeamIdSchema, session: z7.string().min(1), role: RoleIdSchema });
379
- var TeamLeaveSchema = z7.object({ type: z7.literal("team.leave"), team: TeamIdSchema, session: z7.string().min(1) });
380
- var TeamResumeSchema = z7.object({ type: z7.literal("team.resume"), team: TeamIdSchema });
381
- var TeamDissolveSchema = z7.object({ type: z7.literal("team.dissolve"), team: TeamIdSchema });
382
- var TeamSaveSchema = z7.object({ type: z7.literal("team.save"), rid: z7.string().min(1).max(100), name: TeamNameSchema, seats: SeatsSchema, qid: z7.string().min(1).optional() });
383
- var DefinitionScopeSchema = z7.enum(["home", "repo"]);
384
- var RoleDraftSchema = RoleSchema.pick({ id: true, label: true, prompt: true, tool: true, model: true, effort: true, autonomy: true });
385
- var DefinitionCodeSchema = z7.enum(["changed", "exists", "missing", "unknown_project", "invalid"]);
386
- var scopeFields = { rid: z7.string().min(1).max(100), scope: DefinitionScopeSchema, project: ProjectIdSchema.optional() };
387
- var scopeIssue = (m) => m.scope === void 0 || m.scope === "repo" === (m.project !== void 0) ? null : "a repo scope names a project, a home scope none";
388
- var DefinitionBaseSchema = DefinitionHashSchema;
389
- var DefinitionsListSchema = z7.object({ type: z7.literal("definitions.list"), requestId: z7.string().max(100).optional() });
390
- var RoleWriteSchema = z7.object({ type: z7.literal("role.write"), ...scopeFields, role: RoleDraftSchema, base: DefinitionBaseSchema.nullable() });
391
- var RoleDeleteSchema = z7.object({ type: z7.literal("role.delete"), ...scopeFields, id: RoleIdSchema, base: DefinitionBaseSchema });
392
- var TemplateWriteSchema = z7.object({
393
- type: z7.literal("template.write"),
394
- ...scopeFields,
395
- id: TemplateIdSchema.optional(),
396
- name: TeamNameSchema,
397
- seats: SeatsSchema,
398
- base: DefinitionBaseSchema.nullable()
399
- });
400
- var TemplateDeleteSchema = z7.object({ type: z7.literal("template.delete"), ...scopeFields, id: TemplateIdSchema, base: DefinitionBaseSchema });
401
- var TeamComposeSchema = z7.object({ type: z7.literal("team.compose"), rid: z7.string().min(1).max(100), team: TeamIdSchema, seats: SeatsSchema });
402
- var TeamSeatSchema = z7.object({ type: z7.literal("team.seat"), rid: z7.string().min(1).max(100), team: TeamIdSchema, role: RoleIdSchema, action: z7.enum(["start", "restart"]) });
403
- var DefinitionsListingSchema = z7.object({
404
- type: z7.literal("definitions.listing"),
405
- requestId: z7.string().optional(),
406
- scopes: z7.array(z7.object({
407
- scope: DefinitionScopeSchema,
408
- project: z7.string().nullable(),
409
- roles: z7.array(RoleSchema),
410
- templates: z7.array(TeamTemplateSchema),
411
- problems: z7.array(DefinitionProblemSchema)
412
- }))
413
- });
414
- var DefinitionResultSchema = z7.object({ type: z7.literal("definition.result"), rid: z7.string(), id: z7.string().optional(), hash: DefinitionHashSchema.optional(), code: DefinitionCodeSchema.optional(), reason: z7.string().optional() });
415
- var TeamChangedSchema = z7.object({ type: z7.literal("team.changed"), rid: z7.string(), team: TeamSchema.optional(), reason: z7.string().optional() });
416
- var TeamsMessageSchema = z7.object({ type: z7.literal("teams"), teams: z7.array(TeamSchema) });
417
- var TeamUpdatedSchema = z7.object({ type: z7.literal("team.updated"), team: TeamSchema });
418
- var TeamLaunchedSchema = z7.object({ type: z7.literal("team.launched"), rid: z7.string(), team: TeamSchema });
419
- var TeamFailedSchema = z7.object({ type: z7.literal("team.failed"), rid: z7.string(), reason: z7.string(), team: TeamSchema.optional() });
420
- var TeamSavedSchema = z7.object({ type: z7.literal("team.saved"), rid: z7.string(), path: z7.string().optional(), reason: z7.string().optional() });
421
- var isTeamLive = (t) => t.state !== "dissolved";
422
-
423
- // ../protocol/src/tasks.ts
424
- import { z as z8 } from "zod";
425
- var TASK_TITLE_MAX = 200;
426
- var TASK_TEXT_MAX = 2e4;
427
- var LEDGE_GIVER = "ledge";
428
- var TASK_OPS = ["give", "take", "pass", "done", "refuse", "cancel", "drop"];
429
- var TRANSMISSION_OPS = ["give", "pass", "done", "refuse", "cancel"];
430
- var TaskLogEntrySchema = z8.object({
431
- at: z8.string(),
432
- by: z8.string(),
433
- what: z8.string(),
434
- op: z8.enum(TASK_OPS).optional(),
435
- from: z8.string().optional(),
436
- to: z8.string().optional(),
437
- fromName: z8.string().optional(),
438
- toName: z8.string().optional(),
439
- note: z8.string().optional()
440
- });
441
- var TaskSchema = z8.object({
442
- id: z8.string(),
443
- title: z8.string(),
444
- text: z8.string().optional(),
445
- giver: z8.string(),
446
- giverName: z8.string(),
447
- owner: z8.string(),
448
- ownerName: z8.string(),
449
- state: z8.enum(["open", "taken", "done", "refused", "dropped"]),
450
- result: z8.string().optional(),
451
- /** The live team its owner sat in when it was given (or passed), so a team's tasks can be listed. */
452
- team: z8.string().optional(),
453
- /** A task Ledge gave and the user put away from « À toi » once closed. */
454
- seen: z8.boolean().optional(),
455
- at: z8.string(),
456
- updatedAt: z8.string(),
457
- log: z8.array(TaskLogEntrySchema)
458
- });
459
- var handOver = (op) => TRANSMISSION_OPS.includes(op ?? "");
460
- function transmissions(t) {
461
- 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 } : {} }] : []);
462
- }
463
- var givenByLedge = (t) => t.giver === LEDGE_GIVER;
464
- var taskClosed = (t) => t.state === "done" || t.state === "refused" || t.state === "dropped";
465
- var TaskCmdSchema = z8.object({
466
- type: z8.literal("task.cmd"),
467
- rid: z8.string().min(1),
468
- by: z8.string().min(1),
469
- op: z8.enum(["give", "take", "done", "refuse", "pass", "show", "list", "seen", "cancel"]),
470
- task: z8.string().min(1).max(40).optional(),
471
- to: z8.string().min(1).optional(),
472
- title: z8.string().min(1).max(TASK_TITLE_MAX).optional(),
473
- text: z8.string().max(TASK_TEXT_MAX).optional(),
474
- all: z8.boolean().optional(),
475
- team: z8.string().min(1).max(40).optional(),
476
- qid: z8.string().min(1).optional(),
477
- row: z8.number().int().min(0).max(7).optional()
478
- });
479
- var TaskUpdatedSchema = z8.object({ type: z8.literal("task.updated"), task: TaskSchema });
480
- var TaskResultSchema = z8.object({
481
- type: z8.literal("task.result"),
482
- rid: z8.string(),
483
- task: TaskSchema.optional(),
484
- tasks: z8.array(TaskSchema).optional(),
485
- reason: z8.string().optional()
486
- });
487
-
488
- // ../protocol/src/actions.ts
489
- import { z as z11 } from "zod";
490
-
491
- // ../protocol/src/proposal.ts
492
- import { z as z9 } from "zod";
493
- var ProposalBlockSchema = z9.object({ cwd: z9.string().trim().min(1), prompt: z9.string().optional() });
494
- var RelayBlockSchema = z9.object({ session: z9.string().trim().min(1), text: z9.string().trim().min(1) });
495
- var ProjectBlockSchema = z9.object({
496
- name: z9.string().trim().min(1).max(80),
497
- description: z9.string().max(500).optional(),
498
- repos: z9.array(z9.object({ path: z9.string().min(1).max(4096), why: z9.string().trim().min(1).max(200) })).min(1).max(50),
499
- addTo: z9.string().trim().max(80).optional()
500
- });
501
- var CloseBlockSchema = z9.object({ session: z9.string().trim().min(1) });
502
- var TeamBlockSchema = z9.object({
503
- cwd: z9.string().trim().min(1),
504
- template: z9.string().trim().min(1).max(64).optional(),
505
- name: z9.string().trim().min(1).max(60).optional(),
506
- seats: z9.array(z9.object({ role: z9.string().trim().min(1).max(40), tool: z9.enum(["claude", "codex", "cursor", "omp"]).optional(), model: z9.string().max(200).optional(), effort: z9.string().max(20).optional(), name: z9.string().max(40).optional() })).min(1).max(8).optional()
507
- }).refine((b) => !!b.template || !!b.seats?.length, "a template or seats");
508
- var TaskBlockSchema = z9.object({
509
- to: z9.string().trim().min(1).max(80),
510
- team: z9.string().trim().max(40).optional(),
511
- title: z9.string().trim().min(1).max(200),
512
- text: z9.string().max(2e4).optional()
513
- });
514
- var BLOCK = /```ledge-(session|relay|project|close|team|task|action)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
357
+ var CloseBlockSchema = z4.object({ session: z4.string().trim().min(1) });
358
+ var BLOCK = /```ledge-(session|relay|project|close|action)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
515
359
  function extractProposal(answer) {
516
360
  const found = [];
517
- const text = answer.replace(BLOCK, (_m, kind, body) => {
361
+ const text2 = answer.replace(BLOCK, (_m, kind, body) => {
518
362
  found.push({ kind, raw: body.trim() });
519
363
  return "";
520
364
  });
521
365
  const last = found.at(-1);
522
- return last ? { text: text.replace(/\n{3,}/g, "\n\n").trim(), kind: last.kind, raw: last.raw, blocks: found } : { text: answer, kind: null, raw: null, blocks: [] };
366
+ 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: [] };
523
367
  }
524
368
  function parseRelayBlock(raw) {
525
369
  try {
@@ -537,22 +381,6 @@ function parseCloseBlock(raw) {
537
381
  return null;
538
382
  }
539
383
  }
540
- function parseTeamBlock(raw) {
541
- try {
542
- const r = TeamBlockSchema.safeParse(JSON.parse(raw));
543
- return r.success ? r.data : null;
544
- } catch {
545
- return null;
546
- }
547
- }
548
- function parseTaskBlock(raw) {
549
- try {
550
- const r = TaskBlockSchema.safeParse(JSON.parse(raw));
551
- return r.success ? r.data : null;
552
- } catch {
553
- return null;
554
- }
555
- }
556
384
  function parseProjectBlock(raw) {
557
385
  try {
558
386
  const r = ProjectBlockSchema.safeParse(JSON.parse(raw));
@@ -562,9 +390,37 @@ function parseProjectBlock(raw) {
562
390
  }
563
391
  }
564
392
 
393
+ // ../protocol/src/models.ts
394
+ import { z as z5 } from "zod";
395
+ var ModelIdSchema = z5.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@[\]=,+-]*$/, "not a model id");
396
+ var EffortSchema = z5.string().min(1).max(20).regex(/^[a-z]+$/, "not an effort");
397
+ var ModelChoiceSchema = z5.object({
398
+ id: ModelIdSchema,
399
+ label: z5.string(),
400
+ description: z5.string().optional(),
401
+ efforts: z5.array(EffortSchema).optional(),
402
+ defaultEffort: EffortSchema.optional(),
403
+ isDefault: z5.boolean().optional()
404
+ });
405
+ var ModelsListSchema = z5.object({
406
+ type: z5.literal("models.list"),
407
+ requestId: z5.string().min(1).max(100),
408
+ tool: ToolSchema,
409
+ machineId: z5.string().optional()
410
+ });
411
+ var ModelsListingSchema = z5.object({
412
+ type: z5.literal("models.listing"),
413
+ requestId: z5.string(),
414
+ tool: ToolSchema,
415
+ models: z5.array(ModelChoiceSchema),
416
+ error: z5.string().optional()
417
+ });
418
+ var ModelRefusalSchema = z5.enum(["not_launched", "ended", "no_conversation", "not_running", "effort_in_model", "restart_failed", "refused"]);
419
+ var SessionModelAcceptedSchema = z5.object({ type: z5.literal("session.model.accepted"), id: z5.string(), when: z5.enum(["now", "turn", "none"]) });
420
+
565
421
  // ../protocol/src/decisions.ts
566
- import { z as z10 } from "zod";
567
- var DecisionSchema = z10.enum(["accept", "deny", "always"]);
422
+ import { z as z6 } from "zod";
423
+ var DecisionSchema = z6.enum(["accept", "deny", "always"]);
568
424
  var NOTE_MAX = 4e3;
569
425
  var NOTE_NOT_TYPED = "note_not_typed";
570
426
  var DECISION_OFFERS = {
@@ -594,33 +450,22 @@ function pendingKey(p) {
594
450
  }
595
451
 
596
452
  // ../protocol/src/actions.ts
597
- var ActionRiskSchema = z11.enum(["read", "reversible", "destructive"]);
453
+ var ActionRiskSchema = z7.enum(["read", "reversible", "destructive"]);
598
454
  var ACTION_IDS = [
599
455
  "session.launch",
600
456
  "session.relay",
601
457
  "session.close",
602
458
  "project.propose",
603
- "team.launch",
604
- "task.give",
605
459
  "session.model",
606
460
  "session.interrupt",
607
461
  "session.archive",
608
462
  "session.restore",
609
463
  "session.resume",
610
- "session.decide",
611
- "team.join",
612
- "team.leave",
613
- "team.resume",
614
- "team.dissolve",
615
- "team.save",
616
- "task.cancel",
617
- "task.pass"
464
+ "session.decide"
618
465
  ];
619
- var ActionIdSchema = z11.enum(ACTION_IDS);
466
+ var ActionIdSchema = z7.enum(ACTION_IDS);
620
467
  var def = (d) => d;
621
- var SessionRef = z11.string().trim().min(1).max(80);
622
- var TeamRef = z11.string().trim().min(1).max(60);
623
- var TaskRef = z11.string().trim().min(1).max(40);
468
+ var SessionRef = z7.string().trim().min(1).max(80);
624
469
  var LaunchArgs = ProposalBlockSchema.extend({ model: ModelIdSchema.optional(), effort: EffortSchema.optional() });
625
470
  var ACTIONS = [
626
471
  def({
@@ -668,44 +513,20 @@ var ACTIONS = [
668
513
  when: "seulement si l'utilisateur demande explicitement de cr\xE9er ou de compl\xE9ter un projet : lis projects.md, ne cite que des chemins absolus lus dans sessions.md, folders.md ou projects.md, jamais un d\xE9p\xF4t qui appartient d\xE9j\xE0 \xE0 un projet ; ta r\xE9ponse dit le nom, chaque d\xE9p\xF4t et pourquoi",
669
514
  example: { name: "Facturation", description: "Le service et son front", repos: [{ path: "/home/alice/projets/billing-api", why: "le service" }] }
670
515
  }),
671
- def({
672
- id: "team.launch",
673
- args: TeamBlockSchema,
674
- card: "team",
675
- phone: "launch",
676
- risk: "reversible",
677
- max: 1,
678
- cli: "ledge team up <mod\xE8le> --cwd <dossier>",
679
- summary: "monter une \xE9quipe dans un dossier : un mod\xE8le de teams.md (template), ou des si\xE8ges (r\xF4le de roles.md, agent, et si demand\xE9 model, effort)",
680
- when: "quand l'utilisateur demande explicitement de monter, lancer ou composer une \xE9quipe, avec la m\xEAme r\xE8gle de dossier s\xFBr qu'une session ; 8 si\xE8ges au plus, un r\xF4le une seule fois, seulement des r\xF4les de roles.md disponibles dans ce dossier (s'il en manque un, dis lequel et le fichier \xE0 \xE9crire, ~/.ledge/roles/<id>.md). Ne choisis jamais l'autonomie : c'est celle du r\xF4le",
681
- example: { cwd: "/home/alice/projets/api", name: "api", seats: [{ role: "dev-back", tool: "claude" }, { role: "rev-back", tool: "codex" }] }
682
- }),
683
- def({
684
- id: "task.give",
685
- args: TaskBlockSchema,
686
- card: "task",
687
- phone: null,
688
- risk: "reversible",
689
- max: 8,
690
- cli: 'ledge task give <session|@r\xF4le> "<titre>" "<d\xE9tail>"',
691
- summary: "confier une t\xE2che \xE0 une session, ou \xE0 un @r\xF4le d'une \xE9quipe (team : son id dans teams.md ; sans team, un si\xE8ge de l'\xE9quipe propos\xE9e dans la m\xEAme r\xE9ponse)",
692
- when: "quand l'utilisateur demande explicitement de confier une t\xE2che, ou juste apr\xE8s team.launch quand il a dit ce que l'\xE9quipe doit faire. Une fois l'\xE9quipe lanc\xE9e, ne propose rien pour ce que les si\xE8ges se confient entre eux",
693
- example: { to: "@dev-back", title: "Pagination de /invoices", text: "Ajoute la pagination par curseur et ses tests" }
694
- }),
695
516
  def({
696
517
  id: "session.model",
697
518
  card: "generic",
698
519
  phone: null,
699
520
  risk: "reversible",
700
521
  max: 8,
701
- args: z11.object({ session: SessionRef, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
522
+ args: z7.object({ session: SessionRef, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
702
523
  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",
703
524
  when: "quand l'utilisateur demande de passer une session sur un autre mod\xE8le ou un autre effort",
704
525
  example: { session: "Nova", model: "claude-opus-5-5", effort: "high" }
705
526
  }),
706
527
  def({
707
528
  id: "session.interrupt",
708
- args: z11.object({ session: SessionRef }),
529
+ args: z7.object({ session: SessionRef }),
709
530
  card: "generic",
710
531
  phone: null,
711
532
  risk: "reversible",
@@ -716,7 +537,7 @@ var ACTIONS = [
716
537
  }),
717
538
  def({
718
539
  id: "session.archive",
719
- args: z11.object({ session: SessionRef }),
540
+ args: z7.object({ session: SessionRef }),
720
541
  card: "generic",
721
542
  phone: null,
722
543
  risk: "reversible",
@@ -727,7 +548,7 @@ var ACTIONS = [
727
548
  }),
728
549
  def({
729
550
  id: "session.restore",
730
- args: z11.object({ session: SessionRef }),
551
+ args: z7.object({ session: SessionRef }),
731
552
  card: "generic",
732
553
  phone: null,
733
554
  risk: "reversible",
@@ -738,7 +559,7 @@ var ACTIONS = [
738
559
  }),
739
560
  def({
740
561
  id: "session.resume",
741
- args: z11.object({ session: SessionRef }),
562
+ args: z7.object({ session: SessionRef }),
742
563
  card: "generic",
743
564
  phone: "act",
744
565
  risk: "reversible",
@@ -752,102 +573,22 @@ var ACTIONS = [
752
573
  card: "generic",
753
574
  phone: "act",
754
575
  max: 1,
755
- args: z11.object({ session: SessionRef, decision: z11.enum(["accept", "deny"]), note: z11.string().trim().min(1).max(NOTE_MAX).optional() }),
576
+ args: z7.object({ session: SessionRef, decision: z7.enum(["accept", "deny"]), note: z7.string().trim().min(1).max(NOTE_MAX).optional() }),
756
577
  risk: (a) => a.decision === "accept" ? "destructive" : "reversible",
757
578
  summary: "accepter ou refuser la demande en attente d'une session (permission, diff ou plan), note possible avec un refus ; jamais \xAB toujours \xBB",
758
579
  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",
759
580
  example: { session: "Nova", decision: "deny", note: "Pas sur main : cr\xE9e une branche" }
760
- }),
761
- def({
762
- id: "team.join",
763
- args: z11.object({ team: TeamRef, session: SessionRef, role: RoleIdSchema }),
764
- card: "generic",
765
- phone: null,
766
- risk: "reversible",
767
- max: 8,
768
- summary: "faire entrer une session dans une \xE9quipe en cours, \xE0 un r\xF4le de roles.md",
769
- when: "quand l'utilisateur demande d'ajouter une session existante \xE0 une \xE9quipe",
770
- example: { team: "team-3fa2", session: "Nova", role: "dev-back" }
771
- }),
772
- def({
773
- id: "team.leave",
774
- args: z11.object({ team: TeamRef, session: SessionRef }),
775
- card: "generic",
776
- phone: null,
777
- risk: "reversible",
778
- max: 8,
779
- summary: "faire sortir une session d'une \xE9quipe (la session continue)",
780
- when: "quand l'utilisateur demande de retirer une session d'une \xE9quipe",
781
- example: { team: "team-3fa2", session: "Nova" }
782
- }),
783
- def({
784
- id: "team.resume",
785
- args: z11.object({ team: TeamRef }),
786
- card: "generic",
787
- phone: "act",
788
- risk: "reversible",
789
- max: 1,
790
- summary: "reprendre une \xE9quipe au repos : chaque si\xE8ge reprend sa conversation",
791
- when: "quand l'utilisateur demande de reprendre ou relancer une \xE9quipe au repos",
792
- example: { team: "team-3fa2" }
793
- }),
794
- def({
795
- id: "team.dissolve",
796
- args: z11.object({ team: TeamRef }),
797
- card: "generic",
798
- phone: "act",
799
- risk: "destructive",
800
- max: 1,
801
- cli: "ledge team dissolve <\xE9quipe>",
802
- summary: "dissoudre une \xE9quipe : elle dispara\xEEt, ses sessions restent ; irr\xE9versible",
803
- when: "seulement quand l'utilisateur demande explicitement de dissoudre une \xE9quipe pr\xE9cise",
804
- example: { team: "team-3fa2" }
805
- }),
806
- def({
807
- id: "team.save",
808
- args: z11.object({ team: TeamRef, name: TeamNameSchema }),
809
- card: "generic",
810
- phone: null,
811
- risk: "reversible",
812
- max: 1,
813
- summary: "enregistrer une \xE9quipe en cours comme mod\xE8le (~/.ledge/teams/<nom>.yaml)",
814
- when: "quand l'utilisateur demande d'enregistrer ou de garder la composition d'une \xE9quipe",
815
- example: { team: "team-3fa2", name: "api" }
816
- }),
817
- def({
818
- id: "task.cancel",
819
- args: z11.object({ task: TaskRef, reason: z11.string().trim().min(1).max(500).optional() }),
820
- card: "generic",
821
- phone: "act",
822
- risk: "destructive",
823
- max: 8,
824
- summary: "annuler une t\xE2che ouverte ou prise ; la session qui la tient est pr\xE9venue ; irr\xE9versible",
825
- when: "seulement quand l'utilisateur demande explicitement d'annuler une t\xE2che de tasks.md",
826
- example: { task: "t91b0d4", reason: "Plus n\xE9cessaire" }
827
- }),
828
- def({
829
- id: "task.pass",
830
- args: z11.object({ task: TaskRef, to: SessionRef, note: z11.string().trim().min(1).max(TASK_TEXT_MAX).optional() }),
831
- card: "generic",
832
- phone: null,
833
- risk: "reversible",
834
- max: 8,
835
- summary: "passer une t\xE2che de tasks.md \xE0 une autre session (elle redevient ouverte)",
836
- when: "quand l'utilisateur demande de r\xE9assigner ou de passer une t\xE2che \xE0 une autre session",
837
- example: { task: "t91b0d4", to: "Bix" }
838
581
  })
839
582
  ];
840
583
  var byId = new Map(ACTIONS.map((a) => [a.id, a]));
841
- var actionById = (id) => byId.get(id);
584
+ var actionById = (id2) => byId.get(id2);
842
585
  var riskOf = (a, args) => typeof a.risk === "function" ? a.risk(args) : a.risk;
843
- var ActionBlockSchema = z11.object({ action: z11.string().trim().min(1).max(40), args: z11.record(z11.unknown()).default({}) });
586
+ var ActionBlockSchema = z7.object({ action: z7.string().trim().min(1).max(40), args: z7.record(z7.unknown()).default({}) });
844
587
  var LEGACY_FENCE_ACTION = {
845
588
  session: "session.launch",
846
589
  relay: "session.relay",
847
590
  close: "session.close",
848
- project: "project.propose",
849
- team: "team.launch",
850
- task: "task.give"
591
+ project: "project.propose"
851
592
  };
852
593
  function parseActionBlock(kind, raw) {
853
594
  let json;
@@ -856,21 +597,21 @@ function parseActionBlock(kind, raw) {
856
597
  } catch {
857
598
  return { error: "invalid JSON" };
858
599
  }
859
- let id;
600
+ let id2;
860
601
  let args;
861
602
  if (kind === "action") {
862
603
  const b = ActionBlockSchema.safeParse(json);
863
604
  if (!b.success) return { error: "not an {action, args} object" };
864
- id = b.data.action;
605
+ id2 = b.data.action;
865
606
  args = b.data.args;
866
607
  } else {
867
- id = LEGACY_FENCE_ACTION[kind];
608
+ id2 = LEGACY_FENCE_ACTION[kind];
868
609
  args = json;
869
610
  }
870
- const action = actionById(id);
871
- if (!action) return { error: `unknown action ${id}` };
611
+ const action = actionById(id2);
612
+ if (!action) return { error: `unknown action ${id2}` };
872
613
  const parsed = action.args.safeParse(args);
873
- if (!parsed.success) return { error: `invalid args for ${id}: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join("; ")}` };
614
+ if (!parsed.success) return { error: `invalid args for ${id2}: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join("; ")}` };
874
615
  return { action, args: parsed.data };
875
616
  }
876
617
  var NEVER_PROPOSED = [
@@ -882,123 +623,89 @@ var NEVER_PROPOSED = [
882
623
  "r\xE9pondre \xAB toujours \xBB \xE0 une demande, ou r\xE9pondre aux questions \xE0 choix d'OMP"
883
624
  ];
884
625
 
885
- // ../protocol/src/messages.ts
886
- import { z as z21 } from "zod";
626
+ // ../protocol/src/openrig.ts
627
+ import { z as z10 } from "zod";
887
628
 
888
629
  // ../protocol/src/chat.ts
889
- import { z as z13 } from "zod";
630
+ import { z as z9 } from "zod";
890
631
 
891
632
  // ../protocol/src/attachments.ts
892
- import { z as z12 } from "zod";
633
+ import { z as z8 } from "zod";
893
634
  var ATTACHMENT_ID = /^[0-9a-f]{64}$/;
894
- var AttachmentIdSchema = z12.string().regex(ATTACHMENT_ID);
635
+ var AttachmentIdSchema = z8.string().regex(ATTACHMENT_ID);
895
636
  var IMAGE_MIMES = ["image/webp", "image/jpeg", "image/png", "image/gif"];
896
- var ImageMimeSchema = z12.enum(IMAGE_MIMES);
637
+ var ImageMimeSchema = z8.enum(IMAGE_MIMES);
897
638
  var ATTACHMENT_MAX = 4e5;
898
639
  var ATTACHMENT_LOCAL_MAX = 2e6;
899
640
  var PREVIEW_MAX = 4e4;
900
641
  var ATTACHMENTS_MAX = 5;
901
642
  var b64Max = (bytes) => Math.ceil(bytes / 3) * 4;
902
- var AttachmentRefSchema = z12.object({
643
+ var AttachmentRefSchema = z8.object({
903
644
  id: AttachmentIdSchema,
904
645
  mime: ImageMimeSchema,
905
- width: z12.number().int().positive(),
906
- height: z12.number().int().positive(),
907
- bytes: z12.number().int().positive(),
908
- preview: z12.boolean()
646
+ width: z8.number().int().positive(),
647
+ height: z8.number().int().positive(),
648
+ bytes: z8.number().int().positive(),
649
+ preview: z8.boolean()
909
650
  });
910
- var ImageUploadSchema = z12.object({
651
+ var ImageUploadSchema = z8.object({
911
652
  mime: ImageMimeSchema,
912
- data: z12.string().min(1).max(b64Max(ATTACHMENT_LOCAL_MAX)),
913
- preview: z12.object({ mime: ImageMimeSchema, data: z12.string().min(1).max(b64Max(PREVIEW_MAX)) }).optional()
914
- });
915
- var AttachmentErrorReasonSchema = z12.enum(["too_large", "not_image", "unreadable", "not_found", "offline"]);
916
- var requestId = z12.string().min(1).max(100);
917
- var AttachmentPutSchema = z12.object({ type: z12.literal("attachment.put"), requestId, id: z12.string().optional(), image: ImageUploadSchema });
918
- var AttachmentGetSchema = z12.object({ type: z12.literal("attachment.get"), requestId, attachment: AttachmentIdSchema, preview: z12.boolean().optional(), id: z12.string().optional(), machine: z12.string().optional() });
919
- var AttachmentStoredSchema = z12.object({ type: z12.literal("attachment.stored"), requestId, ref: AttachmentRefSchema });
920
- var AttachmentDataSchema = z12.object({ type: z12.literal("attachment.data"), requestId, attachment: AttachmentIdSchema, mime: ImageMimeSchema, data: z12.string() });
921
- var AttachmentErrorSchema = z12.object({ type: z12.literal("attachment.error"), requestId, reason: AttachmentErrorReasonSchema });
653
+ data: z8.string().min(1).max(b64Max(ATTACHMENT_LOCAL_MAX)),
654
+ preview: z8.object({ mime: ImageMimeSchema, data: z8.string().min(1).max(b64Max(PREVIEW_MAX)) }).optional()
655
+ });
656
+ var AttachmentErrorReasonSchema = z8.enum(["too_large", "not_image", "unreadable", "not_found", "offline"]);
657
+ var requestId = z8.string().min(1).max(100);
658
+ var AttachmentPutSchema = z8.object({ type: z8.literal("attachment.put"), requestId, id: z8.string().optional(), image: ImageUploadSchema });
659
+ var AttachmentGetSchema = z8.object({ type: z8.literal("attachment.get"), requestId, attachment: AttachmentIdSchema, preview: z8.boolean().optional(), id: z8.string().optional(), machine: z8.string().optional() });
660
+ var AttachmentStoredSchema = z8.object({ type: z8.literal("attachment.stored"), requestId, ref: AttachmentRefSchema });
661
+ var AttachmentDataSchema = z8.object({ type: z8.literal("attachment.data"), requestId, attachment: AttachmentIdSchema, mime: ImageMimeSchema, data: z8.string() });
662
+ var AttachmentErrorSchema = z8.object({ type: z8.literal("attachment.error"), requestId, reason: AttachmentErrorReasonSchema });
922
663
 
923
664
  // ../protocol/src/chat.ts
924
- var ChatStepSchema = z13.object({
925
- kind: z13.enum(["sessions", "pane", "reply", "git", "repo"]),
926
- id: z13.string().optional(),
927
- name: z13.string().optional(),
928
- path: z13.string().optional()
929
- });
930
- var ChatProposalSchema = z13.object({
931
- cwd: z13.string().min(1),
932
- project: z13.string(),
933
- prompt: z13.string().optional(),
665
+ var ChatStepSchema = z9.object({
666
+ kind: z9.enum(["sessions", "pane", "reply", "git", "repo"]),
667
+ id: z9.string().optional(),
668
+ name: z9.string().optional(),
669
+ path: z9.string().optional()
670
+ });
671
+ var ChatProposalSchema = z9.object({
672
+ cwd: z9.string().min(1),
673
+ project: z9.string(),
674
+ prompt: z9.string().optional(),
934
675
  tool: ToolSchema,
935
676
  /** What Ledge proposed on the card's picker (spec 2026-10-26 action registry §4), the agent's own default when left out. */
936
677
  model: ModelIdSchema.optional(),
937
678
  effort: EffortSchema.optional(),
938
- launchedId: z13.string().optional(),
939
- error: z13.string().optional()
679
+ launchedId: z9.string().optional(),
680
+ error: z9.string().optional()
940
681
  });
941
682
  var MAX_PROPOSALS = 8;
942
- var ChatProposalsSchema = z13.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
943
- var ChatRelaySchema = z13.object({
944
- id: z13.string().min(1),
945
- name: z13.string(),
946
- tool: ToolSchema,
947
- text: z13.string().min(1),
948
- status: z13.enum(["queued", "sent"]).optional(),
949
- sentAt: z13.string().datetime().optional(),
950
- error: z13.string().optional()
951
- });
952
- var ChatCloseSchema = z13.object({
953
- id: z13.string().min(1),
954
- name: z13.string(),
683
+ var ChatProposalsSchema = z9.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
684
+ var ChatRelaySchema = z9.object({
685
+ id: z9.string().min(1),
686
+ name: z9.string(),
955
687
  tool: ToolSchema,
956
- closedAt: z13.string().datetime().optional(),
957
- how: z13.enum(["exit", "killed"]).optional(),
958
- error: z13.string().optional()
959
- });
960
- var ChatClosesSchema = z13.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
961
- var ChatProjectProposalSchema = z13.object({
962
- name: z13.string().min(1).max(80),
963
- description: z13.string().max(500).optional(),
964
- repos: z13.array(z13.object({ path: z13.string().min(1).max(4096), why: z13.string().min(1).max(200) })).min(1).max(50),
965
- addTo: z13.string().min(1).max(80).optional()
966
- });
967
- var ChatTeamSeatSchema = z13.object({
968
- role: z13.string().min(1),
969
- label: z13.string(),
688
+ text: z9.string().min(1),
689
+ status: z9.enum(["queued", "sent"]).optional(),
690
+ sentAt: z9.string().datetime().optional(),
691
+ error: z9.string().optional()
692
+ });
693
+ var ChatCloseSchema = z9.object({
694
+ id: z9.string().min(1),
695
+ name: z9.string(),
970
696
  tool: ToolSchema,
971
- model: z13.string().optional(),
972
- effort: z13.string().optional(),
973
- autonomy: AutonomySchema.optional(),
974
- name: z13.string().optional()
975
- });
976
- var ChatTeamProposalSchema = z13.object({
977
- cwd: z13.string().min(1),
978
- project: z13.string(),
979
- projectName: z13.string().optional(),
980
- name: z13.string().min(1),
981
- template: z13.string().optional(),
982
- seats: z13.array(ChatTeamSeatSchema).min(1).max(8),
983
- teamId: z13.string().optional(),
984
- launchedAt: z13.string().datetime().optional(),
985
- error: z13.string().optional(),
986
- savedPath: z13.string().optional()
987
- });
988
- var ChatTaskProposalSchema = z13.object({
989
- to: z13.string().min(1),
990
- toName: z13.string().optional(),
991
- team: z13.string().optional(),
992
- title: z13.string().min(1).max(200),
993
- text: z13.string().max(2e4).optional(),
994
- taskId: z13.string().optional(),
995
- ownerName: z13.string().optional(),
996
- state: z13.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
997
- givenAt: z13.string().datetime().optional(),
998
- error: z13.string().optional()
999
- });
1000
- var ChatTasksSchema = z13.array(ChatTaskProposalSchema).min(1).max(MAX_PROPOSALS);
1001
- var ActionVarsSchema = z13.record(z13.string().max(40), z13.string().max(300));
697
+ closedAt: z9.string().datetime().optional(),
698
+ how: z9.enum(["exit", "killed"]).optional(),
699
+ error: z9.string().optional()
700
+ });
701
+ var ChatClosesSchema = z9.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
702
+ var ChatProjectProposalSchema = z9.object({
703
+ name: z9.string().min(1).max(80),
704
+ description: z9.string().max(500).optional(),
705
+ repos: z9.array(z9.object({ path: z9.string().min(1).max(4096), why: z9.string().min(1).max(200) })).min(1).max(50),
706
+ addTo: z9.string().min(1).max(80).optional()
707
+ });
708
+ var ActionVarsSchema = z9.record(z9.string().max(40), z9.string().max(300));
1002
709
  var DETAIL_MAX = 4e3;
1003
710
  var ACTION_ERROR_CODES = [
1004
711
  "unknown",
@@ -1013,98 +720,381 @@ var ACTION_ERROR_CODES = [
1013
720
  "no_request",
1014
721
  "in_session",
1015
722
  "no_note",
1016
- "member",
1017
- "not_member",
1018
- "unknown_role",
1019
- "not_resting",
1020
- "dissolved",
1021
- "no_seat",
1022
- "closed",
1023
- "holder",
1024
723
  "changed",
1025
724
  "cut",
1026
725
  "failed"
1027
726
  ];
1028
- var ChatActionStateSchema = z13.enum(["running", "done", "denied", "failed", "refused"]);
1029
- var ChatActionSchema = z13.object({
1030
- action: z13.string().min(1).max(40),
1031
- args: z13.record(z13.unknown()),
1032
- summary: z13.string().min(1).max(300),
727
+ var ChatActionStateSchema = z9.enum(["running", "done", "denied", "failed", "refused"]);
728
+ var ChatActionSchema = z9.object({
729
+ action: z9.string().min(1).max(40),
730
+ args: z9.record(z9.unknown()),
731
+ summary: z9.string().min(1).max(300),
1033
732
  vars: ActionVarsSchema.optional(),
1034
- detail: z13.string().max(DETAIL_MAX).optional(),
1035
- detailCut: z13.literal(true).optional(),
733
+ detail: z9.string().max(DETAIL_MAX).optional(),
734
+ detailCut: z9.literal(true).optional(),
1036
735
  risk: ActionRiskSchema,
1037
- session: z13.string().optional(),
1038
- team: z13.string().optional(),
736
+ session: z9.string().optional(),
1039
737
  state: ChatActionStateSchema.optional(),
1040
- doneAt: z13.string().datetime().optional(),
1041
- error: z13.string().optional(),
1042
- code: z13.string().max(40).optional()
738
+ doneAt: z9.string().datetime().optional(),
739
+ error: z9.string().optional(),
740
+ code: z9.string().max(40).optional()
1043
741
  });
1044
- var ChatActionsSchema = z13.array(ChatActionSchema).min(1).max(MAX_PROPOSALS);
1045
- var ChatTurnSchema = z13.object({
1046
- machineId: z13.string().optional(),
1047
- machineName: z13.string().optional(),
1048
- choices: z13.array(z13.object({ id: z13.string(), label: z13.string() })).optional(),
1049
- qid: z13.string().min(1),
1050
- question: z13.string(),
1051
- askedAt: z13.string().datetime(),
1052
- steps: z13.array(ChatStepSchema),
1053
- answer: z13.string().optional(),
1054
- answeredAt: z13.string().datetime().optional(),
1055
- error: z13.string().optional(),
742
+ var ChatActionsSchema = z9.array(ChatActionSchema).min(1).max(MAX_PROPOSALS);
743
+ var ChatRigLaunchSchema = z9.object({
744
+ spec: z9.string().min(1).max(200).optional(),
745
+ goal: z9.string().min(1).max(8e3),
746
+ project: z9.string().min(1).optional(),
747
+ off: z9.string().min(1).max(40).optional(),
748
+ machine: z9.string().min(1).optional()
749
+ });
750
+ var ChatTurnSchema = z9.object({
751
+ machineId: z9.string().optional(),
752
+ machineName: z9.string().optional(),
753
+ choices: z9.array(z9.object({ id: z9.string(), label: z9.string() })).optional(),
754
+ qid: z9.string().min(1),
755
+ question: z9.string(),
756
+ askedAt: z9.string().datetime(),
757
+ steps: z9.array(ChatStepSchema),
758
+ answer: z9.string().optional(),
759
+ answeredAt: z9.string().datetime().optional(),
760
+ error: z9.string().optional(),
1056
761
  proposal: ChatProposalSchema.optional(),
1057
762
  proposals: ChatProposalsSchema.optional(),
1058
763
  relay: ChatRelaySchema.optional(),
1059
764
  closes: ChatClosesSchema.optional(),
1060
765
  projectProposal: ChatProjectProposalSchema.optional(),
1061
- team: ChatTeamProposalSchema.optional(),
1062
- tasks: ChatTasksSchema.optional(),
1063
766
  actions: ChatActionsSchema.optional(),
1064
- ignoredAt: z13.string().datetime().optional(),
1065
- attachments: z13.array(AttachmentRefSchema).optional()
767
+ rigLaunch: ChatRigLaunchSchema.optional(),
768
+ ignoredAt: z9.string().datetime().optional(),
769
+ attachments: z9.array(AttachmentRefSchema).optional()
1066
770
  });
1067
771
  var proposalRows = (t) => t?.proposals ?? (t?.proposal ? [t.proposal] : []);
1068
772
 
773
+ // ../protocol/src/openrig.ts
774
+ var MandateCategorySchema = z10.enum(["git", "tests", "context", "code", "design", "architecture", "critical"]);
775
+ var ALWAYS_USER = ["design", "architecture", "critical"];
776
+ var MANDATE_CATEGORY_LABEL = {
777
+ git: { fr: "Git et branches", en: "Git and branches" },
778
+ tests: { fr: "Tests et relectures", en: "Tests and reviews" },
779
+ context: { fr: "Contexte et rel\xE8ves", en: "Context and handovers" },
780
+ code: { fr: "Petits choix de code", en: "Small code choices" },
781
+ design: { fr: "Design", en: "Design" },
782
+ architecture: { fr: "Architecture", en: "Architecture" },
783
+ critical: { fr: "Critique", en: "Critical" }
784
+ };
785
+ var OpenRigStateSchema = z10.enum(["absent", "stopped", "starting", "running", "unsupported", "error", "unreachable"]);
786
+ var OpenRigStatusSchema = z10.object({
787
+ machine: z10.string(),
788
+ state: OpenRigStateSchema,
789
+ version: z10.string().optional(),
790
+ human: z10.enum(["registered", "missing", "failed"]).optional(),
791
+ detail: z10.string().optional(),
792
+ install: z10.string().optional()
793
+ });
794
+ var SeatStateSchema = z10.enum(["starting", "trust", "attention", "ready", "working", "idle", "stopped", "crashed", "unreachable"]);
795
+ var RigSeatSchema = z10.object({
796
+ ref: z10.string(),
797
+ // OpenRig session name: "dev-build@starter"
798
+ address: z10.string(),
799
+ // "@build"
800
+ name: z10.string(),
801
+ runtime: z10.string(),
802
+ model: z10.string().optional(),
803
+ state: SeatStateSchema,
804
+ contextPct: z10.number().min(0).max(100).optional(),
805
+ sessionId: z10.string().optional()
806
+ });
807
+ var RigRepoSchema = z10.object({
808
+ path: z10.string(),
809
+ name: z10.string(),
810
+ branch: z10.string().optional(),
811
+ remote: z10.string().optional(),
812
+ modified: z10.number().int().min(0),
813
+ by: z10.string().optional(),
814
+ how: z10.enum(["cloned", "opened", "created"]).optional()
815
+ });
816
+ var RigLifecycleSchema = z10.enum(["launching", "running", "partial", "stopped", "unreachable"]);
817
+ var RIG_NAME = /^[a-z0-9][a-z0-9-]{0,39}$/;
818
+ var RigLaunchInfoSchema = z10.object({
819
+ planId: z10.string(),
820
+ goal: z10.string(),
821
+ lead: z10.string(),
822
+ startedAt: z10.string().datetime(),
823
+ goalSentAt: z10.string().datetime().optional(),
824
+ lateNotice: z10.boolean().optional()
825
+ });
826
+ var RigSchema = z10.object({
827
+ id: z10.string(),
828
+ name: z10.string(),
829
+ machine: z10.string(),
830
+ spec: z10.string().optional(),
831
+ cwd: z10.string(),
832
+ projectId: z10.string().nullable(),
833
+ offProject: z10.boolean(),
834
+ branch: z10.string().optional(),
835
+ lifecycle: RigLifecycleSchema,
836
+ kernel: z10.boolean(),
837
+ seats: z10.array(RigSeatSchema),
838
+ repos: z10.array(RigRepoSchema),
839
+ launch: RigLaunchInfoSchema.optional()
840
+ });
841
+ var EscalationQuestionSchema = z10.object({
842
+ id: z10.string(),
843
+ question: z10.string(),
844
+ options: z10.array(z10.object({ id: z10.string(), label: z10.string(), recommended: z10.boolean().optional() })).min(2).max(4)
845
+ });
846
+ var EscalationStateSchema = z10.enum(["pending", "grace", "answered", "closed"]);
847
+ var EscalationSchema = z10.object({
848
+ id: z10.string(),
849
+ // OpenRig qitemId, or "ask-<id>" for `ledge ask`
850
+ source: z10.enum(["rig", "operator", "ask"]),
851
+ machine: z10.string(),
852
+ rigId: z10.string().optional(),
853
+ from: z10.string(),
854
+ // seat ref, or the Ledge session name for `ask`
855
+ intent: z10.enum(["decision", "update"]),
856
+ summary: z10.string(),
857
+ detail: z10.string().optional(),
858
+ evidenceRef: z10.string().optional(),
859
+ questions: z10.array(EscalationQuestionSchema).max(4),
860
+ replyTo: z10.string().optional(),
861
+ createdAt: z10.string().datetime(),
862
+ state: EscalationStateSchema,
863
+ graceUntil: z10.string().datetime().optional(),
864
+ correcting: z10.string().optional(),
865
+ // id of the decision being corrected (Revenir dessus)
866
+ category: MandateCategorySchema.optional(),
867
+ advice: z10.string().optional(),
868
+ answer: z10.object({
869
+ by: z10.enum(["ledge", "user"]),
870
+ answers: z10.record(z10.string()),
871
+ at: z10.string().datetime(),
872
+ rule: z10.string().optional(),
873
+ delivery: z10.enum(["queued", "sent"]).optional()
874
+ }).optional()
875
+ });
876
+ var ExchangeSchema = z10.object({
877
+ id: z10.string(),
878
+ machine: z10.string(),
879
+ rigId: z10.string(),
880
+ from: z10.string(),
881
+ to: z10.string(),
882
+ kind: z10.enum(["message", "task", "handoff", "ledge"]),
883
+ text: z10.string(),
884
+ at: z10.string().datetime()
885
+ });
886
+ var EXCHANGES_MAX = 200;
887
+ var EXCHANGE_GROUP_MS = 6e5;
888
+ var THREAD_PAGE = 50;
889
+ var LedgeDecisionSchema = z10.object({
890
+ id: z10.string(),
891
+ escalationId: z10.string(),
892
+ by: z10.enum(["ledge", "user"]),
893
+ category: MandateCategorySchema.optional(),
894
+ // absent: a user answer on an uncategorised escalation (never guessed)
895
+ summary: z10.string(),
896
+ answers: z10.record(z10.string()),
897
+ rule: z10.string().optional(),
898
+ from: z10.string().optional(),
899
+ rigId: z10.string().optional(),
900
+ // the seat and rig the decision was for
901
+ why: z10.string().optional(),
902
+ at: z10.string().datetime(),
903
+ revisitedBy: z10.string().optional()
904
+ });
905
+ var LedgeRuleSchema = z10.object({
906
+ id: z10.string(),
907
+ text: z10.string(),
908
+ category: MandateCategorySchema,
909
+ since: z10.string(),
910
+ from: z10.string(),
911
+ appliedCount: z10.number().int().min(0),
912
+ status: z10.enum(["active", "proposed"])
913
+ });
914
+ var LedgeThreadItemSchema = z10.discriminatedUnion("kind", [
915
+ z10.object({
916
+ kind: z10.literal("user"),
917
+ id: z10.string(),
918
+ at: z10.string().datetime(),
919
+ text: z10.string(),
920
+ to: z10.string().optional(),
921
+ // to = "@build"
922
+ qid: z10.string().optional()
923
+ }),
924
+ z10.object({
925
+ kind: z10.literal("ledge"),
926
+ id: z10.string(),
927
+ at: z10.string().datetime(),
928
+ text: z10.string(),
929
+ qid: z10.string().optional(),
930
+ pending: z10.literal(true).optional()
931
+ }),
932
+ z10.object({ kind: z10.literal("escalation"), id: z10.string(), at: z10.string().datetime(), escalationId: z10.string() }),
933
+ z10.object({ kind: z10.literal("decided"), id: z10.string(), at: z10.string().datetime(), decisionId: z10.string() }),
934
+ z10.object({
935
+ kind: z10.literal("exchanges"),
936
+ id: z10.string(),
937
+ at: z10.string().datetime(),
938
+ machine: z10.string(),
939
+ rigId: z10.string(),
940
+ from: z10.string().datetime(),
941
+ to: z10.string().datetime(),
942
+ exchangeIds: z10.array(z10.string())
943
+ }),
944
+ z10.object({ kind: z10.literal("card"), id: z10.string(), at: z10.string().datetime(), action: ChatActionSchema })
945
+ // existing action card
946
+ ]);
947
+ var BrainStatusSchema = z10.object({
948
+ state: z10.enum(["off", "starting", "on", "down"]),
949
+ tool: ToolSchema,
950
+ contextPct: z10.number().optional(),
951
+ sessionId: z10.string().optional(),
952
+ enabled: z10.boolean().nullable().optional(),
953
+ // null: the user was never asked
954
+ restarts: z10.number().int().min(0).optional(),
955
+ reason: z10.string().optional()
956
+ });
957
+ var RigLaunchWhereSchema = z10.discriminatedUnion("kind", [
958
+ z10.object({ kind: z10.literal("project"), projectId: z10.string(), repo: z10.string(), branch: z10.string(), copy: z10.boolean() }),
959
+ z10.object({ kind: z10.literal("off-project"), name: z10.string() })
960
+ ]);
961
+ var RigPlanSchema = z10.object({
962
+ id: z10.string(),
963
+ machine: z10.string(),
964
+ spec: z10.string(),
965
+ goal: z10.string(),
966
+ where: RigLaunchWhereSchema,
967
+ cwd: z10.string(),
968
+ seats: z10.array(z10.object({ address: z10.string(), runtime: z10.string(), model: z10.string().optional(), autonomy: z10.string() })),
969
+ writes: z10.array(z10.string()),
970
+ command: z10.string(),
971
+ required: z10.boolean()
972
+ });
973
+ var RigVerbSchema = z10.enum(["up", "down", "restore", "restart", "stop", "handover"]);
974
+ var RIG_RESULT_CODES = ["rig_managed", "already_answered", "openrig_down", "unsupported", "mandate", "unreachable", "failed", "invalid"];
975
+ var RigResultCodeSchema = z10.enum(RIG_RESULT_CODES);
976
+ var BRAIN_OPS = ["say", "decide", "ask", "rig", "send", "propose", "remember", "who", "rigs", "escalations", "rules", "handover"];
977
+ var BRAIN_RESULT_CODES = ["not_brain", "identity", "mandate", "grace", "invalid", "refused", "already_answered", "failed", "unreachable", "openrig_down"];
978
+ var BrainResultCodeSchema = z10.enum(BRAIN_RESULT_CODES);
979
+ var BRAIN_STOPPED_TEXT = "Ledge est arr\xEAt\xE9 \xB7 D\xE9marrer";
980
+ var OpenRigStatusMessageSchema = z10.object({ type: z10.literal("openrig.status"), status: OpenRigStatusSchema });
981
+ var RigSnapshotSchema = z10.object({ type: z10.literal("rig.snapshot"), machine: z10.string(), rigs: z10.array(RigSchema) });
982
+ var RigUpdatedSchema = z10.object({ type: z10.literal("rig.updated"), rig: RigSchema });
983
+ var RigRemovedSchema = z10.object({ type: z10.literal("rig.removed"), machine: z10.string(), rigId: z10.string() });
984
+ var RigExchangeSchema = z10.object({ type: z10.literal("rig.exchange"), exchange: ExchangeSchema });
985
+ var RigExchangesSchema = z10.object({ type: z10.literal("rig.exchanges"), machine: z10.string(), rigId: z10.string(), items: z10.array(ExchangeSchema) });
986
+ var EscalationUpdatedSchema = z10.object({ type: z10.literal("escalation.updated"), escalation: EscalationSchema });
987
+ var EscalationRemovedSchema = z10.object({ type: z10.literal("escalation.removed"), id: z10.string() });
988
+ var LedgeThreadItemMessageSchema = z10.object({ type: z10.literal("ledge.thread.item"), item: LedgeThreadItemSchema });
989
+ var LedgeThreadHistorySchema = z10.object({ type: z10.literal("ledge.thread.history"), items: z10.array(LedgeThreadItemSchema), before: z10.string().optional() });
990
+ var DecisionsMessageSchema = z10.object({ type: z10.literal("decisions"), items: z10.array(LedgeDecisionSchema) });
991
+ var RulesMessageSchema = z10.object({ type: z10.literal("rules"), items: z10.array(LedgeRuleSchema) });
992
+ var MandateMessageSchema = z10.object({ type: z10.literal("mandate"), text: z10.string(), path: z10.string() });
993
+ var BrainStatusMessageSchema = z10.object({ type: z10.literal("brain.status"), status: BrainStatusSchema });
994
+ var RigPlanMessageSchema = z10.object({ type: z10.literal("rig.plan"), rid: z10.string(), plan: RigPlanSchema });
995
+ var BrainResultSchema = z10.object({ type: z10.literal("brain.result"), rid: z10.string(), ok: z10.boolean(), line: z10.string(), code: BrainResultCodeSchema.optional() });
996
+ var BrainDelegateClaimSchema = z10.object({ type: z10.literal("brain.delegate.claim"), rid: z10.string().min(1).max(100), id: z10.string().min(1).max(200) });
997
+ var BrainDelegateDecideSchema = z10.object({ type: z10.literal("brain.delegate.decide"), rid: z10.string().min(1).max(100), id: z10.string().min(1).max(200), token: z10.string().min(1).max(100), answers: z10.record(z10.string().max(8e3)), category: MandateCategorySchema });
998
+ var BrainDelegateQuerySchema = z10.object({ type: z10.literal("brain.delegate.query"), rid: z10.string().min(1).max(100), operation: z10.string().min(1).max(100), token: z10.string().min(1).max(100) });
999
+ var BrainDelegateReleaseSchema = z10.object({ type: z10.literal("brain.delegate.release"), rid: z10.string().min(1).max(100), id: z10.string().min(1).max(200), token: z10.string().min(1).max(100), advice: z10.string().max(8e3).optional(), category: MandateCategorySchema.optional() });
1000
+ var BrainDelegateResultSchema = z10.object({ type: z10.literal("brain.delegate.result"), rid: z10.string().min(1).max(100), ok: z10.boolean(), token: z10.string().optional(), graceUntil: z10.string().datetime().optional(), status: z10.enum(["applied"]).optional(), code: z10.string().optional(), reason: z10.string().optional() });
1001
+ var RigResultSchema = z10.object({ type: z10.literal("rig.result"), rid: z10.string(), ok: z10.boolean(), confirmed: z10.literal(true).optional(), code: RigResultCodeSchema.optional(), reason: z10.string().optional() });
1002
+ var RigActSchema = z10.object({ type: z10.literal("rig.act"), rid: z10.string(), machine: z10.string(), rigId: z10.string().optional(), seatRef: z10.string().optional(), verb: RigVerbSchema });
1003
+ var RigPrepareSchema = z10.object({ type: z10.literal("rig.prepare"), rid: z10.string(), machine: z10.string(), spec: z10.string(), goal: z10.string().max(8e3), where: RigLaunchWhereSchema });
1004
+ var RigLaunchSchema = z10.object({ type: z10.literal("rig.launch"), rid: z10.string(), planId: z10.string() });
1005
+ var RigAdoptSchema = z10.object({ type: z10.literal("rig.adopt"), rid: z10.string(), machine: z10.string(), rigId: z10.string(), projectId: z10.string() });
1006
+ var RigSendSchema = z10.object({ type: z10.literal("rig.send"), rid: z10.string(), machine: z10.string(), seatRef: z10.string(), text: z10.string() });
1007
+ var RigSpecSeatSchema = z10.object({
1008
+ address: z10.string(),
1009
+ member: z10.string(),
1010
+ pod: z10.string(),
1011
+ runtime: z10.string(),
1012
+ model: z10.string().optional(),
1013
+ effort: z10.string().optional(),
1014
+ profile: z10.string().optional()
1015
+ });
1016
+ var RigSpecInfoSchema = z10.object({
1017
+ id: z10.string(),
1018
+ name: z10.string(),
1019
+ summary: z10.string().optional(),
1020
+ source: z10.enum(["builtin", "library", "registry"]),
1021
+ path: z10.string().optional(),
1022
+ seats: z10.array(RigSpecSeatSchema)
1023
+ });
1024
+ var RigSpecsRequestSchema = z10.object({ type: z10.literal("rig.specs.request"), requestId: z10.string(), machineId: z10.string().optional() });
1025
+ var RigSpecsSchema = z10.object({
1026
+ type: z10.literal("rig.specs"),
1027
+ requestId: z10.string(),
1028
+ machine: z10.string(),
1029
+ specs: z10.array(RigSpecInfoSchema),
1030
+ error: z10.enum(["openrig_down", "unsupported", "failed"]).optional()
1031
+ });
1032
+ var RigExchangesRequestSchema = z10.object({ type: z10.literal("rig.exchanges.request"), machine: z10.string(), rigId: z10.string() });
1033
+ var EscalationAnswerSchema = z10.object({ type: z10.literal("escalation.answer"), rid: z10.string(), id: z10.string(), answers: z10.record(z10.string()), by: z10.literal("user") });
1034
+ var LedgeSaySchema = z10.object({ type: z10.literal("ledge.say"), rid: z10.string(), text: z10.string(), about: z10.string().optional() });
1035
+ var LedgeThreadRequestSchema = z10.object({ type: z10.literal("ledge.thread.request"), before: z10.string().optional() });
1036
+ var MandateUpdateSchema = z10.object({ type: z10.literal("mandate.update"), rid: z10.string(), text: z10.string() });
1037
+ var RuleSetSchema = z10.object({ type: z10.literal("rule.set"), rid: z10.string(), id: z10.string(), status: z10.enum(["active", "rejected"]) });
1038
+ var DecisionRevisitSchema = z10.object({ type: z10.literal("decision.revisit"), rid: z10.string(), id: z10.string(), text: z10.string().optional() });
1039
+ var CategoryEscalateSchema = z10.object({ type: z10.literal("category.escalate"), rid: z10.string(), category: MandateCategorySchema });
1040
+ var BrainSetSchema = z10.object({
1041
+ type: z10.literal("brain.set"),
1042
+ rid: z10.string(),
1043
+ enabled: z10.boolean(),
1044
+ tool: ToolSchema.optional(),
1045
+ first: z10.literal(true).optional()
1046
+ });
1047
+ var BrainCmdSchema = z10.object({
1048
+ type: z10.literal("brain.cmd"),
1049
+ rid: z10.string(),
1050
+ by: z10.string(),
1051
+ key: z10.string(),
1052
+ op: z10.enum(BRAIN_OPS),
1053
+ args: z10.array(z10.string()).max(40)
1054
+ });
1055
+
1056
+ // ../protocol/src/messages.ts
1057
+ import { z as z21 } from "zod";
1058
+
1069
1059
  // ../protocol/src/repos.ts
1070
- import { z as z14 } from "zod";
1071
- var ReposInspectSchema = z14.object({
1072
- type: z14.literal("repos.inspect"),
1073
- requestId: z14.string().min(1).max(100),
1074
- paths: z14.array(z14.string().min(1)).min(1).max(50),
1075
- machineId: z14.string().optional()
1076
- });
1077
- var RepoInfoSchema = z14.object({ path: z14.string(), exists: z14.boolean(), branch: z14.string().nullable() });
1078
- var ReposInfoSchema = z14.object({ type: z14.literal("repos.info"), requestId: z14.string(), repos: z14.array(RepoInfoSchema) });
1060
+ import { z as z11 } from "zod";
1061
+ var ReposInspectSchema = z11.object({
1062
+ type: z11.literal("repos.inspect"),
1063
+ requestId: z11.string().min(1).max(100),
1064
+ paths: z11.array(z11.string().min(1)).min(1).max(50),
1065
+ machineId: z11.string().optional()
1066
+ });
1067
+ var RepoInfoSchema = z11.object({ path: z11.string(), exists: z11.boolean(), branch: z11.string().nullable() });
1068
+ var ReposInfoSchema = z11.object({ type: z11.literal("repos.info"), requestId: z11.string(), repos: z11.array(RepoInfoSchema) });
1079
1069
 
1080
1070
  // ../protocol/src/scan.ts
1081
- import { z as z15 } from "zod";
1082
- var ReposScanSchema = z15.object({ type: z15.literal("repos.scan"), requestId: z15.string().min(1).max(100), machineId: z15.string().optional() });
1083
- var FoundRepoSchema = z15.object({ path: z15.string(), branch: z15.string().nullable(), remote: z15.string().optional(), manifest: z15.string().max(200).optional() });
1084
- var ReposFoundSchema = z15.object({ type: z15.literal("repos.found"), requestId: z15.string(), repos: z15.array(FoundRepoSchema).max(500), truncated: z15.boolean(), error: z15.string().optional() });
1071
+ import { z as z12 } from "zod";
1072
+ var ReposScanSchema = z12.object({ type: z12.literal("repos.scan"), requestId: z12.string().min(1).max(100), machineId: z12.string().optional() });
1073
+ var FoundRepoSchema = z12.object({ path: z12.string(), branch: z12.string().nullable(), remote: z12.string().optional(), manifest: z12.string().max(200).optional() });
1074
+ 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() });
1085
1075
 
1086
1076
  // ../protocol/src/documents.ts
1087
- import { z as z16 } from "zod";
1077
+ import { z as z13 } from "zod";
1088
1078
  var DOCUMENT_MAX_BYTES = 1048576;
1089
- var path = z16.string().min(1).max(4096);
1090
- var requestId2 = z16.string().min(1);
1091
- var DocumentReadSchema = z16.object({
1092
- type: z16.literal("document.read"),
1079
+ var path = z13.string().min(1).max(4096);
1080
+ var requestId2 = z13.string().min(1);
1081
+ var DocumentReadSchema = z13.object({
1082
+ type: z13.literal("document.read"),
1093
1083
  requestId: requestId2,
1094
1084
  path,
1095
- machineId: z16.string().optional(),
1096
- sessionId: z16.string().min(1).optional(),
1085
+ machineId: z13.string().optional(),
1086
+ sessionId: z13.string().min(1).optional(),
1097
1087
  basePath: path.optional()
1098
1088
  });
1099
- var DocumentContentSchema = z16.object({
1100
- type: z16.literal("document.content"),
1089
+ var DocumentContentSchema = z13.object({
1090
+ type: z13.literal("document.content"),
1101
1091
  requestId: requestId2,
1102
1092
  path,
1103
- text: z16.string(),
1104
- bytes: z16.number().int().nonnegative(),
1105
- modifiedAt: z16.string().datetime()
1093
+ text: z13.string(),
1094
+ bytes: z13.number().int().nonnegative(),
1095
+ modifiedAt: z13.string().datetime()
1106
1096
  });
1107
- var DocumentErrorCodeSchema = z16.enum([
1097
+ var DocumentErrorCodeSchema = z13.enum([
1108
1098
  "not_found",
1109
1099
  "not_file",
1110
1100
  "unsupported_path",
@@ -1116,62 +1106,155 @@ var DocumentErrorCodeSchema = z16.enum([
1116
1106
  "session_missing",
1117
1107
  "read_failed"
1118
1108
  ]);
1119
- var DocumentErrorSchema = z16.object({
1120
- type: z16.literal("document.error"),
1109
+ var DocumentErrorSchema = z13.object({
1110
+ type: z13.literal("document.error"),
1121
1111
  requestId: requestId2,
1122
1112
  path,
1123
1113
  code: DocumentErrorCodeSchema
1124
1114
  });
1125
1115
 
1126
1116
  // ../protocol/src/routing.ts
1127
- import { z as z17 } from "zod";
1128
- var MachineInventorySchema = z17.object({
1129
- id: z17.string().min(1).max(80),
1130
- label: z17.string().min(1).max(120),
1131
- online: z17.boolean(),
1132
- local: z17.boolean(),
1133
- sessions: z17.array(z17.object({ id: z17.string(), name: z17.string(), project: z17.string(), cwd: z17.string(), topic: z17.string().optional(), state: z17.string(), readOnly: z17.boolean() })).max(500),
1134
- folders: z17.array(z17.object({ project: z17.string(), cwd: z17.string() })).max(100)
1135
- });
1136
- var RoutingContextSchema = z17.object({
1137
- machines: z17.array(MachineInventorySchema).min(1).max(20),
1138
- history: z17.array(z17.object({ question: z17.string().max(2e4), answer: z17.string().max(2e4).optional(), machineId: z17.string().optional() })).max(50)
1139
- });
1140
- var RouteResultSchema = z17.object({
1117
+ import { z as z14 } from "zod";
1118
+ var MachineInventorySchema = z14.object({
1119
+ id: z14.string().min(1).max(80),
1120
+ label: z14.string().min(1).max(120),
1121
+ online: z14.boolean(),
1122
+ local: z14.boolean(),
1123
+ 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),
1124
+ folders: z14.array(z14.object({ project: z14.string(), cwd: z14.string() })).max(100)
1125
+ });
1126
+ var RoutingContextSchema = z14.object({
1127
+ machines: z14.array(MachineInventorySchema).min(1).max(20),
1128
+ history: z14.array(z14.object({ question: z14.string().max(2e4), answer: z14.string().max(2e4).optional(), machineId: z14.string().optional() })).max(50)
1129
+ });
1130
+ var RouteResultSchema = z14.object({
1141
1131
  /** Null means an answer or a clarification, never an implicit local destination. */
1142
- machineId: z17.string().nullable(),
1143
- text: z17.string().min(1).max(2e4),
1144
- choices: z17.array(z17.string()).max(20).optional()
1132
+ machineId: z14.string().nullable(),
1133
+ text: z14.string().min(1).max(2e4),
1134
+ choices: z14.array(z14.string()).max(20).optional()
1135
+ });
1136
+
1137
+ // ../protocol/src/ssh.ts
1138
+ import { z as z15 } from "zod";
1139
+ var SshAliasSchema = z15.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
1140
+
1141
+ // ../protocol/src/machines.ts
1142
+ import { z as z16 } from "zod";
1143
+ var MachineIdSchema = z16.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
1144
+ var MachineSchema = z16.object({
1145
+ id: MachineIdSchema,
1146
+ label: z16.string().trim().min(1).max(80),
1147
+ ssh: SshAliasSchema.optional()
1148
+ });
1149
+ var MachinesSchema = z16.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
1150
+ if (list[0]?.id !== "local" || list[0].ssh !== void 0) ctx.addIssue({ code: "custom", message: "the first machine is local, without ssh", path: [0] });
1151
+ const ids = /* @__PURE__ */ new Set(), aliases = /* @__PURE__ */ new Set();
1152
+ list.forEach((m, i) => {
1153
+ if (i > 0 && !m.ssh) ctx.addIssue({ code: "custom", message: "a remote machine needs an ssh alias", path: [i, "ssh"] });
1154
+ if (ids.has(m.id)) ctx.addIssue({ code: "custom", message: `duplicate id ${m.id}`, path: [i, "id"] });
1155
+ ids.add(m.id);
1156
+ if (m.ssh) {
1157
+ if (aliases.has(m.ssh)) ctx.addIssue({ code: "custom", message: `duplicate ssh alias ${m.ssh}`, path: [i, "ssh"] });
1158
+ aliases.add(m.ssh);
1159
+ }
1160
+ });
1145
1161
  });
1146
1162
 
1147
1163
  // ../protocol/src/island.ts
1148
- import { z as z18 } from "zod";
1149
- var IslandScaleSchema = z18.union([z18.literal("auto"), z18.literal(1), z18.literal(1.25), z18.literal(1.5), z18.literal(2)]);
1150
- var IslandPrefsSchema = z18.object({
1164
+ import { z as z17 } from "zod";
1165
+ var IslandScaleSchema = z17.union([z17.literal("auto"), z17.literal(1), z17.literal(1.25), z17.literal(1.5), z17.literal(2)]);
1166
+ var IslandPrefsSchema = z17.object({
1151
1167
  /** Shown at the top of the screen; hidden, Desktop still notifies. */
1152
- visible: z18.boolean(),
1168
+ visible: z17.boolean(),
1153
1169
  /** `primary` (whichever it is after a reconfiguration), or a monitor's id as the shell lists it (`list_monitors`: the OS
1154
1170
  * name, `#index` without one). `second`, written by older fronts, is the first monitor that is not the primary. */
1155
- screen: z18.string().trim().min(1).max(200),
1156
- position: z18.enum(["center", "left", "right"]),
1171
+ screen: z17.string().trim().min(1).max(200),
1172
+ position: z17.enum(["center", "left", "right"]),
1157
1173
  /** A strip under a maximised window. */
1158
- discreet: z18.boolean(),
1174
+ discreet: z17.boolean(),
1159
1175
  islandScale: IslandScaleSchema,
1160
1176
  panelScale: IslandScaleSchema,
1161
1177
  /** What the island shows besides asks and questions, which it always shows. */
1162
- shows: z18.object({
1178
+ shows: z17.object({
1163
1179
  /** Allow, deny or answer in a line from the island; off, its cards only say « Open in Ledge ». */
1164
- quick: z18.boolean(),
1180
+ quick: z17.boolean(),
1165
1181
  /** The 6 s bubble when an agent finishes. */
1166
- finished: z18.boolean(),
1182
+ finished: z17.boolean(),
1167
1183
  /** An agent's sentence in a bubble. */
1168
- say: z18.boolean(),
1184
+ say: z17.boolean(),
1169
1185
  /** The sub-agents next to their main agent. */
1170
- helpers: z18.boolean()
1186
+ helpers: z17.boolean()
1171
1187
  }),
1172
1188
  /** The global shortcut that opens the island, as shown (`Ctrl Alt L`). */
1173
- shortcut: z18.string().trim().min(1).max(40)
1189
+ shortcut: z17.string().trim().min(1).max(40)
1190
+ });
1191
+
1192
+ // ../protocol/src/projects.ts
1193
+ import { z as z18 } from "zod";
1194
+ var PROJECT_COLORS = { blue: "#3b82f6", green: "#10b981", amber: "#f59e0b", violet: "#8b5cf6", teal: "#14b8a6", pink: "#ec4899", red: "#ef4444", slate: "#64748b" };
1195
+ var ProjectColorSchema = z18.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
1196
+ var ProjectIdSchema = z18.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
1197
+ function normalizePath(raw) {
1198
+ const p = raw.trim().replace(/\/+/g, "/");
1199
+ return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
1200
+ }
1201
+ var PathSchema = z18.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
1202
+ var RepoSchema = z18.object({ machine: MachineIdSchema, path: PathSchema });
1203
+ var ProjectSchema = z18.object({
1204
+ id: ProjectIdSchema,
1205
+ name: z18.string().trim().min(1).max(80),
1206
+ color: ProjectColorSchema,
1207
+ description: z18.string().max(500).default(""),
1208
+ repos: z18.array(RepoSchema).max(50),
1209
+ /** Ids of the OpenRig rigs filed in this project (a rig lives in one project at most). */
1210
+ rigs: z18.array(z18.string().min(1).max(200)).max(200).default([])
1211
+ });
1212
+ var RuleSchema = z18.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
1213
+ function projectsProblems(p) {
1214
+ const out = [];
1215
+ const ids = /* @__PURE__ */ new Set(), names = /* @__PURE__ */ new Set(), repos = /* @__PURE__ */ new Set(), rigIds = /* @__PURE__ */ new Set(), rules = /* @__PURE__ */ new Set();
1216
+ p.projects.forEach((project, i) => {
1217
+ if (ids.has(project.id)) out.push({ code: "duplicate_id", path: ["projects", i, "id"] });
1218
+ ids.add(project.id);
1219
+ const name = project.name.trim().toLocaleLowerCase();
1220
+ if (names.has(name)) out.push({ code: "duplicate_name", path: ["projects", i, "name"] });
1221
+ names.add(name);
1222
+ project.repos.forEach((repo, j) => {
1223
+ const key2 = `${repo.machine}
1224
+ ${repo.path}`;
1225
+ if (repos.has(key2)) out.push({ code: "duplicate_repo", path: ["projects", i, "repos", j] });
1226
+ repos.add(key2);
1227
+ });
1228
+ project.rigs.forEach((rig, j) => {
1229
+ if (rigIds.has(rig)) out.push({ code: "duplicate_rig", path: ["projects", i, "rigs", j] });
1230
+ rigIds.add(rig);
1231
+ });
1232
+ });
1233
+ p.rules.forEach((rule, i) => {
1234
+ const key2 = `${rule.machine}
1235
+ ${rule.path}`;
1236
+ if (rules.has(key2)) out.push({ code: "duplicate_rule", path: ["rules", i] });
1237
+ rules.add(key2);
1238
+ if (!ids.has(rule.project)) out.push({ code: "unknown_project", path: ["rules", i, "project"] });
1239
+ });
1240
+ return out;
1241
+ }
1242
+ var ProjectsSchema = z18.object({ projects: z18.array(ProjectSchema).max(100), rules: z18.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
1243
+ for (const problem of projectsProblems(p)) ctx.addIssue({ code: "custom", message: problem.code, path: problem.path });
1174
1244
  });
1245
+ var DEFAULT_PROJECTS = { projects: [], rules: [] };
1246
+ var under = (cwd, path2) => path2 === "/" || cwd === path2 || cwd.startsWith(`${path2}/`);
1247
+ function projectOf(session, state) {
1248
+ const machine = session.machineId ?? "local";
1249
+ const cwd = normalizePath(session.cwd);
1250
+ const known2 = new Set(state.projects.map((p) => p.id));
1251
+ const candidates = [
1252
+ ...state.projects.flatMap((p) => p.repos.filter((r) => r.machine === machine).map((r) => ({ project: p.id, path: r.path, rule: false }))),
1253
+ ...state.rules.filter((r) => r.machine === machine && known2.has(r.project)).map((r) => ({ project: r.project, path: r.path, rule: true }))
1254
+ ].filter((c) => under(cwd, c.path));
1255
+ candidates.sort((x, y) => y.path.length - x.path.length || Number(y.rule) - Number(x.rule));
1256
+ return candidates[0]?.project ?? null;
1257
+ }
1175
1258
 
1176
1259
  // ../protocol/src/thread.ts
1177
1260
  import { z as z19 } from "zod";
@@ -1200,13 +1283,6 @@ var ThreadItemSchema = z19.discriminatedUnion("kind", [
1200
1283
  kind: z19.literal("notice"),
1201
1284
  ...base,
1202
1285
  notice: z19.enum(["idle", "exited", "unavailable", "expired", "delivery", "other"]),
1203
- /** An `other` notice from Ledge: `task`, the task a `<ledge-task>` is about; `team`, the team a `<ledge-notice team>` seated the
1204
- * session in (roles spec §3.3). An older front, which knows neither, shows it as any other notice. */
1205
- task: z19.string().optional(),
1206
- team: z19.string().optional(),
1207
- /** A task this session handed over (`outgoing`), and how (issue #92). */
1208
- taskOp: z19.enum(TRANSMISSION_OPS).optional(),
1209
- outgoing: z19.literal(true).optional(),
1210
1286
  name: z19.string().optional(),
1211
1287
  peer: z19.string().optional(),
1212
1288
  time: z19.string().optional(),
@@ -1269,7 +1345,7 @@ var MobileStateSchema = z20.object({
1269
1345
 
1270
1346
  // ../protocol/src/messages.ts
1271
1347
  var PROTOCOL_PORT = 47831;
1272
- 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"]);
1348
+ 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", "rig_managed"]);
1273
1349
  var SessionEventSchema = z21.object({
1274
1350
  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"]),
1275
1351
  at: z21.string().datetime(),
@@ -1338,11 +1414,19 @@ var DaemonInfoSchema = z21.object({
1338
1414
  /** Its home folder, so a client shows the folders under it as `~` (absent from older daemons). */
1339
1415
  home: z21.string().optional(),
1340
1416
  mobile: z21.boolean().optional(),
1341
- /** Knows roles, teams and tasks given by Ledge (spec 2026-10-25 roles). */
1342
- teams: z21.boolean().optional()
1417
+ /** Knows `ask.create` / `ask.answer`: a session's decision for the user (`ledge ask`). */
1418
+ asks: z21.boolean().optional(),
1419
+ /** Reads OpenRig (rigs, escalations, exchanges) through its local bridge. */
1420
+ openrig: z21.boolean().optional(),
1421
+ /** Takes color-preview subscriptions. */
1422
+ colorPreview: z21.boolean().optional(),
1423
+ /** Relays rigs of remote machines. */
1424
+ openrigFleet: z21.boolean().optional(),
1425
+ brainDelegate: z21.boolean().optional()
1343
1426
  });
1344
1427
  var UpdateFailureSchema = z21.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
1345
1428
  var ServerMessageSchema = z21.discriminatedUnion("type", [
1429
+ SessionModelAcceptedSchema,
1346
1430
  z21.object({ type: z21.literal("chat.routed"), qid: z21.string(), result: RouteResultSchema }),
1347
1431
  DocumentContentSchema,
1348
1432
  DocumentErrorSchema,
@@ -1368,6 +1452,8 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1368
1452
  }),
1369
1453
  z21.object({ type: z21.literal("session.removed"), id: z21.string() }),
1370
1454
  z21.object({ type: z21.literal("session.preview"), id: z21.string(), lines: z21.array(z21.string()) }),
1455
+ /** The session's terminal in color (SGR codes kept), sent only to the client that subscribed to it. */
1456
+ z21.object({ type: z21.literal("session.preview.color"), id: z21.string(), lines: z21.array(z21.string()) }),
1371
1457
  /** A session's thread, on request (spec 2026-10-13 §5); then each created or updated item, broadcast. */
1372
1458
  z21.object({ type: z21.literal("session.thread"), id: z21.string(), items: z21.array(ThreadItemSchema) }),
1373
1459
  z21.object({ type: z21.literal("session.thread.item"), id: z21.string(), item: ThreadItemSchema }),
@@ -1393,7 +1479,7 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1393
1479
  /** The chat (spec 2026-10-11 §5): what Ledge reads, its words as they come, the final text, or why it stopped; the day's turns after each snapshot. */
1394
1480
  z21.object({ type: z21.literal("chat.step"), qid: z21.string(), step: ChatStepSchema }),
1395
1481
  z21.object({ type: z21.literal("chat.delta"), qid: z21.string(), text: z21.string() }),
1396
- z21.object({ type: z21.literal("chat.done"), qid: z21.string(), text: z21.string(), at: z21.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), closes: ChatClosesSchema.optional(), projectProposal: ChatProjectProposalSchema.optional(), team: ChatTeamProposalSchema.optional(), tasks: ChatTasksSchema.optional(), actions: ChatActionsSchema.optional() }),
1482
+ z21.object({ type: z21.literal("chat.done"), qid: z21.string(), text: z21.string(), at: z21.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), closes: ChatClosesSchema.optional(), projectProposal: ChatProjectProposalSchema.optional(), actions: ChatActionsSchema.optional() }),
1397
1483
  /** What a `chat.act` applied: the rows done, and the one that failed with why (the rows after it stay proposed). */
1398
1484
  z21.object({ type: z21.literal("chat.acted"), rid: z21.string(), qid: z21.string(), done: z21.array(z21.number().int().min(0)), failed: z21.array(z21.object({
1399
1485
  row: z21.number().int().min(0),
@@ -1403,7 +1489,7 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1403
1489
  })) }),
1404
1490
  /** What an `action.run` came to: `row` the action as the daemon resolved it (its summary, its risk); `reason` why it was not
1405
1491
  * done, with `code` (`refused`: not possible now; `agent`: a session may not run it; `confirm_required`: destructive, ask
1406
- * again; `failed`: the handler turned it down; a free string, as an `error`'s); `path` the template a `team.save` wrote. */
1492
+ * again; `failed`: the handler turned it down; a free string, as an `error`'s); `path` a file the action wrote. */
1407
1493
  z21.object({ type: z21.literal("action.result"), rid: z21.string(), action: z21.string().min(1).max(40), row: ChatActionSchema.optional(), reason: z21.string().optional(), code: z21.string().optional(), path: z21.string().optional() }),
1408
1494
  z21.object({ type: z21.literal("chat.error"), qid: z21.string(), message: z21.string() }),
1409
1495
  z21.object({ type: z21.literal("chat.history"), turns: z21.array(ChatTurnSchema) }),
@@ -1423,20 +1509,31 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1423
1509
  z21.object({ type: z21.literal("message.ack"), rid: z21.string(), outcome: z21.enum(["accepted", "queued", "native", "refused"]), reason: z21.string().optional() }),
1424
1510
  /** The answer to a `message.read`: the body with its sender, target and time, or the refusal's `reason`. */
1425
1511
  z21.object({ type: z21.literal("message.body"), id: z21.string(), from: z21.string().optional(), to: z21.string().optional(), at: z21.string().optional(), text: z21.string().optional(), reason: z21.string().optional() }),
1426
- TaskResultSchema,
1427
1512
  SessionHandoverResultSchema,
1428
1513
  ContextPrefsMessageSchema,
1429
1514
  HandoverResultSchema,
1430
- TaskUpdatedSchema,
1431
- RolesListingSchema,
1432
- TeamsMessageSchema,
1433
- TeamUpdatedSchema,
1434
- TeamLaunchedSchema,
1435
- TeamFailedSchema,
1436
- TeamSavedSchema,
1437
- DefinitionsListingSchema,
1438
- DefinitionResultSchema,
1439
- TeamChangedSchema,
1515
+ AsksMessageSchema,
1516
+ AskUpdatedSchema,
1517
+ AskResultSchema,
1518
+ OpenRigStatusMessageSchema,
1519
+ RigSnapshotSchema,
1520
+ RigUpdatedSchema,
1521
+ RigRemovedSchema,
1522
+ RigExchangeSchema,
1523
+ RigExchangesSchema,
1524
+ RigSpecsSchema,
1525
+ EscalationUpdatedSchema,
1526
+ EscalationRemovedSchema,
1527
+ LedgeThreadItemMessageSchema,
1528
+ LedgeThreadHistorySchema,
1529
+ DecisionsMessageSchema,
1530
+ RulesMessageSchema,
1531
+ MandateMessageSchema,
1532
+ BrainStatusMessageSchema,
1533
+ RigPlanMessageSchema,
1534
+ RigResultSchema,
1535
+ BrainResultSchema,
1536
+ BrainDelegateResultSchema,
1440
1537
  z21.object({
1441
1538
  type: z21.literal("error"),
1442
1539
  message: z21.string(),
@@ -1479,10 +1576,6 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1479
1576
  effort: EffortSchema.optional(),
1480
1577
  /** OMP only: its approval mode, `always-ask` when left out. */
1481
1578
  approval: OmpApprovalSchema.optional(),
1482
- /** A role (spec 2026-10-25 roles §3.4): its prompt reaches the agent at every start, its defaults fill what is left out. */
1483
- role: RoleIdSchema.optional(),
1484
- /** A live team the new session joins at once, in `role`. */
1485
- team: TeamIdSchema.optional(),
1486
1579
  /** Routing by the fleet, removed before the message reaches the daemon. */
1487
1580
  machineId: z21.string().optional()
1488
1581
  }),
@@ -1570,10 +1663,20 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1570
1663
  z21.object({ type: z21.literal("session.delete"), id: z21.string() }),
1571
1664
  /** The user names the session's topic from the overlay (500 characters at most, the hook's prompt cap); an empty topic hands back to the automatic sources. */
1572
1665
  z21.object({ type: z21.literal("session.rename"), id: z21.string(), topic: z21.string().max(500) }),
1666
+ /** The one session this client follows in color; `id: null` follows none. */
1667
+ z21.object({ type: z21.literal("session.preview.subscribe"), id: z21.string().nullable(), color: z21.literal(true) }),
1573
1668
  z21.object({ type: z21.literal("session.thread"), id: z21.string() }),
1574
1669
  z21.object({ type: z21.literal("session.register"), session: SessionSchema }),
1575
1670
  /** `agent` and `cwd` describe the session when the daemon does not know it yet (a hook from an agent started without ledge). */
1576
- z21.object({ type: z21.literal("session.event"), id: z21.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z21.string().optional() }),
1671
+ z21.object({
1672
+ type: z21.literal("session.event"),
1673
+ id: z21.string(),
1674
+ event: SessionEventSchema,
1675
+ agent: ToolSchema.optional(),
1676
+ cwd: z21.string().optional(),
1677
+ /** The OpenRig seat a hook ran in: its session name (`dev-build@starter`) and node id, what pairs the session with its seat. */
1678
+ openrig: z21.object({ sessionName: z21.string().min(1).max(200), nodeId: z21.string().max(200).optional() }).optional()
1679
+ }),
1577
1680
  z21.object({ type: z21.literal("session.unregister"), id: z21.string(), exitCode: z21.number().int().nullable() }),
1578
1681
  z21.object({ type: z21.literal("snapshot.request") }),
1579
1682
  z21.object({ type: z21.literal("chat.ask"), qid: z21.string().min(1), text: z21.string(), images: z21.array(ImageUploadSchema).max(ATTACHMENTS_MAX).optional(), tool: ToolSchema.optional() }),
@@ -1603,29 +1706,33 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1603
1706
  z21.object({ type: z21.literal("session.message"), rid: z21.string().min(1), from: z21.string().min(1), to: z21.string().min(1), text: z21.string().min(1).max(MESSAGE_MAX), summary: z21.string().max(SUMMARY_MAX).optional(), intercept: z21.boolean().optional() }),
1604
1707
  /** `ledge read <id>`: a message's whole body, for its receiver or its sender (`by`, a session id). */
1605
1708
  z21.object({ type: z21.literal("message.read"), id: z21.string().min(1).max(40), by: z21.string().min(1) }),
1606
- TaskCmdSchema,
1709
+ AskCreateSchema,
1710
+ AskAnswerSchema,
1607
1711
  SessionHandoverSchema,
1608
1712
  ContextSetSchema,
1609
1713
  HandoverBriefSchema,
1610
1714
  HandoverShowSchema,
1611
- RolesListSchema,
1612
- TeamLaunchSchema,
1613
- TeamJoinSchema,
1614
- TeamLeaveSchema,
1615
- TeamResumeSchema,
1616
- TeamDissolveSchema,
1617
- TeamSaveSchema,
1618
- DefinitionsListSchema,
1619
- RoleWriteSchema,
1620
- RoleDeleteSchema,
1621
- TemplateWriteSchema,
1622
- TemplateDeleteSchema,
1623
- TeamComposeSchema,
1624
- TeamSeatSchema
1625
- ]).superRefine((m, ctx) => {
1626
- const issue = "scope" in m ? scopeIssue(m) : null;
1627
- if (issue) ctx.addIssue({ code: z21.ZodIssueCode.custom, message: issue, path: ["project"] });
1628
- });
1715
+ RigActSchema,
1716
+ RigPrepareSchema,
1717
+ RigLaunchSchema,
1718
+ RigAdoptSchema,
1719
+ RigSendSchema,
1720
+ RigExchangesRequestSchema,
1721
+ RigSpecsRequestSchema,
1722
+ EscalationAnswerSchema,
1723
+ LedgeSaySchema,
1724
+ LedgeThreadRequestSchema,
1725
+ MandateUpdateSchema,
1726
+ RuleSetSchema,
1727
+ DecisionRevisitSchema,
1728
+ CategoryEscalateSchema,
1729
+ BrainSetSchema,
1730
+ BrainCmdSchema,
1731
+ BrainDelegateClaimSchema,
1732
+ BrainDelegateDecideSchema,
1733
+ BrainDelegateReleaseSchema,
1734
+ BrainDelegateQuerySchema
1735
+ ]);
1629
1736
  function parseClientMessage(raw) {
1630
1737
  return ClientMessageSchema.parse(raw);
1631
1738
  }
@@ -1717,8 +1824,8 @@ function pickName(taken, preferred) {
1717
1824
 
1718
1825
  // ../protocol/src/topic.ts
1719
1826
  var TOPIC_MAX = 80;
1720
- function cleanTopic(text) {
1721
- const line = text.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
1827
+ function cleanTopic(text2) {
1828
+ const line = text2.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
1722
1829
  const flat = line.replace(/\s+/g, " ");
1723
1830
  return flat.length > TOPIC_MAX ? `${flat.slice(0, TOPIC_MAX - 1).trimEnd()}\u2026` : flat;
1724
1831
  }
@@ -1798,9 +1905,61 @@ var MeResponseSchema = z23.object({
1798
1905
  // ../protocol/src/phone.ts
1799
1906
  import { z as z26 } from "zod";
1800
1907
 
1801
- // ../protocol/src/push.ts
1908
+ // ../protocol/src/phoneOpenrig.ts
1802
1909
  import { z as z24 } from "zod";
1803
- var B64U = z24.string().regex(/^[A-Za-z0-9_-]*$/);
1910
+ var id = z24.string().min(1).max(120);
1911
+ var key = z24.string().min(1).max(64);
1912
+ var at = z24.string().datetime();
1913
+ var op = z24.string().uuid();
1914
+ var messageMax = 2e4;
1915
+ var toolTextMax = 2048;
1916
+ var PhoneRigSeatSchema = z24.object({ ref: id, address: id, name: z24.string().max(80), runtime: z24.string().max(80), model: z24.string().max(120).optional(), state: SeatStateSchema, contextPct: z24.number().min(0).max(100).optional(), sessionId: id.optional() }).strict();
1917
+ var PhoneRigSchema = z24.object({ key, name: z24.string().max(80), spec: z24.string().max(80).optional(), project: z24.string().max(80).nullable(), offProject: z24.boolean(), branch: z24.string().max(200).optional(), lifecycle: RigLifecycleSchema, kernel: z24.boolean(), seats: z24.array(PhoneRigSeatSchema).max(16), repos: z24.number().int().nonnegative(), launch: z24.object({ planId: id, goal: z24.string().max(600), lead: id, startedAt: at, goalSentAt: at.optional(), lateNotice: z24.literal(true).optional() }).strict().optional() }).strict();
1918
+ var PhoneExchangeSchema = z24.object({ id, rigKey: key, from: id, to: id, kind: z24.enum(["message", "task", "handoff", "ledge"]), text: z24.string().max(toolTextMax), at, cut: z24.literal(true).optional() }).strict();
1919
+ var PhoneQuestionSchema = z24.object({ id, question: z24.string().max(toolTextMax), options: z24.array(z24.object({ id, label: z24.string().max(200), recommended: z24.boolean().optional() }).strict()).min(2).max(4) }).strict();
1920
+ var PhoneEscalationSchema = z24.object({ id, source: z24.enum(["rig", "operator", "ask"]), rigKey: key.optional(), rigName: z24.string().max(60).optional(), from: id, intent: z24.enum(["decision", "update"]), summary: z24.string().max(toolTextMax), detail: z24.string().max(toolTextMax).optional(), questions: z24.array(PhoneQuestionSchema).max(4), createdAt: at, state: z24.enum(["pending", "grace", "answered", "closed"]), graceUntil: at.optional(), correcting: id.optional(), category: MandateCategorySchema.optional(), advice: z24.string().max(toolTextMax).optional(), rules: z24.array(z24.object({ text: z24.string().max(200), since: z24.string().max(10) }).strict()).max(5).optional(), answer: z24.object({ by: z24.enum(["ledge", "user"]), answers: z24.record(z24.string().max(120), z24.string().max(messageMax)), at, rule: id.optional(), delivery: z24.enum(["queued", "sent"]).optional() }).strict().optional() }).strict();
1921
+ var PhoneLedgeDecisionSchema = z24.object({ id, escalationId: id, by: z24.enum(["ledge", "user"]), category: MandateCategorySchema.optional(), summary: z24.string().max(toolTextMax), answers: z24.record(z24.string().max(120), z24.string().max(messageMax)), rule: id.optional(), from: id.optional(), rigName: z24.string().max(60).optional(), why: z24.string().max(toolTextMax).optional(), at, revisitedBy: id.optional() }).strict();
1922
+ var PhoneRigPlanSchema = z24.object({ id, spec: z24.string().max(80), goal: z24.string().max(600), where: z24.discriminatedUnion("kind", [z24.object({ kind: z24.literal("project"), project: z24.string().max(80), branch: z24.string().max(200), copy: z24.boolean() }).strict(), z24.object({ kind: z24.literal("off-project"), name: z24.string().max(80) }).strict()]), seats: z24.array(z24.object({ address: id, runtime: z24.string().max(80), model: z24.string().max(120).optional(), autonomy: z24.string().max(80) }).strict()).max(16), writes: z24.array(z24.string().max(200)).max(40), required: z24.boolean(), launchedRigKey: key.optional(), launchedAt: at.optional() }).strict();
1923
+ var PhoneThreadItemSchema = z24.discriminatedUnion("kind", [
1924
+ z24.object({ kind: z24.literal("user"), id, at, text: z24.string().max(toolTextMax), to: id.optional(), qid: id.optional() }).strict(),
1925
+ z24.object({ kind: z24.literal("ledge"), id, at, text: z24.string().max(toolTextMax), qid: id.optional(), pending: z24.literal(true).optional() }).strict(),
1926
+ z24.object({ kind: z24.literal("escalation"), id, at, escalation: PhoneEscalationSchema }).strict(),
1927
+ z24.object({ kind: z24.literal("decided"), id, at, decision: PhoneLedgeDecisionSchema }).strict(),
1928
+ z24.object({ kind: z24.literal("exchanges"), id, at, rigKey: key, rigName: z24.string().max(60), from: at, to: at, count: z24.number().int().min(1), items: z24.array(PhoneExchangeSchema).max(20) }).strict(),
1929
+ z24.object({ kind: z24.literal("card"), id, at, action: z24.lazy(() => PhoneChatActionSchema) }).strict(),
1930
+ z24.object({ kind: z24.literal("plan"), id, at, plan: PhoneRigPlanSchema }).strict()
1931
+ ]);
1932
+ var PhoneRigActSchema = z24.object({ type: z24.literal("rig.act"), op, key, verb: z24.enum(["down", "restart"]), seatRef: id.optional(), assertion: z24.lazy(() => AssertionSchema).optional() }).strict();
1933
+ var PhoneRigLaunchSchema = z24.object({ type: z24.literal("rig.launch"), op, planId: id, assertion: z24.lazy(() => AssertionSchema).optional() }).strict();
1934
+ var PhoneEscalationAnswerSchema = z24.object({ type: z24.literal("escalation.answer"), op, id, answers: z24.record(z24.string().max(64), z24.string().max(messageMax)).refine((value) => Object.keys(value).length <= 4) }).strict();
1935
+ var rigSubject = (m) => m.type === "rig.launch" ? `launch@${m.planId}` : `${m.verb}@${m.key}${m.seatRef ? `/${m.seatRef}` : ""}`;
1936
+ var phoneOpenRigClientMessages = [
1937
+ z24.object({ type: z24.literal("ledge.say"), op, text: z24.string().trim().min(1).max(messageMax), about: id.optional() }).strict(),
1938
+ z24.object({ type: z24.literal("ledge.thread.request"), before: id.optional() }).strict(),
1939
+ PhoneEscalationAnswerSchema,
1940
+ z24.object({ type: z24.literal("answer.receipt.request"), answer: PhoneEscalationAnswerSchema }).strict(),
1941
+ z24.object({ type: z24.literal("decision.revisit"), op, id, text: z24.string().max(messageMax).optional() }).strict(),
1942
+ z24.object({ type: z24.literal("rig.exchanges.request"), key }).strict(),
1943
+ z24.object({ type: z24.literal("rig.prepare"), op, goal: z24.string().trim().min(1).max(600), spec: z24.string().max(64).optional(), project: z24.string().max(80).nullable() }).strict(),
1944
+ PhoneRigLaunchSchema,
1945
+ PhoneRigActSchema
1946
+ ];
1947
+ var phoneOpenRigServerMessages = [
1948
+ z24.object({ type: z24.literal("answer.receipt"), op, status: z24.enum(["received", "applied", "unknown"]) }).strict(),
1949
+ z24.object({ type: z24.literal("rig.updated"), rig: PhoneRigSchema }).strict(),
1950
+ z24.object({ type: z24.literal("rig.removed"), key }).strict(),
1951
+ z24.object({ type: z24.literal("rig.exchanges"), key, items: z24.array(PhoneExchangeSchema).max(EXCHANGES_MAX), cut: z24.literal(true).optional() }).strict(),
1952
+ z24.object({ type: z24.literal("escalation.updated"), escalation: PhoneEscalationSchema }).strict(),
1953
+ z24.object({ type: z24.literal("escalation.removed"), id }).strict(),
1954
+ z24.object({ type: z24.literal("ledge.thread.item"), item: PhoneThreadItemSchema }).strict(),
1955
+ z24.object({ type: z24.literal("ledge.thread.history"), items: z24.array(PhoneThreadItemSchema).max(THREAD_PAGE), before: id.optional() }).strict(),
1956
+ z24.object({ type: z24.literal("decisions"), items: z24.array(PhoneLedgeDecisionSchema).max(50) }).strict(),
1957
+ z24.object({ type: z24.literal("rig.plan"), plan: PhoneRigPlanSchema }).strict()
1958
+ ];
1959
+
1960
+ // ../protocol/src/push.ts
1961
+ import { z as z25 } from "zod";
1962
+ var B64U = z25.string().regex(/^[A-Za-z0-9_-]*$/);
1804
1963
  var PUSH_HOSTS = ["*.push.apple.com", "fcm.googleapis.com", "updates.push.services.mozilla.com", "*.notify.windows.com"];
1805
1964
  function pushEndpointAllowed(url) {
1806
1965
  let u;
@@ -1817,75 +1976,33 @@ function pushEndpointAllowed(url) {
1817
1976
  return host.length > suffix.length && host.endsWith(suffix);
1818
1977
  });
1819
1978
  }
1820
- var PushSendSchema = z24.object({
1821
- endpoint: z24.string().url().max(1024),
1979
+ var PushSendSchema = z25.object({
1980
+ endpoint: z25.string().url().max(1024),
1822
1981
  body: B64U.max(5462),
1823
- ttl: z24.number().int().min(0).max(86400),
1824
- urgency: z24.enum(["normal", "high"]),
1825
- topic: z24.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1826
- });
1827
- var PushSendResultSchema = z24.object({
1828
- result: z24.enum(["sent", "gone", "failed"]),
1829
- status: z24.number().int().optional()
1830
- });
1831
- var PushSubscriptionSchema = z24.object({
1832
- endpoint: z24.string().url().max(1024).refine(pushEndpointAllowed),
1833
- keys: z24.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1834
- });
1835
- var PushPayloadSchema = z24.object({
1836
- v: z24.literal(1),
1837
- kind: z24.enum(["request", "question", "finished", "chat"]),
1838
- title: z24.string().max(80),
1839
- body: z24.string().max(120),
1840
- url: z24.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
1841
- badge: z24.number().int().min(0).max(999),
1842
- tag: z24.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1982
+ ttl: z25.number().int().min(0).max(86400),
1983
+ urgency: z25.enum(["normal", "high"]),
1984
+ topic: z25.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1985
+ });
1986
+ var PushSendResultSchema = z25.object({
1987
+ result: z25.enum(["sent", "gone", "failed"]),
1988
+ status: z25.number().int().optional()
1989
+ });
1990
+ var PushSubscriptionSchema = z25.object({
1991
+ endpoint: z25.string().url().max(1024).refine(pushEndpointAllowed),
1992
+ keys: z25.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1993
+ });
1994
+ var PushPayloadSchema = z25.object({
1995
+ v: z25.literal(1),
1996
+ kind: z25.enum(["request", "question", "finished", "chat", "escalation", "decided"]),
1997
+ escalationId: z25.string().min(1).max(120).optional(),
1998
+ count: z25.number().int().min(1).max(999).optional(),
1999
+ actions: z25.array(z25.object({ qId: z25.string().min(1).max(64), optId: z25.string().min(1).max(64), label: z25.string().max(40) }).strict()).max(2).optional(),
2000
+ title: z25.string().max(80),
2001
+ body: z25.string().max(120),
2002
+ url: z25.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
2003
+ badge: z25.number().int().min(0).max(999),
2004
+ tag: z25.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1843
2005
  });
1844
-
1845
- // ../protocol/src/phoneTeams.ts
1846
- import { z as z25 } from "zod";
1847
- var scope = DefinitionScopeSchema;
1848
- var PhoneSeatsSchema = z25.array(SeatSchema.strict()).min(1).max(TEAM_SEATS_MAX).refine(uniqueRoles, "a role twice in one team");
1849
- var PhoneRoleDraftSchema = RoleDraftSchema.strict();
1850
- var projectName = z25.string().max(80).nullable();
1851
- var PhoneRoleSchema = z25.object({
1852
- id: RoleIdSchema,
1853
- label: z25.string().min(1).max(ROLE_LABEL_MAX),
1854
- prompt: z25.string().max(ROLE_PROMPT_MAX),
1855
- cut: z25.literal(true).optional(),
1856
- tool: ToolSchema.optional(),
1857
- model: ModelIdSchema.optional(),
1858
- effort: EffortSchema.optional(),
1859
- autonomy: AutonomySchema.optional(),
1860
- scope,
1861
- project: projectName,
1862
- hash: DefinitionHashSchema,
1863
- shadows: z25.literal(true).optional(),
1864
- shadowed: z25.literal(true).optional(),
1865
- shadowedIn: z25.array(z25.string().max(80)).min(1).max(8).optional()
1866
- }).strict();
1867
- var PhoneTemplateSchema = z25.object({ id: TemplateIdSchema, name: TeamNameSchema, seats: PhoneSeatsSchema, scope, project: projectName, hash: DefinitionHashSchema }).strict();
1868
- var PhoneProblemSchema = z25.object({ file: z25.string().max(80), scope, project: projectName, reason: z25.string().max(200) }).strict();
1869
- var PhoneCatalogSchema = z25.object({
1870
- /** The names of the projects with a local repository: the ones a « Dépôt » scope or a « Lever » can name. */
1871
- projects: z25.array(z25.string().max(80)).max(50),
1872
- roles: z25.array(PhoneRoleSchema).max(200),
1873
- templates: z25.array(PhoneTemplateSchema).max(100),
1874
- problems: z25.array(PhoneProblemSchema).max(50)
1875
- }).strict();
1876
- var TEAMS_OPERATION_TYPES = ["role.write", "role.delete", "template.write", "template.delete", "team.launch", "team.compose", "team.seat", "team.dissolve"];
1877
- var isTeamsOperation = (m) => TEAMS_OPERATION_TYPES.includes(m.type);
1878
- function canonicalJson(v) {
1879
- const sort = (x) => {
1880
- if (Array.isArray(x)) return x.map(sort);
1881
- if (x !== null && typeof x === "object") {
1882
- const o = x;
1883
- return Object.fromEntries(Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => [k, sort(o[k])]));
1884
- }
1885
- return x;
1886
- };
1887
- return JSON.stringify(sort(v));
1888
- }
1889
2006
 
1890
2007
  // ../protocol/src/phone.ts
1891
2008
  var PHONE_PROTOCOL = 1;
@@ -1903,13 +2020,28 @@ var THREAD_ITEMS_MAX = 200;
1903
2020
  var THREAD_BUDGET = 524288;
1904
2021
  var THREAD_TEXT_MAX = 8192;
1905
2022
  var TOOL_TEXT_MAX = 2048;
2023
+ var CHECKLIST_TASKS_MAX = 50;
2024
+ var CHECKLIST_TEXT_MAX = 200;
2025
+ var ChecklistStatusSchema = z26.enum(["pending", "in_progress", "completed"]);
2026
+ var PhoneChecklistSchema = z26.object({
2027
+ tasks: z26.array(z26.object({ text: z26.string().max(CHECKLIST_TEXT_MAX), status: ChecklistStatusSchema })).max(CHECKLIST_TASKS_MAX),
2028
+ done: z26.number().int().nonnegative(),
2029
+ total: z26.number().int().nonnegative(),
2030
+ cut: z26.literal(true).optional()
2031
+ });
2032
+ var PhoneChecklistSummarySchema = z26.object({
2033
+ done: z26.number().int().nonnegative(),
2034
+ total: z26.number().int().nonnegative(),
2035
+ current: z26.string().max(CHECKLIST_TEXT_MAX).optional(),
2036
+ cut: z26.literal(true).optional()
2037
+ });
1906
2038
  var PENDING_BODY_MAX = 65536;
1907
2039
  var SNAPSHOT_BODY_MAX = 2048;
1908
2040
  var RiskSchema = z26.enum(["low", "high"]);
1909
2041
  var RiskReasonSchema = z26.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"]);
1910
2042
  var OperationIdSchema = z26.string().uuid();
1911
2043
  var OperationStatusSchema = z26.enum(["received", "applied", "rejected", "failed"]);
1912
- var OperationReasonSchema = z26.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired", "changed", "exists", "unknown_project", "seat_failed"]);
2044
+ var OperationReasonSchema = z26.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired", "scheduled", ...ModelRefusalSchema.options, ...RIG_RESULT_CODES]);
1913
2045
  var CapabilitiesSchema = z26.object({
1914
2046
  /** A question can be answered in free text. */
1915
2047
  freeTextAnswer: z26.boolean(),
@@ -1921,7 +2053,8 @@ var CapabilitiesSchema = z26.object({
1921
2053
  var UNTYPABLE = /[\u0000-\u001f\u007f-\u009f\u200b\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
1922
2054
  var CapabilitiesByToolSchema = z26.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
1923
2055
  var PhonePendingSchema = PendingSchema.extend({ key: z26.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z26.literal(true).optional() });
1924
- var PhoneSessionSchema = SessionSchema.pick({
2056
+ var SwitchableSessionSchema = SessionSchema.extend({ canSwitch: z26.literal(true).optional() });
2057
+ var PhoneSessionSchema = SwitchableSessionSchema.pick({
1925
2058
  id: true,
1926
2059
  name: true,
1927
2060
  tool: true,
@@ -1936,14 +2069,27 @@ var PhoneSessionSchema = SessionSchema.pick({
1936
2069
  lastReply: true,
1937
2070
  startedAt: true,
1938
2071
  lastActivityAt: true,
1939
- /** Its role and its live team (ledge home spec §2.4): the agents screen groups a project's sessions by table. */
1940
- role: true,
1941
- roleLabel: true,
1942
- team: true,
2072
+ /** Its model, effort and the change waiting for its next free moment: the phone shows them and offers to change them. */
2073
+ model: true,
2074
+ effort: true,
2075
+ modelNext: true,
2076
+ canSwitch: true,
1943
2077
  /** Its context and its relief (context handover spec §3.2): the phone shows the gauge; the relief is started from the PC. */
1944
2078
  context: true,
1945
- relieves: true
1946
- }).extend({ pending: PhonePendingSchema.optional(), unread: z26.boolean(), handover: z26.object({ state: HandoverStateSchema, toName: z26.string().max(80).optional() }).strict().optional() });
2079
+ relieves: true,
2080
+ /** When it ended: « Terminée · à 18:42 » in the list. */
2081
+ exitedAt: true
2082
+ }).extend({
2083
+ pending: PhonePendingSchema.optional(),
2084
+ unread: z26.boolean(),
2085
+ handover: z26.object({ state: HandoverStateSchema, toName: z26.string().max(80).optional() }).strict().optional(),
2086
+ /** Absent: the agent has no checklist. */
2087
+ checklist: PhoneChecklistSummarySchema.optional(),
2088
+ /** Started without Ledge (`hook_only`): the phone can neither close nor resume it. */
2089
+ readOnly: z26.literal(true).optional(),
2090
+ rig: z26.object({ rigName: z26.string().max(80), seat: z26.string().max(120) }).strict().optional(),
2091
+ brain: z26.literal(true).optional()
2092
+ });
1947
2093
  var B64U2 = z26.string().regex(/^[A-Za-z0-9_-]*$/);
1948
2094
  var CredentialSchema = z26.object({
1949
2095
  id: B64U2.max(1366),
@@ -1959,8 +2105,7 @@ var AssertionSchema = z26.object({
1959
2105
  authenticatorData: B64U2.max(4096),
1960
2106
  signature: B64U2.max(1024)
1961
2107
  });
1962
- var TeamsDigestSchema = z26.string().regex(/^[0-9a-f]{64}$/);
1963
- var ConfirmPurposeSchema = z26.enum(["register", "decide", "launch", "close", "act", "define", "team", "pc"]);
2108
+ var ConfirmPurposeSchema = z26.enum(["register", "decide", "launch", "close", "act", "pc", "rig"]);
1964
2109
  var PhoneChatStepSchema = ChatStepSchema.pick({ kind: true, id: true, name: true }).strict();
1965
2110
  var PhoneChatProposalSchema = z26.object({
1966
2111
  project: z26.string().max(80),
@@ -1978,33 +2123,15 @@ var PhoneChatRelaySchema = z26.object({
1978
2123
  sentAt: z26.string().datetime().optional(),
1979
2124
  failed: z26.literal(true).optional()
1980
2125
  }).strict();
2126
+ var CloseHowSchema = z26.enum(["exit", "killed"]);
1981
2127
  var PhoneChatCloseSchema = z26.object({
1982
2128
  id: z26.string().min(1),
1983
2129
  name: z26.string(),
1984
2130
  tool: ToolSchema,
1985
- how: z26.enum(["exit", "killed"]).optional(),
2131
+ how: CloseHowSchema.optional(),
1986
2132
  closedAt: z26.string().datetime().optional(),
1987
2133
  failed: z26.literal(true).optional()
1988
2134
  }).strict();
1989
- var PhoneChatTeamSchema = z26.object({
1990
- project: z26.string().max(80),
1991
- name: z26.string().max(60),
1992
- template: z26.string().max(64).optional(),
1993
- seats: z26.array(z26.object({ role: z26.string().max(40), label: z26.string().max(40), tool: ToolSchema, model: z26.string().max(200).optional(), effort: z26.string().max(20).optional(), autonomy: AutonomySchema.optional() }).strict()).min(1).max(8),
1994
- teamId: z26.string().max(40).optional(),
1995
- launchedAt: z26.string().datetime().optional(),
1996
- failed: z26.literal(true).optional()
1997
- }).strict();
1998
- var PhoneChatTaskSchema = z26.object({
1999
- to: z26.string().max(80),
2000
- toName: z26.string().max(80).optional(),
2001
- title: z26.string().max(200),
2002
- text: z26.string().max(TOOL_TEXT_MAX).optional(),
2003
- taskId: z26.string().max(40).optional(),
2004
- ownerName: z26.string().max(80).optional(),
2005
- state: z26.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
2006
- failed: z26.literal(true).optional()
2007
- }).strict();
2008
2135
  var PhoneChatActionSchema = z26.object({
2009
2136
  action: z26.string().min(1).max(40),
2010
2137
  summary: z26.string().max(300),
@@ -2013,46 +2140,10 @@ var PhoneChatActionSchema = z26.object({
2013
2140
  detailCut: z26.literal(true).optional(),
2014
2141
  risk: ActionRiskSchema,
2015
2142
  session: z26.string().max(100).optional(),
2016
- team: z26.string().max(40).optional(),
2017
2143
  state: ChatActionStateSchema.optional(),
2018
2144
  doneAt: z26.string().datetime().optional(),
2019
2145
  code: z26.string().max(40).optional()
2020
2146
  }).strict();
2021
- var PhoneTeamSchema = z26.object({
2022
- id: z26.string().max(40),
2023
- name: z26.string().max(60),
2024
- project: z26.string().max(80).nullable(),
2025
- state: z26.enum(["active", "resting"]),
2026
- template: z26.string().max(64).optional(),
2027
- /** Absent from a daemon older than the phone: the phone then says no start time. */
2028
- createdAt: z26.string().optional(),
2029
- /** `tool`, `model` and `effort` are what the seat was composed with; `leaving` is a seat that finishes its turn and is then closed. */
2030
- seats: z26.array(z26.object({
2031
- role: z26.string().max(40),
2032
- sessionId: z26.string().max(100).nullable(),
2033
- tool: ToolSchema.optional(),
2034
- model: ModelIdSchema.optional(),
2035
- effort: EffortSchema.optional(),
2036
- leaving: z26.literal(true).optional()
2037
- }).strict()).max(16),
2038
- done: z26.number().int().min(0),
2039
- given: z26.number().int().min(0)
2040
- }).strict();
2041
- var PhoneTaskSchema = z26.object({
2042
- id: z26.string().max(40),
2043
- title: z26.string().max(200),
2044
- owner: z26.string().max(100),
2045
- ownerName: z26.string().max(80),
2046
- ownerRole: z26.string().max(40).optional(),
2047
- team: z26.string().max(40).optional(),
2048
- teamName: z26.string().max(60).optional(),
2049
- state: z26.enum(["open", "taken", "done", "refused", "dropped"]),
2050
- result: z26.string().max(TOOL_TEXT_MAX).optional(),
2051
- cut: z26.literal(true).optional(),
2052
- at: z26.string(),
2053
- updatedAt: z26.string(),
2054
- seen: z26.literal(true).optional()
2055
- }).strict();
2056
2147
  var ChatErrorCodeSchema = z26.enum(["busy", "timeout", "cancelled", "failed"]);
2057
2148
  var PhoneChatTurnSchema = z26.object({
2058
2149
  qid: OperationIdSchema,
@@ -2066,8 +2157,6 @@ var PhoneChatTurnSchema = z26.object({
2066
2157
  relay: PhoneChatRelaySchema.optional(),
2067
2158
  closes: z26.array(PhoneChatCloseSchema).min(1).max(MAX_PROPOSALS).optional(),
2068
2159
  cut: z26.literal(true).optional(),
2069
- team: PhoneChatTeamSchema.optional(),
2070
- tasks: z26.array(PhoneChatTaskSchema).min(1).max(MAX_PROPOSALS).optional(),
2071
2160
  actions: z26.array(PhoneChatActionSchema).min(1).max(MAX_PROPOSALS).optional(),
2072
2161
  ignoredAt: z26.string().datetime().optional(),
2073
2162
  attachments: z26.array(AttachmentRefSchema).max(ATTACHMENTS_MAX).optional()
@@ -2076,30 +2165,32 @@ var CHAT_HISTORY_BUDGET = 393216;
2076
2165
  var sessionId = z26.string().min(1).max(100);
2077
2166
  var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z26.string().min(1).max(64) });
2078
2167
  var CardRowsSchema = z26.array(z26.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice");
2079
- var teamsOp = { op: OperationIdSchema, assertion: AssertionSchema.optional() };
2168
+ var signedOp = { op: OperationIdSchema, assertion: AssertionSchema.optional() };
2080
2169
  var PcActionSchema = z26.enum(["update", "restart"]);
2081
2170
  var pcSubject = (action, version) => action === "update" ? `update@${version ?? ""}` : "restart";
2082
- var teamsScope = { scope: DefinitionScopeSchema, project: z26.string().min(1).max(80).optional() };
2083
2171
  var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2084
2172
  z26.object({ type: z26.literal("snapshot.request") }),
2173
+ ...phoneOpenRigClientMessages,
2085
2174
  z26.object({ type: z26.literal("session.thread"), id: sessionId }),
2086
2175
  /** The session's request with its whole body (up to `PENDING_BODY_MAX`). */
2087
2176
  z26.object({ type: z26.literal("session.pending"), id: sessionId }),
2088
2177
  /** The session whose terminal the phone shows; null when it shows none. */
2089
2178
  z26.object({ type: z26.literal("session.watch"), id: sessionId.nullable() }),
2090
2179
  z26.object({ type: z26.literal("session.read"), id: sessionId, at: z26.string().datetime() }),
2091
- /** Asks for a challenge. One object for both purposes: the gateway refuses a `decide` without `id` and `expect`. */
2180
+ /**
2181
+ * Asks for a challenge. One object for every purpose: the gateway refuses a `decide` without `id` and `expect`. A `close`
2182
+ * or `act` names a turn of the chat (`qid`) or a session (`id`, for `session.close` and `session.resume`).
2183
+ */
2092
2184
  z26.object({
2093
2185
  type: z26.literal("confirm.prepare"),
2094
2186
  purpose: ConfirmPurposeSchema,
2095
2187
  id: sessionId.optional(),
2096
2188
  expect: PhoneExpectSchema.optional(),
2097
2189
  qid: OperationIdSchema.optional(),
2098
- /** sha256 of the `define` or `team` operation the challenge is for (`teamsDigest`). */
2099
- digest: TeamsDigestSchema.optional(),
2100
2190
  /** A `pc` challenge: what it is for, and for an update the version it covers. */
2101
2191
  action: PcActionSchema.optional(),
2102
- version: z26.string().regex(SEMVER).optional()
2192
+ version: z26.string().regex(SEMVER).optional(),
2193
+ rig: z26.discriminatedUnion("type", [PhoneRigLaunchSchema, PhoneRigActSchema]).optional()
2103
2194
  }),
2104
2195
  z26.object({ type: z26.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
2105
2196
  z26.object({
@@ -2134,6 +2225,14 @@ var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2134
2225
  expect: PhoneExpectSchema
2135
2226
  }),
2136
2227
  z26.object({ type: z26.literal("session.interrupt"), op: OperationIdSchema, id: sessionId }),
2228
+ /** Ends a live session as `/exit` would, killed as a last resort; answered with its `how`. Passkey, purpose `close`, over the session. */
2229
+ z26.object({ type: z26.literal("session.close"), op: OperationIdSchema, id: sessionId, assertion: AssertionSchema.optional() }),
2230
+ /** Starts an ended session again on its conversation. Passkey, purpose `act`, over the session. */
2231
+ z26.object({ type: z26.literal("session.resume"), op: OperationIdSchema, id: sessionId, assertion: AssertionSchema.optional() }),
2232
+ /** Changes a live session's model and effort, `null` for the agent's own default; answered by `operation.result` and, when refused, `session.model.failed`. */
2233
+ z26.object({ type: z26.literal("session.model"), op: OperationIdSchema, id: sessionId, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
2234
+ /** The models an agent offers on the PC. */
2235
+ z26.object({ type: z26.literal("models.list"), tool: ToolSchema }),
2137
2236
  /** The phone drops its passkey from the daemon. */
2138
2237
  z26.object({ type: z26.literal("device.forget"), op: OperationIdSchema }),
2139
2238
  /** The phone's push subscription, with the VAPID key it was made with (so a key change is seen). */
@@ -2150,10 +2249,6 @@ var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2150
2249
  z26.object({ type: z26.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
2151
2250
  z26.object({ type: z26.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
2152
2251
  z26.object({ type: z26.literal("chat.close"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
2153
- /** The turn's team card (roles spec §3.8): launched like a session card, with the device check (purpose `launch`, the turn). */
2154
- z26.object({ type: z26.literal("chat.team"), op: OperationIdSchema, qid: OperationIdSchema, assertion: AssertionSchema.optional() }),
2155
- /** A row of the turn's task card: given by Ledge. */
2156
- z26.object({ type: z26.literal("chat.task"), op: OperationIdSchema, qid: OperationIdSchema, row: z26.number().int().min(0).max(MAX_PROPOSALS - 1) }),
2157
2252
  /** Rows of the turn's generic card (action registry spec §5): `confirmed` as the local `chat.act`; a row whose action asks
2158
2253
  * for a device check (`phone`) needs one assertion, purpose `act`, over the turn. */
2159
2254
  z26.object({
@@ -2168,218 +2263,201 @@ var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2168
2263
  }),
2169
2264
  /** « Ignorer » the turn's cards, or `undo` (« Revoir »), as the local `chat.ignore`. */
2170
2265
  z26.object({ type: z26.literal("chat.ignore"), op: OperationIdSchema, qid: OperationIdSchema, undo: z26.literal(true).optional() }),
2171
- /** « Reprendre » a resting team. */
2172
- z26.object({ type: z26.literal("team.resume"), op: OperationIdSchema, team: z26.string().min(1).max(40) }),
2173
- /** Puts a closed task Ledge gave away from « À traiter ». */
2174
- z26.object({ type: z26.literal("task.seen"), op: OperationIdSchema, task: z26.string().min(1).max(40) }),
2175
2266
  /** Whether the phone's app is in front: a device that is visible gets no push. */
2176
2267
  z26.object({ type: z26.literal("presence"), visible: z26.boolean() }),
2177
- /** The roles, templates and problems of every scope the phone may name; then one role whole, when the catalog cut its prompt. */
2178
- z26.object({ type: z26.literal("teams.catalog") }),
2179
- z26.object({ type: z26.literal("teams.role"), ...teamsScope, id: RoleIdSchema }).strict(),
2180
- /** `base` is the hash the phone was shown, null to create. */
2181
- z26.object({ type: z26.literal("role.write"), ...teamsOp, ...teamsScope, role: PhoneRoleDraftSchema, base: DefinitionHashSchema.nullable() }).strict(),
2182
- z26.object({ type: z26.literal("role.delete"), ...teamsOp, ...teamsScope, id: RoleIdSchema, base: DefinitionHashSchema }).strict(),
2183
- /** No `id`: create, the id comes from the name. */
2184
- z26.object({ type: z26.literal("template.write"), ...teamsOp, ...teamsScope, id: TemplateIdSchema.optional(), name: TeamNameSchema, seats: PhoneSeatsSchema, base: DefinitionHashSchema.nullable() }).strict(),
2185
- z26.object({ type: z26.literal("template.delete"), ...teamsOp, ...teamsScope, id: TemplateIdSchema, base: DefinitionHashSchema }).strict(),
2186
- z26.object({ type: z26.literal("team.launch"), ...teamsOp, project: z26.string().min(1).max(80), name: TeamNameSchema, seats: PhoneSeatsSchema, template: z26.string().min(1).max(64).optional() }).strict(),
2187
- z26.object({ type: z26.literal("team.compose"), ...teamsOp, team: TeamIdSchema, seats: PhoneSeatsSchema }).strict(),
2188
- z26.object({ type: z26.literal("team.seat"), ...teamsOp, team: TeamIdSchema, role: RoleIdSchema, action: z26.enum(["start", "restart"]) }).strict(),
2189
- z26.object({ type: z26.literal("team.dissolve"), ...teamsOp, team: TeamIdSchema }).strict(),
2190
2268
  /** Installs `version` on the PC, then restarts its daemon; the phone asks for its own build's version. Passkey, purpose `pc`. */
2191
- z26.object({ type: z26.literal("daemon.update"), ...teamsOp, version: z26.string().regex(SEMVER) }).strict(),
2269
+ z26.object({ type: z26.literal("daemon.update"), ...signedOp, version: z26.string().regex(SEMVER) }).strict(),
2192
2270
  /** Restarts the PC's daemon without installing anything. Passkey, purpose `pc`. */
2193
- z26.object({ type: z26.literal("daemon.restart"), ...teamsOp }).strict()
2194
- ]).superRefine((m, ctx) => {
2195
- const issue = "scope" in m ? scopeIssue(m) : null;
2196
- if (issue) ctx.addIssue({ code: z26.ZodIssueCode.custom, message: issue, path: ["project"] });
2197
- });
2271
+ z26.object({ type: z26.literal("daemon.restart"), ...signedOp }).strict()
2272
+ ]);
2273
+ var PhoneModelChoiceSchema = z26.object({
2274
+ id: ModelChoiceSchema.shape.id.max(120),
2275
+ label: z26.string().max(80),
2276
+ description: z26.string().max(200).optional(),
2277
+ efforts: z26.array(EffortSchema).max(8).optional(),
2278
+ defaultEffort: EffortSchema.optional(),
2279
+ isDefault: z26.boolean().optional()
2280
+ }).strict();
2198
2281
  var PhoneServerMessageSchema = z26.discriminatedUnion("type", [
2199
2282
  z26.object({ type: z26.literal("snapshot"), protocol: z26.literal(PHONE_PROTOCOL), sessions: z26.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z26.object({
2200
2283
  version: z26.string(),
2201
2284
  name: z26.string().max(40),
2202
2285
  boot: z26.string().uuid().optional(),
2203
- teams: z26.literal(true).optional(),
2286
+ /** The daemon takes `session.model`. */
2287
+ models: z26.literal(true).optional(),
2288
+ /** The daemon sends `session.checklist` and a session's `checklist` summary. */
2289
+ checklist: z26.literal(true).optional(),
2204
2290
  /** `update`: the daemon can install a version and restart (npm, systemd); `restart`: it can restart (systemd). */
2205
2291
  update: z26.literal(true).optional(),
2206
- restart: z26.literal(true).optional()
2207
- }), device: z26.object({ confirm: z26.boolean(), push: z26.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z26.literal(true).optional(), contextNear: ContextPrefsSchema.shape.near.optional() }).optional() }),
2292
+ restart: z26.literal(true).optional(),
2293
+ /** The daemon takes `session.close` and `session.resume`. */
2294
+ sessionClose: z26.literal(true).optional(),
2295
+ openrig: z26.literal(true).optional(),
2296
+ answerReceipts: z26.literal(true).optional()
2297
+ }), openrigStatus: z26.object({ state: z26.enum(["absent", "stopped", "starting", "running", "unsupported", "error", "unreachable"]), version: z26.string().max(80).optional() }).strict().optional(), rigs: z26.array(PhoneRigSchema).max(40).optional(), escalations: z26.array(PhoneEscalationSchema).max(40).optional(), decisionsToday: z26.array(PhoneLedgeDecisionSchema).max(50).optional(), projects: z26.array(z26.object({ id: z26.string().min(1).max(80), name: z26.string().min(1).max(80), hasLocalRepo: z26.boolean() }).strict()).max(100).optional(), device: z26.object({ confirm: z26.boolean(), push: z26.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z26.literal(true).optional(), contextNear: ContextPrefsSchema.shape.near.optional() }).optional() }),
2298
+ ...phoneOpenRigServerMessages,
2208
2299
  z26.object({
2209
2300
  type: z26.literal("confirm.challenge"),
2210
2301
  purpose: ConfirmPurposeSchema,
2211
2302
  challenge: B64U2.max(64),
2212
2303
  rpId: z26.string().max(253),
2213
2304
  credentialId: B64U2.max(1366).optional(),
2214
- expiresInMs: z26.number().int().positive(),
2215
- /** Echoed from the `confirm.prepare` of a `define` or `team` challenge, so the phone matches each challenge to its own call. Absent from an older gateway. */
2216
- digest: TeamsDigestSchema.optional()
2305
+ expiresInMs: z26.number().int().positive()
2217
2306
  }),
2218
2307
  z26.object({ type: z26.literal("session.updated"), session: PhoneSessionSchema }),
2219
2308
  z26.object({ type: z26.literal("session.removed"), id: z26.string() }),
2309
+ /** A session's checklist, sent to the phone that watches it; `null`: it has none (any more). */
2310
+ z26.object({ type: z26.literal("session.checklist"), id: z26.string(), checklist: PhoneChecklistSchema.nullable() }),
2220
2311
  /** `pending.cut` is set when even this body was shortened. */
2221
2312
  z26.object({ type: z26.literal("session.pending"), id: z26.string(), pending: PhonePendingSchema }),
2222
2313
  z26.object({ type: z26.literal("session.preview"), id: z26.string(), lines: z26.array(z26.string()) }),
2223
2314
  /** `cut`: the ids of the items shortened; `more`: older items were left out. */
2224
2315
  z26.object({ type: z26.literal("session.thread"), id: z26.string(), items: z26.array(ThreadItemSchema), cut: z26.array(z26.string()), more: z26.boolean() }),
2225
2316
  z26.object({ type: z26.literal("session.thread.item"), id: z26.string(), item: ThreadItemSchema, cut: z26.literal(true).optional() }),
2226
- z26.object({ type: z26.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional() }),
2317
+ z26.object({
2318
+ type: z26.literal("operation.result"),
2319
+ op: OperationIdSchema,
2320
+ status: OperationStatusSchema,
2321
+ reason: OperationReasonSchema.optional(),
2322
+ /** How an applied `session.close` ended the agent: `killed` when it had to be forced. */
2323
+ how: CloseHowSchema.optional()
2324
+ }),
2325
+ /** The models an agent offers; `error`: it could not be asked, `models` is then what Ledge knows without it. */
2326
+ z26.object({ type: z26.literal("models.listing"), tool: ToolSchema, models: z26.array(PhoneModelChoiceSchema).max(40), error: z26.literal(true).optional() }),
2327
+ /** A model change that was accepted and then failed (the agent could not restart). */
2328
+ z26.object({ type: z26.literal("session.model.failed"), id: z26.string(), reason: ModelRefusalSchema }),
2227
2329
  z26.object({ type: z26.literal("chat.history"), turns: z26.array(PhoneChatTurnSchema).max(50) }),
2228
2330
  z26.object({ type: z26.literal("chat.step"), qid: OperationIdSchema, step: PhoneChatStepSchema }),
2229
2331
  /** The whole text so far, not an increment. */
2230
2332
  z26.object({ type: z26.literal("chat.delta"), qid: OperationIdSchema, text: z26.string().max(THREAD_TEXT_MAX) }),
2231
2333
  z26.object({ type: z26.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
2232
2334
  z26.object({ type: z26.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
2233
- z26.object({ type: z26.literal("teams.catalog"), catalog: PhoneCatalogSchema }),
2234
2335
  /** The daemon updating or restarting itself, whoever asked: npm runs, then the service restarts (the link drops right after). */
2235
2336
  z26.object({ type: z26.literal("daemon.progress"), version: z26.string().max(40), step: z26.enum(["installing", "restarting"]) }),
2236
2337
  /** Nothing was changed. Never npm's output nor a command: both hold the PC's paths. */
2237
2338
  z26.object({ type: z26.literal("daemon.failed"), version: z26.string().max(40), reason: UpdateFailureSchema }),
2238
- z26.object({ type: z26.literal("teams.role"), role: PhoneRoleSchema }),
2239
- /** The live teams, after each snapshot and on every change. */
2240
- z26.object({ type: z26.literal("teams"), teams: z26.array(PhoneTeamSchema).max(50) }),
2241
- /** The tasks Ledge gave, open or closed lately, after each snapshot; then each change. */
2242
- z26.object({ type: z26.literal("tasks"), tasks: z26.array(PhoneTaskSchema).max(200) }),
2243
- z26.object({ type: z26.literal("task.updated"), task: PhoneTaskSchema }),
2244
2339
  AttachmentStoredSchema,
2245
2340
  AttachmentDataSchema,
2246
2341
  AttachmentErrorSchema,
2247
2342
  /** Never carries free text. */
2248
2343
  z26.object({ type: z26.literal("error"), code: z26.enum(["invalid", "unknown_session", "too_large"]), request: z26.string().max(40).optional() })
2249
2344
  ]);
2250
- var teamsPurpose = (type) => type.startsWith("team.") ? "team" : "define";
2251
- var DIGEST_OP = "00000000-0000-4000-8000-000000000000";
2252
- async function teamsDigest(m) {
2253
- const { op: _op, assertion: _assertion, ...raw } = m;
2254
- const { op: _parsedOp, assertion: _parsedAssertion, ...rest } = PhoneClientMessageSchema.parse({ ...raw, op: DIGEST_OP });
2255
- const bytes = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(rest))));
2256
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2345
+ function canonicalJson(v) {
2346
+ const sort = (x) => {
2347
+ if (Array.isArray(x)) return x.map(sort);
2348
+ if (x !== null && typeof x === "object") {
2349
+ const o = x;
2350
+ return Object.fromEntries(Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => [k, sort(o[k])]));
2351
+ }
2352
+ return x;
2353
+ };
2354
+ return JSON.stringify(sort(v));
2257
2355
  }
2258
2356
 
2259
- // src/auth.ts
2260
- import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
2261
- import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
2262
- import { readFileSync } from "fs";
2263
- import { dirname } from "path";
2264
- var TOKEN_RE = /^[0-9a-f]{64}$/;
2265
- var DAEMON_PROTOCOL_PREFIX = "ledge.";
2266
- function isDaemonToken(value) {
2267
- return TOKEN_RE.test(value);
2268
- }
2269
- function tokenEquals(presented, expected) {
2270
- const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
2271
- const b = createHash("sha256").update(expected, "utf8").digest();
2272
- return timingSafeEqual(a, b);
2273
- }
2274
- function hostAllowed(hostHeader) {
2275
- const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
2276
- if (!raw || raw.length > 255) return false;
2277
- const host = raw.trim().toLowerCase();
2278
- if (!host || host.includes(" ") || host.endsWith(".")) return false;
2279
- let name;
2280
- if (host.startsWith("[")) {
2281
- const end = host.indexOf("]");
2282
- if (end < 0) return false;
2283
- name = host.slice(1, end);
2284
- const rest = host.slice(end + 1);
2285
- if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
2286
- } else {
2287
- const colon = host.lastIndexOf(":");
2288
- if (colon >= 0) {
2289
- if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
2290
- name = host.slice(0, colon);
2291
- } else name = host;
2357
+ // ../protocol/src/todos.ts
2358
+ var TODO_TOOLS = /* @__PURE__ */ new Set(["TodoWrite", "update_plan", "todo"]);
2359
+ var STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
2360
+ var statusOf = (s) => typeof s === "string" && STATUSES.has(s) ? s : "pending";
2361
+ var text = (s) => typeof s === "string" && s.trim() !== "" ? s : null;
2362
+ var record = (v) => v && typeof v === "object" ? v : {};
2363
+ function omp(input) {
2364
+ if (!Array.isArray(input.phases)) return void 0;
2365
+ const phases = input.phases.map(record);
2366
+ const out = [];
2367
+ for (const ph of phases) {
2368
+ for (const raw of Array.isArray(ph.tasks) ? ph.tasks : []) {
2369
+ const e = record(raw);
2370
+ const said = text(e.content);
2371
+ if (said === null) continue;
2372
+ const blocked = e.status === "blocked";
2373
+ const name = phases.length > 1 ? text(ph.name) : null;
2374
+ out.push({ text: `${name ? `${name} \xB7 ` : ""}${said}${blocked && text(e.blocker) ? ` (blocked: ${e.blocker})` : ""}`, status: blocked ? "pending" : statusOf(e.status) });
2375
+ }
2292
2376
  }
2293
- return name === "localhost" || name === "127.0.0.1" || name === "::1";
2294
- }
2295
- function headerValue(value, max) {
2296
- const raw = Array.isArray(value) ? value.join(",") : value;
2297
- return !raw || raw.length > max ? "" : raw;
2298
- }
2299
- function bearerToken(authorization) {
2300
- if (!authorization || authorization.length > 512) return "";
2301
- return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
2377
+ return out;
2302
2378
  }
2303
- function protocolToken(header) {
2304
- const raw = headerValue(header, 512);
2305
- if (!raw) return "";
2306
- for (const part of raw.split(",").slice(0, 8)) {
2307
- const protocol = part.trim();
2308
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
2379
+ function listOf(t) {
2380
+ if (!TODO_TOOLS.has(t.tool) || t.input === void 0) return void 0;
2381
+ let input;
2382
+ try {
2383
+ input = record(JSON.parse(t.input));
2384
+ } catch {
2385
+ return void 0;
2309
2386
  }
2310
- return "";
2311
- }
2312
- function requestAuthorized(headers, expected) {
2313
- const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
2314
- const presented = protocolToken(headers["sec-websocket-protocol"]);
2315
- return bearerOk || presented !== "" && tokenEquals(presented, expected);
2316
- }
2317
- function selectDaemonProtocol(protocols, expected) {
2318
- for (const protocol of protocols) {
2319
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
2387
+ if (t.tool === "todo") return omp(input);
2388
+ const claude = t.tool === "TodoWrite";
2389
+ const list = claude ? input.todos : input.plan;
2390
+ if (!Array.isArray(list)) return void 0;
2391
+ const out = [];
2392
+ for (const raw of list) {
2393
+ const e = record(raw);
2394
+ const status = statusOf(e.status);
2395
+ const said = claude ? status === "in_progress" ? text(e.activeForm) ?? text(e.content) : text(e.content) : text(e.step);
2396
+ if (said !== null) out.push({ text: said, status });
2320
2397
  }
2321
- return false;
2398
+ return out;
2322
2399
  }
2323
- async function readTokenFile(file) {
2324
- try {
2325
- const raw = (await readFile(file, "utf8")).trim();
2326
- return isDaemonToken(raw) ? raw : void 0;
2327
- } catch (e) {
2328
- if (e.code === "ENOENT") return void 0;
2329
- throw e;
2400
+ function latestTodos(items, helperId) {
2401
+ for (let i = items.length - 1; i >= 0; i--) {
2402
+ const it = items[i];
2403
+ if (it.kind !== "tool" || it.helperId !== helperId || !TODO_TOOLS.has(it.tool)) continue;
2404
+ const list = listOf(it);
2405
+ if (list) return list.length > 0 ? list : null;
2330
2406
  }
2407
+ return null;
2331
2408
  }
2332
- async function lockTokenFile(file) {
2333
- await chmod(file, 384);
2334
- }
2335
- async function ensureDaemonToken() {
2336
- const file = paths().token;
2337
- await mkdir(dirname(file), { recursive: true });
2338
- const current = await readTokenFile(file);
2339
- if (current) {
2340
- await lockTokenFile(file);
2341
- return current;
2342
- }
2343
- const token = randomBytes(32).toString("hex");
2344
- const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
2345
- await writeFile(tmp, `${token}
2346
- `, { mode: 384, flag: "wx" });
2347
- await chmod(tmp, 384);
2348
- try {
2349
- await link(tmp, file);
2350
- } catch (e) {
2351
- await unlink(tmp).catch(() => {
2352
- });
2353
- if (e.code !== "EEXIST") throw e;
2354
- const raced = await readTokenFile(file);
2355
- if (raced) {
2356
- await lockTokenFile(file);
2357
- return raced;
2409
+ var doneCount = (todos) => todos.filter((x) => x.status === "completed").length;
2410
+
2411
+ // ../protocol/src/mandate.ts
2412
+ var SELF_TITLE = "ledge tranche seul";
2413
+ var USER_TITLE = "te revient toujours";
2414
+ var isAlways = (id2) => ALWAYS_USER.includes(id2);
2415
+ function mandateEntries(text2) {
2416
+ const out = { self: [], user: [], always: [], readable: false };
2417
+ let list = null;
2418
+ for (const raw of text2.split("\n")) {
2419
+ const line = raw.trim();
2420
+ if (line.startsWith("#")) {
2421
+ const title = line.replace(/^#+\s*/, "").toLowerCase();
2422
+ list = title.startsWith(SELF_TITLE) ? "self" : title.startsWith(USER_TITLE) ? "user" : null;
2423
+ if (list) out.readable = true;
2424
+ continue;
2358
2425
  }
2359
- await writeFile(file, `${token}
2360
- `, { mode: 384 });
2426
+ const item = /^[-*]\s+(.+)$/.exec(line)?.[1];
2427
+ if (!list || !item) continue;
2428
+ const m = /^(\S+)\s+:\s+(.+)$/.exec(item);
2429
+ const entry = { id: m?.[1], said: (m?.[2] ?? item).trim() };
2430
+ if (list === "self" && entry.id && isAlways(entry.id)) out.always.push(entry);
2431
+ else out[list].push(entry);
2361
2432
  }
2362
- await unlink(tmp).catch(() => {
2363
- });
2364
- await lockTokenFile(file);
2365
- const stored = await readTokenFile(file);
2366
- if (!stored) throw new Error("daemon token was not stored");
2367
- return stored;
2368
- }
2369
- var SESSION_KEY_VAR = "LEDGE_SESSION_KEY";
2370
- function sessionKey(id, token = readDaemonToken()) {
2371
- return token ? createHmac("sha256", token).update(`ledge-session:${id}`).digest("hex") : void 0;
2433
+ return out;
2372
2434
  }
2373
- function readDaemonToken() {
2374
- try {
2375
- const raw = readFileSync(paths().token, "utf8").trim();
2376
- return isDaemonToken(raw) ? raw : void 0;
2377
- } catch {
2378
- return void 0;
2379
- }
2435
+ var known = (id2) => {
2436
+ const r = MandateCategorySchema.safeParse(id2);
2437
+ return r.success ? r.data : null;
2438
+ };
2439
+ function parseMandate(text2) {
2440
+ const e = mandateEntries(text2);
2441
+ if (!e.readable) return { self: [], user: [], readable: false };
2442
+ const uniq = (xs) => [...new Set(xs.filter((x) => x !== null))];
2443
+ const user = uniq([...e.user, ...e.always].map((x) => known(x.id)));
2444
+ return { self: uniq(e.self.map((x) => known(x.id))).filter((c) => !user.includes(c)), user, readable: true };
2380
2445
  }
2381
2446
 
2382
2447
  export {
2448
+ tokenEquals,
2449
+ hostAllowed,
2450
+ requestAuthorized,
2451
+ selectDaemonProtocol,
2452
+ ensureDaemonToken,
2453
+ SESSION_KEY_VAR,
2454
+ sessionKey,
2455
+ readDaemonToken,
2456
+ ASK_QUESTION_MAX,
2457
+ ASK_OPTIONS_MAX,
2458
+ ASK_ANSWER_MAX,
2459
+ AskSchema,
2460
+ askShown,
2383
2461
  ATTACHMENT_ID,
2384
2462
  ATTACHMENT_MAX,
2385
2463
  ATTACHMENT_LOCAL_MAX,
@@ -2390,6 +2468,7 @@ export {
2390
2468
  CONTEXT_NEAR_MIN,
2391
2469
  CONTEXT_NEAR_MAX,
2392
2470
  ContextPrefsSchema,
2471
+ contextShare,
2393
2472
  contextNear,
2394
2473
  BRIEF_MAX,
2395
2474
  ToolSchema,
@@ -2400,37 +2479,16 @@ export {
2400
2479
  isArchived,
2401
2480
  ModelIdSchema,
2402
2481
  EffortSchema,
2482
+ ModelRefusalSchema,
2403
2483
  extractProposal,
2404
2484
  parseRelayBlock,
2405
2485
  parseCloseBlock,
2406
- parseTeamBlock,
2407
- parseTaskBlock,
2408
2486
  parseProjectBlock,
2409
- SshAliasSchema,
2410
- MachinesSchema,
2411
- PROJECT_COLORS,
2412
- ProjectsSchema,
2413
- DEFAULT_PROJECTS,
2414
- projectOf,
2415
- RoleIdSchema,
2416
- ROLE_LABEL_MAX,
2417
- ROLE_PROMPT_MAX,
2418
- RoleSchema,
2419
- SeatsSchema,
2420
- TeamNameSchema,
2421
- TemplateIdSchema,
2422
- TeamSchema,
2423
- isTeamLive,
2424
2487
  NOTE_MAX,
2425
2488
  NOTE_NOT_TYPED,
2426
2489
  offers,
2427
2490
  pendingKey,
2428
- TASK_TITLE_MAX,
2429
- TASK_TEXT_MAX,
2430
- LEDGE_GIVER,
2431
- transmissions,
2432
- givenByLedge,
2433
- taskClosed,
2491
+ ActionIdSchema,
2434
2492
  ACTIONS,
2435
2493
  actionById,
2436
2494
  riskOf,
@@ -2443,10 +2501,29 @@ export {
2443
2501
  ChatTurnSchema,
2444
2502
  proposalRows,
2445
2503
  DOCUMENT_MAX_BYTES,
2504
+ SshAliasSchema,
2505
+ MachinesSchema,
2446
2506
  IslandPrefsSchema,
2507
+ PROJECT_COLORS,
2508
+ ProjectsSchema,
2509
+ DEFAULT_PROJECTS,
2510
+ projectOf,
2447
2511
  RouteResultSchema,
2448
2512
  DEFAULT_RELAY_URL,
2449
2513
  MobileConfigSchema,
2514
+ MandateCategorySchema,
2515
+ ALWAYS_USER,
2516
+ MANDATE_CATEGORY_LABEL,
2517
+ RIG_NAME,
2518
+ EscalationSchema,
2519
+ EXCHANGES_MAX,
2520
+ EXCHANGE_GROUP_MS,
2521
+ THREAD_PAGE,
2522
+ LedgeDecisionSchema,
2523
+ LedgeRuleSchema,
2524
+ LedgeThreadItemSchema,
2525
+ BRAIN_OPS,
2526
+ BRAIN_STOPPED_TEXT,
2450
2527
  PROTOCOL_PORT,
2451
2528
  RefusalCodeSchema,
2452
2529
  RecentSchema,
@@ -2466,20 +2543,19 @@ export {
2466
2543
  RELAY_CLOSE_SIGNED_OUT,
2467
2544
  DeviceStartResponseSchema,
2468
2545
  DevicePollResponseSchema,
2546
+ rigSubject,
2469
2547
  pushEndpointAllowed,
2470
2548
  PushSendResultSchema,
2471
2549
  PushPayloadSchema,
2472
- PhoneRoleSchema,
2473
- PhoneTemplateSchema,
2474
- TEAMS_OPERATION_TYPES,
2475
- isTeamsOperation,
2476
- canonicalJson,
2477
2550
  PHONE_PROTOCOL,
2551
+ PLAIN_MAX,
2478
2552
  padPlain,
2479
2553
  THREAD_ITEMS_MAX,
2480
2554
  THREAD_BUDGET,
2481
2555
  THREAD_TEXT_MAX,
2482
2556
  TOOL_TEXT_MAX,
2557
+ CHECKLIST_TASKS_MAX,
2558
+ CHECKLIST_TEXT_MAX,
2483
2559
  PENDING_BODY_MAX,
2484
2560
  SNAPSHOT_BODY_MAX,
2485
2561
  OperationIdSchema,
@@ -2487,14 +2563,11 @@ export {
2487
2563
  CHAT_HISTORY_BUDGET,
2488
2564
  pcSubject,
2489
2565
  PhoneClientMessageSchema,
2490
- teamsPurpose,
2491
- teamsDigest,
2492
- tokenEquals,
2493
- hostAllowed,
2494
- requestAuthorized,
2495
- selectDaemonProtocol,
2496
- ensureDaemonToken,
2497
- SESSION_KEY_VAR,
2498
- sessionKey,
2499
- readDaemonToken
2566
+ PhoneModelChoiceSchema,
2567
+ canonicalJson,
2568
+ TODO_TOOLS,
2569
+ latestTodos,
2570
+ doneCount,
2571
+ mandateEntries,
2572
+ parseMandate
2500
2573
  };