@paigy/mcp 0.40.42 → 0.40.44

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,23 +1,24 @@
1
1
  // ../../packages/schema/dist/index.js
2
- import { z as z13 } from "zod";
2
+ import { z as z14 } from "zod";
3
3
  import { z } from "zod";
4
- import { z as z3 } from "zod";
4
+ import { z as z4 } from "zod";
5
5
  import { z as z2 } from "zod";
6
- import { z as z6 } from "zod";
6
+ import { z as z3 } from "zod";
7
+ import { z as z7 } from "zod";
7
8
  import { zodToJsonSchema } from "zod-to-json-schema";
8
- import { z as z4 } from "zod";
9
9
  import { z as z5 } from "zod";
10
- import { z as z7 } from "zod";
11
- import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
10
+ import { z as z6 } from "zod";
12
11
  import { z as z8 } from "zod";
13
- import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
12
+ import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
14
13
  import { z as z9 } from "zod";
15
- import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
14
+ import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
16
15
  import { z as z10 } from "zod";
17
- import { zodToJsonSchema as zodToJsonSchema5 } from "zod-to-json-schema";
16
+ import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
18
17
  import { z as z11 } from "zod";
19
- import { zodToJsonSchema as zodToJsonSchema6 } from "zod-to-json-schema";
18
+ import { zodToJsonSchema as zodToJsonSchema5 } from "zod-to-json-schema";
20
19
  import { z as z12 } from "zod";
20
+ import { zodToJsonSchema as zodToJsonSchema6 } from "zod-to-json-schema";
21
+ import { z as z13 } from "zod";
21
22
  import { zodToJsonSchema as zodToJsonSchema7 } from "zod-to-json-schema";
22
23
  var OPTIONS_MIN = 2;
23
24
  var OPTIONS_MAX = 6;
@@ -97,29 +98,43 @@ function capRefusals(text, units2, field) {
97
98
  function refuseOverCaps(text, units2, field, ctx) {
98
99
  for (const { path, message, ...params } of capRefusals(text, units2, field)) ctx.addIssue({ code: z2.ZodIssueCode.custom, path, message, params });
99
100
  }
101
+ var SHIPPED = ["phone", "calls", "server", "store", "agents"];
102
+ var CAUSES_MAX = 10;
103
+ var shipped = z3.enum(SHIPPED, {
104
+ errorMap: () => ({ message: `shipped_unknown: one of ${SHIPPED.join(", ")}.` })
105
+ }).optional().describe(
106
+ "Only when this reports a result that LANDED somewhere the person can see it: phone (an app update reached their phone), calls (the voice bot), server, store (the App Store) or agents (the MCP or SDK). Leave it out for work in progress."
107
+ );
108
+ var causedBy = z3.array(
109
+ z3.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "cause_not_found: an Entry id, or the short id get_goals prints for the person's words.")
110
+ ).min(1).max(CAUSES_MAX, `too_many_causes: at most ${CAUSES_MAX}.`).optional().describe(
111
+ `The person's own words this acts on: the ids (or the short ids get_goals prints) of their answers or notes that led to it, at most ${CAUSES_MAX}. Their app shows each result under the words that caused it, so name every one this follows from. An id that is not their words is refused by name (cause_not_found, cause_ambiguous) and nothing is written.`
112
+ );
100
113
  var COMPLETION_MESSAGE_MAX = 50;
101
- var CompletionSchema = z3.object({
102
- goalId: z3.string().uuid().describe("The existing Goal to complete."),
103
- message: z3.string().trim().min(1).max(COMPLETION_MESSAGE_MAX, "completion_message_too_long: at most 50 characters; put detail in units.").describe("Completion headline, at most 50 characters."),
104
- units: z3.array(UnitInputSchema.extend({
105
- title: z3.string().trim().min(1).max(UNIT_TITLE_MAX),
106
- body: z3.string().trim().min(1).max(UNIT_BODY_MAX)
114
+ var CompletionSchema = z4.object({
115
+ goalId: z4.string().uuid().describe("The existing Goal to complete."),
116
+ message: z4.string().trim().min(1).max(COMPLETION_MESSAGE_MAX, "completion_message_too_long: at most 50 characters; put detail in units.").describe("Completion headline, at most 50 characters: the one thing that would go wrong or stay unclear later if they missed it."),
117
+ units: z4.array(UnitInputSchema.extend({
118
+ title: z4.string().trim().min(1).max(UNIT_TITLE_MAX),
119
+ body: z4.string().trim().min(1).max(UNIT_BODY_MAX)
107
120
  })).max(UNITS_MAX).optional().describe("Suggest initial state, changes, current state with verification and limits, and next steps / leftover work with its owner when useful. One idea per unit; omit unnecessary sections."),
108
- reply: z3.string().trim().min(1).max(OPTION_LABEL_MAX).describe("One acknowledgement in the person's voice, at most 50 characters. Acknowledgement alone requests no follow-up and does not claim they tested the work."),
109
- options: z3.array(z3.object({
110
- label: z3.string().trim().min(1).max(OPTION_LABEL_MAX),
111
- hint: z3.string().trim().min(1).max(OPTION_HINT_MAX)
112
- }).strict()).max(5).optional().describe("Optional next steps the person may initiate: label (50 characters) and consequence in hint (200). Selecting one acknowledges the report and requests that step from its author; it never reopens this Goal.")
121
+ reply: z4.string().trim().min(1).max(OPTION_LABEL_MAX).describe("One acknowledgement in the person's voice, at most 50 characters. Acknowledgement alone requests no follow-up and does not claim they tested the work."),
122
+ options: z4.array(z4.object({
123
+ label: z4.string().trim().min(1).max(OPTION_LABEL_MAX),
124
+ hint: z4.string().trim().min(1).max(OPTION_HINT_MAX)
125
+ }).strict()).max(5).optional().describe("Optional next steps the person may initiate: label (50 characters) and consequence in hint (200). Selecting one acknowledges the report and requests that step from its author; it never reopens this Goal."),
126
+ causedBy,
127
+ shipped
113
128
  }).strict();
114
- var CompleteChangeSchema = CompletionSchema.extend({ kind: z3.literal("complete") });
129
+ var CompleteChangeSchema = CompletionSchema.omit({ causedBy: true, shipped: true }).extend({ kind: z4.literal("complete") });
115
130
  var ASKING_RULES = `HOW TO ASK:
116
131
  1. One question per object in \`questions\`, so each is answered on its own.
117
132
  2. \`goalId\` names the existing Goal it is about (create one with manage_goals first); the answer comes back there.
118
- 3. \`workItBlocks\` only when you are truly blocked: it asks for a call.
133
+ 3. They asked you to call? \`userExplicitlyRequested:"call"\`; \`workItBlocks\` rings when blocked.
119
134
  4. No while-loops. While any Call is open, even one your question joined from their items, keep receiving through empty windows; otherwise stop at one: the answer reaches you on the Goal. Yield only with a listener or wakeup.
120
135
  5. One question, one row: never re-send one still waiting; take back one that no longer matters with \`withdrawQuestionIds\`.
121
136
  6. Read the Goal before asking; never re-ask what was answered or shipped. \`replied\` means they chose no option: it is still yours to settle.
122
- 7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other". \`pickMode\`: one (default), many, rank.
137
+ 7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other".
123
138
  8. A diagnosis says when, why and how it happens, then proposes one fix.
124
139
  9. Give a report its \`reply\`: one line in THEIR voice for THAT report, so you learn whether they verified it or only received it. Required when it is delivered or its Goal is done.`;
125
140
  function draft2020(node) {
@@ -151,25 +166,68 @@ function mcpInputSchema(s) {
151
166
  delete schema.$schema;
152
167
  return draft2020(schema);
153
168
  }
154
- var questionHandle = z4.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
155
- var NodeRef = z4.object({
156
- type: z4.enum(["goal", "question"]),
157
- id: z4.string().uuid()
169
+ var questionHandle = z5.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
170
+ var NodeRef = z5.object({
171
+ type: z5.enum(["goal", "question"]),
172
+ id: z5.string().uuid()
158
173
  }).strict().describe("A Goal or a Question, by its id.");
159
- var BlockedAction = z4.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
160
- var reason = z4.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
174
+ var BlockedAction = z5.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
175
+ var reason = z5.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
161
176
  var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
162
177
  var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
163
- var userExplicitlyRequested = z4.enum(["any", "call"]).optional().describe(
164
- `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.`
178
+ var userExplicitlyRequested = z5.enum(["any", "call"]).optional().describe(
179
+ 'What the person asked for 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. ALSO "any" whenever this message directly answers a question they asked you, wherever they asked it -- on a call, in a reply, in passing: their question is the request, so it is delivered every time, even with no pending question of theirs on file and even if they never said to send it. A correction of something you or another agent told them is such an answer. Never set it to make a message louder. This is the field that rings a phone: "call" here is how you call them when they asked you to, and `help` has the whole mechanism.'
165
180
  );
166
- var units = z4.array(UnitInputSchema).optional().describe(
167
- `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. 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.`
181
+ var units = z5.array(UnitInputSchema).optional().describe(
182
+ `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: context they may open, never something they must hear. Leave it out when the main text says it all. 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.`
168
183
  );
169
- var UpdateInputSchema = z4.object({
170
- message: z4.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
171
- goalIds: z4.array(z4.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
184
+ var KIND_IDS = { "handing": "none", "replaced-by": "goal", "blocked-by": "node", "asking": "none", "found": "none" };
185
+ var KIND_GRAMMAR = "handing, replaced-by <goalId>, blocked-by <goalId|questionId>, asking, found";
186
+ var uuid = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
187
+ var handle = /^[0-9a-fA-F-]{8,36}$/;
188
+ function refuseKind(u, ctx) {
189
+ if (!u.kind) return;
190
+ const refuse = (refusal, kind, message) => ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["kind"], params: { refusal, kind }, message });
191
+ const [name, ...rest] = u.kind.trim().split(/\s+/);
192
+ const shape = KIND_IDS[name];
193
+ if (!shape) {
194
+ refuse("kind_unknown", name, `kind_unknown: "${name}" is not a kind. One of: ${KIND_GRAMMAR}.`);
195
+ return;
196
+ }
197
+ const id2 = rest.join(" ");
198
+ if (shape === "none" && id2) {
199
+ refuse("kind_takes_no_id", name, `kind_takes_no_id: "${name}" is the whole kind, so drop the "${id2}" after it.`);
200
+ }
201
+ if (shape !== "none" && !(shape === "goal" ? uuid.test(id2) : handle.test(id2))) {
202
+ refuse("kind_needs_id", name, shape === "goal" ? `kind_needs_id: "${name}" names the Goal it is about, as "${name} <goalId>".` : `kind_needs_id: "${name}" names the Goal or Question it is about, as "${name} <goalId|questionId>".`);
203
+ }
204
+ if (name === "asking") {
205
+ refuse("kind_is_a_question", name, 'kind_is_a_question: "asking" owes them an answer, so send it in `questions`, where it opens one.');
206
+ }
207
+ if (u.userExplicitlyRequested) {
208
+ refuse("kind_not_for_the_person", name, `kind_not_for_the_person: "${name}" is written for the other agents on the Goal, and userExplicitlyRequested delivers these words to the person. Split it: one message for them, one for the agent.`);
209
+ }
210
+ }
211
+ var UpdateInputSchema = z5.object({
212
+ // ONE LOAD-BEARING POINT PER REPORT (owner, 2026-10-27 14:10 on call, Goal 1b1750e9, settling
213
+ // #3633's pacing: "one point at a time is better, but make sure that it's [load-]bearing ... if
214
+ // there is a lot of key points that I need to be aware of ... meaning something bad would happen
215
+ // if I didn't know them or I wouldn't be able to understand something in the future ... They
216
+ // should go separately. Basically, it should be like one load-bearing thing per report").
217
+ //
218
+ // The call's half of that ruling shipped in #3771: the talker settles an item only once the
219
+ // transcript shows he heard its load-bearing point. This is the author's half, and without it the
220
+ // talker has to guess which of a report's parts was load-bearing. Measured 2026-11-01 on prod:
221
+ // 458 of 866 reports in 21 days carried two or more units, and since the pacing shipped on 10-23
222
+ // only 18 of 72 units that reached a call were ever said -- so a point written as a unit has about
223
+ // a one-in-four chance of being heard, and one ok after the headline settles the whole report.
224
+ // Hence the wording, not a `mustHear` flag (the-heard-record.md P3): he asked for less mechanism
225
+ // and more judgment from the agent, so a point he must not miss is simply its own report.
226
+ message: z5.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters: the one thing that would go wrong or stay unclear later if they missed it, said as what changed for them, in their words ("The screen reader speaks Spanish too", not "Merged #3896"). A second point like that is its own update; the rest goes in units.`),
227
+ goalIds: z5.array(z5.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
172
228
  units,
229
+ causedBy,
230
+ shipped,
173
231
  // A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
174
232
  // until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
175
233
  // still waits to be acknowledged, and it carries the ONE tap that closes it. Never a decision: the
@@ -190,23 +248,30 @@ var UpdateInputSchema = z4.object({
190
248
  // `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
191
249
  // MECHANISM). It rides the door as the report's single stored option (`storedOption`,
192
250
  // `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
193
- reply: z4.string().trim().min(1).describe(
251
+ reply: z5.string().trim().min(1).describe(
194
252
  `What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer. The reply is required for requested delivery and completed Goals; ordinary progress may omit it.`
195
253
  ).optional(),
254
+ // THE KIND, WHICH WAS THE FIRST LINE OF THE MESSAGE UNTIL NOW (`KIND_IDS` above). Optional on
255
+ // purpose: every agent writing updates today sends none, and a door that refused them would turn a
256
+ // convention nobody was obliged to follow into a 400 on live traffic.
257
+ kind: z5.string().trim().min(1).optional().describe(
258
+ `What an agent writing on ANOTHER agent's Goal expects of its reader, as one token: ${KIND_GRAMMAR}. \`handing\` the work is yours now, nothing is asked; \`replaced-by <goalId>\` this Goal is dead and that one carries its work; \`blocked-by <goalId|questionId>\` I cannot go on until that moves, no action unless you own it; \`found\` evidence on your Goal, nothing is asked and nothing has changed. \`asking\` is a question, so it belongs in \`questions\`, not here. It is written for the other agents on the Goal, never the person, so it does not go with userExplicitlyRequested; leave it out for your own progress. Refused by name (kind_unknown, kind_needs_id, kind_takes_no_id, kind_is_a_question, kind_not_for_the_person).`
259
+ ),
196
260
  userExplicitlyRequested
197
261
  }).strict().superRefine((u, ctx) => {
198
262
  if (u.userExplicitlyRequested && !u.reply) {
199
263
  ctx.addIssue({
200
- code: z4.ZodIssueCode.custom,
264
+ code: z5.ZodIssueCode.custom,
201
265
  path: ["reply"],
202
266
  params: { refusal: "reply_required" },
203
267
  message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
204
268
  });
205
269
  }
206
270
  refuseOverCaps(u.message, u.units, "message", ctx);
271
+ refuseKind(u, ctx);
207
272
  if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
208
273
  ctx.addIssue({
209
- code: z4.ZodIssueCode.custom,
274
+ code: z5.ZodIssueCode.custom,
210
275
  path: ["reply"],
211
276
  params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX },
212
277
  message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX}. It is a button's words, so say it in a breath.`
@@ -214,31 +279,31 @@ var UpdateInputSchema = z4.object({
214
279
  }
215
280
  });
216
281
  var questionFields = {
217
- question: z4.string().trim().min(1).describe(
282
+ question: z5.string().trim().min(1).describe(
218
283
  `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.`
219
284
  ),
220
- goalId: z4.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
285
+ goalId: z5.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
221
286
  units,
222
- options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own and differs from the others (duplicate_option_label), at most 50 characters; a hint, at most 200. They can always answer in their own words, so no 'Other' or 'None of these'."),
287
+ options: z5.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own and differs from the others (duplicate_option_label), at most 50 characters; a hint, at most 200. They can always answer in their own words, so no 'Other' or 'None of these'."),
223
288
  // HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
224
289
  // brain_prompts.md §1: structured agent questions need no model). The app draws all three.
225
- pickMode: z4.enum(["one", "many", "rank"]).optional().describe(
290
+ pickMode: z5.enum(["one", "many", "rank"]).optional().describe(
226
291
  '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; "many" and "rank" need at least two (too_few_options).'
227
292
  ),
228
293
  // WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
229
294
  // blocks work asks for a call (the person's settings and ring cooldowns still decide).
230
- workItBlocks: z4.array(z4.object({ blocked: NodeRef, action: BlockedAction, reason: reason.optional() }).strict().refine(actionFits, ACTION_FITS)).min(1).max(10).optional().describe(
295
+ workItBlocks: z5.array(z5.object({ blocked: NodeRef, action: BlockedAction, reason: reason.optional() }).strict().refine(actionFits, ACTION_FITS)).min(1).max(10).optional().describe(
231
296
  "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."
232
297
  ),
233
298
  userExplicitlyRequested
234
299
  };
235
300
  var refineQuestion = (q, ctx) => {
236
301
  refuseOverCaps(q.question, q.units, "question", ctx);
237
- if (q.pickMode && !q.options) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
302
+ if (q.pickMode && !q.options) ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
238
303
  refuseThinOptions(q, ctx);
239
304
  const ids = (q.options ?? []).map((o) => o.id);
240
305
  if (ids.some((id2) => id2) && (ids.some((id2) => !id2) || new Set(ids).size !== ids.length)) {
241
- ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
306
+ ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
242
307
  }
243
308
  };
244
309
  var MIN_LIST_OPTIONS = 2;
@@ -246,7 +311,7 @@ function refuseThinOptions(q, ctx) {
246
311
  const options = q.options ?? [];
247
312
  if ((q.pickMode === "many" || q.pickMode === "rank") && options.length < MIN_LIST_OPTIONS) {
248
313
  ctx.addIssue({
249
- code: z4.ZodIssueCode.custom,
314
+ code: z5.ZodIssueCode.custom,
250
315
  path: ["options"],
251
316
  params: { refusal: "too_few_options", length: options.length, min: MIN_LIST_OPTIONS },
252
317
  message: `too_few_options: pickMode "${q.pickMode}" needs at least ${MIN_LIST_OPTIONS} options; this question has ${options.length}. Add the other choices, or leave pickMode out.`
@@ -261,67 +326,67 @@ function refuseThinOptions(q, ctx) {
261
326
  return;
262
327
  }
263
328
  ctx.addIssue({
264
- code: z4.ZodIssueCode.custom,
329
+ code: z5.ZodIssueCode.custom,
265
330
  path: ["options", index, "label"],
266
331
  params: { refusal: "duplicate_option_label", index, first },
267
332
  message: `duplicate_option_label: options[${index}] repeats the label of options[${first}]. Each option names a different choice.`
268
333
  });
269
334
  });
270
335
  }
271
- var QuestionInputSchema = z4.object({
272
- questionId: z4.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."),
336
+ var QuestionInputSchema = z5.object({
337
+ questionId: z5.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."),
273
338
  ...questionFields
274
339
  }).strict().superRefine(refineQuestion);
275
- var QuestionWireSchema = z4.object({ questionId: z4.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
276
- var AnswerInputSchema = z4.object({
340
+ var QuestionWireSchema = z5.object({ questionId: z5.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
341
+ var AnswerInputSchema = z5.object({
277
342
  questionId: questionHandle.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
278
- answer: z4.object({
279
- text: z4.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
280
- selectedOptionIds: z4.array(z4.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).")
343
+ answer: z5.object({
344
+ text: z5.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
345
+ selectedOptionIds: z5.array(z5.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).")
281
346
  }).strict(),
282
347
  units
283
348
  }).strict().superRefine((a, ctx) => refuseOverCaps(a.answer.text, a.units, "text", ctx));
284
- var QuestionDependencySchema = z4.object({
349
+ var QuestionDependencySchema = z5.object({
285
350
  questionId: questionHandle.describe("A Question you asked."),
286
- change: z4.enum(["add", "remove"]),
351
+ change: z5.enum(["add", "remove"]),
287
352
  blocked: NodeRef,
288
353
  action: BlockedAction,
289
354
  reason: reason.optional()
290
355
  }).strict().refine(actionFits, ACTION_FITS);
291
356
  var sendFields = {
292
- completions: z4.array(CompletionSchema).min(1).max(20).optional().describe(
357
+ completions: z5.array(CompletionSchema).min(1).max(20).optional().describe(
293
358
  "COMPLETIONS close and report in one operation, the same contract as manage_goals kind:complete: closure and the report commit together or neither does. Required children and dependencies must already be finished. options suggest next steps: a tap acknowledges the report and requests that step, and never reopens the Goal (follow-up work needs a new one). No reviewer rejudges it: say only what your evidence supports. Ordinary updates never close a Goal."
294
359
  ),
295
- updates: z4.array(UpdateInputSchema).min(1).max(20).optional().describe(
296
- "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, which the person is not told. Every update reaches the other agents that wrote on its Goal in the last hour: to tell another agent something, write it as an update on that agent's Goal."
360
+ updates: z5.array(UpdateInputSchema).min(1).max(20).optional().describe(
361
+ "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, which the person is not told. Every update reaches the other agents that wrote on its Goal in the last hour: to tell another agent something, write it as an update on that agent's Goal, with its `kind`.\n\nONE LOAD-BEARING POINT PER REPORT: the message is the point they must not miss; a second point that would go wrong or stay unclear later if they missed it is its own update, never a unit."
297
362
  ),
298
- answers: z4.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
299
- withdrawQuestionIds: z4.array(questionHandle).min(1).max(10).optional().describe(
363
+ answers: z5.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
364
+ withdrawQuestionIds: z5.array(questionHandle).min(1).max(10).optional().describe(
300
365
  "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."
301
366
  ),
302
- bumpQuestionIds: z4.array(questionHandle).min(1).max(10).optional().describe(
367
+ bumpQuestionIds: z5.array(questionHandle).min(1).max(10).optional().describe(
303
368
  "Your own questions still waiting on the person that you want them to notice again. Each moves back to the top of its card with one new notification; nothing is resent and no second card is made."
304
369
  ),
305
- questionDependencies: z4.array(QuestionDependencySchema).min(1).max(20).optional().describe(
370
+ questionDependencies: z5.array(QuestionDependencySchema).min(1).max(20).optional().describe(
306
371
  "Add or remove what a Question you asked holds up, after asking it."
307
372
  )
308
373
  };
309
- var ContactSchema = z4.object({
374
+ var ContactSchema = z5.object({
310
375
  ...sendFields,
311
- questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object, each sent exactly as written, with no reading in between."),
376
+ questions: z5.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object, each sent exactly as written, with no reading in between."),
312
377
  /** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
313
- ackEventIds: z4.array(z4.string().uuid()).min(1).max(100).optional().describe(
378
+ ackEventIds: z5.array(z5.string().uuid()).min(1).max(100).optional().describe(
314
379
  "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."
315
380
  ),
316
- wait: z4.boolean().optional().describe(
381
+ wait: z5.boolean().optional().describe(
317
382
  "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`."
318
383
  )
319
384
  }).strict();
320
385
  var sendsAnything = (c) => !!(c.completions?.length || c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.bumpQuestionIds?.length || c.questionDependencies?.length);
321
- var ContactWireSchema = z4.object({
322
- operationId: z4.string().uuid(),
386
+ var ContactWireSchema = z5.object({
387
+ operationId: z5.string().uuid(),
323
388
  ...sendFields,
324
- questions: z4.array(QuestionWireSchema).min(1).max(20).optional()
389
+ questions: z5.array(QuestionWireSchema).min(1).max(20).optional()
325
390
  }).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
326
391
  var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
327
392
  var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals, and what a question holds up. Every Goal named must exist: an unknown one refuses the whole contact. Each item applies on its own: read `results`, where a failed one names why.\n\n" + ASKING_RULES + `
@@ -331,13 +396,14 @@ LENGTH: the question, message or answer is the one point, at most ${ASK_MAX} cha
331
396
  UPDATES ask nothing. One reaches the person only when they asked for it, it answers something they said, or its Goal is done; else it is kept as progress. One that reaches them joins an open report card or a live call.
332
397
 
333
398
  RECEIVING: contact({}) waits ~45 s; contact({wait:false}) returns \`events\` at once. Confirm the ones you handled with contact({ackEventIds}).`;
334
- var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
335
- var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "calls"];
399
+ var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session; `/mcp__paigy__organize` sets up a person\'s work as a board.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Shape your work as projects with steps. Create a project under its area (`create` with `parentGoalId` = the area), then one child Goal per step; complete each step as it lands. A top-level Goal that has children is an area, not a project: never hang steps straight off a top-level Goal. The person\'s Work tab shows a project\'s progress as its steps, "3 of 5 steps done"; a project with no steps shows none. When you touch an older Goal of yours that sits flat or under a finished project, move it into that shape (`move`). A flat Goal is still valid; never reshape another agent\'s Goals.\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Organize\n\nWhen the person asks you to set up or organize their work ("set up my project", "help me manage all this", several products or parts to juggle), turn it into a board they can run like a team. Their Work tab shows it.\n\n- Learn the work first: read the repo and their Goals (`get_goals`, `search`). Ask only what you cannot read, at most three questions, each its own question.\n- The shape: an area per product or business (a top-level Goal); under it a project per part that moves on its own (landing page, graphics, web app, mobile app, launch); under each project its steps, three to seven, each an outcome someone can finish and check.\n- Propose before you build. Create one Goal for the setup itself, then ask one question on it: the tree in `units` (one unit per project: its title, then its steps), options "Build it" and "Change it: I\'ll say how". Create nothing else until they answer.\n- On "Build it", one `manage_goals` call per level, since a parent\'s id comes back in `results[i].goalId`: the areas, then their projects (`parentGoalId`), then every step; then a `dependency` where a project waits on another (web on the design). Never copy work that already has a Goal: yours move into the tree (`move`); another agent\'s stay where they are, named in the proposal. Then complete the setup Goal.\n- Report how to run it, and hand over one brief per project for the person to paste as that session\'s first message. Each brief names the project\'s Goal (title and id) and says: call `listen` and keep the listener running; receive with `contact({ wait: false })` at startup and after every wake; work the steps in order and complete each as it lands; report progress as updates on the project; ask anything you are blocked on through Paigy, as a question on that step\'s Goal (`workItBlocks` only when it truly stops you), never in the terminal.\n- One session per project, plus one lead session that keeps the board current. Any agent, Claude, Codex or another, can pick up any Goal; `check_activity` shows who is on what. A subagent inside a session shares that session\'s identity and listener: it may ask through Paigy, but never starts a listener of its own.\n- Keep it current as you work: complete each step as it lands and add a step when new work appears. Never reshape another agent\'s Goals.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
400
+ var SKILL_MARK = "<!-- paigy-skill";
401
+ var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "organize", "calls"];
336
402
  var unknownTopic = (topic) => `unknown_topic: ${JSON.stringify(topic)} is not a section of the guide. Topics: ${HELP_TOPICS.join(", ")}; or omit topic for the whole guide.`;
337
- var HelpSchema = z5.object({
338
- topic: z5.enum(HELP_TOPICS, { errorMap: (_issue, ctx) => ({ message: unknownTopic(ctx.data) }) }).optional().describe("One section of the guide. Omit it for the whole guide.")
403
+ var HelpSchema = z6.object({
404
+ topic: z6.enum(HELP_TOPICS, { errorMap: (_issue, ctx) => ({ message: unknownTopic(ctx.data) }) }).optional().describe("One section of the guide. Omit it for the whole guide.")
339
405
  }).strict();
340
- var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals and calls. Pass a topic for one section. Read-only; needs no setup.";
406
+ var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals, organizing work, and calls. Pass a topic for one section. Read-only; needs no setup.";
341
407
  function section(topic) {
342
408
  const parts = GUIDE.split(/^(?=## )/m);
343
409
  const found = parts.find((p) => p.slice(3).toLowerCase().startsWith(topic));
@@ -358,7 +424,8 @@ var AGENT_PROMPTS = [
358
424
  description: "How to use Paigy: the whole guide, or one topic of it.",
359
425
  arguments: [{ name: "topic", description: `One section: ${HELP_TOPICS.join(", ")}. Omit it for the whole guide.`, required: false }]
360
426
  },
361
- { name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." }
427
+ { name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." },
428
+ { name: "organize", description: "Set up the person's work as a board: areas, projects and steps, proposed on a card before anything is built." }
362
429
  ];
363
430
  function promptText(name, facts) {
364
431
  if (name === "help") {
@@ -381,6 +448,11 @@ ${helpView(parsed.data).guide}`;
381
448
  ${steps.map((s, i) => `${i + 1}. ${s}`).join("\n")}
382
449
 
383
450
  The whole guide is the \`help\` tool (or /mcp__paigy__help).`;
451
+ }
452
+ if (name === "organize") {
453
+ return `Help me organize my work with Paigy: propose a board of areas, projects and steps, and build it once I approve. Follow this:
454
+
455
+ ${helpView({ topic: "organize" }).guide}`;
384
456
  }
385
457
  throw new Error(`unknown_prompt: ${JSON.stringify(name)}. Prompts: ${AGENT_PROMPTS.map((p) => p.name).join(", ")}.`);
386
458
  }
@@ -388,13 +460,13 @@ function promptResult(name, facts) {
388
460
  const text = promptText(name, facts);
389
461
  return { description: AGENT_PROMPTS.find((p) => p.name === name).description, messages: [{ role: "user", content: { type: "text", text } }] };
390
462
  }
391
- var CreateGoalSchema = z6.object({
392
- outcome: z6.string().trim().min(1).max(1e4),
463
+ var CreateGoalSchema = z7.object({
464
+ outcome: z7.string().trim().min(1).max(1e4),
393
465
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
394
466
  * rings"). Omit it and the brain writes one at admission from the outcome. */
395
- title: z6.string().trim().min(1).max(80).optional(),
396
- ownerParticipant: z6.string().trim().min(1).optional(),
397
- idempotencyKey: z6.string().trim().min(1).max(200),
467
+ title: z7.string().trim().min(1).max(80).optional(),
468
+ ownerParticipant: z7.string().trim().min(1).optional(),
469
+ idempotencyKey: z7.string().trim().min(1).max(200),
398
470
  /** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
399
471
  * didn't get created as a child goal of the voice UI goal, which is how it should've
400
472
  * worked"). It could not have been: this door took no parent, so the only route was
@@ -402,63 +474,63 @@ var CreateGoalSchema = z6.object({
402
474
  * took the short one. The hierarchy has been modelled since Goals existed and had been used
403
475
  * ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
404
476
  * (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
405
- parentGoalId: z6.string().uuid().optional(),
477
+ parentGoalId: z7.string().uuid().optional(),
406
478
  /** The repository or project identifier this Goal belongs to (#2280) — e.g. "owner/repo" or
407
479
  * repo name. Delegated work inherits this from its parent Goal when omitted. */
408
- repo: z6.string().trim().min(1).max(200).optional()
480
+ repo: z7.string().trim().min(1).max(200).optional()
409
481
  });
410
- var UpdateGoalSchema = z6.object({
411
- revision: z6.number().int().positive(),
412
- changes: z6.object({
413
- outcome: z6.string().trim().min(1).max(1e4).optional(),
482
+ var UpdateGoalSchema = z7.object({
483
+ revision: z7.number().int().positive(),
484
+ changes: z7.object({
485
+ outcome: z7.string().trim().min(1).max(1e4).optional(),
414
486
  /** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
415
487
  * brain writes one at admission; this is the owner saying it better. Null clears it. */
416
- title: z6.string().trim().min(1).max(80).nullable().optional(),
417
- ownerParticipant: z6.string().trim().min(1).optional(),
418
- parentGoalId: z6.string().uuid().nullable().optional(),
419
- dependencies: z6.array(z6.object({ goalId: z6.string().uuid(), gate: z6.enum(["start", "finish"]) }).strict()).optional(),
420
- children: z6.array(z6.object({ outcome: z6.string().trim().min(1).max(1e4), ownerParticipant: z6.string().trim().min(1), gate: z6.enum(["start", "finish"]).optional() }).strict()).optional(),
421
- state: z6.enum(["active", "done", "cancelled"]).optional(),
422
- progress: z6.string().trim().min(1).max(1e4).optional(),
423
- reviewed: z6.literal(true).optional(),
424
- dueAt: z6.string().datetime({ offset: true }).nullable().optional()
488
+ title: z7.string().trim().min(1).max(80).nullable().optional(),
489
+ ownerParticipant: z7.string().trim().min(1).optional(),
490
+ parentGoalId: z7.string().uuid().nullable().optional(),
491
+ dependencies: z7.array(z7.object({ goalId: z7.string().uuid(), gate: z7.enum(["start", "finish"]) }).strict()).optional(),
492
+ children: z7.array(z7.object({ outcome: z7.string().trim().min(1).max(1e4), ownerParticipant: z7.string().trim().min(1), gate: z7.enum(["start", "finish"]).optional() }).strict()).optional(),
493
+ state: z7.enum(["active", "done", "cancelled"]).optional(),
494
+ progress: z7.string().trim().min(1).max(1e4).optional(),
495
+ reviewed: z7.literal(true).optional(),
496
+ dueAt: z7.string().datetime({ offset: true }).nullable().optional()
425
497
  }).strict().refine((v) => Object.keys(v).length > 0),
426
- reason: z6.string().trim().min(1).max(2e3),
427
- operationId: z6.string().uuid().optional()
498
+ reason: z7.string().trim().min(1).max(2e3),
499
+ operationId: z7.string().uuid().optional()
428
500
  }).strict();
429
- var changedGoal = z6.string().uuid().describe("The Goal to change, as a read shows it.");
430
- var goalTitle = z6.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.");
501
+ var changedGoal = z7.string().uuid().describe("The Goal to change, as a read shows it.");
502
+ var goalTitle = z7.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.");
431
503
  var GOAL_OUTCOME_MAX = 500;
432
- var goalOutcome = z6.string().trim().min(1).max(GOAL_OUTCOME_MAX, `outcome_too_long: a Goal's outcome is one paragraph, at most ${GOAL_OUTCOME_MAX} characters; split the rest into child Goals (create with parentGoalId).`).describe(`The desired result, one paragraph of at most ${GOAL_OUTCOME_MAX} characters. More than that is more work: create child Goals under it (parentGoalId). There is no third description field.`);
433
- var CreateChange = z6.object({
434
- kind: z6.literal("create"),
504
+ var goalOutcome = z7.string().trim().min(1).max(GOAL_OUTCOME_MAX, `outcome_too_long: a Goal's outcome is one paragraph, at most ${GOAL_OUTCOME_MAX} characters; split the rest into child Goals (create with parentGoalId).`).describe(`The desired result, one paragraph of at most ${GOAL_OUTCOME_MAX} characters. More than that is more work: create child Goals under it (parentGoalId). There is no third description field.`);
505
+ var CreateChange = z7.object({
506
+ kind: z7.literal("create"),
435
507
  title: goalTitle,
436
508
  outcome: goalOutcome,
437
- ownerId: z6.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."),
438
- parentGoalId: z6.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
439
- sourceEntryIds: z6.array(z6.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
509
+ ownerId: z7.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."),
510
+ parentGoalId: z7.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root. A project with several parts: create it, then one child per step under it; the person's Work tab counts a project's progress in those steps."),
511
+ sourceEntryIds: z7.array(z7.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
440
512
  }).strict();
441
- var EditChange = z6.object({ kind: z6.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
442
- var StateChange = z6.object({ kind: z6.literal("state"), goalId: changedGoal, state: z6.enum(["open", "completed", "canceled"]).describe(
513
+ var EditChange = z7.object({ kind: z7.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
514
+ var StateChange = z7.object({ kind: z7.literal("state"), goalId: changedGoal, state: z7.enum(["open", "completed", "canceled"]).describe(
443
515
  "completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
444
516
  ) }).strict();
445
- var AssignChange = z6.object({ kind: z6.literal("assign"), goalId: changedGoal, ownerId: z6.string().trim().min(1).describe("The new owner's participant.") }).strict();
446
- var DeferChange = z6.object({ kind: z6.literal("defer"), goalId: changedGoal, until: z6.string().datetime({ offset: true }).nullable().describe(
517
+ var AssignChange = z7.object({ kind: z7.literal("assign"), goalId: changedGoal, ownerId: z7.string().trim().min(1).describe("The new owner's participant.") }).strict();
518
+ var DeferChange = z7.object({ kind: z7.literal("defer"), goalId: changedGoal, until: z7.string().datetime({ offset: true }).nullable().describe(
447
519
  "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."
448
520
  ) }).strict();
449
- var MoveChange = z6.object({ kind: z6.literal("move"), goalId: changedGoal, parentGoalId: z6.string().uuid().nullable().describe(
521
+ var MoveChange = z7.object({ kind: z7.literal("move"), goalId: changedGoal, parentGoalId: z7.string().uuid().nullable().describe(
450
522
  "Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
451
523
  ) }).strict();
452
- var DependencyChange = z6.object({
453
- kind: z6.literal("dependency"),
454
- change: z6.enum(["add", "remove"]),
524
+ var DependencyChange = z7.object({
525
+ kind: z7.literal("dependency"),
526
+ change: z7.enum(["add", "remove"]),
455
527
  goalId: changedGoal,
456
- dependsOnGoalId: z6.string().uuid().describe("The Goal it waits on."),
457
- action: z6.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
458
- reason: z6.string().trim().min(1).max(250).optional().describe("Why, at most 250 characters, as an explanation, never as policy.")
528
+ dependsOnGoalId: z7.string().uuid().describe("The Goal it waits on."),
529
+ action: z7.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
530
+ reason: z7.string().trim().min(1).max(250).optional().describe("Why, at most 250 characters, as an explanation, never as policy.")
459
531
  }).strict();
460
- var read = { revision: z6.number().int().positive().optional() };
461
- var GoalChangeSchema = z6.discriminatedUnion("kind", [
532
+ var read = { revision: z7.number().int().positive().optional() };
533
+ var GoalChangeSchema = z7.discriminatedUnion("kind", [
462
534
  CreateChange,
463
535
  CompleteChangeSchema.extend(read),
464
536
  EditChange.extend(read),
@@ -470,226 +542,233 @@ var GoalChangeSchema = z6.discriminatedUnion("kind", [
470
542
  ]);
471
543
  var editNames = (changes, ctx) => changes.forEach((c, i) => {
472
544
  if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
473
- ctx.addIssue({ code: z6.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
545
+ ctx.addIssue({ code: z7.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
474
546
  }
475
547
  });
476
- var ManageGoalsSchema = z6.object({
477
- requestId: z6.string().uuid().optional(),
478
- changes: z6.array(GoalChangeSchema).min(1).max(50)
548
+ var ManageGoalsSchema = z7.object({
549
+ requestId: z7.string().uuid().optional(),
550
+ changes: z7.array(GoalChangeSchema).min(1).max(50)
479
551
  }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
480
- var ManageGoalsToolSchema = z6.object({
481
- changes: z6.array(z6.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
552
+ var ManageGoalsToolSchema = z7.object({
553
+ changes: z7.array(z7.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
482
554
  }).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
483
- 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: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; 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 (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); 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_goals) 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.";
484
- var GetGoalsSchema = z6.object({
485
- goalIds: z6.array(z6.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
555
+ var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. SHAPE IT AS PROJECTS WITH STEPS: create a project under its area (parentGoalId; a top-level Goal with children is an area), then each part as its child Goal, so the person sees N of M steps. Move a flat or orphaned Goal of yours into that shape when you touch it; a flat Goal is valid. 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: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; 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 (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); 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_goals) 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.";
556
+ var GetGoalsSchema = z7.object({
557
+ goalIds: z7.array(z7.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
486
558
  /** Every entry in full. Without it the read carries the person's words, open questions and your
487
559
  * newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
488
- history: z6.boolean().optional()
560
+ history: z7.boolean().optional()
489
561
  }).strict();
490
- var GetDeliverySchema = z6.object({
491
- deliveryId: z6.string().uuid().describe("The Delivery id returned by contact or receive."),
562
+ var GetDeliverySchema = z7.object({
563
+ deliveryId: z7.string().uuid().describe("The Delivery id returned by contact or receive."),
492
564
  // WHAT WAS SAID ON A CALL (owner, 2026-10-26, Goal c743f4a4: "A flag on get_delivery", "Any of my agents").
493
- transcript: z6.boolean().optional().describe("For a Call: also return what was said on it, one page of spoken lines."),
494
- after: z6.string().max(40).optional().describe("With transcript: the next value the previous page returned, to read the next page.")
565
+ transcript: z7.boolean().optional().describe("For a Call: also return what was said on it, one page of spoken lines."),
566
+ after: z7.string().max(40).optional().describe("With transcript: the next value the previous page returned, to read the next page.")
495
567
  }).strict();
496
568
  var GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. For a Call, transcript: true adds what was said: each spoken line in order, who said it, and when from pickup, a page at a time. A line from the room, not the person, is marked room. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
497
569
  var GET_GOALS_DESCRIPTION = "Read one to ten Goals by goalIds, returned in the same order under goals (a read never puts you on them). history applies to every requested Goal. A refused read fails the call with its original refusal; no partial result is presented as complete. Each Goal includes its ten most recent other contributors (names, latest entry headlines and times), 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.";
498
- var SearchToolSchema = z6.object({
499
- query: z6.string().trim().min(1).max(500),
500
- types: z6.array(z6.enum(["entry", "goal", "answer"])).min(1).optional(),
501
- goalId: z6.string().uuid().optional(),
502
- limit: z6.number().int().min(1).max(20).optional()
570
+ var SearchToolSchema = z7.object({
571
+ query: z7.string().trim().min(1).max(500),
572
+ types: z7.array(z7.enum(["entry", "goal", "answer"])).min(1).optional(),
573
+ goalId: z7.string().uuid().optional(),
574
+ limit: z7.number().int().min(1).max(20).optional()
503
575
  }).strict();
504
576
  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 replies -- the person's answers, acknowledgements and deferrals (type answer; found by their summary, by the question or report they reply to, 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, the replies to its items, 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 (a reply carries its kind -- answered, acknowledged or deferred -- whether it is closed (a deferral is not), its summary in under ten words, its question or report, 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.";
505
- var ConfigureIdentitySchema = z6.object({}).strict();
577
+ var READS = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
578
+ var SENDS = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };
579
+ var ConfigureIdentitySchema = z7.object({}).strict();
506
580
  var CONFIGURE_IDENTITY_DESCRIPTION = "Read this authenticated agent\u2019s current name, voice and token ID. Read-only; no arguments. Pairing, renaming and logout are available only through the local configure driver.";
507
- var CheckActivitySchema = z6.object({}).strict();
581
+ var CheckActivitySchema = z7.object({}).strict();
508
582
  var FEEDBACK_TEXT_MAX = 5e4;
509
- var SendFeedbackSchema = z6.object({
510
- text: z6.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.`),
511
- title: z6.string().trim().min(1).max(200).optional().describe("One line naming it."),
512
- kind: z6.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
583
+ var SendFeedbackSchema = z7.object({
584
+ text: z7.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.`),
585
+ title: z7.string().trim().min(1).max(200).optional().describe("One line naming it."),
586
+ kind: z7.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
513
587
  }).strict();
514
588
  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.";
515
589
  var AGENT_TOOLS = [
516
- { name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
517
- { name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
518
- { name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
519
- { name: "get_delivery", description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
520
- { name: "get_goals", description: GET_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalsSchema) },
521
- { name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
522
- { 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_goals to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
523
- { name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) },
590
+ { name: "configure", title: "Who am I", annotations: READS, description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
591
+ { name: "contact", title: "Contact the person", annotations: SENDS, description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
592
+ { name: "manage_goals", title: "Manage Goals", annotations: SENDS, description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
593
+ { name: "get_delivery", title: "Read a delivery", annotations: READS, description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
594
+ { name: "get_goals", title: "Read Goals", annotations: READS, description: GET_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalsSchema) },
595
+ { name: "search", title: "Search history", annotations: READS, description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
596
+ { name: "check_activity", title: "Recent agent activity", annotations: READS, 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_goals to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
597
+ { name: "send_feedback", title: "Send feedback to Paigy", annotations: SENDS, description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) },
524
598
  // THE GUIDE FROM INSIDE THE MCP (owner, 2026-10-27): the skill's text, needing no identity (`help.ts`).
525
- { name: "help", description: HELP_DESCRIPTION, inputSchema: mcpInputSchema(HelpSchema) }
599
+ { name: "help", title: "Paigy guide", annotations: READS, description: HELP_DESCRIPTION, inputSchema: mcpInputSchema(HelpSchema) }
526
600
  ];
527
601
  var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
528
602
  var LISTEN_COMMAND = "npx -y -p @paigy/mcp@latest paigy-listen --brief";
529
- var LISTEN_MONITOR = "your harness's MONITOR tool, the kind that wakes you on every line it prints (in Claude Code: Monitor, not a background Bash command, which only tells you when it exits)";
603
+ var LISTEN_MONITOR = "your harness's MONITOR tool, the kind that wakes you on every line it prints (in Claude Code: Monitor, not a background Bash command, which only tells you when it exits; in Grok: the monitor tool, the same way)";
530
604
  function serverInstructions(opts) {
531
605
  const waits = opts.waits ? `contact({wait:true}) waits ~45 s for a reply to what you sent; contact({}) for anything addressed to you. Call \`listen\` once to wake at rest.` : "A contact here returns after one read; answers wake you, and contact({wait:false}) collects them.";
532
- return `New here? Call help. On startup and after a wake, contact({wait:false}) returns your events. They repeat until acknowledged with contact({ackEventIds}); reading does not. get_goals reads a Goal's conversation; your first write puts you on it, and every Goal of your person is open to you. manage_goals creates, assigns and organizes work; to follow up, defer the Goal with manage_goals (woken when due). Complete and report at once: contact.completions or manage_goals kind:complete. Every read ends in \`next\`: your next step. ${waits} Report progress as a contact update; one they don't need yet stays on the Goal. An open Call is not proof it rang. Your user is remote: reach them only through Paigy, never the terminal.
606
+ return `New here? Call help. On startup and after a wake, contact({wait:false}) returns your events. They repeat until acknowledged with contact({ackEventIds}); reading doesn't. get_goals reads a Goal; your first write puts you on it; every Goal of your person is open to you. manage_goals creates, assigns and organizes work as projects with steps; to follow up, defer it (woken when due). Complete and report at once: contact.completions or manage_goals kind:complete. Every read ends in \`next\`: your next step. ${waits} Report progress as a contact update (what changed for them; causedBy, shipped); one they don't need yet stays on the Goal. An open Call isn't proof it rang. Your user is remote: reach them only through Paigy, never the terminal.
533
607
 
534
608
  ${ASKING_RULES}`;
535
609
  }
536
- var id = z7.string().uuid();
537
- var key = z7.string().min(1).max(40);
538
- var participant = z7.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
539
- var SourceSchema = z7.object({ entryId: id }).strict();
540
- var ResultRefSchema = z7.union([z7.object({ id }).strict(), z7.object({ local: key }).strict()]);
541
- var NodeRefSchema = z7.object({ type: z7.enum(["goal", "question"]), id }).strict();
542
- var DependencySchema = z7.object({
610
+ var SKILL = '---\nname: paigy\ndescription: Reach the user through Paigy. Use whenever you need to tell the user something, ask them a question, get a decision or approval, call their phone, wait for or receive their answers, start or check the Paigy listener, or create, assign, defer or complete Paigy Goals.\n---\n<!-- paigy-skill \xB7 source: paigy monorepo packs/skill/paigy/SKILL.md; mirrored to paigy-ai/claude plugins/paigy/skills/paigy/SKILL.md and written by https://paigy.ai/install, which replaces only a file carrying this line. Edit the source, never a copy. -->\n\n# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session; `/mcp__paigy__organize` sets up a person\'s work as a board.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- Grok: run it with the **monitor** tool, never a background shell command. Start it again whenever the monitor expires.\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- One load-bearing point per report: `message` is the point they must not miss, and a second point that would go wrong or stay unclear later if they missed it is its own update. `units` are context they may open, never something they must hear.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Shape your work as projects with steps. Create a project under its area (`create` with `parentGoalId` = the area), then one child Goal per step; complete each step as it lands. A top-level Goal that has children is an area, not a project: never hang steps straight off a top-level Goal. The person\'s Work tab shows a project\'s progress as its steps, "3 of 5 steps done"; a project with no steps shows none. When you touch an older Goal of yours that sits flat or under a finished project, move it into that shape (`move`). A flat Goal is still valid; never reshape another agent\'s Goals.\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Organize\n\nWhen the person asks you to set up or organize their work ("set up my project", "help me manage all this", several products or parts to juggle), turn it into a board they can run like a team. Their Work tab shows it.\n\n- Learn the work first: read the repo and their Goals (`get_goals`, `search`). Ask only what you cannot read, at most three questions, each its own question.\n- The shape: an area per product or business (a top-level Goal); under it a project per part that moves on its own (landing page, graphics, web app, mobile app, launch); under each project its steps, three to seven, each an outcome someone can finish and check.\n- Propose before you build. Create one Goal for the setup itself, then ask one question on it: the tree in `units` (one unit per project: its title, then its steps), options "Build it" and "Change it: I\'ll say how". Create nothing else until they answer.\n- On "Build it", one `manage_goals` call per level, since a parent\'s id comes back in `results[i].goalId`: the areas, then their projects (`parentGoalId`), then every step; then a `dependency` where a project waits on another (web on the design). Never copy work that already has a Goal: yours move into the tree (`move`); another agent\'s stay where they are, named in the proposal. Then complete the setup Goal.\n- Report how to run it, and hand over one brief per project for the person to paste as that session\'s first message. Each brief names the project\'s Goal (title and id) and says: call `listen` and keep the listener running; receive with `contact({ wait: false })` at startup and after every wake; work the steps in order and complete each as it lands; report progress as updates on the project; ask anything you are blocked on through Paigy, as a question on that step\'s Goal (`workItBlocks` only when it truly stops you), never in the terminal.\n- One session per project, plus one lead session that keeps the board current. Any agent, Claude, Codex or another, can pick up any Goal; `check_activity` shows who is on what. A subagent inside a session shares that session\'s identity and listener: it may ask through Paigy, but never starts a listener of its own.\n- Keep it current as you work: complete each step as it lands and add a step when new work appears. Never reshape another agent\'s Goals.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
611
+ var id = z8.string().uuid();
612
+ var key = z8.string().min(1).max(40);
613
+ var participant = z8.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
614
+ var SourceSchema = z8.object({ entryId: id }).strict();
615
+ var ResultRefSchema = z8.union([z8.object({ id }).strict(), z8.object({ local: key }).strict()]);
616
+ var NodeRefSchema = z8.object({ type: z8.enum(["goal", "question"]), id }).strict();
617
+ var DependencySchema = z8.object({
543
618
  blocker: NodeRefSchema,
544
619
  blocked: NodeRefSchema,
545
- action: z7.enum(["start", "complete", "answer"]),
546
- reason: z7.string().max(500).optional()
620
+ action: z8.enum(["start", "complete", "answer"]),
621
+ reason: z8.string().max(500).optional()
547
622
  }).strict();
548
- var goalState = z7.enum(["open", "completed", "canceled"]);
623
+ var goalState = z8.enum(["open", "completed", "canceled"]);
549
624
  var existingGoalChanges = [
550
- z7.object({ kind: z7.literal("edit"), goalId: id, title: z7.string().min(1).max(120).optional(), outcome: z7.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
551
- z7.object({ kind: z7.literal("state"), goalId: id, state: goalState }).strict(),
552
- z7.object({ kind: z7.literal("assign"), goalId: id, ownerId: participant }).strict(),
553
- z7.object({ kind: z7.literal("defer"), goalId: id, until: z7.string().datetime().nullable() }).strict(),
554
- z7.object({ kind: z7.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
555
- z7.object({
556
- kind: z7.literal("dependency"),
557
- change: z7.enum(["add", "remove"]),
625
+ z8.object({ kind: z8.literal("edit"), goalId: id, title: z8.string().min(1).max(120).optional(), outcome: z8.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
626
+ z8.object({ kind: z8.literal("state"), goalId: id, state: goalState }).strict(),
627
+ z8.object({ kind: z8.literal("assign"), goalId: id, ownerId: participant }).strict(),
628
+ z8.object({ kind: z8.literal("defer"), goalId: id, until: z8.string().datetime().nullable() }).strict(),
629
+ z8.object({ kind: z8.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
630
+ z8.object({
631
+ kind: z8.literal("dependency"),
632
+ change: z8.enum(["add", "remove"]),
558
633
  goalId: id,
559
634
  dependsOnGoalId: id,
560
- action: z7.enum(["start", "complete"]),
561
- reason: z7.string().max(500).optional()
635
+ action: z8.enum(["start", "complete"]),
636
+ reason: z8.string().max(500).optional()
562
637
  }).strict()
563
638
  ];
564
- var BrainGoalChangeSchema = z7.union([
639
+ var BrainGoalChangeSchema = z8.union([
565
640
  // newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
566
- z7.object({
567
- kind: z7.literal("create"),
568
- outcome: z7.string().min(1),
569
- title: z7.string().min(1).max(120),
641
+ z8.object({
642
+ kind: z8.literal("create"),
643
+ outcome: z8.string().min(1),
644
+ title: z8.string().min(1).max(120),
570
645
  ownerId: participant,
571
646
  parentGoal: ResultRefSchema.nullable(),
572
- newSession: z7.literal(true).optional()
647
+ newSession: z8.literal(true).optional()
573
648
  }).strict(),
574
649
  ...existingGoalChanges
575
650
  ]);
576
- var option = z7.object({ id: z7.string().min(1).max(40), label: z7.string().min(1), hint: z7.string().optional() }).strict();
577
- var QuestionChangeSchema = z7.union([
578
- z7.object({
579
- kind: z7.literal("create"),
580
- text: z7.string().min(1),
581
- summary: z7.string().optional(),
651
+ var option = z8.object({ id: z8.string().min(1).max(40), label: z8.string().min(1), hint: z8.string().optional() }).strict();
652
+ var QuestionChangeSchema = z8.union([
653
+ z8.object({
654
+ kind: z8.literal("create"),
655
+ text: z8.string().min(1),
656
+ summary: z8.string().optional(),
582
657
  answererId: participant,
583
658
  goal: ResultRefSchema.optional(),
584
- blocks: z7.array(z7.object({ question: ResultRefSchema }).strict()),
585
- options: z7.array(option).min(2).optional(),
586
- pickMode: z7.enum(["one", "many", "rank"]).optional()
659
+ blocks: z8.array(z8.object({ question: ResultRefSchema }).strict()),
660
+ options: z8.array(option).min(2).optional(),
661
+ pickMode: z8.enum(["one", "many", "rank"]).optional()
587
662
  }).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
588
- z7.object({ kind: z7.literal("edit"), questionId: id, text: z7.string().min(1) }).strict(),
589
- z7.object({ kind: z7.literal("assign"), questionId: id, answererId: participant }).strict(),
590
- z7.object({ kind: z7.literal("withdraw"), questionId: id }).strict(),
591
- z7.object({ kind: z7.literal("dependency"), change: z7.enum(["add", "remove"]), edge: DependencySchema }).strict()
663
+ z8.object({ kind: z8.literal("edit"), questionId: id, text: z8.string().min(1) }).strict(),
664
+ z8.object({ kind: z8.literal("assign"), questionId: id, answererId: participant }).strict(),
665
+ z8.object({ kind: z8.literal("withdraw"), questionId: id }).strict(),
666
+ z8.object({ kind: z8.literal("dependency"), change: z8.enum(["add", "remove"]), edge: DependencySchema }).strict()
592
667
  ]);
593
- var lessonScope = z7.union([
594
- z7.object({ kind: z7.literal("user") }).strict(),
595
- z7.object({ kind: z7.literal("goal"), goal: ResultRefSchema }).strict()
668
+ var lessonScope = z8.union([
669
+ z8.object({ kind: z8.literal("user") }).strict(),
670
+ z8.object({ kind: z8.literal("goal"), goal: ResultRefSchema }).strict()
596
671
  ]);
597
- var LessonChangeSchema = z7.union([
598
- z7.object({ kind: z7.literal("remember"), text: z7.string().min(1), scope: lessonScope }).strict(),
599
- z7.object({ kind: z7.literal("revise"), lessonId: id, text: z7.string().min(1), scope: lessonScope }).strict(),
600
- z7.object({ kind: z7.literal("withdraw"), lessonId: id }).strict()
672
+ var LessonChangeSchema = z8.union([
673
+ z8.object({ kind: z8.literal("remember"), text: z8.string().min(1), scope: lessonScope }).strict(),
674
+ z8.object({ kind: z8.literal("revise"), lessonId: id, text: z8.string().min(1), scope: lessonScope }).strict(),
675
+ z8.object({ kind: z8.literal("withdraw"), lessonId: id }).strict()
601
676
  ]);
602
- var BrainNextSchema = z7.object({
603
- instructions: z7.array(z7.object({ entryKeys: z7.array(key).min(1) }).strict()),
604
- say: z7.array(key),
605
- then: z7.enum(["listen", "hold", "end", "none"]),
606
- waitFor: z7.array(ResultRefSchema),
607
- reason: z7.string()
677
+ var BrainNextSchema = z8.object({
678
+ instructions: z8.array(z8.object({ entryKeys: z8.array(key).min(1) }).strict()),
679
+ say: z8.array(key),
680
+ then: z8.enum(["listen", "hold", "end", "none"]),
681
+ waitFor: z8.array(ResultRefSchema),
682
+ reason: z8.string()
608
683
  }).strict();
609
684
  var messageBase = {
610
685
  key,
611
- text: z7.string().min(1),
612
- goals: z7.array(ResultRefSchema),
613
- questions: z7.array(ResultRefSchema),
614
- entryKeys: z7.array(key),
615
- obligationIds: z7.array(id)
686
+ text: z8.string().min(1),
687
+ goals: z8.array(ResultRefSchema),
688
+ questions: z8.array(ResultRefSchema),
689
+ entryKeys: z8.array(key),
690
+ obligationIds: z8.array(id)
616
691
  };
617
- var BrainMessageSchema = z7.union([
692
+ var BrainMessageSchema = z8.union([
618
693
  // A MESSAGE TO THE PERSON KEEPS ITS SENTENCES (turns.md): the talker writes them one by one, and each is
619
694
  // its own unit of speech (`<cycle>:<key>:<n>`). `text` is the same words joined, for every reader of a
620
695
  // message's words. Absent on results written before sentences.
621
- z7.object({
696
+ z8.object({
622
697
  ...messageBase,
623
- to: z7.object({ kind: z7.literal("user") }).strict(),
624
- sentences: z7.array(z7.string().min(1)).min(1).optional(),
698
+ to: z8.object({ kind: z8.literal("user") }).strict(),
699
+ sentences: z8.array(z8.string().min(1)).min(1).optional(),
625
700
  // THE REPORTS IT TELLS, BY THEIR OWN IDENTITY (call 9d1c096c, 2026-10-15): the talker's message names the report
626
701
  // Entries it speaks of, so the plan's line names that report rather than re-finding it by its Goal. Absent on
627
702
  // results written before it.
628
- tells: z7.array(z7.object({ id }).strict()).optional()
703
+ tells: z8.array(z8.object({ id }).strict()).optional()
629
704
  }).strict(),
630
- z7.object({
705
+ z8.object({
631
706
  ...messageBase,
632
- to: z7.object({ kind: z7.literal("agent"), agentId: z7.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
633
- call: z7.object({ callId: id, listen: z7.literal(true) }).strict().optional(),
707
+ to: z8.object({ kind: z8.literal("agent"), agentId: z8.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
708
+ call: z8.object({ callId: id, listen: z8.literal(true) }).strict().optional(),
634
709
  // WHAT IT ASKS THE AGENT, IN UNDER TEN WORDS (owner, 2026-10-15: "Yes, add the summary"): the talker's own, stored
635
710
  // as the sent Entry's summary, which the recap's "Sent to <agent>: …" row shows. Absent on results written before it.
636
- summary: z7.string().optional(),
711
+ summary: z8.string().optional(),
712
+ // THE PERSON'S LINES IT CARRIES, IN THEIR WORDS (owner, 2026-11-05: "just deliver my exact words to the agent"): the
713
+ // `entries` keys of those lines, each relayed whole on the agent's Delivery (`context = false`), where the rest of
714
+ // `entryKeys` ride as context. Absent on results written before it, and on a message that carries none.
715
+ carries: z8.array(key).optional(),
637
716
  // THE PERSON ASKED TO BE CALLED BACK ONCE THIS AGENT'S WORK IS DONE (turns.md, step 0): the agent owes
638
717
  // the call, and its next contact about it rings them (`public.callbacks`).
639
- callback: z7.literal(true).optional()
718
+ callback: z8.literal(true).optional()
640
719
  }).strict()
641
720
  ]);
642
- var BrainSearchSchema = z7.object({
643
- query: z7.string().min(1).max(500),
644
- within: z7.array(z7.enum(["entries", "goals", "answers"])).min(1),
721
+ var BrainSearchSchema = z8.object({
722
+ query: z8.string().min(1).max(500),
723
+ within: z8.array(z8.enum(["entries", "goals", "answers"])).min(1),
645
724
  goalId: id.optional()
646
725
  }).strict();
647
- var BrainResultSchema = z7.object({
648
- messages: z7.array(BrainMessageSchema),
649
- entries: z7.array(z7.object({ key, sources: z7.array(SourceSchema).min(1), goals: z7.array(ResultRefSchema), summary: z7.string().optional() }).strict()),
650
- questions: z7.array(z7.object({ key, entryKeys: z7.array(key), change: QuestionChangeSchema }).strict()),
651
- answers: z7.array(z7.object({
726
+ var BrainResultSchema = z8.object({
727
+ messages: z8.array(BrainMessageSchema),
728
+ entries: z8.array(z8.object({ key, sources: z8.array(SourceSchema).min(1), goals: z8.array(ResultRefSchema), summary: z8.string().optional() }).strict()),
729
+ questions: z8.array(z8.object({ key, entryKeys: z8.array(key), change: QuestionChangeSchema }).strict()),
730
+ answers: z8.array(z8.object({
652
731
  question: ResultRefSchema,
653
- entryKeys: z7.array(key).min(1),
654
- summary: z7.string(),
655
- selectedOptionIds: z7.array(z7.string().min(1)).optional()
732
+ entryKeys: z8.array(key).min(1),
733
+ summary: z8.string(),
734
+ selectedOptionIds: z8.array(z8.string().min(1)).optional()
656
735
  }).strict()),
657
- goals: z7.array(z7.object({ key, entryKeys: z7.array(key), change: BrainGoalChangeSchema }).strict()),
658
- feedback: z7.array(z7.object({ entryKeys: z7.array(key).min(1) }).strict()),
659
- lessons: z7.array(z7.object({ entryKeys: z7.array(key).min(1), change: LessonChangeSchema }).strict()),
736
+ goals: z8.array(z8.object({ key, entryKeys: z8.array(key), change: BrainGoalChangeSchema }).strict()),
737
+ feedback: z8.array(z8.object({ entryKeys: z8.array(key).min(1) }).strict()),
738
+ lessons: z8.array(z8.object({ entryKeys: z8.array(key).min(1), change: LessonChangeSchema }).strict()),
660
739
  next: BrainNextSchema,
661
740
  search: BrainSearchSchema.optional()
662
741
  }).strict();
663
- var str2 = z7.string();
664
- var strs = z7.array(z7.string());
665
- var CompactResultSchema = z7.object({
666
- next: z7.object({ say: strs, then: z7.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str2, instructions: strs }).strict(),
667
- messages: z7.array(z7.object({ key: str2, to: str2, text: str2, about: strs, entries: strs, owed: strs, inviteToCall: z7.boolean() }).strict()),
668
- entries: z7.array(z7.object({ key: str2, from: str2, on: strs }).strict()),
669
- answers: z7.array(z7.object({ question: str2, entries: strs, summary: str2, picked: strs }).strict()),
670
- changes: z7.array(z7.object({
742
+ var str2 = z8.string();
743
+ var strs = z8.array(z8.string());
744
+ var CompactResultSchema = z8.object({
745
+ next: z8.object({ say: strs, then: z8.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str2, instructions: strs }).strict(),
746
+ messages: z8.array(z8.object({ key: str2, to: str2, text: str2, about: strs, entries: strs, owed: strs, inviteToCall: z8.boolean() }).strict()),
747
+ entries: z8.array(z8.object({ key: str2, from: str2, on: strs }).strict()),
748
+ answers: z8.array(z8.object({ question: str2, entries: strs, summary: str2, picked: strs }).strict()),
749
+ changes: z8.array(z8.object({
671
750
  key: str2,
672
- what: z7.enum(["goal", "question", "lesson"]),
673
- op: z7.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
751
+ what: z8.enum(["goal", "question", "lesson"]),
752
+ op: z8.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
674
753
  id: str2,
675
754
  text: str2,
676
755
  title: str2,
677
756
  who: str2,
678
757
  under: str2,
679
- gate: z7.enum(["start", "complete", "answer", "none"]),
680
- options: z7.array(z7.object({ id: str2, label: str2 }).strict()),
681
- pick: z7.enum(["one", "many", "rank", "words"]),
758
+ gate: z8.enum(["start", "complete", "answer", "none"]),
759
+ options: z8.array(z8.object({ id: str2, label: str2 }).strict()),
760
+ pick: z8.enum(["one", "many", "rank", "words"]),
682
761
  entries: strs
683
762
  }).strict()),
684
- feedback: z7.array(strs),
685
- search: z7.object({ query: str2, within: strs, goal: str2 }).strict()
763
+ feedback: z8.array(strs),
764
+ search: z8.object({ query: str2, within: strs, goal: str2 }).strict()
686
765
  }).strict();
687
766
  var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
688
767
  zodToJsonSchema2(CompactResultSchema, { $refStrategy: "none" })
689
768
  );
690
- var str3 = z8.string();
691
- var strs2 = z8.array(z8.string());
692
- var TalkerReplySchema = z8.object({
769
+ var str3 = z9.string();
770
+ var strs2 = z9.array(z9.string());
771
+ var TalkerReplySchema = z9.object({
693
772
  /** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
694
773
  * or an agent's handle; `sentences` is what a message to the person says, one short sentence each
695
774
  * ([] to an agent); `text` is what a message to an agent says ("" to the person); `summary` is what it
@@ -697,37 +776,45 @@ var TalkerReplySchema = z8.object({
697
776
  * "Yes, add the summary"), which the recap's "Sent to <agent>: …" row shows; `about` names
698
777
  * handles; `blocks` is the item a question to an agent must be answered before ("" for none);
699
778
  * `callback` is true on a message to the agent doing work the person asked to be called back about
700
- * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). */
701
- messages: z8.array(z8.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z8.boolean() }).strict()),
779
+ * once it is done (false otherwise): that agent then owes the call (turns.md, step 0). `lines` are the
780
+ * person's line handles a message to an agent carries ([] for none): the agent receives those lines, in their
781
+ * words, and `text` is then only what the talker adds beyond them (owner, 2026-11-05: "just deliver my exact
782
+ * words to the agent, keeping the summary as a way for me to navigate them more easily"). */
783
+ messages: z9.array(z9.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z9.boolean(), lines: strs2.optional() }).strict()),
702
784
  /** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
703
- then: z8.enum(["listen", "hold", "end"]),
785
+ then: z9.enum(["listen", "hold", "end"]),
704
786
  /** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
705
787
  * IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
706
788
  * "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
707
789
  * end of this call), a question's handle (that question's answer) or an ISO time. */
708
- replies: z8.array(z8.object({ item: str3, options: strs2, lines: strs2, summary: str3, defer: str3 }).strict()),
790
+ replies: z9.array(z9.object({ item: str3, options: strs2, lines: strs2, summary: str3, defer: str3 }).strict()),
709
791
  /** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
710
792
  instruction: strs2,
711
793
  /** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
712
794
  * work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
713
795
  file: strs2,
714
796
  /** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
715
- need: z8.object({ kind: z8.enum(["search", "item", "agent", ""]), text: str3 }).strict(),
797
+ need: z9.object({ kind: z9.enum(["search", "item", "agent", ""]), text: str3 }).strict(),
716
798
  /** The handle of the item to explain ("" for none). */
717
799
  explain: str3
718
800
  }).strict();
719
- var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
801
+ var TALKER_REPLY_JSON_SCHEMA = withEveryMessageField((({ $schema: _, ...rest }) => rest)(
720
802
  zodToJsonSchema3(TalkerReplySchema, { $refStrategy: "none" })
721
- );
722
- var str4 = z9.string();
723
- var strs3 = z9.array(z9.string());
724
- var FilerReplySchema = z9.object({
803
+ ));
804
+ function withEveryMessageField(schema) {
805
+ const item = schema.properties?.messages?.items;
806
+ if (item?.properties) item.required = Object.keys(item.properties);
807
+ return schema;
808
+ }
809
+ var str4 = z10.string();
810
+ var strs3 = z10.array(z10.string());
811
+ var FilerReplySchema = z10.object({
725
812
  /** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals: a line that asks
726
813
  * for new work goes on the Goal it creates, owner 2026-10-26), and what the line says in at most twenty
727
814
  * words (owner, 2026-10-26: "bump it from 10 to 20 words"). */
728
- filed: z9.array(z9.object({ line: str4, goals: strs3, summary: str4 }).strict()),
729
- goals: z9.array(z9.object({
730
- op: z9.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
815
+ filed: z10.array(z10.object({ line: str4, goals: strs3, summary: str4 }).strict()),
816
+ goals: z10.array(z10.object({
817
+ op: z10.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
731
818
  key: str4,
732
819
  goal: str4,
733
820
  title: str4,
@@ -736,33 +823,33 @@ var FilerReplySchema = z9.object({
736
823
  under: str4,
737
824
  until: str4,
738
825
  on: str4,
739
- gate: z9.enum(["start", "complete", "answer", ""]),
826
+ gate: z10.enum(["start", "complete", "answer", ""]),
740
827
  why: str4
741
828
  }).strict()),
742
- questions: z9.array(z9.object({
743
- op: z9.enum(["create", "edit", "assign", "withdraw"]),
829
+ questions: z10.array(z10.object({
830
+ op: z10.enum(["create", "edit", "assign", "withdraw"]),
744
831
  key: str4,
745
832
  question: str4,
746
833
  text: str4,
747
834
  summary: str4,
748
835
  to: str4,
749
836
  goal: str4,
750
- options: z9.array(z9.object({ id: str4, label: str4 }).strict()),
751
- pick: z9.enum(["one", "many", "rank", "words", ""]),
837
+ options: z10.array(z10.object({ id: str4, label: str4 }).strict()),
838
+ pick: z10.enum(["one", "many", "rank", "words", ""]),
752
839
  blocks: str4
753
840
  }).strict()),
754
- lessons: z9.array(z9.object({ op: z9.enum(["remember", "revise", "withdraw"]), lesson: str4, text: str4, scope: str4 }).strict()),
841
+ lessons: z10.array(z10.object({ op: z10.enum(["remember", "revise", "withdraw"]), lesson: str4, text: str4, scope: str4 }).strict()),
755
842
  /** Line handles whose words comment on Paigy itself. */
756
843
  feedback: strs3
757
844
  }).strict();
758
845
  var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
759
846
  zodToJsonSchema4(FilerReplySchema, { $refStrategy: "none" })
760
847
  );
761
- var ExplainerReplySchema = z10.object({
848
+ var ExplainerReplySchema = z11.object({
762
849
  /** The explanation, as sentences, spoken in order. */
763
- say: z10.array(z10.string()),
850
+ say: z11.array(z11.string()),
764
851
  /** Questions to an agent about gaps in what it read: the agent's handle and the question. */
765
- followUps: z10.array(z10.object({ to: z10.string(), text: z10.string() }).strict())
852
+ followUps: z11.array(z11.object({ to: z11.string(), text: z11.string() }).strict())
766
853
  }).strict();
767
854
  var EXPLAINER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
768
855
  zodToJsonSchema5(ExplainerReplySchema, { $refStrategy: "none" })
@@ -785,49 +872,49 @@ var DISTILLER_DEFECTS = [
785
872
  "dropped"
786
873
  // something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
787
874
  ];
788
- var DistillerReplySchema = z11.object({
789
- defects: z11.array(z11.object({
790
- kind: z11.enum(DISTILLER_DEFECTS),
875
+ var DistillerReplySchema = z12.object({
876
+ defects: z12.array(z12.object({
877
+ kind: z12.enum(DISTILLER_DEFECTS),
791
878
  /** The handles of the lines it shows in. */
792
- lines: z11.array(z11.string()),
879
+ lines: z12.array(z12.string()),
793
880
  /** The handle of the record it concerns, or "". */
794
- about: z11.string(),
881
+ about: z12.string(),
795
882
  /** One plain sentence, in words, never handles: it is what the feedback row keeps. */
796
- why: z11.string()
883
+ why: z12.string()
797
884
  }).strict())
798
885
  }).strict();
799
886
  var DISTILLER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
800
887
  zodToJsonSchema6(DistillerReplySchema, { $refStrategy: "none" })
801
888
  );
802
889
  var TIDY_AUDIENCES = ["talker", "filer", "agents"];
803
- var TidyReplySchema = z11.object({
804
- lessons: z11.array(z11.object({
805
- lesson: z11.string(),
806
- op: z11.enum(["keep", "revise", "withdraw"]),
807
- text: z11.string(),
808
- for: z11.enum([...TIDY_AUDIENCES, ""]),
809
- into: z11.string()
890
+ var TidyReplySchema = z12.object({
891
+ lessons: z12.array(z12.object({
892
+ lesson: z12.string(),
893
+ op: z12.enum(["keep", "revise", "withdraw"]),
894
+ text: z12.string(),
895
+ for: z12.enum([...TIDY_AUDIENCES, ""]),
896
+ into: z12.string()
810
897
  }).strict())
811
898
  }).strict();
812
899
  var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
813
900
  zodToJsonSchema6(TidyReplySchema, { $refStrategy: "none" })
814
901
  );
815
- var HandoutReplySchema = z12.object({
816
- assign: z12.array(z12.object({
902
+ var HandoutReplySchema = z13.object({
903
+ assign: z13.array(z13.object({
817
904
  /** The Goal's handle (G). */
818
- goal: z12.string(),
905
+ goal: z13.string(),
819
906
  /** The agent's handle (A). */
820
- to: z12.string(),
907
+ to: z13.string(),
821
908
  /** At most ten words, naming the Goal of theirs it is most like: the reason the hand-out records. */
822
- why: z12.string()
909
+ why: z13.string()
823
910
  }).strict()),
824
911
  /** The Goals no listed agent's work is even loosely like: Paigy starts a session for them (owner, 2026-10-24,
825
912
  * Goal 37603006, "One per family of related Goals"). */
826
- new: z12.array(z12.object({
913
+ new: z13.array(z13.object({
827
914
  /** The Goal's handle (G). */
828
- goal: z12.string(),
915
+ goal: z13.string(),
829
916
  /** At most ten words: why nothing active fits it. */
830
- why: z12.string()
917
+ why: z13.string()
831
918
  }).strict())
832
919
  }).strict();
833
920
  var HANDOUT_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
@@ -861,20 +948,20 @@ var LIVE_MS = 3 * 6e4;
861
948
  var WORKING_MS = 60 * 6e4;
862
949
  var WORKING_TIER_MS = 2 * 60 * 6e4;
863
950
  var RECENT_TIER_MS = 24 * 60 * 6e4;
864
- var ContextSchema = z13.object({
865
- title: z13.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
866
- description: z13.array(z13.string().min(1)).describe(
951
+ var ContextSchema = z14.object({
952
+ title: z14.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
953
+ description: z14.array(z14.string().min(1)).describe(
867
954
  "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."
868
955
  ),
869
956
  /** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
870
957
  * a body on a tap. Absent on an ask sent without units, which renders as before. */
871
- units: z13.array(z13.object({ title: z13.string(), body: z13.string() })).optional()
958
+ units: z14.array(z14.object({ title: z14.string(), body: z14.string() })).optional()
872
959
  });
873
- var ParticipantSchema = z13.object({
874
- kind: z13.enum(["human", "agent"]),
875
- id: z13.string()
960
+ var ParticipantSchema = z14.object({
961
+ kind: z14.enum(["human", "agent"]),
962
+ id: z14.string()
876
963
  });
877
- var TransformSchema = z13.enum([
964
+ var TransformSchema = z14.enum([
878
965
  "structure",
879
966
  // shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
880
967
  "request_more",
@@ -890,13 +977,13 @@ var TransformSchema = z13.enum([
890
977
  "summarize"
891
978
  // reduce volume, keep decision value — 30-turn cap, spoken briefing
892
979
  ]);
893
- var VisualSchema = z13.object({
894
- url: z13.string().url(),
895
- label: z13.string().optional()
980
+ var VisualSchema = z14.object({
981
+ url: z14.string().url(),
982
+ label: z14.string().optional()
896
983
  });
897
- var NotifyLevelSchema = z13.enum(["inbox", "push", "banner", "call"]);
898
- var SelectShapeSchema = z13.enum(["one", "many", "rank", "confirm", "text"]);
899
- var ReceiptEventSchema = z13.enum([
984
+ var NotifyLevelSchema = z14.enum(["inbox", "push", "banner", "call"]);
985
+ var SelectShapeSchema = z14.enum(["one", "many", "rank", "confirm", "text"]);
986
+ var ReceiptEventSchema = z14.enum([
900
987
  "delivered",
901
988
  // the bundle reached the recipient at some level
902
989
  "seen",
@@ -926,47 +1013,47 @@ var ReceiptEventSchema = z13.enum([
926
1013
  // be rewound by a writer that forgot to advance it.
927
1014
  "restarted"
928
1015
  ]);
929
- var AttentionSchema = z13.object({
1016
+ var AttentionSchema = z14.object({
930
1017
  urgency: NotifyLevelSchema,
931
1018
  /** The required answer shape, or null for a plain notify that asks nothing back. */
932
1019
  select: SelectShapeSchema.nullable(),
933
1020
  /** Coverage contract (#396) — points the answer must address; null = none declared. */
934
- points: z13.array(z13.string()).nullable(),
1021
+ points: z14.array(z14.string()).nullable(),
935
1022
  /** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
936
- blocking: z13.boolean(),
1023
+ blocking: z14.boolean(),
937
1024
  /** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
938
1025
  * slice wires it; optional so today's rows/callers project cleanly. */
939
- deadline: z13.string().datetime().nullable().optional()
1026
+ deadline: z14.string().datetime().nullable().optional()
940
1027
  });
941
- var NotifyRequestFields = z13.object({
1028
+ var NotifyRequestFields = z14.object({
942
1029
  /** Plaintext message content. Present on the plaintext path (today's shape);
943
1030
  * ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
944
1031
  * superRefine at the bottom enforces exactly one of the two. */
945
1032
  context: ContextSchema.optional(),
946
- options: z13.array(OptionSchema.omit({ id: true }).extend({ label: z13.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
1033
+ options: z14.array(OptionSchema.omit({ id: true }).extend({ label: z14.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
947
1034
  "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)."
948
1035
  ),
949
- points: z13.array(z13.string().min(1)).optional().describe(
1036
+ points: z14.array(z14.string().min(1)).optional().describe(
950
1037
  "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."
951
1038
  ),
952
- visuals: z13.array(VisualSchema).optional().describe(
1039
+ visuals: z14.array(VisualSchema).optional().describe(
953
1040
  "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."
954
1041
  ),
955
1042
  /** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
956
- repo: z13.string().optional(),
1043
+ repo: z14.string().optional(),
957
1044
  /** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
958
- branch: z13.string().optional(),
1045
+ branch: z14.string().optional(),
959
1046
  /** Continue an existing conversation — the id of any notification in it (its root
960
1047
  * is the conversation's identity). Omitted = start a new conversation. Renamed
961
1048
  * from `parentId` (2026-08-03): one linkage system, the parent; the API edge
962
1049
  * still accepts the old name from older clients. */
963
- parentId: z13.string().uuid().optional(),
1050
+ parentId: z14.string().uuid().optional(),
964
1051
  /** The durable outcome this contact advances. Optional during the notification-to-Work
965
1052
  * migration; when present, a blocking ask creates a DecisionNeed for this Work. */
966
- workId: z13.string().uuid().optional(),
1053
+ workId: z14.string().uuid().optional(),
967
1054
  /** Target Goal scope. During staged migration this is accepted by the shared contract but
968
1055
  * target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
969
- goalId: z13.string().uuid().optional(),
1056
+ goalId: z14.string().uuid().optional(),
970
1057
  urgency: NotifyLevelSchema.default("inbox").describe(
971
1058
  "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."
972
1059
  ),
@@ -974,7 +1061,7 @@ var NotifyRequestFields = z13.object({
974
1061
  * visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
975
1062
  * when `parentId` became the conversation handle: `parentId` says WHERE, this
976
1063
  * says HOW. */
977
- clarifies: z13.string().optional(),
1064
+ clarifies: z14.string().optional(),
978
1065
  select: SelectShapeSchema.optional().describe(
979
1066
  "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."
980
1067
  ),
@@ -988,20 +1075,20 @@ var NotifyRequestFields = z13.object({
988
1075
  // (docs/history/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
989
1076
  // Owner, 2026-07-28: "our actual limitation on how long something is to the user should
990
1077
  // come from the broker splitting and summarizing." The cap that remains is a size guard.
991
- ask: z13.string().min(1).max(1e4).optional().describe(
1078
+ ask: z14.string().min(1).max(1e4).optional().describe(
992
1079
  '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.'
993
1080
  ),
994
- needs: z13.array(z13.string().min(1)).optional().describe(
1081
+ needs: z14.array(z14.string().min(1)).optional().describe(
995
1082
  "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."
996
1083
  ),
997
- urgencyHint: z13.enum(["whenever", "soon", "now"]).optional().describe(
1084
+ urgencyHint: z14.enum(["whenever", "soon", "now"]).optional().describe(
998
1085
  "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."
999
1086
  ),
1000
1087
  /** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
1001
1088
  * to the agent's work while it waits. Normalized server-side into those two
1002
1089
  * fields (normalizeWaiting) so everything downstream is untouched; explicit
1003
1090
  * urgencyHint/blocking win when both are sent. */
1004
- waiting: z13.enum(["none", "soft", "hard"]).optional().describe(
1091
+ waiting: z14.enum(["none", "soft", "hard"]).optional().describe(
1005
1092
  "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."
1006
1093
  ),
1007
1094
  /** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
@@ -1009,98 +1096,98 @@ var NotifyRequestFields = z13.object({
1009
1096
  * holding by default would charge every quiet claim that minute before any agent could
1010
1097
  * correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
1011
1098
  * and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
1012
- confirm: z13.boolean().optional().describe(
1099
+ confirm: z14.boolean().optional().describe(
1013
1100
  "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'."
1014
1101
  ),
1015
1102
  /** #575: a RELAY of the user's explicitly stated preference, never the agent's
1016
1103
  * choice. Outranks waiting in both directions: 'call' rings even for a
1017
1104
  * waiting:'none' "call me when it's done"; 'message' never rings even for
1018
1105
  * waiting:'hard'. */
1019
- channel: z13.enum(["call", "message"]).optional().describe(
1106
+ channel: z14.enum(["call", "message"]).optional().describe(
1020
1107
  "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."
1021
1108
  ),
1022
- confirmStyle: z13.enum(["yesno", "approve"]).default("yesno").describe(
1109
+ confirmStyle: z14.enum(["yesno", "approve"]).default("yesno").describe(
1023
1110
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
1024
1111
  ),
1025
- blocking: z13.boolean().default(false).describe(
1112
+ blocking: z14.boolean().default(false).describe(
1026
1113
  "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."
1027
1114
  )
1028
1115
  });
1029
1116
  var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
1030
- if (r.workId && r.goalId) ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
1117
+ if (r.workId && r.goalId) ctx.addIssue({ code: z14.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
1031
1118
  if (r.ask !== void 0) {
1032
1119
  for (const f of ["context", "select", "points"]) {
1033
1120
  if (r[f] !== void 0)
1034
- ctx.addIssue({ code: z13.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.` });
1121
+ ctx.addIssue({ code: z14.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.` });
1035
1122
  }
1036
1123
  return;
1037
1124
  }
1038
1125
  if (r.needs !== void 0 || r.urgencyHint !== void 0)
1039
- ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
1126
+ ctx.addIssue({ code: z14.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
1040
1127
  if (!r.context)
1041
- ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
1128
+ ctx.addIssue({ code: z14.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
1042
1129
  if (!r.select)
1043
- ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
1130
+ ctx.addIssue({ code: z14.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
1044
1131
  const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
1045
1132
  if (needsOptions && !r.options?.length)
1046
- ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
1133
+ ctx.addIssue({ code: z14.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
1047
1134
  if (!needsOptions && r.options?.length)
1048
- ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
1135
+ ctx.addIssue({ code: z14.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
1049
1136
  });
1050
- var NotifyStatusSchema = z13.enum(["pending", "answered", "ignored"]);
1051
- var AgentStateSchema = z13.enum(["idle", "in_progress", "completed", "needs_input"]);
1052
- var TurnSchema = z13.object({
1053
- prompt: z13.string(),
1054
- reply: z13.string()
1137
+ var NotifyStatusSchema = z14.enum(["pending", "answered", "ignored"]);
1138
+ var AgentStateSchema = z14.enum(["idle", "in_progress", "completed", "needs_input"]);
1139
+ var TurnSchema = z14.object({
1140
+ prompt: z14.string(),
1141
+ reply: z14.string()
1055
1142
  });
1056
- var UserAnswerSchema = z13.discriminatedUnion("kind", [
1057
- z13.object({ kind: z13.literal("option"), optionId: z13.string(), label: z13.string().optional() }),
1058
- z13.object({ kind: z13.literal("text"), text: z13.string() }),
1059
- z13.object({ kind: z13.literal("ignored") }),
1060
- z13.object({ kind: z13.literal("multi"), optionIds: z13.array(z13.string()), labels: z13.array(z13.string()).optional() }),
1061
- z13.object({ kind: z13.literal("ranked"), optionIds: z13.array(z13.string()), labels: z13.array(z13.string()).optional() }),
1062
- z13.object({ kind: z13.literal("clarify"), chunks: z13.array(z13.string()).min(1) }),
1063
- z13.object({ kind: z13.literal("confirm"), approved: z13.boolean() }),
1064
- z13.object({ kind: z13.literal("turns"), turns: z13.array(TurnSchema).min(1) }),
1143
+ var UserAnswerSchema = z14.discriminatedUnion("kind", [
1144
+ z14.object({ kind: z14.literal("option"), optionId: z14.string(), label: z14.string().optional() }),
1145
+ z14.object({ kind: z14.literal("text"), text: z14.string() }),
1146
+ z14.object({ kind: z14.literal("ignored") }),
1147
+ z14.object({ kind: z14.literal("multi"), optionIds: z14.array(z14.string()), labels: z14.array(z14.string()).optional() }),
1148
+ z14.object({ kind: z14.literal("ranked"), optionIds: z14.array(z14.string()), labels: z14.array(z14.string()).optional() }),
1149
+ z14.object({ kind: z14.literal("clarify"), chunks: z14.array(z14.string()).min(1) }),
1150
+ z14.object({ kind: z14.literal("confirm"), approved: z14.boolean() }),
1151
+ z14.object({ kind: z14.literal("turns"), turns: z14.array(TurnSchema).min(1) }),
1065
1152
  /** An auto-answer derived from the user's PAST decisions (docs/history/precedent-design.md §2):
1066
1153
  * delivered through the same settle/await path as a human answer, carrying the judge's
1067
1154
  * derivation and the precedent ids it grew from. Always paired with a visible trail
1068
1155
  * card the user can reply to — the broker never overrides the user. */
1069
- z13.object({ kind: z13.literal("precedent"), answer: z13.string(), derivation: z13.string(), sources: z13.array(z13.string()).min(1) })
1156
+ z14.object({ kind: z14.literal("precedent"), answer: z14.string(), derivation: z14.string(), sources: z14.array(z14.string()).min(1) })
1070
1157
  ]);
1071
- var IntentSchema = z13.object({
1158
+ var IntentSchema = z14.object({
1072
1159
  // The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
1073
1160
  // it by two ("detail", "feedback"), and because the settle handler parsed the array
1074
1161
  // all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
1075
1162
  // questions included. Found auditing five calls' stored feedback, 2026-08-01.
1076
- kind: z13.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
1077
- detail: z13.string(),
1163
+ kind: z14.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
1164
+ detail: z14.string(),
1078
1165
  /** Defer only: seconds until the callback the caller asked for, when something upstream
1079
1166
  * already read the time. Nothing sets it today (#397 documented an MCP parser that was
1080
1167
  * never written) — the API reads the defer's `detail` itself with `notes/when.ts`
1081
1168
  * (`parseDelay`, #1292), and a value here simply wins over that reading. */
1082
- dueInSeconds: z13.number().int().positive().optional(),
1169
+ dueInSeconds: z14.number().int().positive().optional(),
1083
1170
  /** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
1084
1171
  * already read the utterance, so `signals.kind` stops defaulting to
1085
1172
  * 'other' on every row. A table that records that something was wrong and nothing
1086
1173
  * about what cannot answer "is the bot looping less this week?". */
1087
- fault: z13.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
1174
+ fault: z14.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
1088
1175
  });
1089
- var RideAlongSchema = z13.object({
1176
+ var RideAlongSchema = z14.object({
1090
1177
  /** The note this came from — assign/clarify/close it through /api/notes/:id. */
1091
- noteId: z13.string(),
1178
+ noteId: z14.string(),
1092
1179
  /** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
1093
- text: z13.string(),
1180
+ text: z14.string(),
1094
1181
  /** The thread to report back on, when the note was dispatched over the request rail. */
1095
- parentId: z13.string().nullable()
1182
+ parentId: z14.string().nullable()
1096
1183
  });
1097
- var AwaitItemSchema = z13.discriminatedUnion("type", [
1098
- z13.object({
1099
- type: z13.literal("reply"),
1100
- parentId: z13.string(),
1101
- notificationId: z13.string(),
1102
- workId: z13.string().uuid().optional(),
1103
- decisionId: z13.string().uuid().optional(),
1184
+ var AwaitItemSchema = z14.discriminatedUnion("type", [
1185
+ z14.object({
1186
+ type: z14.literal("reply"),
1187
+ parentId: z14.string(),
1188
+ notificationId: z14.string(),
1189
+ workId: z14.string().uuid().optional(),
1190
+ decisionId: z14.string().uuid().optional(),
1104
1191
  answer: UserAnswerSchema,
1105
1192
  /** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
1106
1193
  * #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
@@ -1110,109 +1197,109 @@ var AwaitItemSchema = z13.discriminatedUnion("type", [
1110
1197
  * "call me back after you merge" in their own words decides for itself what to do,
1111
1198
  * and now knows exactly which call to make. Absent when either half is missing —
1112
1199
  * a sentence with a hole in it is worse than no sentence. */
1113
- note: z13.string().optional(),
1200
+ note: z14.string().optional(),
1114
1201
  /** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
1115
1202
  * shaped answer was mapped from, filtered to its own claims. There is no second list
1116
1203
  * of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
1117
1204
  * 2026-09-04): the agent reads the sentence and decides. */
1118
- transcript: z13.string().optional(),
1205
+ transcript: z14.string().optional(),
1119
1206
  /** Coverage report (#396), when the ask declared `points`: which of them this
1120
1207
  * answer addressed. Missing points = re-ask or proceed knowingly partial. */
1121
- covered: z13.array(z13.string()).optional(),
1208
+ covered: z14.array(z14.string()).optional(),
1122
1209
  /** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
1123
1210
  * became free. Only `reply` and `idle` carry it: those are the two outcomes that
1124
1211
  * END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
1125
1212
  * agent a side-quest while it is still holding the line is how the main thing gets
1126
1213
  * dropped. Absent/empty = nothing owed. */
1127
- also: z13.array(RideAlongSchema).optional()
1214
+ also: z14.array(RideAlongSchema).optional()
1128
1215
  }),
1129
- z13.object({
1130
- type: z13.literal("remind"),
1131
- parentId: z13.string(),
1132
- notificationId: z13.string(),
1133
- remindAt: z13.string().datetime({ offset: true }),
1216
+ z14.object({
1217
+ type: z14.literal("remind"),
1218
+ parentId: z14.string(),
1219
+ notificationId: z14.string(),
1220
+ remindAt: z14.string().datetime({ offset: true }),
1134
1221
  /** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
1135
- remindInSeconds: z13.number()
1222
+ remindInSeconds: z14.number()
1136
1223
  }),
1137
1224
  /** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
1138
1225
  * post-feedback revision, #633) — the user will never answer this id. Stop
1139
1226
  * awaiting it; the live ask is the thread's newest turn (await that one, or
1140
1227
  * re-orient via contact({})). */
1141
- z13.object({
1142
- type: z13.literal("superseded"),
1143
- parentId: z13.string(),
1144
- notificationId: z13.string()
1228
+ z14.object({
1229
+ type: z14.literal("superseded"),
1230
+ parentId: z14.string(),
1231
+ notificationId: z14.string()
1145
1232
  }),
1146
1233
  /** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
1147
1234
  * revise any of these until the final reply arrives — partial = intelligence,
1148
1235
  * settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
1149
1236
  * irreversibly. If `acts` carries a question aimed at you and you know the answer,
1150
1237
  * contact on the same thread right away — the caller hears it on the same call. */
1151
- z13.object({
1152
- type: z13.literal("partial"),
1153
- notificationId: z13.string(),
1154
- inFlight: z13.literal(true),
1155
- turn: z13.object({
1156
- idx: z13.number(),
1157
- prompt: z13.string(),
1158
- reply: z13.string(),
1159
- acts: z13.array(IntentSchema).nullable().optional()
1238
+ z14.object({
1239
+ type: z14.literal("partial"),
1240
+ notificationId: z14.string(),
1241
+ inFlight: z14.literal(true),
1242
+ turn: z14.object({
1243
+ idx: z14.number(),
1244
+ prompt: z14.string(),
1245
+ reply: z14.string(),
1246
+ acts: z14.array(IntentSchema).nullable().optional()
1160
1247
  })
1161
1248
  }),
1162
- z13.object({
1163
- type: z13.literal("idle"),
1164
- also: z13.array(RideAlongSchema).optional(),
1249
+ z14.object({
1250
+ type: z14.literal("idle"),
1251
+ also: z14.array(RideAlongSchema).optional(),
1165
1252
  /** Is a call live for this agent's user right now? The SDK polls the partial stream
1166
1253
  * (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
1167
1254
  * during a live call, and polling for one on a banner/message was a wasted HTTP call +
1168
1255
  * 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
1169
1256
  * Absent = an older API → the SDK keeps polling, exactly as before. */
1170
- inFlight: z13.boolean().optional()
1257
+ inFlight: z14.boolean().optional()
1171
1258
  })
1172
1259
  ]);
1173
- var VoiceKeySchema = z13.enum(["rachel", "george", "jessica", "brian", "lily"]);
1174
- var AgendaTurnSchema = z13.object({
1260
+ var VoiceKeySchema = z14.enum(["rachel", "george", "jessica", "brian", "lily"]);
1261
+ var AgendaTurnSchema = z14.object({
1175
1262
  /** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
1176
1263
  * it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
1177
1264
  * sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
1178
1265
  * completion of a streamed re-plan carries the published sentence again, and the walk
1179
1266
  * drops what it already said by identity, never by the API's guess of what was polled.
1180
1267
  * Absent on plans nothing streams (a ring plan, a floor). */
1181
- id: z13.string().optional(),
1268
+ id: z14.string().optional(),
1182
1269
  /** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
1183
1270
  * the planner declares duplicates instead of asking them twice. */
1184
- coveredIds: z13.array(z13.string()).optional(),
1271
+ coveredIds: z14.array(z14.string()).optional(),
1185
1272
  /** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
1186
1273
  * (owner, 2026-09-25), and a count here refused whole plans. */
1187
- info: z13.array(z13.string().min(1)).default([]),
1274
+ info: z14.array(z14.string().min(1)).default([]),
1188
1275
  /** THE ID OF EACH SENTENCE, when the plan names them (turns.md): a talker message's sentences are its
1189
1276
  * `info`, one piece each, and `units[i]` is the id the bot's line receipt carries for `info[i]`
1190
1277
  * (`<cycle>:<key>:<n>`), so what was heard is known per sentence. Absent on every other turn. */
1191
- units: z13.array(z13.string().min(1)).optional(),
1192
- question: z13.string().min(1).nullable(),
1278
+ units: z14.array(z14.string().min(1)).optional(),
1279
+ question: z14.string().min(1).nullable(),
1193
1280
  /** True on the one turn carrying the agent's own declared question. */
1194
- asks: z13.boolean().optional(),
1281
+ asks: z14.boolean().optional(),
1195
1282
  /** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
1196
1283
  * Absent on a single-claim plan (the session's own claim) and on shared context turns,
1197
1284
  * which route nothing. */
1198
- claimId: z13.string().optional(),
1285
+ claimId: z14.string().optional(),
1199
1286
  /** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
1200
- voice: z13.string().optional(),
1287
+ voice: z14.string().optional(),
1201
1288
  /** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
1202
1289
  * whose request this is: an item that folded in from another agent arrived as a bare
1203
1290
  * non-sequitur ("First real production sign-in is yours to make whenever you want.")
1204
1291
  * and the owner answered "What?". The bot names the agent before its first turn. */
1205
- agent: z13.string().optional(),
1292
+ agent: z14.string().optional(),
1206
1293
  /** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
1207
1294
  * same id a face is minted from. A name is not an identity: two pairings may be called
1208
1295
  * "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
1209
1296
  * so "who said that" survives the call, and it rides PER TURN because a coalesced call
1210
1297
  * speaks for several agents — the turn is the only place that knows which. */
1211
- agentId: z13.string().optional(),
1298
+ agentId: z14.string().optional(),
1212
1299
  /** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
1213
1300
  * the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
1214
1301
  * question turn — the talker owns re-asking and the waits, so `question` stays null. */
1215
- onTable: z13.boolean().optional(),
1302
+ onTable: z14.boolean().optional(),
1216
1303
  /** THE CARRIED ITEM THIS LINE TELLS (Goal 2a1e50eb, owner 2026-10-14: "Build the whole design") — a
1217
1304
  * request the Call carries that asks NOTHING: a report, a progress note, another agent's waiting news.
1218
1305
  * Its request Entry id, so the phone can make that agenda row the current one while it is read
@@ -1222,14 +1309,14 @@ var AgendaTurnSchema = z13.object({
1222
1309
  * record tags the line with it (`workId`) — and an answer must never route to an Entry that asks
1223
1310
  * nothing. Absent on a turn speaking for a question (`claimId` says that) and on one speaking for no
1224
1311
  * carried item at all (an opener, a sign-off, Paigy's own line). */
1225
- telling: z13.string().optional(),
1312
+ telling: z14.string().optional(),
1226
1313
  /** PAIGY'S OWN LINE (owner, 2026-10-15; calls ed2cbdce, ad50ad25): a turn that speaks for no item -- an
1227
1314
  * opener, a sign-off, a reply about nothing an agent asked or told -- is recorded as Paigy's, not as the
1228
1315
  * agent that rang. Absent on every turn that speaks for an item (`agentId` names whose), and on plans
1229
1316
  * from before it: a bot that does not know it records the ringing agent, as before. */
1230
- speaksFor: z13.literal("paigy").optional(),
1317
+ speaksFor: z14.literal("paigy").optional(),
1231
1318
  select: SelectShapeSchema.optional(),
1232
- options: z13.array(OptionSchema.omit({ id: true })).optional(),
1319
+ options: z14.array(OptionSchema.omit({ id: true })).optional(),
1233
1320
  /* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
1234
1321
  paces itself now — a short beat between the sentences of a turn, the longer one at its end
1235
1322
  (owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
@@ -1238,58 +1325,58 @@ var AgendaTurnSchema = z13.object({
1238
1325
  /** Whether the walk WAITS for an answer before moving on. Absent = derived as today
1239
1326
  * (a question blocks, context flows). blocking:false on a question = ask and move
1240
1327
  * on, the claim stays pending; blocking:true on context = hold for a reply. */
1241
- blocking: z13.boolean().optional(),
1328
+ blocking: z14.boolean().optional(),
1242
1329
  /** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
1243
1330
  * shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
1244
1331
  * would you need more information"). The walk holds this turn out of its queue; at the queue's end it
1245
1332
  * listens for the last word, and only if that listen is silent is this turn said and asked. If they
1246
1333
  * speak, it is dropped and their words are taken like any reply. */
1247
- ifSilent: z13.boolean().optional()
1334
+ ifSilent: z14.boolean().optional()
1248
1335
  });
1249
1336
  var CLAIM_STALE_MS = 30 * 6e4;
1250
- var CallQuestionSchema = z13.object({
1251
- id: z13.string(),
1252
- entryId: z13.string(),
1253
- title: z13.string(),
1254
- state: z13.enum(["open", "answered"]),
1255
- answer: z13.string().nullable(),
1337
+ var CallQuestionSchema = z14.object({
1338
+ id: z14.string(),
1339
+ entryId: z14.string(),
1340
+ title: z14.string(),
1341
+ state: z14.enum(["open", "answered"]),
1342
+ answer: z14.string().nullable(),
1256
1343
  /** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
1257
1344
  * request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
1258
1345
  * carry for the call's one agent, so the phone draws it with the same seed. Absent when the
1259
1346
  * author is not an agent this account holds (unpaired since, or a person). */
1260
- agent: z13.object({ tokenId: z13.string(), name: z13.string(), voice: VoiceKeySchema.optional() }).optional(),
1347
+ agent: z14.object({ tokenId: z14.string(), name: z14.string(), voice: VoiceKeySchema.optional() }).optional(),
1261
1348
  /** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
1262
1349
  * its need offers, exactly as its own card carries them, present only when one of them has a
1263
1350
  * preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
1264
1351
  * never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
1265
- options: z13.array(OptionSchema).optional(),
1352
+ options: z14.array(OptionSchema).optional(),
1266
1353
  /** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
1267
1354
  * options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
1268
1355
  * numbered chips above the call's controls, so a checklist is never only something read aloud. A
1269
1356
  * field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
1270
1357
  * preview layout for any `options`, and must see exactly what it did. */
1271
- choices: z13.array(OptionSchema).optional(),
1358
+ choices: z14.array(OptionSchema).optional(),
1272
1359
  /** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
1273
- select: z13.enum(["one", "many", "rank"]).optional()
1360
+ select: z14.enum(["one", "many", "rank"]).optional()
1274
1361
  });
1275
- var InboxItemSchema = z13.object({
1276
- id: z13.string(),
1277
- tokenId: z13.string().optional(),
1362
+ var InboxItemSchema = z14.object({
1363
+ id: z14.string(),
1364
+ tokenId: z14.string().optional(),
1278
1365
  status: NotifyStatusSchema,
1279
1366
  context: ContextSchema,
1280
- options: z13.array(OptionSchema).optional(),
1367
+ options: z14.array(OptionSchema).optional(),
1281
1368
  /** The ask's declared coverage points (#396), when the agent sent them. */
1282
- points: z13.array(z13.string()).optional(),
1369
+ points: z14.array(z14.string()).optional(),
1283
1370
  /** Does this claim want an ANSWER, or is it telling you something? Written per row from
1284
1371
  * `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
1285
1372
  * its acknowledge affordance: without it a status update offers a text box and a dismiss,
1286
1373
  * and neither of those is "got it" (owner, 2026-08-10). */
1287
- asks: z13.boolean().optional(),
1374
+ asks: z14.boolean().optional(),
1288
1375
  /** When a live process last pulsed for this row's agent — the liveness input for
1289
1376
  * "working requires a pulse" (#928): the list said "Working…" from agent_state alone
1290
1377
  * while the party called the same dead claim stalled. Absent = no token/no data,
1291
1378
  * which must never CLAIM stalled. */
1292
- lastSeenAt: z13.string().optional(),
1379
+ lastSeenAt: z14.string().optional(),
1293
1380
  /** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
1294
1381
  * the `notification_events` ledger (trigger-written since 20260621010000, so every row a
1295
1382
  * user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
@@ -1299,7 +1386,7 @@ var InboxItemSchema = z13.object({
1299
1386
  * work. Reading the row's birth as the claim's age brands that "No update in 8h" the
1300
1387
  * instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
1301
1388
  * `createdAt`. */
1302
- agentStateAt: z13.string().datetime().optional(),
1389
+ agentStateAt: z14.string().datetime().optional(),
1303
1390
  /** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
1304
1391
  * 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
1305
1392
  * superseded or cancelled need is no longer a question anyone is asked). Present only on a
@@ -1313,35 +1400,39 @@ var InboxItemSchema = z13.object({
1313
1400
  * `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
1314
1401
  * the card's own concise heading; `answer` the accepted answer in words, null while open. It
1315
1402
  * REPLACED `agenda` (turns), which nothing ever filled. */
1316
- questions: z13.array(CallQuestionSchema).optional(),
1403
+ questions: z14.array(CallQuestionSchema).optional(),
1317
1404
  /** The same call carrying order, including request Entries with no DecisionNeed as reports.
1318
1405
  * Additive: questions stays decision-only for every other surface and older bundles. */
1319
- callItems: z13.array(CallQuestionSchema.extend({ state: z13.enum(["open", "answered", "report"]), acknowledged: z13.boolean().optional() })).optional(),
1320
- visuals: z13.array(VisualSchema).optional(),
1406
+ callItems: z14.array(CallQuestionSchema.extend({ state: z14.enum(["open", "answered", "report"]), acknowledged: z14.boolean().optional() })).optional(),
1407
+ visuals: z14.array(VisualSchema).optional(),
1321
1408
  /** The connected agent's name (the single pairing name — user-typed, or the
1322
1409
  * agent's suggestion, or a default silly name). */
1323
- name: z13.string(),
1410
+ name: z14.string(),
1324
1411
  /** The pairing's assigned voice (#462); absent = the default voice. */
1325
1412
  voice: VoiceKeySchema.optional(),
1326
- repo: z13.string().optional(),
1327
- branch: z13.string().optional(),
1328
- createdAt: z13.string().datetime(),
1329
- snoozedUntil: z13.string().datetime().optional(),
1413
+ repo: z14.string().optional(),
1414
+ branch: z14.string().optional(),
1415
+ createdAt: z14.string().datetime(),
1416
+ snoozedUntil: z14.string().datetime().optional(),
1417
+ /** A past card the person dismissed that Restore can take back (#2699): its newest close is their
1418
+ * dismissal, with what it withdrew recorded (`private.restore_delivery`). Absent on everything else —
1419
+ * a put-off, a card an ended Goal or a settled question closed, and every open card. */
1420
+ restorable: z14.boolean().optional(),
1330
1421
  agentState: AgentStateSchema.default("idle"),
1331
1422
  /** Whose action the item is waiting on: "you" = an agent asked you (the default,
1332
1423
  * every agent→user notification); "agent" = you sent a request and it's awaiting the
1333
1424
  * agent (held in the inbox until the agent replies on the thread). */
1334
- turn: z13.enum(["you", "agent"]).default("you"),
1425
+ turn: z14.enum(["you", "agent"]).default("you"),
1335
1426
  /** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
1336
1427
  * the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
1337
1428
  * error badge + Retry. */
1338
- error: z13.string().optional(),
1339
- clarifies: z13.string().optional(),
1429
+ error: z14.string().optional(),
1430
+ clarifies: z14.string().optional(),
1340
1431
  /** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
1341
1432
  * live"). Present only while an open Call Delivery carries the card's request Entry — read
1342
1433
  * off the same open list the card came from, so it clears when the Call does. A card is the
1343
1434
  * backup for a call not taken; while the call has it, the call is where it is answered. */
1344
- onCall: z13.literal(true).optional(),
1435
+ onCall: z14.literal(true).optional(),
1345
1436
  /** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
1346
1437
  * declined, and what the ladder will do next — read off the cron's own row, never computed
1347
1438
  * on the phone. Present only while a `declined` receipt stands on the card's last Call.
@@ -1351,31 +1442,31 @@ var InboxItemSchema = z13.object({
1351
1442
  * It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
1352
1443
  * never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
1353
1444
  * and the roster's `unreached` read `declinedAt`, and stand while it does. */
1354
- ring: z13.object({
1355
- declinedAt: z13.string().datetime(),
1356
- anchorAt: z13.string().datetime(),
1357
- nextRingAt: z13.string().datetime().nullable(),
1358
- step: z13.number().int()
1445
+ ring: z14.object({
1446
+ declinedAt: z14.string().datetime(),
1447
+ anchorAt: z14.string().datetime(),
1448
+ nextRingAt: z14.string().datetime().nullable(),
1449
+ step: z14.number().int()
1359
1450
  }).optional(),
1360
1451
  /** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
1361
1452
  * Absent for anything never delivered through a push, and for older rows written before
1362
1453
  * the reason was recorded. Deliberately a debug affordance, shown small (owner,
1363
1454
  * 2026-08-07) — its real job is to give "this didn't need a call" something to be
1364
1455
  * feedback ABOUT. */
1365
- why: z13.object({
1456
+ why: z14.object({
1366
1457
  asked: NotifyLevelSchema,
1367
1458
  got: NotifyLevelSchema,
1368
- because: z13.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
1369
- line: z13.string()
1459
+ because: z14.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
1460
+ line: z14.string()
1370
1461
  }).optional(),
1371
- select: z13.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
1372
- confirmStyle: z13.enum(["yesno", "approve"]).default("yesno").describe(
1462
+ select: z14.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
1463
+ confirmStyle: z14.enum(["yesno", "approve"]).default("yesno").describe(
1373
1464
  "Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
1374
1465
  ),
1375
1466
  /** Real downstream work is stuck behind this one — set by the agent, independent of
1376
1467
  * urgency (see the main README's "premier use case" + docs/history/notify-states.md). Drives the
1377
1468
  * inbox's blocking badge and the extra confirm step before dismissing it. */
1378
- blocking: z13.boolean().default(false),
1469
+ blocking: z14.boolean().default(false),
1379
1470
  /** WHAT FREED THIS QUESTION (owner, 2026-10-16, Goal 2383ef14: "we don't really show the question
1380
1471
  * at the top until the question it could reply to with is resolved. And then we show it again
1381
1472
  * with the context of the other thing that was resolved"; asked how it should carry that, "The
@@ -1383,25 +1474,25 @@ var InboxItemSchema = z13.object({
1383
1474
  * (`private.ask_back`) and drops out of the front while it waits; when it comes back, the card
1384
1475
  * draws their question and the answer it got ABOVE the ask, as one card. Absent on a question
1385
1476
  * nothing ever held back. */
1386
- freedBy: z13.object({
1387
- questionId: z13.string(),
1388
- asked: z13.string(),
1389
- answer: z13.string(),
1390
- answeredAt: z13.string(),
1477
+ freedBy: z14.object({
1478
+ questionId: z14.string(),
1479
+ asked: z14.string(),
1480
+ answer: z14.string(),
1481
+ answeredAt: z14.string(),
1391
1482
  /** The answer in ten words, when the talker wrote one beside it. */
1392
- summary: z13.string().optional()
1483
+ summary: z14.string().optional()
1393
1484
  }).optional(),
1394
1485
  /** WHAT THIS CARD ANSWERS (owner, 2026-10-27, Goal 81b6a9d6: "this reads very clearly as an answer to
1395
1486
  * something I asked, but I don't see the question… To show the question near the top of the card").
1396
1487
  * An agent's card that is its answer to one of the person's own questions carries that question's
1397
1488
  * words, drawn above it as "You asked" (`public.questions_answered_by`, read beside the card read).
1398
1489
  * Absent on every card that answers nothing of theirs, and on a held question, which has `freedBy`. */
1399
- answering: z13.object({ asked: z13.string() }).optional(),
1490
+ answering: z14.object({ asked: z14.string() }).optional(),
1400
1491
  /** WHEN ITS AGENT LAST BUMPED THIS QUESTION (#2855, `contact`'s `bumpQuestionIds`): the agent asked
1401
1492
  * the person to notice it again, and For you puts it first, the latest bump on top
1402
1493
  * (`question/order.ts`). Absent on a question never bumped, and on every card from an API older
1403
1494
  * than the bump. */
1404
- bumpedAt: z13.string().optional(),
1495
+ bumpedAt: z14.string().optional(),
1405
1496
  /** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
1406
1497
  answer: UserAnswerSchema.optional(),
1407
1498
  /** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
@@ -1409,17 +1500,17 @@ var InboxItemSchema = z13.object({
1409
1500
  * for a request that asks nothing), whether its content is sealed, and that Goal's state. The
1410
1501
  * answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
1411
1502
  * here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
1412
- communication: z13.object({
1413
- deliveryId: z13.string(),
1414
- kind: z13.enum(["notification", "call"]),
1415
- entryId: z13.string(),
1416
- goalIds: z13.array(z13.string()),
1417
- decisionNeedId: z13.string().optional(),
1418
- sealed: z13.boolean(),
1419
- goalState: z13.string().optional(),
1503
+ communication: z14.object({
1504
+ deliveryId: z14.string(),
1505
+ kind: z14.enum(["notification", "call"]),
1506
+ entryId: z14.string(),
1507
+ goalIds: z14.array(z14.string()),
1508
+ decisionNeedId: z14.string().optional(),
1509
+ sealed: z14.boolean(),
1510
+ goalState: z14.string().optional(),
1420
1511
  /** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
1421
1512
  * and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
1422
- goalTitle: z13.string().optional()
1513
+ goalTitle: z14.string().optional()
1423
1514
  }).optional(),
1424
1515
  /** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
1425
1516
  * by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
@@ -1431,27 +1522,57 @@ var InboxItemSchema = z13.object({
1431
1522
  * own moves (its Goal's state and name, whether a ring is
1432
1523
  * live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
1433
1524
  * with no rev is simply always re-sent. */
1434
- rev: z13.string().optional()
1525
+ rev: z14.string().optional()
1435
1526
  });
1436
1527
  var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
1437
- var PushTokenSchema = z13.object({
1438
- voipToken: z13.string().min(1).optional(),
1439
- alertToken: z13.string().min(1).optional(),
1440
- fcmToken: z13.string().min(1).optional(),
1441
- platform: z13.enum(["ios", "android"])
1528
+ var PushDeviceIdSchema = z14.string().regex(/^[A-Za-z0-9._:-]{1,200}$/, "not an install id");
1529
+ var PushDeviceSchema = z14.object({
1530
+ deviceId: z14.string(),
1531
+ platform: z14.enum(["ios", "android"]),
1532
+ label: z14.string().nullable(),
1533
+ model: z14.string().nullable(),
1534
+ /** Calls and banners go here. */
1535
+ rings: z14.boolean(),
1536
+ /** It has a token a call can ring. */
1537
+ ringable: z14.boolean(),
1538
+ chosenAt: z14.string().nullable(),
1539
+ updatedAt: z14.string()
1540
+ });
1541
+ var PushTokenDeviceSchema = z14.object({
1542
+ deviceId: z14.string(),
1543
+ rings: z14.boolean(),
1544
+ ask: z14.boolean(),
1545
+ ringsOn: z14.object({ deviceId: z14.string().nullable(), label: z14.string().nullable() }).nullable()
1546
+ });
1547
+ var PushRingsSchema = z14.object({ rings: z14.boolean() });
1548
+ var PushTokenSchema = z14.object({
1549
+ voipToken: z14.string().min(1).optional(),
1550
+ alertToken: z14.string().min(1).optional(),
1551
+ fcmToken: z14.string().min(1).optional(),
1552
+ platform: z14.enum(["ios", "android"]),
1553
+ /** WHICH INSTALL POSTED IT (owner, 2026-11-05: "Ask on each new device"). Absent from every
1554
+ * build before it, and then the post is exactly what it always was: the one row per account.
1555
+ * With it, the install has its own row and calls ring only where the person chose. */
1556
+ deviceId: PushDeviceIdSchema.optional(),
1557
+ /** The install's name as the person knows it ("iPhone", "iPad"), and its model. */
1558
+ label: z14.string().trim().min(1).max(80).optional(),
1559
+ model: z14.string().trim().min(1).max(80).optional(),
1560
+ /** The person's choice, when the post carries it; absent, the install keeps what it has (and
1561
+ * its first ring token decides once: it rings unless another install already does). */
1562
+ rings: z14.boolean().optional()
1442
1563
  }).superRefine((v, ctx) => {
1443
1564
  if (v.platform !== "ios") return;
1444
1565
  for (const field of ["voipToken", "alertToken"]) {
1445
1566
  const token = v[field];
1446
1567
  if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
1447
1568
  ctx.addIssue({
1448
- code: z13.ZodIssueCode.custom,
1569
+ code: z14.ZodIssueCode.custom,
1449
1570
  path: [field],
1450
1571
  message: `not an APNs device token (want 64 hex chars, got ${token.length})`
1451
1572
  });
1452
1573
  }
1453
1574
  });
1454
- var MissedCallSchema = z13.enum([
1575
+ var MissedCallSchema = z14.enum([
1455
1576
  "retry_10m",
1456
1577
  "retry_30m",
1457
1578
  "retry_60m",
@@ -1464,31 +1585,31 @@ var MissedCallSchema = z13.enum([
1464
1585
  var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
1465
1586
  var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
1466
1587
  var THEIR_QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} their time; the count waits for morning.`;
1467
- var BrokerTuningSchema = z13.object({
1588
+ var BrokerTuningSchema = z14.object({
1468
1589
  /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
1469
- ackVerbosity: z13.enum(["normal", "none"]).optional(),
1590
+ ackVerbosity: z14.enum(["normal", "none"]).optional(),
1470
1591
  /** How readily the mapper asks its one clarification: 'low' = only when truly
1471
1592
  * uninterpretable, 'high' = whenever not fully certain. */
1472
- clarifyEagerness: z13.enum(["low", "normal", "high"]).optional(),
1593
+ clarifyEagerness: z14.enum(["low", "normal", "high"]).optional(),
1473
1594
  /** The user's own shorthand: when they say `say`, they mean `mean`. */
1474
- phrasebook: z13.array(z13.object({ say: z13.string().min(1).max(60), mean: z13.string().min(1).max(120) })).max(24).optional(),
1595
+ phrasebook: z14.array(z14.object({ say: z14.string().min(1).max(60), mean: z14.string().min(1).max(120) })).max(24).optional(),
1475
1596
  /** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
1476
1597
  * which is every account today — means the agent's own words decide, per ask: a call
1477
1598
  * about an English ask opens in English. This is the only thing that overrides that,
1478
1599
  * and a live caller who switches language mid-call still outranks it (broker/lang.ts).
1479
1600
  * Set per user (no UI yet), like `voiceTuning`. */
1480
- language: z13.enum(["en", "es"]).optional()
1601
+ language: z14.enum(["en", "es"]).optional()
1481
1602
  });
1482
- var UserSettingsSchema = z13.object({
1483
- permissions: z13.object({
1484
- call: z13.boolean(),
1485
- banner: z13.boolean(),
1486
- push: z13.boolean()
1603
+ var UserSettingsSchema = z14.object({
1604
+ permissions: z14.object({
1605
+ call: z14.boolean(),
1606
+ banner: z14.boolean(),
1607
+ push: z14.boolean()
1487
1608
  }),
1488
1609
  /** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
1489
1610
  * they are an enum on a live column across every account, and the rename is a rename of
1490
1611
  * what people read (owner, 2026-09-30). */
1491
- sessionMode: z13.enum(["default", "all_calls", "silent"]),
1612
+ sessionMode: z14.enum(["default", "all_calls", "silent"]),
1492
1613
  /** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
1493
1614
  * optional long after DateNight stopped reading it, on the theory that a phone on an older
1494
1615
  * bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
@@ -1497,7 +1618,7 @@ var UserSettingsSchema = z13.object({
1497
1618
  * an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
1498
1619
  * next dead field for the same reason. */
1499
1620
  /** Opt-in (default false) to using your content to improve Paigy and train models. */
1500
- improveConsent: z13.boolean(),
1621
+ improveConsent: z14.boolean(),
1501
1622
  missedCall: MissedCallSchema.default("backoff_standard"),
1502
1623
  /** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
1503
1624
  * (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
@@ -1505,14 +1626,14 @@ var UserSettingsSchema = z13.object({
1505
1626
  * Optional, NOT defaulted: a stale client PATCHing the full settings object
1506
1627
  * must not silently reset this privacy choice. Absent = leave unchanged on
1507
1628
  * write, 'hosted' on read (see store.ts). */
1508
- voiceMode: z13.enum(["hosted", "on_device"]).optional(),
1629
+ voiceMode: z14.enum(["hosted", "on_device"]).optional(),
1509
1630
  /** `talk` lived here until #2373: it gated auto-play, which went in #2374. An older bundle's
1510
- * `talk` is stripped like `silentPush` was; the `user_settings.talk` column outlives it until
1511
- * its own drop migration (#2373, step two). */
1631
+ * `talk` is stripped like `silentPush` was, and the `user_settings.talk` column went in
1632
+ * 20261028142255. */
1512
1633
  /** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
1513
1634
  * load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
1514
1635
  * newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
1515
- callDiagnostics: z13.boolean().optional(),
1636
+ callDiagnostics: z14.boolean().optional(),
1516
1637
  /** Per-user ring budget (#603): calls per rolling day before further calls
1517
1638
  * degrade to banner. Absent = the global default (25). A number, never a
1518
1639
  * bypass — every account keeps a ceiling. No UI; set per user for testing. */
@@ -1520,7 +1641,7 @@ var UserSettingsSchema = z13.object({
1520
1641
  * payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
1521
1642
  * slower speaker). No API-side semantics; the bot resolves each key with its
1522
1643
  * own defaults. Set per user (no UI yet); absent = bot defaults. */
1523
- voiceTuning: z13.record(z13.string(), z13.union([z13.number(), z13.string()])).optional(),
1644
+ voiceTuning: z14.record(z14.string(), z14.union([z14.number(), z14.string()])).optional(),
1524
1645
  /** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
1525
1646
  * only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
1526
1647
  * an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
@@ -1529,71 +1650,75 @@ var UserSettingsSchema = z13.object({
1529
1650
  * that failure reads as the reminder rail being unreliable rather than as a missing
1530
1651
  * setting. Absent = a spoken time can't be landed, so the reminder rides the next
1531
1652
  * call — honest about what we know. */
1532
- timezone: z13.string().min(1).max(64).optional(),
1653
+ timezone: z14.string().min(1).max(64).optional(),
1533
1654
  /** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
1534
1655
  * clobber guard as voiceMode: absent = leave unchanged on write. */
1535
1656
  broker: BrokerTuningSchema.optional()
1536
1657
  });
1537
- var GoalLatestSchema = z13.object({
1538
- from: z13.enum(["person", "agent"]),
1539
- said: z13.string(),
1540
- at: z13.string(),
1541
- entryId: z13.string(),
1658
+ var GoalLatestSchema = z14.object({
1659
+ from: z14.enum(["person", "agent"]),
1660
+ said: z14.string(),
1661
+ at: z14.string(),
1662
+ entryId: z14.string(),
1542
1663
  /** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
1543
1664
  * at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
1544
1665
  * none was. The Work row reads it before `said`. */
1545
- summary: z13.string().optional(),
1666
+ summary: z14.string().optional(),
1546
1667
  /** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
1547
1668
  * of each unit"): each at most 50 characters, in order; absent when it has none. */
1548
- titles: z13.array(z13.string()).optional(),
1669
+ titles: z14.array(z14.string()).optional(),
1549
1670
  /** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
1550
1671
  * update's own line, before its units; the row caps it at 200. Absent on anything else. */
1551
- line: z13.string().optional()
1672
+ line: z14.string().optional(),
1673
+ /** THE PERSON'S ENTRIES IT CARRIED TO AN AGENT, IN THEIR WORDS (owner, 2026-11-05: "paraphrasing shouldn't change
1674
+ * attribution in the UI"): on a message Paigy sent an agent, the person's Entries its Delivery relays whole. A row
1675
+ * draws it as theirs. Absent on anything else, and from a door older than it. */
1676
+ carries: z14.array(z14.string()).optional()
1552
1677
  });
1553
- var HistoryWorkSchema = z13.object({
1554
- id: z13.string(),
1555
- title: z13.string(),
1556
- state: z13.enum(["done", "cancelled"]),
1678
+ var HistoryWorkSchema = z14.object({
1679
+ id: z14.string(),
1680
+ title: z14.string(),
1681
+ state: z14.enum(["done", "cancelled"]),
1557
1682
  /** Who held it (`agent:<tokenId>` or `human:<userId>`). */
1558
- assignee: z13.string(),
1683
+ assignee: z14.string(),
1559
1684
  latest: GoalLatestSchema.nullable().optional()
1560
1685
  });
1561
- var HistoryEntrySchema = z13.union([
1562
- z13.object({ at: z13.string(), card: InboxItemSchema }),
1563
- z13.object({ at: z13.string(), work: HistoryWorkSchema })
1686
+ var HistoryEntrySchema = z14.union([
1687
+ z14.object({ at: z14.string(), card: InboxItemSchema }),
1688
+ z14.object({ at: z14.string(), work: HistoryWorkSchema })
1564
1689
  ]);
1565
- var HistoryPageSchema = z13.object({
1566
- entries: z13.array(HistoryEntrySchema),
1567
- next: z13.string().nullable()
1690
+ var HistoryPageSchema = z14.object({
1691
+ entries: z14.array(HistoryEntrySchema),
1692
+ next: z14.string().nullable()
1568
1693
  });
1569
1694
  var ACTIVITY_LINES = 2;
1570
1695
  var ACTIVITY_LINE_MAX = 80;
1571
- var AgentActivitySchema = z13.object({
1696
+ var AgentActivitySchema = z14.object({
1572
1697
  /** Oldest first, so the newest line is last — the one that replaces in place. */
1573
- lines: z13.array(z13.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
1698
+ lines: z14.array(z14.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
1574
1699
  /** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
1575
1700
  * that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
1576
- at: z13.string().datetime()
1701
+ at: z14.string().datetime()
1577
1702
  });
1578
- var ConnectionSummarySchema = z13.object({
1703
+ var ConnectionSummarySchema = z14.object({
1579
1704
  /** The connection = the agent's token id (used to address a request). */
1580
- id: z13.string(),
1705
+ id: z14.string(),
1581
1706
  /** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
1582
1707
  * never talks); "agent" = an identity that sends. The roster and devices surfaces split
1583
1708
  * on this. Optional/absent reads as "agent" (a row predating the kind column). See
1584
1709
  * docs/server/tokens/devices-vs-agents-design.md. */
1585
- kind: z13.enum(["device", "agent"]).optional(),
1710
+ kind: z14.enum(["device", "agent"]).optional(),
1586
1711
  /** For an agent, the token id of the DEVICE that minted it — so agents group under their
1587
1712
  * machine, and revoking a device cascades to them. Null on devices, and on unlinked
1588
1713
  * agents (provider-managed, hatched by another agent, or minted before the link existed);
1589
1714
  * a phone-launched agent is linked to the computer it runs on since #1602. */
1590
- mintedByDevice: z13.string().nullable().optional(),
1591
- device: z13.string().nullable(),
1715
+ mintedByDevice: z14.string().nullable().optional(),
1716
+ device: z14.string().nullable(),
1592
1717
  /** The agent's display name (the single pairing name). */
1593
- name: z13.string(),
1718
+ name: z14.string(),
1594
1719
  /** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
1595
1720
  * label; null for a local connection. Sourced from the token's provider, not the name. */
1596
- provider: z13.string().nullable(),
1721
+ provider: z14.string().nullable(),
1597
1722
  /** The pairing's assigned voice (#462); null = the default voice. */
1598
1723
  voice: VoiceKeySchema.nullable(),
1599
1724
  /** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
@@ -1604,34 +1729,40 @@ var ConnectionSummarySchema = z13.object({
1604
1729
  * every surface at once and outranks even `sessionMode: all_calls` — a mode the user
1605
1730
  * set once must not overrule a rule they set about one agent. */
1606
1731
  reach: NotifyLevelSchema.nullable().optional(),
1607
- createdAt: z13.string().datetime(),
1732
+ createdAt: z14.string().datetime(),
1608
1733
  /** Most recent notification on this connection, either direction. Null = no contact yet.
1609
1734
  * Drives the agents-page recency grouping (Today / This week / …). */
1610
- lastContactAt: z13.string().datetime().nullable(),
1735
+ lastContactAt: z14.string().datetime().nullable(),
1611
1736
  /** Last presence heartbeat from a running agent process (POST /api/presence) — the
1612
1737
  * desktop app while open. Null = never seen; stale = offline. */
1613
- lastSeenAt: z13.string().datetime().nullable().optional(),
1738
+ lastSeenAt: z14.string().datetime().nullable().optional(),
1614
1739
  /** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
1615
1740
  * its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
1616
1741
  * `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
1617
- lastWorkedAt: z13.string().datetime().nullable().optional(),
1742
+ lastWorkedAt: z14.string().datetime().nullable().optional(),
1743
+ /** A TERMINAL SESSION'S ONLY PULSE (`tokens.listened_at`): `paigy-listen` stamps it on every sweep
1744
+ * (90 s). It is deliberately NOT presence — `last_seen_at` is the desktop host's beat, and a
1745
+ * listener orphaned from a dead conversation must not light the live dot — but it is the one fact
1746
+ * that says a terminal session's process is UP, which is why the wake already reads it
1747
+ * (`wake.ts`, `listener`). Null = no listener sweeping. */
1748
+ listenedAt: z14.string().datetime().nullable().optional(),
1618
1749
  /** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
1619
1750
  * With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
1620
- oldestReadyAt: z13.string().datetime().nullable().optional(),
1751
+ oldestReadyAt: z14.string().datetime().nullable().optional(),
1621
1752
  /** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
1622
1753
  * harness availabilities + granted workspaces — the option set the phone's
1623
1754
  * "new session" sheet offers. Absent for ordinary MCP agents. */
1624
- runtime: z13.object({
1755
+ runtime: z14.object({
1625
1756
  /** The @paigy/harness this host is running — a machine the self-update has not reached
1626
1757
  * shows its age here (`apps/desktop/src/update.ts`). */
1627
- version: z13.string().optional(),
1628
- harnesses: z13.array(z13.object({ name: z13.string(), label: z13.string(), status: z13.string(), models: z13.array(z13.string()).optional() })).optional(),
1629
- workspaces: z13.array(z13.string()).optional(),
1758
+ version: z14.string().optional(),
1759
+ harnesses: z14.array(z14.object({ name: z14.string(), label: z14.string(), status: z14.string(), models: z14.array(z14.string()).optional() })).optional(),
1760
+ workspaces: z14.array(z14.string()).optional(),
1630
1761
  /** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
1631
1762
  * repo, and each repo directly inside one, with its `origin` remote. A session started for
1632
1763
  * work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
1633
1764
  * own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
1634
- repos: z13.array(z13.object({ path: z13.string(), remote: z13.string() })).optional()
1765
+ repos: z14.array(z14.object({ path: z14.string(), remote: z14.string() })).optional()
1635
1766
  }).optional(),
1636
1767
  /** The tail of this agent's working log, when a harness is driving it — the agent page's
1637
1768
  * live strip. Absent for anything the desktop harness isn't running (a hatched identity
@@ -1640,151 +1771,151 @@ var ConnectionSummarySchema = z13.object({
1640
1771
  activity: AgentActivitySchema.optional(),
1641
1772
  /** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
1642
1773
  * false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
1643
- managed: z13.boolean()
1774
+ managed: z14.boolean()
1644
1775
  });
1645
- var LedgerItemSchema = z13.object({ id: z13.string(), parentId: z13.string(), title: z13.string(), createdAt: z13.string() });
1646
- var AgentLedgerSchema = z13.object({
1776
+ var LedgerItemSchema = z14.object({ id: z14.string(), parentId: z14.string(), title: z14.string(), createdAt: z14.string() });
1777
+ var AgentLedgerSchema = z14.object({
1647
1778
  /** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
1648
- agent: z13.object({ id: z13.string(), name: z13.string().nullable(), revokedAt: z13.string().nullable() }),
1779
+ agent: z14.object({ id: z14.string(), name: z14.string().nullable(), revokedAt: z14.string().nullable() }),
1649
1780
  /** Its own questions you have not answered. */
1650
- asks: z13.array(LedgerItemSchema),
1781
+ asks: z14.array(LedgerItemSchema),
1651
1782
  /** Its questions you answered that nobody acted on — still owed to somebody. */
1652
- answered: z13.array(LedgerItemSchema),
1783
+ answered: z14.array(LedgerItemSchema),
1653
1784
  /** Requests you sent it that it never took. */
1654
- requests: z13.array(LedgerItemSchema),
1655
- goals: z13.array(z13.object({ id: z13.string(), outcome: z13.string(), state: z13.string() })),
1656
- callbacks: z13.array(z13.object({ id: z13.string(), parentId: z13.string(), trigger: z13.string(), note: z13.string(), dueAt: z13.string().nullable() }))
1785
+ requests: z14.array(LedgerItemSchema),
1786
+ goals: z14.array(z14.object({ id: z14.string(), outcome: z14.string(), state: z14.string() })),
1787
+ callbacks: z14.array(z14.object({ id: z14.string(), parentId: z14.string(), trigger: z14.string(), note: z14.string(), dueAt: z14.string().nullable() }))
1657
1788
  });
1658
- var ReassignResultSchema = z13.object({
1659
- moved: z13.object({ asks: z13.number(), answered: z13.number(), requests: z13.number(), goals: z13.number(), callbacks: z13.number() }),
1660
- parentId: z13.string().nullable()
1789
+ var ReassignResultSchema = z14.object({
1790
+ moved: z14.object({ asks: z14.number(), answered: z14.number(), requests: z14.number(), goals: z14.number(), callbacks: z14.number() }),
1791
+ parentId: z14.string().nullable()
1661
1792
  });
1662
- var LessonStateSchema = z13.enum(["active", "proposed", "retired"]);
1663
- var LessonViewSchema = z13.object({
1664
- id: z13.string(),
1665
- text: z13.string(),
1793
+ var LessonStateSchema = z14.enum(["active", "proposed", "retired"]);
1794
+ var LessonViewSchema = z14.object({
1795
+ id: z14.string(),
1796
+ text: z14.string(),
1666
1797
  state: LessonStateSchema,
1667
1798
  /** The Goal it is scoped to; null = the whole account. */
1668
- scopeGoalId: z13.string().nullable(),
1669
- goalTitle: z13.string().nullable(),
1670
- version: z13.number(),
1671
- pinned: z13.boolean(),
1799
+ scopeGoalId: z14.string().nullable(),
1800
+ goalTitle: z14.string().nullable(),
1801
+ version: z14.number(),
1802
+ pinned: z14.boolean(),
1672
1803
  /** When the person last wrote its text themselves. */
1673
- editedAt: z13.string().nullable(),
1674
- createdAt: z13.string(),
1675
- updatedAt: z13.string(),
1804
+ editedAt: z14.string().nullable(),
1805
+ createdAt: z14.string(),
1806
+ updatedAt: z14.string(),
1676
1807
  /** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
1677
- sources: z13.array(z13.object({ entryId: z13.string(), words: z13.string().nullable(), at: z13.string() }))
1808
+ sources: z14.array(z14.object({ entryId: z14.string(), words: z14.string().nullable(), at: z14.string() }))
1678
1809
  });
1679
- var QueueQuestionSchema = z13.object({
1810
+ var QueueQuestionSchema = z14.object({
1680
1811
  /** The decision need's id — what an answer is accepted against. */
1681
- id: z13.string(),
1812
+ id: z14.string(),
1682
1813
  /** The words that were asked, from the request Entry that asked them. */
1683
- question: z13.string(),
1814
+ question: z14.string(),
1684
1815
  /** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
1685
1816
  * for a need whose request Entry is carried by no interactive Delivery, which nothing
1686
1817
  * can answer. */
1687
- deliveryId: z13.string().nullable().default(null),
1818
+ deliveryId: z14.string().nullable().default(null),
1688
1819
  /** The Entry the ruling is about. */
1689
- aboutId: z13.string().nullable().default(null),
1820
+ aboutId: z14.string().nullable().default(null),
1690
1821
  /** Empty for a free-text question. */
1691
- options: z13.array(OptionSchema).default([]),
1692
- select: z13.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
1693
- askedAt: z13.string(),
1822
+ options: z14.array(OptionSchema).default([]),
1823
+ select: z14.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
1824
+ askedAt: z14.string(),
1694
1825
  /** Null while the question is open — which is how the page tells the two apart. */
1695
- answeredAt: z13.string().nullable().default(null),
1826
+ answeredAt: z14.string().nullable().default(null),
1696
1827
  /** The ruling in the person's own words, from the contribution that replied — not the
1697
1828
  * option id, which is not something anyone reads back. Null while it is open, and null
1698
1829
  * for a settled question whose reply carried nothing readable. */
1699
- answer: z13.string().nullable().default(null),
1830
+ answer: z14.string().nullable().default(null),
1700
1831
  /** The Goal this question belongs to — a step knows its Goal on its own, not only through
1701
1832
  * an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
1702
1833
  * READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
1703
1834
  * order, its route, home's trees and the list of steps all take a step's Goal from here, so
1704
1835
  * this is the field they agree through rather than each re-deriving it from the row it
1705
1836
  * arrived under. Required because the API projects it on every need it sends. */
1706
- goalId: z13.string(),
1837
+ goalId: z14.string(),
1707
1838
  /** True only while an unmet START gate holds the Goal — a Goal that merely waits to
1708
1839
  * *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
1709
1840
  * the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
1710
1841
  * gate at all. */
1711
- blocked: z13.boolean().default(false)
1842
+ blocked: z14.boolean().default(false)
1712
1843
  });
1713
- var QueueReplySchema = z13.object({
1844
+ var QueueReplySchema = z14.object({
1714
1845
  /** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
1715
1846
  * id) — so the phone can tell a reply it just sent from one the queue already carries, and the
1716
1847
  * walk can name it in its zoom. */
1717
- id: z13.string(),
1848
+ id: z14.string(),
1718
1849
  /** The Goal the note is on. */
1719
- goalId: z13.string(),
1850
+ goalId: z14.string(),
1720
1851
  /** What the note said. */
1721
- note: z13.string(),
1852
+ note: z14.string(),
1722
1853
  /** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
1723
- deliveryId: z13.string(),
1724
- requestEntryId: z13.string(),
1725
- askedAt: z13.string(),
1854
+ deliveryId: z14.string(),
1855
+ requestEntryId: z14.string(),
1856
+ askedAt: z14.string(),
1726
1857
  /** When the person last replied — the window's start. */
1727
- repliedAt: z13.string(),
1858
+ repliedAt: z14.string(),
1728
1859
  /** The person's latest words about it; null when there is nothing readable in them. */
1729
- reply: z13.string().nullable()
1860
+ reply: z14.string().nullable()
1730
1861
  });
1731
- var QueueItemSchema = z13.object({
1732
- id: z13.string(),
1862
+ var QueueItemSchema = z14.object({
1863
+ id: z14.string(),
1733
1864
  /** One-line headline — the first sentence of the outcome. */
1734
- title: z13.string(),
1865
+ title: z14.string(),
1735
1866
  /** The outcome in full, verbatim: the person's own words are what an assignee sees. */
1736
- intent: z13.string(),
1867
+ intent: z14.string(),
1737
1868
  /** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
1738
- state: z13.string(),
1869
+ state: z14.string(),
1739
1870
  /** Who holds it (a participant ref); null when nobody does yet. */
1740
- assignee: z13.string().nullable().default(null),
1871
+ assignee: z14.string().nullable().default(null),
1741
1872
  /** What the agent last said it was doing; null if it has said nothing. */
1742
- progress: z13.string().nullable().default(null),
1873
+ progress: z14.string().nullable().default(null),
1743
1874
  /** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
1744
1875
  * served only while it was written for the current note. Null means show the Goal's name. */
1745
- progressLine: z13.string().nullable().optional(),
1746
- reviewPending: z13.boolean().default(false),
1747
- dueAt: z13.string().nullable().default(null),
1876
+ progressLine: z14.string().nullable().optional(),
1877
+ reviewPending: z14.boolean().default(false),
1878
+ dueAt: z14.string().nullable().default(null),
1748
1879
  /** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
1749
1880
  * with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
1750
- finishedAt: z13.string().nullable().optional(),
1881
+ finishedAt: z14.string().nullable().optional(),
1751
1882
  /** The Goal this one was opened under; null at the root. */
1752
- parentGoalId: z13.string().nullable().default(null),
1883
+ parentGoalId: z14.string().nullable().default(null),
1753
1884
  /** Goals opened under this one — only those the same list holds. */
1754
- childGoalIds: z13.array(z13.string()).default([]),
1885
+ childGoalIds: z14.array(z14.string()).default([]),
1755
1886
  /** Goals this one waits on (start or finish gates). */
1756
- dependencyGoalIds: z13.array(z13.string()).default([]),
1887
+ dependencyGoalIds: z14.array(z14.string()).default([]),
1757
1888
  /** True while any gate is on a Goal that is not done — the walk draws it dashed. */
1758
- blocked: z13.boolean().default(false),
1889
+ blocked: z14.boolean().default(false),
1759
1890
  /** Its questions: every OPEN one, and at most ten settled, newest settled first
1760
1891
  * (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
1761
1892
  * `answered` are, and a settled one's words are a line (280 characters), its body read when the
1762
1893
  * question is opened. Never a CANCELLED one (withdrawn, dismissed, or its Goal or call ended): it
1763
1894
  * asks nothing of anyone (#3723). */
1764
- questions: z13.array(QueueQuestionSchema).default([]),
1895
+ questions: z14.array(QueueQuestionSchema).default([]),
1765
1896
  /** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
1766
1897
  * bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
1767
1898
  * right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
1768
1899
  * by the projection, so hand-built queues (fixtures, the demo) need not spell them. A cancelled
1769
1900
  * question is not asked (#3725). */
1770
- asked: z13.number().optional(),
1771
- answered: z13.number().optional(),
1901
+ asked: z14.number().optional(),
1902
+ answered: z14.number().optional(),
1772
1903
  /** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
1773
1904
  * Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
1774
1905
  * not spell an empty list. */
1775
- replies: z13.array(QueueReplySchema).optional(),
1906
+ replies: z14.array(QueueReplySchema).optional(),
1776
1907
  /** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
1777
- repo: z13.string().nullable().optional(),
1778
- createdAt: z13.string(),
1779
- updatedAt: z13.string().nullable().default(null),
1908
+ repo: z14.string().nullable().optional(),
1909
+ createdAt: z14.string(),
1910
+ updatedAt: z14.string().nullable().default(null),
1780
1911
  /** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
1781
1912
  * as progress). `updatedAt` moves for reasons nobody chose — a
1782
1913
  * state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
1783
- lastProgressAt: z13.string().nullable().optional(),
1914
+ lastProgressAt: z14.string().nullable().optional(),
1784
1915
  /** WHEN AN AGENT LAST ACTED ON IT (owner, 2026-10-27: "Move it now", Goal 567258dc): its newest agent write, an
1785
1916
  * Entry or a Goal change, Paigy's excluded (`goals.worked_at`). What keeps work an agent is writing on from
1786
1917
  * reading as gone quiet (`coldSince`); absent on a door that does not carry it. */
1787
- workedAt: z13.string().nullable().optional(),
1918
+ workedAt: z14.string().nullable().optional(),
1788
1919
  /** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
1789
1920
  * kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
1790
1921
  * note. A progress note is an Entry, so this is already the newer of the two: the person's note
@@ -1803,7 +1934,7 @@ var QueueItemSchema = z13.object({
1803
1934
  * minute after the person speaks erases their instant from it, and the durable traces the client
1804
1935
  * can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
1805
1936
  * Entry with no `about_id` is in neither. */
1806
- lastPersonAt: z13.string().nullable().optional(),
1937
+ lastPersonAt: z14.string().nullable().optional(),
1807
1938
  /** EACH SIDE'S NEWEST WORD IN THE LAST DAY (owner, 2026-10-13, the Work tab's order): the person's
1808
1939
  * (`yours`) and the agents' (`theirs`), each null when that side wrote nothing on it in 24 hours.
1809
1940
  * `latest` holds one Entry, so whichever side wrote first is hidden there; Work orders a Goal by
@@ -1811,6 +1942,11 @@ var QueueItemSchema = z13.object({
1811
1942
  * and a door older than 20261013045542 need not spell them. */
1812
1943
  yours: GoalLatestSchema.nullable().optional(),
1813
1944
  theirs: GoalLatestSchema.nullable().optional(),
1945
+ /** ITS STEPS, COUNTED IN THE DATABASE (owner, 2026-11-04: "Count on the server, and agents make steps"): its
1946
+ * direct children that were not cancelled, and how many of them are done. The queue holds only a recent
1947
+ * slice of finished work, so counting `childGoalIds` read "0 of 1" for a project with five done. Absent
1948
+ * from a door older than this field, or when the count failed: the app then counts what it holds. */
1949
+ steps: z14.object({ total: z14.number().int().nonnegative(), done: z14.number().int().nonnegative() }).nullable().optional(),
1814
1950
  /** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
1815
1951
  * by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
1816
1952
  * incremental read knows a row has not moved: the phone echoes back the revs it holds
@@ -1821,40 +1957,40 @@ var QueueItemSchema = z13.object({
1821
1957
  * the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
1822
1958
  * sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
1823
1959
  * queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
1824
- rev: z13.string().optional()
1960
+ rev: z14.string().optional()
1825
1961
  });
1826
- var QueueDeltaSchema = z13.object({
1827
- ids: z13.array(z13.string()),
1828
- items: z13.array(QueueItemSchema)
1962
+ var QueueDeltaSchema = z14.object({
1963
+ ids: z14.array(z14.string()),
1964
+ items: z14.array(QueueItemSchema)
1829
1965
  });
1830
1966
  var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
1831
- var NoteSourceSchema = z13.enum(["app", "call"]);
1832
- var NoteStatusSchema = z13.enum(["open", "assigned", "in_progress", "done"]);
1833
- var NoteRepeatSchema = z13.enum(["once", "until_done"]);
1834
- var DecisionSchema = z13.object({
1835
- id: z13.string(),
1967
+ var NoteSourceSchema = z14.enum(["app", "call"]);
1968
+ var NoteStatusSchema = z14.enum(["open", "assigned", "in_progress", "done"]);
1969
+ var NoteRepeatSchema = z14.enum(["once", "until_done"]);
1970
+ var DecisionSchema = z14.object({
1971
+ id: z14.string(),
1836
1972
  /** The note this decision refines; null = recorded on a bare thread (the
1837
1973
  * extensibility seam — any conversation can accrue decisions). */
1838
- noteId: z13.string().nullable(),
1974
+ noteId: z14.string().nullable(),
1839
1975
  /** What was ambiguous — the broker's (or the user's own) question. */
1840
- question: z13.string(),
1976
+ question: z14.string(),
1841
1977
  /** The user's ruling; null while the question is open. */
1842
- answer: z13.string().nullable(),
1843
- decidedAt: z13.string().nullable(),
1844
- createdAt: z13.string()
1978
+ answer: z14.string().nullable(),
1979
+ decidedAt: z14.string().nullable(),
1980
+ createdAt: z14.string()
1845
1981
  });
1846
- var NoteSchema = z13.object({
1847
- id: z13.string(),
1982
+ var NoteSchema = z14.object({
1983
+ id: z14.string(),
1848
1984
  /** One-line headline (broker-titled; deterministic floor). */
1849
- title: z13.string(),
1985
+ title: z14.string(),
1850
1986
  /** The original intent, verbatim — assignees always see the user's own words. */
1851
- intent: z13.string(),
1987
+ intent: z14.string(),
1852
1988
  source: NoteSourceSchema,
1853
1989
  status: NoteStatusSchema,
1854
1990
  /** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
1855
- assignee: z13.string().nullable(),
1991
+ assignee: z14.string().nullable(),
1856
1992
  /** The request thread minted at assignment; null until assigned. */
1857
- parentId: z13.string().nullable(),
1993
+ parentId: z14.string().nullable(),
1858
1994
  /** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
1859
1995
  * call — never a deadline. It only ever comes from the user's own words, so when it
1860
1996
  * passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
@@ -1863,100 +1999,100 @@ var NoteSchema = z13.object({
1863
1999
  // Defaulted, not required: a Note from an API deploy older than the reminders
1864
2000
  // migration has none of these, and the defaults ARE what it means — no not-before,
1865
2001
  // one ride, never ridden. Parsing must not fail across a rolling deploy.
1866
- dueAt: z13.string().nullable().default(null),
2002
+ dueAt: z14.string().nullable().default(null),
1867
2003
  repeat: NoteRepeatSchema.default("once"),
1868
2004
  /** How many calls have already carried it — the fatigue cap counts rides, not days. */
1869
- rides: z13.number().int().default(0),
1870
- lastRideAt: z13.string().nullable().default(null),
1871
- createdAt: z13.string()
2005
+ rides: z14.number().int().default(0),
2006
+ lastRideAt: z14.string().nullable().default(null),
2007
+ createdAt: z14.string()
1872
2008
  });
1873
- var DeliveryModeSchema = z13.enum(["poll", "self_hosted"]);
1874
- var RegisterDeliverySchema = z13.object({ mode: DeliveryModeSchema });
1875
- var OAuthStartSchema = z13.object({
1876
- provider: z13.enum(["cma"]),
1877
- returnTo: z13.string().min(1)
2009
+ var DeliveryModeSchema = z14.enum(["poll", "self_hosted"]);
2010
+ var RegisterDeliverySchema = z14.object({ mode: DeliveryModeSchema });
2011
+ var OAuthStartSchema = z14.object({
2012
+ provider: z14.enum(["cma"]),
2013
+ returnTo: z14.string().min(1)
1878
2014
  });
1879
- var DeliveryConfigSchema = z13.object({
1880
- tokenId: z13.string(),
2015
+ var DeliveryConfigSchema = z14.object({
2016
+ tokenId: z14.string(),
1881
2017
  mode: DeliveryModeSchema,
1882
2018
  /** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
1883
2019
  * (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
1884
2020
  * carries credentials; only a `poll` registration can come back with null here. */
1885
- realtime: z13.object({ url: z13.string(), anonKey: z13.string() }).nullable()
2021
+ realtime: z14.object({ url: z14.string(), anonKey: z14.string() }).nullable()
1886
2022
  });
1887
- var HostDecisionSchema = z13.object({
2023
+ var HostDecisionSchema = z14.object({
1888
2024
  /** The agent's token id: the row's `recipient`. */
1889
- agent: z13.string().uuid(),
1890
- decision: z13.enum(["stood_back", "took_over"]),
2025
+ agent: z14.string().uuid(),
2026
+ decision: z14.enum(["stood_back", "took_over"]),
1891
2027
  /** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
1892
- goalId: z13.string().uuid().nullable().optional(),
2028
+ goalId: z14.string().uuid().nullable().optional(),
1893
2029
  /** When the server last heard from the agent, as the host read it: the presence it stood back for. */
1894
- seenAt: z13.string().datetime().nullable().optional(),
2030
+ seenAt: z14.string().datetime().nullable().optional(),
1895
2031
  /** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
1896
- since: z13.string().datetime().nullable().optional(),
2032
+ since: z14.string().datetime().nullable().optional(),
1897
2033
  /** What the host said, in its log's own words: why it stood back, or what the take-over did. */
1898
- said: z13.string().max(300).optional()
2034
+ said: z14.string().max(300).optional()
1899
2035
  });
1900
- var WakeNudgeSchema = z13.object({
1901
- kind: z13.enum(["reply", "request", "callback"]),
1902
- notificationId: z13.string().optional(),
1903
- parentId: z13.string()
2036
+ var WakeNudgeSchema = z14.object({
2037
+ kind: z14.enum(["reply", "request", "callback"]),
2038
+ notificationId: z14.string().optional(),
2039
+ parentId: z14.string()
1904
2040
  });
1905
- var PairingStatusSchema = z13.enum(["pending", "approved", "denied", "expired"]);
1906
- var DeviceCodeSchema = z13.object({
1907
- device_code: z13.string(),
1908
- user_code: z13.string(),
1909
- verification_uri: z13.string().url(),
1910
- verification_uri_complete: z13.string().url(),
1911
- interval: z13.number(),
1912
- expires_in: z13.number()
2041
+ var PairingStatusSchema = z14.enum(["pending", "approved", "denied", "expired"]);
2042
+ var DeviceCodeSchema = z14.object({
2043
+ device_code: z14.string(),
2044
+ user_code: z14.string(),
2045
+ verification_uri: z14.string().url(),
2046
+ verification_uri_complete: z14.string().url(),
2047
+ interval: z14.number(),
2048
+ expires_in: z14.number()
1913
2049
  });
1914
- var DeviceInfoSchema = z13.object({
1915
- code: z13.string(),
2050
+ var DeviceInfoSchema = z14.object({
2051
+ code: z14.string(),
1916
2052
  /** The agent's suggested name (from /device/code) — shown on the approval screen,
1917
2053
  * pre-filling the name field the human can edit. */
1918
- name: z13.string(),
2054
+ name: z14.string(),
1919
2055
  /** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
1920
2056
  * build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
1921
2057
  * install crashes on the pairing screen on first launch, before the OTA lands
1922
2058
  * (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
1923
- agent: z13.string().optional(),
1924
- device: z13.string().nullable(),
2059
+ agent: z14.string().optional(),
2060
+ device: z14.string().nullable(),
1925
2061
  status: PairingStatusSchema
1926
2062
  });
1927
- var DeviceTokenSchema = z13.object({
1928
- access_token: z13.string(),
2063
+ var DeviceTokenSchema = z14.object({
2064
+ access_token: z14.string(),
1929
2065
  /** The pairing's single name (user-typed at approval, the agent's suggestion, or
1930
2066
  * a default silly name). */
1931
- name: z13.string(),
1932
- device: z13.string().nullable(),
2067
+ name: z14.string(),
2068
+ device: z14.string().nullable(),
1933
2069
  /** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
1934
2070
  * draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
1935
- voice: z13.string().nullable().optional(),
2071
+ voice: z14.string().nullable().optional(),
1936
2072
  /** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
1937
2073
  * that survives a rename. Cached by the host's identity beat. */
1938
- token_id: z13.string().nullable().optional(),
2074
+ token_id: z14.string().nullable().optional(),
1939
2075
  /** WHERE this identity works — the folder a wake should land it in. Written by the host
1940
2076
  * at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
1941
2077
  * landed in the FIRST granted workspace and the agent rediscovered its own repo from
1942
2078
  * the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
1943
- workspace: z13.string().nullable().optional(),
2079
+ workspace: z14.string().nullable().optional(),
1944
2080
  /** Local host recovery must preserve the launch's runtime and Paigy identity. */
1945
- harness: z13.enum(["claude", "codex", "agy"]).optional(),
1946
- session_id: z13.string().uuid().optional(),
2081
+ harness: z14.enum(["claude", "codex", "agy", "grok"]).optional(),
2082
+ session_id: z14.string().uuid().optional(),
1947
2083
  /** A conversation the host must not resume: its context is full, so every turn fails
1948
2084
  * ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
1949
2085
  * whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
1950
2086
  * copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
1951
- full_session: z13.string().optional(),
1952
- uik_pub: z13.string().nullable().optional()
2087
+ full_session: z14.string().optional(),
2088
+ uik_pub: z14.string().nullable().optional()
1953
2089
  });
1954
- var SupportRequestSchema = z13.object({
1955
- email: z13.string().email().max(320),
1956
- message: z13.string().trim().min(1).max(5e3),
1957
- name: z13.string().trim().max(120).optional()
2090
+ var SupportRequestSchema = z14.object({
2091
+ email: z14.string().email().max(320),
2092
+ message: z14.string().trim().min(1).max(5e3),
2093
+ name: z14.string().trim().max(120).optional()
1958
2094
  });
1959
- var NotificationFeedbackKindSchema = z13.enum([
2095
+ var NotificationFeedbackKindSchema = z14.enum([
1960
2096
  "break_down",
1961
2097
  // "This should be more than one ask — break it down."
1962
2098
  "regenerate_options",
@@ -1971,133 +2107,133 @@ var NotificationFeedbackKindSchema = z13.enum([
1971
2107
  // anything else — the note carries it.
1972
2108
  ]);
1973
2109
  var SlimOptionSchema = OptionSchema.omit({ html: true });
1974
- var QuestionRowSchema = z13.object({
2110
+ var QuestionRowSchema = z14.object({
1975
2111
  /** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
1976
- id: z13.string(),
1977
- deliveryId: z13.string(),
1978
- entryId: z13.string(),
2112
+ id: z14.string(),
2113
+ deliveryId: z14.string(),
2114
+ entryId: z14.string(),
1979
2115
  /** The decision it waits on; null for an update, which asks nothing. */
1980
- needId: z13.string().nullable(),
1981
- goalIds: z13.array(z13.string()),
2116
+ needId: z14.string().nullable(),
2117
+ goalIds: z14.array(z14.string()),
1982
2118
  /** The name of the work it is about, when the read could word it. */
1983
- goalTitle: z13.string().optional(),
1984
- tokenId: z13.string().optional(),
1985
- name: z13.string(),
1986
- title: z13.string(),
1987
- body: z13.string(),
1988
- select: z13.enum(["one", "many", "rank", "confirm", "text"]),
1989
- options: z13.array(SlimOptionSchema),
1990
- hasPreview: z13.boolean(),
1991
- blocking: z13.boolean(),
1992
- askedAt: z13.string().datetime(),
2119
+ goalTitle: z14.string().optional(),
2120
+ tokenId: z14.string().optional(),
2121
+ name: z14.string(),
2122
+ title: z14.string(),
2123
+ body: z14.string(),
2124
+ select: z14.enum(["one", "many", "rank", "confirm", "text"]),
2125
+ options: z14.array(SlimOptionSchema),
2126
+ hasPreview: z14.boolean(),
2127
+ blocking: z14.boolean(),
2128
+ askedAt: z14.string().datetime(),
1993
2129
  ring: InboxItemSchema.shape.ring,
1994
- onCall: z13.literal(true).optional(),
1995
- sealed: z13.boolean()
2130
+ onCall: z14.literal(true).optional(),
2131
+ sealed: z14.boolean()
1996
2132
  });
1997
- var WorkStateSchema = z13.enum(["ready", "active", "waiting", "done", "cancelled"]);
1998
- var WorkRowSchema = z13.object({
1999
- id: z13.string(),
2000
- parentId: z13.string().nullable(),
2001
- title: z13.string(),
2133
+ var WorkStateSchema = z14.enum(["ready", "active", "waiting", "done", "cancelled"]);
2134
+ var WorkRowSchema = z14.object({
2135
+ id: z14.string(),
2136
+ parentId: z14.string().nullable(),
2137
+ title: z14.string(),
2002
2138
  /** Straight off the Goal. */
2003
2139
  state: WorkStateSchema,
2004
- owner: z13.string().nullable(),
2005
- revision: z13.number().int(),
2140
+ owner: z14.string().nullable(),
2141
+ revision: z14.number().int(),
2006
2142
  /** Open questions on it, counted to 100. */
2007
- waiting: z13.number().int(),
2143
+ waiting: z14.number().int(),
2008
2144
  /** Held by a gate on work that is not done. */
2009
- blocked: z13.boolean(),
2010
- lastProgressAt: z13.string().datetime().nullable(),
2145
+ blocked: z14.boolean(),
2146
+ lastProgressAt: z14.string().datetime().nullable(),
2011
2147
  /** The line written for its newest progress note, else that note's first words. */
2012
- line: z13.string().nullable(),
2148
+ line: z14.string().nullable(),
2013
2149
  /** Work directly under it, counted to 100; the list carries up to 12 of them. */
2014
- children: z13.number().int(),
2015
- createdAt: z13.string().datetime(),
2016
- updatedAt: z13.string().datetime(),
2150
+ children: z14.number().int(),
2151
+ createdAt: z14.string().datetime(),
2152
+ updatedAt: z14.string().datetime(),
2017
2153
  /** When anything at or under it last moved — the order the list is in. */
2018
- activeAt: z13.string().datetime(),
2154
+ activeAt: z14.string().datetime(),
2019
2155
  /** A sealed outcome has no title here; the work's page opens it. */
2020
- sealed: z13.boolean()
2156
+ sealed: z14.boolean()
2021
2157
  });
2022
2158
  var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
2023
2159
  var AgentRowSchema = ComputerRowSchema.extend({
2024
2160
  /** Open questions it is asking the person, over every open card; null when that read failed. */
2025
- asking: z13.number().int().nullable(),
2026
- oldestAskAt: z13.string().datetime().nullable(),
2161
+ asking: z14.number().int().nullable(),
2162
+ oldestAskAt: z14.string().datetime().nullable(),
2027
2163
  /** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
2028
2164
  * many in all among the account's 200 most recently active agent-held live Goals
2029
2165
  * (`agent_holds`); null when that read failed. */
2030
- holds: z13.array(z13.object({ id: z13.string(), title: z13.string() })).nullable(),
2031
- held: z13.number().int().nullable(),
2166
+ holds: z14.array(z14.object({ id: z14.string(), title: z14.string() })).nullable(),
2167
+ held: z14.number().int().nullable(),
2032
2168
  /** The newest line of its working log, and when the harness saw it. */
2033
- line: z13.string().nullable(),
2034
- lineAt: z13.string().datetime().nullable()
2169
+ line: z14.string().nullable(),
2170
+ lineAt: z14.string().datetime().nullable()
2035
2171
  });
2036
- var SnapshotSchema = z13.object({
2172
+ var SnapshotSchema = z14.object({
2037
2173
  /** The API's clock, taken before the first read: what a later delta will start from. */
2038
- at: z13.string().datetime(),
2039
- questions: z13.object({
2174
+ at: z14.string().datetime(),
2175
+ questions: z14.object({
2040
2176
  /** The newest 30 open cards, questions before updates. */
2041
- items: z13.array(QuestionRowSchema),
2177
+ items: z14.array(QuestionRowSchema),
2042
2178
  /** Every open question, and apart from them every update, and what was put off. */
2043
- total: z13.number().int(),
2044
- updates: z13.number().int(),
2045
- putOff: z13.number().int()
2179
+ total: z14.number().int(),
2180
+ updates: z14.number().int(),
2181
+ putOff: z14.number().int()
2046
2182
  }).nullable(),
2047
- agents: z13.object({
2183
+ agents: z14.object({
2048
2184
  /** Up to 60, most recently seen first. */
2049
- items: z13.array(AgentRowSchema),
2050
- more: z13.boolean()
2185
+ items: z14.array(AgentRowSchema),
2186
+ more: z14.boolean()
2051
2187
  }).nullable(),
2052
- work: z13.object({
2188
+ work: z14.object({
2053
2189
  /** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
2054
- items: z13.array(WorkRowSchema),
2190
+ items: z14.array(WorkRowSchema),
2055
2191
  /** How much work is behind each of the Work tab's four filters, each counted to 100, read with
2056
2192
  * the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
2057
2193
  * `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
2058
2194
  * is `ready`; Done is `done` or `cancelled`. */
2059
- counts: z13.object({ live: z13.number().int(), waiting: z13.number().int(), notStarted: z13.number().int(), done: z13.number().int() })
2195
+ counts: z14.object({ live: z14.number().int(), waiting: z14.number().int(), notStarted: z14.number().int(), done: z14.number().int() })
2060
2196
  }).nullable(),
2061
- you: z13.object({
2197
+ you: z14.object({
2062
2198
  settings: UserSettingsSchema,
2063
- callable: z13.boolean(),
2199
+ callable: z14.boolean(),
2064
2200
  /** Up to 20 paired computers; null when the roster read failed. */
2065
- computers: z13.array(ComputerRowSchema).nullable()
2201
+ computers: z14.array(ComputerRowSchema).nullable()
2066
2202
  }).nullable()
2067
2203
  });
2068
- var CallRecapSchema = z13.object({
2069
- call: z13.object({
2070
- status: z13.string(),
2071
- startedAt: z13.string(),
2072
- durationMs: z13.number().nullable(),
2204
+ var CallRecapSchema = z14.object({
2205
+ call: z14.object({
2206
+ status: z14.string(),
2207
+ startedAt: z14.string(),
2208
+ durationMs: z14.number().nullable(),
2073
2209
  /** Bot-clock pickup offset; absent before the timestamp release, null before its report lands. */
2074
- pickupMs: z13.number().nullable().optional(),
2210
+ pickupMs: z14.number().nullable().optional(),
2075
2211
  /** STILL FILING (20261026182117): the call has not ended on the server, or it has and a brain cycle on it is
2076
2212
  * still pending, the next writer, which may still settle, file or start something. What the close itself
2077
2213
  * waits for (`private.close_call`). `status` cannot say it: nothing writes "finalizing", and a call reads
2078
2214
  * "open" until the sweep closes it, after nothing is pending. Absent from an API before it. */
2079
- filing: z13.boolean().optional(),
2080
- agents: z13.array(z13.object({ id: z13.string(), name: z13.string().nullable() }))
2215
+ filing: z14.boolean().optional(),
2216
+ agents: z14.array(z14.object({ id: z14.string(), name: z14.string().nullable() }))
2081
2217
  }),
2082
- topics: z13.array(z13.object({
2083
- goalId: z13.string().uuid(),
2084
- title: z13.string(),
2085
- owner: z13.string(),
2086
- state: z13.string(),
2218
+ topics: z14.array(z14.object({
2219
+ goalId: z14.string().uuid(),
2220
+ title: z14.string(),
2221
+ owner: z14.string(),
2222
+ state: z14.string(),
2087
2223
  /** Presented Entry content and its author, separately from the current Goal owner. */
2088
- items: z13.array(z13.object({ entryId: z13.string(), author: z13.string(), text: z13.string(), summary: z13.string().nullable(), acknowledged: z13.boolean().optional() })).optional(),
2224
+ items: z14.array(z14.object({ entryId: z14.string(), author: z14.string(), text: z14.string(), summary: z14.string().nullable(), acknowledged: z14.boolean().optional() })).optional(),
2089
2225
  /** Whether the call put it to the person. An item it carried and never said has no row on the
2090
2226
  * review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
2091
- presented: z13.boolean(),
2227
+ presented: z14.boolean(),
2092
2228
  /** Whether THIS call created it — the third way a call touches work, beside carrying a request
2093
2229
  * there and filing words there (owner, 2026-10-08, Goal `5b4b1a0a`: "Show the new work, keep my
2094
2230
  * words under it"). Created work is never carried, so it is never `presented`; without this the
2095
2231
  * row is dropped by the "only what happened on the call" filter. False on a database before
2096
2232
  * 20261013080133. */
2097
- created: z13.boolean(),
2233
+ created: z14.boolean(),
2098
2234
  /** `request` is the Entry that asked it — what a line's `about` names when the line answered or put
2099
2235
  * it off. Optional: an older API sent none. */
2100
- questions: z13.array(z13.object({ id: z13.string().uuid(), state: z13.string(), title: z13.string(), request: z13.string().optional() })),
2236
+ questions: z14.array(z14.object({ id: z14.string().uuid(), state: z14.string(), title: z14.string(), request: z14.string().optional() })),
2101
2237
  /** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
2102
2238
  * the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
2103
2239
  * gave, so the row scans like a chosen option and their own words stay under it. Absent when
@@ -2107,34 +2243,34 @@ var CallRecapSchema = z13.object({
2107
2243
  /** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
2108
2244
  * however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
2109
2245
  * said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
2110
- lines: z13.array(z13.object({
2111
- entryId: z13.string().uuid(),
2112
- words: z13.string(),
2113
- summary: z13.string().optional(),
2114
- about: z13.string().nullable().optional(),
2115
- reply: z13.string().nullable().optional(),
2116
- replyKind: z13.string().nullable().optional(),
2117
- at: z13.array(z13.number()).optional()
2246
+ lines: z14.array(z14.object({
2247
+ entryId: z14.string().uuid(),
2248
+ words: z14.string(),
2249
+ summary: z14.string().optional(),
2250
+ about: z14.string().nullable().optional(),
2251
+ reply: z14.string().nullable().optional(),
2252
+ replyKind: z14.string().nullable().optional(),
2253
+ at: z14.array(z14.number()).optional()
2118
2254
  }))
2119
2255
  })),
2120
- unfiled: z13.array(z13.object({ lineId: z13.string().uuid(), words: z13.string(), atMs: z13.number() })),
2256
+ unfiled: z14.array(z14.object({ lineId: z14.string().uuid(), words: z14.string(), atMs: z14.number() })),
2121
2257
  /** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
2122
2258
  * (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
2123
2259
  * it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
2124
2260
  * person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
2125
2261
  * theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
2126
2262
  * BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
2127
- said: z13.array(z13.object({
2128
- lineId: z13.string().uuid(),
2129
- speaker: z13.enum(["user", "agent"]),
2130
- agent: z13.object({ id: z13.string().nullable(), name: z13.string().nullable() }).nullable(),
2131
- words: z13.string(),
2132
- atMs: z13.number()
2263
+ said: z14.array(z14.object({
2264
+ lineId: z14.string().uuid(),
2265
+ speaker: z14.enum(["user", "agent"]),
2266
+ agent: z14.object({ id: z14.string().nullable(), name: z14.string().nullable() }).nullable(),
2267
+ words: z14.string(),
2268
+ atMs: z14.number()
2133
2269
  })),
2134
2270
  /** What did not fit, counted. `settles` counts items past the recap's 100; absent before 20261026165518. */
2135
- more: z13.object({ lines: z13.number(), entries: z13.number(), topics: z13.number(), settles: z13.number().optional() }),
2271
+ more: z14.object({ lines: z14.number(), entries: z14.number(), topics: z14.number(), settles: z14.number().optional() }),
2136
2272
  /** Last stored position on this page when another exists; absent before paging shipped. */
2137
- next: z13.number().int().nonnegative().nullable().optional(),
2273
+ next: z14.number().int().nonnegative().nullable().optional(),
2138
2274
  /** THE CALL AS ONE LIST OF ROWS (owner, 2026-10-14: "we only really want a thing like transcript but
2139
2275
  * built from the summaries", then "the transcript tabs should show the same but with full words").
2140
2276
  * Both tabs draw these rows, in this order: Summary from `summary`, Transcript from `words`.
@@ -2145,32 +2281,38 @@ var CallRecapSchema = z13.object({
2145
2281
  * `by` is the row's real author, never inferred from `kind`: a relay Paigy sent to an agent is
2146
2282
  * Paigy's, not the person's. Optional and additive (owner: "make sure whatever you do is backward
2147
2283
  * compatible"): an API before it sends none, and the screen falls back to `topics` and `said`. */
2148
- timeline: z13.array(z13.object({
2149
- id: z13.string(),
2150
- position: z13.number(),
2151
- atMs: z13.number(),
2152
- kind: z13.enum(["put", "said", "did"]),
2153
- by: z13.object({ participant: z13.string().nullable(), agentId: z13.string().nullable(), name: z13.string().nullable() }),
2154
- goalId: z13.string().uuid().nullable(),
2155
- requestId: z13.string().nullable(),
2156
- summary: z13.string().nullable(),
2157
- words: z13.string(),
2284
+ timeline: z14.array(z14.object({
2285
+ id: z14.string(),
2286
+ position: z14.number(),
2287
+ atMs: z14.number(),
2288
+ kind: z14.enum(["put", "said", "did"]),
2289
+ by: z14.object({ participant: z14.string().nullable(), agentId: z14.string().nullable(), name: z14.string().nullable() }),
2290
+ goalId: z14.string().uuid().nullable(),
2291
+ requestId: z14.string().nullable(),
2292
+ summary: z14.string().nullable(),
2293
+ words: z14.string(),
2158
2294
  /** Paigy's sentences folded into this row: on a `put` row, what it said putting the item to the
2159
2295
  * person (before their next row); on a `said` row, what it said in reply (until their next row).
2160
2296
  * `atMs` is when each was said; optional, absent before 2026-10-14's fix. */
2161
- spoken: z13.array(z13.object({ lineId: z13.string(), words: z13.string(), atMs: z13.number().optional() })),
2297
+ spoken: z14.array(z14.object({ lineId: z14.string(), words: z14.string(), atMs: z14.number().optional() })),
2162
2298
  /** On a `said` row: the work these words were filed on, and whether they answered (recorded) a
2163
2299
  * question. They replace the "Filed on ..." and "Recorded: ..." rows that repeated the person's own
2164
2300
  * summary (Brain, 2026-10-14). Optional: absent from an API before it. */
2165
- filedOn: z13.array(z13.object({ goalId: z13.string().uuid(), title: z13.string() })).optional(),
2166
- recorded: z13.boolean().optional(),
2167
- settled: z13.object({ requestId: z13.string(), answer: z13.string() }).nullable().optional(),
2168
- acknowledged: z13.boolean().optional(),
2301
+ filedOn: z14.array(z14.object({ goalId: z14.string().uuid(), title: z14.string() })).optional(),
2302
+ recorded: z14.boolean().optional(),
2303
+ /** On a `said` row: the agents the call SENT those words to, from the sending action's own
2304
+ * `about.agents` (20261106220220). The review's receipt is drawn from this and from the row's
2305
+ * own `requestId`, never from the asker of a question the words happened to sit under: on call
2306
+ * f9670f67 that asker was Interface while the send named Comms. `[]` or absent means nothing
2307
+ * was sent for that row, which is why the receipt is positive-only. */
2308
+ sentTo: z14.array(z14.object({ id: z14.string().nullable(), name: z14.string() })).optional(),
2309
+ settled: z14.object({ requestId: z14.string(), answer: z14.string() }).nullable().optional(),
2310
+ acknowledged: z14.boolean().optional(),
2169
2311
  /** On a `put` row: still waiting for the person's answer (not settled, not answered, not put off).
2170
2312
  * The server's judgement; the screen draws "Still waiting for your answer" from it. Optional: absent
2171
2313
  * from an API before it, where the screen falls back to its own reading. */
2172
- waiting: z13.boolean().optional(),
2173
- lineIds: z13.array(z13.string())
2314
+ waiting: z14.boolean().optional(),
2315
+ lineIds: z14.array(z14.string())
2174
2316
  })).optional(),
2175
2317
  /** THE RECAP (owner, 2026-10-26: "the transcript should just be the entries that settled or
2176
2318
  * acknowledged the items; that's it"). Each item the person's words settled on this call, in the
@@ -2182,13 +2324,13 @@ var CallRecapSchema = z13.object({
2182
2324
  * with `requestId` null and the Goal's name as `title`; a started Goal may have no `lines` yet. `kind`
2183
2325
  * is a string, not an enum, so a later kind never fails an older build's parse; `requestId` is the
2184
2326
  * request the words settled. On the first page only; absent from an API before it. */
2185
- settles: z13.array(z13.object({
2186
- requestId: z13.string().nullable(),
2187
- kind: z13.string(),
2188
- by: z13.object({ participant: z13.string(), agentId: z13.string().nullable(), name: z13.string() }),
2189
- goalId: z13.string().uuid().nullable(),
2190
- title: z13.string(),
2191
- lines: z13.array(z13.object({ lineId: z13.string(), words: z13.string(), atMs: z13.number() }))
2327
+ settles: z14.array(z14.object({
2328
+ requestId: z14.string().nullable(),
2329
+ kind: z14.string(),
2330
+ by: z14.object({ participant: z14.string(), agentId: z14.string().nullable(), name: z14.string() }),
2331
+ goalId: z14.string().uuid().nullable(),
2332
+ title: z14.string(),
2333
+ lines: z14.array(z14.object({ lineId: z14.string(), words: z14.string(), atMs: z14.number() }))
2192
2334
  })).optional()
2193
2335
  });
2194
2336
 
@@ -2229,6 +2371,7 @@ PAIGY
2229
2371
  exit 0
2230
2372
  `;
2231
2373
  }
2374
+ var GROK_STALLED_HOOK = "npx -y -p @paigy/mcp@latest paigy-stalled";
2232
2375
  function withSessionStartHook(existing, command) {
2233
2376
  let config = {};
2234
2377
  if (existing) {
@@ -2244,6 +2387,16 @@ function withSessionStartHook(existing, command) {
2244
2387
  if (!already) starts.push({ hooks: [{ type: "command", command }] });
2245
2388
  return JSON.stringify(config, null, 2);
2246
2389
  }
2390
+ function withGrokPaigyHooks(existing, sessionStart) {
2391
+ const withStart = withSessionStartHook(existing, sessionStart);
2392
+ if (withStart === null) return null;
2393
+ const config = JSON.parse(withStart);
2394
+ const hooks = config.hooks ??= {};
2395
+ const stops = hooks.Stop ??= [];
2396
+ const already = stops.some((group) => (group.hooks ?? []).some((h) => (h.command ?? "").includes("paigy-stalled")));
2397
+ if (!already) stops.push({ hooks: [{ type: "command", command: GROK_STALLED_HOOK, timeout: 60 }] });
2398
+ return JSON.stringify(config, null, 2);
2399
+ }
2247
2400
  var word = (s) => `'${s.replace(/'/g, "'\\''")}'`;
2248
2401
  function decideListen(f) {
2249
2402
  if (harnessDriven(f.env)) {
@@ -2368,7 +2521,7 @@ function withReach(shaped, reach) {
2368
2521
  if (!reach || !shaped || typeof shaped !== "object" || Array.isArray(shaped)) return shaped;
2369
2522
  return { ...reach, ...shaped };
2370
2523
  }
2371
- var HOOKED_HARNESSES = "Claude Code and Codex";
2524
+ var HOOKED_HARNESSES = "Claude Code, Codex, and Grok";
2372
2525
  function listenBlock(decision, f, stale) {
2373
2526
  const block = {
2374
2527
  listen: decision,
@@ -2456,16 +2609,21 @@ function waitingSummary(received) {
2456
2609
 
2457
2610
  export {
2458
2611
  mcpInputSchema,
2612
+ SKILL_MARK,
2459
2613
  AGENT_PROMPTS,
2460
2614
  promptResult,
2615
+ READS,
2616
+ SENDS,
2461
2617
  AGENT_TOOLS,
2462
2618
  LISTEN_COMMAND,
2463
2619
  serverInstructions,
2620
+ SKILL,
2464
2621
  entryWords,
2465
2622
  entryUnit,
2466
2623
  harnessDriven,
2467
2624
  sessionStartHook,
2468
2625
  withSessionStartHook,
2626
+ withGrokPaigyHooks,
2469
2627
  decideListen,
2470
2628
  writeListenMark,
2471
2629
  removeListenMark,