@paradow/ledge 0.28.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-NQRSZEC5.js → action-MCR4BCSE.js} +5 -5
  3. package/dist/cli/ask-L3PSQZ2K.js +17 -0
  4. package/dist/cli/{attach-KXOSFUHO.js → attach-R7FT7LFH.js} +9 -14
  5. package/dist/cli/brain-BJTBN2ZK.js +20 -0
  6. package/dist/cli/{bridge-XCTXVWKH.js → bridge-YBHXFEFX.js} +2 -2
  7. package/dist/cli/{chunk-VKRFHDRK.js → chunk-2X56ZRK7.js} +1122 -1164
  8. package/dist/cli/{chunk-3NQNWGDX.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-VMGACWNP.js → chunk-FWLT3SAN.js} +1 -1
  12. package/dist/cli/{chunk-6IYPRUQG.js → chunk-G3ST5WKR.js} +1 -1
  13. package/dist/cli/{chunk-ZI47U5CQ.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-LG3XFIPV.js → chunk-O4MUYFPH.js} +6 -7
  17. package/dist/cli/{chunk-JLUHY5AB.js → chunk-OR2NHT5O.js} +4 -4
  18. package/dist/cli/chunk-S6BZV7AY.js +57 -0
  19. package/dist/cli/{chunk-CR3LGTZQ.js → chunk-TCQ5PYIF.js} +3 -3
  20. package/dist/cli/{chunk-RYZ6FHYT.js → chunk-UMQGRTA4.js} +17 -39
  21. package/dist/cli/{chunk-3CF4FQWG.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-OSSTI3FE.js → decide-JSY46QZC.js} +14 -16
  27. package/dist/cli/{handover-2G7OEWIG.js → handover-5RUSRVAD.js} +5 -5
  28. package/dist/cli/{hook-XY4E5H7O.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-RUHGKLMA.js → send-BIIH6SQW.js} +5 -5
  32. package/dist/cli/{setup-K42IVMHS.js → setup-OLVRJLJE.js} +4 -4
  33. package/dist/cli/{status-4Z2VJFID.js → status-Z3I33KZJ.js} +3 -3
  34. package/dist/cli/{tui-4IRYQXYU.js → tui-AIXCETOB.js} +6036 -3629
  35. package/dist/cli/{uninstall-4IMMR55T.js → uninstall-X65CWHIJ.js} +4 -4
  36. package/dist/cli/{version-L2Y7MRH6.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-CIQAFCX3.js +0 -118
  40. package/dist/cli/chunk-JHFKBKSO.js +0 -159
  41. package/dist/cli/chunk-OYDTU6AP.js +0 -9482
  42. package/dist/cli/chunk-QSRB3F5Y.js +0 -198
  43. package/dist/cli/chunk-UME2F7VM.js +0 -6
  44. package/dist/cli/daemon-ZUHJKLSL.js +0 -2322
  45. package/dist/cli/launch-7WOARLZM.js +0 -35
  46. package/dist/cli/task-2GESYTOL.js +0 -19
  47. package/dist/cli/team-MTSKNMAE.js +0 -205
  48. package/dist/cli/who-PSKHASMK.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,346 +341,21 @@ function isArchived(s) {
175
341
  return s.archivedAt !== void 0;
176
342
  }
177
343
 
178
- // ../protocol/src/teams.ts
179
- import { z as z7 } from "zod";
180
-
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
- var ModelRefusalSchema = z3.enum(["not_launched", "ended", "no_conversation", "not_running", "effort_in_model", "restart_failed", "refused"]);
207
- var SessionModelAcceptedSchema = z3.object({ type: z3.literal("session.model.accepted"), id: z3.string(), when: z3.enum(["now", "turn", "none"]) });
208
-
209
- // ../protocol/src/projects.ts
210
- import { z as z6 } from "zod";
211
-
212
- // ../protocol/src/machines.ts
213
- import { z as z5 } from "zod";
214
-
215
- // ../protocol/src/ssh.ts
216
- import { z as z4 } from "zod";
217
- var SshAliasSchema = z4.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/);
218
-
219
- // ../protocol/src/machines.ts
220
- var MachineIdSchema = z5.string().regex(/^[a-zA-Z0-9_-]{1,64}$/);
221
- var MachineSchema = z5.object({
222
- id: MachineIdSchema,
223
- label: z5.string().trim().min(1).max(80),
224
- ssh: SshAliasSchema.optional()
225
- });
226
- var MachinesSchema = z5.array(MachineSchema).min(1).max(20).superRefine((list, ctx) => {
227
- if (list[0]?.id !== "local" || list[0].ssh !== void 0) ctx.addIssue({ code: "custom", message: "the first machine is local, without ssh", path: [0] });
228
- const ids = /* @__PURE__ */ new Set(), aliases = /* @__PURE__ */ new Set();
229
- list.forEach((m, i) => {
230
- if (i > 0 && !m.ssh) ctx.addIssue({ code: "custom", message: "a remote machine needs an ssh alias", path: [i, "ssh"] });
231
- if (ids.has(m.id)) ctx.addIssue({ code: "custom", message: `duplicate id ${m.id}`, path: [i, "id"] });
232
- ids.add(m.id);
233
- if (m.ssh) {
234
- if (aliases.has(m.ssh)) ctx.addIssue({ code: "custom", message: `duplicate ssh alias ${m.ssh}`, path: [i, "ssh"] });
235
- aliases.add(m.ssh);
236
- }
237
- });
238
- });
239
-
240
- // ../protocol/src/projects.ts
241
- var PROJECT_COLORS = { blue: "#3b82f6", green: "#10b981", amber: "#f59e0b", violet: "#8b5cf6", teal: "#14b8a6", pink: "#ec4899", red: "#ef4444", slate: "#64748b" };
242
- var ProjectColorSchema = z6.enum(["blue", "green", "amber", "violet", "teal", "pink", "red", "slate"]);
243
- var ProjectIdSchema = z6.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
244
- function normalizePath(raw) {
245
- const p = raw.trim().replace(/\/+/g, "/");
246
- return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
247
- }
248
- var PathSchema = z6.string().max(4096).refine((p) => p.startsWith("/") && p === normalizePath(p), "an absolute, normalized path");
249
- var RepoSchema = z6.object({ machine: MachineIdSchema, path: PathSchema });
250
- var ProjectSchema = z6.object({
251
- id: ProjectIdSchema,
252
- name: z6.string().trim().min(1).max(80),
253
- color: ProjectColorSchema,
254
- description: z6.string().max(500).default(""),
255
- repos: z6.array(RepoSchema).max(50)
256
- });
257
- var RuleSchema = z6.object({ machine: MachineIdSchema, path: PathSchema, project: ProjectIdSchema });
258
- function projectsProblems(p) {
259
- const out = [];
260
- const ids = /* @__PURE__ */ new Set(), names = /* @__PURE__ */ new Set(), repos = /* @__PURE__ */ new Set(), rules = /* @__PURE__ */ new Set();
261
- p.projects.forEach((project, i) => {
262
- if (ids.has(project.id)) out.push({ code: "duplicate_id", path: ["projects", i, "id"] });
263
- ids.add(project.id);
264
- const name = project.name.trim().toLocaleLowerCase();
265
- if (names.has(name)) out.push({ code: "duplicate_name", path: ["projects", i, "name"] });
266
- names.add(name);
267
- project.repos.forEach((repo, j) => {
268
- const key = `${repo.machine}
269
- ${repo.path}`;
270
- if (repos.has(key)) out.push({ code: "duplicate_repo", path: ["projects", i, "repos", j] });
271
- repos.add(key);
272
- });
273
- });
274
- p.rules.forEach((rule, i) => {
275
- const key = `${rule.machine}
276
- ${rule.path}`;
277
- if (rules.has(key)) out.push({ code: "duplicate_rule", path: ["rules", i] });
278
- rules.add(key);
279
- if (!ids.has(rule.project)) out.push({ code: "unknown_project", path: ["rules", i, "project"] });
280
- });
281
- return out;
282
- }
283
- var ProjectsSchema = z6.object({ projects: z6.array(ProjectSchema).max(100), rules: z6.array(RuleSchema).max(1e3) }).superRefine((p, ctx) => {
284
- for (const problem of projectsProblems(p)) ctx.addIssue({ code: "custom", message: problem.code, path: problem.path });
285
- });
286
- var DEFAULT_PROJECTS = { projects: [], rules: [] };
287
- var under = (cwd, path2) => path2 === "/" || cwd === path2 || cwd.startsWith(`${path2}/`);
288
- function projectOf(session, state) {
289
- const machine = session.machineId ?? "local";
290
- const cwd = normalizePath(session.cwd);
291
- const known = new Set(state.projects.map((p) => p.id));
292
- const candidates = [
293
- ...state.projects.flatMap((p) => p.repos.filter((r) => r.machine === machine).map((r) => ({ project: p.id, path: r.path, rule: false }))),
294
- ...state.rules.filter((r) => r.machine === machine && known.has(r.project)).map((r) => ({ project: r.project, path: r.path, rule: true }))
295
- ].filter((c) => under(cwd, c.path));
296
- candidates.sort((x, y) => y.path.length - x.path.length || Number(y.rule) - Number(x.rule));
297
- return candidates[0]?.project ?? null;
298
- }
299
-
300
- // ../protocol/src/teams.ts
301
- var RoleIdSchema = z7.string().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/);
302
- var ROLE_LABEL_MAX = 40;
303
- var ROLE_PROMPT_MAX = 8e3;
304
- var DefinitionHashSchema = z7.string().regex(/^[0-9a-f]{16}$/);
305
- var RoleSchema = z7.object({
306
- id: RoleIdSchema,
307
- label: z7.string().min(1).max(ROLE_LABEL_MAX),
308
- prompt: z7.string().max(ROLE_PROMPT_MAX),
309
- tool: ToolSchema.optional(),
310
- model: ModelIdSchema.optional(),
311
- effort: EffortSchema.optional(),
312
- autonomy: AutonomySchema.optional(),
313
- source: z7.enum(["repo", "home"]),
314
- path: z7.string(),
315
- shadows: z7.string().optional(),
316
- /** 16 hex chars of the file's text: what a write or delete must name as its base (absent from older daemons). */
317
- hash: DefinitionHashSchema.optional()
318
- });
319
- var DefinitionProblemSchema = z7.object({ path: z7.string(), reason: z7.string() });
320
- var TEAM_SEATS_MAX = 8;
321
- var SeatSchema = z7.object({
322
- role: RoleIdSchema,
323
- tool: ToolSchema.optional(),
324
- model: ModelIdSchema.optional(),
325
- effort: EffortSchema.optional(),
326
- name: z7.string().min(1).max(40).optional()
327
- });
328
- var uniqueRoles = (seats) => new Set(seats.map((s) => s.role)).size === seats.length;
329
- var SeatsSchema = z7.array(SeatSchema).min(1).max(TEAM_SEATS_MAX).refine(uniqueRoles, "a role twice in one team");
330
- var TeamNameSchema = z7.string().trim().min(1).max(60);
331
- var TemplateIdSchema = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/);
332
- 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() });
333
- var TeamIdSchema = z7.string().regex(/^team-[0-9a-f]{4,8}$/);
334
- var TeamStateSchema = z7.enum(["active", "resting", "dissolved"]);
335
- var TeamSchema = z7.object({
336
- id: TeamIdSchema,
337
- name: TeamNameSchema,
338
- template: z7.string().optional(),
339
- project: z7.string().nullable(),
340
- machine: z7.string(),
341
- cwd: z7.string(),
342
- state: TeamStateSchema,
343
- /** A live seat: `tool`, `model` and `effort` are what it was composed with over the role's defaults; `leaving` marks a seat removed
344
- * from the composition that finishes its turn and then is closed. */
345
- seats: z7.array(z7.object({
346
- role: RoleIdSchema,
347
- sessionId: z7.string().nullable(),
348
- tool: ToolSchema.optional(),
349
- model: ModelIdSchema.optional(),
350
- effort: EffortSchema.optional(),
351
- leaving: z7.literal(true).optional()
352
- })).max(TEAM_SEATS_MAX * 2),
353
- createdAt: z7.string(),
354
- updatedAt: z7.string(),
355
- restingAt: z7.string().optional(),
356
- dissolvedAt: z7.string().optional()
357
- });
358
- 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() });
359
- var RolesListingSchema = z7.object({
360
- type: z7.literal("roles.listing"),
361
- requestId: z7.string().optional(),
362
- roles: z7.array(RoleSchema),
363
- templates: z7.array(TeamTemplateSchema).default([]),
364
- problems: z7.array(DefinitionProblemSchema),
365
- /** Set by the fleet when the project's machine could not answer (#99): 'offline', or 'outdated' for a daemon without roles.list. */
366
- error: z7.string().optional()
367
- });
368
- var TeamLaunchSchema = z7.object({
369
- type: z7.literal("team.launch"),
370
- rid: z7.string().min(1).max(100),
371
- cwd: z7.string().min(1).optional(),
372
- template: z7.string().min(1).max(64).optional(),
373
- seats: SeatsSchema.optional(),
374
- name: TeamNameSchema.optional(),
375
- qid: z7.string().min(1).optional(),
376
- machineId: z7.string().optional(),
377
- /** The project's name, for a client that holds no path (the phone): the daemon resolves its folder. */
378
- project: ProjectIdSchema.optional()
379
- });
380
- var TeamJoinSchema = z7.object({ type: z7.literal("team.join"), team: TeamIdSchema, session: z7.string().min(1), role: RoleIdSchema });
381
- var TeamLeaveSchema = z7.object({ type: z7.literal("team.leave"), team: TeamIdSchema, session: z7.string().min(1) });
382
- var TeamResumeSchema = z7.object({ type: z7.literal("team.resume"), team: TeamIdSchema });
383
- var TeamDissolveSchema = z7.object({ type: z7.literal("team.dissolve"), team: TeamIdSchema });
384
- 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() });
385
- var DefinitionScopeSchema = z7.enum(["home", "repo"]);
386
- var RoleDraftSchema = RoleSchema.pick({ id: true, label: true, prompt: true, tool: true, model: true, effort: true, autonomy: true });
387
- var DefinitionCodeSchema = z7.enum(["changed", "exists", "missing", "unknown_project", "invalid"]);
388
- var scopeFields = { rid: z7.string().min(1).max(100), scope: DefinitionScopeSchema, project: ProjectIdSchema.optional() };
389
- 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";
390
- var DefinitionBaseSchema = DefinitionHashSchema;
391
- var DefinitionsListSchema = z7.object({ type: z7.literal("definitions.list"), requestId: z7.string().max(100).optional() });
392
- var RoleWriteSchema = z7.object({ type: z7.literal("role.write"), ...scopeFields, role: RoleDraftSchema, base: DefinitionBaseSchema.nullable() });
393
- var RoleDeleteSchema = z7.object({ type: z7.literal("role.delete"), ...scopeFields, id: RoleIdSchema, base: DefinitionBaseSchema });
394
- var TemplateWriteSchema = z7.object({
395
- type: z7.literal("template.write"),
396
- ...scopeFields,
397
- id: TemplateIdSchema.optional(),
398
- name: TeamNameSchema,
399
- seats: SeatsSchema,
400
- base: DefinitionBaseSchema.nullable()
401
- });
402
- var TemplateDeleteSchema = z7.object({ type: z7.literal("template.delete"), ...scopeFields, id: TemplateIdSchema, base: DefinitionBaseSchema });
403
- var TeamComposeSchema = z7.object({ type: z7.literal("team.compose"), rid: z7.string().min(1).max(100), team: TeamIdSchema, seats: SeatsSchema });
404
- 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"]) });
405
- var DefinitionsListingSchema = z7.object({
406
- type: z7.literal("definitions.listing"),
407
- requestId: z7.string().optional(),
408
- scopes: z7.array(z7.object({
409
- scope: DefinitionScopeSchema,
410
- project: z7.string().nullable(),
411
- roles: z7.array(RoleSchema),
412
- templates: z7.array(TeamTemplateSchema),
413
- problems: z7.array(DefinitionProblemSchema)
414
- }))
415
- });
416
- 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() });
417
- var SeatModelOutcomeSchema = z7.object({ role: z7.string(), session: z7.string(), when: z7.enum(["now", "turn"]).optional(), code: ModelRefusalSchema.optional() });
418
- var TeamChangedSchema = z7.object({ type: z7.literal("team.changed"), rid: z7.string(), team: TeamSchema.optional(), reason: z7.string().optional(), models: z7.array(SeatModelOutcomeSchema).max(16).optional() });
419
- var TeamsMessageSchema = z7.object({ type: z7.literal("teams"), teams: z7.array(TeamSchema) });
420
- var TeamUpdatedSchema = z7.object({ type: z7.literal("team.updated"), team: TeamSchema });
421
- var TeamLaunchedSchema = z7.object({ type: z7.literal("team.launched"), rid: z7.string(), team: TeamSchema });
422
- var TeamFailedSchema = z7.object({ type: z7.literal("team.failed"), rid: z7.string(), reason: z7.string(), team: TeamSchema.optional() });
423
- var TeamSavedSchema = z7.object({ type: z7.literal("team.saved"), rid: z7.string(), path: z7.string().optional(), reason: z7.string().optional() });
424
- var isTeamLive = (t) => t.state !== "dissolved";
425
-
426
- // ../protocol/src/tasks.ts
427
- import { z as z8 } from "zod";
428
- var TASK_TITLE_MAX = 200;
429
- var TASK_TEXT_MAX = 2e4;
430
- var LEDGE_GIVER = "ledge";
431
- var TASK_OPS = ["give", "take", "pass", "done", "refuse", "cancel", "drop"];
432
- var TRANSMISSION_OPS = ["give", "pass", "done", "refuse", "cancel"];
433
- var TaskLogEntrySchema = z8.object({
434
- at: z8.string(),
435
- by: z8.string(),
436
- what: z8.string(),
437
- op: z8.enum(TASK_OPS).optional(),
438
- from: z8.string().optional(),
439
- to: z8.string().optional(),
440
- fromName: z8.string().optional(),
441
- toName: z8.string().optional(),
442
- note: z8.string().optional()
443
- });
444
- var TaskSchema = z8.object({
445
- id: z8.string(),
446
- title: z8.string(),
447
- text: z8.string().optional(),
448
- giver: z8.string(),
449
- giverName: z8.string(),
450
- owner: z8.string(),
451
- ownerName: z8.string(),
452
- state: z8.enum(["open", "taken", "done", "refused", "dropped"]),
453
- result: z8.string().optional(),
454
- /** The live team its owner sat in when it was given (or passed), so a team's tasks can be listed. */
455
- team: z8.string().optional(),
456
- /** A task Ledge gave and the user put away from « À toi » once closed. */
457
- seen: z8.boolean().optional(),
458
- at: z8.string(),
459
- updatedAt: z8.string(),
460
- log: z8.array(TaskLogEntrySchema)
461
- });
462
- var handOver = (op) => TRANSMISSION_OPS.includes(op ?? "");
463
- function transmissions(t) {
464
- 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 } : {} }] : []);
465
- }
466
- var givenByLedge = (t) => t.giver === LEDGE_GIVER;
467
- var taskClosed = (t) => t.state === "done" || t.state === "refused" || t.state === "dropped";
468
- var TaskCmdSchema = z8.object({
469
- type: z8.literal("task.cmd"),
470
- rid: z8.string().min(1),
471
- by: z8.string().min(1),
472
- op: z8.enum(["give", "take", "done", "refuse", "pass", "show", "list", "seen", "cancel"]),
473
- task: z8.string().min(1).max(40).optional(),
474
- to: z8.string().min(1).optional(),
475
- title: z8.string().min(1).max(TASK_TITLE_MAX).optional(),
476
- text: z8.string().max(TASK_TEXT_MAX).optional(),
477
- all: z8.boolean().optional(),
478
- team: z8.string().min(1).max(40).optional(),
479
- qid: z8.string().min(1).optional(),
480
- row: z8.number().int().min(0).max(7).optional()
481
- });
482
- var TaskUpdatedSchema = z8.object({ type: z8.literal("task.updated"), task: TaskSchema });
483
- var TaskResultSchema = z8.object({
484
- type: z8.literal("task.result"),
485
- rid: z8.string(),
486
- task: TaskSchema.optional(),
487
- tasks: z8.array(TaskSchema).optional(),
488
- reason: z8.string().optional()
489
- });
490
-
491
344
  // ../protocol/src/actions.ts
492
- import { z as z11 } from "zod";
345
+ import { z as z7 } from "zod";
493
346
 
494
347
  // ../protocol/src/proposal.ts
495
- import { z as z9 } from "zod";
496
- var ProposalBlockSchema = z9.object({ cwd: z9.string().trim().min(1), prompt: z9.string().optional() });
497
- var RelayBlockSchema = z9.object({ session: z9.string().trim().min(1), text: z9.string().trim().min(1) });
498
- var ProjectBlockSchema = z9.object({
499
- name: z9.string().trim().min(1).max(80),
500
- description: z9.string().max(500).optional(),
501
- repos: z9.array(z9.object({ path: z9.string().min(1).max(4096), why: z9.string().trim().min(1).max(200) })).min(1).max(50),
502
- addTo: z9.string().trim().max(80).optional()
503
- });
504
- var CloseBlockSchema = z9.object({ session: z9.string().trim().min(1) });
505
- var TeamBlockSchema = z9.object({
506
- cwd: z9.string().trim().min(1),
507
- template: z9.string().trim().min(1).max(64).optional(),
508
- name: z9.string().trim().min(1).max(60).optional(),
509
- 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()
510
- }).refine((b) => !!b.template || !!b.seats?.length, "a template or seats");
511
- var TaskBlockSchema = z9.object({
512
- to: z9.string().trim().min(1).max(80),
513
- team: z9.string().trim().max(40).optional(),
514
- title: z9.string().trim().min(1).max(200),
515
- text: z9.string().max(2e4).optional()
516
- });
517
- var BLOCK = /```ledge-(session|relay|project|close|team|task|action)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
348
+ import { z as z4 } from "zod";
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()
356
+ });
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;
518
359
  function extractProposal(answer) {
519
360
  const found = [];
520
361
  const text2 = answer.replace(BLOCK, (_m, kind, body) => {
@@ -540,22 +381,6 @@ function parseCloseBlock(raw) {
540
381
  return null;
541
382
  }
542
383
  }
543
- function parseTeamBlock(raw) {
544
- try {
545
- const r = TeamBlockSchema.safeParse(JSON.parse(raw));
546
- return r.success ? r.data : null;
547
- } catch {
548
- return null;
549
- }
550
- }
551
- function parseTaskBlock(raw) {
552
- try {
553
- const r = TaskBlockSchema.safeParse(JSON.parse(raw));
554
- return r.success ? r.data : null;
555
- } catch {
556
- return null;
557
- }
558
- }
559
384
  function parseProjectBlock(raw) {
560
385
  try {
561
386
  const r = ProjectBlockSchema.safeParse(JSON.parse(raw));
@@ -565,9 +390,37 @@ function parseProjectBlock(raw) {
565
390
  }
566
391
  }
567
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
+
568
421
  // ../protocol/src/decisions.ts
569
- import { z as z10 } from "zod";
570
- var DecisionSchema = z10.enum(["accept", "deny", "always"]);
422
+ import { z as z6 } from "zod";
423
+ var DecisionSchema = z6.enum(["accept", "deny", "always"]);
571
424
  var NOTE_MAX = 4e3;
572
425
  var NOTE_NOT_TYPED = "note_not_typed";
573
426
  var DECISION_OFFERS = {
@@ -597,33 +450,22 @@ function pendingKey(p) {
597
450
  }
598
451
 
599
452
  // ../protocol/src/actions.ts
600
- var ActionRiskSchema = z11.enum(["read", "reversible", "destructive"]);
453
+ var ActionRiskSchema = z7.enum(["read", "reversible", "destructive"]);
601
454
  var ACTION_IDS = [
602
455
  "session.launch",
603
456
  "session.relay",
604
457
  "session.close",
605
458
  "project.propose",
606
- "team.launch",
607
- "task.give",
608
459
  "session.model",
609
460
  "session.interrupt",
610
461
  "session.archive",
611
462
  "session.restore",
612
463
  "session.resume",
613
- "session.decide",
614
- "team.join",
615
- "team.leave",
616
- "team.resume",
617
- "team.dissolve",
618
- "team.save",
619
- "task.cancel",
620
- "task.pass"
464
+ "session.decide"
621
465
  ];
622
- var ActionIdSchema = z11.enum(ACTION_IDS);
466
+ var ActionIdSchema = z7.enum(ACTION_IDS);
623
467
  var def = (d) => d;
624
- var SessionRef = z11.string().trim().min(1).max(80);
625
- var TeamRef = z11.string().trim().min(1).max(60);
626
- var TaskRef = z11.string().trim().min(1).max(40);
468
+ var SessionRef = z7.string().trim().min(1).max(80);
627
469
  var LaunchArgs = ProposalBlockSchema.extend({ model: ModelIdSchema.optional(), effort: EffortSchema.optional() });
628
470
  var ACTIONS = [
629
471
  def({
@@ -671,44 +513,20 @@ var ACTIONS = [
671
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",
672
514
  example: { name: "Facturation", description: "Le service et son front", repos: [{ path: "/home/alice/projets/billing-api", why: "le service" }] }
673
515
  }),
674
- def({
675
- id: "team.launch",
676
- args: TeamBlockSchema,
677
- card: "team",
678
- phone: "launch",
679
- risk: "reversible",
680
- max: 1,
681
- cli: "ledge team up <mod\xE8le> --cwd <dossier>",
682
- 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)",
683
- 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",
684
- example: { cwd: "/home/alice/projets/api", name: "api", seats: [{ role: "dev-back", tool: "claude" }, { role: "rev-back", tool: "codex" }] }
685
- }),
686
- def({
687
- id: "task.give",
688
- args: TaskBlockSchema,
689
- card: "task",
690
- phone: null,
691
- risk: "reversible",
692
- max: 8,
693
- cli: 'ledge task give <session|@r\xF4le> "<titre>" "<d\xE9tail>"',
694
- 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)",
695
- 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",
696
- example: { to: "@dev-back", title: "Pagination de /invoices", text: "Ajoute la pagination par curseur et ses tests" }
697
- }),
698
516
  def({
699
517
  id: "session.model",
700
518
  card: "generic",
701
519
  phone: null,
702
520
  risk: "reversible",
703
521
  max: 8,
704
- args: z11.object({ session: SessionRef, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
522
+ args: z7.object({ session: SessionRef, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
705
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",
706
524
  when: "quand l'utilisateur demande de passer une session sur un autre mod\xE8le ou un autre effort",
707
525
  example: { session: "Nova", model: "claude-opus-5-5", effort: "high" }
708
526
  }),
709
527
  def({
710
528
  id: "session.interrupt",
711
- args: z11.object({ session: SessionRef }),
529
+ args: z7.object({ session: SessionRef }),
712
530
  card: "generic",
713
531
  phone: null,
714
532
  risk: "reversible",
@@ -719,7 +537,7 @@ var ACTIONS = [
719
537
  }),
720
538
  def({
721
539
  id: "session.archive",
722
- args: z11.object({ session: SessionRef }),
540
+ args: z7.object({ session: SessionRef }),
723
541
  card: "generic",
724
542
  phone: null,
725
543
  risk: "reversible",
@@ -730,7 +548,7 @@ var ACTIONS = [
730
548
  }),
731
549
  def({
732
550
  id: "session.restore",
733
- args: z11.object({ session: SessionRef }),
551
+ args: z7.object({ session: SessionRef }),
734
552
  card: "generic",
735
553
  phone: null,
736
554
  risk: "reversible",
@@ -741,7 +559,7 @@ var ACTIONS = [
741
559
  }),
742
560
  def({
743
561
  id: "session.resume",
744
- args: z11.object({ session: SessionRef }),
562
+ args: z7.object({ session: SessionRef }),
745
563
  card: "generic",
746
564
  phone: "act",
747
565
  risk: "reversible",
@@ -755,102 +573,22 @@ var ACTIONS = [
755
573
  card: "generic",
756
574
  phone: "act",
757
575
  max: 1,
758
- 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() }),
759
577
  risk: (a) => a.decision === "accept" ? "destructive" : "reversible",
760
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",
761
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",
762
580
  example: { session: "Nova", decision: "deny", note: "Pas sur main : cr\xE9e une branche" }
763
- }),
764
- def({
765
- id: "team.join",
766
- args: z11.object({ team: TeamRef, session: SessionRef, role: RoleIdSchema }),
767
- card: "generic",
768
- phone: null,
769
- risk: "reversible",
770
- max: 8,
771
- summary: "faire entrer une session dans une \xE9quipe en cours, \xE0 un r\xF4le de roles.md",
772
- when: "quand l'utilisateur demande d'ajouter une session existante \xE0 une \xE9quipe",
773
- example: { team: "team-3fa2", session: "Nova", role: "dev-back" }
774
- }),
775
- def({
776
- id: "team.leave",
777
- args: z11.object({ team: TeamRef, session: SessionRef }),
778
- card: "generic",
779
- phone: null,
780
- risk: "reversible",
781
- max: 8,
782
- summary: "faire sortir une session d'une \xE9quipe (la session continue)",
783
- when: "quand l'utilisateur demande de retirer une session d'une \xE9quipe",
784
- example: { team: "team-3fa2", session: "Nova" }
785
- }),
786
- def({
787
- id: "team.resume",
788
- args: z11.object({ team: TeamRef }),
789
- card: "generic",
790
- phone: "act",
791
- risk: "reversible",
792
- max: 1,
793
- summary: "reprendre une \xE9quipe au repos : chaque si\xE8ge reprend sa conversation",
794
- when: "quand l'utilisateur demande de reprendre ou relancer une \xE9quipe au repos",
795
- example: { team: "team-3fa2" }
796
- }),
797
- def({
798
- id: "team.dissolve",
799
- args: z11.object({ team: TeamRef }),
800
- card: "generic",
801
- phone: "act",
802
- risk: "destructive",
803
- max: 1,
804
- cli: "ledge team dissolve <\xE9quipe>",
805
- summary: "dissoudre une \xE9quipe : elle dispara\xEEt, ses sessions restent ; irr\xE9versible",
806
- when: "seulement quand l'utilisateur demande explicitement de dissoudre une \xE9quipe pr\xE9cise",
807
- example: { team: "team-3fa2" }
808
- }),
809
- def({
810
- id: "team.save",
811
- args: z11.object({ team: TeamRef, name: TeamNameSchema }),
812
- card: "generic",
813
- phone: null,
814
- risk: "reversible",
815
- max: 1,
816
- summary: "enregistrer une \xE9quipe en cours comme mod\xE8le (~/.ledge/teams/<nom>.yaml)",
817
- when: "quand l'utilisateur demande d'enregistrer ou de garder la composition d'une \xE9quipe",
818
- example: { team: "team-3fa2", name: "api" }
819
- }),
820
- def({
821
- id: "task.cancel",
822
- args: z11.object({ task: TaskRef, reason: z11.string().trim().min(1).max(500).optional() }),
823
- card: "generic",
824
- phone: "act",
825
- risk: "destructive",
826
- max: 8,
827
- summary: "annuler une t\xE2che ouverte ou prise ; la session qui la tient est pr\xE9venue ; irr\xE9versible",
828
- when: "seulement quand l'utilisateur demande explicitement d'annuler une t\xE2che de tasks.md",
829
- example: { task: "t91b0d4", reason: "Plus n\xE9cessaire" }
830
- }),
831
- def({
832
- id: "task.pass",
833
- args: z11.object({ task: TaskRef, to: SessionRef, note: z11.string().trim().min(1).max(TASK_TEXT_MAX).optional() }),
834
- card: "generic",
835
- phone: null,
836
- risk: "reversible",
837
- max: 8,
838
- summary: "passer une t\xE2che de tasks.md \xE0 une autre session (elle redevient ouverte)",
839
- when: "quand l'utilisateur demande de r\xE9assigner ou de passer une t\xE2che \xE0 une autre session",
840
- example: { task: "t91b0d4", to: "Bix" }
841
581
  })
842
582
  ];
843
583
  var byId = new Map(ACTIONS.map((a) => [a.id, a]));
844
- var actionById = (id) => byId.get(id);
584
+ var actionById = (id2) => byId.get(id2);
845
585
  var riskOf = (a, args) => typeof a.risk === "function" ? a.risk(args) : a.risk;
846
- 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({}) });
847
587
  var LEGACY_FENCE_ACTION = {
848
588
  session: "session.launch",
849
589
  relay: "session.relay",
850
590
  close: "session.close",
851
- project: "project.propose",
852
- team: "team.launch",
853
- task: "task.give"
591
+ project: "project.propose"
854
592
  };
855
593
  function parseActionBlock(kind, raw) {
856
594
  let json;
@@ -859,21 +597,21 @@ function parseActionBlock(kind, raw) {
859
597
  } catch {
860
598
  return { error: "invalid JSON" };
861
599
  }
862
- let id;
600
+ let id2;
863
601
  let args;
864
602
  if (kind === "action") {
865
603
  const b = ActionBlockSchema.safeParse(json);
866
604
  if (!b.success) return { error: "not an {action, args} object" };
867
- id = b.data.action;
605
+ id2 = b.data.action;
868
606
  args = b.data.args;
869
607
  } else {
870
- id = LEGACY_FENCE_ACTION[kind];
608
+ id2 = LEGACY_FENCE_ACTION[kind];
871
609
  args = json;
872
610
  }
873
- const action = actionById(id);
874
- if (!action) return { error: `unknown action ${id}` };
611
+ const action = actionById(id2);
612
+ if (!action) return { error: `unknown action ${id2}` };
875
613
  const parsed = action.args.safeParse(args);
876
- 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("; ")}` };
877
615
  return { action, args: parsed.data };
878
616
  }
879
617
  var NEVER_PROPOSED = [
@@ -885,123 +623,89 @@ var NEVER_PROPOSED = [
885
623
  "r\xE9pondre \xAB toujours \xBB \xE0 une demande, ou r\xE9pondre aux questions \xE0 choix d'OMP"
886
624
  ];
887
625
 
888
- // ../protocol/src/messages.ts
889
- import { z as z21 } from "zod";
626
+ // ../protocol/src/openrig.ts
627
+ import { z as z10 } from "zod";
890
628
 
891
629
  // ../protocol/src/chat.ts
892
- import { z as z13 } from "zod";
630
+ import { z as z9 } from "zod";
893
631
 
894
632
  // ../protocol/src/attachments.ts
895
- import { z as z12 } from "zod";
633
+ import { z as z8 } from "zod";
896
634
  var ATTACHMENT_ID = /^[0-9a-f]{64}$/;
897
- var AttachmentIdSchema = z12.string().regex(ATTACHMENT_ID);
635
+ var AttachmentIdSchema = z8.string().regex(ATTACHMENT_ID);
898
636
  var IMAGE_MIMES = ["image/webp", "image/jpeg", "image/png", "image/gif"];
899
- var ImageMimeSchema = z12.enum(IMAGE_MIMES);
637
+ var ImageMimeSchema = z8.enum(IMAGE_MIMES);
900
638
  var ATTACHMENT_MAX = 4e5;
901
639
  var ATTACHMENT_LOCAL_MAX = 2e6;
902
640
  var PREVIEW_MAX = 4e4;
903
641
  var ATTACHMENTS_MAX = 5;
904
642
  var b64Max = (bytes) => Math.ceil(bytes / 3) * 4;
905
- var AttachmentRefSchema = z12.object({
643
+ var AttachmentRefSchema = z8.object({
906
644
  id: AttachmentIdSchema,
907
645
  mime: ImageMimeSchema,
908
- width: z12.number().int().positive(),
909
- height: z12.number().int().positive(),
910
- bytes: z12.number().int().positive(),
911
- 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()
912
650
  });
913
- var ImageUploadSchema = z12.object({
651
+ var ImageUploadSchema = z8.object({
914
652
  mime: ImageMimeSchema,
915
- data: z12.string().min(1).max(b64Max(ATTACHMENT_LOCAL_MAX)),
916
- preview: z12.object({ mime: ImageMimeSchema, data: z12.string().min(1).max(b64Max(PREVIEW_MAX)) }).optional()
917
- });
918
- var AttachmentErrorReasonSchema = z12.enum(["too_large", "not_image", "unreadable", "not_found", "offline"]);
919
- var requestId = z12.string().min(1).max(100);
920
- var AttachmentPutSchema = z12.object({ type: z12.literal("attachment.put"), requestId, id: z12.string().optional(), image: ImageUploadSchema });
921
- var AttachmentGetSchema = z12.object({ type: z12.literal("attachment.get"), requestId, attachment: AttachmentIdSchema, preview: z12.boolean().optional(), id: z12.string().optional(), machine: z12.string().optional() });
922
- var AttachmentStoredSchema = z12.object({ type: z12.literal("attachment.stored"), requestId, ref: AttachmentRefSchema });
923
- var AttachmentDataSchema = z12.object({ type: z12.literal("attachment.data"), requestId, attachment: AttachmentIdSchema, mime: ImageMimeSchema, data: z12.string() });
924
- 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 });
925
663
 
926
664
  // ../protocol/src/chat.ts
927
- var ChatStepSchema = z13.object({
928
- kind: z13.enum(["sessions", "pane", "reply", "git", "repo"]),
929
- id: z13.string().optional(),
930
- name: z13.string().optional(),
931
- path: z13.string().optional()
932
- });
933
- var ChatProposalSchema = z13.object({
934
- cwd: z13.string().min(1),
935
- project: z13.string(),
936
- 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(),
937
675
  tool: ToolSchema,
938
676
  /** What Ledge proposed on the card's picker (spec 2026-10-26 action registry §4), the agent's own default when left out. */
939
677
  model: ModelIdSchema.optional(),
940
678
  effort: EffortSchema.optional(),
941
- launchedId: z13.string().optional(),
942
- error: z13.string().optional()
679
+ launchedId: z9.string().optional(),
680
+ error: z9.string().optional()
943
681
  });
944
682
  var MAX_PROPOSALS = 8;
945
- var ChatProposalsSchema = z13.array(ChatProposalSchema).min(1).max(MAX_PROPOSALS);
946
- var ChatRelaySchema = z13.object({
947
- id: z13.string().min(1),
948
- 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(),
949
687
  tool: ToolSchema,
950
- text: z13.string().min(1),
951
- status: z13.enum(["queued", "sent"]).optional(),
952
- sentAt: z13.string().datetime().optional(),
953
- error: z13.string().optional()
954
- });
955
- var ChatCloseSchema = z13.object({
956
- id: z13.string().min(1),
957
- name: 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(),
958
696
  tool: ToolSchema,
959
- closedAt: z13.string().datetime().optional(),
960
- how: z13.enum(["exit", "killed"]).optional(),
961
- error: z13.string().optional()
962
- });
963
- var ChatClosesSchema = z13.array(ChatCloseSchema).min(1).max(MAX_PROPOSALS);
964
- var ChatProjectProposalSchema = z13.object({
965
- name: z13.string().min(1).max(80),
966
- description: z13.string().max(500).optional(),
967
- repos: z13.array(z13.object({ path: z13.string().min(1).max(4096), why: z13.string().min(1).max(200) })).min(1).max(50),
968
- addTo: z13.string().min(1).max(80).optional()
969
- });
970
- var ChatTeamSeatSchema = z13.object({
971
- role: z13.string().min(1),
972
- label: z13.string(),
973
- tool: ToolSchema,
974
- model: z13.string().optional(),
975
- effort: z13.string().optional(),
976
- autonomy: AutonomySchema.optional(),
977
- name: z13.string().optional()
978
- });
979
- var ChatTeamProposalSchema = z13.object({
980
- cwd: z13.string().min(1),
981
- project: z13.string(),
982
- projectName: z13.string().optional(),
983
- name: z13.string().min(1),
984
- template: z13.string().optional(),
985
- seats: z13.array(ChatTeamSeatSchema).min(1).max(8),
986
- teamId: z13.string().optional(),
987
- launchedAt: z13.string().datetime().optional(),
988
- error: z13.string().optional(),
989
- savedPath: z13.string().optional()
990
- });
991
- var ChatTaskProposalSchema = z13.object({
992
- to: z13.string().min(1),
993
- toName: z13.string().optional(),
994
- team: z13.string().optional(),
995
- title: z13.string().min(1).max(200),
996
- text: z13.string().max(2e4).optional(),
997
- taskId: z13.string().optional(),
998
- ownerName: z13.string().optional(),
999
- state: z13.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
1000
- givenAt: z13.string().datetime().optional(),
1001
- error: z13.string().optional()
1002
- });
1003
- var ChatTasksSchema = z13.array(ChatTaskProposalSchema).min(1).max(MAX_PROPOSALS);
1004
- 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));
1005
709
  var DETAIL_MAX = 4e3;
1006
710
  var ACTION_ERROR_CODES = [
1007
711
  "unknown",
@@ -1016,98 +720,381 @@ var ACTION_ERROR_CODES = [
1016
720
  "no_request",
1017
721
  "in_session",
1018
722
  "no_note",
1019
- "member",
1020
- "not_member",
1021
- "unknown_role",
1022
- "not_resting",
1023
- "dissolved",
1024
- "no_seat",
1025
- "closed",
1026
- "holder",
1027
723
  "changed",
1028
724
  "cut",
1029
725
  "failed"
1030
726
  ];
1031
- var ChatActionStateSchema = z13.enum(["running", "done", "denied", "failed", "refused"]);
1032
- var ChatActionSchema = z13.object({
1033
- action: z13.string().min(1).max(40),
1034
- args: z13.record(z13.unknown()),
1035
- 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),
1036
732
  vars: ActionVarsSchema.optional(),
1037
- detail: z13.string().max(DETAIL_MAX).optional(),
1038
- detailCut: z13.literal(true).optional(),
733
+ detail: z9.string().max(DETAIL_MAX).optional(),
734
+ detailCut: z9.literal(true).optional(),
1039
735
  risk: ActionRiskSchema,
1040
- session: z13.string().optional(),
1041
- team: z13.string().optional(),
736
+ session: z9.string().optional(),
1042
737
  state: ChatActionStateSchema.optional(),
1043
- doneAt: z13.string().datetime().optional(),
1044
- error: z13.string().optional(),
1045
- code: z13.string().max(40).optional()
1046
- });
1047
- var ChatActionsSchema = z13.array(ChatActionSchema).min(1).max(MAX_PROPOSALS);
1048
- var ChatTurnSchema = z13.object({
1049
- machineId: z13.string().optional(),
1050
- machineName: z13.string().optional(),
1051
- choices: z13.array(z13.object({ id: z13.string(), label: z13.string() })).optional(),
1052
- qid: z13.string().min(1),
1053
- question: z13.string(),
1054
- askedAt: z13.string().datetime(),
1055
- steps: z13.array(ChatStepSchema),
1056
- answer: z13.string().optional(),
1057
- answeredAt: z13.string().datetime().optional(),
1058
- error: z13.string().optional(),
738
+ doneAt: z9.string().datetime().optional(),
739
+ error: z9.string().optional(),
740
+ code: z9.string().max(40).optional()
741
+ });
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(),
1059
761
  proposal: ChatProposalSchema.optional(),
1060
762
  proposals: ChatProposalsSchema.optional(),
1061
763
  relay: ChatRelaySchema.optional(),
1062
764
  closes: ChatClosesSchema.optional(),
1063
765
  projectProposal: ChatProjectProposalSchema.optional(),
1064
- team: ChatTeamProposalSchema.optional(),
1065
- tasks: ChatTasksSchema.optional(),
1066
766
  actions: ChatActionsSchema.optional(),
1067
- ignoredAt: z13.string().datetime().optional(),
1068
- attachments: z13.array(AttachmentRefSchema).optional()
767
+ rigLaunch: ChatRigLaunchSchema.optional(),
768
+ ignoredAt: z9.string().datetime().optional(),
769
+ attachments: z9.array(AttachmentRefSchema).optional()
1069
770
  });
1070
771
  var proposalRows = (t) => t?.proposals ?? (t?.proposal ? [t.proposal] : []);
1071
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
+
1072
1059
  // ../protocol/src/repos.ts
1073
- import { z as z14 } from "zod";
1074
- var ReposInspectSchema = z14.object({
1075
- type: z14.literal("repos.inspect"),
1076
- requestId: z14.string().min(1).max(100),
1077
- paths: z14.array(z14.string().min(1)).min(1).max(50),
1078
- machineId: z14.string().optional()
1079
- });
1080
- var RepoInfoSchema = z14.object({ path: z14.string(), exists: z14.boolean(), branch: z14.string().nullable() });
1081
- 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) });
1082
1069
 
1083
1070
  // ../protocol/src/scan.ts
1084
- import { z as z15 } from "zod";
1085
- var ReposScanSchema = z15.object({ type: z15.literal("repos.scan"), requestId: z15.string().min(1).max(100), machineId: z15.string().optional() });
1086
- var FoundRepoSchema = z15.object({ path: z15.string(), branch: z15.string().nullable(), remote: z15.string().optional(), manifest: z15.string().max(200).optional() });
1087
- 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() });
1088
1075
 
1089
1076
  // ../protocol/src/documents.ts
1090
- import { z as z16 } from "zod";
1077
+ import { z as z13 } from "zod";
1091
1078
  var DOCUMENT_MAX_BYTES = 1048576;
1092
- var path = z16.string().min(1).max(4096);
1093
- var requestId2 = z16.string().min(1);
1094
- var DocumentReadSchema = z16.object({
1095
- 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"),
1096
1083
  requestId: requestId2,
1097
1084
  path,
1098
- machineId: z16.string().optional(),
1099
- sessionId: z16.string().min(1).optional(),
1085
+ machineId: z13.string().optional(),
1086
+ sessionId: z13.string().min(1).optional(),
1100
1087
  basePath: path.optional()
1101
1088
  });
1102
- var DocumentContentSchema = z16.object({
1103
- type: z16.literal("document.content"),
1089
+ var DocumentContentSchema = z13.object({
1090
+ type: z13.literal("document.content"),
1104
1091
  requestId: requestId2,
1105
1092
  path,
1106
- text: z16.string(),
1107
- bytes: z16.number().int().nonnegative(),
1108
- modifiedAt: z16.string().datetime()
1093
+ text: z13.string(),
1094
+ bytes: z13.number().int().nonnegative(),
1095
+ modifiedAt: z13.string().datetime()
1109
1096
  });
1110
- var DocumentErrorCodeSchema = z16.enum([
1097
+ var DocumentErrorCodeSchema = z13.enum([
1111
1098
  "not_found",
1112
1099
  "not_file",
1113
1100
  "unsupported_path",
@@ -1119,62 +1106,155 @@ var DocumentErrorCodeSchema = z16.enum([
1119
1106
  "session_missing",
1120
1107
  "read_failed"
1121
1108
  ]);
1122
- var DocumentErrorSchema = z16.object({
1123
- type: z16.literal("document.error"),
1109
+ var DocumentErrorSchema = z13.object({
1110
+ type: z13.literal("document.error"),
1124
1111
  requestId: requestId2,
1125
1112
  path,
1126
1113
  code: DocumentErrorCodeSchema
1127
1114
  });
1128
1115
 
1129
1116
  // ../protocol/src/routing.ts
1130
- import { z as z17 } from "zod";
1131
- var MachineInventorySchema = z17.object({
1132
- id: z17.string().min(1).max(80),
1133
- label: z17.string().min(1).max(120),
1134
- online: z17.boolean(),
1135
- local: z17.boolean(),
1136
- 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),
1137
- folders: z17.array(z17.object({ project: z17.string(), cwd: z17.string() })).max(100)
1138
- });
1139
- var RoutingContextSchema = z17.object({
1140
- machines: z17.array(MachineInventorySchema).min(1).max(20),
1141
- history: z17.array(z17.object({ question: z17.string().max(2e4), answer: z17.string().max(2e4).optional(), machineId: z17.string().optional() })).max(50)
1142
- });
1143
- 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({
1144
1131
  /** Null means an answer or a clarification, never an implicit local destination. */
1145
- machineId: z17.string().nullable(),
1146
- text: z17.string().min(1).max(2e4),
1147
- 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
+ });
1148
1161
  });
1149
1162
 
1150
1163
  // ../protocol/src/island.ts
1151
- import { z as z18 } from "zod";
1152
- var IslandScaleSchema = z18.union([z18.literal("auto"), z18.literal(1), z18.literal(1.25), z18.literal(1.5), z18.literal(2)]);
1153
- 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({
1154
1167
  /** Shown at the top of the screen; hidden, Desktop still notifies. */
1155
- visible: z18.boolean(),
1168
+ visible: z17.boolean(),
1156
1169
  /** `primary` (whichever it is after a reconfiguration), or a monitor's id as the shell lists it (`list_monitors`: the OS
1157
1170
  * name, `#index` without one). `second`, written by older fronts, is the first monitor that is not the primary. */
1158
- screen: z18.string().trim().min(1).max(200),
1159
- position: z18.enum(["center", "left", "right"]),
1171
+ screen: z17.string().trim().min(1).max(200),
1172
+ position: z17.enum(["center", "left", "right"]),
1160
1173
  /** A strip under a maximised window. */
1161
- discreet: z18.boolean(),
1174
+ discreet: z17.boolean(),
1162
1175
  islandScale: IslandScaleSchema,
1163
1176
  panelScale: IslandScaleSchema,
1164
1177
  /** What the island shows besides asks and questions, which it always shows. */
1165
- shows: z18.object({
1178
+ shows: z17.object({
1166
1179
  /** Allow, deny or answer in a line from the island; off, its cards only say « Open in Ledge ». */
1167
- quick: z18.boolean(),
1180
+ quick: z17.boolean(),
1168
1181
  /** The 6 s bubble when an agent finishes. */
1169
- finished: z18.boolean(),
1182
+ finished: z17.boolean(),
1170
1183
  /** An agent's sentence in a bubble. */
1171
- say: z18.boolean(),
1184
+ say: z17.boolean(),
1172
1185
  /** The sub-agents next to their main agent. */
1173
- helpers: z18.boolean()
1186
+ helpers: z17.boolean()
1174
1187
  }),
1175
1188
  /** The global shortcut that opens the island, as shown (`Ctrl Alt L`). */
1176
- 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 });
1177
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
+ }
1178
1258
 
1179
1259
  // ../protocol/src/thread.ts
1180
1260
  import { z as z19 } from "zod";
@@ -1203,13 +1283,6 @@ var ThreadItemSchema = z19.discriminatedUnion("kind", [
1203
1283
  kind: z19.literal("notice"),
1204
1284
  ...base,
1205
1285
  notice: z19.enum(["idle", "exited", "unavailable", "expired", "delivery", "other"]),
1206
- /** An `other` notice from Ledge: `task`, the task a `<ledge-task>` is about; `team`, the team a `<ledge-notice team>` seated the
1207
- * session in (roles spec §3.3). An older front, which knows neither, shows it as any other notice. */
1208
- task: z19.string().optional(),
1209
- team: z19.string().optional(),
1210
- /** A task this session handed over (`outgoing`), and how (issue #92). */
1211
- taskOp: z19.enum(TRANSMISSION_OPS).optional(),
1212
- outgoing: z19.literal(true).optional(),
1213
1286
  name: z19.string().optional(),
1214
1287
  peer: z19.string().optional(),
1215
1288
  time: z19.string().optional(),
@@ -1272,7 +1345,7 @@ var MobileStateSchema = z20.object({
1272
1345
 
1273
1346
  // ../protocol/src/messages.ts
1274
1347
  var PROTOCOL_PORT = 47831;
1275
- 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"]);
1276
1349
  var SessionEventSchema = z21.object({
1277
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"]),
1278
1351
  at: z21.string().datetime(),
@@ -1341,8 +1414,15 @@ var DaemonInfoSchema = z21.object({
1341
1414
  /** Its home folder, so a client shows the folders under it as `~` (absent from older daemons). */
1342
1415
  home: z21.string().optional(),
1343
1416
  mobile: z21.boolean().optional(),
1344
- /** Knows roles, teams and tasks given by Ledge (spec 2026-10-25 roles). */
1345
- 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()
1346
1426
  });
1347
1427
  var UpdateFailureSchema = z21.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
1348
1428
  var ServerMessageSchema = z21.discriminatedUnion("type", [
@@ -1372,6 +1452,8 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1372
1452
  }),
1373
1453
  z21.object({ type: z21.literal("session.removed"), id: z21.string() }),
1374
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()) }),
1375
1457
  /** A session's thread, on request (spec 2026-10-13 §5); then each created or updated item, broadcast. */
1376
1458
  z21.object({ type: z21.literal("session.thread"), id: z21.string(), items: z21.array(ThreadItemSchema) }),
1377
1459
  z21.object({ type: z21.literal("session.thread.item"), id: z21.string(), item: ThreadItemSchema }),
@@ -1397,7 +1479,7 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1397
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. */
1398
1480
  z21.object({ type: z21.literal("chat.step"), qid: z21.string(), step: ChatStepSchema }),
1399
1481
  z21.object({ type: z21.literal("chat.delta"), qid: z21.string(), text: z21.string() }),
1400
- 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() }),
1401
1483
  /** What a `chat.act` applied: the rows done, and the one that failed with why (the rows after it stay proposed). */
1402
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({
1403
1485
  row: z21.number().int().min(0),
@@ -1407,7 +1489,7 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1407
1489
  })) }),
1408
1490
  /** What an `action.run` came to: `row` the action as the daemon resolved it (its summary, its risk); `reason` why it was not
1409
1491
  * done, with `code` (`refused`: not possible now; `agent`: a session may not run it; `confirm_required`: destructive, ask
1410
- * 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. */
1411
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() }),
1412
1494
  z21.object({ type: z21.literal("chat.error"), qid: z21.string(), message: z21.string() }),
1413
1495
  z21.object({ type: z21.literal("chat.history"), turns: z21.array(ChatTurnSchema) }),
@@ -1427,20 +1509,31 @@ var ServerMessageSchema = z21.discriminatedUnion("type", [
1427
1509
  z21.object({ type: z21.literal("message.ack"), rid: z21.string(), outcome: z21.enum(["accepted", "queued", "native", "refused"]), reason: z21.string().optional() }),
1428
1510
  /** The answer to a `message.read`: the body with its sender, target and time, or the refusal's `reason`. */
1429
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() }),
1430
- TaskResultSchema,
1431
1512
  SessionHandoverResultSchema,
1432
1513
  ContextPrefsMessageSchema,
1433
1514
  HandoverResultSchema,
1434
- TaskUpdatedSchema,
1435
- RolesListingSchema,
1436
- TeamsMessageSchema,
1437
- TeamUpdatedSchema,
1438
- TeamLaunchedSchema,
1439
- TeamFailedSchema,
1440
- TeamSavedSchema,
1441
- DefinitionsListingSchema,
1442
- DefinitionResultSchema,
1443
- 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,
1444
1537
  z21.object({
1445
1538
  type: z21.literal("error"),
1446
1539
  message: z21.string(),
@@ -1483,10 +1576,6 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1483
1576
  effort: EffortSchema.optional(),
1484
1577
  /** OMP only: its approval mode, `always-ask` when left out. */
1485
1578
  approval: OmpApprovalSchema.optional(),
1486
- /** A role (spec 2026-10-25 roles §3.4): its prompt reaches the agent at every start, its defaults fill what is left out. */
1487
- role: RoleIdSchema.optional(),
1488
- /** A live team the new session joins at once, in `role`. */
1489
- team: TeamIdSchema.optional(),
1490
1579
  /** Routing by the fleet, removed before the message reaches the daemon. */
1491
1580
  machineId: z21.string().optional()
1492
1581
  }),
@@ -1574,10 +1663,20 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1574
1663
  z21.object({ type: z21.literal("session.delete"), id: z21.string() }),
1575
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. */
1576
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) }),
1577
1668
  z21.object({ type: z21.literal("session.thread"), id: z21.string() }),
1578
1669
  z21.object({ type: z21.literal("session.register"), session: SessionSchema }),
1579
1670
  /** `agent` and `cwd` describe the session when the daemon does not know it yet (a hook from an agent started without ledge). */
1580
- 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
+ }),
1581
1680
  z21.object({ type: z21.literal("session.unregister"), id: z21.string(), exitCode: z21.number().int().nullable() }),
1582
1681
  z21.object({ type: z21.literal("snapshot.request") }),
1583
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() }),
@@ -1607,29 +1706,33 @@ var ClientMessageSchema = z21.discriminatedUnion("type", [
1607
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() }),
1608
1707
  /** `ledge read <id>`: a message's whole body, for its receiver or its sender (`by`, a session id). */
1609
1708
  z21.object({ type: z21.literal("message.read"), id: z21.string().min(1).max(40), by: z21.string().min(1) }),
1610
- TaskCmdSchema,
1709
+ AskCreateSchema,
1710
+ AskAnswerSchema,
1611
1711
  SessionHandoverSchema,
1612
1712
  ContextSetSchema,
1613
1713
  HandoverBriefSchema,
1614
1714
  HandoverShowSchema,
1615
- RolesListSchema,
1616
- TeamLaunchSchema,
1617
- TeamJoinSchema,
1618
- TeamLeaveSchema,
1619
- TeamResumeSchema,
1620
- TeamDissolveSchema,
1621
- TeamSaveSchema,
1622
- DefinitionsListSchema,
1623
- RoleWriteSchema,
1624
- RoleDeleteSchema,
1625
- TemplateWriteSchema,
1626
- TemplateDeleteSchema,
1627
- TeamComposeSchema,
1628
- TeamSeatSchema
1629
- ]).superRefine((m, ctx) => {
1630
- const issue = "scope" in m ? scopeIssue(m) : null;
1631
- if (issue) ctx.addIssue({ code: z21.ZodIssueCode.custom, message: issue, path: ["project"] });
1632
- });
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
+ ]);
1633
1736
  function parseClientMessage(raw) {
1634
1737
  return ClientMessageSchema.parse(raw);
1635
1738
  }
@@ -1802,9 +1905,61 @@ var MeResponseSchema = z23.object({
1802
1905
  // ../protocol/src/phone.ts
1803
1906
  import { z as z26 } from "zod";
1804
1907
 
1805
- // ../protocol/src/push.ts
1908
+ // ../protocol/src/phoneOpenrig.ts
1806
1909
  import { z as z24 } from "zod";
1807
- 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_-]*$/);
1808
1963
  var PUSH_HOSTS = ["*.push.apple.com", "fcm.googleapis.com", "updates.push.services.mozilla.com", "*.notify.windows.com"];
1809
1964
  function pushEndpointAllowed(url) {
1810
1965
  let u;
@@ -1821,76 +1976,34 @@ function pushEndpointAllowed(url) {
1821
1976
  return host.length > suffix.length && host.endsWith(suffix);
1822
1977
  });
1823
1978
  }
1824
- var PushSendSchema = z24.object({
1825
- endpoint: z24.string().url().max(1024),
1979
+ var PushSendSchema = z25.object({
1980
+ endpoint: z25.string().url().max(1024),
1826
1981
  body: B64U.max(5462),
1827
- ttl: z24.number().int().min(0).max(86400),
1828
- urgency: z24.enum(["normal", "high"]),
1829
- topic: z24.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1830
- });
1831
- var PushSendResultSchema = z24.object({
1832
- result: z24.enum(["sent", "gone", "failed"]),
1833
- status: z24.number().int().optional()
1834
- });
1835
- var PushSubscriptionSchema = z24.object({
1836
- endpoint: z24.string().url().max(1024).refine(pushEndpointAllowed),
1837
- keys: z24.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1838
- });
1839
- var PushPayloadSchema = z24.object({
1840
- v: z24.literal(1),
1841
- kind: z24.enum(["request", "question", "finished", "chat"]),
1842
- title: z24.string().max(80),
1843
- body: z24.string().max(120),
1844
- url: z24.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
1845
- badge: z24.number().int().min(0).max(999),
1846
- 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}$/)
1847
2005
  });
1848
2006
 
1849
- // ../protocol/src/phoneTeams.ts
1850
- import { z as z25 } from "zod";
1851
- var scope = DefinitionScopeSchema;
1852
- var PhoneSeatsSchema = z25.array(SeatSchema.strict()).min(1).max(TEAM_SEATS_MAX).refine(uniqueRoles, "a role twice in one team");
1853
- var PhoneRoleDraftSchema = RoleDraftSchema.strict();
1854
- var projectName = z25.string().max(80).nullable();
1855
- var PhoneRoleSchema = z25.object({
1856
- id: RoleIdSchema,
1857
- label: z25.string().min(1).max(ROLE_LABEL_MAX),
1858
- prompt: z25.string().max(ROLE_PROMPT_MAX),
1859
- cut: z25.literal(true).optional(),
1860
- tool: ToolSchema.optional(),
1861
- model: ModelIdSchema.optional(),
1862
- effort: EffortSchema.optional(),
1863
- autonomy: AutonomySchema.optional(),
1864
- scope,
1865
- project: projectName,
1866
- hash: DefinitionHashSchema,
1867
- shadows: z25.literal(true).optional(),
1868
- shadowed: z25.literal(true).optional(),
1869
- shadowedIn: z25.array(z25.string().max(80)).min(1).max(8).optional()
1870
- }).strict();
1871
- var PhoneTemplateSchema = z25.object({ id: TemplateIdSchema, name: TeamNameSchema, seats: PhoneSeatsSchema, scope, project: projectName, hash: DefinitionHashSchema }).strict();
1872
- var PhoneProblemSchema = z25.object({ file: z25.string().max(80), scope, project: projectName, reason: z25.string().max(200) }).strict();
1873
- var PhoneCatalogSchema = z25.object({
1874
- /** The names of the projects with a local repository: the ones a « Dépôt » scope or a « Lever » can name. */
1875
- projects: z25.array(z25.string().max(80)).max(50),
1876
- roles: z25.array(PhoneRoleSchema).max(200),
1877
- templates: z25.array(PhoneTemplateSchema).max(100),
1878
- problems: z25.array(PhoneProblemSchema).max(50)
1879
- }).strict();
1880
- var TEAMS_OPERATION_TYPES = ["role.write", "role.delete", "template.write", "template.delete", "team.launch", "team.compose", "team.seat", "team.dissolve"];
1881
- var isTeamsOperation = (m) => TEAMS_OPERATION_TYPES.includes(m.type);
1882
- function canonicalJson(v) {
1883
- const sort = (x) => {
1884
- if (Array.isArray(x)) return x.map(sort);
1885
- if (x !== null && typeof x === "object") {
1886
- const o = x;
1887
- return Object.fromEntries(Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => [k, sort(o[k])]));
1888
- }
1889
- return x;
1890
- };
1891
- return JSON.stringify(sort(v));
1892
- }
1893
-
1894
2007
  // ../protocol/src/phone.ts
1895
2008
  var PHONE_PROTOCOL = 1;
1896
2009
  var PAD_TIERS = [1024, 4096, 16384, 65536, 262144, 655360];
@@ -1928,7 +2041,7 @@ var RiskSchema = z26.enum(["low", "high"]);
1928
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"]);
1929
2042
  var OperationIdSchema = z26.string().uuid();
1930
2043
  var OperationStatusSchema = z26.enum(["received", "applied", "rejected", "failed"]);
1931
- var OperationReasonSchema = z26.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired", "changed", "exists", "unknown_project", "seat_failed", "scheduled", ...ModelRefusalSchema.options]);
2044
+ var OperationReasonSchema = z26.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired", "scheduled", ...ModelRefusalSchema.options, ...RIG_RESULT_CODES]);
1932
2045
  var CapabilitiesSchema = z26.object({
1933
2046
  /** A question can be answered in free text. */
1934
2047
  freeTextAnswer: z26.boolean(),
@@ -1961,19 +2074,21 @@ var PhoneSessionSchema = SwitchableSessionSchema.pick({
1961
2074
  effort: true,
1962
2075
  modelNext: true,
1963
2076
  canSwitch: true,
1964
- /** Its role and its live team (ledge home spec §2.4): the agents screen groups a project's sessions by table. */
1965
- role: true,
1966
- roleLabel: true,
1967
- team: true,
1968
2077
  /** Its context and its relief (context handover spec §3.2): the phone shows the gauge; the relief is started from the PC. */
1969
2078
  context: true,
1970
- relieves: true
2079
+ relieves: true,
2080
+ /** When it ended: « Terminée · à 18:42 » in the list. */
2081
+ exitedAt: true
1971
2082
  }).extend({
1972
2083
  pending: PhonePendingSchema.optional(),
1973
2084
  unread: z26.boolean(),
1974
2085
  handover: z26.object({ state: HandoverStateSchema, toName: z26.string().max(80).optional() }).strict().optional(),
1975
2086
  /** Absent: the agent has no checklist. */
1976
- checklist: PhoneChecklistSummarySchema.optional()
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()
1977
2092
  });
1978
2093
  var B64U2 = z26.string().regex(/^[A-Za-z0-9_-]*$/);
1979
2094
  var CredentialSchema = z26.object({
@@ -1990,8 +2105,7 @@ var AssertionSchema = z26.object({
1990
2105
  authenticatorData: B64U2.max(4096),
1991
2106
  signature: B64U2.max(1024)
1992
2107
  });
1993
- var TeamsDigestSchema = z26.string().regex(/^[0-9a-f]{64}$/);
1994
- var ConfirmPurposeSchema = z26.enum(["register", "decide", "launch", "close", "act", "define", "team", "pc"]);
2108
+ var ConfirmPurposeSchema = z26.enum(["register", "decide", "launch", "close", "act", "pc", "rig"]);
1995
2109
  var PhoneChatStepSchema = ChatStepSchema.pick({ kind: true, id: true, name: true }).strict();
1996
2110
  var PhoneChatProposalSchema = z26.object({
1997
2111
  project: z26.string().max(80),
@@ -2009,33 +2123,15 @@ var PhoneChatRelaySchema = z26.object({
2009
2123
  sentAt: z26.string().datetime().optional(),
2010
2124
  failed: z26.literal(true).optional()
2011
2125
  }).strict();
2126
+ var CloseHowSchema = z26.enum(["exit", "killed"]);
2012
2127
  var PhoneChatCloseSchema = z26.object({
2013
2128
  id: z26.string().min(1),
2014
2129
  name: z26.string(),
2015
2130
  tool: ToolSchema,
2016
- how: z26.enum(["exit", "killed"]).optional(),
2131
+ how: CloseHowSchema.optional(),
2017
2132
  closedAt: z26.string().datetime().optional(),
2018
2133
  failed: z26.literal(true).optional()
2019
2134
  }).strict();
2020
- var PhoneChatTeamSchema = z26.object({
2021
- project: z26.string().max(80),
2022
- name: z26.string().max(60),
2023
- template: z26.string().max(64).optional(),
2024
- 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),
2025
- teamId: z26.string().max(40).optional(),
2026
- launchedAt: z26.string().datetime().optional(),
2027
- failed: z26.literal(true).optional()
2028
- }).strict();
2029
- var PhoneChatTaskSchema = z26.object({
2030
- to: z26.string().max(80),
2031
- toName: z26.string().max(80).optional(),
2032
- title: z26.string().max(200),
2033
- text: z26.string().max(TOOL_TEXT_MAX).optional(),
2034
- taskId: z26.string().max(40).optional(),
2035
- ownerName: z26.string().max(80).optional(),
2036
- state: z26.enum(["open", "taken", "done", "refused", "dropped"]).optional(),
2037
- failed: z26.literal(true).optional()
2038
- }).strict();
2039
2135
  var PhoneChatActionSchema = z26.object({
2040
2136
  action: z26.string().min(1).max(40),
2041
2137
  summary: z26.string().max(300),
@@ -2044,46 +2140,10 @@ var PhoneChatActionSchema = z26.object({
2044
2140
  detailCut: z26.literal(true).optional(),
2045
2141
  risk: ActionRiskSchema,
2046
2142
  session: z26.string().max(100).optional(),
2047
- team: z26.string().max(40).optional(),
2048
2143
  state: ChatActionStateSchema.optional(),
2049
2144
  doneAt: z26.string().datetime().optional(),
2050
2145
  code: z26.string().max(40).optional()
2051
2146
  }).strict();
2052
- var PhoneTeamSchema = z26.object({
2053
- id: z26.string().max(40),
2054
- name: z26.string().max(60),
2055
- project: z26.string().max(80).nullable(),
2056
- state: z26.enum(["active", "resting"]),
2057
- template: z26.string().max(64).optional(),
2058
- /** Absent from a daemon older than the phone: the phone then says no start time. */
2059
- createdAt: z26.string().optional(),
2060
- /** `tool`, `model` and `effort` are what the seat was composed with; `leaving` is a seat that finishes its turn and is then closed. */
2061
- seats: z26.array(z26.object({
2062
- role: z26.string().max(40),
2063
- sessionId: z26.string().max(100).nullable(),
2064
- tool: ToolSchema.optional(),
2065
- model: ModelIdSchema.optional(),
2066
- effort: EffortSchema.optional(),
2067
- leaving: z26.literal(true).optional()
2068
- }).strict()).max(16),
2069
- done: z26.number().int().min(0),
2070
- given: z26.number().int().min(0)
2071
- }).strict();
2072
- var PhoneTaskSchema = z26.object({
2073
- id: z26.string().max(40),
2074
- title: z26.string().max(200),
2075
- owner: z26.string().max(100),
2076
- ownerName: z26.string().max(80),
2077
- ownerRole: z26.string().max(40).optional(),
2078
- team: z26.string().max(40).optional(),
2079
- teamName: z26.string().max(60).optional(),
2080
- state: z26.enum(["open", "taken", "done", "refused", "dropped"]),
2081
- result: z26.string().max(TOOL_TEXT_MAX).optional(),
2082
- cut: z26.literal(true).optional(),
2083
- at: z26.string(),
2084
- updatedAt: z26.string(),
2085
- seen: z26.literal(true).optional()
2086
- }).strict();
2087
2147
  var ChatErrorCodeSchema = z26.enum(["busy", "timeout", "cancelled", "failed"]);
2088
2148
  var PhoneChatTurnSchema = z26.object({
2089
2149
  qid: OperationIdSchema,
@@ -2097,8 +2157,6 @@ var PhoneChatTurnSchema = z26.object({
2097
2157
  relay: PhoneChatRelaySchema.optional(),
2098
2158
  closes: z26.array(PhoneChatCloseSchema).min(1).max(MAX_PROPOSALS).optional(),
2099
2159
  cut: z26.literal(true).optional(),
2100
- team: PhoneChatTeamSchema.optional(),
2101
- tasks: z26.array(PhoneChatTaskSchema).min(1).max(MAX_PROPOSALS).optional(),
2102
2160
  actions: z26.array(PhoneChatActionSchema).min(1).max(MAX_PROPOSALS).optional(),
2103
2161
  ignoredAt: z26.string().datetime().optional(),
2104
2162
  attachments: z26.array(AttachmentRefSchema).max(ATTACHMENTS_MAX).optional()
@@ -2107,30 +2165,32 @@ var CHAT_HISTORY_BUDGET = 393216;
2107
2165
  var sessionId = z26.string().min(1).max(100);
2108
2166
  var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z26.string().min(1).max(64) });
2109
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");
2110
- var teamsOp = { op: OperationIdSchema, assertion: AssertionSchema.optional() };
2168
+ var signedOp = { op: OperationIdSchema, assertion: AssertionSchema.optional() };
2111
2169
  var PcActionSchema = z26.enum(["update", "restart"]);
2112
2170
  var pcSubject = (action, version) => action === "update" ? `update@${version ?? ""}` : "restart";
2113
- var teamsScope = { scope: DefinitionScopeSchema, project: z26.string().min(1).max(80).optional() };
2114
2171
  var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2115
2172
  z26.object({ type: z26.literal("snapshot.request") }),
2173
+ ...phoneOpenRigClientMessages,
2116
2174
  z26.object({ type: z26.literal("session.thread"), id: sessionId }),
2117
2175
  /** The session's request with its whole body (up to `PENDING_BODY_MAX`). */
2118
2176
  z26.object({ type: z26.literal("session.pending"), id: sessionId }),
2119
2177
  /** The session whose terminal the phone shows; null when it shows none. */
2120
2178
  z26.object({ type: z26.literal("session.watch"), id: sessionId.nullable() }),
2121
2179
  z26.object({ type: z26.literal("session.read"), id: sessionId, at: z26.string().datetime() }),
2122
- /** 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
+ */
2123
2184
  z26.object({
2124
2185
  type: z26.literal("confirm.prepare"),
2125
2186
  purpose: ConfirmPurposeSchema,
2126
2187
  id: sessionId.optional(),
2127
2188
  expect: PhoneExpectSchema.optional(),
2128
2189
  qid: OperationIdSchema.optional(),
2129
- /** sha256 of the `define` or `team` operation the challenge is for (`teamsDigest`). */
2130
- digest: TeamsDigestSchema.optional(),
2131
2190
  /** A `pc` challenge: what it is for, and for an update the version it covers. */
2132
2191
  action: PcActionSchema.optional(),
2133
- version: z26.string().regex(SEMVER).optional()
2192
+ version: z26.string().regex(SEMVER).optional(),
2193
+ rig: z26.discriminatedUnion("type", [PhoneRigLaunchSchema, PhoneRigActSchema]).optional()
2134
2194
  }),
2135
2195
  z26.object({ type: z26.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
2136
2196
  z26.object({
@@ -2165,6 +2225,10 @@ var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2165
2225
  expect: PhoneExpectSchema
2166
2226
  }),
2167
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() }),
2168
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`. */
2169
2233
  z26.object({ type: z26.literal("session.model"), op: OperationIdSchema, id: sessionId, model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
2170
2234
  /** The models an agent offers on the PC. */
@@ -2185,10 +2249,6 @@ var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2185
2249
  z26.object({ type: z26.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
2186
2250
  z26.object({ type: z26.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
2187
2251
  z26.object({ type: z26.literal("chat.close"), op: OperationIdSchema, qid: OperationIdSchema, rows: CardRowsSchema.optional(), assertion: AssertionSchema.optional() }),
2188
- /** The turn's team card (roles spec §3.8): launched like a session card, with the device check (purpose `launch`, the turn). */
2189
- z26.object({ type: z26.literal("chat.team"), op: OperationIdSchema, qid: OperationIdSchema, assertion: AssertionSchema.optional() }),
2190
- /** A row of the turn's task card: given by Ledge. */
2191
- z26.object({ type: z26.literal("chat.task"), op: OperationIdSchema, qid: OperationIdSchema, row: z26.number().int().min(0).max(MAX_PROPOSALS - 1) }),
2192
2252
  /** Rows of the turn's generic card (action registry spec §5): `confirmed` as the local `chat.act`; a row whose action asks
2193
2253
  * for a device check (`phone`) needs one assertion, purpose `act`, over the turn. */
2194
2254
  z26.object({
@@ -2203,33 +2263,13 @@ var PhoneClientMessageSchema = z26.discriminatedUnion("type", [
2203
2263
  }),
2204
2264
  /** « Ignorer » the turn's cards, or `undo` (« Revoir »), as the local `chat.ignore`. */
2205
2265
  z26.object({ type: z26.literal("chat.ignore"), op: OperationIdSchema, qid: OperationIdSchema, undo: z26.literal(true).optional() }),
2206
- /** « Reprendre » a resting team. */
2207
- z26.object({ type: z26.literal("team.resume"), op: OperationIdSchema, team: z26.string().min(1).max(40) }),
2208
- /** Puts a closed task Ledge gave away from « À traiter ». */
2209
- z26.object({ type: z26.literal("task.seen"), op: OperationIdSchema, task: z26.string().min(1).max(40) }),
2210
2266
  /** Whether the phone's app is in front: a device that is visible gets no push. */
2211
2267
  z26.object({ type: z26.literal("presence"), visible: z26.boolean() }),
2212
- /** The roles, templates and problems of every scope the phone may name; then one role whole, when the catalog cut its prompt. */
2213
- z26.object({ type: z26.literal("teams.catalog") }),
2214
- z26.object({ type: z26.literal("teams.role"), ...teamsScope, id: RoleIdSchema }).strict(),
2215
- /** `base` is the hash the phone was shown, null to create. */
2216
- z26.object({ type: z26.literal("role.write"), ...teamsOp, ...teamsScope, role: PhoneRoleDraftSchema, base: DefinitionHashSchema.nullable() }).strict(),
2217
- z26.object({ type: z26.literal("role.delete"), ...teamsOp, ...teamsScope, id: RoleIdSchema, base: DefinitionHashSchema }).strict(),
2218
- /** No `id`: create, the id comes from the name. */
2219
- z26.object({ type: z26.literal("template.write"), ...teamsOp, ...teamsScope, id: TemplateIdSchema.optional(), name: TeamNameSchema, seats: PhoneSeatsSchema, base: DefinitionHashSchema.nullable() }).strict(),
2220
- z26.object({ type: z26.literal("template.delete"), ...teamsOp, ...teamsScope, id: TemplateIdSchema, base: DefinitionHashSchema }).strict(),
2221
- 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(),
2222
- z26.object({ type: z26.literal("team.compose"), ...teamsOp, team: TeamIdSchema, seats: PhoneSeatsSchema }).strict(),
2223
- z26.object({ type: z26.literal("team.seat"), ...teamsOp, team: TeamIdSchema, role: RoleIdSchema, action: z26.enum(["start", "restart"]) }).strict(),
2224
- z26.object({ type: z26.literal("team.dissolve"), ...teamsOp, team: TeamIdSchema }).strict(),
2225
2268
  /** Installs `version` on the PC, then restarts its daemon; the phone asks for its own build's version. Passkey, purpose `pc`. */
2226
- 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(),
2227
2270
  /** Restarts the PC's daemon without installing anything. Passkey, purpose `pc`. */
2228
- z26.object({ type: z26.literal("daemon.restart"), ...teamsOp }).strict()
2229
- ]).superRefine((m, ctx) => {
2230
- const issue = "scope" in m ? scopeIssue(m) : null;
2231
- if (issue) ctx.addIssue({ code: z26.ZodIssueCode.custom, message: issue, path: ["project"] });
2232
- });
2271
+ z26.object({ type: z26.literal("daemon.restart"), ...signedOp }).strict()
2272
+ ]);
2233
2273
  var PhoneModelChoiceSchema = z26.object({
2234
2274
  id: ModelChoiceSchema.shape.id.max(120),
2235
2275
  label: z26.string().max(80),
@@ -2238,30 +2278,31 @@ var PhoneModelChoiceSchema = z26.object({
2238
2278
  defaultEffort: EffortSchema.optional(),
2239
2279
  isDefault: z26.boolean().optional()
2240
2280
  }).strict();
2241
- var PhoneSeatModelSchema = z26.object({ role: z26.string().max(40), session: sessionId, status: z26.enum(["applied", "scheduled", "refused"]), reason: ModelRefusalSchema.optional() });
2242
2281
  var PhoneServerMessageSchema = z26.discriminatedUnion("type", [
2243
2282
  z26.object({ type: z26.literal("snapshot"), protocol: z26.literal(PHONE_PROTOCOL), sessions: z26.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z26.object({
2244
2283
  version: z26.string(),
2245
2284
  name: z26.string().max(40),
2246
2285
  boot: z26.string().uuid().optional(),
2247
- teams: z26.literal(true).optional(),
2248
2286
  /** The daemon takes `session.model`. */
2249
2287
  models: z26.literal(true).optional(),
2250
2288
  /** The daemon sends `session.checklist` and a session's `checklist` summary. */
2251
2289
  checklist: z26.literal(true).optional(),
2252
2290
  /** `update`: the daemon can install a version and restart (npm, systemd); `restart`: it can restart (systemd). */
2253
2291
  update: z26.literal(true).optional(),
2254
- restart: z26.literal(true).optional()
2255
- }), 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,
2256
2299
  z26.object({
2257
2300
  type: z26.literal("confirm.challenge"),
2258
2301
  purpose: ConfirmPurposeSchema,
2259
2302
  challenge: B64U2.max(64),
2260
2303
  rpId: z26.string().max(253),
2261
2304
  credentialId: B64U2.max(1366).optional(),
2262
- expiresInMs: z26.number().int().positive(),
2263
- /** 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. */
2264
- digest: TeamsDigestSchema.optional()
2305
+ expiresInMs: z26.number().int().positive()
2265
2306
  }),
2266
2307
  z26.object({ type: z26.literal("session.updated"), session: PhoneSessionSchema }),
2267
2308
  z26.object({ type: z26.literal("session.removed"), id: z26.string() }),
@@ -2273,7 +2314,14 @@ var PhoneServerMessageSchema = z26.discriminatedUnion("type", [
2273
2314
  /** `cut`: the ids of the items shortened; `more`: older items were left out. */
2274
2315
  z26.object({ type: z26.literal("session.thread"), id: z26.string(), items: z26.array(ThreadItemSchema), cut: z26.array(z26.string()), more: z26.boolean() }),
2275
2316
  z26.object({ type: z26.literal("session.thread.item"), id: z26.string(), item: ThreadItemSchema, cut: z26.literal(true).optional() }),
2276
- z26.object({ type: z26.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional(), models: z26.array(PhoneSeatModelSchema).max(16).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
+ }),
2277
2325
  /** The models an agent offers; `error`: it could not be asked, `models` is then what Ledge knows without it. */
2278
2326
  z26.object({ type: z26.literal("models.listing"), tool: ToolSchema, models: z26.array(PhoneModelChoiceSchema).max(40), error: z26.literal(true).optional() }),
2279
2327
  /** A model change that was accepted and then failed (the agent could not restart). */
@@ -2284,30 +2332,26 @@ var PhoneServerMessageSchema = z26.discriminatedUnion("type", [
2284
2332
  z26.object({ type: z26.literal("chat.delta"), qid: OperationIdSchema, text: z26.string().max(THREAD_TEXT_MAX) }),
2285
2333
  z26.object({ type: z26.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
2286
2334
  z26.object({ type: z26.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
2287
- z26.object({ type: z26.literal("teams.catalog"), catalog: PhoneCatalogSchema }),
2288
2335
  /** The daemon updating or restarting itself, whoever asked: npm runs, then the service restarts (the link drops right after). */
2289
2336
  z26.object({ type: z26.literal("daemon.progress"), version: z26.string().max(40), step: z26.enum(["installing", "restarting"]) }),
2290
2337
  /** Nothing was changed. Never npm's output nor a command: both hold the PC's paths. */
2291
2338
  z26.object({ type: z26.literal("daemon.failed"), version: z26.string().max(40), reason: UpdateFailureSchema }),
2292
- z26.object({ type: z26.literal("teams.role"), role: PhoneRoleSchema }),
2293
- /** The live teams, after each snapshot and on every change. */
2294
- z26.object({ type: z26.literal("teams"), teams: z26.array(PhoneTeamSchema).max(50) }),
2295
- /** The tasks Ledge gave, open or closed lately, after each snapshot; then each change. */
2296
- z26.object({ type: z26.literal("tasks"), tasks: z26.array(PhoneTaskSchema).max(200) }),
2297
- z26.object({ type: z26.literal("task.updated"), task: PhoneTaskSchema }),
2298
2339
  AttachmentStoredSchema,
2299
2340
  AttachmentDataSchema,
2300
2341
  AttachmentErrorSchema,
2301
2342
  /** Never carries free text. */
2302
2343
  z26.object({ type: z26.literal("error"), code: z26.enum(["invalid", "unknown_session", "too_large"]), request: z26.string().max(40).optional() })
2303
2344
  ]);
2304
- var teamsPurpose = (type) => type.startsWith("team.") ? "team" : "define";
2305
- var DIGEST_OP = "00000000-0000-4000-8000-000000000000";
2306
- async function teamsDigest(m) {
2307
- const { op: _op, assertion: _assertion, ...raw } = m;
2308
- const { op: _parsedOp, assertion: _parsedAssertion, ...rest } = PhoneClientMessageSchema.parse({ ...raw, op: DIGEST_OP });
2309
- const bytes = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(rest))));
2310
- 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));
2311
2355
  }
2312
2356
 
2313
2357
  // ../protocol/src/todos.ts
@@ -2364,130 +2408,56 @@ function latestTodos(items, helperId) {
2364
2408
  }
2365
2409
  var doneCount = (todos) => todos.filter((x) => x.status === "completed").length;
2366
2410
 
2367
- // src/auth.ts
2368
- import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
2369
- import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
2370
- import { readFileSync } from "fs";
2371
- import { dirname } from "path";
2372
- var TOKEN_RE = /^[0-9a-f]{64}$/;
2373
- var DAEMON_PROTOCOL_PREFIX = "ledge.";
2374
- function isDaemonToken(value) {
2375
- return TOKEN_RE.test(value);
2376
- }
2377
- function tokenEquals(presented, expected) {
2378
- const a = createHash("sha256").update(presented.slice(0, 256), "utf8").digest();
2379
- const b = createHash("sha256").update(expected, "utf8").digest();
2380
- return timingSafeEqual(a, b);
2381
- }
2382
- function hostAllowed(hostHeader) {
2383
- const raw = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
2384
- if (!raw || raw.length > 255) return false;
2385
- const host = raw.trim().toLowerCase();
2386
- if (!host || host.includes(" ") || host.endsWith(".")) return false;
2387
- let name;
2388
- if (host.startsWith("[")) {
2389
- const end = host.indexOf("]");
2390
- if (end < 0) return false;
2391
- name = host.slice(1, end);
2392
- const rest = host.slice(end + 1);
2393
- if (rest !== "" && !/^:\d{1,5}$/.test(rest)) return false;
2394
- } else {
2395
- const colon = host.lastIndexOf(":");
2396
- if (colon >= 0) {
2397
- if (!/^\d{1,5}$/.test(host.slice(colon + 1))) return false;
2398
- name = host.slice(0, colon);
2399
- } else name = host;
2400
- }
2401
- return name === "localhost" || name === "127.0.0.1" || name === "::1";
2402
- }
2403
- function headerValue(value, max) {
2404
- const raw = Array.isArray(value) ? value.join(",") : value;
2405
- return !raw || raw.length > max ? "" : raw;
2406
- }
2407
- function bearerToken(authorization) {
2408
- if (!authorization || authorization.length > 512) return "";
2409
- return /^Bearer[ \t]+(\S+)$/i.exec(authorization)?.[1] ?? "";
2410
- }
2411
- function protocolToken(header) {
2412
- const raw = headerValue(header, 512);
2413
- if (!raw) return "";
2414
- for (const part of raw.split(",").slice(0, 8)) {
2415
- const protocol = part.trim();
2416
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX)) return protocol.slice(DAEMON_PROTOCOL_PREFIX.length);
2417
- }
2418
- return "";
2419
- }
2420
- function requestAuthorized(headers, expected) {
2421
- const bearerOk = tokenEquals(bearerToken(typeof headers.authorization === "string" ? headers.authorization : void 0), expected);
2422
- const presented = protocolToken(headers["sec-websocket-protocol"]);
2423
- return bearerOk || presented !== "" && tokenEquals(presented, expected);
2424
- }
2425
- function selectDaemonProtocol(protocols, expected) {
2426
- for (const protocol of protocols) {
2427
- if (protocol.startsWith(DAEMON_PROTOCOL_PREFIX) && tokenEquals(protocol.slice(DAEMON_PROTOCOL_PREFIX.length), expected)) return protocol;
2428
- }
2429
- return false;
2430
- }
2431
- async function readTokenFile(file) {
2432
- try {
2433
- const raw = (await readFile(file, "utf8")).trim();
2434
- return isDaemonToken(raw) ? raw : void 0;
2435
- } catch (e) {
2436
- if (e.code === "ENOENT") return void 0;
2437
- throw e;
2438
- }
2439
- }
2440
- async function lockTokenFile(file) {
2441
- await chmod(file, 384);
2442
- }
2443
- async function ensureDaemonToken() {
2444
- const file = paths().token;
2445
- await mkdir(dirname(file), { recursive: true });
2446
- const current = await readTokenFile(file);
2447
- if (current) {
2448
- await lockTokenFile(file);
2449
- return current;
2450
- }
2451
- const token = randomBytes(32).toString("hex");
2452
- const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
2453
- await writeFile(tmp, `${token}
2454
- `, { mode: 384, flag: "wx" });
2455
- await chmod(tmp, 384);
2456
- try {
2457
- await link(tmp, file);
2458
- } catch (e) {
2459
- await unlink(tmp).catch(() => {
2460
- });
2461
- if (e.code !== "EEXIST") throw e;
2462
- const raced = await readTokenFile(file);
2463
- if (raced) {
2464
- await lockTokenFile(file);
2465
- return raced;
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;
2466
2425
  }
2467
- await writeFile(file, `${token}
2468
- `, { 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);
2469
2432
  }
2470
- await unlink(tmp).catch(() => {
2471
- });
2472
- await lockTokenFile(file);
2473
- const stored = await readTokenFile(file);
2474
- if (!stored) throw new Error("daemon token was not stored");
2475
- return stored;
2476
- }
2477
- var SESSION_KEY_VAR = "LEDGE_SESSION_KEY";
2478
- function sessionKey(id, token = readDaemonToken()) {
2479
- return token ? createHmac("sha256", token).update(`ledge-session:${id}`).digest("hex") : void 0;
2433
+ return out;
2480
2434
  }
2481
- function readDaemonToken() {
2482
- try {
2483
- const raw = readFileSync(paths().token, "utf8").trim();
2484
- return isDaemonToken(raw) ? raw : void 0;
2485
- } catch {
2486
- return void 0;
2487
- }
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 };
2488
2445
  }
2489
2446
 
2490
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,
2491
2461
  ATTACHMENT_ID,
2492
2462
  ATTACHMENT_MAX,
2493
2463
  ATTACHMENT_LOCAL_MAX,
@@ -2498,6 +2468,7 @@ export {
2498
2468
  CONTEXT_NEAR_MIN,
2499
2469
  CONTEXT_NEAR_MAX,
2500
2470
  ContextPrefsSchema,
2471
+ contextShare,
2501
2472
  contextNear,
2502
2473
  BRIEF_MAX,
2503
2474
  ToolSchema,
@@ -2512,34 +2483,12 @@ export {
2512
2483
  extractProposal,
2513
2484
  parseRelayBlock,
2514
2485
  parseCloseBlock,
2515
- parseTeamBlock,
2516
- parseTaskBlock,
2517
2486
  parseProjectBlock,
2518
- SshAliasSchema,
2519
- MachinesSchema,
2520
- PROJECT_COLORS,
2521
- ProjectsSchema,
2522
- DEFAULT_PROJECTS,
2523
- projectOf,
2524
- RoleIdSchema,
2525
- ROLE_LABEL_MAX,
2526
- ROLE_PROMPT_MAX,
2527
- RoleSchema,
2528
- SeatsSchema,
2529
- TeamNameSchema,
2530
- TemplateIdSchema,
2531
- TeamSchema,
2532
- isTeamLive,
2533
2487
  NOTE_MAX,
2534
2488
  NOTE_NOT_TYPED,
2535
2489
  offers,
2536
2490
  pendingKey,
2537
- TASK_TITLE_MAX,
2538
- TASK_TEXT_MAX,
2539
- LEDGE_GIVER,
2540
- transmissions,
2541
- givenByLedge,
2542
- taskClosed,
2491
+ ActionIdSchema,
2543
2492
  ACTIONS,
2544
2493
  actionById,
2545
2494
  riskOf,
@@ -2552,10 +2501,29 @@ export {
2552
2501
  ChatTurnSchema,
2553
2502
  proposalRows,
2554
2503
  DOCUMENT_MAX_BYTES,
2504
+ SshAliasSchema,
2505
+ MachinesSchema,
2555
2506
  IslandPrefsSchema,
2507
+ PROJECT_COLORS,
2508
+ ProjectsSchema,
2509
+ DEFAULT_PROJECTS,
2510
+ projectOf,
2556
2511
  RouteResultSchema,
2557
2512
  DEFAULT_RELAY_URL,
2558
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,
2559
2527
  PROTOCOL_PORT,
2560
2528
  RefusalCodeSchema,
2561
2529
  RecentSchema,
@@ -2575,15 +2543,12 @@ export {
2575
2543
  RELAY_CLOSE_SIGNED_OUT,
2576
2544
  DeviceStartResponseSchema,
2577
2545
  DevicePollResponseSchema,
2546
+ rigSubject,
2578
2547
  pushEndpointAllowed,
2579
2548
  PushSendResultSchema,
2580
2549
  PushPayloadSchema,
2581
- PhoneRoleSchema,
2582
- PhoneTemplateSchema,
2583
- TEAMS_OPERATION_TYPES,
2584
- isTeamsOperation,
2585
- canonicalJson,
2586
2550
  PHONE_PROTOCOL,
2551
+ PLAIN_MAX,
2587
2552
  padPlain,
2588
2553
  THREAD_ITEMS_MAX,
2589
2554
  THREAD_BUDGET,
@@ -2599,17 +2564,10 @@ export {
2599
2564
  pcSubject,
2600
2565
  PhoneClientMessageSchema,
2601
2566
  PhoneModelChoiceSchema,
2602
- teamsPurpose,
2603
- teamsDigest,
2567
+ canonicalJson,
2604
2568
  TODO_TOOLS,
2605
2569
  latestTodos,
2606
2570
  doneCount,
2607
- tokenEquals,
2608
- hostAllowed,
2609
- requestAuthorized,
2610
- selectDaemonProtocol,
2611
- ensureDaemonToken,
2612
- SESSION_KEY_VAR,
2613
- sessionKey,
2614
- readDaemonToken
2571
+ mandateEntries,
2572
+ parseMandate
2615
2573
  };