@paigy/mcp 0.40.25 → 0.40.27

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,11 +1,15 @@
1
1
  // ../../packages/schema/dist/index.js
2
- import { z as z5 } from "zod";
2
+ import { z as z7 } 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
7
  import { z as z4 } from "zod";
8
8
  import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
9
+ import { z as z5 } from "zod";
10
+ import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
11
+ import { z as z6 } from "zod";
12
+ import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
9
13
  var OPTIONS_MIN = 2;
10
14
  var OPTIONS_MAX = 6;
11
15
  var OptionSchema = z.object({
@@ -15,14 +19,20 @@ var OptionSchema = z.object({
15
19
  // .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
16
20
  // the constraints below are what an agent reads when deciding to use these.
17
21
  html: z.string().max(16384).describe(
18
- "Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
22
+ "Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use the image URL instead."
19
23
  ).optional(),
20
24
  image: z.string().url().describe(
21
- "Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
25
+ "Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use the HTML preview instead."
22
26
  ).optional()
23
27
  });
24
- var OptionInputSchema = OptionSchema.omit({ id: true }).extend({
25
- label: z.string().trim().min(1).max(1e3)
28
+ var OptionInputSchema = z.object({
29
+ id: z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_.:-]+$/).optional().describe(
30
+ `Optional: this option's own id, which answers name it by (selectedOptionIds). Give every option one, or none: without them the options are numbered "1", "2", \u2026 in order.`
31
+ ),
32
+ label: z.string().trim().min(1).max(1e3),
33
+ hint: OptionSchema.shape.hint,
34
+ htmlPreview: OptionSchema.shape.html,
35
+ imgUrl: OptionSchema.shape.image
26
36
  }).strict();
27
37
  var NIGHT = { from: 23, to: 7 };
28
38
  function draft2020(node) {
@@ -54,41 +64,162 @@ function mcpInputSchema(s) {
54
64
  delete schema.$schema;
55
65
  return draft2020(schema);
56
66
  }
57
- var AskInputSchema = z2.object({
58
- id: z2.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
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."),
60
- repo: z2.string().optional().describe("Optional repository context."),
61
- ask: z2.string().trim().min(1).max(1e4).describe(
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."
67
+ var ASK_MAX = 250;
68
+ var UNITS_MAX = 12;
69
+ var UNIT_TITLE_MAX = 50;
70
+ var UNIT_BODY_MAX = 200;
71
+ var UnitInputSchema = z2.object({
72
+ title: z2.string().trim().min(1).describe(`What this unit is about, at most ${UNIT_TITLE_MAX} characters. The app lists the titles; the person taps one to read its body.`),
73
+ body: z2.string().trim().min(1).describe(`The unit itself, at most ${UNIT_BODY_MAX} characters.`)
74
+ }).strict();
75
+ function capRefusals(text, units2, field) {
76
+ const out = [];
77
+ const name = field === "text" ? "answer" : field;
78
+ if (text.length > ASK_MAX) out.push({
79
+ refusal: "ask_too_long",
80
+ length: text.length,
81
+ cap: ASK_MAX,
82
+ path: field === "text" ? ["answer", "text"] : [field],
83
+ message: `ask_too_long: the ${name} is ${text.length} characters; the cap is ${ASK_MAX}. Keep the one point in the ${name} and move the context into units.`
84
+ });
85
+ const all = units2 ?? [];
86
+ if (all.length > UNITS_MAX) out.push({
87
+ refusal: "too_many_units",
88
+ length: all.length,
89
+ cap: UNITS_MAX,
90
+ path: ["units"],
91
+ message: `too_many_units: ${all.length} units; the cap is ${UNITS_MAX}. Keep only the context this ${name} needs.`
92
+ });
93
+ all.forEach((unit, index) => {
94
+ if (unit.title.length > UNIT_TITLE_MAX) out.push({
95
+ refusal: "unit_title_too_long",
96
+ index,
97
+ length: unit.title.length,
98
+ cap: UNIT_TITLE_MAX,
99
+ path: ["units", index, "title"],
100
+ message: `unit_title_too_long: units[${index}]'s title is ${unit.title.length} characters; the cap is ${UNIT_TITLE_MAX}.`
101
+ });
102
+ if (unit.body.length > UNIT_BODY_MAX) out.push({
103
+ refusal: "unit_body_too_long",
104
+ index,
105
+ length: unit.body.length,
106
+ cap: UNIT_BODY_MAX,
107
+ path: ["units", index, "body"],
108
+ message: `unit_body_too_long: units[${index}]'s body is ${unit.body.length} characters; the cap is ${UNIT_BODY_MAX}. Cut it into two units.`
109
+ });
110
+ });
111
+ return out;
112
+ }
113
+ function refuseOverCaps(text, units2, field, ctx) {
114
+ for (const { path, message, ...params } of capRefusals(text, units2, field)) ctx.addIssue({ code: z2.ZodIssueCode.custom, path, message, params });
115
+ }
116
+ var questionHandle = z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
117
+ var NodeRef = z2.object({
118
+ type: z2.enum(["goal", "question"]),
119
+ id: z2.string().uuid()
120
+ }).strict().describe("A Goal or a Question, by its id.");
121
+ var BlockedAction = z2.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
122
+ var reason = z2.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
123
+ var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
124
+ var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
125
+ var userExplicitlyRequested = z2.enum(["any", "call"]).optional().describe(
126
+ `Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
127
+ );
128
+ var units = z2.array(UnitInputSchema).optional().describe(
129
+ `The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit. Leave it out when the main text says it all.`
130
+ );
131
+ var UpdateInputSchema = z2.object({
132
+ message: z2.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
133
+ goalIds: z2.array(z2.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
134
+ units,
135
+ userExplicitlyRequested
136
+ }).strict().superRefine((u, ctx) => refuseOverCaps(u.message, u.units, "message", ctx));
137
+ var questionFields = {
138
+ question: z2.string().trim().min(1).describe(
139
+ `ONE question, in at most ${ASK_MAX} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
63
140
  ),
64
- options: z2.array(OptionInputSchema).min(1).max(6).optional(),
141
+ goalId: z2.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
142
+ units,
143
+ options: z2.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own."),
65
144
  // 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.
69
- select: z2.enum(["one", "many"]).optional().describe(
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.'
145
+ // brain_prompts.md §1: structured agent questions need no model). The app draws all three.
146
+ pickMode: z2.enum(["one", "many", "rank"]).optional().describe(
147
+ 'How the options are answered: "one" (the default) a pick, for alternatives; "many" a checklist, for independent options they may want several of; "rank" an order of all of them. Only with options.'
71
148
  ),
72
- answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
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."
74
- )
75
- }).strict();
76
- var StartContactSchema = z2.object({
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."),
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."),
79
- channel: z2.enum(["notification", "call"]).default("notification")
80
- }).strict();
81
- var ReceiveContactSchema = z2.object({
82
- wait: z2.boolean().optional().describe(
83
- "Only controls when this returns. true (the default when you send and acknowledge nothing): wait up to about 45 seconds for incoming communication addressed to you, and return as soon as some is available. false: return what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived in the window; wait again without sending again), or `not_waited` (a hosted connection never holds a wait)."
149
+ // WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
150
+ // blocks work asks for a call (the person's settings and ring cooldowns still decide).
151
+ workItBlocks: z2.array(z2.object({ blocked: NodeRef, action: BlockedAction, reason: reason.optional() }).strict().refine(actionFits, ACTION_FITS)).min(1).max(10).optional().describe(
152
+ "The work that cannot go on until this is answered: a Goal's start or completion, or another Question's answer. Only for work truly blocked; it asks for a call. Leave it out for a question you can keep working around."
84
153
  ),
154
+ userExplicitlyRequested
155
+ };
156
+ var refineQuestion = (q, ctx) => {
157
+ refuseOverCaps(q.question, q.units, "question", ctx);
158
+ if (q.pickMode && !q.options) ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
159
+ const ids = (q.options ?? []).map((o) => o.id);
160
+ if (ids.some((id2) => id2) && (ids.some((id2) => !id2) || new Set(ids).size !== ids.length)) {
161
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
162
+ }
163
+ };
164
+ var QuestionInputSchema = z2.object({
165
+ questionId: z2.string().uuid().optional().describe("Optional: your own stable id for this question (a UUID), so a resend is the same question. Minted for you when omitted; the result returns it."),
166
+ ...questionFields
167
+ }).strict().superRefine(refineQuestion);
168
+ var QuestionWireSchema = z2.object({ questionId: z2.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
169
+ var AnswerInputSchema = z2.object({
170
+ questionId: questionHandle.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
171
+ answer: z2.object({
172
+ text: z2.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
173
+ selectedOptionIds: z2.array(z2.string().trim().min(1)).min(1).max(6).optional().describe("The ids of the options you choose, when the Question offered options (in order, for a ranking).")
174
+ }).strict(),
175
+ units
176
+ }).strict().superRefine((a, ctx) => refuseOverCaps(a.answer.text, a.units, "text", ctx));
177
+ var QuestionDependencySchema = z2.object({
178
+ questionId: questionHandle.describe("A Question you asked."),
179
+ change: z2.enum(["add", "remove"]),
180
+ blocked: NodeRef,
181
+ action: BlockedAction,
182
+ reason: reason.optional()
183
+ }).strict().refine(actionFits, ACTION_FITS);
184
+ var sendFields = {
185
+ updates: z2.array(UpdateInputSchema).min(1).max(20).optional().describe(
186
+ "News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
187
+ ),
188
+ answers: z2.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
189
+ withdrawQuestionIds: z2.array(questionHandle).min(1).max(10).optional().describe(
190
+ "Questions you asked that no longer need an answer: you acted on them yourself, they no longer matter, or you asked wrongly. Each is cancelled, not answered: its card closes and nothing rings for it."
191
+ ),
192
+ questionDependencies: z2.array(QuestionDependencySchema).min(1).max(20).optional().describe(
193
+ "Add or remove what a Question you asked holds up, after asking it."
194
+ )
195
+ };
196
+ var ContactSchema = z2.object({
197
+ ...sendFields,
198
+ questions: z2.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
199
+ /** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
85
200
  ackEventIds: z2.array(z2.string().uuid()).min(1).max(100).optional().describe(
86
- "The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (an ask with `answers`), never acknowledged away: that id is refused until it is answered."
201
+ "The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (answers), never acknowledged away."
202
+ ),
203
+ wait: z2.boolean().optional().describe(
204
+ "Only controls when this returns. Sending: false (the default) returns at once; true waits up to about 45 seconds for a response to what you sent. Sending nothing: true (the default) waits up to about 45 seconds for anything addressed to you; false returns what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived; wait again without sending again), or `not_waited`."
87
205
  )
88
206
  }).strict();
89
- var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict(), ReceiveContactSchema]);
207
+ var sendsAnything = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
208
+ var ContactWireSchema = z2.object({
209
+ operationId: z2.string().uuid(),
210
+ ...sendFields,
211
+ questions: z2.array(QuestionWireSchema).min(1).max(20).optional()
212
+ }).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
90
213
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
91
- 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 answers by receiving (below). 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, never a claim on them, so no answer is owed and none should be awaited. A report reaches the person only when it answers something they asked you, or once the Goal is done; any other report is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first, then send one message saying what is done and anything they need to do or check. On a Goal whose report card is still open, a report that does reach them is added to that card, with no new push. 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, a contact that reaches them 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.\n\nRECEIVING: contact with no asks sends nothing and returns `events`, a limited batch of what is addressed to you (not a history page): `question`, a Question you owe (answer it with an ask whose `answers` is its questionId); `update`, something new on one of your Goals (a reply, an answer: read it with get_goal); `instruction`, a request or note sent to you. `hasMore` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (`assigned`; claim_goal starts it) and your work gone quiet (`stalled`).";
214
+ var CONTACT_DESCRIPTION = `Send to and receive from the person: updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read \`results\`, where a failed item names why while the rest still apply.
215
+
216
+ QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. 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'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
217
+
218
+ CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
219
+
220
+ UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
221
+
222
+ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
92
223
  var CreateGoalSchema = z3.object({
93
224
  outcome: z3.string().trim().min(1).max(1e4),
94
225
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
@@ -122,21 +253,11 @@ var UpdateGoalSchema = z3.object({
122
253
  state: z3.enum(["active", "done", "cancelled"]).optional(),
123
254
  progress: z3.string().trim().min(1).max(1e4).optional(),
124
255
  reviewed: z3.literal(true).optional(),
125
- dueAt: z3.string().datetime({ offset: true }).nullable().optional(),
126
- /** WITHDRAW YOUR OWN QUESTION (#2777, owner 2026-09-30). The question's id as every read shows it
127
- * (the conversation's `id`, eight characters, or the whole request Entry id), on THIS Goal, asked
128
- * by you and still open. It is cancelled, not answered: its cards close and nothing rings for it. */
129
- withdraw: z3.array(z3.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/)).min(1).max(10).optional()
256
+ dueAt: z3.string().datetime({ offset: true }).nullable().optional()
130
257
  }).strict().refine((v) => Object.keys(v).length > 0),
131
258
  reason: z3.string().trim().min(1).max(2e3),
132
259
  operationId: z3.string().uuid().optional()
133
260
  }).strict();
134
- var UpdateGoalToolSchema = z3.object({
135
- goalId: z3.string().uuid(),
136
- revision: UpdateGoalSchema.shape.revision,
137
- changes: UpdateGoalSchema.shape.changes.innerType().pick({ progress: true, reviewed: true, dueAt: true, withdraw: true }).strict().refine((v) => Object.keys(v).length > 0),
138
- reason: UpdateGoalSchema.shape.reason
139
- }).strict();
140
261
  var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
141
262
  var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
142
263
  var goalOutcome = z3.string().trim().min(1).max(1e4).describe("The full desired result. There is no third description field.");
@@ -144,7 +265,7 @@ var CreateChange = z3.object({
144
265
  kind: z3.literal("create"),
145
266
  title: goalTitle,
146
267
  outcome: goalOutcome,
147
- ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as who_is_working shows it) or the person's. Omitted: you."),
268
+ ownerId: z3.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
148
269
  parentGoalId: z3.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
149
270
  sourceEntryIds: z3.array(z3.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
150
271
  }).strict();
@@ -154,7 +275,7 @@ var StateChange = z3.object({ kind: z3.literal("state"), goalId: changedGoal, st
154
275
  ) }).strict();
155
276
  var AssignChange = z3.object({ kind: z3.literal("assign"), goalId: changedGoal, ownerId: z3.string().trim().min(1).describe("The new owner's participant.") }).strict();
156
277
  var DeferChange = z3.object({ kind: z3.literal("defer"), goalId: changedGoal, until: z3.string().datetime({ offset: true }).nullable().describe(
157
- "Postpone it until this instant (its owner is woken then), or null to take the postponement back."
278
+ "Postpone it until this instant: it waits until then, and its owner is woken when it passes (a promise to follow up later). null takes the postponement back."
158
279
  ) }).strict();
159
280
  var MoveChange = z3.object({ kind: z3.literal("move"), goalId: changedGoal, parentGoalId: z3.string().uuid().nullable().describe(
160
281
  "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
@@ -189,8 +310,7 @@ var ManageGoalsSchema = z3.object({
189
310
  var ManageGoalsToolSchema = z3.object({
190
311
  changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
191
312
  }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
192
- var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_joined, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until, or null); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change a Goal you own or have written on. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
193
- var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
313
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
194
314
  var GetGoalSchema = z3.object({
195
315
  goalId: z3.string().uuid(),
196
316
  /** Every entry in full. Without it the read carries the person's words, open questions and your
@@ -201,9 +321,7 @@ var GetGoalSchema = z3.object({
201
321
  * between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
202
322
  diagnose: z3.boolean().optional()
203
323
  }).strict();
204
- var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it, including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
205
- var UPDATE_GOAL_DESCRIPTION = "Report work on a Goal you are on, at an exact revision: progress, review acknowledgement, when to wake for it, and withdrawing a question of yours. What the Goal is (its title, outcome, state, owner, parent, dependencies) changes with manage_goals. You are on a Goal you own, or one you have written on (an update with progress, or a contact on it); any of your person's agents may write on any of their Goals, and one that never touched a Goal is refused (409 goal_not_joined). Stale revisions are rejected. A CONTACT ON THIS GOAL MOVES ITS REVISION: a question filed on a Goal is a change to it, so an update prepared before a contact and sent after it is refused as stale (409 goal_revision_conflict) \u2014 re-read the Goal, then write. progress says where the work stands. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision (contact({ackEventIds}) does the same for an `update` event). dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. withdraw: [questionId] takes back a question YOU asked on this Goal that is still open \u2014 because you acted on it yourself, it no longer matters, or you asked it wrongly (e.g. waiting: hard when nothing was blocked): it is cancelled, not answered, its card closes and it stops ringing; the id is the one the conversation shows. Returns the Goal as get_goal reads it, at its new revision.";
206
- var CLAIM_GOAL_DESCRIPTION = "Claim a pending answer to your question or the oldest runnable or review-pending Goal you own. Pass goalId to join any Goal of your person; its assignment stays unchanged. Read others before overlapping another agent\u2019s work. Joining lets you contribute and change it; use manage_goals (assign) when the assignment itself should change. Returns the Goal as get_goal reads it, and marks you as on it, which never shuts another agent out: other agents of your person may write on it and change it too, and two changes at once are told apart by revision (409 goal_revision_conflict).";
324
+ var GET_GOAL_DESCRIPTION = "Read one Goal (a read never puts you on it), including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
207
325
  var SearchToolSchema = z3.object({
208
326
  query: z3.string().trim().min(1).max(500),
209
327
  types: z3.array(z3.enum(["entry", "goal", "answer"])).min(1).optional(),
@@ -211,29 +329,35 @@ var SearchToolSchema = z3.object({
211
329
  limit: z3.number().int().min(1).max(20).optional()
212
330
  }).strict();
213
331
  var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and Answers (found by their Question or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, its Questions' Answers, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (an Answer carries its Question, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
214
- var WhoIsWorkingSchema = z3.object({}).strict();
332
+ var CheckActivitySchema = z3.object({}).strict();
333
+ var FEEDBACK_TEXT_MAX = 5e4;
334
+ var SendFeedbackSchema = z3.object({
335
+ text: z3.string().trim().min(1).max(FEEDBACK_TEXT_MAX).describe(`The report, in markdown: what happened, what was expected, how to reproduce it, versions, evidence. At most ${FEEDBACK_TEXT_MAX} characters.`),
336
+ title: z3.string().trim().min(1).max(200).optional().describe("One line naming it."),
337
+ kind: z3.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
338
+ }).strict();
339
+ var SEND_FEEDBACK_DESCRIPTION = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
215
340
  var AGENT_TOOLS = [
216
- { name: "who_is_working", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(WhoIsWorkingSchema) },
217
341
  { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
218
342
  { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
219
- { name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
220
343
  { name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
221
- { name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) },
222
- { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) }
344
+ { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
345
+ { name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
346
+ { name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) }
223
347
  ];
224
348
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
225
349
  function serverInstructions(opts) {
226
- const calls = opts.waits ? "A Call holds one bounded window here; continue with contact({deliveryId}) to hold the next or reread that exact Delivery." : "A Call returns after one read; continue with contact({deliveryId}) to reread that exact Delivery.";
227
- return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), and claim_goal for your runnable or review-pending Goal and the conversation on it. get_goal rereads it without claiming. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goal, write on it (contact with its id as parentId, or update_goal progress), and once you have written on it you may change it too (manage_goals). Create, edit, assign, organize or close work with manage_goals, never as a contact. Every read ends in \`next\`, the one step to take. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. ${calls} Events repeat until you acknowledge them: reading alone acknowledges nothing. Report progress with update_goal, never as a contact. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
350
+ const waits = opts.waits ? "contact({..., wait:true}) holds one bounded ~45 s window for a response to what you sent; contact({}) holds one for anything addressed to you." : "A contact here returns after one read (`waitOutcome: not_waited`); answers wake you, and contact({wait:false}) collects them.";
351
+ return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), which hand you your work: answers, assignments, reviews and due Goals. get_goal reads a Goal and the conversation on it; a read never puts you on it. You are on a Goal from your first write to it (a contact update, question or answer naming it, or a manage_goals change); there is nothing to claim or join. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goal, write on it (a contact naming its id) and change it (manage_goals). Create, edit, assign, organize or close work with manage_goals, never as a contact; a contact only names Goals that already exist. Every read ends in \`next\`, the one step to take. Sending returns immediately unless you ask it to wait: keep working and collect answers by receiving or through get_goal. ${waits} Events repeat until you acknowledge them: reading alone acknowledges nothing; an \`update\` event acknowledged is a review cleared. Report progress as a contact update: one the person does not need yet is kept as the Goal's progress and reaches no one. To follow up later, defer the Goal with manage_goals: you are woken when its time comes. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
228
352
 
229
353
  HOW TO ASK:
230
- 1. One question per ask. Five questions are five objects in \`asks\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
231
- 2. Name the Goal you are working on as the ask's \`parentId\`: the question goes onto that Goal and its answer comes back there. Without one, Paigy places it.
232
- 3. \`waiting: hard\` only for a decision you are blocked on; \`waiting: none\` for a question you can keep working around.
233
- 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 \`contact({wait:false})\` or \`claim_goal\` on that wake.
234
- 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.
354
+ 1. One question per question object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
355
+ 2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
356
+ 3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
357
+ 4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. STOP WAITING when a window comes back with nothing new \u2014 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 \`contact({wait:false})\` or \`get_goal\` on that wake.
358
+ 5. One question, 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. Take back one that no longer matters with \`withdrawQuestionIds\`.
235
359
  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.
236
- 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.
360
+ 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\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Your question is sent exactly as you wrote it. They can always answer in their own words, so no "Other" option.
237
361
  8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.`;
238
362
  }
239
363
  var id = z4.string().uuid();
@@ -372,6 +496,66 @@ var CompactResultSchema = z4.object({
372
496
  var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
373
497
  zodToJsonSchema2(CompactResultSchema, { $refStrategy: "none" })
374
498
  );
499
+ var str2 = z5.string();
500
+ var strs2 = z5.array(z5.string());
501
+ var TalkerReplySchema = z5.object({
502
+ /** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
503
+ * or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
504
+ * answered before ("" for none). */
505
+ messages: z5.array(z5.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
506
+ /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
507
+ then: z5.enum(["listen", "hold", "end"]),
508
+ /** Items the person's words settle: the item's handle, the chosen option IDs, the line handles. */
509
+ answers: z5.array(z5.object({ item: str2, options: strs2, lines: strs2 }).strict()),
510
+ /** The line handles that hold a new instruction from the person. */
511
+ instruction: strs2,
512
+ /** The line handles that ask for new work, a change to existing work, or a standing rule: the filer
513
+ * reads only these (3e). */
514
+ file: strs2,
515
+ /** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
516
+ need: z5.object({ kind: z5.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
517
+ /** The handle of the item to explain ("" for none). */
518
+ explain: str2
519
+ }).strict();
520
+ var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
521
+ zodToJsonSchema3(TalkerReplySchema, { $refStrategy: "none" })
522
+ );
523
+ var str3 = z6.string();
524
+ var strs3 = z6.array(z6.string());
525
+ var FilerReplySchema = z6.object({
526
+ /** Which Goals each new line goes on: a line's handle and Goal handles (or keys of new Goals). */
527
+ filed: z6.array(z6.object({ line: str3, goals: strs3 }).strict()),
528
+ goals: z6.array(z6.object({
529
+ op: z6.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
530
+ key: str3,
531
+ goal: str3,
532
+ title: str3,
533
+ outcome: str3,
534
+ owner: str3,
535
+ under: str3,
536
+ until: str3,
537
+ on: str3,
538
+ gate: z6.enum(["start", "complete", "answer", ""]),
539
+ why: str3
540
+ }).strict()),
541
+ questions: z6.array(z6.object({
542
+ op: z6.enum(["create", "edit", "assign", "withdraw"]),
543
+ key: str3,
544
+ question: str3,
545
+ text: str3,
546
+ to: str3,
547
+ goal: str3,
548
+ options: z6.array(z6.object({ id: str3, label: str3 }).strict()),
549
+ pick: z6.enum(["one", "many", "rank", "words", ""]),
550
+ blocks: str3
551
+ }).strict()),
552
+ lessons: z6.array(z6.object({ op: z6.enum(["remember", "revise", "withdraw"]), lesson: str3, text: str3, scope: str3 }).strict()),
553
+ /** Line handles whose words comment on Paigy itself. */
554
+ feedback: strs3
555
+ }).strict();
556
+ var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
557
+ zodToJsonSchema4(FilerReplySchema, { $refStrategy: "none" })
558
+ );
375
559
  function entryWords(entry) {
376
560
  const content = entry.content;
377
561
  if (content && "sealed" in content) return "";
@@ -389,19 +573,29 @@ function entryWords(entry) {
389
573
  }
390
574
  return entry.sources.map((source) => source.text).join("\n");
391
575
  }
576
+ function entryUnit(entry) {
577
+ if (entry.kind !== "contribution" || !entry.aboutId) return null;
578
+ const plain = entry.content && "plain" in entry.content ? entry.content.plain : null;
579
+ if (!plain || typeof plain !== "object") return null;
580
+ const { title, text } = plain;
581
+ return typeof title === "string" && typeof text === "string" ? { title, body: text } : null;
582
+ }
392
583
  var LIVE_MS = 3 * 6e4;
393
584
  var WORKING_MS = 60 * 6e4;
394
- var ContextSchema = z5.object({
395
- title: z5.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
396
- description: z5.array(z5.string().min(1)).describe(
585
+ var ContextSchema = z7.object({
586
+ title: z7.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
587
+ description: z7.array(z7.string().min(1)).describe(
397
588
  "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."
398
- )
589
+ ),
590
+ /** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
591
+ * a body on a tap. Absent on an ask sent without units, which renders as before. */
592
+ units: z7.array(z7.object({ title: z7.string(), body: z7.string() })).optional()
399
593
  });
400
- var ParticipantSchema = z5.object({
401
- kind: z5.enum(["human", "agent"]),
402
- id: z5.string()
594
+ var ParticipantSchema = z7.object({
595
+ kind: z7.enum(["human", "agent"]),
596
+ id: z7.string()
403
597
  });
404
- var TransformSchema = z5.enum([
598
+ var TransformSchema = z7.enum([
405
599
  "structure",
406
600
  // shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
407
601
  "request_more",
@@ -417,13 +611,13 @@ var TransformSchema = z5.enum([
417
611
  "summarize"
418
612
  // reduce volume, keep decision value — 30-turn cap, spoken briefing
419
613
  ]);
420
- var VisualSchema = z5.object({
421
- url: z5.string().url(),
422
- label: z5.string().optional()
614
+ var VisualSchema = z7.object({
615
+ url: z7.string().url(),
616
+ label: z7.string().optional()
423
617
  });
424
- var NotifyLevelSchema = z5.enum(["inbox", "push", "banner", "call"]);
425
- var SelectShapeSchema = z5.enum(["one", "many", "rank", "confirm", "text"]);
426
- var ReceiptEventSchema = z5.enum([
618
+ var NotifyLevelSchema = z7.enum(["inbox", "push", "banner", "call"]);
619
+ var SelectShapeSchema = z7.enum(["one", "many", "rank", "confirm", "text"]);
620
+ var ReceiptEventSchema = z7.enum([
427
621
  "delivered",
428
622
  // the bundle reached the recipient at some level
429
623
  "seen",
@@ -453,47 +647,47 @@ var ReceiptEventSchema = z5.enum([
453
647
  // be rewound by a writer that forgot to advance it.
454
648
  "restarted"
455
649
  ]);
456
- var AttentionSchema = z5.object({
650
+ var AttentionSchema = z7.object({
457
651
  urgency: NotifyLevelSchema,
458
652
  /** The required answer shape, or null for a plain notify that asks nothing back. */
459
653
  select: SelectShapeSchema.nullable(),
460
654
  /** Coverage contract (#396) — points the answer must address; null = none declared. */
461
- points: z5.array(z5.string()).nullable(),
655
+ points: z7.array(z7.string()).nullable(),
462
656
  /** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
463
- blocking: z5.boolean(),
657
+ blocking: z7.boolean(),
464
658
  /** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
465
659
  * slice wires it; optional so today's rows/callers project cleanly. */
466
- deadline: z5.string().datetime().nullable().optional()
660
+ deadline: z7.string().datetime().nullable().optional()
467
661
  });
468
- var NotifyRequestFields = z5.object({
662
+ var NotifyRequestFields = z7.object({
469
663
  /** Plaintext message content. Present on the plaintext path (today's shape);
470
664
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
471
665
  * superRefine at the bottom enforces exactly one of the two. */
472
666
  context: ContextSchema.optional(),
473
- options: z5.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
667
+ options: z7.array(OptionSchema.omit({ id: true }).extend({ label: z7.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
474
668
  "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)."
475
669
  ),
476
- points: z5.array(z5.string().min(1)).optional().describe(
670
+ points: z7.array(z7.string().min(1)).optional().describe(
477
671
  "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."
478
672
  ),
479
- visuals: z5.array(VisualSchema).optional().describe(
673
+ visuals: z7.array(VisualSchema).optional().describe(
480
674
  "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."
481
675
  ),
482
676
  /** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
483
- repo: z5.string().optional(),
677
+ repo: z7.string().optional(),
484
678
  /** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
485
- branch: z5.string().optional(),
679
+ branch: z7.string().optional(),
486
680
  /** Continue an existing conversation — the id of any notification in it (its root
487
681
  * is the conversation's identity). Omitted = start a new conversation. Renamed
488
682
  * from `parentId` (2026-08-03): one linkage system, the parent; the API edge
489
683
  * still accepts the old name from older clients. */
490
- parentId: z5.string().uuid().optional(),
684
+ parentId: z7.string().uuid().optional(),
491
685
  /** The durable outcome this contact advances. Optional during the notification-to-Work
492
686
  * migration; when present, a blocking ask creates a DecisionNeed for this Work. */
493
- workId: z5.string().uuid().optional(),
687
+ workId: z7.string().uuid().optional(),
494
688
  /** Target Goal scope. During staged migration this is accepted by the shared contract but
495
689
  * target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
496
- goalId: z5.string().uuid().optional(),
690
+ goalId: z7.string().uuid().optional(),
497
691
  urgency: NotifyLevelSchema.default("inbox").describe(
498
692
  "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."
499
693
  ),
@@ -501,7 +695,7 @@ var NotifyRequestFields = z5.object({
501
695
  * visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
502
696
  * when `parentId` became the conversation handle: `parentId` says WHERE, this
503
697
  * says HOW. */
504
- clarifies: z5.string().optional(),
698
+ clarifies: z7.string().optional(),
505
699
  select: SelectShapeSchema.optional().describe(
506
700
  "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."
507
701
  ),
@@ -515,20 +709,20 @@ var NotifyRequestFields = z5.object({
515
709
  // (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
516
710
  // Owner, 2026-07-28: "our actual limitation on how long something is to the user should
517
711
  // come from the broker splitting and summarizing." The cap that remains is a size guard.
518
- ask: z5.string().min(1).max(1e4).optional().describe(
712
+ ask: z7.string().min(1).max(1e4).optional().describe(
519
713
  '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.'
520
714
  ),
521
- needs: z5.array(z5.string().min(1)).optional().describe(
715
+ needs: z7.array(z7.string().min(1)).optional().describe(
522
716
  "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."
523
717
  ),
524
- urgencyHint: z5.enum(["whenever", "soon", "now"]).optional().describe(
718
+ urgencyHint: z7.enum(["whenever", "soon", "now"]).optional().describe(
525
719
  "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."
526
720
  ),
527
721
  /** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
528
722
  * to the agent's work while it waits. Normalized server-side into those two
529
723
  * fields (normalizeWaiting) so everything downstream is untouched; explicit
530
724
  * urgencyHint/blocking win when both are sent. */
531
- waiting: z5.enum(["none", "soft", "hard"]).optional().describe(
725
+ waiting: z7.enum(["none", "soft", "hard"]).optional().describe(
532
726
  "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."
533
727
  ),
534
728
  /** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
@@ -536,98 +730,98 @@ var NotifyRequestFields = z5.object({
536
730
  * holding by default would charge every quiet claim that minute before any agent could
537
731
  * correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
538
732
  * and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
539
- confirm: z5.boolean().optional().describe(
733
+ confirm: z7.boolean().optional().describe(
540
734
  "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'."
541
735
  ),
542
736
  /** #575: a RELAY of the user's explicitly stated preference, never the agent's
543
737
  * choice. Outranks waiting in both directions: 'call' rings even for a
544
738
  * waiting:'none' "call me when it's done"; 'message' never rings even for
545
739
  * waiting:'hard'. */
546
- channel: z5.enum(["call", "message"]).optional().describe(
740
+ channel: z7.enum(["call", "message"]).optional().describe(
547
741
  "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."
548
742
  ),
549
- confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
743
+ confirmStyle: z7.enum(["yesno", "approve"]).default("yesno").describe(
550
744
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
551
745
  ),
552
- blocking: z5.boolean().default(false).describe(
746
+ blocking: z7.boolean().default(false).describe(
553
747
  "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."
554
748
  )
555
749
  });
556
750
  var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
557
- if (r.workId && r.goalId) ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
751
+ if (r.workId && r.goalId) ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
558
752
  if (r.ask !== void 0) {
559
753
  for (const f of ["context", "select", "points"]) {
560
754
  if (r[f] !== void 0)
561
- 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.` });
755
+ ctx.addIssue({ code: z7.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.` });
562
756
  }
563
757
  return;
564
758
  }
565
759
  if (r.needs !== void 0 || r.urgencyHint !== void 0)
566
- 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" });
760
+ ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
567
761
  if (!r.context)
568
- ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
762
+ ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
569
763
  if (!r.select)
570
- ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
764
+ ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
571
765
  const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
572
766
  if (needsOptions && !r.options?.length)
573
- ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
767
+ ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
574
768
  if (!needsOptions && r.options?.length)
575
- ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
769
+ ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
576
770
  });
577
- var NotifyStatusSchema = z5.enum(["pending", "answered", "ignored"]);
578
- var AgentStateSchema = z5.enum(["idle", "in_progress", "completed", "needs_input"]);
579
- var TurnSchema = z5.object({
580
- prompt: z5.string(),
581
- reply: z5.string()
771
+ var NotifyStatusSchema = z7.enum(["pending", "answered", "ignored"]);
772
+ var AgentStateSchema = z7.enum(["idle", "in_progress", "completed", "needs_input"]);
773
+ var TurnSchema = z7.object({
774
+ prompt: z7.string(),
775
+ reply: z7.string()
582
776
  });
583
- var UserAnswerSchema = z5.discriminatedUnion("kind", [
584
- z5.object({ kind: z5.literal("option"), optionId: z5.string(), label: z5.string().optional() }),
585
- z5.object({ kind: z5.literal("text"), text: z5.string() }),
586
- z5.object({ kind: z5.literal("ignored") }),
587
- z5.object({ kind: z5.literal("multi"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
588
- z5.object({ kind: z5.literal("ranked"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
589
- z5.object({ kind: z5.literal("clarify"), chunks: z5.array(z5.string()).min(1) }),
590
- z5.object({ kind: z5.literal("confirm"), approved: z5.boolean() }),
591
- z5.object({ kind: z5.literal("turns"), turns: z5.array(TurnSchema).min(1) }),
777
+ var UserAnswerSchema = z7.discriminatedUnion("kind", [
778
+ z7.object({ kind: z7.literal("option"), optionId: z7.string(), label: z7.string().optional() }),
779
+ z7.object({ kind: z7.literal("text"), text: z7.string() }),
780
+ z7.object({ kind: z7.literal("ignored") }),
781
+ z7.object({ kind: z7.literal("multi"), optionIds: z7.array(z7.string()), labels: z7.array(z7.string()).optional() }),
782
+ z7.object({ kind: z7.literal("ranked"), optionIds: z7.array(z7.string()), labels: z7.array(z7.string()).optional() }),
783
+ z7.object({ kind: z7.literal("clarify"), chunks: z7.array(z7.string()).min(1) }),
784
+ z7.object({ kind: z7.literal("confirm"), approved: z7.boolean() }),
785
+ z7.object({ kind: z7.literal("turns"), turns: z7.array(TurnSchema).min(1) }),
592
786
  /** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
593
787
  * delivered through the same settle/await path as a human answer, carrying the judge's
594
788
  * derivation and the precedent ids it grew from. Always paired with a visible trail
595
789
  * card the user can reply to — the broker never overrides the user. */
596
- z5.object({ kind: z5.literal("precedent"), answer: z5.string(), derivation: z5.string(), sources: z5.array(z5.string()).min(1) })
790
+ z7.object({ kind: z7.literal("precedent"), answer: z7.string(), derivation: z7.string(), sources: z7.array(z7.string()).min(1) })
597
791
  ]);
598
- var IntentSchema = z5.object({
792
+ var IntentSchema = z7.object({
599
793
  // The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
600
794
  // it by two ("detail", "feedback"), and because the settle handler parsed the array
601
795
  // all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
602
796
  // questions included. Found auditing five calls' stored feedback, 2026-08-01.
603
- kind: z5.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
604
- detail: z5.string(),
797
+ kind: z7.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
798
+ detail: z7.string(),
605
799
  /** Defer only: seconds until the callback the caller asked for, when something upstream
606
800
  * already read the time. Nothing sets it today (#397 documented an MCP parser that was
607
801
  * never written) — the API reads the defer's `detail` itself with `notes/when.ts`
608
802
  * (`parseDelay`, #1292), and a value here simply wins over that reading. */
609
- dueInSeconds: z5.number().int().positive().optional(),
803
+ dueInSeconds: z7.number().int().positive().optional(),
610
804
  /** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
611
805
  * already read the utterance, so `signals.kind` stops defaulting to
612
806
  * 'other' on every row. A table that records that something was wrong and nothing
613
807
  * about what cannot answer "is the bot looping less this week?". */
614
- fault: z5.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
808
+ fault: z7.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
615
809
  });
616
- var RideAlongSchema = z5.object({
810
+ var RideAlongSchema = z7.object({
617
811
  /** The note this came from — assign/clarify/close it through /api/notes/:id. */
618
- noteId: z5.string(),
812
+ noteId: z7.string(),
619
813
  /** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
620
- text: z5.string(),
814
+ text: z7.string(),
621
815
  /** The thread to report back on, when the note was dispatched over the request rail. */
622
- parentId: z5.string().nullable()
816
+ parentId: z7.string().nullable()
623
817
  });
624
- var AwaitItemSchema = z5.discriminatedUnion("type", [
625
- z5.object({
626
- type: z5.literal("reply"),
627
- parentId: z5.string(),
628
- notificationId: z5.string(),
629
- workId: z5.string().uuid().optional(),
630
- decisionId: z5.string().uuid().optional(),
818
+ var AwaitItemSchema = z7.discriminatedUnion("type", [
819
+ z7.object({
820
+ type: z7.literal("reply"),
821
+ parentId: z7.string(),
822
+ notificationId: z7.string(),
823
+ workId: z7.string().uuid().optional(),
824
+ decisionId: z7.string().uuid().optional(),
631
825
  answer: UserAnswerSchema,
632
826
  /** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
633
827
  * #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
@@ -637,103 +831,103 @@ var AwaitItemSchema = z5.discriminatedUnion("type", [
637
831
  * "call me back after you merge" in their own words decides for itself what to do,
638
832
  * and now knows exactly which call to make. Absent when either half is missing —
639
833
  * a sentence with a hole in it is worse than no sentence. */
640
- note: z5.string().optional(),
834
+ note: z7.string().optional(),
641
835
  /** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
642
836
  * shaped answer was mapped from, filtered to its own claims. There is no second list
643
837
  * of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
644
838
  * 2026-09-04): the agent reads the sentence and decides. */
645
- transcript: z5.string().optional(),
839
+ transcript: z7.string().optional(),
646
840
  /** Coverage report (#396), when the ask declared `points`: which of them this
647
841
  * answer addressed. Missing points = re-ask or proceed knowingly partial. */
648
- covered: z5.array(z5.string()).optional(),
842
+ covered: z7.array(z7.string()).optional(),
649
843
  /** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
650
844
  * became free. Only `reply` and `idle` carry it: those are the two outcomes that
651
845
  * END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
652
846
  * agent a side-quest while it is still holding the line is how the main thing gets
653
847
  * dropped. Absent/empty = nothing owed. */
654
- also: z5.array(RideAlongSchema).optional()
848
+ also: z7.array(RideAlongSchema).optional()
655
849
  }),
656
- z5.object({
657
- type: z5.literal("remind"),
658
- parentId: z5.string(),
659
- notificationId: z5.string(),
660
- remindAt: z5.string().datetime({ offset: true }),
850
+ z7.object({
851
+ type: z7.literal("remind"),
852
+ parentId: z7.string(),
853
+ notificationId: z7.string(),
854
+ remindAt: z7.string().datetime({ offset: true }),
661
855
  /** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
662
- remindInSeconds: z5.number()
856
+ remindInSeconds: z7.number()
663
857
  }),
664
858
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
665
859
  * post-feedback revision, #633) — the user will never answer this id. Stop
666
860
  * awaiting it; the live ask is the thread's newest turn (await that one, or
667
861
  * re-orient via contact({})). */
668
- z5.object({
669
- type: z5.literal("superseded"),
670
- parentId: z5.string(),
671
- notificationId: z5.string()
862
+ z7.object({
863
+ type: z7.literal("superseded"),
864
+ parentId: z7.string(),
865
+ notificationId: z7.string()
672
866
  }),
673
867
  /** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
674
868
  * revise any of these until the final reply arrives — partial = intelligence,
675
869
  * settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
676
870
  * irreversibly. If `acts` carries a question aimed at you and you know the answer,
677
871
  * contact on the same thread right away — the caller hears it on the same call. */
678
- z5.object({
679
- type: z5.literal("partial"),
680
- notificationId: z5.string(),
681
- inFlight: z5.literal(true),
682
- turn: z5.object({
683
- idx: z5.number(),
684
- prompt: z5.string(),
685
- reply: z5.string(),
686
- acts: z5.array(IntentSchema).nullable().optional()
872
+ z7.object({
873
+ type: z7.literal("partial"),
874
+ notificationId: z7.string(),
875
+ inFlight: z7.literal(true),
876
+ turn: z7.object({
877
+ idx: z7.number(),
878
+ prompt: z7.string(),
879
+ reply: z7.string(),
880
+ acts: z7.array(IntentSchema).nullable().optional()
687
881
  })
688
882
  }),
689
- z5.object({
690
- type: z5.literal("idle"),
691
- also: z5.array(RideAlongSchema).optional(),
883
+ z7.object({
884
+ type: z7.literal("idle"),
885
+ also: z7.array(RideAlongSchema).optional(),
692
886
  /** Is a call live for this agent's user right now? The SDK polls the partial stream
693
887
  * (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
694
888
  * during a live call, and polling for one on a banner/message was a wasted HTTP call +
695
889
  * 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
696
890
  * Absent = an older API → the SDK keeps polling, exactly as before. */
697
- inFlight: z5.boolean().optional()
891
+ inFlight: z7.boolean().optional()
698
892
  })
699
893
  ]);
700
- var VoiceKeySchema = z5.enum(["rachel", "george", "jessica", "brian", "lily"]);
701
- var AgendaTurnSchema = z5.object({
894
+ var VoiceKeySchema = z7.enum(["rachel", "george", "jessica", "brian", "lily"]);
895
+ var AgendaTurnSchema = z7.object({
702
896
  /** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
703
897
  * it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
704
898
  * sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
705
899
  * completion of a streamed re-plan carries the published sentence again, and the walk
706
900
  * drops what it already said by identity, never by the API's guess of what was polled.
707
901
  * Absent on plans nothing streams (a ring plan, a floor). */
708
- id: z5.string().optional(),
902
+ id: z7.string().optional(),
709
903
  /** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
710
904
  * the planner declares duplicates instead of asking them twice. */
711
- coveredIds: z5.array(z5.string()).optional(),
905
+ coveredIds: z7.array(z7.string()).optional(),
712
906
  /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
713
907
  * (owner, 2026-09-25), and a count here refused whole plans. */
714
- info: z5.array(z5.string().min(1)).default([]),
715
- question: z5.string().min(1).nullable(),
908
+ info: z7.array(z7.string().min(1)).default([]),
909
+ question: z7.string().min(1).nullable(),
716
910
  /** True on the one turn carrying the agent's own declared question. */
717
- asks: z5.boolean().optional(),
911
+ asks: z7.boolean().optional(),
718
912
  /** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
719
913
  * Absent on a single-claim plan (the session's own claim) and on shared context turns,
720
914
  * which route nothing. */
721
- claimId: z5.string().optional(),
915
+ claimId: z7.string().optional(),
722
916
  /** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
723
- voice: z5.string().optional(),
917
+ voice: z7.string().optional(),
724
918
  /** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
725
919
  * whose request this is: an item that folded in from another agent arrived as a bare
726
920
  * non-sequitur ("First real production sign-in is yours to make whenever you want.")
727
921
  * and the owner answered "What?". The bot names the agent before its first turn. */
728
- agent: z5.string().optional(),
922
+ agent: z7.string().optional(),
729
923
  /** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
730
924
  * same id a face is minted from. A name is not an identity: two pairings may be called
731
925
  * "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
732
926
  * so "who said that" survives the call, and it rides PER TURN because a coalesced call
733
927
  * speaks for several agents — the turn is the only place that knows which. */
734
- agentId: z5.string().optional(),
928
+ agentId: z7.string().optional(),
735
929
  select: SelectShapeSchema.optional(),
736
- options: z5.array(OptionSchema.omit({ id: true })).optional(),
930
+ options: z7.array(OptionSchema.omit({ id: true })).optional(),
737
931
  /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
738
932
  paces itself now — a short beat between the sentences of a turn, the longer one at its end
739
933
  (owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
@@ -742,33 +936,33 @@ var AgendaTurnSchema = z5.object({
742
936
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
743
937
  * (a question blocks, context flows). blocking:false on a question = ask and move
744
938
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
745
- blocking: z5.boolean().optional(),
939
+ blocking: z7.boolean().optional(),
746
940
  /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
747
941
  * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
748
942
  * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
749
943
  * listens for the last word, and only if that listen is silent is this turn said and asked. If they
750
944
  * speak, it is dropped and their words are taken like any reply. */
751
- ifSilent: z5.boolean().optional()
945
+ ifSilent: z7.boolean().optional()
752
946
  });
753
947
  var CLAIM_STALE_MS = 30 * 6e4;
754
- var InboxItemSchema = z5.object({
755
- id: z5.string(),
756
- tokenId: z5.string().optional(),
948
+ var InboxItemSchema = z7.object({
949
+ id: z7.string(),
950
+ tokenId: z7.string().optional(),
757
951
  status: NotifyStatusSchema,
758
952
  context: ContextSchema,
759
- options: z5.array(OptionSchema).optional(),
953
+ options: z7.array(OptionSchema).optional(),
760
954
  /** The ask's declared coverage points (#396), when the agent sent them. */
761
- points: z5.array(z5.string()).optional(),
955
+ points: z7.array(z7.string()).optional(),
762
956
  /** Does this claim want an ANSWER, or is it telling you something? Written per row from
763
957
  * `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
764
958
  * its acknowledge affordance: without it a status update offers a text box and a dismiss,
765
959
  * and neither of those is "got it" (owner, 2026-08-10). */
766
- asks: z5.boolean().optional(),
960
+ asks: z7.boolean().optional(),
767
961
  /** When a live process last pulsed for this row's agent — the liveness input for
768
962
  * "working requires a pulse" (#928): the list said "Working…" from agent_state alone
769
963
  * while the party called the same dead claim stalled. Absent = no token/no data,
770
964
  * which must never CLAIM stalled. */
771
- lastSeenAt: z5.string().optional(),
965
+ lastSeenAt: z7.string().optional(),
772
966
  /** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
773
967
  * the `notification_events` ledger (trigger-written since 20260621010000, so every row a
774
968
  * user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
@@ -778,7 +972,7 @@ var InboxItemSchema = z5.object({
778
972
  * work. Reading the row's birth as the claim's age brands that "No update in 8h" the
779
973
  * instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
780
974
  * `createdAt`. */
781
- agentStateAt: z5.string().datetime().optional(),
975
+ agentStateAt: z7.string().datetime().optional(),
782
976
  /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
783
977
  * 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
784
978
  * superseded or cancelled need is no longer a question anyone is asked). Present only on a
@@ -792,54 +986,54 @@ var InboxItemSchema = z5.object({
792
986
  * `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
793
987
  * the card's own concise heading; `answer` the accepted answer in words, null while open. It
794
988
  * REPLACED `agenda` (turns), which nothing ever filled. */
795
- questions: z5.array(z5.object({
796
- id: z5.string(),
797
- entryId: z5.string(),
798
- title: z5.string(),
799
- state: z5.enum(["open", "answered"]),
800
- answer: z5.string().nullable(),
989
+ questions: z7.array(z7.object({
990
+ id: z7.string(),
991
+ entryId: z7.string(),
992
+ title: z7.string(),
993
+ state: z7.enum(["open", "answered"]),
994
+ answer: z7.string().nullable(),
801
995
  /** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
802
996
  * request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
803
997
  * carry for the call's one agent, so the phone draws it with the same seed. Absent when the
804
998
  * author is not an agent this account holds (unpaired since, or a person). */
805
- agent: z5.object({ tokenId: z5.string(), name: z5.string(), voice: VoiceKeySchema.optional() }).optional(),
999
+ agent: z7.object({ tokenId: z7.string(), name: z7.string(), voice: VoiceKeySchema.optional() }).optional(),
806
1000
  /** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
807
1001
  * its need offers, exactly as its own card carries them, present only when one of them has a
808
1002
  * preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
809
1003
  * never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
810
1004
  * says those, and the list stays small (an `html` is up to 16 KB). */
811
- options: z5.array(OptionSchema).optional()
1005
+ options: z7.array(OptionSchema).optional()
812
1006
  })).optional(),
813
- visuals: z5.array(VisualSchema).optional(),
1007
+ visuals: z7.array(VisualSchema).optional(),
814
1008
  /** The connected agent's name (the single pairing name — user-typed, or the
815
1009
  * agent's suggestion, or a default silly name). */
816
- name: z5.string(),
1010
+ name: z7.string(),
817
1011
  /** The pairing's assigned voice (#462); absent = the default voice. */
818
1012
  voice: VoiceKeySchema.optional(),
819
- repo: z5.string().optional(),
820
- branch: z5.string().optional(),
821
- createdAt: z5.string().datetime(),
822
- snoozedUntil: z5.string().datetime().optional(),
1013
+ repo: z7.string().optional(),
1014
+ branch: z7.string().optional(),
1015
+ createdAt: z7.string().datetime(),
1016
+ snoozedUntil: z7.string().datetime().optional(),
823
1017
  agentState: AgentStateSchema.default("idle"),
824
1018
  /** Whose action the item is waiting on: "you" = an agent asked you (the default,
825
1019
  * every agent→user notification); "agent" = you sent a request and it's awaiting the
826
1020
  * agent (held in the inbox until the agent replies on the thread). */
827
- turn: z5.enum(["you", "agent"]).default("you"),
1021
+ turn: z7.enum(["you", "agent"]).default("you"),
828
1022
  /** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
829
1023
  * the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
830
1024
  * error badge + Retry. */
831
- error: z5.string().optional(),
1025
+ error: z7.string().optional(),
832
1026
  /** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
833
1027
  * with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
834
1028
  * this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
835
1029
  * and "dismiss the stalled ones" cancelled 37 pieces of live work). */
836
- cold: z5.string().datetime().optional(),
837
- clarifies: z5.string().optional(),
1030
+ cold: z7.string().datetime().optional(),
1031
+ clarifies: z7.string().optional(),
838
1032
  /** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
839
1033
  * live"). Present only while an open Call Delivery carries the card's request Entry — read
840
1034
  * off the same open list the card came from, so it clears when the Call does. A card is the
841
1035
  * backup for a call not taken; while the call has it, the call is where it is answered. */
842
- onCall: z5.literal(true).optional(),
1036
+ onCall: z7.literal(true).optional(),
843
1037
  /** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
844
1038
  * declined, and what the ladder will do next — read off the cron's own row, never computed
845
1039
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
@@ -849,31 +1043,31 @@ var InboxItemSchema = z5.object({
849
1043
  * It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
850
1044
  * never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
851
1045
  * and the roster's `unreached` read `declinedAt`, and stand while it does. */
852
- ring: z5.object({
853
- declinedAt: z5.string().datetime(),
854
- anchorAt: z5.string().datetime(),
855
- nextRingAt: z5.string().datetime().nullable(),
856
- step: z5.number().int()
1046
+ ring: z7.object({
1047
+ declinedAt: z7.string().datetime(),
1048
+ anchorAt: z7.string().datetime(),
1049
+ nextRingAt: z7.string().datetime().nullable(),
1050
+ step: z7.number().int()
857
1051
  }).optional(),
858
1052
  /** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
859
1053
  * Absent for anything never delivered through a push, and for older rows written before
860
1054
  * the reason was recorded. Deliberately a debug affordance, shown small (owner,
861
1055
  * 2026-08-07) — its real job is to give "this didn't need a call" something to be
862
1056
  * feedback ABOUT. */
863
- why: z5.object({
1057
+ why: z7.object({
864
1058
  asked: NotifyLevelSchema,
865
1059
  got: NotifyLevelSchema,
866
- because: z5.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
867
- line: z5.string()
1060
+ because: z7.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
1061
+ line: z7.string()
868
1062
  }).optional(),
869
- select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
870
- confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
1063
+ select: z7.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
1064
+ confirmStyle: z7.enum(["yesno", "approve"]).default("yesno").describe(
871
1065
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
872
1066
  ),
873
1067
  /** Real downstream work is stuck behind this one — set by the agent, independent of
874
1068
  * urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
875
1069
  * inbox's blocking badge and the extra confirm step before dismissing it. */
876
- blocking: z5.boolean().default(false),
1070
+ blocking: z7.boolean().default(false),
877
1071
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
878
1072
  answer: UserAnswerSchema.optional(),
879
1073
  /** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
@@ -881,17 +1075,17 @@ var InboxItemSchema = z5.object({
881
1075
  * for a request that asks nothing), whether its content is sealed, and that Goal's state. The
882
1076
  * answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
883
1077
  * here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
884
- communication: z5.object({
885
- deliveryId: z5.string(),
886
- kind: z5.enum(["notification", "call"]),
887
- entryId: z5.string(),
888
- goalIds: z5.array(z5.string()),
889
- decisionNeedId: z5.string().optional(),
890
- sealed: z5.boolean(),
891
- goalState: z5.string().optional(),
1078
+ communication: z7.object({
1079
+ deliveryId: z7.string(),
1080
+ kind: z7.enum(["notification", "call"]),
1081
+ entryId: z7.string(),
1082
+ goalIds: z7.array(z7.string()),
1083
+ decisionNeedId: z7.string().optional(),
1084
+ sealed: z7.boolean(),
1085
+ goalState: z7.string().optional(),
892
1086
  /** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
893
1087
  * and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
894
- goalTitle: z5.string().optional()
1088
+ goalTitle: z7.string().optional()
895
1089
  }).optional(),
896
1090
  /** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
897
1091
  * by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
@@ -903,27 +1097,27 @@ var InboxItemSchema = z5.object({
903
1097
  * own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
904
1098
  * live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
905
1099
  * with no rev is simply always re-sent. */
906
- rev: z5.string().optional()
1100
+ rev: z7.string().optional()
907
1101
  });
908
1102
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
909
- var PushTokenSchema = z5.object({
910
- voipToken: z5.string().min(1).optional(),
911
- alertToken: z5.string().min(1).optional(),
912
- fcmToken: z5.string().min(1).optional(),
913
- platform: z5.enum(["ios", "android"])
1103
+ var PushTokenSchema = z7.object({
1104
+ voipToken: z7.string().min(1).optional(),
1105
+ alertToken: z7.string().min(1).optional(),
1106
+ fcmToken: z7.string().min(1).optional(),
1107
+ platform: z7.enum(["ios", "android"])
914
1108
  }).superRefine((v, ctx) => {
915
1109
  if (v.platform !== "ios") return;
916
1110
  for (const field of ["voipToken", "alertToken"]) {
917
1111
  const token = v[field];
918
1112
  if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
919
1113
  ctx.addIssue({
920
- code: z5.ZodIssueCode.custom,
1114
+ code: z7.ZodIssueCode.custom,
921
1115
  path: [field],
922
1116
  message: `not an APNs device token (want 64 hex chars, got ${token.length})`
923
1117
  });
924
1118
  }
925
1119
  });
926
- var MissedCallSchema = z5.enum([
1120
+ var MissedCallSchema = z7.enum([
927
1121
  "retry_10m",
928
1122
  "retry_30m",
929
1123
  "retry_60m",
@@ -935,31 +1129,31 @@ var MissedCallSchema = z5.enum([
935
1129
  ]);
936
1130
  var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
937
1131
  var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
938
- var BrokerTuningSchema = z5.object({
1132
+ var BrokerTuningSchema = z7.object({
939
1133
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
940
- ackVerbosity: z5.enum(["normal", "none"]).optional(),
1134
+ ackVerbosity: z7.enum(["normal", "none"]).optional(),
941
1135
  /** How readily the mapper asks its one clarification: 'low' = only when truly
942
1136
  * uninterpretable, 'high' = whenever not fully certain. */
943
- clarifyEagerness: z5.enum(["low", "normal", "high"]).optional(),
1137
+ clarifyEagerness: z7.enum(["low", "normal", "high"]).optional(),
944
1138
  /** The user's own shorthand: when they say `say`, they mean `mean`. */
945
- phrasebook: z5.array(z5.object({ say: z5.string().min(1).max(60), mean: z5.string().min(1).max(120) })).max(24).optional(),
1139
+ phrasebook: z7.array(z7.object({ say: z7.string().min(1).max(60), mean: z7.string().min(1).max(120) })).max(24).optional(),
946
1140
  /** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
947
1141
  * which is every account today — means the agent's own words decide, per ask: a call
948
1142
  * about an English ask opens in English. This is the only thing that overrides that,
949
1143
  * and a live caller who switches language mid-call still outranks it (broker/lang.ts).
950
1144
  * Set per user (no UI yet), like `voiceTuning`. */
951
- language: z5.enum(["en", "es"]).optional()
1145
+ language: z7.enum(["en", "es"]).optional()
952
1146
  });
953
- var UserSettingsSchema = z5.object({
954
- permissions: z5.object({
955
- call: z5.boolean(),
956
- banner: z5.boolean(),
957
- push: z5.boolean()
1147
+ var UserSettingsSchema = z7.object({
1148
+ permissions: z7.object({
1149
+ call: z7.boolean(),
1150
+ banner: z7.boolean(),
1151
+ push: z7.boolean()
958
1152
  }),
959
1153
  /** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
960
1154
  * they are an enum on a live column across every account, and the rename is a rename of
961
1155
  * what people read (owner, 2026-09-30). */
962
- sessionMode: z5.enum(["default", "all_calls", "silent"]),
1156
+ sessionMode: z7.enum(["default", "all_calls", "silent"]),
963
1157
  /** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
964
1158
  * optional long after DateNight stopped reading it, on the theory that a phone on an older
965
1159
  * bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
@@ -968,7 +1162,7 @@ var UserSettingsSchema = z5.object({
968
1162
  * an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
969
1163
  * next dead field for the same reason. */
970
1164
  /** Opt-in (default false) to using your content to improve Paigy and train models. */
971
- improveConsent: z5.boolean(),
1165
+ improveConsent: z7.boolean(),
972
1166
  missedCall: MissedCallSchema.default("backoff_standard"),
973
1167
  /** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
974
1168
  * (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
@@ -976,17 +1170,17 @@ var UserSettingsSchema = z5.object({
976
1170
  * Optional, NOT defaulted: a stale client PATCHing the full settings object
977
1171
  * must not silently reset this privacy choice. Absent = leave unchanged on
978
1172
  * write, 'hosted' on read (see store.ts). */
979
- voiceMode: z5.enum(["hosted", "on_device"]).optional(),
1173
+ voiceMode: z7.enum(["hosted", "on_device"]).optional(),
980
1174
  /** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
981
1175
  * turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
982
1176
  * defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
983
1177
  * object must not silently turn it back on. Absent = leave unchanged on write, true on
984
1178
  * read (see store.ts). */
985
- talk: z5.boolean().optional(),
1179
+ talk: z7.boolean().optional(),
986
1180
  /** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
987
1181
  * load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
988
1182
  * newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
989
- callDiagnostics: z5.boolean().optional(),
1183
+ callDiagnostics: z7.boolean().optional(),
990
1184
  /** Per-user ring budget (#603): calls per rolling day before further calls
991
1185
  * degrade to banner. Absent = the global default (25). A number, never a
992
1186
  * bypass — every account keeps a ceiling. No UI; set per user for testing. */
@@ -994,7 +1188,7 @@ var UserSettingsSchema = z5.object({
994
1188
  * payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
995
1189
  * slower speaker). No API-side semantics; the bot resolves each key with its
996
1190
  * own defaults. Set per user (no UI yet); absent = bot defaults. */
997
- voiceTuning: z5.record(z5.string(), z5.union([z5.number(), z5.string()])).optional(),
1191
+ voiceTuning: z7.record(z7.string(), z7.union([z7.number(), z7.string()])).optional(),
998
1192
  /** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
999
1193
  * only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
1000
1194
  * an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
@@ -1003,53 +1197,53 @@ var UserSettingsSchema = z5.object({
1003
1197
  * that failure reads as the reminder rail being unreliable rather than as a missing
1004
1198
  * setting. Absent = a spoken time can't be landed, so the reminder rides the next
1005
1199
  * call — honest about what we know. */
1006
- timezone: z5.string().min(1).max(64).optional(),
1200
+ timezone: z7.string().min(1).max(64).optional(),
1007
1201
  /** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
1008
1202
  * clobber guard as voiceMode: absent = leave unchanged on write. */
1009
1203
  broker: BrokerTuningSchema.optional()
1010
1204
  });
1011
- var HistoryWorkSchema = z5.object({
1012
- id: z5.string(),
1013
- title: z5.string(),
1014
- state: z5.enum(["done", "cancelled"]),
1205
+ var HistoryWorkSchema = z7.object({
1206
+ id: z7.string(),
1207
+ title: z7.string(),
1208
+ state: z7.enum(["done", "cancelled"]),
1015
1209
  /** Who held it (`agent:<tokenId>` or `human:<userId>`). */
1016
- assignee: z5.string()
1210
+ assignee: z7.string()
1017
1211
  });
1018
- var HistoryEntrySchema = z5.union([
1019
- z5.object({ at: z5.string(), card: InboxItemSchema }),
1020
- z5.object({ at: z5.string(), work: HistoryWorkSchema })
1212
+ var HistoryEntrySchema = z7.union([
1213
+ z7.object({ at: z7.string(), card: InboxItemSchema }),
1214
+ z7.object({ at: z7.string(), work: HistoryWorkSchema })
1021
1215
  ]);
1022
- var HistoryPageSchema = z5.object({
1023
- entries: z5.array(HistoryEntrySchema),
1024
- next: z5.string().nullable()
1216
+ var HistoryPageSchema = z7.object({
1217
+ entries: z7.array(HistoryEntrySchema),
1218
+ next: z7.string().nullable()
1025
1219
  });
1026
1220
  var ACTIVITY_LINES = 2;
1027
1221
  var ACTIVITY_LINE_MAX = 80;
1028
- var AgentActivitySchema = z5.object({
1222
+ var AgentActivitySchema = z7.object({
1029
1223
  /** Oldest first, so the newest line is last — the one that replaces in place. */
1030
- lines: z5.array(z5.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
1224
+ lines: z7.array(z7.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
1031
1225
  /** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
1032
1226
  * that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
1033
- at: z5.string().datetime()
1227
+ at: z7.string().datetime()
1034
1228
  });
1035
- var ConnectionSummarySchema = z5.object({
1229
+ var ConnectionSummarySchema = z7.object({
1036
1230
  /** The connection = the agent's token id (used to address a request). */
1037
- id: z5.string(),
1231
+ id: z7.string(),
1038
1232
  /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
1039
1233
  * never talks); "agent" = an identity that sends. The roster and devices surfaces split
1040
1234
  * on this. Optional/absent reads as "agent" (a row predating the kind column). See
1041
1235
  * docs/server/tokens/devices-vs-agents-design.md. */
1042
- kind: z5.enum(["device", "agent"]).optional(),
1236
+ kind: z7.enum(["device", "agent"]).optional(),
1043
1237
  /** For an agent, the token id of the DEVICE that minted it — so agents group under their
1044
1238
  * machine, and revoking a device cascades to them. Null on devices, and on unlinked
1045
1239
  * agents (phone-launched, provider-managed, or minted before the link existed). */
1046
- mintedByDevice: z5.string().nullable().optional(),
1047
- device: z5.string().nullable(),
1240
+ mintedByDevice: z7.string().nullable().optional(),
1241
+ device: z7.string().nullable(),
1048
1242
  /** The agent's display name (the single pairing name). */
1049
- name: z5.string(),
1243
+ name: z7.string(),
1050
1244
  /** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
1051
1245
  * label; null for a local connection. Sourced from the token's provider, not the name. */
1052
- provider: z5.string().nullable(),
1246
+ provider: z7.string().nullable(),
1053
1247
  /** The pairing's assigned voice (#462); null = the default voice. */
1054
1248
  voice: VoiceKeySchema.nullable(),
1055
1249
  /** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
@@ -1060,34 +1254,34 @@ var ConnectionSummarySchema = z5.object({
1060
1254
  * every surface at once and outranks even `sessionMode: all_calls` — a mode the user
1061
1255
  * set once must not overrule a rule they set about one agent. */
1062
1256
  reach: NotifyLevelSchema.nullable().optional(),
1063
- createdAt: z5.string().datetime(),
1257
+ createdAt: z7.string().datetime(),
1064
1258
  /** Most recent notification on this connection, either direction. Null = no contact yet.
1065
1259
  * Drives the agents-page recency grouping (Today / This week / …). */
1066
- lastContactAt: z5.string().datetime().nullable(),
1260
+ lastContactAt: z7.string().datetime().nullable(),
1067
1261
  /** Last presence heartbeat from a running agent process (POST /api/presence) — the
1068
1262
  * desktop app while open. Null = never seen; stale = offline. */
1069
- lastSeenAt: z5.string().datetime().nullable().optional(),
1263
+ lastSeenAt: z7.string().datetime().nullable().optional(),
1070
1264
  /** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
1071
1265
  * its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
1072
1266
  * `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
1073
- lastWorkedAt: z5.string().datetime().nullable().optional(),
1267
+ lastWorkedAt: z7.string().datetime().nullable().optional(),
1074
1268
  /** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
1075
1269
  * With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
1076
- oldestReadyAt: z5.string().datetime().nullable().optional(),
1270
+ oldestReadyAt: z7.string().datetime().nullable().optional(),
1077
1271
  /** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
1078
1272
  * harness availabilities + granted workspaces — the option set the phone's
1079
1273
  * "new session" sheet offers. Absent for ordinary MCP agents. */
1080
- runtime: z5.object({
1274
+ runtime: z7.object({
1081
1275
  /** The @paigy/harness this host is running — a machine the self-update has not reached
1082
1276
  * shows its age here (`apps/desktop/src/update.ts`). */
1083
- version: z5.string().optional(),
1084
- harnesses: z5.array(z5.object({ name: z5.string(), label: z5.string(), status: z5.string() })).optional(),
1085
- workspaces: z5.array(z5.string()).optional(),
1277
+ version: z7.string().optional(),
1278
+ harnesses: z7.array(z7.object({ name: z7.string(), label: z7.string(), status: z7.string() })).optional(),
1279
+ workspaces: z7.array(z7.string()).optional(),
1086
1280
  /** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
1087
1281
  * repo, and each repo directly inside one, with its `origin` remote. A session started for
1088
1282
  * work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
1089
1283
  * own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
1090
- repos: z5.array(z5.object({ path: z5.string(), remote: z5.string() })).optional()
1284
+ repos: z7.array(z7.object({ path: z7.string(), remote: z7.string() })).optional()
1091
1285
  }).optional(),
1092
1286
  /** The tail of this agent's working log, when a harness is driving it — the agent page's
1093
1287
  * live strip. Absent for anything the desktop harness isn't running (a hatched identity
@@ -1096,156 +1290,156 @@ var ConnectionSummarySchema = z5.object({
1096
1290
  activity: AgentActivitySchema.optional(),
1097
1291
  /** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
1098
1292
  * false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
1099
- managed: z5.boolean()
1293
+ managed: z7.boolean()
1100
1294
  });
1101
- var LedgerItemSchema = z5.object({ id: z5.string(), parentId: z5.string(), title: z5.string(), createdAt: z5.string() });
1102
- var AgentLedgerSchema = z5.object({
1295
+ var LedgerItemSchema = z7.object({ id: z7.string(), parentId: z7.string(), title: z7.string(), createdAt: z7.string() });
1296
+ var AgentLedgerSchema = z7.object({
1103
1297
  /** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
1104
- agent: z5.object({ id: z5.string(), name: z5.string().nullable(), revokedAt: z5.string().nullable() }),
1298
+ agent: z7.object({ id: z7.string(), name: z7.string().nullable(), revokedAt: z7.string().nullable() }),
1105
1299
  /** Its own questions you have not answered. */
1106
- asks: z5.array(LedgerItemSchema),
1300
+ asks: z7.array(LedgerItemSchema),
1107
1301
  /** Its questions you answered that nobody acted on — still owed to somebody. */
1108
- answered: z5.array(LedgerItemSchema),
1302
+ answered: z7.array(LedgerItemSchema),
1109
1303
  /** Requests you sent it that it never took. */
1110
- requests: z5.array(LedgerItemSchema),
1111
- goals: z5.array(z5.object({ id: z5.string(), outcome: z5.string(), state: z5.string() })),
1112
- callbacks: z5.array(z5.object({ id: z5.string(), parentId: z5.string(), trigger: z5.string(), note: z5.string(), dueAt: z5.string().nullable() }))
1304
+ requests: z7.array(LedgerItemSchema),
1305
+ goals: z7.array(z7.object({ id: z7.string(), outcome: z7.string(), state: z7.string() })),
1306
+ callbacks: z7.array(z7.object({ id: z7.string(), parentId: z7.string(), trigger: z7.string(), note: z7.string(), dueAt: z7.string().nullable() }))
1113
1307
  });
1114
- var ReassignResultSchema = z5.object({
1115
- moved: z5.object({ asks: z5.number(), answered: z5.number(), requests: z5.number(), goals: z5.number(), callbacks: z5.number() }),
1116
- parentId: z5.string().nullable()
1308
+ var ReassignResultSchema = z7.object({
1309
+ moved: z7.object({ asks: z7.number(), answered: z7.number(), requests: z7.number(), goals: z7.number(), callbacks: z7.number() }),
1310
+ parentId: z7.string().nullable()
1117
1311
  });
1118
- var LessonStateSchema = z5.enum(["active", "proposed", "retired"]);
1119
- var LessonViewSchema = z5.object({
1120
- id: z5.string(),
1121
- text: z5.string(),
1312
+ var LessonStateSchema = z7.enum(["active", "proposed", "retired"]);
1313
+ var LessonViewSchema = z7.object({
1314
+ id: z7.string(),
1315
+ text: z7.string(),
1122
1316
  state: LessonStateSchema,
1123
1317
  /** The Goal it is scoped to; null = the whole account. */
1124
- scopeGoalId: z5.string().nullable(),
1125
- goalTitle: z5.string().nullable(),
1126
- version: z5.number(),
1127
- pinned: z5.boolean(),
1318
+ scopeGoalId: z7.string().nullable(),
1319
+ goalTitle: z7.string().nullable(),
1320
+ version: z7.number(),
1321
+ pinned: z7.boolean(),
1128
1322
  /** When the person last wrote its text themselves. */
1129
- editedAt: z5.string().nullable(),
1130
- createdAt: z5.string(),
1131
- updatedAt: z5.string(),
1323
+ editedAt: z7.string().nullable(),
1324
+ createdAt: z7.string(),
1325
+ updatedAt: z7.string(),
1132
1326
  /** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
1133
- sources: z5.array(z5.object({ entryId: z5.string(), words: z5.string().nullable(), at: z5.string() }))
1327
+ sources: z7.array(z7.object({ entryId: z7.string(), words: z7.string().nullable(), at: z7.string() }))
1134
1328
  });
1135
- var QueueQuestionSchema = z5.object({
1329
+ var QueueQuestionSchema = z7.object({
1136
1330
  /** The decision need's id — what an answer is accepted against. */
1137
- id: z5.string(),
1331
+ id: z7.string(),
1138
1332
  /** The words that were asked, from the request Entry that asked them. */
1139
- question: z5.string(),
1333
+ question: z7.string(),
1140
1334
  /** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
1141
1335
  * for a need whose request Entry is carried by no interactive Delivery, which nothing
1142
1336
  * can answer. */
1143
- deliveryId: z5.string().nullable().default(null),
1337
+ deliveryId: z7.string().nullable().default(null),
1144
1338
  /** The Entry the ruling is about. */
1145
- aboutId: z5.string().nullable().default(null),
1339
+ aboutId: z7.string().nullable().default(null),
1146
1340
  /** Empty for a free-text question. */
1147
- options: z5.array(OptionSchema).default([]),
1148
- select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
1149
- askedAt: z5.string(),
1341
+ options: z7.array(OptionSchema).default([]),
1342
+ select: z7.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
1343
+ askedAt: z7.string(),
1150
1344
  /** Null while the question is open — which is how the page tells the two apart. */
1151
- answeredAt: z5.string().nullable().default(null),
1345
+ answeredAt: z7.string().nullable().default(null),
1152
1346
  /** The ruling in the person's own words, from the contribution that replied — not the
1153
1347
  * option id, which is not something anyone reads back. Null while it is open, and null
1154
1348
  * for a settled question whose reply carried nothing readable. */
1155
- answer: z5.string().nullable().default(null),
1349
+ answer: z7.string().nullable().default(null),
1156
1350
  /** The Goal this question belongs to — a step knows its Goal on its own, not only through
1157
1351
  * an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
1158
1352
  * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
1159
1353
  * order, its route, home's trees and the list of steps all take a step's Goal from here, so
1160
1354
  * this is the field they agree through rather than each re-deriving it from the row it
1161
1355
  * arrived under. Required because the API projects it on every need it sends. */
1162
- goalId: z5.string(),
1356
+ goalId: z7.string(),
1163
1357
  /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
1164
1358
  * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
1165
1359
  * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
1166
1360
  * gate at all. */
1167
- blocked: z5.boolean().default(false)
1361
+ blocked: z7.boolean().default(false)
1168
1362
  });
1169
- var QueueReplySchema = z5.object({
1363
+ var QueueReplySchema = z7.object({
1170
1364
  /** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
1171
1365
  * id) — so the phone can tell a reply it just sent from one the queue already carries, and the
1172
1366
  * walk can name it in its zoom. */
1173
- id: z5.string(),
1367
+ id: z7.string(),
1174
1368
  /** The Goal the note is on. */
1175
- goalId: z5.string(),
1369
+ goalId: z7.string(),
1176
1370
  /** What the note said. */
1177
- note: z5.string(),
1371
+ note: z7.string(),
1178
1372
  /** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
1179
- deliveryId: z5.string(),
1180
- requestEntryId: z5.string(),
1181
- askedAt: z5.string(),
1373
+ deliveryId: z7.string(),
1374
+ requestEntryId: z7.string(),
1375
+ askedAt: z7.string(),
1182
1376
  /** When the person last replied — the window's start. */
1183
- repliedAt: z5.string(),
1377
+ repliedAt: z7.string(),
1184
1378
  /** The person's latest words about it; null when there is nothing readable in them. */
1185
- reply: z5.string().nullable()
1379
+ reply: z7.string().nullable()
1186
1380
  });
1187
- var QueueItemSchema = z5.object({
1188
- id: z5.string(),
1381
+ var QueueItemSchema = z7.object({
1382
+ id: z7.string(),
1189
1383
  /** One-line headline — the first sentence of the outcome. */
1190
- title: z5.string(),
1384
+ title: z7.string(),
1191
1385
  /** The outcome in full, verbatim: the person's own words are what an assignee sees. */
1192
- intent: z5.string(),
1386
+ intent: z7.string(),
1193
1387
  /** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
1194
- state: z5.string(),
1388
+ state: z7.string(),
1195
1389
  /** Who holds it (a participant ref); null when nobody does yet. */
1196
- assignee: z5.string().nullable().default(null),
1390
+ assignee: z7.string().nullable().default(null),
1197
1391
  /** What the agent last said it was doing; null if it has said nothing. */
1198
- progress: z5.string().nullable().default(null),
1392
+ progress: z7.string().nullable().default(null),
1199
1393
  /** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
1200
1394
  * served only while it was written for the current note. Null means show the Goal's name. */
1201
- progressLine: z5.string().nullable().optional(),
1202
- reviewPending: z5.boolean().default(false),
1203
- dueAt: z5.string().nullable().default(null),
1395
+ progressLine: z7.string().nullable().optional(),
1396
+ reviewPending: z7.boolean().default(false),
1397
+ dueAt: z7.string().nullable().default(null),
1204
1398
  /** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
1205
1399
  * with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
1206
- finishedAt: z5.string().nullable().optional(),
1400
+ finishedAt: z7.string().nullable().optional(),
1207
1401
  /** The Goal this one was opened under; null at the root. */
1208
- parentGoalId: z5.string().nullable().default(null),
1402
+ parentGoalId: z7.string().nullable().default(null),
1209
1403
  /** Goals opened under this one — only those the same list holds. */
1210
- childGoalIds: z5.array(z5.string()).default([]),
1404
+ childGoalIds: z7.array(z7.string()).default([]),
1211
1405
  /** Goals this one waits on (start or finish gates). */
1212
- dependencyGoalIds: z5.array(z5.string()).default([]),
1406
+ dependencyGoalIds: z7.array(z7.string()).default([]),
1213
1407
  /** True while any gate is on a Goal that is not done — the walk draws it dashed. */
1214
- blocked: z5.boolean().default(false),
1408
+ blocked: z7.boolean().default(false),
1215
1409
  /** Its questions: every OPEN one, and at most ten settled, newest settled first
1216
1410
  * (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
1217
1411
  * `answered` are, and a settled one's words are a line (280 characters), its body read when the
1218
1412
  * question is opened. */
1219
- questions: z5.array(QueueQuestionSchema).default([]),
1413
+ questions: z7.array(QueueQuestionSchema).default([]),
1220
1414
  /** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
1221
1415
  * bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
1222
1416
  * right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
1223
1417
  * by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
1224
- asked: z5.number().optional(),
1225
- answered: z5.number().optional(),
1418
+ asked: z7.number().optional(),
1419
+ answered: z7.number().optional(),
1226
1420
  /** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
1227
1421
  * Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
1228
1422
  * not spell an empty list. */
1229
- replies: z5.array(QueueReplySchema).optional(),
1423
+ replies: z7.array(QueueReplySchema).optional(),
1230
1424
  /** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
1231
- repo: z5.string().nullable().optional(),
1232
- createdAt: z5.string(),
1233
- updatedAt: z5.string().nullable().default(null),
1234
- /** When its owner last SAID something about it (`goals.last_progress_at`, written by every
1235
- * `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
1425
+ repo: z7.string().nullable().optional(),
1426
+ createdAt: z7.string(),
1427
+ updatedAt: z7.string().nullable().default(null),
1428
+ /** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
1429
+ * as progress). `updatedAt` moves for reasons nobody chose — a
1236
1430
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
1237
- lastProgressAt: z5.string().nullable().optional(),
1431
+ lastProgressAt: z7.string().nullable().optional(),
1238
1432
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
1239
1433
  * kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
1240
1434
  * note. A progress note is an Entry, so this is already the newer of the two: the person's note
1241
1435
  * shows the moment it is written, and the agent's reply or next note replaces it by being newer.
1242
1436
  * `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
1243
1437
  * Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
1244
- latest: z5.object({
1245
- from: z5.enum(["person", "agent"]),
1246
- said: z5.string(),
1247
- at: z5.string(),
1248
- entryId: z5.string()
1438
+ latest: z7.object({
1439
+ from: z7.enum(["person", "agent"]),
1440
+ said: z7.string(),
1441
+ at: z7.string(),
1442
+ entryId: z7.string()
1249
1443
  }).nullable().optional(),
1250
1444
  /** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
1251
1445
  * the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
@@ -1258,7 +1452,7 @@ var QueueItemSchema = z5.object({
1258
1452
  * minute after the person speaks erases their instant from it, and the durable traces the client
1259
1453
  * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
1260
1454
  * Entry with no `about_id` is in neither. */
1261
- lastPersonAt: z5.string().nullable().optional(),
1455
+ lastPersonAt: z7.string().nullable().optional(),
1262
1456
  /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
1263
1457
  * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
1264
1458
  * incremental read knows a row has not moved: the phone echoes back the revs it holds
@@ -1269,40 +1463,40 @@ var QueueItemSchema = z5.object({
1269
1463
  * the row shows that no `updated_at` moves for (a lease lapsing, a dependency's state, a
1270
1464
  * sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
1271
1465
  * queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
1272
- rev: z5.string().optional()
1466
+ rev: z7.string().optional()
1273
1467
  });
1274
- var QueueDeltaSchema = z5.object({
1275
- ids: z5.array(z5.string()),
1276
- items: z5.array(QueueItemSchema)
1468
+ var QueueDeltaSchema = z7.object({
1469
+ ids: z7.array(z7.string()),
1470
+ items: z7.array(QueueItemSchema)
1277
1471
  });
1278
1472
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
1279
- var NoteSourceSchema = z5.enum(["app", "call"]);
1280
- var NoteStatusSchema = z5.enum(["open", "assigned", "in_progress", "done"]);
1281
- var NoteRepeatSchema = z5.enum(["once", "until_done"]);
1282
- var DecisionSchema = z5.object({
1283
- id: z5.string(),
1473
+ var NoteSourceSchema = z7.enum(["app", "call"]);
1474
+ var NoteStatusSchema = z7.enum(["open", "assigned", "in_progress", "done"]);
1475
+ var NoteRepeatSchema = z7.enum(["once", "until_done"]);
1476
+ var DecisionSchema = z7.object({
1477
+ id: z7.string(),
1284
1478
  /** The note this decision refines; null = recorded on a bare thread (the
1285
1479
  * extensibility seam — any conversation can accrue decisions). */
1286
- noteId: z5.string().nullable(),
1480
+ noteId: z7.string().nullable(),
1287
1481
  /** What was ambiguous — the broker's (or the user's own) question. */
1288
- question: z5.string(),
1482
+ question: z7.string(),
1289
1483
  /** The user's ruling; null while the question is open. */
1290
- answer: z5.string().nullable(),
1291
- decidedAt: z5.string().nullable(),
1292
- createdAt: z5.string()
1484
+ answer: z7.string().nullable(),
1485
+ decidedAt: z7.string().nullable(),
1486
+ createdAt: z7.string()
1293
1487
  });
1294
- var NoteSchema = z5.object({
1295
- id: z5.string(),
1488
+ var NoteSchema = z7.object({
1489
+ id: z7.string(),
1296
1490
  /** One-line headline (broker-titled; deterministic floor). */
1297
- title: z5.string(),
1491
+ title: z7.string(),
1298
1492
  /** The original intent, verbatim — assignees always see the user's own words. */
1299
- intent: z5.string(),
1493
+ intent: z7.string(),
1300
1494
  source: NoteSourceSchema,
1301
1495
  status: NoteStatusSchema,
1302
1496
  /** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
1303
- assignee: z5.string().nullable(),
1497
+ assignee: z7.string().nullable(),
1304
1498
  /** The request thread minted at assignment; null until assigned. */
1305
- parentId: z5.string().nullable(),
1499
+ parentId: z7.string().nullable(),
1306
1500
  /** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
1307
1501
  * call — never a deadline. It only ever comes from the user's own words, so when it
1308
1502
  * passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
@@ -1311,152 +1505,152 @@ var NoteSchema = z5.object({
1311
1505
  // Defaulted, not required: a Note from an API deploy older than the reminders
1312
1506
  // migration has none of these, and the defaults ARE what it means — no not-before,
1313
1507
  // one ride, never ridden. Parsing must not fail across a rolling deploy.
1314
- dueAt: z5.string().nullable().default(null),
1508
+ dueAt: z7.string().nullable().default(null),
1315
1509
  repeat: NoteRepeatSchema.default("once"),
1316
1510
  /** How many calls have already carried it — the fatigue cap counts rides, not days. */
1317
- rides: z5.number().int().default(0),
1318
- lastRideAt: z5.string().nullable().default(null),
1319
- createdAt: z5.string()
1511
+ rides: z7.number().int().default(0),
1512
+ lastRideAt: z7.string().nullable().default(null),
1513
+ createdAt: z7.string()
1320
1514
  });
1321
- var TriageItemSchema = z5.object({
1322
- noteId: z5.string(),
1515
+ var TriageItemSchema = z7.object({
1516
+ noteId: z7.string(),
1323
1517
  /** The note's headline at run time. */
1324
- title: z5.string(),
1518
+ title: z7.string(),
1325
1519
  /** WHY, in one short human line, evidence first — this is read on a phone underneath
1326
1520
  * the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
1327
1521
  * Never a model's reasoning transcript, never an id. */
1328
- why: z5.string()
1522
+ why: z7.string()
1329
1523
  });
1330
- var TriageAssignmentSchema = z5.object({
1524
+ var TriageAssignmentSchema = z7.object({
1331
1525
  /** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
1332
- agent: z5.string(),
1526
+ agent: z7.string(),
1333
1527
  /** Its display name at run time (the name on the hatchling's card). Denormalized for the
1334
1528
  * same reason as `title`: the card must render from the proposal alone. */
1335
- agentName: z5.string(),
1336
- notes: z5.array(TriageItemSchema)
1529
+ agentName: z7.string(),
1530
+ notes: z7.array(TriageItemSchema)
1337
1531
  });
1338
- var TriageStatusSchema = z5.enum(["open", "superseded", "dismissed"]);
1339
- var SubmitTriageSchema = z5.object({
1532
+ var TriageStatusSchema = z7.enum(["open", "superseded", "dismissed"]);
1533
+ var SubmitTriageSchema = z7.object({
1340
1534
  /** Which runtime judged: "ollama" (inference never left the machine) or a harness the
1341
1535
  * user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
1342
1536
  * so the phone can say where the content went — an unattributed privacy claim is worth
1343
1537
  * nothing, and #1106's promise is precisely "Paigy's servers never see this". */
1344
- provider: z5.string().min(1).max(60),
1538
+ provider: z7.string().min(1).max(60),
1345
1539
  /** The concrete model when the provider names one (an ollama tag); null otherwise. */
1346
- model: z5.string().max(200).nullable().optional(),
1540
+ model: z7.string().max(200).nullable().optional(),
1347
1541
  /** How many open notes the run actually looked at — the denominator on the phone
1348
1542
  * ("6 of 50"), and the honest answer to "did it read the whole queue?". */
1349
- reviewed: z5.number().int().min(0).max(1e4).default(0),
1350
- close: z5.array(TriageItemSchema).max(200).default([]),
1351
- stale: z5.array(TriageItemSchema).max(200).default([]),
1352
- assign: z5.array(TriageAssignmentSchema).max(50).default([])
1543
+ reviewed: z7.number().int().min(0).max(1e4).default(0),
1544
+ close: z7.array(TriageItemSchema).max(200).default([]),
1545
+ stale: z7.array(TriageItemSchema).max(200).default([]),
1546
+ assign: z7.array(TriageAssignmentSchema).max(50).default([])
1353
1547
  });
1354
1548
  var TriageProposalSchema = SubmitTriageSchema.extend({
1355
- id: z5.string(),
1356
- runAt: z5.string(),
1549
+ id: z7.string(),
1550
+ runAt: z7.string(),
1357
1551
  status: TriageStatusSchema,
1358
- model: z5.string().nullable().default(null)
1552
+ model: z7.string().nullable().default(null)
1359
1553
  });
1360
- var AcceptTriageSchema = z5.discriminatedUnion("group", [
1361
- z5.object({ group: z5.literal("close"), noteIds: z5.array(z5.string()).max(200).optional() }),
1362
- z5.object({ group: z5.literal("stale"), noteIds: z5.array(z5.string()).max(200).optional() }),
1363
- z5.object({
1364
- group: z5.literal("assign"),
1365
- agent: z5.string().min(1),
1366
- noteIds: z5.array(z5.string()).max(200).optional()
1554
+ var AcceptTriageSchema = z7.discriminatedUnion("group", [
1555
+ z7.object({ group: z7.literal("close"), noteIds: z7.array(z7.string()).max(200).optional() }),
1556
+ z7.object({ group: z7.literal("stale"), noteIds: z7.array(z7.string()).max(200).optional() }),
1557
+ z7.object({
1558
+ group: z7.literal("assign"),
1559
+ agent: z7.string().min(1),
1560
+ noteIds: z7.array(z7.string()).max(200).optional()
1367
1561
  })
1368
1562
  ]);
1369
- var AcceptTriageResultSchema = z5.object({
1370
- accepted: z5.array(z5.string()),
1371
- failed: z5.array(z5.object({ noteId: z5.string(), reason: z5.string() }))
1563
+ var AcceptTriageResultSchema = z7.object({
1564
+ accepted: z7.array(z7.string()),
1565
+ failed: z7.array(z7.object({ noteId: z7.string(), reason: z7.string() }))
1372
1566
  });
1373
- var DeliveryModeSchema = z5.enum(["poll", "self_hosted"]);
1374
- var RegisterDeliverySchema = z5.object({ mode: DeliveryModeSchema });
1375
- var OAuthStartSchema = z5.object({
1376
- provider: z5.enum(["cma"]),
1377
- returnTo: z5.string().min(1)
1567
+ var DeliveryModeSchema = z7.enum(["poll", "self_hosted"]);
1568
+ var RegisterDeliverySchema = z7.object({ mode: DeliveryModeSchema });
1569
+ var OAuthStartSchema = z7.object({
1570
+ provider: z7.enum(["cma"]),
1571
+ returnTo: z7.string().min(1)
1378
1572
  });
1379
- var DeliveryConfigSchema = z5.object({
1380
- tokenId: z5.string(),
1573
+ var DeliveryConfigSchema = z7.object({
1574
+ tokenId: z7.string(),
1381
1575
  mode: DeliveryModeSchema,
1382
1576
  /** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
1383
1577
  * (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
1384
1578
  * carries credentials; only a `poll` registration can come back with null here. */
1385
- realtime: z5.object({ url: z5.string(), anonKey: z5.string() }).nullable()
1579
+ realtime: z7.object({ url: z7.string(), anonKey: z7.string() }).nullable()
1386
1580
  });
1387
- var HostDecisionSchema = z5.object({
1581
+ var HostDecisionSchema = z7.object({
1388
1582
  /** The agent's token id: the row's `recipient`. */
1389
- agent: z5.string().uuid(),
1390
- decision: z5.enum(["stood_back", "took_over"]),
1391
- /** The work it was about: the Goal `claim_goal` would hand that agent next. */
1392
- goalId: z5.string().uuid().nullable().optional(),
1583
+ agent: z7.string().uuid(),
1584
+ decision: z7.enum(["stood_back", "took_over"]),
1585
+ /** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
1586
+ goalId: z7.string().uuid().nullable().optional(),
1393
1587
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
1394
- seenAt: z5.string().datetime().nullable().optional(),
1588
+ seenAt: z7.string().datetime().nullable().optional(),
1395
1589
  /** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
1396
- since: z5.string().datetime().nullable().optional(),
1590
+ since: z7.string().datetime().nullable().optional(),
1397
1591
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
1398
- said: z5.string().max(300).optional()
1592
+ said: z7.string().max(300).optional()
1399
1593
  });
1400
- var WakeNudgeSchema = z5.object({
1401
- kind: z5.enum(["reply", "request", "callback"]),
1402
- notificationId: z5.string().optional(),
1403
- parentId: z5.string()
1594
+ var WakeNudgeSchema = z7.object({
1595
+ kind: z7.enum(["reply", "request", "callback"]),
1596
+ notificationId: z7.string().optional(),
1597
+ parentId: z7.string()
1404
1598
  });
1405
- var PairingStatusSchema = z5.enum(["pending", "approved", "denied", "expired"]);
1406
- var DeviceCodeSchema = z5.object({
1407
- device_code: z5.string(),
1408
- user_code: z5.string(),
1409
- verification_uri: z5.string().url(),
1410
- verification_uri_complete: z5.string().url(),
1411
- interval: z5.number(),
1412
- expires_in: z5.number()
1599
+ var PairingStatusSchema = z7.enum(["pending", "approved", "denied", "expired"]);
1600
+ var DeviceCodeSchema = z7.object({
1601
+ device_code: z7.string(),
1602
+ user_code: z7.string(),
1603
+ verification_uri: z7.string().url(),
1604
+ verification_uri_complete: z7.string().url(),
1605
+ interval: z7.number(),
1606
+ expires_in: z7.number()
1413
1607
  });
1414
- var DeviceInfoSchema = z5.object({
1415
- code: z5.string(),
1608
+ var DeviceInfoSchema = z7.object({
1609
+ code: z7.string(),
1416
1610
  /** The agent's suggested name (from /device/code) — shown on the approval screen,
1417
1611
  * pre-filling the name field the human can edit. */
1418
- name: z5.string(),
1612
+ name: z7.string(),
1419
1613
  /** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
1420
1614
  * build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
1421
1615
  * install crashes on the pairing screen on first launch, before the OTA lands
1422
1616
  * (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
1423
- agent: z5.string().optional(),
1424
- device: z5.string().nullable(),
1617
+ agent: z7.string().optional(),
1618
+ device: z7.string().nullable(),
1425
1619
  status: PairingStatusSchema
1426
1620
  });
1427
- var DeviceTokenSchema = z5.object({
1428
- access_token: z5.string(),
1621
+ var DeviceTokenSchema = z7.object({
1622
+ access_token: z7.string(),
1429
1623
  /** The pairing's single name (user-typed at approval, the agent's suggestion, or
1430
1624
  * a default silly name). */
1431
- name: z5.string(),
1432
- device: z5.string().nullable(),
1625
+ name: z7.string(),
1626
+ device: z7.string().nullable(),
1433
1627
  /** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
1434
1628
  * draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
1435
- voice: z5.string().nullable().optional(),
1629
+ voice: z7.string().nullable().optional(),
1436
1630
  /** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
1437
1631
  * that survives a rename. Cached by the host's identity beat. */
1438
- token_id: z5.string().nullable().optional(),
1632
+ token_id: z7.string().nullable().optional(),
1439
1633
  /** WHERE this identity works — the folder a wake should land it in. Written by the host
1440
1634
  * at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
1441
1635
  * landed in the FIRST granted workspace and the agent rediscovered its own repo from
1442
1636
  * the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
1443
- workspace: z5.string().nullable().optional(),
1637
+ workspace: z7.string().nullable().optional(),
1444
1638
  /** Local host recovery must preserve the launch's runtime and Paigy identity. */
1445
- harness: z5.enum(["claude", "codex", "agy"]).optional(),
1446
- session_id: z5.string().uuid().optional(),
1639
+ harness: z7.enum(["claude", "codex", "agy"]).optional(),
1640
+ session_id: z7.string().uuid().optional(),
1447
1641
  /** A conversation the host must not resume: its context is full, so every turn fails
1448
1642
  * ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
1449
1643
  * whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
1450
1644
  * copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
1451
- full_session: z5.string().optional(),
1452
- uik_pub: z5.string().nullable().optional()
1645
+ full_session: z7.string().optional(),
1646
+ uik_pub: z7.string().nullable().optional()
1453
1647
  });
1454
- var SupportRequestSchema = z5.object({
1455
- email: z5.string().email().max(320),
1456
- message: z5.string().trim().min(1).max(5e3),
1457
- name: z5.string().trim().max(120).optional()
1648
+ var SupportRequestSchema = z7.object({
1649
+ email: z7.string().email().max(320),
1650
+ message: z7.string().trim().min(1).max(5e3),
1651
+ name: z7.string().trim().max(120).optional()
1458
1652
  });
1459
- var NotificationFeedbackKindSchema = z5.enum([
1653
+ var NotificationFeedbackKindSchema = z7.enum([
1460
1654
  "break_down",
1461
1655
  // "This should be more than one ask — break it down."
1462
1656
  "regenerate_options",
@@ -1471,116 +1665,116 @@ var NotificationFeedbackKindSchema = z5.enum([
1471
1665
  // anything else — the note carries it.
1472
1666
  ]);
1473
1667
  var SlimOptionSchema = OptionSchema.omit({ html: true });
1474
- var QuestionRowSchema = z5.object({
1668
+ var QuestionRowSchema = z7.object({
1475
1669
  /** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
1476
- id: z5.string(),
1477
- deliveryId: z5.string(),
1478
- entryId: z5.string(),
1670
+ id: z7.string(),
1671
+ deliveryId: z7.string(),
1672
+ entryId: z7.string(),
1479
1673
  /** The decision it waits on; null for an update, which asks nothing. */
1480
- needId: z5.string().nullable(),
1481
- goalIds: z5.array(z5.string()),
1674
+ needId: z7.string().nullable(),
1675
+ goalIds: z7.array(z7.string()),
1482
1676
  /** The name of the work it is about, when the read could word it. */
1483
- goalTitle: z5.string().optional(),
1484
- tokenId: z5.string().optional(),
1485
- name: z5.string(),
1486
- title: z5.string(),
1487
- body: z5.string(),
1488
- select: z5.enum(["one", "many", "rank", "confirm", "text"]),
1489
- options: z5.array(SlimOptionSchema),
1490
- hasPreview: z5.boolean(),
1491
- blocking: z5.boolean(),
1492
- askedAt: z5.string().datetime(),
1677
+ goalTitle: z7.string().optional(),
1678
+ tokenId: z7.string().optional(),
1679
+ name: z7.string(),
1680
+ title: z7.string(),
1681
+ body: z7.string(),
1682
+ select: z7.enum(["one", "many", "rank", "confirm", "text"]),
1683
+ options: z7.array(SlimOptionSchema),
1684
+ hasPreview: z7.boolean(),
1685
+ blocking: z7.boolean(),
1686
+ askedAt: z7.string().datetime(),
1493
1687
  ring: InboxItemSchema.shape.ring,
1494
- onCall: z5.literal(true).optional(),
1495
- sealed: z5.boolean()
1688
+ onCall: z7.literal(true).optional(),
1689
+ sealed: z7.boolean()
1496
1690
  });
1497
- var WorkStateSchema = z5.enum(["ready", "active", "waiting", "done", "cancelled"]);
1498
- var WorkRowSchema = z5.object({
1499
- id: z5.string(),
1500
- parentId: z5.string().nullable(),
1501
- title: z5.string(),
1691
+ var WorkStateSchema = z7.enum(["ready", "active", "waiting", "done", "cancelled"]);
1692
+ var WorkRowSchema = z7.object({
1693
+ id: z7.string(),
1694
+ parentId: z7.string().nullable(),
1695
+ title: z7.string(),
1502
1696
  /** Straight off the Goal. */
1503
1697
  state: WorkStateSchema,
1504
- owner: z5.string().nullable(),
1505
- revision: z5.number().int(),
1698
+ owner: z7.string().nullable(),
1699
+ revision: z7.number().int(),
1506
1700
  /** Open questions on it, counted to 100. */
1507
- waiting: z5.number().int(),
1701
+ waiting: z7.number().int(),
1508
1702
  /** Held by a gate on work that is not done. */
1509
- blocked: z5.boolean(),
1510
- lastProgressAt: z5.string().datetime().nullable(),
1703
+ blocked: z7.boolean(),
1704
+ lastProgressAt: z7.string().datetime().nullable(),
1511
1705
  /** The line written for its newest progress note, else that note's first words. */
1512
- line: z5.string().nullable(),
1706
+ line: z7.string().nullable(),
1513
1707
  /** Work directly under it, counted to 100; the list carries up to 12 of them. */
1514
- children: z5.number().int(),
1515
- createdAt: z5.string().datetime(),
1516
- updatedAt: z5.string().datetime(),
1708
+ children: z7.number().int(),
1709
+ createdAt: z7.string().datetime(),
1710
+ updatedAt: z7.string().datetime(),
1517
1711
  /** When anything at or under it last moved — the order the list is in. */
1518
- activeAt: z5.string().datetime(),
1712
+ activeAt: z7.string().datetime(),
1519
1713
  /** A sealed outcome has no title here; the work's page opens it. */
1520
- sealed: z5.boolean()
1714
+ sealed: z7.boolean()
1521
1715
  });
1522
1716
  var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
1523
1717
  var AgentRowSchema = ComputerRowSchema.extend({
1524
1718
  /** Open questions it is asking the person, over every open card; null when that read failed. */
1525
- asking: z5.number().int().nullable(),
1526
- oldestAskAt: z5.string().datetime().nullable(),
1719
+ asking: z7.number().int().nullable(),
1720
+ oldestAskAt: z7.string().datetime().nullable(),
1527
1721
  /** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
1528
1722
  * many in all among the account's 200 most recently active agent-held live Goals
1529
1723
  * (`agent_holds`); null when that read failed. */
1530
- holds: z5.array(z5.object({ id: z5.string(), title: z5.string() })).nullable(),
1531
- held: z5.number().int().nullable(),
1724
+ holds: z7.array(z7.object({ id: z7.string(), title: z7.string() })).nullable(),
1725
+ held: z7.number().int().nullable(),
1532
1726
  /** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
1533
1727
  * while none has, or when that read failed. */
1534
- cold: z5.string().datetime().nullable(),
1728
+ cold: z7.string().datetime().nullable(),
1535
1729
  /** The newest line of its working log, and when the harness saw it. */
1536
- line: z5.string().nullable(),
1537
- lineAt: z5.string().datetime().nullable()
1730
+ line: z7.string().nullable(),
1731
+ lineAt: z7.string().datetime().nullable()
1538
1732
  });
1539
- var SnapshotSchema = z5.object({
1733
+ var SnapshotSchema = z7.object({
1540
1734
  /** The API's clock, taken before the first read: what a later delta will start from. */
1541
- at: z5.string().datetime(),
1542
- questions: z5.object({
1735
+ at: z7.string().datetime(),
1736
+ questions: z7.object({
1543
1737
  /** The newest 30 open cards, questions before updates. */
1544
- items: z5.array(QuestionRowSchema),
1738
+ items: z7.array(QuestionRowSchema),
1545
1739
  /** Every open question, and apart from them every update, and what was put off. */
1546
- total: z5.number().int(),
1547
- updates: z5.number().int(),
1548
- putOff: z5.number().int()
1740
+ total: z7.number().int(),
1741
+ updates: z7.number().int(),
1742
+ putOff: z7.number().int()
1549
1743
  }).nullable(),
1550
- agents: z5.object({
1744
+ agents: z7.object({
1551
1745
  /** Up to 60, most recently seen first. */
1552
- items: z5.array(AgentRowSchema),
1553
- more: z5.boolean()
1746
+ items: z7.array(AgentRowSchema),
1747
+ more: z7.boolean()
1554
1748
  }).nullable(),
1555
- work: z5.object({
1749
+ work: z7.object({
1556
1750
  /** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
1557
- items: z5.array(WorkRowSchema),
1751
+ items: z7.array(WorkRowSchema),
1558
1752
  /** How much work is behind each of the Work tab's four filters, each counted to 100, read with
1559
1753
  * the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
1560
1754
  * `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
1561
1755
  * is `ready`; Done is `done` or `cancelled`. */
1562
- counts: z5.object({ live: z5.number().int(), waiting: z5.number().int(), notStarted: z5.number().int(), done: z5.number().int() })
1756
+ counts: z7.object({ live: z7.number().int(), waiting: z7.number().int(), notStarted: z7.number().int(), done: z7.number().int() })
1563
1757
  }).nullable(),
1564
- you: z5.object({
1758
+ you: z7.object({
1565
1759
  settings: UserSettingsSchema,
1566
- callable: z5.boolean(),
1760
+ callable: z7.boolean(),
1567
1761
  /** Up to 20 paired computers; null when the roster read failed. */
1568
- computers: z5.array(ComputerRowSchema).nullable()
1762
+ computers: z7.array(ComputerRowSchema).nullable()
1569
1763
  }).nullable()
1570
1764
  });
1571
- var CallRecapSchema = z5.object({
1572
- call: z5.object({
1573
- status: z5.string(),
1574
- startedAt: z5.string(),
1575
- durationMs: z5.number().nullable(),
1576
- agents: z5.array(z5.object({ id: z5.string(), name: z5.string().nullable() }))
1765
+ var CallRecapSchema = z7.object({
1766
+ call: z7.object({
1767
+ status: z7.string(),
1768
+ startedAt: z7.string(),
1769
+ durationMs: z7.number().nullable(),
1770
+ agents: z7.array(z7.object({ id: z7.string(), name: z7.string().nullable() }))
1577
1771
  }),
1578
- topics: z5.array(z5.object({
1579
- goalId: z5.string().uuid(),
1580
- title: z5.string(),
1581
- owner: z5.string(),
1582
- state: z5.string(),
1583
- questions: z5.array(z5.object({ id: z5.string().uuid(), state: z5.string(), title: z5.string() })),
1772
+ topics: z7.array(z7.object({
1773
+ goalId: z7.string().uuid(),
1774
+ title: z7.string(),
1775
+ owner: z7.string(),
1776
+ state: z7.string(),
1777
+ questions: z7.array(z7.object({ id: z7.string().uuid(), state: z7.string(), title: z7.string() })),
1584
1778
  /** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
1585
1779
  * their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
1586
1780
  * summarized like a pre-made option is"), so the row scans like a chosen option and their
@@ -1588,10 +1782,10 @@ var CallRecapSchema = z5.object({
1588
1782
  * anything that is not an answer. */
1589
1783
  /** `about` is the request the line answered (its question), null for words that answered none —
1590
1784
  * the key the screen groups on, so one question is one row however many times it was answered. */
1591
- lines: z5.array(z5.object({ entryId: z5.string().uuid(), words: z5.string(), headline: z5.string().optional(), about: z5.string().nullable().optional() }))
1785
+ lines: z7.array(z7.object({ entryId: z7.string().uuid(), words: z7.string(), headline: z7.string().optional(), about: z7.string().nullable().optional() }))
1592
1786
  })),
1593
- unfiled: z5.array(z5.object({ lineId: z5.string().uuid(), words: z5.string(), atMs: z5.number() })),
1594
- more: z5.object({ lines: z5.number(), entries: z5.number(), topics: z5.number() })
1787
+ unfiled: z7.array(z7.object({ lineId: z7.string().uuid(), words: z7.string(), atMs: z7.number() })),
1788
+ more: z7.object({ lines: z7.number(), entries: z7.number(), topics: z7.number() })
1595
1789
  });
1596
1790
 
1597
1791
  // src/listening.ts
@@ -1619,7 +1813,7 @@ next contact({ wait: false }). Start your own listener now, as a BACKGROUND task
1619
1813
 
1620
1814
  Each line it prints is a Delivery waiting for you: read the Goal, and reply with contact when
1621
1815
  the answer needs a decision. Start it once; if it is already running, do nothing. Then call
1622
- claim_goal once: work handed to you before this session started waits there.
1816
+ contact({ wait: false }) once: work handed to you before this session started waits there.
1623
1817
  PAIGY
1624
1818
  exit 0
1625
1819
  `;
@@ -1648,12 +1842,6 @@ function decideListen(f) {
1648
1842
  message: "Already listening: this session was started by the Paigy harness, whose pump delivers every answer into it as it lands. Nothing to start."
1649
1843
  };
1650
1844
  }
1651
- if (!f.token) {
1652
- return {
1653
- status: "unpaired",
1654
- message: "Not paired: no token in this session's slot, so there is no identity to listen as \u2014 call onboard first."
1655
- };
1656
- }
1657
1845
  if (f.pidAlive && f.pid !== void 0) {
1658
1846
  const connected = f.wake?.ready === true && (f.wake.target.kind !== "codex" || f.wake.target.thread === f.env.CODEX_THREAD_ID);
1659
1847
  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.` };
@@ -1661,7 +1849,7 @@ function decideListen(f) {
1661
1849
  return {
1662
1850
  status: "start",
1663
1851
  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`,
1664
- 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."
1852
+ 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: read the Goal it names with get_goal, and answer with contact. Start it once."
1665
1853
  };
1666
1854
  }
1667
1855
  function listenMark(slot, home = homedir()) {
@@ -1713,6 +1901,7 @@ export {
1713
1901
  AGENT_TOOLS,
1714
1902
  serverInstructions,
1715
1903
  entryWords,
1904
+ entryUnit,
1716
1905
  harnessDriven,
1717
1906
  sessionStartHook,
1718
1907
  withSessionStartHook,