@paigy/mcp 0.40.23 → 0.40.24

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.
@@ -1,9 +1,11 @@
1
1
  // ../../packages/schema/dist/index.js
2
- import { z as z4 } from "zod";
2
+ import { z as z5 } from "zod";
3
3
  import { z } from "zod";
4
4
  import { z as z3 } from "zod";
5
5
  import { zodToJsonSchema } from "zod-to-json-schema";
6
6
  import { z as z2 } from "zod";
7
+ import { z as z4 } from "zod";
8
+ import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
7
9
  var OPTIONS_MIN = 2;
8
10
  var OPTIONS_MAX = 6;
9
11
  var OptionSchema = z.object({
@@ -54,32 +56,31 @@ function mcpInputSchema(s) {
54
56
  }
55
57
  var AskInputSchema = z2.object({
56
58
  id: z2.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
57
- parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
59
+ parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and the question starts a new Goal of yours."),
58
60
  repo: z2.string().optional().describe("Optional repository context."),
59
61
  ask: z2.string().trim().min(1).max(1e4).describe(
60
- "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 each question a Question on the one Goal the ask lands on, never a Goal of its own \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
62
+ "ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy sends each ask exactly as you wrote it: a bundled ask arrives as one card. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
61
63
  ),
62
64
  options: z2.array(OptionInputSchema).min(1).max(6).optional(),
63
- // YOUR VIEW OF ONE OR MANY, A HINT TO PAIGY'S READ (owner, 2026-10-02: "one and many makes sense...
64
- // make sure the brain that formats is aware of how it should work"). Three independent fixes went
65
- // out as "All three / 1 and 3 only / Just log it" because every card was pick-one. #2943 made this
66
- // field override the read; no agent sent it, so nothing changed. The read now decides each card's
67
- // shape (`goal/intake.ts` `answerOf`), and this reaches it as the agent's view, weighed, not obeyed.
65
+ // HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
66
+ // brain_prompts.md §1: structured agent questions need no model). Every card was pick-one while no
67
+ // agent sent this; no read reshapes cards any more, so this is the card's shape, and the
68
+ // description says so plainly.
68
69
  select: z2.enum(["one", "many"]).optional().describe(
69
- `Your view of how the options are answered: "one" to pick one, "many" to tick any number of them. A hint: Paigy's read decides each card's shape, a checklist when the options can all be wanted at once, a pick when choosing one rules out the others. The person can always answer in their own words as well. Ignored without options.`
70
+ 'How the options are answered: "many" makes a checklist (they may want several, e.g. independent fixes), "one" (the default) a pick (choosing one rules out the others). Send "many" whenever more than one option could be wanted at once. The person can always answer in their own words as well. Ignored without options.'
70
71
  ),
71
72
  answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
72
- "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. Needs parentId: the Goal the question is on. The reply is sent to them as it is; to also ask something new, send that as its own ask."
73
+ "Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. parentId is optional here: the question's own Goal is used. The reply is sent to them as it is; to also ask something new, send that as its own ask."
73
74
  )
74
75
  }).strict();
75
76
  var StartContactSchema = z2.object({
76
- asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; only an ask naming no Goal is placed in the tree by Paigy."),
77
+ asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; an ask naming no Goal starts a new Goal of yours."),
77
78
  waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
78
79
  channel: z2.enum(["notification", "call"]).default("notification")
79
80
  }).strict();
80
81
  var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
81
82
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
82
- var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read, every one on the Goal the ask lands on; separate pieces of work are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so). Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). Whether a card is a checklist or a pick is decided by Paigy's read: independent options they may want several of become a checklist, alternatives a pick. select:'many' or select:'one' tells it your view, a hint it weighs. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
83
+ var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; Where your work stands while under way is progress: report it with update_goal, not contact. Send options (or waiting:'hard') when you actually need an answer. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
83
84
  var CreateGoalSchema = z3.object({
84
85
  outcome: z3.string().trim().min(1).max(1e4),
85
86
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -164,9 +165,156 @@ HOW TO ASK:
164
165
  4. Never hold the process open with while-loops. For an open Call with a pending decision, follow its \`next\` with another bounded \`contact({deliveryId})\` tool call; a returned wait window is not a completed conversation. STOP REREADING when a window comes back with nothing new said \u2014 they are not typing, the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`check_replies\` or \`claim_goal\` on that wake.
165
166
  5. One ask, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open.
166
167
  6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
167
- 7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\`. Paigy's read decides whether a card is a checklist (options they may want several of) or a pick (one rules out the others); \`select: "many"\` or \`"one"\` is your view, a hint it weighs. They can always answer in their own words, so no "Other" option.
168
+ 7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\`. \`select: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others). Your ask is sent exactly as you wrote it. They can always answer in their own words, so no "Other" option.
168
169
  8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.`;
169
170
  }
171
+ var id = z4.string().uuid();
172
+ var key = z4.string().min(1).max(40);
173
+ var participant = z4.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
174
+ var SourceSchema = z4.object({ entryId: id }).strict();
175
+ var ResultRefSchema = z4.union([z4.object({ id }).strict(), z4.object({ local: key }).strict()]);
176
+ var NodeRefSchema = z4.object({ type: z4.enum(["goal", "question"]), id }).strict();
177
+ var DependencySchema = z4.object({
178
+ blocker: NodeRefSchema,
179
+ blocked: NodeRefSchema,
180
+ action: z4.enum(["start", "complete", "answer"]),
181
+ reason: z4.string().max(500).optional()
182
+ }).strict();
183
+ var goalState = z4.enum(["open", "completed", "canceled"]);
184
+ var existingGoalChanges = [
185
+ z4.object({ kind: z4.literal("edit"), goalId: id, title: z4.string().min(1).max(120).optional(), outcome: z4.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
186
+ z4.object({ kind: z4.literal("state"), goalId: id, state: goalState }).strict(),
187
+ z4.object({ kind: z4.literal("assign"), goalId: id, ownerId: participant }).strict(),
188
+ z4.object({ kind: z4.literal("defer"), goalId: id, until: z4.string().datetime().nullable() }).strict(),
189
+ z4.object({ kind: z4.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
190
+ z4.object({
191
+ kind: z4.literal("dependency"),
192
+ change: z4.enum(["add", "remove"]),
193
+ goalId: id,
194
+ dependsOnGoalId: id,
195
+ action: z4.enum(["start", "complete"]),
196
+ reason: z4.string().max(500).optional()
197
+ }).strict()
198
+ ];
199
+ var GoalChangeSchema = z4.union([
200
+ z4.object({
201
+ kind: z4.literal("create"),
202
+ title: z4.string().min(1).max(120),
203
+ outcome: z4.string().min(1),
204
+ ownerId: participant,
205
+ parentGoalId: id.optional(),
206
+ sourceEntryIds: z4.array(id)
207
+ }).strict(),
208
+ ...existingGoalChanges
209
+ ]);
210
+ var BrainGoalChangeSchema = z4.union([
211
+ // newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
212
+ z4.object({
213
+ kind: z4.literal("create"),
214
+ outcome: z4.string().min(1),
215
+ title: z4.string().min(1).max(120),
216
+ ownerId: participant,
217
+ parentGoal: ResultRefSchema.nullable(),
218
+ newSession: z4.literal(true).optional()
219
+ }).strict(),
220
+ ...existingGoalChanges
221
+ ]);
222
+ var option = z4.object({ id: z4.string().min(1).max(40), label: z4.string().min(1), hint: z4.string().optional() }).strict();
223
+ var QuestionChangeSchema = z4.union([
224
+ z4.object({
225
+ kind: z4.literal("create"),
226
+ text: z4.string().min(1),
227
+ answererId: participant,
228
+ goal: ResultRefSchema.optional(),
229
+ blocks: z4.array(z4.object({ question: ResultRefSchema }).strict()),
230
+ options: z4.array(option).min(2).optional(),
231
+ pickMode: z4.enum(["one", "many", "rank"]).optional()
232
+ }).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
233
+ z4.object({ kind: z4.literal("edit"), questionId: id, text: z4.string().min(1) }).strict(),
234
+ z4.object({ kind: z4.literal("assign"), questionId: id, answererId: participant }).strict(),
235
+ z4.object({ kind: z4.literal("withdraw"), questionId: id }).strict(),
236
+ z4.object({ kind: z4.literal("dependency"), change: z4.enum(["add", "remove"]), edge: DependencySchema }).strict()
237
+ ]);
238
+ var lessonScope = z4.union([
239
+ z4.object({ kind: z4.literal("user") }).strict(),
240
+ z4.object({ kind: z4.literal("goal"), goal: ResultRefSchema }).strict()
241
+ ]);
242
+ var LessonChangeSchema = z4.union([
243
+ z4.object({ kind: z4.literal("remember"), text: z4.string().min(1), scope: lessonScope }).strict(),
244
+ z4.object({ kind: z4.literal("revise"), lessonId: id, text: z4.string().min(1), scope: lessonScope }).strict(),
245
+ z4.object({ kind: z4.literal("withdraw"), lessonId: id }).strict()
246
+ ]);
247
+ var BrainNextSchema = z4.object({
248
+ instructions: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
249
+ say: z4.array(key),
250
+ then: z4.enum(["listen", "hold", "end", "none"]),
251
+ waitFor: z4.array(ResultRefSchema),
252
+ reason: z4.string()
253
+ }).strict();
254
+ var messageBase = {
255
+ key,
256
+ text: z4.string().min(1),
257
+ goals: z4.array(ResultRefSchema),
258
+ questions: z4.array(ResultRefSchema),
259
+ entryKeys: z4.array(key),
260
+ obligationIds: z4.array(id)
261
+ };
262
+ var BrainMessageSchema = z4.union([
263
+ z4.object({ ...messageBase, to: z4.object({ kind: z4.literal("user") }).strict() }).strict(),
264
+ z4.object({
265
+ ...messageBase,
266
+ to: z4.object({ kind: z4.literal("agent"), agentId: z4.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
267
+ call: z4.object({ callId: id, listen: z4.literal(true) }).strict().optional()
268
+ }).strict()
269
+ ]);
270
+ var BrainSearchSchema = z4.object({
271
+ query: z4.string().min(1).max(500),
272
+ within: z4.array(z4.enum(["entries", "goals", "answers"])).min(1),
273
+ goalId: id.optional()
274
+ }).strict();
275
+ var BrainResultSchema = z4.object({
276
+ messages: z4.array(BrainMessageSchema),
277
+ entries: z4.array(z4.object({ key, sources: z4.array(SourceSchema).min(1), goals: z4.array(ResultRefSchema) }).strict()),
278
+ questions: z4.array(z4.object({ key, entryKeys: z4.array(key), change: QuestionChangeSchema }).strict()),
279
+ answers: z4.array(z4.object({
280
+ question: ResultRefSchema,
281
+ entryKeys: z4.array(key).min(1),
282
+ summary: z4.string().min(1),
283
+ selectedOptionIds: z4.array(z4.string().min(1)).optional()
284
+ }).strict()),
285
+ goals: z4.array(z4.object({ key, entryKeys: z4.array(key), change: BrainGoalChangeSchema }).strict()),
286
+ feedback: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
287
+ lessons: z4.array(z4.object({ entryKeys: z4.array(key).min(1), change: LessonChangeSchema }).strict()),
288
+ next: BrainNextSchema,
289
+ search: BrainSearchSchema.optional()
290
+ }).strict();
291
+ var str = z4.string();
292
+ var strs = z4.array(z4.string());
293
+ var CompactResultSchema = z4.object({
294
+ next: z4.object({ say: strs, then: z4.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
295
+ messages: z4.array(z4.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z4.boolean() }).strict()),
296
+ entries: z4.array(z4.object({ key: str, from: str, on: strs }).strict()),
297
+ answers: z4.array(z4.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
298
+ changes: z4.array(z4.object({
299
+ key: str,
300
+ what: z4.enum(["goal", "question", "lesson"]),
301
+ op: z4.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
302
+ id: str,
303
+ text: str,
304
+ title: str,
305
+ who: str,
306
+ under: str,
307
+ gate: z4.enum(["start", "complete", "answer", "none"]),
308
+ options: z4.array(z4.object({ id: str, label: str }).strict()),
309
+ pick: z4.enum(["one", "many", "rank", "words"]),
310
+ entries: strs
311
+ }).strict()),
312
+ feedback: z4.array(strs),
313
+ search: z4.object({ query: str, within: strs, goal: str }).strict()
314
+ }).strict();
315
+ var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
316
+ zodToJsonSchema2(CompactResultSchema, { $refStrategy: "none" })
317
+ );
170
318
  function entryWords(entry) {
171
319
  const content = entry.content;
172
320
  if (content && "sealed" in content) return "";
@@ -179,22 +327,24 @@ function entryWords(entry) {
179
327
  const description = plain.description;
180
328
  const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
181
329
  if (parts.length) return parts.join("\n\n");
330
+ const line = plain.line;
331
+ if (typeof line === "string" && line.trim()) return line;
182
332
  }
183
333
  return entry.sources.map((source) => source.text).join("\n");
184
334
  }
185
335
  var LIVE_MS = 3 * 6e4;
186
336
  var WORKING_MS = 60 * 6e4;
187
- var ContextSchema = z4.object({
188
- title: z4.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
189
- description: z4.array(z4.string().min(1)).describe(
337
+ var ContextSchema = z5.object({
338
+ title: z5.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
339
+ description: z5.array(z5.string().min(1)).describe(
190
340
  "Semantic chunks of detail (each a standalone, non-empty piece). The user can select chunks to ask you to expand. MAY BE EMPTY: a claim whose whole content is its heading \u2014 a single sentence \u2014 has no body, and saying so beats repeating the heading underneath itself. That repeat is what `min(1)` used to force, at 2x the storage, with every reader subtracting it back out at render time."
191
341
  )
192
342
  });
193
- var ParticipantSchema = z4.object({
194
- kind: z4.enum(["human", "agent"]),
195
- id: z4.string()
343
+ var ParticipantSchema = z5.object({
344
+ kind: z5.enum(["human", "agent"]),
345
+ id: z5.string()
196
346
  });
197
- var TransformSchema = z4.enum([
347
+ var TransformSchema = z5.enum([
198
348
  "structure",
199
349
  // shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
200
350
  "request_more",
@@ -210,13 +360,13 @@ var TransformSchema = z4.enum([
210
360
  "summarize"
211
361
  // reduce volume, keep decision value — 30-turn cap, spoken briefing
212
362
  ]);
213
- var VisualSchema = z4.object({
214
- url: z4.string().url(),
215
- label: z4.string().optional()
363
+ var VisualSchema = z5.object({
364
+ url: z5.string().url(),
365
+ label: z5.string().optional()
216
366
  });
217
- var NotifyLevelSchema = z4.enum(["inbox", "push", "banner", "call"]);
218
- var SelectShapeSchema = z4.enum(["one", "many", "rank", "confirm", "text"]);
219
- var ReceiptEventSchema = z4.enum([
367
+ var NotifyLevelSchema = z5.enum(["inbox", "push", "banner", "call"]);
368
+ var SelectShapeSchema = z5.enum(["one", "many", "rank", "confirm", "text"]);
369
+ var ReceiptEventSchema = z5.enum([
220
370
  "delivered",
221
371
  // the bundle reached the recipient at some level
222
372
  "seen",
@@ -246,47 +396,47 @@ var ReceiptEventSchema = z4.enum([
246
396
  // be rewound by a writer that forgot to advance it.
247
397
  "restarted"
248
398
  ]);
249
- var AttentionSchema = z4.object({
399
+ var AttentionSchema = z5.object({
250
400
  urgency: NotifyLevelSchema,
251
401
  /** The required answer shape, or null for a plain notify that asks nothing back. */
252
402
  select: SelectShapeSchema.nullable(),
253
403
  /** Coverage contract (#396) — points the answer must address; null = none declared. */
254
- points: z4.array(z4.string()).nullable(),
404
+ points: z5.array(z5.string()).nullable(),
255
405
  /** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
256
- blocking: z4.boolean(),
406
+ blocking: z5.boolean(),
257
407
  /** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
258
408
  * slice wires it; optional so today's rows/callers project cleanly. */
259
- deadline: z4.string().datetime().nullable().optional()
409
+ deadline: z5.string().datetime().nullable().optional()
260
410
  });
261
- var NotifyRequestFields = z4.object({
411
+ var NotifyRequestFields = z5.object({
262
412
  /** Plaintext message content. Present on the plaintext path (today's shape);
263
413
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
264
414
  * superRefine at the bottom enforces exactly one of the two. */
265
415
  context: ContextSchema.optional(),
266
- options: z4.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
416
+ options: z5.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
267
417
  "The choices, in order \u2014 required when select is 'one'/'many'/'rank', omitted otherwise. Ids are assigned automatically by position ('1', '2', \u2026); the user's answer references them as optionId(s)."
268
418
  ),
269
- points: z4.array(z4.string().min(1)).optional().describe(
419
+ points: z5.array(z5.string().min(1)).optional().describe(
270
420
  "The distinct things you need answered, each a short phrase \u2014 on a call the broker keeps the conversation going until each is addressed, and the reply reports which were covered, so a half-answer is never silently returned as final. Omit for single-part asks."
271
421
  ),
272
- visuals: z4.array(VisualSchema).optional().describe(
422
+ visuals: z5.array(VisualSchema).optional().describe(
273
423
  "Images attached to the message itself \u2014 context for the whole question (a screenshot, a chart). For a preview on one selectable choice, use that option's `html`/`image` instead."
274
424
  ),
275
425
  /** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
276
- repo: z4.string().optional(),
426
+ repo: z5.string().optional(),
277
427
  /** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
278
- branch: z4.string().optional(),
428
+ branch: z5.string().optional(),
279
429
  /** Continue an existing conversation — the id of any notification in it (its root
280
430
  * is the conversation's identity). Omitted = start a new conversation. Renamed
281
431
  * from `parentId` (2026-08-03): one linkage system, the parent; the API edge
282
432
  * still accepts the old name from older clients. */
283
- parentId: z4.string().uuid().optional(),
433
+ parentId: z5.string().uuid().optional(),
284
434
  /** The durable outcome this contact advances. Optional during the notification-to-Work
285
435
  * migration; when present, a blocking ask creates a DecisionNeed for this Work. */
286
- workId: z4.string().uuid().optional(),
436
+ workId: z5.string().uuid().optional(),
287
437
  /** Target Goal scope. During staged migration this is accepted by the shared contract but
288
438
  * target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
289
- goalId: z4.string().uuid().optional(),
439
+ goalId: z5.string().uuid().optional(),
290
440
  urgency: NotifyLevelSchema.default("inbox").describe(
291
441
  "The level you're requesting \u2014 the user's account permissions + session mode can lower it. 'inbox' (default) = sits silently in the inbox for the user to get to. 'push' = a quiet passive push (lands in Notification Center, no sound) \u2014 a gentle heads-up. 'banner' = a time-sensitive banner/lock-screen push with sound (a 'paige') they tap to open \u2014 use when you need them soon-ish but it's not worth ringing them. 'call' = rings the user's phone now (a CallKit voice call) \u2014 use only when you genuinely need them in the moment (blocked and waiting, time-sensitive). context.title is what they see on the banner/ring, so make it specific."
292
442
  ),
@@ -294,7 +444,7 @@ var NotifyRequestFields = z4.object({
294
444
  * visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
295
445
  * when `parentId` became the conversation handle: `parentId` says WHERE, this
296
446
  * says HOW. */
297
- clarifies: z4.string().optional(),
447
+ clarifies: z5.string().optional(),
298
448
  select: SelectShapeSchema.optional().describe(
299
449
  "How the user answers \u2014 required on the fully-shaped form, pick the shape that fits the question: 'one' = pick one option, 'many' = pick several, 'rank' = pick & order (each needs `options`); 'confirm' = yes/no or approve/deny; 'text' = free-form reply only (status updates, open questions). 'confirm' and 'text' take no options. Omit only when sending the simplified `ask` form \u2014 the broker picks the shape."
300
450
  ),
@@ -308,20 +458,20 @@ var NotifyRequestFields = z4.object({
308
458
  // (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
309
459
  // Owner, 2026-07-28: "our actual limitation on how long something is to the user should
310
460
  // come from the broker splitting and summarizing." The cap that remains is a size guard.
311
- ask: z4.string().min(1).max(1e4).optional().describe(
461
+ ask: z5.string().min(1).max(1e4).optional().describe(
312
462
  'SIMPLIFIED FORM \u2014 state in plain prose what you need to learn from the user and why it matters now (e.g. "I need to know whether to deploy the auth fix \u2014 tests are green, staging verified"). Write as much as the situation needs (up to 10k characters) \u2014 Paigy breaks it into topics and reads it back a few sentences at a time; do NOT pre-summarize it into one line. Paigy derives the title, answer shape, options, and delivery channel for you. Mutually exclusive with context/select/options \u2014 send one form or the other.'
313
463
  ),
314
- needs: z4.array(z4.string().min(1)).optional().describe(
464
+ needs: z5.array(z5.string().min(1)).optional().describe(
315
465
  "With `ask` only: the distinct things you need answered when the ask is multi-part \u2014 becomes the coverage contract (`points`), so a half-answer is never silently final."
316
466
  ),
317
- urgencyHint: z4.enum(["whenever", "soon", "now"]).optional().describe(
467
+ urgencyHint: z5.enum(["whenever", "soon", "now"]).optional().describe(
318
468
  "With `ask` only: how urgently you need the answer \u2014 'whenever' (inbox), 'soon' (worth a heads-up), 'now' (you're blocked this minute). A hint, not a command: the user's settings still have the final word."
319
469
  ),
320
470
  /** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
321
471
  * to the agent's work while it waits. Normalized server-side into those two
322
472
  * fields (normalizeWaiting) so everything downstream is untouched; explicit
323
473
  * urgencyHint/blocking win when both are sent. */
324
- waiting: z4.enum(["none", "soft", "hard"]).optional().describe(
474
+ waiting: z5.enum(["none", "soft", "hard"]).optional().describe(
325
475
  "With `ask`: what happens to your work while you wait. 'none' = you're just informing the user. 'soft' = you'd like an answer but can keep working. 'hard' = you are stopped until they answer (reaches them urgently and escalates to a real phone call if unanswered). Replaces urgencyHint + blocking \u2014 send this one field."
326
476
  ),
327
477
  /** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
@@ -329,98 +479,98 @@ var NotifyRequestFields = z4.object({
329
479
  * holding by default would charge every quiet claim that minute before any agent could
330
480
  * correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
331
481
  * and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
332
- confirm: z4.boolean().optional().describe(
482
+ confirm: z5.boolean().optional().describe(
333
483
  "Hold this one so you can correct the plan before the user is interrupted. The response comes back with `held: true` and the plan; POST the confirm route to release it (with options/visuals/urgency corrections, or nothing at all). If you never do, it is announced anyway a couple of minutes later. Ignored when waiting is 'hard'."
334
484
  ),
335
485
  /** #575: a RELAY of the user's explicitly stated preference, never the agent's
336
486
  * choice. Outranks waiting in both directions: 'call' rings even for a
337
487
  * waiting:'none' "call me when it's done"; 'message' never rings even for
338
488
  * waiting:'hard'. */
339
- channel: z4.enum(["call", "message"]).optional().describe(
489
+ channel: z5.enum(["call", "message"]).optional().describe(
340
490
  "Only if the user explicitly said how to reach them \u2014 'call me' \u2192 'call', 'just message/text me' \u2192 'message'. Omit otherwise; Paigy picks."
341
491
  ),
342
- confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
492
+ confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
343
493
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
344
494
  ),
345
- blocking: z4.boolean().default(false).describe(
495
+ blocking: z5.boolean().default(false).describe(
346
496
  "Set true when real downstream work is stuck behind this specific decision \u2014 you can't make meaningful progress until it's answered. This is the real signal for how urgently the user should be reached; it's what the premier use case (an agent that stays unblocked instead of going idle) depends on. Independent of `urgency`: a `banner`-level question can still be `blocking` (something IS stuck, just not time-critical enough to ring for immediately) \u2014 if it goes unanswered a while, Paigy escalates it to a real call using this flag rather than guessing from how many other things happen to be pending. Leave false for anything you could work around, defer, or where other useful work exists meanwhile."
347
497
  )
348
498
  });
349
499
  var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
350
- if (r.workId && r.goalId) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
500
+ if (r.workId && r.goalId) ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
351
501
  if (r.ask !== void 0) {
352
502
  for (const f of ["context", "select", "points"]) {
353
503
  if (r[f] !== void 0)
354
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
504
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
355
505
  }
356
506
  return;
357
507
  }
358
508
  if (r.needs !== void 0 || r.urgencyHint !== void 0)
359
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
509
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
360
510
  if (!r.context)
361
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
511
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
362
512
  if (!r.select)
363
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
513
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
364
514
  const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
365
515
  if (needsOptions && !r.options?.length)
366
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
516
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
367
517
  if (!needsOptions && r.options?.length)
368
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
518
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
369
519
  });
370
- var NotifyStatusSchema = z4.enum(["pending", "answered", "ignored"]);
371
- var AgentStateSchema = z4.enum(["idle", "in_progress", "completed", "needs_input"]);
372
- var TurnSchema = z4.object({
373
- prompt: z4.string(),
374
- reply: z4.string()
520
+ var NotifyStatusSchema = z5.enum(["pending", "answered", "ignored"]);
521
+ var AgentStateSchema = z5.enum(["idle", "in_progress", "completed", "needs_input"]);
522
+ var TurnSchema = z5.object({
523
+ prompt: z5.string(),
524
+ reply: z5.string()
375
525
  });
376
- var UserAnswerSchema = z4.discriminatedUnion("kind", [
377
- z4.object({ kind: z4.literal("option"), optionId: z4.string(), label: z4.string().optional() }),
378
- z4.object({ kind: z4.literal("text"), text: z4.string() }),
379
- z4.object({ kind: z4.literal("ignored") }),
380
- z4.object({ kind: z4.literal("multi"), optionIds: z4.array(z4.string()), labels: z4.array(z4.string()).optional() }),
381
- z4.object({ kind: z4.literal("ranked"), optionIds: z4.array(z4.string()), labels: z4.array(z4.string()).optional() }),
382
- z4.object({ kind: z4.literal("clarify"), chunks: z4.array(z4.string()).min(1) }),
383
- z4.object({ kind: z4.literal("confirm"), approved: z4.boolean() }),
384
- z4.object({ kind: z4.literal("turns"), turns: z4.array(TurnSchema).min(1) }),
526
+ var UserAnswerSchema = z5.discriminatedUnion("kind", [
527
+ z5.object({ kind: z5.literal("option"), optionId: z5.string(), label: z5.string().optional() }),
528
+ z5.object({ kind: z5.literal("text"), text: z5.string() }),
529
+ z5.object({ kind: z5.literal("ignored") }),
530
+ z5.object({ kind: z5.literal("multi"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
531
+ z5.object({ kind: z5.literal("ranked"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
532
+ z5.object({ kind: z5.literal("clarify"), chunks: z5.array(z5.string()).min(1) }),
533
+ z5.object({ kind: z5.literal("confirm"), approved: z5.boolean() }),
534
+ z5.object({ kind: z5.literal("turns"), turns: z5.array(TurnSchema).min(1) }),
385
535
  /** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
386
536
  * delivered through the same settle/await path as a human answer, carrying the judge's
387
537
  * derivation and the precedent ids it grew from. Always paired with a visible trail
388
538
  * card the user can reply to — the broker never overrides the user. */
389
- z4.object({ kind: z4.literal("precedent"), answer: z4.string(), derivation: z4.string(), sources: z4.array(z4.string()).min(1) })
539
+ z5.object({ kind: z5.literal("precedent"), answer: z5.string(), derivation: z5.string(), sources: z5.array(z5.string()).min(1) })
390
540
  ]);
391
- var IntentSchema = z4.object({
541
+ var IntentSchema = z5.object({
392
542
  // The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
393
543
  // it by two ("detail", "feedback"), and because the settle handler parsed the array
394
544
  // all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
395
545
  // questions included. Found auditing five calls' stored feedback, 2026-08-01.
396
- kind: z4.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
397
- detail: z4.string(),
546
+ kind: z5.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
547
+ detail: z5.string(),
398
548
  /** Defer only: seconds until the callback the caller asked for, when something upstream
399
549
  * already read the time. Nothing sets it today (#397 documented an MCP parser that was
400
550
  * never written) — the API reads the defer's `detail` itself with `notes/when.ts`
401
551
  * (`parseDelay`, #1292), and a value here simply wins over that reading. */
402
- dueInSeconds: z4.number().int().positive().optional(),
552
+ dueInSeconds: z5.number().int().positive().optional(),
403
553
  /** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
404
- * already read the utterance, so `feedback_from_call.kind` stops defaulting to
554
+ * already read the utterance, so `signals.kind` stops defaulting to
405
555
  * 'other' on every row. A table that records that something was wrong and nothing
406
556
  * about what cannot answer "is the bot looping less this week?". */
407
- fault: z4.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
557
+ fault: z5.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
408
558
  });
409
- var RideAlongSchema = z4.object({
559
+ var RideAlongSchema = z5.object({
410
560
  /** The note this came from — assign/clarify/close it through /api/notes/:id. */
411
- noteId: z4.string(),
561
+ noteId: z5.string(),
412
562
  /** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
413
- text: z4.string(),
563
+ text: z5.string(),
414
564
  /** The thread to report back on, when the note was dispatched over the request rail. */
415
- parentId: z4.string().nullable()
565
+ parentId: z5.string().nullable()
416
566
  });
417
- var AwaitItemSchema = z4.discriminatedUnion("type", [
418
- z4.object({
419
- type: z4.literal("reply"),
420
- parentId: z4.string(),
421
- notificationId: z4.string(),
422
- workId: z4.string().uuid().optional(),
423
- decisionId: z4.string().uuid().optional(),
567
+ var AwaitItemSchema = z5.discriminatedUnion("type", [
568
+ z5.object({
569
+ type: z5.literal("reply"),
570
+ parentId: z5.string(),
571
+ notificationId: z5.string(),
572
+ workId: z5.string().uuid().optional(),
573
+ decisionId: z5.string().uuid().optional(),
424
574
  answer: UserAnswerSchema,
425
575
  /** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
426
576
  * #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
@@ -430,103 +580,103 @@ var AwaitItemSchema = z4.discriminatedUnion("type", [
430
580
  * "call me back after you merge" in their own words decides for itself what to do,
431
581
  * and now knows exactly which call to make. Absent when either half is missing —
432
582
  * a sentence with a hole in it is worse than no sentence. */
433
- note: z4.string().optional(),
583
+ note: z5.string().optional(),
434
584
  /** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
435
585
  * shaped answer was mapped from, filtered to its own claims. There is no second list
436
586
  * of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
437
587
  * 2026-09-04): the agent reads the sentence and decides. */
438
- transcript: z4.string().optional(),
588
+ transcript: z5.string().optional(),
439
589
  /** Coverage report (#396), when the ask declared `points`: which of them this
440
590
  * answer addressed. Missing points = re-ask or proceed knowingly partial. */
441
- covered: z4.array(z4.string()).optional(),
591
+ covered: z5.array(z5.string()).optional(),
442
592
  /** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
443
593
  * became free. Only `reply` and `idle` carry it: those are the two outcomes that
444
594
  * END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
445
595
  * agent a side-quest while it is still holding the line is how the main thing gets
446
596
  * dropped. Absent/empty = nothing owed. */
447
- also: z4.array(RideAlongSchema).optional()
597
+ also: z5.array(RideAlongSchema).optional()
448
598
  }),
449
- z4.object({
450
- type: z4.literal("remind"),
451
- parentId: z4.string(),
452
- notificationId: z4.string(),
453
- remindAt: z4.string().datetime({ offset: true }),
599
+ z5.object({
600
+ type: z5.literal("remind"),
601
+ parentId: z5.string(),
602
+ notificationId: z5.string(),
603
+ remindAt: z5.string().datetime({ offset: true }),
454
604
  /** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
455
- remindInSeconds: z4.number()
605
+ remindInSeconds: z5.number()
456
606
  }),
457
607
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
458
608
  * post-feedback revision, #633) — the user will never answer this id. Stop
459
609
  * awaiting it; the live ask is the thread's newest turn (await that one, or
460
610
  * re-orient via check_replies). */
461
- z4.object({
462
- type: z4.literal("superseded"),
463
- parentId: z4.string(),
464
- notificationId: z4.string()
611
+ z5.object({
612
+ type: z5.literal("superseded"),
613
+ parentId: z5.string(),
614
+ notificationId: z5.string()
465
615
  }),
466
616
  /** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
467
617
  * revise any of these until the final reply arrives — partial = intelligence,
468
618
  * settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
469
619
  * irreversibly. If `acts` carries a question aimed at you and you know the answer,
470
620
  * contact on the same thread right away — the caller hears it on the same call. */
471
- z4.object({
472
- type: z4.literal("partial"),
473
- notificationId: z4.string(),
474
- inFlight: z4.literal(true),
475
- turn: z4.object({
476
- idx: z4.number(),
477
- prompt: z4.string(),
478
- reply: z4.string(),
479
- acts: z4.array(IntentSchema).nullable().optional()
621
+ z5.object({
622
+ type: z5.literal("partial"),
623
+ notificationId: z5.string(),
624
+ inFlight: z5.literal(true),
625
+ turn: z5.object({
626
+ idx: z5.number(),
627
+ prompt: z5.string(),
628
+ reply: z5.string(),
629
+ acts: z5.array(IntentSchema).nullable().optional()
480
630
  })
481
631
  }),
482
- z4.object({
483
- type: z4.literal("idle"),
484
- also: z4.array(RideAlongSchema).optional(),
632
+ z5.object({
633
+ type: z5.literal("idle"),
634
+ also: z5.array(RideAlongSchema).optional(),
485
635
  /** Is a call live for this agent's user right now? The SDK polls the partial stream
486
636
  * (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
487
637
  * during a live call, and polling for one on a banner/message was a wasted HTTP call +
488
638
  * 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
489
639
  * Absent = an older API → the SDK keeps polling, exactly as before. */
490
- inFlight: z4.boolean().optional()
640
+ inFlight: z5.boolean().optional()
491
641
  })
492
642
  ]);
493
- var VoiceKeySchema = z4.enum(["rachel", "george", "jessica", "brian", "lily"]);
494
- var AgendaTurnSchema = z4.object({
643
+ var VoiceKeySchema = z5.enum(["rachel", "george", "jessica", "brian", "lily"]);
644
+ var AgendaTurnSchema = z5.object({
495
645
  /** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
496
646
  * it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
497
647
  * sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
498
648
  * completion of a streamed re-plan carries the published sentence again, and the walk
499
649
  * drops what it already said by identity, never by the API's guess of what was polled.
500
650
  * Absent on plans nothing streams (a ring plan, a floor). */
501
- id: z4.string().optional(),
651
+ id: z5.string().optional(),
502
652
  /** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
503
653
  * the planner declares duplicates instead of asking them twice. */
504
- coveredIds: z4.array(z4.string()).optional(),
654
+ coveredIds: z5.array(z5.string()).optional(),
505
655
  /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
506
656
  * (owner, 2026-09-25), and a count here refused whole plans. */
507
- info: z4.array(z4.string().min(1)).default([]),
508
- question: z4.string().min(1).nullable(),
657
+ info: z5.array(z5.string().min(1)).default([]),
658
+ question: z5.string().min(1).nullable(),
509
659
  /** True on the one turn carrying the agent's own declared question. */
510
- asks: z4.boolean().optional(),
660
+ asks: z5.boolean().optional(),
511
661
  /** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
512
662
  * Absent on a single-claim plan (the session's own claim) and on shared context turns,
513
663
  * which route nothing. */
514
- claimId: z4.string().optional(),
664
+ claimId: z5.string().optional(),
515
665
  /** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
516
- voice: z4.string().optional(),
666
+ voice: z5.string().optional(),
517
667
  /** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
518
668
  * whose request this is: an item that folded in from another agent arrived as a bare
519
669
  * non-sequitur ("First real production sign-in is yours to make whenever you want.")
520
670
  * and the owner answered "What?". The bot names the agent before its first turn. */
521
- agent: z4.string().optional(),
671
+ agent: z5.string().optional(),
522
672
  /** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
523
673
  * same id a face is minted from. A name is not an identity: two pairings may be called
524
674
  * "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
525
675
  * so "who said that" survives the call, and it rides PER TURN because a coalesced call
526
676
  * speaks for several agents — the turn is the only place that knows which. */
527
- agentId: z4.string().optional(),
677
+ agentId: z5.string().optional(),
528
678
  select: SelectShapeSchema.optional(),
529
- options: z4.array(OptionSchema.omit({ id: true })).optional(),
679
+ options: z5.array(OptionSchema.omit({ id: true })).optional(),
530
680
  /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
531
681
  paces itself now — a short beat between the sentences of a turn, the longer one at its end
532
682
  (owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
@@ -535,33 +685,33 @@ var AgendaTurnSchema = z4.object({
535
685
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
536
686
  * (a question blocks, context flows). blocking:false on a question = ask and move
537
687
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
538
- blocking: z4.boolean().optional(),
688
+ blocking: z5.boolean().optional(),
539
689
  /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
540
690
  * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
541
691
  * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
542
692
  * listens for the last word, and only if that listen is silent is this turn said and asked. If they
543
693
  * speak, it is dropped and their words are taken like any reply. */
544
- ifSilent: z4.boolean().optional()
694
+ ifSilent: z5.boolean().optional()
545
695
  });
546
696
  var CLAIM_STALE_MS = 30 * 6e4;
547
- var InboxItemSchema = z4.object({
548
- id: z4.string(),
549
- tokenId: z4.string().optional(),
697
+ var InboxItemSchema = z5.object({
698
+ id: z5.string(),
699
+ tokenId: z5.string().optional(),
550
700
  status: NotifyStatusSchema,
551
701
  context: ContextSchema,
552
- options: z4.array(OptionSchema).optional(),
702
+ options: z5.array(OptionSchema).optional(),
553
703
  /** The ask's declared coverage points (#396), when the agent sent them. */
554
- points: z4.array(z4.string()).optional(),
704
+ points: z5.array(z5.string()).optional(),
555
705
  /** Does this claim want an ANSWER, or is it telling you something? Written per row from
556
706
  * `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
557
707
  * its acknowledge affordance: without it a status update offers a text box and a dismiss,
558
708
  * and neither of those is "got it" (owner, 2026-08-10). */
559
- asks: z4.boolean().optional(),
709
+ asks: z5.boolean().optional(),
560
710
  /** When a live process last pulsed for this row's agent — the liveness input for
561
711
  * "working requires a pulse" (#928): the list said "Working…" from agent_state alone
562
712
  * while the party called the same dead claim stalled. Absent = no token/no data,
563
713
  * which must never CLAIM stalled. */
564
- lastSeenAt: z4.string().optional(),
714
+ lastSeenAt: z5.string().optional(),
565
715
  /** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
566
716
  * the `notification_events` ledger (trigger-written since 20260621010000, so every row a
567
717
  * user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
@@ -571,7 +721,7 @@ var InboxItemSchema = z4.object({
571
721
  * work. Reading the row's birth as the claim's age brands that "No update in 8h" the
572
722
  * instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
573
723
  * `createdAt`. */
574
- agentStateAt: z4.string().datetime().optional(),
724
+ agentStateAt: z5.string().datetime().optional(),
575
725
  /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
576
726
  * 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
577
727
  * superseded or cancelled need is no longer a question anyone is asked). Present only on a
@@ -585,54 +735,54 @@ var InboxItemSchema = z4.object({
585
735
  * `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
586
736
  * the card's own concise heading; `answer` the accepted answer in words, null while open. It
587
737
  * REPLACED `agenda` (turns), which nothing ever filled. */
588
- questions: z4.array(z4.object({
589
- id: z4.string(),
590
- entryId: z4.string(),
591
- title: z4.string(),
592
- state: z4.enum(["open", "answered"]),
593
- answer: z4.string().nullable(),
738
+ questions: z5.array(z5.object({
739
+ id: z5.string(),
740
+ entryId: z5.string(),
741
+ title: z5.string(),
742
+ state: z5.enum(["open", "answered"]),
743
+ answer: z5.string().nullable(),
594
744
  /** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
595
745
  * request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
596
746
  * carry for the call's one agent, so the phone draws it with the same seed. Absent when the
597
747
  * author is not an agent this account holds (unpaired since, or a person). */
598
- agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional(),
748
+ agent: z5.object({ tokenId: z5.string(), name: z5.string(), voice: VoiceKeySchema.optional() }).optional(),
599
749
  /** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
600
750
  * its need offers, exactly as its own card carries them, present only when one of them has a
601
751
  * preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
602
752
  * never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
603
753
  * says those, and the list stays small (an `html` is up to 16 KB). */
604
- options: z4.array(OptionSchema).optional()
754
+ options: z5.array(OptionSchema).optional()
605
755
  })).optional(),
606
- visuals: z4.array(VisualSchema).optional(),
756
+ visuals: z5.array(VisualSchema).optional(),
607
757
  /** The connected agent's name (the single pairing name — user-typed, or the
608
758
  * agent's suggestion, or a default silly name). */
609
- name: z4.string(),
759
+ name: z5.string(),
610
760
  /** The pairing's assigned voice (#462); absent = the default voice. */
611
761
  voice: VoiceKeySchema.optional(),
612
- repo: z4.string().optional(),
613
- branch: z4.string().optional(),
614
- createdAt: z4.string().datetime(),
615
- snoozedUntil: z4.string().datetime().optional(),
762
+ repo: z5.string().optional(),
763
+ branch: z5.string().optional(),
764
+ createdAt: z5.string().datetime(),
765
+ snoozedUntil: z5.string().datetime().optional(),
616
766
  agentState: AgentStateSchema.default("idle"),
617
767
  /** Whose action the item is waiting on: "you" = an agent asked you (the default,
618
768
  * every agent→user notification); "agent" = you sent a request and it's awaiting the
619
769
  * agent (held in the inbox until the agent replies on the thread). */
620
- turn: z4.enum(["you", "agent"]).default("you"),
770
+ turn: z5.enum(["you", "agent"]).default("you"),
621
771
  /** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
622
772
  * the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
623
773
  * error badge + Retry. */
624
- error: z4.string().optional(),
774
+ error: z5.string().optional(),
625
775
  /** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
626
776
  * with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
627
777
  * this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
628
778
  * and "dismiss the stalled ones" cancelled 37 pieces of live work). */
629
- cold: z4.string().datetime().optional(),
630
- clarifies: z4.string().optional(),
779
+ cold: z5.string().datetime().optional(),
780
+ clarifies: z5.string().optional(),
631
781
  /** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
632
782
  * live"). Present only while an open Call Delivery carries the card's request Entry — read
633
783
  * off the same open list the card came from, so it clears when the Call does. A card is the
634
784
  * backup for a call not taken; while the call has it, the call is where it is answered. */
635
- onCall: z4.literal(true).optional(),
785
+ onCall: z5.literal(true).optional(),
636
786
  /** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
637
787
  * declined, and what the ladder will do next — read off the cron's own row, never computed
638
788
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
@@ -642,31 +792,31 @@ var InboxItemSchema = z4.object({
642
792
  * It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
643
793
  * never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
644
794
  * and the roster's `unreached` read `declinedAt`, and stand while it does. */
645
- ring: z4.object({
646
- declinedAt: z4.string().datetime(),
647
- anchorAt: z4.string().datetime(),
648
- nextRingAt: z4.string().datetime().nullable(),
649
- step: z4.number().int()
795
+ ring: z5.object({
796
+ declinedAt: z5.string().datetime(),
797
+ anchorAt: z5.string().datetime(),
798
+ nextRingAt: z5.string().datetime().nullable(),
799
+ step: z5.number().int()
650
800
  }).optional(),
651
801
  /** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
652
802
  * Absent for anything never delivered through a push, and for older rows written before
653
803
  * the reason was recorded. Deliberately a debug affordance, shown small (owner,
654
804
  * 2026-08-07) — its real job is to give "this didn't need a call" something to be
655
805
  * feedback ABOUT. */
656
- why: z4.object({
806
+ why: z5.object({
657
807
  asked: NotifyLevelSchema,
658
808
  got: NotifyLevelSchema,
659
- because: z4.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
660
- line: z4.string()
809
+ because: z5.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
810
+ line: z5.string()
661
811
  }).optional(),
662
- select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
663
- confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
812
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
813
+ confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
664
814
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
665
815
  ),
666
816
  /** Real downstream work is stuck behind this one — set by the agent, independent of
667
817
  * urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
668
818
  * inbox's blocking badge and the extra confirm step before dismissing it. */
669
- blocking: z4.boolean().default(false),
819
+ blocking: z5.boolean().default(false),
670
820
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
671
821
  answer: UserAnswerSchema.optional(),
672
822
  /** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
@@ -674,38 +824,49 @@ var InboxItemSchema = z4.object({
674
824
  * for a request that asks nothing), whether its content is sealed, and that Goal's state. The
675
825
  * answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
676
826
  * here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
677
- communication: z4.object({
678
- deliveryId: z4.string(),
679
- kind: z4.enum(["notification", "call"]),
680
- entryId: z4.string(),
681
- goalIds: z4.array(z4.string()),
682
- decisionNeedId: z4.string().optional(),
683
- sealed: z4.boolean(),
684
- goalState: z4.string().optional(),
827
+ communication: z5.object({
828
+ deliveryId: z5.string(),
829
+ kind: z5.enum(["notification", "call"]),
830
+ entryId: z5.string(),
831
+ goalIds: z5.array(z5.string()),
832
+ decisionNeedId: z5.string().optional(),
833
+ sealed: z5.boolean(),
834
+ goalState: z5.string().optional(),
685
835
  /** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
686
836
  * and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
687
- goalTitle: z4.string().optional()
688
- }).optional()
837
+ goalTitle: z5.string().optional()
838
+ }).optional(),
839
+ /** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
840
+ * by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
841
+ * how the incremental read knows a card has not moved: the phone echoes back the revs it holds
842
+ * (`POST /api/inbox/changes`) and is sent only the cards whose rev differs.
843
+ *
844
+ * THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK — the same mechanism as `QueueItem.rev`
845
+ * (#2928) and an entry's (#3018), for the same reason: a card shows facts no `updated_at` of its
846
+ * own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
847
+ * live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
848
+ * with no rev is simply always re-sent. */
849
+ rev: z5.string().optional()
689
850
  });
690
851
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
691
- var PushTokenSchema = z4.object({
692
- voipToken: z4.string().min(1).optional(),
693
- alertToken: z4.string().min(1).optional(),
694
- fcmToken: z4.string().min(1).optional(),
695
- platform: z4.enum(["ios", "android"])
852
+ var PushTokenSchema = z5.object({
853
+ voipToken: z5.string().min(1).optional(),
854
+ alertToken: z5.string().min(1).optional(),
855
+ fcmToken: z5.string().min(1).optional(),
856
+ platform: z5.enum(["ios", "android"])
696
857
  }).superRefine((v, ctx) => {
697
858
  if (v.platform !== "ios") return;
698
859
  for (const field of ["voipToken", "alertToken"]) {
699
860
  const token = v[field];
700
861
  if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
701
862
  ctx.addIssue({
702
- code: z4.ZodIssueCode.custom,
863
+ code: z5.ZodIssueCode.custom,
703
864
  path: [field],
704
865
  message: `not an APNs device token (want 64 hex chars, got ${token.length})`
705
866
  });
706
867
  }
707
868
  });
708
- var MissedCallSchema = z4.enum([
869
+ var MissedCallSchema = z5.enum([
709
870
  "retry_10m",
710
871
  "retry_30m",
711
872
  "retry_60m",
@@ -717,31 +878,31 @@ var MissedCallSchema = z4.enum([
717
878
  ]);
718
879
  var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
719
880
  var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
720
- var BrokerTuningSchema = z4.object({
881
+ var BrokerTuningSchema = z5.object({
721
882
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
722
- ackVerbosity: z4.enum(["normal", "none"]).optional(),
883
+ ackVerbosity: z5.enum(["normal", "none"]).optional(),
723
884
  /** How readily the mapper asks its one clarification: 'low' = only when truly
724
885
  * uninterpretable, 'high' = whenever not fully certain. */
725
- clarifyEagerness: z4.enum(["low", "normal", "high"]).optional(),
886
+ clarifyEagerness: z5.enum(["low", "normal", "high"]).optional(),
726
887
  /** The user's own shorthand: when they say `say`, they mean `mean`. */
727
- phrasebook: z4.array(z4.object({ say: z4.string().min(1).max(60), mean: z4.string().min(1).max(120) })).max(24).optional(),
888
+ phrasebook: z5.array(z5.object({ say: z5.string().min(1).max(60), mean: z5.string().min(1).max(120) })).max(24).optional(),
728
889
  /** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
729
890
  * which is every account today — means the agent's own words decide, per ask: a call
730
891
  * about an English ask opens in English. This is the only thing that overrides that,
731
892
  * and a live caller who switches language mid-call still outranks it (broker/lang.ts).
732
893
  * Set per user (no UI yet), like `voiceTuning`. */
733
- language: z4.enum(["en", "es"]).optional()
894
+ language: z5.enum(["en", "es"]).optional()
734
895
  });
735
- var UserSettingsSchema = z4.object({
736
- permissions: z4.object({
737
- call: z4.boolean(),
738
- banner: z4.boolean(),
739
- push: z4.boolean()
896
+ var UserSettingsSchema = z5.object({
897
+ permissions: z5.object({
898
+ call: z5.boolean(),
899
+ banner: z5.boolean(),
900
+ push: z5.boolean()
740
901
  }),
741
902
  /** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
742
903
  * they are an enum on a live column across every account, and the rename is a rename of
743
904
  * what people read (owner, 2026-09-30). */
744
- sessionMode: z4.enum(["default", "all_calls", "silent"]),
905
+ sessionMode: z5.enum(["default", "all_calls", "silent"]),
745
906
  /** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
746
907
  * optional long after DateNight stopped reading it, on the theory that a phone on an older
747
908
  * bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
@@ -750,7 +911,7 @@ var UserSettingsSchema = z4.object({
750
911
  * an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
751
912
  * next dead field for the same reason. */
752
913
  /** Opt-in (default false) to using your content to improve Paigy and train models. */
753
- improveConsent: z4.boolean(),
914
+ improveConsent: z5.boolean(),
754
915
  missedCall: MissedCallSchema.default("backoff_standard"),
755
916
  /** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
756
917
  * (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
@@ -758,17 +919,17 @@ var UserSettingsSchema = z4.object({
758
919
  * Optional, NOT defaulted: a stale client PATCHing the full settings object
759
920
  * must not silently reset this privacy choice. Absent = leave unchanged on
760
921
  * write, 'hosted' on read (see store.ts). */
761
- voiceMode: z4.enum(["hosted", "on_device"]).optional(),
922
+ voiceMode: z5.enum(["hosted", "on_device"]).optional(),
762
923
  /** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
763
924
  * turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
764
925
  * defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
765
926
  * object must not silently turn it back on. Absent = leave unchanged on write, true on
766
927
  * read (see store.ts). */
767
- talk: z4.boolean().optional(),
928
+ talk: z5.boolean().optional(),
768
929
  /** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
769
930
  * load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
770
931
  * newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
771
- callDiagnostics: z4.boolean().optional(),
932
+ callDiagnostics: z5.boolean().optional(),
772
933
  /** Per-user ring budget (#603): calls per rolling day before further calls
773
934
  * degrade to banner. Absent = the global default (25). A number, never a
774
935
  * bypass — every account keeps a ceiling. No UI; set per user for testing. */
@@ -776,7 +937,7 @@ var UserSettingsSchema = z4.object({
776
937
  * payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
777
938
  * slower speaker). No API-side semantics; the bot resolves each key with its
778
939
  * own defaults. Set per user (no UI yet); absent = bot defaults. */
779
- voiceTuning: z4.record(z4.string(), z4.union([z4.number(), z4.string()])).optional(),
940
+ voiceTuning: z5.record(z5.string(), z5.union([z5.number(), z5.string()])).optional(),
780
941
  /** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
781
942
  * only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
782
943
  * an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
@@ -785,53 +946,53 @@ var UserSettingsSchema = z4.object({
785
946
  * that failure reads as the reminder rail being unreliable rather than as a missing
786
947
  * setting. Absent = a spoken time can't be landed, so the reminder rides the next
787
948
  * call — honest about what we know. */
788
- timezone: z4.string().min(1).max(64).optional(),
949
+ timezone: z5.string().min(1).max(64).optional(),
789
950
  /** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
790
951
  * clobber guard as voiceMode: absent = leave unchanged on write. */
791
952
  broker: BrokerTuningSchema.optional()
792
953
  });
793
- var HistoryWorkSchema = z4.object({
794
- id: z4.string(),
795
- title: z4.string(),
796
- state: z4.enum(["done", "cancelled"]),
954
+ var HistoryWorkSchema = z5.object({
955
+ id: z5.string(),
956
+ title: z5.string(),
957
+ state: z5.enum(["done", "cancelled"]),
797
958
  /** Who held it (`agent:<tokenId>` or `human:<userId>`). */
798
- assignee: z4.string()
959
+ assignee: z5.string()
799
960
  });
800
- var HistoryEntrySchema = z4.union([
801
- z4.object({ at: z4.string(), card: InboxItemSchema }),
802
- z4.object({ at: z4.string(), work: HistoryWorkSchema })
961
+ var HistoryEntrySchema = z5.union([
962
+ z5.object({ at: z5.string(), card: InboxItemSchema }),
963
+ z5.object({ at: z5.string(), work: HistoryWorkSchema })
803
964
  ]);
804
- var HistoryPageSchema = z4.object({
805
- entries: z4.array(HistoryEntrySchema),
806
- next: z4.string().nullable()
965
+ var HistoryPageSchema = z5.object({
966
+ entries: z5.array(HistoryEntrySchema),
967
+ next: z5.string().nullable()
807
968
  });
808
969
  var ACTIVITY_LINES = 2;
809
970
  var ACTIVITY_LINE_MAX = 80;
810
- var AgentActivitySchema = z4.object({
971
+ var AgentActivitySchema = z5.object({
811
972
  /** Oldest first, so the newest line is last — the one that replaces in place. */
812
- lines: z4.array(z4.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
973
+ lines: z5.array(z5.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
813
974
  /** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
814
975
  * that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
815
- at: z4.string().datetime()
976
+ at: z5.string().datetime()
816
977
  });
817
- var ConnectionSummarySchema = z4.object({
978
+ var ConnectionSummarySchema = z5.object({
818
979
  /** The connection = the agent's token id (used to address a request). */
819
- id: z4.string(),
980
+ id: z5.string(),
820
981
  /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
821
982
  * never talks); "agent" = an identity that sends. The roster and devices surfaces split
822
983
  * on this. Optional/absent reads as "agent" (a row predating the kind column). See
823
984
  * docs/server/tokens/devices-vs-agents-design.md. */
824
- kind: z4.enum(["device", "agent"]).optional(),
985
+ kind: z5.enum(["device", "agent"]).optional(),
825
986
  /** For an agent, the token id of the DEVICE that minted it — so agents group under their
826
987
  * machine, and revoking a device cascades to them. Null on devices, and on unlinked
827
988
  * agents (phone-launched, provider-managed, or minted before the link existed). */
828
- mintedByDevice: z4.string().nullable().optional(),
829
- device: z4.string().nullable(),
989
+ mintedByDevice: z5.string().nullable().optional(),
990
+ device: z5.string().nullable(),
830
991
  /** The agent's display name (the single pairing name). */
831
- name: z4.string(),
992
+ name: z5.string(),
832
993
  /** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
833
994
  * label; null for a local connection. Sourced from the token's provider, not the name. */
834
- provider: z4.string().nullable(),
995
+ provider: z5.string().nullable(),
835
996
  /** The pairing's assigned voice (#462); null = the default voice. */
836
997
  voice: VoiceKeySchema.nullable(),
837
998
  /** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
@@ -842,34 +1003,34 @@ var ConnectionSummarySchema = z4.object({
842
1003
  * every surface at once and outranks even `sessionMode: all_calls` — a mode the user
843
1004
  * set once must not overrule a rule they set about one agent. */
844
1005
  reach: NotifyLevelSchema.nullable().optional(),
845
- createdAt: z4.string().datetime(),
1006
+ createdAt: z5.string().datetime(),
846
1007
  /** Most recent notification on this connection, either direction. Null = no contact yet.
847
1008
  * Drives the agents-page recency grouping (Today / This week / …). */
848
- lastContactAt: z4.string().datetime().nullable(),
1009
+ lastContactAt: z5.string().datetime().nullable(),
849
1010
  /** Last presence heartbeat from a running agent process (POST /api/presence) — the
850
1011
  * desktop app while open. Null = never seen; stale = offline. */
851
- lastSeenAt: z4.string().datetime().nullable().optional(),
1012
+ lastSeenAt: z5.string().datetime().nullable().optional(),
852
1013
  /** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
853
1014
  * its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
854
1015
  * `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
855
- lastWorkedAt: z4.string().datetime().nullable().optional(),
1016
+ lastWorkedAt: z5.string().datetime().nullable().optional(),
856
1017
  /** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
857
1018
  * With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
858
- oldestReadyAt: z4.string().datetime().nullable().optional(),
1019
+ oldestReadyAt: z5.string().datetime().nullable().optional(),
859
1020
  /** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
860
1021
  * harness availabilities + granted workspaces — the option set the phone's
861
1022
  * "new session" sheet offers. Absent for ordinary MCP agents. */
862
- runtime: z4.object({
1023
+ runtime: z5.object({
863
1024
  /** The @paigy/harness this host is running — a machine the self-update has not reached
864
1025
  * shows its age here (`apps/desktop/src/update.ts`). */
865
- version: z4.string().optional(),
866
- harnesses: z4.array(z4.object({ name: z4.string(), label: z4.string(), status: z4.string() })).optional(),
867
- workspaces: z4.array(z4.string()).optional(),
1026
+ version: z5.string().optional(),
1027
+ harnesses: z5.array(z5.object({ name: z5.string(), label: z5.string(), status: z5.string() })).optional(),
1028
+ workspaces: z5.array(z5.string()).optional(),
868
1029
  /** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
869
1030
  * repo, and each repo directly inside one, with its `origin` remote. A session started for
870
1031
  * work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
871
1032
  * own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
872
- repos: z4.array(z4.object({ path: z4.string(), remote: z4.string() })).optional()
1033
+ repos: z5.array(z5.object({ path: z5.string(), remote: z5.string() })).optional()
873
1034
  }).optional(),
874
1035
  /** The tail of this agent's working log, when a harness is driving it — the agent page's
875
1036
  * live strip. Absent for anything the desktop harness isn't running (a hatched identity
@@ -878,173 +1039,156 @@ var ConnectionSummarySchema = z4.object({
878
1039
  activity: AgentActivitySchema.optional(),
879
1040
  /** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
880
1041
  * false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
881
- managed: z4.boolean()
1042
+ managed: z5.boolean()
882
1043
  });
883
- var LedgerItemSchema = z4.object({ id: z4.string(), parentId: z4.string(), title: z4.string(), createdAt: z4.string() });
884
- var AgentLedgerSchema = z4.object({
1044
+ var LedgerItemSchema = z5.object({ id: z5.string(), parentId: z5.string(), title: z5.string(), createdAt: z5.string() });
1045
+ var AgentLedgerSchema = z5.object({
885
1046
  /** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
886
- agent: z4.object({ id: z4.string(), name: z4.string().nullable(), revokedAt: z4.string().nullable() }),
1047
+ agent: z5.object({ id: z5.string(), name: z5.string().nullable(), revokedAt: z5.string().nullable() }),
887
1048
  /** Its own questions you have not answered. */
888
- asks: z4.array(LedgerItemSchema),
1049
+ asks: z5.array(LedgerItemSchema),
889
1050
  /** Its questions you answered that nobody acted on — still owed to somebody. */
890
- answered: z4.array(LedgerItemSchema),
1051
+ answered: z5.array(LedgerItemSchema),
891
1052
  /** Requests you sent it that it never took. */
892
- requests: z4.array(LedgerItemSchema),
893
- goals: z4.array(z4.object({ id: z4.string(), outcome: z4.string(), state: z4.string() })),
894
- callbacks: z4.array(z4.object({ id: z4.string(), parentId: z4.string(), trigger: z4.string(), note: z4.string(), dueAt: z4.string().nullable() }))
1053
+ requests: z5.array(LedgerItemSchema),
1054
+ goals: z5.array(z5.object({ id: z5.string(), outcome: z5.string(), state: z5.string() })),
1055
+ callbacks: z5.array(z5.object({ id: z5.string(), parentId: z5.string(), trigger: z5.string(), note: z5.string(), dueAt: z5.string().nullable() }))
895
1056
  });
896
- var ReassignResultSchema = z4.object({
897
- moved: z4.object({ asks: z4.number(), answered: z4.number(), requests: z4.number(), goals: z4.number(), callbacks: z4.number() }),
898
- parentId: z4.string().nullable()
1057
+ var ReassignResultSchema = z5.object({
1058
+ moved: z5.object({ asks: z5.number(), answered: z5.number(), requests: z5.number(), goals: z5.number(), callbacks: z5.number() }),
1059
+ parentId: z5.string().nullable()
899
1060
  });
900
- var MoveRingSchema = z4.enum(["home", "travels", "retired", "quarantined"]);
901
- var MoveSchema = z4.object({
902
- id: z4.string(),
903
- /** The reusable question, as distill normalized it. */
904
- question: z4.string(),
905
- /** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
906
- answer: z4.string(),
907
- /** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
908
- * told so, and the ruling only derives essentially the same question in the same scope. */
909
- rationale: z4.string().nullable(),
910
- /** Where the ruling lives: a repo/workspace, or 'global'. */
911
- scope: z4.string(),
912
- ring: MoveRingSchema,
913
- /** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
914
- pinned: z4.boolean(),
915
- /** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
916
- * break is visible instead of a pin silently disappearing. */
917
- pinBroken: z4.boolean(),
918
- /** When the ruling was distilled. */
919
- learnedAt: z4.string(),
920
- /** Last time it answered an ask. Null = never fired. */
921
- lastUsedAt: z4.string().nullable(),
922
- /** How many asks it has answered. Instrumentation — deliberately NOT an input to the
923
- * evidence curve: firing says the question keeps arising, not that the ruling is right. */
924
- usedCount: z4.number(),
925
- /** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
926
- confirms: z4.number(),
927
- /** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
928
- * Linear and priced above the entire confirmation budget, so any full counter wins. */
929
- counters: z4.number(),
930
- /** The agent that asked the question this move came from, when known. Null for a move
931
- * distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
932
- learnedFrom: z4.object({ id: z4.string(), name: z4.string() }).nullable()
1061
+ var LessonStateSchema = z5.enum(["active", "proposed", "retired"]);
1062
+ var LessonViewSchema = z5.object({
1063
+ id: z5.string(),
1064
+ text: z5.string(),
1065
+ state: LessonStateSchema,
1066
+ /** The Goal it is scoped to; null = the whole account. */
1067
+ scopeGoalId: z5.string().nullable(),
1068
+ goalTitle: z5.string().nullable(),
1069
+ version: z5.number(),
1070
+ pinned: z5.boolean(),
1071
+ /** When the person last wrote its text themselves. */
1072
+ editedAt: z5.string().nullable(),
1073
+ createdAt: z5.string(),
1074
+ updatedAt: z5.string(),
1075
+ /** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
1076
+ sources: z5.array(z5.object({ entryId: z5.string(), words: z5.string().nullable(), at: z5.string() }))
933
1077
  });
934
- var QueueQuestionSchema = z4.object({
1078
+ var QueueQuestionSchema = z5.object({
935
1079
  /** The decision need's id — what an answer is accepted against. */
936
- id: z4.string(),
1080
+ id: z5.string(),
937
1081
  /** The words that were asked, from the request Entry that asked them. */
938
- question: z4.string(),
1082
+ question: z5.string(),
939
1083
  /** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
940
1084
  * for a need whose request Entry is carried by no interactive Delivery, which nothing
941
1085
  * can answer. */
942
- deliveryId: z4.string().nullable().default(null),
1086
+ deliveryId: z5.string().nullable().default(null),
943
1087
  /** The Entry the ruling is about. */
944
- aboutId: z4.string().nullable().default(null),
1088
+ aboutId: z5.string().nullable().default(null),
945
1089
  /** Empty for a free-text question. */
946
- options: z4.array(OptionSchema).default([]),
947
- select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
948
- askedAt: z4.string(),
1090
+ options: z5.array(OptionSchema).default([]),
1091
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
1092
+ askedAt: z5.string(),
949
1093
  /** Null while the question is open — which is how the page tells the two apart. */
950
- answeredAt: z4.string().nullable().default(null),
1094
+ answeredAt: z5.string().nullable().default(null),
951
1095
  /** The ruling in the person's own words, from the contribution that replied — not the
952
1096
  * option id, which is not something anyone reads back. Null while it is open, and null
953
1097
  * for a settled question whose reply carried nothing readable. */
954
- answer: z4.string().nullable().default(null),
1098
+ answer: z5.string().nullable().default(null),
955
1099
  /** The Goal this question belongs to — a step knows its Goal on its own, not only through
956
1100
  * an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
957
1101
  * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
958
1102
  * order, its route, home's trees and the list of steps all take a step's Goal from here, so
959
1103
  * this is the field they agree through rather than each re-deriving it from the row it
960
1104
  * arrived under. Required because the API projects it on every need it sends. */
961
- goalId: z4.string(),
1105
+ goalId: z5.string(),
962
1106
  /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
963
1107
  * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
964
1108
  * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
965
1109
  * gate at all. */
966
- blocked: z4.boolean().default(false)
1110
+ blocked: z5.boolean().default(false)
967
1111
  });
968
- var QueueReplySchema = z4.object({
1112
+ var QueueReplySchema = z5.object({
969
1113
  /** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
970
1114
  * id) — so the phone can tell a reply it just sent from one the queue already carries, and the
971
1115
  * walk can name it in its zoom. */
972
- id: z4.string(),
1116
+ id: z5.string(),
973
1117
  /** The Goal the note is on. */
974
- goalId: z4.string(),
1118
+ goalId: z5.string(),
975
1119
  /** What the note said. */
976
- note: z4.string(),
1120
+ note: z5.string(),
977
1121
  /** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
978
- deliveryId: z4.string(),
979
- requestEntryId: z4.string(),
980
- askedAt: z4.string(),
1122
+ deliveryId: z5.string(),
1123
+ requestEntryId: z5.string(),
1124
+ askedAt: z5.string(),
981
1125
  /** When the person last replied — the window's start. */
982
- repliedAt: z4.string(),
1126
+ repliedAt: z5.string(),
983
1127
  /** The person's latest words about it; null when there is nothing readable in them. */
984
- reply: z4.string().nullable()
1128
+ reply: z5.string().nullable()
985
1129
  });
986
- var QueueItemSchema = z4.object({
987
- id: z4.string(),
1130
+ var QueueItemSchema = z5.object({
1131
+ id: z5.string(),
988
1132
  /** One-line headline — the first sentence of the outcome. */
989
- title: z4.string(),
1133
+ title: z5.string(),
990
1134
  /** The outcome in full, verbatim: the person's own words are what an assignee sees. */
991
- intent: z4.string(),
1135
+ intent: z5.string(),
992
1136
  /** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
993
- state: z4.string(),
1137
+ state: z5.string(),
994
1138
  /** Who holds it (a participant ref); null when nobody does yet. */
995
- assignee: z4.string().nullable().default(null),
1139
+ assignee: z5.string().nullable().default(null),
996
1140
  /** What the agent last said it was doing; null if it has said nothing. */
997
- progress: z4.string().nullable().default(null),
1141
+ progress: z5.string().nullable().default(null),
998
1142
  /** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
999
1143
  * served only while it was written for the current note. Null means show the Goal's name. */
1000
- progressLine: z4.string().nullable().optional(),
1001
- reviewPending: z4.boolean().default(false),
1002
- dueAt: z4.string().nullable().default(null),
1144
+ progressLine: z5.string().nullable().optional(),
1145
+ reviewPending: z5.boolean().default(false),
1146
+ dueAt: z5.string().nullable().default(null),
1003
1147
  /** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
1004
1148
  * with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
1005
- finishedAt: z4.string().nullable().optional(),
1149
+ finishedAt: z5.string().nullable().optional(),
1006
1150
  /** The Goal this one was opened under; null at the root. */
1007
- parentGoalId: z4.string().nullable().default(null),
1151
+ parentGoalId: z5.string().nullable().default(null),
1008
1152
  /** Goals opened under this one — only those the same list holds. */
1009
- childGoalIds: z4.array(z4.string()).default([]),
1153
+ childGoalIds: z5.array(z5.string()).default([]),
1010
1154
  /** Goals this one waits on (start or finish gates). */
1011
- dependencyGoalIds: z4.array(z4.string()).default([]),
1155
+ dependencyGoalIds: z5.array(z5.string()).default([]),
1012
1156
  /** True while any gate is on a Goal that is not done — the walk draws it dashed. */
1013
- blocked: z4.boolean().default(false),
1157
+ blocked: z5.boolean().default(false),
1014
1158
  /** Its questions: every OPEN one, and at most ten settled, newest settled first
1015
1159
  * (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
1016
1160
  * `answered` are, and a settled one's words are a line (280 characters), its body read when the
1017
1161
  * question is opened. */
1018
- questions: z4.array(QueueQuestionSchema).default([]),
1162
+ questions: z5.array(QueueQuestionSchema).default([]),
1019
1163
  /** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
1020
1164
  * bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
1021
1165
  * right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
1022
1166
  * by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
1023
- asked: z4.number().optional(),
1024
- answered: z4.number().optional(),
1167
+ asked: z5.number().optional(),
1168
+ answered: z5.number().optional(),
1025
1169
  /** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
1026
1170
  * Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
1027
1171
  * not spell an empty list. */
1028
- replies: z4.array(QueueReplySchema).optional(),
1172
+ replies: z5.array(QueueReplySchema).optional(),
1029
1173
  /** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
1030
- repo: z4.string().nullable().optional(),
1031
- createdAt: z4.string(),
1032
- updatedAt: z4.string().nullable().default(null),
1174
+ repo: z5.string().nullable().optional(),
1175
+ createdAt: z5.string(),
1176
+ updatedAt: z5.string().nullable().default(null),
1033
1177
  /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
1034
1178
  * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
1035
1179
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
1036
- lastProgressAt: z4.string().nullable().optional(),
1180
+ lastProgressAt: z5.string().nullable().optional(),
1037
1181
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
1038
1182
  * kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
1039
1183
  * note. A progress note is an Entry, so this is already the newer of the two: the person's note
1040
1184
  * shows the moment it is written, and the agent's reply or next note replaces it by being newer.
1041
1185
  * `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
1042
1186
  * Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
1043
- latest: z4.object({
1044
- from: z4.enum(["person", "agent"]),
1045
- said: z4.string(),
1046
- at: z4.string(),
1047
- entryId: z4.string()
1187
+ latest: z5.object({
1188
+ from: z5.enum(["person", "agent"]),
1189
+ said: z5.string(),
1190
+ at: z5.string(),
1191
+ entryId: z5.string()
1048
1192
  }).nullable().optional(),
1049
1193
  /** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
1050
1194
  * the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
@@ -1057,36 +1201,51 @@ var QueueItemSchema = z4.object({
1057
1201
  * minute after the person speaks erases their instant from it, and the durable traces the client
1058
1202
  * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
1059
1203
  * Entry with no `about_id` is in neither. */
1060
- lastPersonAt: z4.string().nullable().optional()
1204
+ lastPersonAt: z5.string().nullable().optional(),
1205
+ /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
1206
+ * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
1207
+ * incremental read knows a row has not moved: the phone echoes back the revs it holds
1208
+ * (`POST /api/goals/changes`) and is sent only the rows whose rev differs.
1209
+ *
1210
+ * IT IS THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK. Nothing here reasons about which
1211
+ * writes change which field — the comparison is over the bytes the phone is holding, so a fact
1212
+ * the row shows that no `updated_at` moves for (a lease lapsing, a dependency's state, a
1213
+ * sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
1214
+ * queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
1215
+ rev: z5.string().optional()
1216
+ });
1217
+ var QueueDeltaSchema = z5.object({
1218
+ ids: z5.array(z5.string()),
1219
+ items: z5.array(QueueItemSchema)
1061
1220
  });
1062
1221
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
1063
- var NoteSourceSchema = z4.enum(["app", "call"]);
1064
- var NoteStatusSchema = z4.enum(["open", "assigned", "in_progress", "done"]);
1065
- var NoteRepeatSchema = z4.enum(["once", "until_done"]);
1066
- var DecisionSchema = z4.object({
1067
- id: z4.string(),
1222
+ var NoteSourceSchema = z5.enum(["app", "call"]);
1223
+ var NoteStatusSchema = z5.enum(["open", "assigned", "in_progress", "done"]);
1224
+ var NoteRepeatSchema = z5.enum(["once", "until_done"]);
1225
+ var DecisionSchema = z5.object({
1226
+ id: z5.string(),
1068
1227
  /** The note this decision refines; null = recorded on a bare thread (the
1069
1228
  * extensibility seam — any conversation can accrue decisions). */
1070
- noteId: z4.string().nullable(),
1229
+ noteId: z5.string().nullable(),
1071
1230
  /** What was ambiguous — the broker's (or the user's own) question. */
1072
- question: z4.string(),
1231
+ question: z5.string(),
1073
1232
  /** The user's ruling; null while the question is open. */
1074
- answer: z4.string().nullable(),
1075
- decidedAt: z4.string().nullable(),
1076
- createdAt: z4.string()
1233
+ answer: z5.string().nullable(),
1234
+ decidedAt: z5.string().nullable(),
1235
+ createdAt: z5.string()
1077
1236
  });
1078
- var NoteSchema = z4.object({
1079
- id: z4.string(),
1237
+ var NoteSchema = z5.object({
1238
+ id: z5.string(),
1080
1239
  /** One-line headline (broker-titled; deterministic floor). */
1081
- title: z4.string(),
1240
+ title: z5.string(),
1082
1241
  /** The original intent, verbatim — assignees always see the user's own words. */
1083
- intent: z4.string(),
1242
+ intent: z5.string(),
1084
1243
  source: NoteSourceSchema,
1085
1244
  status: NoteStatusSchema,
1086
1245
  /** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
1087
- assignee: z4.string().nullable(),
1246
+ assignee: z5.string().nullable(),
1088
1247
  /** The request thread minted at assignment; null until assigned. */
1089
- parentId: z4.string().nullable(),
1248
+ parentId: z5.string().nullable(),
1090
1249
  /** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
1091
1250
  * call — never a deadline. It only ever comes from the user's own words, so when it
1092
1251
  * passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
@@ -1095,147 +1254,152 @@ var NoteSchema = z4.object({
1095
1254
  // Defaulted, not required: a Note from an API deploy older than the reminders
1096
1255
  // migration has none of these, and the defaults ARE what it means — no not-before,
1097
1256
  // one ride, never ridden. Parsing must not fail across a rolling deploy.
1098
- dueAt: z4.string().nullable().default(null),
1257
+ dueAt: z5.string().nullable().default(null),
1099
1258
  repeat: NoteRepeatSchema.default("once"),
1100
1259
  /** How many calls have already carried it — the fatigue cap counts rides, not days. */
1101
- rides: z4.number().int().default(0),
1102
- lastRideAt: z4.string().nullable().default(null),
1103
- createdAt: z4.string()
1260
+ rides: z5.number().int().default(0),
1261
+ lastRideAt: z5.string().nullable().default(null),
1262
+ createdAt: z5.string()
1104
1263
  });
1105
- var TriageItemSchema = z4.object({
1106
- noteId: z4.string(),
1264
+ var TriageItemSchema = z5.object({
1265
+ noteId: z5.string(),
1107
1266
  /** The note's headline at run time. */
1108
- title: z4.string(),
1267
+ title: z5.string(),
1109
1268
  /** WHY, in one short human line, evidence first — this is read on a phone underneath
1110
1269
  * the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
1111
1270
  * Never a model's reasoning transcript, never an id. */
1112
- why: z4.string()
1271
+ why: z5.string()
1113
1272
  });
1114
- var TriageAssignmentSchema = z4.object({
1273
+ var TriageAssignmentSchema = z5.object({
1115
1274
  /** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
1116
- agent: z4.string(),
1275
+ agent: z5.string(),
1117
1276
  /** Its display name at run time (the name on the hatchling's card). Denormalized for the
1118
1277
  * same reason as `title`: the card must render from the proposal alone. */
1119
- agentName: z4.string(),
1120
- notes: z4.array(TriageItemSchema)
1278
+ agentName: z5.string(),
1279
+ notes: z5.array(TriageItemSchema)
1121
1280
  });
1122
- var TriageStatusSchema = z4.enum(["open", "superseded", "dismissed"]);
1123
- var SubmitTriageSchema = z4.object({
1281
+ var TriageStatusSchema = z5.enum(["open", "superseded", "dismissed"]);
1282
+ var SubmitTriageSchema = z5.object({
1124
1283
  /** Which runtime judged: "ollama" (inference never left the machine) or a harness the
1125
1284
  * user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
1126
1285
  * so the phone can say where the content went — an unattributed privacy claim is worth
1127
1286
  * nothing, and #1106's promise is precisely "Paigy's servers never see this". */
1128
- provider: z4.string().min(1).max(60),
1287
+ provider: z5.string().min(1).max(60),
1129
1288
  /** The concrete model when the provider names one (an ollama tag); null otherwise. */
1130
- model: z4.string().max(200).nullable().optional(),
1289
+ model: z5.string().max(200).nullable().optional(),
1131
1290
  /** How many open notes the run actually looked at — the denominator on the phone
1132
1291
  * ("6 of 50"), and the honest answer to "did it read the whole queue?". */
1133
- reviewed: z4.number().int().min(0).max(1e4).default(0),
1134
- close: z4.array(TriageItemSchema).max(200).default([]),
1135
- stale: z4.array(TriageItemSchema).max(200).default([]),
1136
- assign: z4.array(TriageAssignmentSchema).max(50).default([])
1292
+ reviewed: z5.number().int().min(0).max(1e4).default(0),
1293
+ close: z5.array(TriageItemSchema).max(200).default([]),
1294
+ stale: z5.array(TriageItemSchema).max(200).default([]),
1295
+ assign: z5.array(TriageAssignmentSchema).max(50).default([])
1137
1296
  });
1138
1297
  var TriageProposalSchema = SubmitTriageSchema.extend({
1139
- id: z4.string(),
1140
- runAt: z4.string(),
1298
+ id: z5.string(),
1299
+ runAt: z5.string(),
1141
1300
  status: TriageStatusSchema,
1142
- model: z4.string().nullable().default(null)
1301
+ model: z5.string().nullable().default(null)
1143
1302
  });
1144
- var AcceptTriageSchema = z4.discriminatedUnion("group", [
1145
- z4.object({ group: z4.literal("close"), noteIds: z4.array(z4.string()).max(200).optional() }),
1146
- z4.object({ group: z4.literal("stale"), noteIds: z4.array(z4.string()).max(200).optional() }),
1147
- z4.object({
1148
- group: z4.literal("assign"),
1149
- agent: z4.string().min(1),
1150
- noteIds: z4.array(z4.string()).max(200).optional()
1303
+ var AcceptTriageSchema = z5.discriminatedUnion("group", [
1304
+ z5.object({ group: z5.literal("close"), noteIds: z5.array(z5.string()).max(200).optional() }),
1305
+ z5.object({ group: z5.literal("stale"), noteIds: z5.array(z5.string()).max(200).optional() }),
1306
+ z5.object({
1307
+ group: z5.literal("assign"),
1308
+ agent: z5.string().min(1),
1309
+ noteIds: z5.array(z5.string()).max(200).optional()
1151
1310
  })
1152
1311
  ]);
1153
- var AcceptTriageResultSchema = z4.object({
1154
- accepted: z4.array(z4.string()),
1155
- failed: z4.array(z4.object({ noteId: z4.string(), reason: z4.string() }))
1312
+ var AcceptTriageResultSchema = z5.object({
1313
+ accepted: z5.array(z5.string()),
1314
+ failed: z5.array(z5.object({ noteId: z5.string(), reason: z5.string() }))
1156
1315
  });
1157
- var DeliveryModeSchema = z4.enum(["poll", "self_hosted"]);
1158
- var RegisterDeliverySchema = z4.object({ mode: DeliveryModeSchema });
1159
- var OAuthStartSchema = z4.object({
1160
- provider: z4.enum(["cma"]),
1161
- returnTo: z4.string().min(1)
1316
+ var DeliveryModeSchema = z5.enum(["poll", "self_hosted"]);
1317
+ var RegisterDeliverySchema = z5.object({ mode: DeliveryModeSchema });
1318
+ var OAuthStartSchema = z5.object({
1319
+ provider: z5.enum(["cma"]),
1320
+ returnTo: z5.string().min(1)
1162
1321
  });
1163
- var DeliveryConfigSchema = z4.object({
1164
- tokenId: z4.string(),
1322
+ var DeliveryConfigSchema = z5.object({
1323
+ tokenId: z5.string(),
1165
1324
  mode: DeliveryModeSchema,
1166
1325
  /** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
1167
1326
  * (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
1168
1327
  * carries credentials; only a `poll` registration can come back with null here. */
1169
- realtime: z4.object({ url: z4.string(), anonKey: z4.string() }).nullable()
1328
+ realtime: z5.object({ url: z5.string(), anonKey: z5.string() }).nullable()
1170
1329
  });
1171
- var HostDecisionSchema = z4.object({
1330
+ var HostDecisionSchema = z5.object({
1172
1331
  /** The agent's token id: the row's `recipient`. */
1173
- agent: z4.string().uuid(),
1174
- decision: z4.enum(["stood_back", "took_over"]),
1332
+ agent: z5.string().uuid(),
1333
+ decision: z5.enum(["stood_back", "took_over"]),
1175
1334
  /** The work it was about: the Goal `claim_goal` would hand that agent next. */
1176
- goalId: z4.string().uuid().nullable().optional(),
1335
+ goalId: z5.string().uuid().nullable().optional(),
1177
1336
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
1178
- seenAt: z4.string().datetime().nullable().optional(),
1337
+ seenAt: z5.string().datetime().nullable().optional(),
1179
1338
  /** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
1180
- since: z4.string().datetime().nullable().optional(),
1339
+ since: z5.string().datetime().nullable().optional(),
1181
1340
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
1182
- said: z4.string().max(300).optional()
1341
+ said: z5.string().max(300).optional()
1183
1342
  });
1184
- var WakeNudgeSchema = z4.object({
1185
- kind: z4.enum(["reply", "request", "callback"]),
1186
- notificationId: z4.string().optional(),
1187
- parentId: z4.string()
1343
+ var WakeNudgeSchema = z5.object({
1344
+ kind: z5.enum(["reply", "request", "callback"]),
1345
+ notificationId: z5.string().optional(),
1346
+ parentId: z5.string()
1188
1347
  });
1189
- var PairingStatusSchema = z4.enum(["pending", "approved", "denied", "expired"]);
1190
- var DeviceCodeSchema = z4.object({
1191
- device_code: z4.string(),
1192
- user_code: z4.string(),
1193
- verification_uri: z4.string().url(),
1194
- verification_uri_complete: z4.string().url(),
1195
- interval: z4.number(),
1196
- expires_in: z4.number()
1348
+ var PairingStatusSchema = z5.enum(["pending", "approved", "denied", "expired"]);
1349
+ var DeviceCodeSchema = z5.object({
1350
+ device_code: z5.string(),
1351
+ user_code: z5.string(),
1352
+ verification_uri: z5.string().url(),
1353
+ verification_uri_complete: z5.string().url(),
1354
+ interval: z5.number(),
1355
+ expires_in: z5.number()
1197
1356
  });
1198
- var DeviceInfoSchema = z4.object({
1199
- code: z4.string(),
1357
+ var DeviceInfoSchema = z5.object({
1358
+ code: z5.string(),
1200
1359
  /** The agent's suggested name (from /device/code) — shown on the approval screen,
1201
1360
  * pre-filling the name field the human can edit. */
1202
- name: z4.string(),
1361
+ name: z5.string(),
1203
1362
  /** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
1204
1363
  * build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
1205
1364
  * install crashes on the pairing screen on first launch, before the OTA lands
1206
1365
  * (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
1207
- agent: z4.string().optional(),
1208
- device: z4.string().nullable(),
1366
+ agent: z5.string().optional(),
1367
+ device: z5.string().nullable(),
1209
1368
  status: PairingStatusSchema
1210
1369
  });
1211
- var DeviceTokenSchema = z4.object({
1212
- access_token: z4.string(),
1370
+ var DeviceTokenSchema = z5.object({
1371
+ access_token: z5.string(),
1213
1372
  /** The pairing's single name (user-typed at approval, the agent's suggestion, or
1214
1373
  * a default silly name). */
1215
- name: z4.string(),
1216
- device: z4.string().nullable(),
1374
+ name: z5.string(),
1375
+ device: z5.string().nullable(),
1217
1376
  /** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
1218
1377
  * draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
1219
- voice: z4.string().nullable().optional(),
1378
+ voice: z5.string().nullable().optional(),
1220
1379
  /** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
1221
1380
  * that survives a rename. Cached by the host's identity beat. */
1222
- token_id: z4.string().nullable().optional(),
1381
+ token_id: z5.string().nullable().optional(),
1223
1382
  /** WHERE this identity works — the folder a wake should land it in. Written by the host
1224
1383
  * at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
1225
1384
  * landed in the FIRST granted workspace and the agent rediscovered its own repo from
1226
1385
  * the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
1227
- workspace: z4.string().nullable().optional(),
1386
+ workspace: z5.string().nullable().optional(),
1228
1387
  /** Local host recovery must preserve the launch's runtime and Paigy identity. */
1229
- harness: z4.enum(["claude", "codex", "agy"]).optional(),
1230
- session_id: z4.string().uuid().optional(),
1231
- uik_pub: z4.string().nullable().optional()
1388
+ harness: z5.enum(["claude", "codex", "agy"]).optional(),
1389
+ session_id: z5.string().uuid().optional(),
1390
+ /** A conversation the host must not resume: its context is full, so every turn fails
1391
+ * ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
1392
+ * whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
1393
+ * copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
1394
+ full_session: z5.string().optional(),
1395
+ uik_pub: z5.string().nullable().optional()
1232
1396
  });
1233
- var SupportRequestSchema = z4.object({
1234
- email: z4.string().email().max(320),
1235
- message: z4.string().trim().min(1).max(5e3),
1236
- name: z4.string().trim().max(120).optional()
1397
+ var SupportRequestSchema = z5.object({
1398
+ email: z5.string().email().max(320),
1399
+ message: z5.string().trim().min(1).max(5e3),
1400
+ name: z5.string().trim().max(120).optional()
1237
1401
  });
1238
- var NotificationFeedbackKindSchema = z4.enum([
1402
+ var NotificationFeedbackKindSchema = z5.enum([
1239
1403
  "break_down",
1240
1404
  // "This should be more than one ask — break it down."
1241
1405
  "regenerate_options",
@@ -1250,116 +1414,116 @@ var NotificationFeedbackKindSchema = z4.enum([
1250
1414
  // anything else — the note carries it.
1251
1415
  ]);
1252
1416
  var SlimOptionSchema = OptionSchema.omit({ html: true });
1253
- var QuestionRowSchema = z4.object({
1417
+ var QuestionRowSchema = z5.object({
1254
1418
  /** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
1255
- id: z4.string(),
1256
- deliveryId: z4.string(),
1257
- entryId: z4.string(),
1419
+ id: z5.string(),
1420
+ deliveryId: z5.string(),
1421
+ entryId: z5.string(),
1258
1422
  /** The decision it waits on; null for an update, which asks nothing. */
1259
- needId: z4.string().nullable(),
1260
- goalIds: z4.array(z4.string()),
1423
+ needId: z5.string().nullable(),
1424
+ goalIds: z5.array(z5.string()),
1261
1425
  /** The name of the work it is about, when the read could word it. */
1262
- goalTitle: z4.string().optional(),
1263
- tokenId: z4.string().optional(),
1264
- name: z4.string(),
1265
- title: z4.string(),
1266
- body: z4.string(),
1267
- select: z4.enum(["one", "many", "rank", "confirm", "text"]),
1268
- options: z4.array(SlimOptionSchema),
1269
- hasPreview: z4.boolean(),
1270
- blocking: z4.boolean(),
1271
- askedAt: z4.string().datetime(),
1426
+ goalTitle: z5.string().optional(),
1427
+ tokenId: z5.string().optional(),
1428
+ name: z5.string(),
1429
+ title: z5.string(),
1430
+ body: z5.string(),
1431
+ select: z5.enum(["one", "many", "rank", "confirm", "text"]),
1432
+ options: z5.array(SlimOptionSchema),
1433
+ hasPreview: z5.boolean(),
1434
+ blocking: z5.boolean(),
1435
+ askedAt: z5.string().datetime(),
1272
1436
  ring: InboxItemSchema.shape.ring,
1273
- onCall: z4.literal(true).optional(),
1274
- sealed: z4.boolean()
1437
+ onCall: z5.literal(true).optional(),
1438
+ sealed: z5.boolean()
1275
1439
  });
1276
- var WorkStateSchema = z4.enum(["ready", "active", "waiting", "done", "cancelled"]);
1277
- var WorkRowSchema = z4.object({
1278
- id: z4.string(),
1279
- parentId: z4.string().nullable(),
1280
- title: z4.string(),
1440
+ var WorkStateSchema = z5.enum(["ready", "active", "waiting", "done", "cancelled"]);
1441
+ var WorkRowSchema = z5.object({
1442
+ id: z5.string(),
1443
+ parentId: z5.string().nullable(),
1444
+ title: z5.string(),
1281
1445
  /** Straight off the Goal. */
1282
1446
  state: WorkStateSchema,
1283
- owner: z4.string().nullable(),
1284
- revision: z4.number().int(),
1447
+ owner: z5.string().nullable(),
1448
+ revision: z5.number().int(),
1285
1449
  /** Open questions on it, counted to 100. */
1286
- waiting: z4.number().int(),
1450
+ waiting: z5.number().int(),
1287
1451
  /** Held by a gate on work that is not done. */
1288
- blocked: z4.boolean(),
1289
- lastProgressAt: z4.string().datetime().nullable(),
1452
+ blocked: z5.boolean(),
1453
+ lastProgressAt: z5.string().datetime().nullable(),
1290
1454
  /** The line written for its newest progress note, else that note's first words. */
1291
- line: z4.string().nullable(),
1455
+ line: z5.string().nullable(),
1292
1456
  /** Work directly under it, counted to 100; the list carries up to 12 of them. */
1293
- children: z4.number().int(),
1294
- createdAt: z4.string().datetime(),
1295
- updatedAt: z4.string().datetime(),
1457
+ children: z5.number().int(),
1458
+ createdAt: z5.string().datetime(),
1459
+ updatedAt: z5.string().datetime(),
1296
1460
  /** When anything at or under it last moved — the order the list is in. */
1297
- activeAt: z4.string().datetime(),
1461
+ activeAt: z5.string().datetime(),
1298
1462
  /** A sealed outcome has no title here; the work's page opens it. */
1299
- sealed: z4.boolean()
1463
+ sealed: z5.boolean()
1300
1464
  });
1301
1465
  var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
1302
1466
  var AgentRowSchema = ComputerRowSchema.extend({
1303
1467
  /** Open questions it is asking the person, over every open card; null when that read failed. */
1304
- asking: z4.number().int().nullable(),
1305
- oldestAskAt: z4.string().datetime().nullable(),
1468
+ asking: z5.number().int().nullable(),
1469
+ oldestAskAt: z5.string().datetime().nullable(),
1306
1470
  /** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
1307
1471
  * many in all among the account's 200 most recently active agent-held live Goals
1308
1472
  * (`agent_holds`); null when that read failed. */
1309
- holds: z4.array(z4.object({ id: z4.string(), title: z4.string() })).nullable(),
1310
- held: z4.number().int().nullable(),
1473
+ holds: z5.array(z5.object({ id: z5.string(), title: z5.string() })).nullable(),
1474
+ held: z5.number().int().nullable(),
1311
1475
  /** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
1312
1476
  * while none has, or when that read failed. */
1313
- cold: z4.string().datetime().nullable(),
1477
+ cold: z5.string().datetime().nullable(),
1314
1478
  /** The newest line of its working log, and when the harness saw it. */
1315
- line: z4.string().nullable(),
1316
- lineAt: z4.string().datetime().nullable()
1479
+ line: z5.string().nullable(),
1480
+ lineAt: z5.string().datetime().nullable()
1317
1481
  });
1318
- var SnapshotSchema = z4.object({
1482
+ var SnapshotSchema = z5.object({
1319
1483
  /** The API's clock, taken before the first read: what a later delta will start from. */
1320
- at: z4.string().datetime(),
1321
- questions: z4.object({
1484
+ at: z5.string().datetime(),
1485
+ questions: z5.object({
1322
1486
  /** The newest 30 open cards, questions before updates. */
1323
- items: z4.array(QuestionRowSchema),
1487
+ items: z5.array(QuestionRowSchema),
1324
1488
  /** Every open question, and apart from them every update, and what was put off. */
1325
- total: z4.number().int(),
1326
- updates: z4.number().int(),
1327
- putOff: z4.number().int()
1489
+ total: z5.number().int(),
1490
+ updates: z5.number().int(),
1491
+ putOff: z5.number().int()
1328
1492
  }).nullable(),
1329
- agents: z4.object({
1493
+ agents: z5.object({
1330
1494
  /** Up to 60, most recently seen first. */
1331
- items: z4.array(AgentRowSchema),
1332
- more: z4.boolean()
1495
+ items: z5.array(AgentRowSchema),
1496
+ more: z5.boolean()
1333
1497
  }).nullable(),
1334
- work: z4.object({
1498
+ work: z5.object({
1335
1499
  /** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
1336
- items: z4.array(WorkRowSchema),
1500
+ items: z5.array(WorkRowSchema),
1337
1501
  /** How much work is behind each of the Work tab's four filters, each counted to 100, read with
1338
1502
  * the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
1339
1503
  * `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
1340
1504
  * is `ready`; Done is `done` or `cancelled`. */
1341
- counts: z4.object({ live: z4.number().int(), waiting: z4.number().int(), notStarted: z4.number().int(), done: z4.number().int() })
1505
+ counts: z5.object({ live: z5.number().int(), waiting: z5.number().int(), notStarted: z5.number().int(), done: z5.number().int() })
1342
1506
  }).nullable(),
1343
- you: z4.object({
1507
+ you: z5.object({
1344
1508
  settings: UserSettingsSchema,
1345
- callable: z4.boolean(),
1509
+ callable: z5.boolean(),
1346
1510
  /** Up to 20 paired computers; null when the roster read failed. */
1347
- computers: z4.array(ComputerRowSchema).nullable()
1511
+ computers: z5.array(ComputerRowSchema).nullable()
1348
1512
  }).nullable()
1349
1513
  });
1350
- var CallRecapSchema = z4.object({
1351
- call: z4.object({
1352
- status: z4.string(),
1353
- startedAt: z4.string(),
1354
- durationMs: z4.number().nullable(),
1355
- agents: z4.array(z4.object({ id: z4.string(), name: z4.string().nullable() }))
1514
+ var CallRecapSchema = z5.object({
1515
+ call: z5.object({
1516
+ status: z5.string(),
1517
+ startedAt: z5.string(),
1518
+ durationMs: z5.number().nullable(),
1519
+ agents: z5.array(z5.object({ id: z5.string(), name: z5.string().nullable() }))
1356
1520
  }),
1357
- topics: z4.array(z4.object({
1358
- goalId: z4.string().uuid(),
1359
- title: z4.string(),
1360
- owner: z4.string(),
1361
- state: z4.string(),
1362
- questions: z4.array(z4.object({ id: z4.string().uuid(), state: z4.string(), title: z4.string() })),
1521
+ topics: z5.array(z5.object({
1522
+ goalId: z5.string().uuid(),
1523
+ title: z5.string(),
1524
+ owner: z5.string(),
1525
+ state: z5.string(),
1526
+ questions: z5.array(z5.object({ id: z5.string().uuid(), state: z5.string(), title: z5.string() })),
1363
1527
  /** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
1364
1528
  * their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
1365
1529
  * summarized like a pre-made option is"), so the row scans like a chosen option and their
@@ -1367,17 +1531,18 @@ var CallRecapSchema = z4.object({
1367
1531
  * anything that is not an answer. */
1368
1532
  /** `about` is the request the line answered (its question), null for words that answered none —
1369
1533
  * the key the screen groups on, so one question is one row however many times it was answered. */
1370
- lines: z4.array(z4.object({ entryId: z4.string().uuid(), words: z4.string(), headline: z4.string().optional(), about: z4.string().nullable().optional() }))
1534
+ lines: z5.array(z5.object({ entryId: z5.string().uuid(), words: z5.string(), headline: z5.string().optional(), about: z5.string().nullable().optional() }))
1371
1535
  })),
1372
- unfiled: z4.array(z4.object({ lineId: z4.string().uuid(), words: z4.string(), atMs: z4.number() })),
1373
- more: z4.object({ lines: z4.number(), entries: z4.number(), topics: z4.number() })
1536
+ unfiled: z5.array(z5.object({ lineId: z5.string().uuid(), words: z5.string(), atMs: z5.number() })),
1537
+ more: z5.object({ lines: z5.number(), entries: z5.number(), topics: z5.number() })
1374
1538
  });
1375
1539
 
1376
1540
  // src/listening.ts
1541
+ import { createHash } from "crypto";
1377
1542
  import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
1378
1543
  import { homedir } from "os";
1379
1544
  import { dirname, join } from "path";
1380
- var HARNESS_MARKS = ["PAIGY_HARNESS", "PAIGY_SESSION_ID"];
1545
+ var HARNESS_MARKS = ["PAIGY_HARNESS"];
1381
1546
  function harnessDriven(env) {
1382
1547
  return HARNESS_MARKS.some((mark) => !!env[mark]);
1383
1548
  }
@@ -1433,11 +1598,12 @@ function decideListen(f) {
1433
1598
  };
1434
1599
  }
1435
1600
  if (f.pidAlive && f.pid !== void 0) {
1436
- return { status: "listening", via: "daemon", pid: f.pid, message: `Already listening (paigy-listen pid ${f.pid}). Do nothing.` };
1601
+ const connected = f.wake?.ready === true && (f.wake.target.kind !== "codex" || f.wake.target.thread === f.env.CODEX_THREAD_ID);
1602
+ return { status: connected ? "listening" : "receiving", via: "daemon", pid: f.pid, ...f.wake ? { wake: f.wake } : {}, message: connected ? `Listener running (pid ${f.pid}); wake adapter ready. A queued message is not proof the agent handled it.` : `Listener running (pid ${f.pid}), but automatic session resume is not confirmed${f.wake?.lastError ? `: ${f.wake.lastError}` : ". Watch its output or configure a supported wake adapter"}. Do not start a second listener.` };
1437
1603
  }
1438
1604
  return {
1439
1605
  status: "start",
1440
- command: `PAIGY_AGENT=${word(f.slot)} PAIGY_SESSION_ID=${word(f.session)} ${word(f.execPath)} ${word(f.listenJs)} --brief`,
1606
+ command: `PAIGY_AGENT=${word(f.slot)} PAIGY_SESSION_ID=${word(f.session)} ${f.env.CODEX_THREAD_ID ? `CODEX_THREAD_ID=${word(f.env.CODEX_THREAD_ID)} ` : ""}${f.env.PAIGY_ON_WAKE ? `PAIGY_ON_WAKE=${word(f.env.PAIGY_ON_WAKE)} ` : ""}${word(f.execPath)} ${word(f.listenJs)} --brief`,
1441
1607
  how: "Run this command NOW as a watched background task (your harness's background-task/monitor tool), and leave it running for this session. Each line it prints is a Delivery waiting for you: claim_goal the Goal it names, read it, and answer with contact. Start it once."
1442
1608
  };
1443
1609
  }
@@ -1475,16 +1641,28 @@ function listenerAlive(slot, home = homedir()) {
1475
1641
  return null;
1476
1642
  }
1477
1643
  }
1644
+ function listenerKey(slot, env) {
1645
+ return env.CODEX_THREAD_ID ? `${slot}-${createHash("sha256").update(env.CODEX_THREAD_ID).digest("hex")}` : slot;
1646
+ }
1647
+ function wakeStatusPath(slot, home = homedir()) {
1648
+ return `${listenMark(slot, home)}.wake.json`;
1649
+ }
1650
+ function existingListener(slot, env, home = homedir()) {
1651
+ return listenerAlive(listenerKey(slot, env), home) ?? (env.CODEX_THREAD_ID ? listenerAlive(slot, home) : null);
1652
+ }
1478
1653
 
1479
1654
  export {
1480
1655
  mcpInputSchema,
1481
1656
  AGENT_TOOLS,
1482
1657
  serverInstructions,
1483
1658
  entryWords,
1659
+ harnessDriven,
1484
1660
  sessionStartHook,
1485
1661
  withSessionStartHook,
1486
1662
  decideListen,
1487
1663
  writeListenMark,
1488
1664
  removeListenMark,
1489
- listenerAlive
1665
+ listenerKey,
1666
+ wakeStatusPath,
1667
+ existingListener
1490
1668
  };