@paigy/mcp 0.40.31 → 0.40.33
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.
- package/dist/{chunk-CULBHBFU.js → chunk-6YV42HAB.js} +675 -506
- package/dist/{chunk-NHPLQOQP.js → chunk-WL5BT4E5.js} +6 -3
- package/dist/{chunk-AWI252HQ.js → chunk-ZIDPFMB2.js} +578 -493
- package/dist/{chunk-FAS3IB2F.js → chunk-ZSZIECZW.js} +1 -1
- package/dist/{dist-2WLAZWSH.js → dist-PPH5BD2P.js} +3 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +2 -2
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ../../packages/schema/dist/index.js
|
|
2
|
-
import { z as
|
|
2
|
+
import { z as z10 } from "zod";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { z as z3 } from "zod";
|
|
5
5
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
@@ -14,6 +14,7 @@ import { z as z7 } from "zod";
|
|
|
14
14
|
import { zodToJsonSchema as zodToJsonSchema5 } from "zod-to-json-schema";
|
|
15
15
|
import { z as z8 } from "zod";
|
|
16
16
|
import { zodToJsonSchema as zodToJsonSchema6 } from "zod-to-json-schema";
|
|
17
|
+
import { z as z9 } from "zod";
|
|
17
18
|
var OPTIONS_MIN = 2;
|
|
18
19
|
var OPTIONS_MAX = 6;
|
|
19
20
|
var OPTION_LABEL_MAX = 50;
|
|
@@ -43,6 +44,16 @@ var OptionInputSchema = z.object({
|
|
|
43
44
|
imgUrl: OptionSchema.shape.image
|
|
44
45
|
}).strict();
|
|
45
46
|
var NIGHT = { from: 23, to: 7 };
|
|
47
|
+
var ASKING_RULES = `HOW TO ASK:
|
|
48
|
+
1. Write one question per object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
49
|
+
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
50
|
+
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
51
|
+
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. While a Call you placed is open, keep receiving window after window until it ends, even when a window is empty: the person may give follow-ups or instructions. With no open Call, STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goal\` on that wake.
|
|
52
|
+
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
53
|
+
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
54
|
+
7. A question carries its options when you have them; a free-text question may omit them. Each option\u2019s label must stand on its own and name its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Each question is sent exactly as written, with no reading in between. They can always answer in their own words, so never add an 'Other' option.
|
|
55
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
|
|
56
|
+
9. GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. What they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Requested delivery and completed-Goal reports require a reply (reply_required); ordinary progress may omit it.`;
|
|
46
57
|
function draft2020(node) {
|
|
47
58
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
48
59
|
if (node && typeof node === "object") {
|
|
@@ -161,10 +172,18 @@ var UpdateInputSchema = z2.object({
|
|
|
161
172
|
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
162
173
|
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
163
174
|
reply: z2.string().trim().min(1).describe(
|
|
164
|
-
`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.
|
|
175
|
+
`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.`
|
|
165
176
|
).optional(),
|
|
166
177
|
userExplicitlyRequested
|
|
167
178
|
}).strict().superRefine((u, ctx) => {
|
|
179
|
+
if (u.userExplicitlyRequested && !u.reply) {
|
|
180
|
+
ctx.addIssue({
|
|
181
|
+
code: z2.ZodIssueCode.custom,
|
|
182
|
+
path: ["reply"],
|
|
183
|
+
params: { refusal: "reply_required" },
|
|
184
|
+
message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
|
|
185
|
+
});
|
|
186
|
+
}
|
|
168
187
|
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
169
188
|
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
170
189
|
ctx.addIssue({
|
|
@@ -252,14 +271,10 @@ var ContactWireSchema = z2.object({
|
|
|
252
271
|
questions: z2.array(QuestionWireSchema).min(1).max(20).optional()
|
|
253
272
|
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
254
273
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
255
|
-
var CONTACT_DESCRIPTION =
|
|
256
|
-
|
|
257
|
-
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
274
|
+
var CONTACT_DESCRIPTION = "Send to and receive from the person: updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read `results`, where a failed item names why while the rest still apply.\n\n" + ASKING_RULES + `
|
|
258
275
|
|
|
259
276
|
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
260
277
|
|
|
261
|
-
GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. It is what their button says instead of "Got it", and what they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Nothing is owed on it either way, and a report that writes none offers "Got it".
|
|
262
|
-
|
|
263
278
|
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
264
279
|
|
|
265
280
|
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
@@ -365,6 +380,10 @@ var GetGoalSchema = z3.object({
|
|
|
365
380
|
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
366
381
|
diagnose: z3.boolean().optional()
|
|
367
382
|
}).strict();
|
|
383
|
+
var GetDeliverySchema = z3.object({
|
|
384
|
+
deliveryId: z3.string().uuid().describe("The Delivery id returned by contact or receive.")
|
|
385
|
+
}).strict();
|
|
386
|
+
var GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. 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.";
|
|
368
387
|
var GET_GOAL_DESCRIPTION = "Read one Goal (a read never puts you on it), including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
369
388
|
var SearchToolSchema = z3.object({
|
|
370
389
|
query: z3.string().trim().min(1).max(500),
|
|
@@ -373,6 +392,8 @@ var SearchToolSchema = z3.object({
|
|
|
373
392
|
limit: z3.number().int().min(1).max(20).optional()
|
|
374
393
|
}).strict();
|
|
375
394
|
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.";
|
|
395
|
+
var ConfigureIdentitySchema = z3.object({}).strict();
|
|
396
|
+
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.";
|
|
376
397
|
var CheckActivitySchema = z3.object({}).strict();
|
|
377
398
|
var FEEDBACK_TEXT_MAX = 5e4;
|
|
378
399
|
var SendFeedbackSchema = z3.object({
|
|
@@ -382,8 +403,10 @@ var SendFeedbackSchema = z3.object({
|
|
|
382
403
|
}).strict();
|
|
383
404
|
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.";
|
|
384
405
|
var AGENT_TOOLS = [
|
|
406
|
+
{ name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
|
|
385
407
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
386
408
|
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
409
|
+
{ name: "get_delivery", description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
|
|
387
410
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
388
411
|
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
389
412
|
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
|
|
@@ -392,17 +415,9 @@ var AGENT_TOOLS = [
|
|
|
392
415
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
393
416
|
function serverInstructions(opts) {
|
|
394
417
|
const waits = opts.waits ? "contact({..., wait:true}) holds one bounded ~45 s window for a response to what you sent; contact({}) holds one for anything addressed to you." : "A contact here returns after one read (`waitOutcome: not_waited`); answers wake you, and contact({wait:false}) collects them.";
|
|
395
|
-
return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), which hand you your work: answers, assignments, reviews and due Goals. get_goal reads a Goal and the conversation on it; a read never puts you on it. You are on a Goal from your first write to it (a contact update, question or answer naming it, or a manage_goals change); there is nothing to claim or join. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goal, write on it (a contact naming its id) and change it (manage_goals). Create, edit, assign, organize or close work with manage_goals, never as a contact; a contact only names Goals that already exist. Every read ends in \`next\`, the one step to take. Sending returns immediately unless you ask it to wait: keep working and collect answers by receiving or through get_goal. ${waits} Events repeat until you acknowledge them: reading alone acknowledges nothing; an \`update\` event acknowledged is a review cleared. Report progress as a contact update: one the person does not need yet is kept as the Goal's progress and reaches no one. To follow up later, defer the Goal with manage_goals: you are woken when its time comes. Never infer ringing from an open Call Delivery.
|
|
418
|
+
return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), which hand you your work: answers, assignments, reviews and due Goals. get_goal reads a Goal and the conversation on it; a read never puts you on it. You are on a Goal from your first write to it (a contact update, question or answer naming it, or a manage_goals change); there is nothing to claim or join. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goal, write on it (a contact naming its id) and change it (manage_goals). Create, edit, assign, organize or close work with manage_goals, never as a contact; a contact only names Goals that already exist. Every read ends in \`next\`, the one step to take. Sending returns immediately unless you ask it to wait: keep working and collect answers by receiving or through get_goal. ${waits} Events repeat until you acknowledge them: reading alone acknowledges nothing; an \`update\` event acknowledged is a review cleared. Report progress as a contact update: one the person does not need yet is kept as the Goal's progress and reaches no one. To follow up later, defer the Goal with manage_goals: you are woken when its time comes. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
|
|
396
419
|
|
|
397
|
-
|
|
398
|
-
1. One question per question object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
399
|
-
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
400
|
-
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
401
|
-
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goal\` on that wake.
|
|
402
|
-
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
403
|
-
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
404
|
-
7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Your question is sent exactly as you wrote it. They can always answer in their own words, so no "Other" option.
|
|
405
|
-
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.`;
|
|
420
|
+
${ASKING_RULES}`;
|
|
406
421
|
}
|
|
407
422
|
var id = z4.string().uuid();
|
|
408
423
|
var key = z4.string().min(1).max(40);
|
|
@@ -686,20 +701,58 @@ function entryUnit(entry) {
|
|
|
686
701
|
}
|
|
687
702
|
var LIVE_MS = 3 * 6e4;
|
|
688
703
|
var WORKING_MS = 60 * 6e4;
|
|
689
|
-
var
|
|
690
|
-
|
|
691
|
-
|
|
704
|
+
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
705
|
+
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
706
|
+
var json = z9.lazy(() => z9.union([
|
|
707
|
+
z9.string(),
|
|
708
|
+
z9.number().finite(),
|
|
709
|
+
z9.boolean(),
|
|
710
|
+
z9.null(),
|
|
711
|
+
z9.array(json),
|
|
712
|
+
z9.record(json)
|
|
713
|
+
]));
|
|
714
|
+
var clock = z9.number().int().nonnegative().safe();
|
|
715
|
+
var HeardEventSchema = z9.object({
|
|
716
|
+
eventId: z9.string().uuid(),
|
|
717
|
+
source: z9.enum(["phone", "bot"]),
|
|
718
|
+
sequence: clock,
|
|
719
|
+
kind: z9.string().min(1),
|
|
720
|
+
phoneAtMs: clock.optional(),
|
|
721
|
+
botAtMs: clock.optional(),
|
|
722
|
+
payload: z9.record(json)
|
|
723
|
+
}).strict();
|
|
724
|
+
var HeardCursorSchema = HeardEventSchema.pick({ source: true, sequence: true, eventId: true });
|
|
725
|
+
var HeardPageSchema = z9.object({
|
|
726
|
+
events: z9.array(HeardEventSchema).max(200),
|
|
727
|
+
next: HeardCursorSchema.nullable()
|
|
728
|
+
}).strict();
|
|
729
|
+
var MAX_EVENTS = 50;
|
|
730
|
+
var MAX_BYTES = 8e4;
|
|
731
|
+
var batchBytes = (events) => {
|
|
732
|
+
let bytes = 0;
|
|
733
|
+
for (const char of JSON.stringify({ events })) {
|
|
734
|
+
const point = char.codePointAt(0);
|
|
735
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
736
|
+
}
|
|
737
|
+
return bytes;
|
|
738
|
+
};
|
|
739
|
+
var HeardBatchSchema = z9.object({
|
|
740
|
+
events: z9.array(HeardEventSchema).min(1).max(MAX_EVENTS)
|
|
741
|
+
}).strict().refine((value) => batchBytes(value.events) <= MAX_BYTES, "heard_batch_too_large");
|
|
742
|
+
var ContextSchema = z10.object({
|
|
743
|
+
title: z10.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
744
|
+
description: z10.array(z10.string().min(1)).describe(
|
|
692
745
|
"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."
|
|
693
746
|
),
|
|
694
747
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
695
748
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
696
|
-
units:
|
|
749
|
+
units: z10.array(z10.object({ title: z10.string(), body: z10.string() })).optional()
|
|
697
750
|
});
|
|
698
|
-
var ParticipantSchema =
|
|
699
|
-
kind:
|
|
700
|
-
id:
|
|
751
|
+
var ParticipantSchema = z10.object({
|
|
752
|
+
kind: z10.enum(["human", "agent"]),
|
|
753
|
+
id: z10.string()
|
|
701
754
|
});
|
|
702
|
-
var TransformSchema =
|
|
755
|
+
var TransformSchema = z10.enum([
|
|
703
756
|
"structure",
|
|
704
757
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
705
758
|
"request_more",
|
|
@@ -715,13 +768,13 @@ var TransformSchema = z9.enum([
|
|
|
715
768
|
"summarize"
|
|
716
769
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
717
770
|
]);
|
|
718
|
-
var VisualSchema =
|
|
719
|
-
url:
|
|
720
|
-
label:
|
|
771
|
+
var VisualSchema = z10.object({
|
|
772
|
+
url: z10.string().url(),
|
|
773
|
+
label: z10.string().optional()
|
|
721
774
|
});
|
|
722
|
-
var NotifyLevelSchema =
|
|
723
|
-
var SelectShapeSchema =
|
|
724
|
-
var ReceiptEventSchema =
|
|
775
|
+
var NotifyLevelSchema = z10.enum(["inbox", "push", "banner", "call"]);
|
|
776
|
+
var SelectShapeSchema = z10.enum(["one", "many", "rank", "confirm", "text"]);
|
|
777
|
+
var ReceiptEventSchema = z10.enum([
|
|
725
778
|
"delivered",
|
|
726
779
|
// the bundle reached the recipient at some level
|
|
727
780
|
"seen",
|
|
@@ -751,47 +804,47 @@ var ReceiptEventSchema = z9.enum([
|
|
|
751
804
|
// be rewound by a writer that forgot to advance it.
|
|
752
805
|
"restarted"
|
|
753
806
|
]);
|
|
754
|
-
var AttentionSchema =
|
|
807
|
+
var AttentionSchema = z10.object({
|
|
755
808
|
urgency: NotifyLevelSchema,
|
|
756
809
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
757
810
|
select: SelectShapeSchema.nullable(),
|
|
758
811
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
759
|
-
points:
|
|
812
|
+
points: z10.array(z10.string()).nullable(),
|
|
760
813
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
761
|
-
blocking:
|
|
814
|
+
blocking: z10.boolean(),
|
|
762
815
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
763
816
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
764
|
-
deadline:
|
|
817
|
+
deadline: z10.string().datetime().nullable().optional()
|
|
765
818
|
});
|
|
766
|
-
var NotifyRequestFields =
|
|
819
|
+
var NotifyRequestFields = z10.object({
|
|
767
820
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
768
821
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
769
822
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
770
823
|
context: ContextSchema.optional(),
|
|
771
|
-
options:
|
|
824
|
+
options: z10.array(OptionSchema.omit({ id: true }).extend({ label: z10.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
772
825
|
"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)."
|
|
773
826
|
),
|
|
774
|
-
points:
|
|
827
|
+
points: z10.array(z10.string().min(1)).optional().describe(
|
|
775
828
|
"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."
|
|
776
829
|
),
|
|
777
|
-
visuals:
|
|
830
|
+
visuals: z10.array(VisualSchema).optional().describe(
|
|
778
831
|
"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."
|
|
779
832
|
),
|
|
780
833
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
781
|
-
repo:
|
|
834
|
+
repo: z10.string().optional(),
|
|
782
835
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
783
|
-
branch:
|
|
836
|
+
branch: z10.string().optional(),
|
|
784
837
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
785
838
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
786
839
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
787
840
|
* still accepts the old name from older clients. */
|
|
788
|
-
parentId:
|
|
841
|
+
parentId: z10.string().uuid().optional(),
|
|
789
842
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
790
843
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
791
|
-
workId:
|
|
844
|
+
workId: z10.string().uuid().optional(),
|
|
792
845
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
793
846
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
794
|
-
goalId:
|
|
847
|
+
goalId: z10.string().uuid().optional(),
|
|
795
848
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
796
849
|
"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."
|
|
797
850
|
),
|
|
@@ -799,7 +852,7 @@ var NotifyRequestFields = z9.object({
|
|
|
799
852
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
800
853
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
801
854
|
* says HOW. */
|
|
802
|
-
clarifies:
|
|
855
|
+
clarifies: z10.string().optional(),
|
|
803
856
|
select: SelectShapeSchema.optional().describe(
|
|
804
857
|
"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."
|
|
805
858
|
),
|
|
@@ -813,20 +866,20 @@ var NotifyRequestFields = z9.object({
|
|
|
813
866
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
814
867
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
815
868
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
816
|
-
ask:
|
|
869
|
+
ask: z10.string().min(1).max(1e4).optional().describe(
|
|
817
870
|
'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.'
|
|
818
871
|
),
|
|
819
|
-
needs:
|
|
872
|
+
needs: z10.array(z10.string().min(1)).optional().describe(
|
|
820
873
|
"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."
|
|
821
874
|
),
|
|
822
|
-
urgencyHint:
|
|
875
|
+
urgencyHint: z10.enum(["whenever", "soon", "now"]).optional().describe(
|
|
823
876
|
"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."
|
|
824
877
|
),
|
|
825
878
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
826
879
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
827
880
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
828
881
|
* urgencyHint/blocking win when both are sent. */
|
|
829
|
-
waiting:
|
|
882
|
+
waiting: z10.enum(["none", "soft", "hard"]).optional().describe(
|
|
830
883
|
"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."
|
|
831
884
|
),
|
|
832
885
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -834,98 +887,98 @@ var NotifyRequestFields = z9.object({
|
|
|
834
887
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
835
888
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
836
889
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
837
|
-
confirm:
|
|
890
|
+
confirm: z10.boolean().optional().describe(
|
|
838
891
|
"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'."
|
|
839
892
|
),
|
|
840
893
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
841
894
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
842
895
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
843
896
|
* waiting:'hard'. */
|
|
844
|
-
channel:
|
|
897
|
+
channel: z10.enum(["call", "message"]).optional().describe(
|
|
845
898
|
"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."
|
|
846
899
|
),
|
|
847
|
-
confirmStyle:
|
|
900
|
+
confirmStyle: z10.enum(["yesno", "approve"]).default("yesno").describe(
|
|
848
901
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
849
902
|
),
|
|
850
|
-
blocking:
|
|
903
|
+
blocking: z10.boolean().default(false).describe(
|
|
851
904
|
"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."
|
|
852
905
|
)
|
|
853
906
|
});
|
|
854
907
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
855
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
908
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
856
909
|
if (r.ask !== void 0) {
|
|
857
910
|
for (const f of ["context", "select", "points"]) {
|
|
858
911
|
if (r[f] !== void 0)
|
|
859
|
-
ctx.addIssue({ code:
|
|
912
|
+
ctx.addIssue({ code: z10.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.` });
|
|
860
913
|
}
|
|
861
914
|
return;
|
|
862
915
|
}
|
|
863
916
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
864
|
-
ctx.addIssue({ code:
|
|
917
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
865
918
|
if (!r.context)
|
|
866
|
-
ctx.addIssue({ code:
|
|
919
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
867
920
|
if (!r.select)
|
|
868
|
-
ctx.addIssue({ code:
|
|
921
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
869
922
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
870
923
|
if (needsOptions && !r.options?.length)
|
|
871
|
-
ctx.addIssue({ code:
|
|
924
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
872
925
|
if (!needsOptions && r.options?.length)
|
|
873
|
-
ctx.addIssue({ code:
|
|
926
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
874
927
|
});
|
|
875
|
-
var NotifyStatusSchema =
|
|
876
|
-
var AgentStateSchema =
|
|
877
|
-
var TurnSchema =
|
|
878
|
-
prompt:
|
|
879
|
-
reply:
|
|
928
|
+
var NotifyStatusSchema = z10.enum(["pending", "answered", "ignored"]);
|
|
929
|
+
var AgentStateSchema = z10.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
930
|
+
var TurnSchema = z10.object({
|
|
931
|
+
prompt: z10.string(),
|
|
932
|
+
reply: z10.string()
|
|
880
933
|
});
|
|
881
|
-
var UserAnswerSchema =
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
934
|
+
var UserAnswerSchema = z10.discriminatedUnion("kind", [
|
|
935
|
+
z10.object({ kind: z10.literal("option"), optionId: z10.string(), label: z10.string().optional() }),
|
|
936
|
+
z10.object({ kind: z10.literal("text"), text: z10.string() }),
|
|
937
|
+
z10.object({ kind: z10.literal("ignored") }),
|
|
938
|
+
z10.object({ kind: z10.literal("multi"), optionIds: z10.array(z10.string()), labels: z10.array(z10.string()).optional() }),
|
|
939
|
+
z10.object({ kind: z10.literal("ranked"), optionIds: z10.array(z10.string()), labels: z10.array(z10.string()).optional() }),
|
|
940
|
+
z10.object({ kind: z10.literal("clarify"), chunks: z10.array(z10.string()).min(1) }),
|
|
941
|
+
z10.object({ kind: z10.literal("confirm"), approved: z10.boolean() }),
|
|
942
|
+
z10.object({ kind: z10.literal("turns"), turns: z10.array(TurnSchema).min(1) }),
|
|
890
943
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
891
944
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
892
945
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
893
946
|
* card the user can reply to — the broker never overrides the user. */
|
|
894
|
-
|
|
947
|
+
z10.object({ kind: z10.literal("precedent"), answer: z10.string(), derivation: z10.string(), sources: z10.array(z10.string()).min(1) })
|
|
895
948
|
]);
|
|
896
|
-
var IntentSchema =
|
|
949
|
+
var IntentSchema = z10.object({
|
|
897
950
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
898
951
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
899
952
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
900
953
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
901
|
-
kind:
|
|
902
|
-
detail:
|
|
954
|
+
kind: z10.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
955
|
+
detail: z10.string(),
|
|
903
956
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
904
957
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
905
958
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
906
959
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
907
|
-
dueInSeconds:
|
|
960
|
+
dueInSeconds: z10.number().int().positive().optional(),
|
|
908
961
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
909
962
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
910
963
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
911
964
|
* about what cannot answer "is the bot looping less this week?". */
|
|
912
|
-
fault:
|
|
965
|
+
fault: z10.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
913
966
|
});
|
|
914
|
-
var RideAlongSchema =
|
|
967
|
+
var RideAlongSchema = z10.object({
|
|
915
968
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
916
|
-
noteId:
|
|
969
|
+
noteId: z10.string(),
|
|
917
970
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
918
|
-
text:
|
|
971
|
+
text: z10.string(),
|
|
919
972
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
920
|
-
parentId:
|
|
973
|
+
parentId: z10.string().nullable()
|
|
921
974
|
});
|
|
922
|
-
var AwaitItemSchema =
|
|
923
|
-
|
|
924
|
-
type:
|
|
925
|
-
parentId:
|
|
926
|
-
notificationId:
|
|
927
|
-
workId:
|
|
928
|
-
decisionId:
|
|
975
|
+
var AwaitItemSchema = z10.discriminatedUnion("type", [
|
|
976
|
+
z10.object({
|
|
977
|
+
type: z10.literal("reply"),
|
|
978
|
+
parentId: z10.string(),
|
|
979
|
+
notificationId: z10.string(),
|
|
980
|
+
workId: z10.string().uuid().optional(),
|
|
981
|
+
decisionId: z10.string().uuid().optional(),
|
|
929
982
|
answer: UserAnswerSchema,
|
|
930
983
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
931
984
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -935,103 +988,107 @@ var AwaitItemSchema = z9.discriminatedUnion("type", [
|
|
|
935
988
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
936
989
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
937
990
|
* a sentence with a hole in it is worse than no sentence. */
|
|
938
|
-
note:
|
|
991
|
+
note: z10.string().optional(),
|
|
939
992
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
940
993
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
941
994
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
942
995
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
943
|
-
transcript:
|
|
996
|
+
transcript: z10.string().optional(),
|
|
944
997
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
945
998
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
946
|
-
covered:
|
|
999
|
+
covered: z10.array(z10.string()).optional(),
|
|
947
1000
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
948
1001
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
949
1002
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
950
1003
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
951
1004
|
* dropped. Absent/empty = nothing owed. */
|
|
952
|
-
also:
|
|
1005
|
+
also: z10.array(RideAlongSchema).optional()
|
|
953
1006
|
}),
|
|
954
|
-
|
|
955
|
-
type:
|
|
956
|
-
parentId:
|
|
957
|
-
notificationId:
|
|
958
|
-
remindAt:
|
|
1007
|
+
z10.object({
|
|
1008
|
+
type: z10.literal("remind"),
|
|
1009
|
+
parentId: z10.string(),
|
|
1010
|
+
notificationId: z10.string(),
|
|
1011
|
+
remindAt: z10.string().datetime({ offset: true }),
|
|
959
1012
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
960
|
-
remindInSeconds:
|
|
1013
|
+
remindInSeconds: z10.number()
|
|
961
1014
|
}),
|
|
962
1015
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
963
1016
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
964
1017
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
965
1018
|
* re-orient via contact({})). */
|
|
966
|
-
|
|
967
|
-
type:
|
|
968
|
-
parentId:
|
|
969
|
-
notificationId:
|
|
1019
|
+
z10.object({
|
|
1020
|
+
type: z10.literal("superseded"),
|
|
1021
|
+
parentId: z10.string(),
|
|
1022
|
+
notificationId: z10.string()
|
|
970
1023
|
}),
|
|
971
1024
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
972
1025
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
973
1026
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
974
1027
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
975
1028
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
976
|
-
|
|
977
|
-
type:
|
|
978
|
-
notificationId:
|
|
979
|
-
inFlight:
|
|
980
|
-
turn:
|
|
981
|
-
idx:
|
|
982
|
-
prompt:
|
|
983
|
-
reply:
|
|
984
|
-
acts:
|
|
1029
|
+
z10.object({
|
|
1030
|
+
type: z10.literal("partial"),
|
|
1031
|
+
notificationId: z10.string(),
|
|
1032
|
+
inFlight: z10.literal(true),
|
|
1033
|
+
turn: z10.object({
|
|
1034
|
+
idx: z10.number(),
|
|
1035
|
+
prompt: z10.string(),
|
|
1036
|
+
reply: z10.string(),
|
|
1037
|
+
acts: z10.array(IntentSchema).nullable().optional()
|
|
985
1038
|
})
|
|
986
1039
|
}),
|
|
987
|
-
|
|
988
|
-
type:
|
|
989
|
-
also:
|
|
1040
|
+
z10.object({
|
|
1041
|
+
type: z10.literal("idle"),
|
|
1042
|
+
also: z10.array(RideAlongSchema).optional(),
|
|
990
1043
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
991
1044
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
992
1045
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
993
1046
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
994
1047
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
995
|
-
inFlight:
|
|
1048
|
+
inFlight: z10.boolean().optional()
|
|
996
1049
|
})
|
|
997
1050
|
]);
|
|
998
|
-
var VoiceKeySchema =
|
|
999
|
-
var AgendaTurnSchema =
|
|
1051
|
+
var VoiceKeySchema = z10.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
1052
|
+
var AgendaTurnSchema = z10.object({
|
|
1000
1053
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
1001
1054
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
1002
1055
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
1003
1056
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
1004
1057
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
1005
1058
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
1006
|
-
id:
|
|
1059
|
+
id: z10.string().optional(),
|
|
1007
1060
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
1008
1061
|
* the planner declares duplicates instead of asking them twice. */
|
|
1009
|
-
coveredIds:
|
|
1062
|
+
coveredIds: z10.array(z10.string()).optional(),
|
|
1010
1063
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
1011
1064
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
1012
|
-
info:
|
|
1013
|
-
question:
|
|
1065
|
+
info: z10.array(z10.string().min(1)).default([]),
|
|
1066
|
+
question: z10.string().min(1).nullable(),
|
|
1014
1067
|
/** True on the one turn carrying the agent's own declared question. */
|
|
1015
|
-
asks:
|
|
1068
|
+
asks: z10.boolean().optional(),
|
|
1016
1069
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
1017
1070
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
1018
1071
|
* which route nothing. */
|
|
1019
|
-
claimId:
|
|
1072
|
+
claimId: z10.string().optional(),
|
|
1020
1073
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
1021
|
-
voice:
|
|
1074
|
+
voice: z10.string().optional(),
|
|
1022
1075
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
1023
1076
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
1024
1077
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
1025
1078
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
1026
|
-
agent:
|
|
1079
|
+
agent: z10.string().optional(),
|
|
1027
1080
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
1028
1081
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
1029
1082
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
1030
1083
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
1031
1084
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
1032
|
-
agentId:
|
|
1085
|
+
agentId: z10.string().optional(),
|
|
1086
|
+
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
1087
|
+
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
1088
|
+
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
1089
|
+
onTable: z10.boolean().optional(),
|
|
1033
1090
|
select: SelectShapeSchema.optional(),
|
|
1034
|
-
options:
|
|
1091
|
+
options: z10.array(OptionSchema.omit({ id: true })).optional(),
|
|
1035
1092
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
1036
1093
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
1037
1094
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -1040,33 +1097,33 @@ var AgendaTurnSchema = z9.object({
|
|
|
1040
1097
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
1041
1098
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
1042
1099
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
1043
|
-
blocking:
|
|
1100
|
+
blocking: z10.boolean().optional(),
|
|
1044
1101
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
1045
1102
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
1046
1103
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
1047
1104
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
1048
1105
|
* speak, it is dropped and their words are taken like any reply. */
|
|
1049
|
-
ifSilent:
|
|
1106
|
+
ifSilent: z10.boolean().optional()
|
|
1050
1107
|
});
|
|
1051
1108
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
1052
|
-
var InboxItemSchema =
|
|
1053
|
-
id:
|
|
1054
|
-
tokenId:
|
|
1109
|
+
var InboxItemSchema = z10.object({
|
|
1110
|
+
id: z10.string(),
|
|
1111
|
+
tokenId: z10.string().optional(),
|
|
1055
1112
|
status: NotifyStatusSchema,
|
|
1056
1113
|
context: ContextSchema,
|
|
1057
|
-
options:
|
|
1114
|
+
options: z10.array(OptionSchema).optional(),
|
|
1058
1115
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
1059
|
-
points:
|
|
1116
|
+
points: z10.array(z10.string()).optional(),
|
|
1060
1117
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
1061
1118
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
1062
1119
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
1063
1120
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
1064
|
-
asks:
|
|
1121
|
+
asks: z10.boolean().optional(),
|
|
1065
1122
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
1066
1123
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
1067
1124
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
1068
1125
|
* which must never CLAIM stalled. */
|
|
1069
|
-
lastSeenAt:
|
|
1126
|
+
lastSeenAt: z10.string().optional(),
|
|
1070
1127
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
1071
1128
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
1072
1129
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -1076,7 +1133,7 @@ var InboxItemSchema = z9.object({
|
|
|
1076
1133
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
1077
1134
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
1078
1135
|
* `createdAt`. */
|
|
1079
|
-
agentStateAt:
|
|
1136
|
+
agentStateAt: z10.string().datetime().optional(),
|
|
1080
1137
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
1081
1138
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
1082
1139
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -1090,61 +1147,61 @@ var InboxItemSchema = z9.object({
|
|
|
1090
1147
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
1091
1148
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
1092
1149
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
1093
|
-
questions:
|
|
1094
|
-
id:
|
|
1095
|
-
entryId:
|
|
1096
|
-
title:
|
|
1097
|
-
state:
|
|
1098
|
-
answer:
|
|
1150
|
+
questions: z10.array(z10.object({
|
|
1151
|
+
id: z10.string(),
|
|
1152
|
+
entryId: z10.string(),
|
|
1153
|
+
title: z10.string(),
|
|
1154
|
+
state: z10.enum(["open", "answered"]),
|
|
1155
|
+
answer: z10.string().nullable(),
|
|
1099
1156
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
1100
1157
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
1101
1158
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
1102
1159
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
1103
|
-
agent:
|
|
1160
|
+
agent: z10.object({ tokenId: z10.string(), name: z10.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
1104
1161
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
1105
1162
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
1106
1163
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
1107
1164
|
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
1108
|
-
options:
|
|
1165
|
+
options: z10.array(OptionSchema).optional(),
|
|
1109
1166
|
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
1110
1167
|
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
1111
1168
|
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
1112
1169
|
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
1113
1170
|
* preview layout for any `options`, and must see exactly what it did. */
|
|
1114
|
-
choices:
|
|
1171
|
+
choices: z10.array(OptionSchema).optional(),
|
|
1115
1172
|
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
1116
|
-
select:
|
|
1173
|
+
select: z10.enum(["one", "many", "rank"]).optional()
|
|
1117
1174
|
})).optional(),
|
|
1118
|
-
visuals:
|
|
1175
|
+
visuals: z10.array(VisualSchema).optional(),
|
|
1119
1176
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
1120
1177
|
* agent's suggestion, or a default silly name). */
|
|
1121
|
-
name:
|
|
1178
|
+
name: z10.string(),
|
|
1122
1179
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
1123
1180
|
voice: VoiceKeySchema.optional(),
|
|
1124
|
-
repo:
|
|
1125
|
-
branch:
|
|
1126
|
-
createdAt:
|
|
1127
|
-
snoozedUntil:
|
|
1181
|
+
repo: z10.string().optional(),
|
|
1182
|
+
branch: z10.string().optional(),
|
|
1183
|
+
createdAt: z10.string().datetime(),
|
|
1184
|
+
snoozedUntil: z10.string().datetime().optional(),
|
|
1128
1185
|
agentState: AgentStateSchema.default("idle"),
|
|
1129
1186
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
1130
1187
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
1131
1188
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
1132
|
-
turn:
|
|
1189
|
+
turn: z10.enum(["you", "agent"]).default("you"),
|
|
1133
1190
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
1134
1191
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
1135
1192
|
* error badge + Retry. */
|
|
1136
|
-
error:
|
|
1193
|
+
error: z10.string().optional(),
|
|
1137
1194
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
1138
1195
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
1139
1196
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
1140
1197
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
1141
|
-
cold:
|
|
1142
|
-
clarifies:
|
|
1198
|
+
cold: z10.string().datetime().optional(),
|
|
1199
|
+
clarifies: z10.string().optional(),
|
|
1143
1200
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
1144
1201
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
1145
1202
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
1146
1203
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
1147
|
-
onCall:
|
|
1204
|
+
onCall: z10.literal(true).optional(),
|
|
1148
1205
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
1149
1206
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
1150
1207
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -1154,31 +1211,31 @@ var InboxItemSchema = z9.object({
|
|
|
1154
1211
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
1155
1212
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
1156
1213
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
1157
|
-
ring:
|
|
1158
|
-
declinedAt:
|
|
1159
|
-
anchorAt:
|
|
1160
|
-
nextRingAt:
|
|
1161
|
-
step:
|
|
1214
|
+
ring: z10.object({
|
|
1215
|
+
declinedAt: z10.string().datetime(),
|
|
1216
|
+
anchorAt: z10.string().datetime(),
|
|
1217
|
+
nextRingAt: z10.string().datetime().nullable(),
|
|
1218
|
+
step: z10.number().int()
|
|
1162
1219
|
}).optional(),
|
|
1163
1220
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
1164
1221
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
1165
1222
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
1166
1223
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
1167
1224
|
* feedback ABOUT. */
|
|
1168
|
-
why:
|
|
1225
|
+
why: z10.object({
|
|
1169
1226
|
asked: NotifyLevelSchema,
|
|
1170
1227
|
got: NotifyLevelSchema,
|
|
1171
|
-
because:
|
|
1172
|
-
line:
|
|
1228
|
+
because: z10.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
1229
|
+
line: z10.string()
|
|
1173
1230
|
}).optional(),
|
|
1174
|
-
select:
|
|
1175
|
-
confirmStyle:
|
|
1231
|
+
select: z10.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
1232
|
+
confirmStyle: z10.enum(["yesno", "approve"]).default("yesno").describe(
|
|
1176
1233
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
1177
1234
|
),
|
|
1178
1235
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
1179
1236
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
1180
1237
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
1181
|
-
blocking:
|
|
1238
|
+
blocking: z10.boolean().default(false),
|
|
1182
1239
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
1183
1240
|
answer: UserAnswerSchema.optional(),
|
|
1184
1241
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -1186,17 +1243,17 @@ var InboxItemSchema = z9.object({
|
|
|
1186
1243
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
1187
1244
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
1188
1245
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
1189
|
-
communication:
|
|
1190
|
-
deliveryId:
|
|
1191
|
-
kind:
|
|
1192
|
-
entryId:
|
|
1193
|
-
goalIds:
|
|
1194
|
-
decisionNeedId:
|
|
1195
|
-
sealed:
|
|
1196
|
-
goalState:
|
|
1246
|
+
communication: z10.object({
|
|
1247
|
+
deliveryId: z10.string(),
|
|
1248
|
+
kind: z10.enum(["notification", "call"]),
|
|
1249
|
+
entryId: z10.string(),
|
|
1250
|
+
goalIds: z10.array(z10.string()),
|
|
1251
|
+
decisionNeedId: z10.string().optional(),
|
|
1252
|
+
sealed: z10.boolean(),
|
|
1253
|
+
goalState: z10.string().optional(),
|
|
1197
1254
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
1198
1255
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
1199
|
-
goalTitle:
|
|
1256
|
+
goalTitle: z10.string().optional()
|
|
1200
1257
|
}).optional(),
|
|
1201
1258
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
1202
1259
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -1208,27 +1265,27 @@ var InboxItemSchema = z9.object({
|
|
|
1208
1265
|
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
1209
1266
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
1210
1267
|
* with no rev is simply always re-sent. */
|
|
1211
|
-
rev:
|
|
1268
|
+
rev: z10.string().optional()
|
|
1212
1269
|
});
|
|
1213
1270
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
1214
|
-
var PushTokenSchema =
|
|
1215
|
-
voipToken:
|
|
1216
|
-
alertToken:
|
|
1217
|
-
fcmToken:
|
|
1218
|
-
platform:
|
|
1271
|
+
var PushTokenSchema = z10.object({
|
|
1272
|
+
voipToken: z10.string().min(1).optional(),
|
|
1273
|
+
alertToken: z10.string().min(1).optional(),
|
|
1274
|
+
fcmToken: z10.string().min(1).optional(),
|
|
1275
|
+
platform: z10.enum(["ios", "android"])
|
|
1219
1276
|
}).superRefine((v, ctx) => {
|
|
1220
1277
|
if (v.platform !== "ios") return;
|
|
1221
1278
|
for (const field of ["voipToken", "alertToken"]) {
|
|
1222
1279
|
const token = v[field];
|
|
1223
1280
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
1224
1281
|
ctx.addIssue({
|
|
1225
|
-
code:
|
|
1282
|
+
code: z10.ZodIssueCode.custom,
|
|
1226
1283
|
path: [field],
|
|
1227
1284
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
1228
1285
|
});
|
|
1229
1286
|
}
|
|
1230
1287
|
});
|
|
1231
|
-
var MissedCallSchema =
|
|
1288
|
+
var MissedCallSchema = z10.enum([
|
|
1232
1289
|
"retry_10m",
|
|
1233
1290
|
"retry_30m",
|
|
1234
1291
|
"retry_60m",
|
|
@@ -1238,33 +1295,33 @@ var MissedCallSchema = z9.enum([
|
|
|
1238
1295
|
"inbox",
|
|
1239
1296
|
"dismiss"
|
|
1240
1297
|
]);
|
|
1241
|
-
var
|
|
1242
|
-
var QUIET = ` Nothing rings from ${
|
|
1243
|
-
var BrokerTuningSchema =
|
|
1298
|
+
var clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
1299
|
+
var QUIET = ` Nothing rings from ${clock2(NIGHT.from)} to ${clock2(NIGHT.to)} your time; the count waits for morning.`;
|
|
1300
|
+
var BrokerTuningSchema = z10.object({
|
|
1244
1301
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
1245
|
-
ackVerbosity:
|
|
1302
|
+
ackVerbosity: z10.enum(["normal", "none"]).optional(),
|
|
1246
1303
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
1247
1304
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
1248
|
-
clarifyEagerness:
|
|
1305
|
+
clarifyEagerness: z10.enum(["low", "normal", "high"]).optional(),
|
|
1249
1306
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
1250
|
-
phrasebook:
|
|
1307
|
+
phrasebook: z10.array(z10.object({ say: z10.string().min(1).max(60), mean: z10.string().min(1).max(120) })).max(24).optional(),
|
|
1251
1308
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
1252
1309
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
1253
1310
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
1254
1311
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
1255
1312
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
1256
|
-
language:
|
|
1313
|
+
language: z10.enum(["en", "es"]).optional()
|
|
1257
1314
|
});
|
|
1258
|
-
var UserSettingsSchema =
|
|
1259
|
-
permissions:
|
|
1260
|
-
call:
|
|
1261
|
-
banner:
|
|
1262
|
-
push:
|
|
1315
|
+
var UserSettingsSchema = z10.object({
|
|
1316
|
+
permissions: z10.object({
|
|
1317
|
+
call: z10.boolean(),
|
|
1318
|
+
banner: z10.boolean(),
|
|
1319
|
+
push: z10.boolean()
|
|
1263
1320
|
}),
|
|
1264
1321
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
1265
1322
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
1266
1323
|
* what people read (owner, 2026-09-30). */
|
|
1267
|
-
sessionMode:
|
|
1324
|
+
sessionMode: z10.enum(["default", "all_calls", "silent"]),
|
|
1268
1325
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
1269
1326
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
1270
1327
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -1273,7 +1330,7 @@ var UserSettingsSchema = z9.object({
|
|
|
1273
1330
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
1274
1331
|
* next dead field for the same reason. */
|
|
1275
1332
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
1276
|
-
improveConsent:
|
|
1333
|
+
improveConsent: z10.boolean(),
|
|
1277
1334
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
1278
1335
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
1279
1336
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -1281,17 +1338,17 @@ var UserSettingsSchema = z9.object({
|
|
|
1281
1338
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
1282
1339
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
1283
1340
|
* write, 'hosted' on read (see store.ts). */
|
|
1284
|
-
voiceMode:
|
|
1341
|
+
voiceMode: z10.enum(["hosted", "on_device"]).optional(),
|
|
1285
1342
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
1286
1343
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
1287
1344
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
1288
1345
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
1289
1346
|
* read (see store.ts). */
|
|
1290
|
-
talk:
|
|
1347
|
+
talk: z10.boolean().optional(),
|
|
1291
1348
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
1292
1349
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
1293
1350
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
1294
|
-
callDiagnostics:
|
|
1351
|
+
callDiagnostics: z10.boolean().optional(),
|
|
1295
1352
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
1296
1353
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
1297
1354
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -1299,7 +1356,7 @@ var UserSettingsSchema = z9.object({
|
|
|
1299
1356
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
1300
1357
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
1301
1358
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
1302
|
-
voiceTuning:
|
|
1359
|
+
voiceTuning: z10.record(z10.string(), z10.union([z10.number(), z10.string()])).optional(),
|
|
1303
1360
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
1304
1361
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
1305
1362
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -1308,53 +1365,53 @@ var UserSettingsSchema = z9.object({
|
|
|
1308
1365
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
1309
1366
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
1310
1367
|
* call — honest about what we know. */
|
|
1311
|
-
timezone:
|
|
1368
|
+
timezone: z10.string().min(1).max(64).optional(),
|
|
1312
1369
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
1313
1370
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
1314
1371
|
broker: BrokerTuningSchema.optional()
|
|
1315
1372
|
});
|
|
1316
|
-
var HistoryWorkSchema =
|
|
1317
|
-
id:
|
|
1318
|
-
title:
|
|
1319
|
-
state:
|
|
1373
|
+
var HistoryWorkSchema = z10.object({
|
|
1374
|
+
id: z10.string(),
|
|
1375
|
+
title: z10.string(),
|
|
1376
|
+
state: z10.enum(["done", "cancelled"]),
|
|
1320
1377
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
1321
|
-
assignee:
|
|
1378
|
+
assignee: z10.string()
|
|
1322
1379
|
});
|
|
1323
|
-
var HistoryEntrySchema =
|
|
1324
|
-
|
|
1325
|
-
|
|
1380
|
+
var HistoryEntrySchema = z10.union([
|
|
1381
|
+
z10.object({ at: z10.string(), card: InboxItemSchema }),
|
|
1382
|
+
z10.object({ at: z10.string(), work: HistoryWorkSchema })
|
|
1326
1383
|
]);
|
|
1327
|
-
var HistoryPageSchema =
|
|
1328
|
-
entries:
|
|
1329
|
-
next:
|
|
1384
|
+
var HistoryPageSchema = z10.object({
|
|
1385
|
+
entries: z10.array(HistoryEntrySchema),
|
|
1386
|
+
next: z10.string().nullable()
|
|
1330
1387
|
});
|
|
1331
1388
|
var ACTIVITY_LINES = 2;
|
|
1332
1389
|
var ACTIVITY_LINE_MAX = 80;
|
|
1333
|
-
var AgentActivitySchema =
|
|
1390
|
+
var AgentActivitySchema = z10.object({
|
|
1334
1391
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
1335
|
-
lines:
|
|
1392
|
+
lines: z10.array(z10.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
1336
1393
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
1337
1394
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
1338
|
-
at:
|
|
1395
|
+
at: z10.string().datetime()
|
|
1339
1396
|
});
|
|
1340
|
-
var ConnectionSummarySchema =
|
|
1397
|
+
var ConnectionSummarySchema = z10.object({
|
|
1341
1398
|
/** The connection = the agent's token id (used to address a request). */
|
|
1342
|
-
id:
|
|
1399
|
+
id: z10.string(),
|
|
1343
1400
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
1344
1401
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
1345
1402
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
1346
1403
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
1347
|
-
kind:
|
|
1404
|
+
kind: z10.enum(["device", "agent"]).optional(),
|
|
1348
1405
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
1349
1406
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
1350
1407
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
1351
|
-
mintedByDevice:
|
|
1352
|
-
device:
|
|
1408
|
+
mintedByDevice: z10.string().nullable().optional(),
|
|
1409
|
+
device: z10.string().nullable(),
|
|
1353
1410
|
/** The agent's display name (the single pairing name). */
|
|
1354
|
-
name:
|
|
1411
|
+
name: z10.string(),
|
|
1355
1412
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
1356
1413
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
1357
|
-
provider:
|
|
1414
|
+
provider: z10.string().nullable(),
|
|
1358
1415
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
1359
1416
|
voice: VoiceKeySchema.nullable(),
|
|
1360
1417
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -1365,34 +1422,34 @@ var ConnectionSummarySchema = z9.object({
|
|
|
1365
1422
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
1366
1423
|
* set once must not overrule a rule they set about one agent. */
|
|
1367
1424
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
1368
|
-
createdAt:
|
|
1425
|
+
createdAt: z10.string().datetime(),
|
|
1369
1426
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
1370
1427
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
1371
|
-
lastContactAt:
|
|
1428
|
+
lastContactAt: z10.string().datetime().nullable(),
|
|
1372
1429
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
1373
1430
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
1374
|
-
lastSeenAt:
|
|
1431
|
+
lastSeenAt: z10.string().datetime().nullable().optional(),
|
|
1375
1432
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
1376
1433
|
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
1377
1434
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
1378
|
-
lastWorkedAt:
|
|
1435
|
+
lastWorkedAt: z10.string().datetime().nullable().optional(),
|
|
1379
1436
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
1380
1437
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
1381
|
-
oldestReadyAt:
|
|
1438
|
+
oldestReadyAt: z10.string().datetime().nullable().optional(),
|
|
1382
1439
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
1383
1440
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
1384
1441
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
1385
|
-
runtime:
|
|
1442
|
+
runtime: z10.object({
|
|
1386
1443
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
1387
1444
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
1388
|
-
version:
|
|
1389
|
-
harnesses:
|
|
1390
|
-
workspaces:
|
|
1445
|
+
version: z10.string().optional(),
|
|
1446
|
+
harnesses: z10.array(z10.object({ name: z10.string(), label: z10.string(), status: z10.string() })).optional(),
|
|
1447
|
+
workspaces: z10.array(z10.string()).optional(),
|
|
1391
1448
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
1392
1449
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
1393
1450
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
1394
1451
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
1395
|
-
repos:
|
|
1452
|
+
repos: z10.array(z10.object({ path: z10.string(), remote: z10.string() })).optional()
|
|
1396
1453
|
}).optional(),
|
|
1397
1454
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
1398
1455
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -1401,156 +1458,166 @@ var ConnectionSummarySchema = z9.object({
|
|
|
1401
1458
|
activity: AgentActivitySchema.optional(),
|
|
1402
1459
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
1403
1460
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
1404
|
-
managed:
|
|
1461
|
+
managed: z10.boolean()
|
|
1405
1462
|
});
|
|
1406
|
-
var LedgerItemSchema =
|
|
1407
|
-
var AgentLedgerSchema =
|
|
1463
|
+
var LedgerItemSchema = z10.object({ id: z10.string(), parentId: z10.string(), title: z10.string(), createdAt: z10.string() });
|
|
1464
|
+
var AgentLedgerSchema = z10.object({
|
|
1408
1465
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
1409
|
-
agent:
|
|
1466
|
+
agent: z10.object({ id: z10.string(), name: z10.string().nullable(), revokedAt: z10.string().nullable() }),
|
|
1410
1467
|
/** Its own questions you have not answered. */
|
|
1411
|
-
asks:
|
|
1468
|
+
asks: z10.array(LedgerItemSchema),
|
|
1412
1469
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
1413
|
-
answered:
|
|
1470
|
+
answered: z10.array(LedgerItemSchema),
|
|
1414
1471
|
/** Requests you sent it that it never took. */
|
|
1415
|
-
requests:
|
|
1416
|
-
goals:
|
|
1417
|
-
callbacks:
|
|
1472
|
+
requests: z10.array(LedgerItemSchema),
|
|
1473
|
+
goals: z10.array(z10.object({ id: z10.string(), outcome: z10.string(), state: z10.string() })),
|
|
1474
|
+
callbacks: z10.array(z10.object({ id: z10.string(), parentId: z10.string(), trigger: z10.string(), note: z10.string(), dueAt: z10.string().nullable() }))
|
|
1418
1475
|
});
|
|
1419
|
-
var ReassignResultSchema =
|
|
1420
|
-
moved:
|
|
1421
|
-
parentId:
|
|
1476
|
+
var ReassignResultSchema = z10.object({
|
|
1477
|
+
moved: z10.object({ asks: z10.number(), answered: z10.number(), requests: z10.number(), goals: z10.number(), callbacks: z10.number() }),
|
|
1478
|
+
parentId: z10.string().nullable()
|
|
1422
1479
|
});
|
|
1423
|
-
var LessonStateSchema =
|
|
1424
|
-
var LessonViewSchema =
|
|
1425
|
-
id:
|
|
1426
|
-
text:
|
|
1480
|
+
var LessonStateSchema = z10.enum(["active", "proposed", "retired"]);
|
|
1481
|
+
var LessonViewSchema = z10.object({
|
|
1482
|
+
id: z10.string(),
|
|
1483
|
+
text: z10.string(),
|
|
1427
1484
|
state: LessonStateSchema,
|
|
1428
1485
|
/** The Goal it is scoped to; null = the whole account. */
|
|
1429
|
-
scopeGoalId:
|
|
1430
|
-
goalTitle:
|
|
1431
|
-
version:
|
|
1432
|
-
pinned:
|
|
1486
|
+
scopeGoalId: z10.string().nullable(),
|
|
1487
|
+
goalTitle: z10.string().nullable(),
|
|
1488
|
+
version: z10.number(),
|
|
1489
|
+
pinned: z10.boolean(),
|
|
1433
1490
|
/** When the person last wrote its text themselves. */
|
|
1434
|
-
editedAt:
|
|
1435
|
-
createdAt:
|
|
1436
|
-
updatedAt:
|
|
1491
|
+
editedAt: z10.string().nullable(),
|
|
1492
|
+
createdAt: z10.string(),
|
|
1493
|
+
updatedAt: z10.string(),
|
|
1437
1494
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
1438
|
-
sources:
|
|
1495
|
+
sources: z10.array(z10.object({ entryId: z10.string(), words: z10.string().nullable(), at: z10.string() }))
|
|
1439
1496
|
});
|
|
1440
|
-
var QueueQuestionSchema =
|
|
1497
|
+
var QueueQuestionSchema = z10.object({
|
|
1441
1498
|
/** The decision need's id — what an answer is accepted against. */
|
|
1442
|
-
id:
|
|
1499
|
+
id: z10.string(),
|
|
1443
1500
|
/** The words that were asked, from the request Entry that asked them. */
|
|
1444
|
-
question:
|
|
1501
|
+
question: z10.string(),
|
|
1445
1502
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
1446
1503
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
1447
1504
|
* can answer. */
|
|
1448
|
-
deliveryId:
|
|
1505
|
+
deliveryId: z10.string().nullable().default(null),
|
|
1449
1506
|
/** The Entry the ruling is about. */
|
|
1450
|
-
aboutId:
|
|
1507
|
+
aboutId: z10.string().nullable().default(null),
|
|
1451
1508
|
/** Empty for a free-text question. */
|
|
1452
|
-
options:
|
|
1453
|
-
select:
|
|
1454
|
-
askedAt:
|
|
1509
|
+
options: z10.array(OptionSchema).default([]),
|
|
1510
|
+
select: z10.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
1511
|
+
askedAt: z10.string(),
|
|
1455
1512
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
1456
|
-
answeredAt:
|
|
1513
|
+
answeredAt: z10.string().nullable().default(null),
|
|
1457
1514
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
1458
1515
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
1459
1516
|
* for a settled question whose reply carried nothing readable. */
|
|
1460
|
-
answer:
|
|
1517
|
+
answer: z10.string().nullable().default(null),
|
|
1461
1518
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
1462
1519
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
1463
1520
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
1464
1521
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
1465
1522
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
1466
1523
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
1467
|
-
goalId:
|
|
1524
|
+
goalId: z10.string(),
|
|
1468
1525
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
1469
1526
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
1470
1527
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
1471
1528
|
* gate at all. */
|
|
1472
|
-
blocked:
|
|
1529
|
+
blocked: z10.boolean().default(false)
|
|
1473
1530
|
});
|
|
1474
|
-
var QueueReplySchema =
|
|
1531
|
+
var QueueReplySchema = z10.object({
|
|
1475
1532
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
1476
1533
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
1477
1534
|
* walk can name it in its zoom. */
|
|
1478
|
-
id:
|
|
1535
|
+
id: z10.string(),
|
|
1479
1536
|
/** The Goal the note is on. */
|
|
1480
|
-
goalId:
|
|
1537
|
+
goalId: z10.string(),
|
|
1481
1538
|
/** What the note said. */
|
|
1482
|
-
note:
|
|
1539
|
+
note: z10.string(),
|
|
1483
1540
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
1484
|
-
deliveryId:
|
|
1485
|
-
requestEntryId:
|
|
1486
|
-
askedAt:
|
|
1541
|
+
deliveryId: z10.string(),
|
|
1542
|
+
requestEntryId: z10.string(),
|
|
1543
|
+
askedAt: z10.string(),
|
|
1487
1544
|
/** When the person last replied — the window's start. */
|
|
1488
|
-
repliedAt:
|
|
1545
|
+
repliedAt: z10.string(),
|
|
1489
1546
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
1490
|
-
reply:
|
|
1547
|
+
reply: z10.string().nullable()
|
|
1491
1548
|
});
|
|
1492
|
-
var QueueItemSchema =
|
|
1493
|
-
id:
|
|
1549
|
+
var QueueItemSchema = z10.object({
|
|
1550
|
+
id: z10.string(),
|
|
1494
1551
|
/** One-line headline — the first sentence of the outcome. */
|
|
1495
|
-
title:
|
|
1552
|
+
title: z10.string(),
|
|
1496
1553
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
1497
|
-
intent:
|
|
1554
|
+
intent: z10.string(),
|
|
1498
1555
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
1499
|
-
state:
|
|
1556
|
+
state: z10.string(),
|
|
1500
1557
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
1501
|
-
assignee:
|
|
1558
|
+
assignee: z10.string().nullable().default(null),
|
|
1502
1559
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
1503
|
-
progress:
|
|
1560
|
+
progress: z10.string().nullable().default(null),
|
|
1504
1561
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
1505
1562
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
1506
|
-
progressLine:
|
|
1507
|
-
reviewPending:
|
|
1508
|
-
dueAt:
|
|
1563
|
+
progressLine: z10.string().nullable().optional(),
|
|
1564
|
+
reviewPending: z10.boolean().default(false),
|
|
1565
|
+
dueAt: z10.string().nullable().default(null),
|
|
1509
1566
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
1510
1567
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
1511
|
-
finishedAt:
|
|
1568
|
+
finishedAt: z10.string().nullable().optional(),
|
|
1512
1569
|
/** The Goal this one was opened under; null at the root. */
|
|
1513
|
-
parentGoalId:
|
|
1570
|
+
parentGoalId: z10.string().nullable().default(null),
|
|
1514
1571
|
/** Goals opened under this one — only those the same list holds. */
|
|
1515
|
-
childGoalIds:
|
|
1572
|
+
childGoalIds: z10.array(z10.string()).default([]),
|
|
1516
1573
|
/** Goals this one waits on (start or finish gates). */
|
|
1517
|
-
dependencyGoalIds:
|
|
1574
|
+
dependencyGoalIds: z10.array(z10.string()).default([]),
|
|
1518
1575
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
1519
|
-
blocked:
|
|
1576
|
+
blocked: z10.boolean().default(false),
|
|
1520
1577
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
1521
1578
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
1522
1579
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
1523
1580
|
* question is opened. */
|
|
1524
|
-
questions:
|
|
1581
|
+
questions: z10.array(QueueQuestionSchema).default([]),
|
|
1525
1582
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
1526
1583
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
1527
1584
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
1528
1585
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
1529
|
-
asked:
|
|
1530
|
-
answered:
|
|
1586
|
+
asked: z10.number().optional(),
|
|
1587
|
+
answered: z10.number().optional(),
|
|
1531
1588
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
1532
1589
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
1533
1590
|
* not spell an empty list. */
|
|
1534
|
-
replies:
|
|
1591
|
+
replies: z10.array(QueueReplySchema).optional(),
|
|
1535
1592
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
1536
|
-
repo:
|
|
1537
|
-
createdAt:
|
|
1538
|
-
updatedAt:
|
|
1593
|
+
repo: z10.string().nullable().optional(),
|
|
1594
|
+
createdAt: z10.string(),
|
|
1595
|
+
updatedAt: z10.string().nullable().default(null),
|
|
1539
1596
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
1540
1597
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
1541
1598
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
1542
|
-
lastProgressAt:
|
|
1599
|
+
lastProgressAt: z10.string().nullable().optional(),
|
|
1543
1600
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
1544
1601
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
1545
1602
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
1546
1603
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
1547
1604
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
1548
1605
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
1549
|
-
latest:
|
|
1550
|
-
from:
|
|
1551
|
-
said:
|
|
1552
|
-
at:
|
|
1553
|
-
entryId:
|
|
1606
|
+
latest: z10.object({
|
|
1607
|
+
from: z10.enum(["person", "agent"]),
|
|
1608
|
+
said: z10.string(),
|
|
1609
|
+
at: z10.string(),
|
|
1610
|
+
entryId: z10.string(),
|
|
1611
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
1612
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
1613
|
+
* none was. The Work row reads it before `said`. */
|
|
1614
|
+
summary: z10.string().optional(),
|
|
1615
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
1616
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
1617
|
+
titles: z10.array(z10.string()).optional(),
|
|
1618
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
1619
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
1620
|
+
line: z10.string().optional()
|
|
1554
1621
|
}).nullable().optional(),
|
|
1555
1622
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
1556
1623
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -1563,7 +1630,7 @@ var QueueItemSchema = z9.object({
|
|
|
1563
1630
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
1564
1631
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
1565
1632
|
* Entry with no `about_id` is in neither. */
|
|
1566
|
-
lastPersonAt:
|
|
1633
|
+
lastPersonAt: z10.string().nullable().optional(),
|
|
1567
1634
|
/** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
|
|
1568
1635
|
* by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
|
|
1569
1636
|
* incremental read knows a row has not moved: the phone echoes back the revs it holds
|
|
@@ -1574,40 +1641,40 @@ var QueueItemSchema = z9.object({
|
|
|
1574
1641
|
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
1575
1642
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
1576
1643
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
1577
|
-
rev:
|
|
1644
|
+
rev: z10.string().optional()
|
|
1578
1645
|
});
|
|
1579
|
-
var QueueDeltaSchema =
|
|
1580
|
-
ids:
|
|
1581
|
-
items:
|
|
1646
|
+
var QueueDeltaSchema = z10.object({
|
|
1647
|
+
ids: z10.array(z10.string()),
|
|
1648
|
+
items: z10.array(QueueItemSchema)
|
|
1582
1649
|
});
|
|
1583
1650
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
1584
|
-
var NoteSourceSchema =
|
|
1585
|
-
var NoteStatusSchema =
|
|
1586
|
-
var NoteRepeatSchema =
|
|
1587
|
-
var DecisionSchema =
|
|
1588
|
-
id:
|
|
1651
|
+
var NoteSourceSchema = z10.enum(["app", "call"]);
|
|
1652
|
+
var NoteStatusSchema = z10.enum(["open", "assigned", "in_progress", "done"]);
|
|
1653
|
+
var NoteRepeatSchema = z10.enum(["once", "until_done"]);
|
|
1654
|
+
var DecisionSchema = z10.object({
|
|
1655
|
+
id: z10.string(),
|
|
1589
1656
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
1590
1657
|
* extensibility seam — any conversation can accrue decisions). */
|
|
1591
|
-
noteId:
|
|
1658
|
+
noteId: z10.string().nullable(),
|
|
1592
1659
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
1593
|
-
question:
|
|
1660
|
+
question: z10.string(),
|
|
1594
1661
|
/** The user's ruling; null while the question is open. */
|
|
1595
|
-
answer:
|
|
1596
|
-
decidedAt:
|
|
1597
|
-
createdAt:
|
|
1662
|
+
answer: z10.string().nullable(),
|
|
1663
|
+
decidedAt: z10.string().nullable(),
|
|
1664
|
+
createdAt: z10.string()
|
|
1598
1665
|
});
|
|
1599
|
-
var NoteSchema =
|
|
1600
|
-
id:
|
|
1666
|
+
var NoteSchema = z10.object({
|
|
1667
|
+
id: z10.string(),
|
|
1601
1668
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
1602
|
-
title:
|
|
1669
|
+
title: z10.string(),
|
|
1603
1670
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
1604
|
-
intent:
|
|
1671
|
+
intent: z10.string(),
|
|
1605
1672
|
source: NoteSourceSchema,
|
|
1606
1673
|
status: NoteStatusSchema,
|
|
1607
1674
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
1608
|
-
assignee:
|
|
1675
|
+
assignee: z10.string().nullable(),
|
|
1609
1676
|
/** The request thread minted at assignment; null until assigned. */
|
|
1610
|
-
parentId:
|
|
1677
|
+
parentId: z10.string().nullable(),
|
|
1611
1678
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
1612
1679
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
1613
1680
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -1616,152 +1683,152 @@ var NoteSchema = z9.object({
|
|
|
1616
1683
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
1617
1684
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
1618
1685
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
1619
|
-
dueAt:
|
|
1686
|
+
dueAt: z10.string().nullable().default(null),
|
|
1620
1687
|
repeat: NoteRepeatSchema.default("once"),
|
|
1621
1688
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
1622
|
-
rides:
|
|
1623
|
-
lastRideAt:
|
|
1624
|
-
createdAt:
|
|
1689
|
+
rides: z10.number().int().default(0),
|
|
1690
|
+
lastRideAt: z10.string().nullable().default(null),
|
|
1691
|
+
createdAt: z10.string()
|
|
1625
1692
|
});
|
|
1626
|
-
var TriageItemSchema =
|
|
1627
|
-
noteId:
|
|
1693
|
+
var TriageItemSchema = z10.object({
|
|
1694
|
+
noteId: z10.string(),
|
|
1628
1695
|
/** The note's headline at run time. */
|
|
1629
|
-
title:
|
|
1696
|
+
title: z10.string(),
|
|
1630
1697
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
1631
1698
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
1632
1699
|
* Never a model's reasoning transcript, never an id. */
|
|
1633
|
-
why:
|
|
1700
|
+
why: z10.string()
|
|
1634
1701
|
});
|
|
1635
|
-
var TriageAssignmentSchema =
|
|
1702
|
+
var TriageAssignmentSchema = z10.object({
|
|
1636
1703
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
1637
|
-
agent:
|
|
1704
|
+
agent: z10.string(),
|
|
1638
1705
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
1639
1706
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
1640
|
-
agentName:
|
|
1641
|
-
notes:
|
|
1707
|
+
agentName: z10.string(),
|
|
1708
|
+
notes: z10.array(TriageItemSchema)
|
|
1642
1709
|
});
|
|
1643
|
-
var TriageStatusSchema =
|
|
1644
|
-
var SubmitTriageSchema =
|
|
1710
|
+
var TriageStatusSchema = z10.enum(["open", "superseded", "dismissed"]);
|
|
1711
|
+
var SubmitTriageSchema = z10.object({
|
|
1645
1712
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
1646
1713
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
1647
1714
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
1648
1715
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
1649
|
-
provider:
|
|
1716
|
+
provider: z10.string().min(1).max(60),
|
|
1650
1717
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
1651
|
-
model:
|
|
1718
|
+
model: z10.string().max(200).nullable().optional(),
|
|
1652
1719
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
1653
1720
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
1654
|
-
reviewed:
|
|
1655
|
-
close:
|
|
1656
|
-
stale:
|
|
1657
|
-
assign:
|
|
1721
|
+
reviewed: z10.number().int().min(0).max(1e4).default(0),
|
|
1722
|
+
close: z10.array(TriageItemSchema).max(200).default([]),
|
|
1723
|
+
stale: z10.array(TriageItemSchema).max(200).default([]),
|
|
1724
|
+
assign: z10.array(TriageAssignmentSchema).max(50).default([])
|
|
1658
1725
|
});
|
|
1659
1726
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
1660
|
-
id:
|
|
1661
|
-
runAt:
|
|
1727
|
+
id: z10.string(),
|
|
1728
|
+
runAt: z10.string(),
|
|
1662
1729
|
status: TriageStatusSchema,
|
|
1663
|
-
model:
|
|
1730
|
+
model: z10.string().nullable().default(null)
|
|
1664
1731
|
});
|
|
1665
|
-
var AcceptTriageSchema =
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
group:
|
|
1670
|
-
agent:
|
|
1671
|
-
noteIds:
|
|
1732
|
+
var AcceptTriageSchema = z10.discriminatedUnion("group", [
|
|
1733
|
+
z10.object({ group: z10.literal("close"), noteIds: z10.array(z10.string()).max(200).optional() }),
|
|
1734
|
+
z10.object({ group: z10.literal("stale"), noteIds: z10.array(z10.string()).max(200).optional() }),
|
|
1735
|
+
z10.object({
|
|
1736
|
+
group: z10.literal("assign"),
|
|
1737
|
+
agent: z10.string().min(1),
|
|
1738
|
+
noteIds: z10.array(z10.string()).max(200).optional()
|
|
1672
1739
|
})
|
|
1673
1740
|
]);
|
|
1674
|
-
var AcceptTriageResultSchema =
|
|
1675
|
-
accepted:
|
|
1676
|
-
failed:
|
|
1741
|
+
var AcceptTriageResultSchema = z10.object({
|
|
1742
|
+
accepted: z10.array(z10.string()),
|
|
1743
|
+
failed: z10.array(z10.object({ noteId: z10.string(), reason: z10.string() }))
|
|
1677
1744
|
});
|
|
1678
|
-
var DeliveryModeSchema =
|
|
1679
|
-
var RegisterDeliverySchema =
|
|
1680
|
-
var OAuthStartSchema =
|
|
1681
|
-
provider:
|
|
1682
|
-
returnTo:
|
|
1745
|
+
var DeliveryModeSchema = z10.enum(["poll", "self_hosted"]);
|
|
1746
|
+
var RegisterDeliverySchema = z10.object({ mode: DeliveryModeSchema });
|
|
1747
|
+
var OAuthStartSchema = z10.object({
|
|
1748
|
+
provider: z10.enum(["cma"]),
|
|
1749
|
+
returnTo: z10.string().min(1)
|
|
1683
1750
|
});
|
|
1684
|
-
var DeliveryConfigSchema =
|
|
1685
|
-
tokenId:
|
|
1751
|
+
var DeliveryConfigSchema = z10.object({
|
|
1752
|
+
tokenId: z10.string(),
|
|
1686
1753
|
mode: DeliveryModeSchema,
|
|
1687
1754
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
1688
1755
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
1689
1756
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
1690
|
-
realtime:
|
|
1757
|
+
realtime: z10.object({ url: z10.string(), anonKey: z10.string() }).nullable()
|
|
1691
1758
|
});
|
|
1692
|
-
var HostDecisionSchema =
|
|
1759
|
+
var HostDecisionSchema = z10.object({
|
|
1693
1760
|
/** The agent's token id: the row's `recipient`. */
|
|
1694
|
-
agent:
|
|
1695
|
-
decision:
|
|
1761
|
+
agent: z10.string().uuid(),
|
|
1762
|
+
decision: z10.enum(["stood_back", "took_over"]),
|
|
1696
1763
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
1697
|
-
goalId:
|
|
1764
|
+
goalId: z10.string().uuid().nullable().optional(),
|
|
1698
1765
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
1699
|
-
seenAt:
|
|
1766
|
+
seenAt: z10.string().datetime().nullable().optional(),
|
|
1700
1767
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
1701
|
-
since:
|
|
1768
|
+
since: z10.string().datetime().nullable().optional(),
|
|
1702
1769
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
1703
|
-
said:
|
|
1770
|
+
said: z10.string().max(300).optional()
|
|
1704
1771
|
});
|
|
1705
|
-
var WakeNudgeSchema =
|
|
1706
|
-
kind:
|
|
1707
|
-
notificationId:
|
|
1708
|
-
parentId:
|
|
1772
|
+
var WakeNudgeSchema = z10.object({
|
|
1773
|
+
kind: z10.enum(["reply", "request", "callback"]),
|
|
1774
|
+
notificationId: z10.string().optional(),
|
|
1775
|
+
parentId: z10.string()
|
|
1709
1776
|
});
|
|
1710
|
-
var PairingStatusSchema =
|
|
1711
|
-
var DeviceCodeSchema =
|
|
1712
|
-
device_code:
|
|
1713
|
-
user_code:
|
|
1714
|
-
verification_uri:
|
|
1715
|
-
verification_uri_complete:
|
|
1716
|
-
interval:
|
|
1717
|
-
expires_in:
|
|
1777
|
+
var PairingStatusSchema = z10.enum(["pending", "approved", "denied", "expired"]);
|
|
1778
|
+
var DeviceCodeSchema = z10.object({
|
|
1779
|
+
device_code: z10.string(),
|
|
1780
|
+
user_code: z10.string(),
|
|
1781
|
+
verification_uri: z10.string().url(),
|
|
1782
|
+
verification_uri_complete: z10.string().url(),
|
|
1783
|
+
interval: z10.number(),
|
|
1784
|
+
expires_in: z10.number()
|
|
1718
1785
|
});
|
|
1719
|
-
var DeviceInfoSchema =
|
|
1720
|
-
code:
|
|
1786
|
+
var DeviceInfoSchema = z10.object({
|
|
1787
|
+
code: z10.string(),
|
|
1721
1788
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
1722
1789
|
* pre-filling the name field the human can edit. */
|
|
1723
|
-
name:
|
|
1790
|
+
name: z10.string(),
|
|
1724
1791
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
1725
1792
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
1726
1793
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
1727
1794
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
1728
|
-
agent:
|
|
1729
|
-
device:
|
|
1795
|
+
agent: z10.string().optional(),
|
|
1796
|
+
device: z10.string().nullable(),
|
|
1730
1797
|
status: PairingStatusSchema
|
|
1731
1798
|
});
|
|
1732
|
-
var DeviceTokenSchema =
|
|
1733
|
-
access_token:
|
|
1799
|
+
var DeviceTokenSchema = z10.object({
|
|
1800
|
+
access_token: z10.string(),
|
|
1734
1801
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
1735
1802
|
* a default silly name). */
|
|
1736
|
-
name:
|
|
1737
|
-
device:
|
|
1803
|
+
name: z10.string(),
|
|
1804
|
+
device: z10.string().nullable(),
|
|
1738
1805
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
1739
1806
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
1740
|
-
voice:
|
|
1807
|
+
voice: z10.string().nullable().optional(),
|
|
1741
1808
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
1742
1809
|
* that survives a rename. Cached by the host's identity beat. */
|
|
1743
|
-
token_id:
|
|
1810
|
+
token_id: z10.string().nullable().optional(),
|
|
1744
1811
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
1745
1812
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
1746
1813
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
1747
1814
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
1748
|
-
workspace:
|
|
1815
|
+
workspace: z10.string().nullable().optional(),
|
|
1749
1816
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
1750
|
-
harness:
|
|
1751
|
-
session_id:
|
|
1817
|
+
harness: z10.enum(["claude", "codex", "agy"]).optional(),
|
|
1818
|
+
session_id: z10.string().uuid().optional(),
|
|
1752
1819
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
1753
1820
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
1754
1821
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
1755
1822
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
1756
|
-
full_session:
|
|
1757
|
-
uik_pub:
|
|
1823
|
+
full_session: z10.string().optional(),
|
|
1824
|
+
uik_pub: z10.string().nullable().optional()
|
|
1758
1825
|
});
|
|
1759
|
-
var SupportRequestSchema =
|
|
1760
|
-
email:
|
|
1761
|
-
message:
|
|
1762
|
-
name:
|
|
1826
|
+
var SupportRequestSchema = z10.object({
|
|
1827
|
+
email: z10.string().email().max(320),
|
|
1828
|
+
message: z10.string().trim().min(1).max(5e3),
|
|
1829
|
+
name: z10.string().trim().max(120).optional()
|
|
1763
1830
|
});
|
|
1764
|
-
var NotificationFeedbackKindSchema =
|
|
1831
|
+
var NotificationFeedbackKindSchema = z10.enum([
|
|
1765
1832
|
"break_down",
|
|
1766
1833
|
// "This should be more than one ask — break it down."
|
|
1767
1834
|
"regenerate_options",
|
|
@@ -1776,116 +1843,119 @@ var NotificationFeedbackKindSchema = z9.enum([
|
|
|
1776
1843
|
// anything else — the note carries it.
|
|
1777
1844
|
]);
|
|
1778
1845
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
1779
|
-
var QuestionRowSchema =
|
|
1846
|
+
var QuestionRowSchema = z10.object({
|
|
1780
1847
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
1781
|
-
id:
|
|
1782
|
-
deliveryId:
|
|
1783
|
-
entryId:
|
|
1848
|
+
id: z10.string(),
|
|
1849
|
+
deliveryId: z10.string(),
|
|
1850
|
+
entryId: z10.string(),
|
|
1784
1851
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
1785
|
-
needId:
|
|
1786
|
-
goalIds:
|
|
1852
|
+
needId: z10.string().nullable(),
|
|
1853
|
+
goalIds: z10.array(z10.string()),
|
|
1787
1854
|
/** The name of the work it is about, when the read could word it. */
|
|
1788
|
-
goalTitle:
|
|
1789
|
-
tokenId:
|
|
1790
|
-
name:
|
|
1791
|
-
title:
|
|
1792
|
-
body:
|
|
1793
|
-
select:
|
|
1794
|
-
options:
|
|
1795
|
-
hasPreview:
|
|
1796
|
-
blocking:
|
|
1797
|
-
askedAt:
|
|
1855
|
+
goalTitle: z10.string().optional(),
|
|
1856
|
+
tokenId: z10.string().optional(),
|
|
1857
|
+
name: z10.string(),
|
|
1858
|
+
title: z10.string(),
|
|
1859
|
+
body: z10.string(),
|
|
1860
|
+
select: z10.enum(["one", "many", "rank", "confirm", "text"]),
|
|
1861
|
+
options: z10.array(SlimOptionSchema),
|
|
1862
|
+
hasPreview: z10.boolean(),
|
|
1863
|
+
blocking: z10.boolean(),
|
|
1864
|
+
askedAt: z10.string().datetime(),
|
|
1798
1865
|
ring: InboxItemSchema.shape.ring,
|
|
1799
|
-
onCall:
|
|
1800
|
-
sealed:
|
|
1866
|
+
onCall: z10.literal(true).optional(),
|
|
1867
|
+
sealed: z10.boolean()
|
|
1801
1868
|
});
|
|
1802
|
-
var WorkStateSchema =
|
|
1803
|
-
var WorkRowSchema =
|
|
1804
|
-
id:
|
|
1805
|
-
parentId:
|
|
1806
|
-
title:
|
|
1869
|
+
var WorkStateSchema = z10.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
1870
|
+
var WorkRowSchema = z10.object({
|
|
1871
|
+
id: z10.string(),
|
|
1872
|
+
parentId: z10.string().nullable(),
|
|
1873
|
+
title: z10.string(),
|
|
1807
1874
|
/** Straight off the Goal. */
|
|
1808
1875
|
state: WorkStateSchema,
|
|
1809
|
-
owner:
|
|
1810
|
-
revision:
|
|
1876
|
+
owner: z10.string().nullable(),
|
|
1877
|
+
revision: z10.number().int(),
|
|
1811
1878
|
/** Open questions on it, counted to 100. */
|
|
1812
|
-
waiting:
|
|
1879
|
+
waiting: z10.number().int(),
|
|
1813
1880
|
/** Held by a gate on work that is not done. */
|
|
1814
|
-
blocked:
|
|
1815
|
-
lastProgressAt:
|
|
1881
|
+
blocked: z10.boolean(),
|
|
1882
|
+
lastProgressAt: z10.string().datetime().nullable(),
|
|
1816
1883
|
/** The line written for its newest progress note, else that note's first words. */
|
|
1817
|
-
line:
|
|
1884
|
+
line: z10.string().nullable(),
|
|
1818
1885
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
1819
|
-
children:
|
|
1820
|
-
createdAt:
|
|
1821
|
-
updatedAt:
|
|
1886
|
+
children: z10.number().int(),
|
|
1887
|
+
createdAt: z10.string().datetime(),
|
|
1888
|
+
updatedAt: z10.string().datetime(),
|
|
1822
1889
|
/** When anything at or under it last moved — the order the list is in. */
|
|
1823
|
-
activeAt:
|
|
1890
|
+
activeAt: z10.string().datetime(),
|
|
1824
1891
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
1825
|
-
sealed:
|
|
1892
|
+
sealed: z10.boolean()
|
|
1826
1893
|
});
|
|
1827
1894
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
1828
1895
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
1829
1896
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
1830
|
-
asking:
|
|
1831
|
-
oldestAskAt:
|
|
1897
|
+
asking: z10.number().int().nullable(),
|
|
1898
|
+
oldestAskAt: z10.string().datetime().nullable(),
|
|
1832
1899
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
1833
1900
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
1834
1901
|
* (`agent_holds`); null when that read failed. */
|
|
1835
|
-
holds:
|
|
1836
|
-
held:
|
|
1902
|
+
holds: z10.array(z10.object({ id: z10.string(), title: z10.string() })).nullable(),
|
|
1903
|
+
held: z10.number().int().nullable(),
|
|
1837
1904
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
1838
1905
|
* while none has, or when that read failed. */
|
|
1839
|
-
cold:
|
|
1906
|
+
cold: z10.string().datetime().nullable(),
|
|
1840
1907
|
/** The newest line of its working log, and when the harness saw it. */
|
|
1841
|
-
line:
|
|
1842
|
-
lineAt:
|
|
1908
|
+
line: z10.string().nullable(),
|
|
1909
|
+
lineAt: z10.string().datetime().nullable()
|
|
1843
1910
|
});
|
|
1844
|
-
var SnapshotSchema =
|
|
1911
|
+
var SnapshotSchema = z10.object({
|
|
1845
1912
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
1846
|
-
at:
|
|
1847
|
-
questions:
|
|
1913
|
+
at: z10.string().datetime(),
|
|
1914
|
+
questions: z10.object({
|
|
1848
1915
|
/** The newest 30 open cards, questions before updates. */
|
|
1849
|
-
items:
|
|
1916
|
+
items: z10.array(QuestionRowSchema),
|
|
1850
1917
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
1851
|
-
total:
|
|
1852
|
-
updates:
|
|
1853
|
-
putOff:
|
|
1918
|
+
total: z10.number().int(),
|
|
1919
|
+
updates: z10.number().int(),
|
|
1920
|
+
putOff: z10.number().int()
|
|
1854
1921
|
}).nullable(),
|
|
1855
|
-
agents:
|
|
1922
|
+
agents: z10.object({
|
|
1856
1923
|
/** Up to 60, most recently seen first. */
|
|
1857
|
-
items:
|
|
1858
|
-
more:
|
|
1924
|
+
items: z10.array(AgentRowSchema),
|
|
1925
|
+
more: z10.boolean()
|
|
1859
1926
|
}).nullable(),
|
|
1860
|
-
work:
|
|
1927
|
+
work: z10.object({
|
|
1861
1928
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
1862
|
-
items:
|
|
1929
|
+
items: z10.array(WorkRowSchema),
|
|
1863
1930
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
1864
1931
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
1865
1932
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
1866
1933
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
1867
|
-
counts:
|
|
1934
|
+
counts: z10.object({ live: z10.number().int(), waiting: z10.number().int(), notStarted: z10.number().int(), done: z10.number().int() })
|
|
1868
1935
|
}).nullable(),
|
|
1869
|
-
you:
|
|
1936
|
+
you: z10.object({
|
|
1870
1937
|
settings: UserSettingsSchema,
|
|
1871
|
-
callable:
|
|
1938
|
+
callable: z10.boolean(),
|
|
1872
1939
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
1873
|
-
computers:
|
|
1940
|
+
computers: z10.array(ComputerRowSchema).nullable()
|
|
1874
1941
|
}).nullable()
|
|
1875
1942
|
});
|
|
1876
|
-
var CallRecapSchema =
|
|
1877
|
-
call:
|
|
1878
|
-
status:
|
|
1879
|
-
startedAt:
|
|
1880
|
-
durationMs:
|
|
1881
|
-
agents:
|
|
1943
|
+
var CallRecapSchema = z10.object({
|
|
1944
|
+
call: z10.object({
|
|
1945
|
+
status: z10.string(),
|
|
1946
|
+
startedAt: z10.string(),
|
|
1947
|
+
durationMs: z10.number().nullable(),
|
|
1948
|
+
agents: z10.array(z10.object({ id: z10.string(), name: z10.string().nullable() }))
|
|
1882
1949
|
}),
|
|
1883
|
-
topics:
|
|
1884
|
-
goalId:
|
|
1885
|
-
title:
|
|
1886
|
-
owner:
|
|
1887
|
-
state:
|
|
1888
|
-
|
|
1950
|
+
topics: z10.array(z10.object({
|
|
1951
|
+
goalId: z10.string().uuid(),
|
|
1952
|
+
title: z10.string(),
|
|
1953
|
+
owner: z10.string(),
|
|
1954
|
+
state: z10.string(),
|
|
1955
|
+
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
1956
|
+
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
1957
|
+
presented: z10.boolean(),
|
|
1958
|
+
questions: z10.array(z10.object({ id: z10.string().uuid(), state: z10.string(), title: z10.string() })),
|
|
1889
1959
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
1890
1960
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
1891
1961
|
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
@@ -1895,18 +1965,33 @@ var CallRecapSchema = z9.object({
|
|
|
1895
1965
|
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
1896
1966
|
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
1897
1967
|
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
1898
|
-
lines:
|
|
1899
|
-
entryId:
|
|
1900
|
-
words:
|
|
1901
|
-
summary:
|
|
1902
|
-
about:
|
|
1903
|
-
reply:
|
|
1904
|
-
replyKind:
|
|
1905
|
-
at:
|
|
1968
|
+
lines: z10.array(z10.object({
|
|
1969
|
+
entryId: z10.string().uuid(),
|
|
1970
|
+
words: z10.string(),
|
|
1971
|
+
summary: z10.string().optional(),
|
|
1972
|
+
about: z10.string().nullable().optional(),
|
|
1973
|
+
reply: z10.string().nullable().optional(),
|
|
1974
|
+
replyKind: z10.string().nullable().optional(),
|
|
1975
|
+
at: z10.array(z10.number()).optional()
|
|
1906
1976
|
}))
|
|
1907
1977
|
})),
|
|
1908
|
-
unfiled:
|
|
1909
|
-
|
|
1978
|
+
unfiled: z10.array(z10.object({ lineId: z10.string().uuid(), words: z10.string(), atMs: z10.number() })),
|
|
1979
|
+
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
1980
|
+
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
1981
|
+
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
1982
|
+
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
1983
|
+
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
1984
|
+
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
1985
|
+
said: z10.array(z10.object({
|
|
1986
|
+
lineId: z10.string().uuid(),
|
|
1987
|
+
speaker: z10.enum(["user", "agent"]),
|
|
1988
|
+
agent: z10.object({ id: z10.string().nullable(), name: z10.string().nullable() }).nullable(),
|
|
1989
|
+
words: z10.string(),
|
|
1990
|
+
atMs: z10.number()
|
|
1991
|
+
})),
|
|
1992
|
+
more: z10.object({ lines: z10.number(), entries: z10.number(), topics: z10.number() }),
|
|
1993
|
+
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
1994
|
+
next: z10.number().int().nonnegative().nullable().optional()
|
|
1910
1995
|
});
|
|
1911
1996
|
|
|
1912
1997
|
// src/listening.ts
|