@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
|
@@ -7,7 +7,7 @@ import { homedir } from "os";
|
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9
9
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
10
|
-
import { z as
|
|
10
|
+
import { z as z10 } from "zod";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { z as z3 } from "zod";
|
|
13
13
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
@@ -19,6 +19,7 @@ import { z as z5 } from "zod";
|
|
|
19
19
|
import { z as z6 } from "zod";
|
|
20
20
|
import { z as z7 } from "zod";
|
|
21
21
|
import { z as z8 } from "zod";
|
|
22
|
+
import { z as z9 } from "zod";
|
|
22
23
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
23
24
|
import { homedir as homedir2 } from "os";
|
|
24
25
|
import { join as join2 } from "path";
|
|
@@ -67,10 +68,47 @@ function claudeConversation() {
|
|
|
67
68
|
if (!envSession("CLAUDE_CODE_SESSION_ID") || process.env.CLAUDE_CODE_CHILD_SESSION) return void 0;
|
|
68
69
|
const dir = envSession("CLAUDE_CONFIG_DIR") ?? join(homedir(), ".claude");
|
|
69
70
|
for (const pid of ancestors()) {
|
|
71
|
+
let text;
|
|
70
72
|
try {
|
|
71
|
-
|
|
72
|
-
return typeof sessionId2 === "string" && sessionId2.trim() ? sessionId2 : void 0;
|
|
73
|
+
text = readFileSync(join(dir, "sessions", `${pid}.json`), "utf8");
|
|
73
74
|
} catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const record = parseSessionRecord(text);
|
|
78
|
+
if (!record) continue;
|
|
79
|
+
return typeof record.sessionId === "string" && record.sessionId.trim() ? record.sessionId : void 0;
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
function parseSessionRecord(text) {
|
|
84
|
+
const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
85
|
+
try {
|
|
86
|
+
return object(JSON.parse(text));
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
let depth = 0;
|
|
90
|
+
let start = -1;
|
|
91
|
+
let quoted = false;
|
|
92
|
+
let escaped = false;
|
|
93
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
94
|
+
const c = text[i];
|
|
95
|
+
if (quoted) {
|
|
96
|
+
if (escaped) escaped = false;
|
|
97
|
+
else if (c === "\\") escaped = true;
|
|
98
|
+
else if (c === '"') quoted = false;
|
|
99
|
+
} else if (c === '"') quoted = true;
|
|
100
|
+
else if (c === "{") {
|
|
101
|
+
if (depth === 0) start = i;
|
|
102
|
+
depth += 1;
|
|
103
|
+
} else if (c === "}" && depth > 0) {
|
|
104
|
+
depth -= 1;
|
|
105
|
+
if (depth === 0) {
|
|
106
|
+
try {
|
|
107
|
+
return object(JSON.parse(text.slice(start, i + 1)));
|
|
108
|
+
} catch {
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
74
112
|
}
|
|
75
113
|
}
|
|
76
114
|
return void 0;
|
|
@@ -1458,16 +1496,16 @@ function parseWhen(said2, tz, nowMs, read22 = "earliest") {
|
|
|
1458
1496
|
let hour = null;
|
|
1459
1497
|
let minute = 0;
|
|
1460
1498
|
const spoken = new RegExp(`\\bat\\s+(${Object.keys(SPOKEN_HOUR).join("|")})\\b`, "i").exec(text);
|
|
1461
|
-
const
|
|
1499
|
+
const clock3 = CLOCK.exec(text);
|
|
1462
1500
|
if (spoken) {
|
|
1463
1501
|
hour = SPOKEN_HOUR[spoken[1].toLowerCase()];
|
|
1464
|
-
} else if (
|
|
1465
|
-
hour = Number(
|
|
1466
|
-
minute =
|
|
1502
|
+
} else if (clock3 && (clock3[3] || /\bat\s/i.test(clock3[0]))) {
|
|
1503
|
+
hour = Number(clock3[1]);
|
|
1504
|
+
minute = clock3[2] ? Number(clock3[2]) : 0;
|
|
1467
1505
|
if (hour > 23 || minute > 59) return null;
|
|
1468
1506
|
}
|
|
1469
1507
|
if (hour !== null) {
|
|
1470
|
-
const mer =
|
|
1508
|
+
const mer = clock3?.[3]?.toLowerCase().replace(/\./g, "") ?? "";
|
|
1471
1509
|
const pmWord = /\b(p\.?m\.?|afternoon|evening|tonight|at night)\b/i.test(text);
|
|
1472
1510
|
const amWord = /\b(a\.?m\.?|morning)\b/i.test(text);
|
|
1473
1511
|
if (mer.startsWith("p") || pmWord && !amWord) {
|
|
@@ -1511,6 +1549,16 @@ function parseDelay(said2, tz, nowMs, read22 = "earliest") {
|
|
|
1511
1549
|
return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
|
|
1512
1550
|
}
|
|
1513
1551
|
var NIGHT = { from: 23, to: 7 };
|
|
1552
|
+
var ASKING_RULES = `HOW TO ASK:
|
|
1553
|
+
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.
|
|
1554
|
+
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.
|
|
1555
|
+
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.
|
|
1556
|
+
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.
|
|
1557
|
+
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\`.
|
|
1558
|
+
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.
|
|
1559
|
+
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.
|
|
1560
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
|
|
1561
|
+
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.`;
|
|
1514
1562
|
function draft2020(node) {
|
|
1515
1563
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
1516
1564
|
if (node && typeof node === "object") {
|
|
@@ -1629,10 +1677,18 @@ var UpdateInputSchema = z2.object({
|
|
|
1629
1677
|
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
1630
1678
|
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
1631
1679
|
reply: z2.string().trim().min(1).describe(
|
|
1632
|
-
`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.
|
|
1680
|
+
`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.`
|
|
1633
1681
|
).optional(),
|
|
1634
1682
|
userExplicitlyRequested
|
|
1635
1683
|
}).strict().superRefine((u, ctx) => {
|
|
1684
|
+
if (u.userExplicitlyRequested && !u.reply) {
|
|
1685
|
+
ctx.addIssue({
|
|
1686
|
+
code: z2.ZodIssueCode.custom,
|
|
1687
|
+
path: ["reply"],
|
|
1688
|
+
params: { refusal: "reply_required" },
|
|
1689
|
+
message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1636
1692
|
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
1637
1693
|
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
1638
1694
|
ctx.addIssue({
|
|
@@ -1720,14 +1776,10 @@ var ContactWireSchema = z2.object({
|
|
|
1720
1776
|
questions: z2.array(QuestionWireSchema).min(1).max(20).optional()
|
|
1721
1777
|
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
1722
1778
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1723
|
-
var CONTACT_DESCRIPTION =
|
|
1724
|
-
|
|
1725
|
-
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.
|
|
1779
|
+
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 + `
|
|
1726
1780
|
|
|
1727
1781
|
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.
|
|
1728
1782
|
|
|
1729
|
-
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".
|
|
1730
|
-
|
|
1731
1783
|
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.
|
|
1732
1784
|
|
|
1733
1785
|
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\`).`;
|
|
@@ -1833,6 +1885,10 @@ var GetGoalSchema = z3.object({
|
|
|
1833
1885
|
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
1834
1886
|
diagnose: z3.boolean().optional()
|
|
1835
1887
|
}).strict();
|
|
1888
|
+
var GetDeliverySchema = z3.object({
|
|
1889
|
+
deliveryId: z3.string().uuid().describe("The Delivery id returned by contact or receive.")
|
|
1890
|
+
}).strict();
|
|
1891
|
+
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.";
|
|
1836
1892
|
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.";
|
|
1837
1893
|
var SearchToolSchema = z3.object({
|
|
1838
1894
|
query: z3.string().trim().min(1).max(500),
|
|
@@ -1841,6 +1897,8 @@ var SearchToolSchema = z3.object({
|
|
|
1841
1897
|
limit: z3.number().int().min(1).max(20).optional()
|
|
1842
1898
|
}).strict();
|
|
1843
1899
|
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.";
|
|
1900
|
+
var ConfigureIdentitySchema = z3.object({}).strict();
|
|
1901
|
+
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.";
|
|
1844
1902
|
var CheckActivitySchema = z3.object({}).strict();
|
|
1845
1903
|
var FEEDBACK_TEXT_MAX = 5e4;
|
|
1846
1904
|
var SendFeedbackSchema = z3.object({
|
|
@@ -1850,8 +1908,10 @@ var SendFeedbackSchema = z3.object({
|
|
|
1850
1908
|
}).strict();
|
|
1851
1909
|
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.";
|
|
1852
1910
|
var AGENT_TOOLS = [
|
|
1911
|
+
{ name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
|
|
1853
1912
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
1854
1913
|
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
1914
|
+
{ name: "get_delivery", description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
|
|
1855
1915
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
1856
1916
|
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
1857
1917
|
{ 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) },
|
|
@@ -2147,20 +2207,58 @@ function askUnits(request, entries) {
|
|
|
2147
2207
|
}
|
|
2148
2208
|
var LIVE_MS = 3 * 6e4;
|
|
2149
2209
|
var WORKING_MS = 60 * 6e4;
|
|
2150
|
-
var
|
|
2151
|
-
|
|
2152
|
-
|
|
2210
|
+
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
2211
|
+
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
2212
|
+
var json = z9.lazy(() => z9.union([
|
|
2213
|
+
z9.string(),
|
|
2214
|
+
z9.number().finite(),
|
|
2215
|
+
z9.boolean(),
|
|
2216
|
+
z9.null(),
|
|
2217
|
+
z9.array(json),
|
|
2218
|
+
z9.record(json)
|
|
2219
|
+
]));
|
|
2220
|
+
var clock = z9.number().int().nonnegative().safe();
|
|
2221
|
+
var HeardEventSchema = z9.object({
|
|
2222
|
+
eventId: z9.string().uuid(),
|
|
2223
|
+
source: z9.enum(["phone", "bot"]),
|
|
2224
|
+
sequence: clock,
|
|
2225
|
+
kind: z9.string().min(1),
|
|
2226
|
+
phoneAtMs: clock.optional(),
|
|
2227
|
+
botAtMs: clock.optional(),
|
|
2228
|
+
payload: z9.record(json)
|
|
2229
|
+
}).strict();
|
|
2230
|
+
var HeardCursorSchema = HeardEventSchema.pick({ source: true, sequence: true, eventId: true });
|
|
2231
|
+
var HeardPageSchema = z9.object({
|
|
2232
|
+
events: z9.array(HeardEventSchema).max(200),
|
|
2233
|
+
next: HeardCursorSchema.nullable()
|
|
2234
|
+
}).strict();
|
|
2235
|
+
var MAX_EVENTS = 50;
|
|
2236
|
+
var MAX_BYTES = 8e4;
|
|
2237
|
+
var batchBytes = (events) => {
|
|
2238
|
+
let bytes = 0;
|
|
2239
|
+
for (const char of JSON.stringify({ events })) {
|
|
2240
|
+
const point = char.codePointAt(0);
|
|
2241
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
2242
|
+
}
|
|
2243
|
+
return bytes;
|
|
2244
|
+
};
|
|
2245
|
+
var HeardBatchSchema = z9.object({
|
|
2246
|
+
events: z9.array(HeardEventSchema).min(1).max(MAX_EVENTS)
|
|
2247
|
+
}).strict().refine((value) => batchBytes(value.events) <= MAX_BYTES, "heard_batch_too_large");
|
|
2248
|
+
var ContextSchema = z10.object({
|
|
2249
|
+
title: z10.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
2250
|
+
description: z10.array(z10.string().min(1)).describe(
|
|
2153
2251
|
"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."
|
|
2154
2252
|
),
|
|
2155
2253
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
2156
2254
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
2157
|
-
units:
|
|
2255
|
+
units: z10.array(z10.object({ title: z10.string(), body: z10.string() })).optional()
|
|
2158
2256
|
});
|
|
2159
|
-
var ParticipantSchema =
|
|
2160
|
-
kind:
|
|
2161
|
-
id:
|
|
2257
|
+
var ParticipantSchema = z10.object({
|
|
2258
|
+
kind: z10.enum(["human", "agent"]),
|
|
2259
|
+
id: z10.string()
|
|
2162
2260
|
});
|
|
2163
|
-
var TransformSchema =
|
|
2261
|
+
var TransformSchema = z10.enum([
|
|
2164
2262
|
"structure",
|
|
2165
2263
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
2166
2264
|
"request_more",
|
|
@@ -2183,13 +2281,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
|
|
|
2183
2281
|
function isPaigy(ref) {
|
|
2184
2282
|
return ref === participantRef(PAIGY_SELF);
|
|
2185
2283
|
}
|
|
2186
|
-
var VisualSchema =
|
|
2187
|
-
url:
|
|
2188
|
-
label:
|
|
2284
|
+
var VisualSchema = z10.object({
|
|
2285
|
+
url: z10.string().url(),
|
|
2286
|
+
label: z10.string().optional()
|
|
2189
2287
|
});
|
|
2190
|
-
var NotifyLevelSchema =
|
|
2191
|
-
var SelectShapeSchema =
|
|
2192
|
-
var ReceiptEventSchema =
|
|
2288
|
+
var NotifyLevelSchema = z10.enum(["inbox", "push", "banner", "call"]);
|
|
2289
|
+
var SelectShapeSchema = z10.enum(["one", "many", "rank", "confirm", "text"]);
|
|
2290
|
+
var ReceiptEventSchema = z10.enum([
|
|
2193
2291
|
"delivered",
|
|
2194
2292
|
// the bundle reached the recipient at some level
|
|
2195
2293
|
"seen",
|
|
@@ -2219,47 +2317,47 @@ var ReceiptEventSchema = z9.enum([
|
|
|
2219
2317
|
// be rewound by a writer that forgot to advance it.
|
|
2220
2318
|
"restarted"
|
|
2221
2319
|
]);
|
|
2222
|
-
var AttentionSchema =
|
|
2320
|
+
var AttentionSchema = z10.object({
|
|
2223
2321
|
urgency: NotifyLevelSchema,
|
|
2224
2322
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
2225
2323
|
select: SelectShapeSchema.nullable(),
|
|
2226
2324
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
2227
|
-
points:
|
|
2325
|
+
points: z10.array(z10.string()).nullable(),
|
|
2228
2326
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
2229
|
-
blocking:
|
|
2327
|
+
blocking: z10.boolean(),
|
|
2230
2328
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
2231
2329
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
2232
|
-
deadline:
|
|
2330
|
+
deadline: z10.string().datetime().nullable().optional()
|
|
2233
2331
|
});
|
|
2234
|
-
var NotifyRequestFields =
|
|
2332
|
+
var NotifyRequestFields = z10.object({
|
|
2235
2333
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
2236
2334
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
2237
2335
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
2238
2336
|
context: ContextSchema.optional(),
|
|
2239
|
-
options:
|
|
2337
|
+
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(
|
|
2240
2338
|
"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)."
|
|
2241
2339
|
),
|
|
2242
|
-
points:
|
|
2340
|
+
points: z10.array(z10.string().min(1)).optional().describe(
|
|
2243
2341
|
"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."
|
|
2244
2342
|
),
|
|
2245
|
-
visuals:
|
|
2343
|
+
visuals: z10.array(VisualSchema).optional().describe(
|
|
2246
2344
|
"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."
|
|
2247
2345
|
),
|
|
2248
2346
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
2249
|
-
repo:
|
|
2347
|
+
repo: z10.string().optional(),
|
|
2250
2348
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
2251
|
-
branch:
|
|
2349
|
+
branch: z10.string().optional(),
|
|
2252
2350
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
2253
2351
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
2254
2352
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
2255
2353
|
* still accepts the old name from older clients. */
|
|
2256
|
-
parentId:
|
|
2354
|
+
parentId: z10.string().uuid().optional(),
|
|
2257
2355
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
2258
2356
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
2259
|
-
workId:
|
|
2357
|
+
workId: z10.string().uuid().optional(),
|
|
2260
2358
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
2261
2359
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
2262
|
-
goalId:
|
|
2360
|
+
goalId: z10.string().uuid().optional(),
|
|
2263
2361
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
2264
2362
|
"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."
|
|
2265
2363
|
),
|
|
@@ -2267,7 +2365,7 @@ var NotifyRequestFields = z9.object({
|
|
|
2267
2365
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
2268
2366
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
2269
2367
|
* says HOW. */
|
|
2270
|
-
clarifies:
|
|
2368
|
+
clarifies: z10.string().optional(),
|
|
2271
2369
|
select: SelectShapeSchema.optional().describe(
|
|
2272
2370
|
"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."
|
|
2273
2371
|
),
|
|
@@ -2281,20 +2379,20 @@ var NotifyRequestFields = z9.object({
|
|
|
2281
2379
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2282
2380
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2283
2381
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2284
|
-
ask:
|
|
2382
|
+
ask: z10.string().min(1).max(1e4).optional().describe(
|
|
2285
2383
|
'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.'
|
|
2286
2384
|
),
|
|
2287
|
-
needs:
|
|
2385
|
+
needs: z10.array(z10.string().min(1)).optional().describe(
|
|
2288
2386
|
"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."
|
|
2289
2387
|
),
|
|
2290
|
-
urgencyHint:
|
|
2388
|
+
urgencyHint: z10.enum(["whenever", "soon", "now"]).optional().describe(
|
|
2291
2389
|
"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."
|
|
2292
2390
|
),
|
|
2293
2391
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
2294
2392
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
2295
2393
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
2296
2394
|
* urgencyHint/blocking win when both are sent. */
|
|
2297
|
-
waiting:
|
|
2395
|
+
waiting: z10.enum(["none", "soft", "hard"]).optional().describe(
|
|
2298
2396
|
"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."
|
|
2299
2397
|
),
|
|
2300
2398
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -2302,98 +2400,98 @@ var NotifyRequestFields = z9.object({
|
|
|
2302
2400
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
2303
2401
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
2304
2402
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
2305
|
-
confirm:
|
|
2403
|
+
confirm: z10.boolean().optional().describe(
|
|
2306
2404
|
"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'."
|
|
2307
2405
|
),
|
|
2308
2406
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
2309
2407
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
2310
2408
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
2311
2409
|
* waiting:'hard'. */
|
|
2312
|
-
channel:
|
|
2410
|
+
channel: z10.enum(["call", "message"]).optional().describe(
|
|
2313
2411
|
"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."
|
|
2314
2412
|
),
|
|
2315
|
-
confirmStyle:
|
|
2413
|
+
confirmStyle: z10.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2316
2414
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2317
2415
|
),
|
|
2318
|
-
blocking:
|
|
2416
|
+
blocking: z10.boolean().default(false).describe(
|
|
2319
2417
|
"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."
|
|
2320
2418
|
)
|
|
2321
2419
|
});
|
|
2322
2420
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
2323
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
2421
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
2324
2422
|
if (r.ask !== void 0) {
|
|
2325
2423
|
for (const f of ["context", "select", "points"]) {
|
|
2326
2424
|
if (r[f] !== void 0)
|
|
2327
|
-
ctx.addIssue({ code:
|
|
2425
|
+
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.` });
|
|
2328
2426
|
}
|
|
2329
2427
|
return;
|
|
2330
2428
|
}
|
|
2331
2429
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
2332
|
-
ctx.addIssue({ code:
|
|
2430
|
+
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" });
|
|
2333
2431
|
if (!r.context)
|
|
2334
|
-
ctx.addIssue({ code:
|
|
2432
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
2335
2433
|
if (!r.select)
|
|
2336
|
-
ctx.addIssue({ code:
|
|
2434
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
2337
2435
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
2338
2436
|
if (needsOptions && !r.options?.length)
|
|
2339
|
-
ctx.addIssue({ code:
|
|
2437
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
2340
2438
|
if (!needsOptions && r.options?.length)
|
|
2341
|
-
ctx.addIssue({ code:
|
|
2439
|
+
ctx.addIssue({ code: z10.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
2342
2440
|
});
|
|
2343
|
-
var NotifyStatusSchema =
|
|
2344
|
-
var AgentStateSchema =
|
|
2345
|
-
var TurnSchema =
|
|
2346
|
-
prompt:
|
|
2347
|
-
reply:
|
|
2441
|
+
var NotifyStatusSchema = z10.enum(["pending", "answered", "ignored"]);
|
|
2442
|
+
var AgentStateSchema = z10.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
2443
|
+
var TurnSchema = z10.object({
|
|
2444
|
+
prompt: z10.string(),
|
|
2445
|
+
reply: z10.string()
|
|
2348
2446
|
});
|
|
2349
|
-
var UserAnswerSchema =
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2447
|
+
var UserAnswerSchema = z10.discriminatedUnion("kind", [
|
|
2448
|
+
z10.object({ kind: z10.literal("option"), optionId: z10.string(), label: z10.string().optional() }),
|
|
2449
|
+
z10.object({ kind: z10.literal("text"), text: z10.string() }),
|
|
2450
|
+
z10.object({ kind: z10.literal("ignored") }),
|
|
2451
|
+
z10.object({ kind: z10.literal("multi"), optionIds: z10.array(z10.string()), labels: z10.array(z10.string()).optional() }),
|
|
2452
|
+
z10.object({ kind: z10.literal("ranked"), optionIds: z10.array(z10.string()), labels: z10.array(z10.string()).optional() }),
|
|
2453
|
+
z10.object({ kind: z10.literal("clarify"), chunks: z10.array(z10.string()).min(1) }),
|
|
2454
|
+
z10.object({ kind: z10.literal("confirm"), approved: z10.boolean() }),
|
|
2455
|
+
z10.object({ kind: z10.literal("turns"), turns: z10.array(TurnSchema).min(1) }),
|
|
2358
2456
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
2359
2457
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
2360
2458
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
2361
2459
|
* card the user can reply to — the broker never overrides the user. */
|
|
2362
|
-
|
|
2460
|
+
z10.object({ kind: z10.literal("precedent"), answer: z10.string(), derivation: z10.string(), sources: z10.array(z10.string()).min(1) })
|
|
2363
2461
|
]);
|
|
2364
|
-
var IntentSchema =
|
|
2462
|
+
var IntentSchema = z10.object({
|
|
2365
2463
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
2366
2464
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
2367
2465
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
2368
2466
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2369
|
-
kind:
|
|
2370
|
-
detail:
|
|
2467
|
+
kind: z10.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2468
|
+
detail: z10.string(),
|
|
2371
2469
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2372
2470
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2373
2471
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2374
2472
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2375
|
-
dueInSeconds:
|
|
2473
|
+
dueInSeconds: z10.number().int().positive().optional(),
|
|
2376
2474
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2377
2475
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
2378
2476
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
2379
2477
|
* about what cannot answer "is the bot looping less this week?". */
|
|
2380
|
-
fault:
|
|
2478
|
+
fault: z10.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
2381
2479
|
});
|
|
2382
|
-
var RideAlongSchema =
|
|
2480
|
+
var RideAlongSchema = z10.object({
|
|
2383
2481
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
2384
|
-
noteId:
|
|
2482
|
+
noteId: z10.string(),
|
|
2385
2483
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
2386
|
-
text:
|
|
2484
|
+
text: z10.string(),
|
|
2387
2485
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
2388
|
-
parentId:
|
|
2486
|
+
parentId: z10.string().nullable()
|
|
2389
2487
|
});
|
|
2390
|
-
var AwaitItemSchema =
|
|
2391
|
-
|
|
2392
|
-
type:
|
|
2393
|
-
parentId:
|
|
2394
|
-
notificationId:
|
|
2395
|
-
workId:
|
|
2396
|
-
decisionId:
|
|
2488
|
+
var AwaitItemSchema = z10.discriminatedUnion("type", [
|
|
2489
|
+
z10.object({
|
|
2490
|
+
type: z10.literal("reply"),
|
|
2491
|
+
parentId: z10.string(),
|
|
2492
|
+
notificationId: z10.string(),
|
|
2493
|
+
workId: z10.string().uuid().optional(),
|
|
2494
|
+
decisionId: z10.string().uuid().optional(),
|
|
2397
2495
|
answer: UserAnswerSchema,
|
|
2398
2496
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
2399
2497
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -2403,103 +2501,107 @@ var AwaitItemSchema = z9.discriminatedUnion("type", [
|
|
|
2403
2501
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
2404
2502
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
2405
2503
|
* a sentence with a hole in it is worse than no sentence. */
|
|
2406
|
-
note:
|
|
2504
|
+
note: z10.string().optional(),
|
|
2407
2505
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
2408
2506
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
2409
2507
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
2410
2508
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
2411
|
-
transcript:
|
|
2509
|
+
transcript: z10.string().optional(),
|
|
2412
2510
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
2413
2511
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
2414
|
-
covered:
|
|
2512
|
+
covered: z10.array(z10.string()).optional(),
|
|
2415
2513
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
2416
2514
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
2417
2515
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
2418
2516
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
2419
2517
|
* dropped. Absent/empty = nothing owed. */
|
|
2420
|
-
also:
|
|
2518
|
+
also: z10.array(RideAlongSchema).optional()
|
|
2421
2519
|
}),
|
|
2422
|
-
|
|
2423
|
-
type:
|
|
2424
|
-
parentId:
|
|
2425
|
-
notificationId:
|
|
2426
|
-
remindAt:
|
|
2520
|
+
z10.object({
|
|
2521
|
+
type: z10.literal("remind"),
|
|
2522
|
+
parentId: z10.string(),
|
|
2523
|
+
notificationId: z10.string(),
|
|
2524
|
+
remindAt: z10.string().datetime({ offset: true }),
|
|
2427
2525
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
2428
|
-
remindInSeconds:
|
|
2526
|
+
remindInSeconds: z10.number()
|
|
2429
2527
|
}),
|
|
2430
2528
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2431
2529
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2432
2530
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2433
2531
|
* re-orient via contact({})). */
|
|
2434
|
-
|
|
2435
|
-
type:
|
|
2436
|
-
parentId:
|
|
2437
|
-
notificationId:
|
|
2532
|
+
z10.object({
|
|
2533
|
+
type: z10.literal("superseded"),
|
|
2534
|
+
parentId: z10.string(),
|
|
2535
|
+
notificationId: z10.string()
|
|
2438
2536
|
}),
|
|
2439
2537
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2440
2538
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2441
2539
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2442
2540
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2443
2541
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2444
|
-
|
|
2445
|
-
type:
|
|
2446
|
-
notificationId:
|
|
2447
|
-
inFlight:
|
|
2448
|
-
turn:
|
|
2449
|
-
idx:
|
|
2450
|
-
prompt:
|
|
2451
|
-
reply:
|
|
2452
|
-
acts:
|
|
2542
|
+
z10.object({
|
|
2543
|
+
type: z10.literal("partial"),
|
|
2544
|
+
notificationId: z10.string(),
|
|
2545
|
+
inFlight: z10.literal(true),
|
|
2546
|
+
turn: z10.object({
|
|
2547
|
+
idx: z10.number(),
|
|
2548
|
+
prompt: z10.string(),
|
|
2549
|
+
reply: z10.string(),
|
|
2550
|
+
acts: z10.array(IntentSchema).nullable().optional()
|
|
2453
2551
|
})
|
|
2454
2552
|
}),
|
|
2455
|
-
|
|
2456
|
-
type:
|
|
2457
|
-
also:
|
|
2553
|
+
z10.object({
|
|
2554
|
+
type: z10.literal("idle"),
|
|
2555
|
+
also: z10.array(RideAlongSchema).optional(),
|
|
2458
2556
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
2459
2557
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
2460
2558
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
2461
2559
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
2462
2560
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
2463
|
-
inFlight:
|
|
2561
|
+
inFlight: z10.boolean().optional()
|
|
2464
2562
|
})
|
|
2465
2563
|
]);
|
|
2466
|
-
var VoiceKeySchema =
|
|
2467
|
-
var AgendaTurnSchema =
|
|
2564
|
+
var VoiceKeySchema = z10.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2565
|
+
var AgendaTurnSchema = z10.object({
|
|
2468
2566
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
2469
2567
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
2470
2568
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
2471
2569
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
2472
2570
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
2473
2571
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
2474
|
-
id:
|
|
2572
|
+
id: z10.string().optional(),
|
|
2475
2573
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
2476
2574
|
* the planner declares duplicates instead of asking them twice. */
|
|
2477
|
-
coveredIds:
|
|
2575
|
+
coveredIds: z10.array(z10.string()).optional(),
|
|
2478
2576
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
2479
2577
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
2480
|
-
info:
|
|
2481
|
-
question:
|
|
2578
|
+
info: z10.array(z10.string().min(1)).default([]),
|
|
2579
|
+
question: z10.string().min(1).nullable(),
|
|
2482
2580
|
/** True on the one turn carrying the agent's own declared question. */
|
|
2483
|
-
asks:
|
|
2581
|
+
asks: z10.boolean().optional(),
|
|
2484
2582
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
2485
2583
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
2486
2584
|
* which route nothing. */
|
|
2487
|
-
claimId:
|
|
2585
|
+
claimId: z10.string().optional(),
|
|
2488
2586
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
2489
|
-
voice:
|
|
2587
|
+
voice: z10.string().optional(),
|
|
2490
2588
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
2491
2589
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
2492
2590
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
2493
2591
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
2494
|
-
agent:
|
|
2592
|
+
agent: z10.string().optional(),
|
|
2495
2593
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
2496
2594
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
2497
2595
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
2498
2596
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
2499
2597
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
2500
|
-
agentId:
|
|
2598
|
+
agentId: z10.string().optional(),
|
|
2599
|
+
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
2600
|
+
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
2601
|
+
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
2602
|
+
onTable: z10.boolean().optional(),
|
|
2501
2603
|
select: SelectShapeSchema.optional(),
|
|
2502
|
-
options:
|
|
2604
|
+
options: z10.array(OptionSchema.omit({ id: true })).optional(),
|
|
2503
2605
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
2504
2606
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
2505
2607
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -2508,33 +2610,33 @@ var AgendaTurnSchema = z9.object({
|
|
|
2508
2610
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
2509
2611
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
2510
2612
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2511
|
-
blocking:
|
|
2613
|
+
blocking: z10.boolean().optional(),
|
|
2512
2614
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
2513
2615
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
2514
2616
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
2515
2617
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
2516
2618
|
* speak, it is dropped and their words are taken like any reply. */
|
|
2517
|
-
ifSilent:
|
|
2619
|
+
ifSilent: z10.boolean().optional()
|
|
2518
2620
|
});
|
|
2519
2621
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2520
|
-
var InboxItemSchema =
|
|
2521
|
-
id:
|
|
2522
|
-
tokenId:
|
|
2622
|
+
var InboxItemSchema = z10.object({
|
|
2623
|
+
id: z10.string(),
|
|
2624
|
+
tokenId: z10.string().optional(),
|
|
2523
2625
|
status: NotifyStatusSchema,
|
|
2524
2626
|
context: ContextSchema,
|
|
2525
|
-
options:
|
|
2627
|
+
options: z10.array(OptionSchema).optional(),
|
|
2526
2628
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2527
|
-
points:
|
|
2629
|
+
points: z10.array(z10.string()).optional(),
|
|
2528
2630
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
2529
2631
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2530
2632
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2531
2633
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2532
|
-
asks:
|
|
2634
|
+
asks: z10.boolean().optional(),
|
|
2533
2635
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2534
2636
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2535
2637
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2536
2638
|
* which must never CLAIM stalled. */
|
|
2537
|
-
lastSeenAt:
|
|
2639
|
+
lastSeenAt: z10.string().optional(),
|
|
2538
2640
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2539
2641
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2540
2642
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2544,7 +2646,7 @@ var InboxItemSchema = z9.object({
|
|
|
2544
2646
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2545
2647
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2546
2648
|
* `createdAt`. */
|
|
2547
|
-
agentStateAt:
|
|
2649
|
+
agentStateAt: z10.string().datetime().optional(),
|
|
2548
2650
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
2549
2651
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
2550
2652
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -2558,61 +2660,61 @@ var InboxItemSchema = z9.object({
|
|
|
2558
2660
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2559
2661
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2560
2662
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2561
|
-
questions:
|
|
2562
|
-
id:
|
|
2563
|
-
entryId:
|
|
2564
|
-
title:
|
|
2565
|
-
state:
|
|
2566
|
-
answer:
|
|
2663
|
+
questions: z10.array(z10.object({
|
|
2664
|
+
id: z10.string(),
|
|
2665
|
+
entryId: z10.string(),
|
|
2666
|
+
title: z10.string(),
|
|
2667
|
+
state: z10.enum(["open", "answered"]),
|
|
2668
|
+
answer: z10.string().nullable(),
|
|
2567
2669
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2568
2670
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2569
2671
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2570
2672
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2571
|
-
agent:
|
|
2673
|
+
agent: z10.object({ tokenId: z10.string(), name: z10.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
2572
2674
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
2573
2675
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
2574
2676
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
2575
2677
|
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
2576
|
-
options:
|
|
2678
|
+
options: z10.array(OptionSchema).optional(),
|
|
2577
2679
|
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
2578
2680
|
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
2579
2681
|
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
2580
2682
|
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
2581
2683
|
* preview layout for any `options`, and must see exactly what it did. */
|
|
2582
|
-
choices:
|
|
2684
|
+
choices: z10.array(OptionSchema).optional(),
|
|
2583
2685
|
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
2584
|
-
select:
|
|
2686
|
+
select: z10.enum(["one", "many", "rank"]).optional()
|
|
2585
2687
|
})).optional(),
|
|
2586
|
-
visuals:
|
|
2688
|
+
visuals: z10.array(VisualSchema).optional(),
|
|
2587
2689
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2588
2690
|
* agent's suggestion, or a default silly name). */
|
|
2589
|
-
name:
|
|
2691
|
+
name: z10.string(),
|
|
2590
2692
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2591
2693
|
voice: VoiceKeySchema.optional(),
|
|
2592
|
-
repo:
|
|
2593
|
-
branch:
|
|
2594
|
-
createdAt:
|
|
2595
|
-
snoozedUntil:
|
|
2694
|
+
repo: z10.string().optional(),
|
|
2695
|
+
branch: z10.string().optional(),
|
|
2696
|
+
createdAt: z10.string().datetime(),
|
|
2697
|
+
snoozedUntil: z10.string().datetime().optional(),
|
|
2596
2698
|
agentState: AgentStateSchema.default("idle"),
|
|
2597
2699
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2598
2700
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2599
2701
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2600
|
-
turn:
|
|
2702
|
+
turn: z10.enum(["you", "agent"]).default("you"),
|
|
2601
2703
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2602
2704
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2603
2705
|
* error badge + Retry. */
|
|
2604
|
-
error:
|
|
2706
|
+
error: z10.string().optional(),
|
|
2605
2707
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
2606
2708
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2607
2709
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2608
2710
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2609
|
-
cold:
|
|
2610
|
-
clarifies:
|
|
2711
|
+
cold: z10.string().datetime().optional(),
|
|
2712
|
+
clarifies: z10.string().optional(),
|
|
2611
2713
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2612
2714
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2613
2715
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2614
2716
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2615
|
-
onCall:
|
|
2717
|
+
onCall: z10.literal(true).optional(),
|
|
2616
2718
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2617
2719
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2618
2720
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2622,31 +2724,31 @@ var InboxItemSchema = z9.object({
|
|
|
2622
2724
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2623
2725
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2624
2726
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2625
|
-
ring:
|
|
2626
|
-
declinedAt:
|
|
2627
|
-
anchorAt:
|
|
2628
|
-
nextRingAt:
|
|
2629
|
-
step:
|
|
2727
|
+
ring: z10.object({
|
|
2728
|
+
declinedAt: z10.string().datetime(),
|
|
2729
|
+
anchorAt: z10.string().datetime(),
|
|
2730
|
+
nextRingAt: z10.string().datetime().nullable(),
|
|
2731
|
+
step: z10.number().int()
|
|
2630
2732
|
}).optional(),
|
|
2631
2733
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
2632
2734
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
2633
2735
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
2634
2736
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
2635
2737
|
* feedback ABOUT. */
|
|
2636
|
-
why:
|
|
2738
|
+
why: z10.object({
|
|
2637
2739
|
asked: NotifyLevelSchema,
|
|
2638
2740
|
got: NotifyLevelSchema,
|
|
2639
|
-
because:
|
|
2640
|
-
line:
|
|
2741
|
+
because: z10.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2742
|
+
line: z10.string()
|
|
2641
2743
|
}).optional(),
|
|
2642
|
-
select:
|
|
2643
|
-
confirmStyle:
|
|
2744
|
+
select: z10.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2745
|
+
confirmStyle: z10.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2644
2746
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2645
2747
|
),
|
|
2646
2748
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
2647
2749
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
2648
2750
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
2649
|
-
blocking:
|
|
2751
|
+
blocking: z10.boolean().default(false),
|
|
2650
2752
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
2651
2753
|
answer: UserAnswerSchema.optional(),
|
|
2652
2754
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -2654,17 +2756,17 @@ var InboxItemSchema = z9.object({
|
|
|
2654
2756
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
2655
2757
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
2656
2758
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
2657
|
-
communication:
|
|
2658
|
-
deliveryId:
|
|
2659
|
-
kind:
|
|
2660
|
-
entryId:
|
|
2661
|
-
goalIds:
|
|
2662
|
-
decisionNeedId:
|
|
2663
|
-
sealed:
|
|
2664
|
-
goalState:
|
|
2759
|
+
communication: z10.object({
|
|
2760
|
+
deliveryId: z10.string(),
|
|
2761
|
+
kind: z10.enum(["notification", "call"]),
|
|
2762
|
+
entryId: z10.string(),
|
|
2763
|
+
goalIds: z10.array(z10.string()),
|
|
2764
|
+
decisionNeedId: z10.string().optional(),
|
|
2765
|
+
sealed: z10.boolean(),
|
|
2766
|
+
goalState: z10.string().optional(),
|
|
2665
2767
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2666
2768
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2667
|
-
goalTitle:
|
|
2769
|
+
goalTitle: z10.string().optional()
|
|
2668
2770
|
}).optional(),
|
|
2669
2771
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
2670
2772
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -2676,27 +2778,27 @@ var InboxItemSchema = z9.object({
|
|
|
2676
2778
|
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
2677
2779
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
2678
2780
|
* with no rev is simply always re-sent. */
|
|
2679
|
-
rev:
|
|
2781
|
+
rev: z10.string().optional()
|
|
2680
2782
|
});
|
|
2681
2783
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
2682
|
-
var PushTokenSchema =
|
|
2683
|
-
voipToken:
|
|
2684
|
-
alertToken:
|
|
2685
|
-
fcmToken:
|
|
2686
|
-
platform:
|
|
2784
|
+
var PushTokenSchema = z10.object({
|
|
2785
|
+
voipToken: z10.string().min(1).optional(),
|
|
2786
|
+
alertToken: z10.string().min(1).optional(),
|
|
2787
|
+
fcmToken: z10.string().min(1).optional(),
|
|
2788
|
+
platform: z10.enum(["ios", "android"])
|
|
2687
2789
|
}).superRefine((v, ctx) => {
|
|
2688
2790
|
if (v.platform !== "ios") return;
|
|
2689
2791
|
for (const field of ["voipToken", "alertToken"]) {
|
|
2690
2792
|
const token = v[field];
|
|
2691
2793
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
2692
2794
|
ctx.addIssue({
|
|
2693
|
-
code:
|
|
2795
|
+
code: z10.ZodIssueCode.custom,
|
|
2694
2796
|
path: [field],
|
|
2695
2797
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
2696
2798
|
});
|
|
2697
2799
|
}
|
|
2698
2800
|
});
|
|
2699
|
-
var MissedCallSchema =
|
|
2801
|
+
var MissedCallSchema = z10.enum([
|
|
2700
2802
|
"retry_10m",
|
|
2701
2803
|
"retry_30m",
|
|
2702
2804
|
"retry_60m",
|
|
@@ -2706,33 +2808,33 @@ var MissedCallSchema = z9.enum([
|
|
|
2706
2808
|
"inbox",
|
|
2707
2809
|
"dismiss"
|
|
2708
2810
|
]);
|
|
2709
|
-
var
|
|
2710
|
-
var QUIET = ` Nothing rings from ${
|
|
2711
|
-
var BrokerTuningSchema =
|
|
2811
|
+
var clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
2812
|
+
var QUIET = ` Nothing rings from ${clock2(NIGHT.from)} to ${clock2(NIGHT.to)} your time; the count waits for morning.`;
|
|
2813
|
+
var BrokerTuningSchema = z10.object({
|
|
2712
2814
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
2713
|
-
ackVerbosity:
|
|
2815
|
+
ackVerbosity: z10.enum(["normal", "none"]).optional(),
|
|
2714
2816
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
2715
2817
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
2716
|
-
clarifyEagerness:
|
|
2818
|
+
clarifyEagerness: z10.enum(["low", "normal", "high"]).optional(),
|
|
2717
2819
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
2718
|
-
phrasebook:
|
|
2820
|
+
phrasebook: z10.array(z10.object({ say: z10.string().min(1).max(60), mean: z10.string().min(1).max(120) })).max(24).optional(),
|
|
2719
2821
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
2720
2822
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
2721
2823
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
2722
2824
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
2723
2825
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
2724
|
-
language:
|
|
2826
|
+
language: z10.enum(["en", "es"]).optional()
|
|
2725
2827
|
});
|
|
2726
|
-
var UserSettingsSchema =
|
|
2727
|
-
permissions:
|
|
2728
|
-
call:
|
|
2729
|
-
banner:
|
|
2730
|
-
push:
|
|
2828
|
+
var UserSettingsSchema = z10.object({
|
|
2829
|
+
permissions: z10.object({
|
|
2830
|
+
call: z10.boolean(),
|
|
2831
|
+
banner: z10.boolean(),
|
|
2832
|
+
push: z10.boolean()
|
|
2731
2833
|
}),
|
|
2732
2834
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
2733
2835
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
2734
2836
|
* what people read (owner, 2026-09-30). */
|
|
2735
|
-
sessionMode:
|
|
2837
|
+
sessionMode: z10.enum(["default", "all_calls", "silent"]),
|
|
2736
2838
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
2737
2839
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
2738
2840
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -2741,7 +2843,7 @@ var UserSettingsSchema = z9.object({
|
|
|
2741
2843
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
2742
2844
|
* next dead field for the same reason. */
|
|
2743
2845
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
2744
|
-
improveConsent:
|
|
2846
|
+
improveConsent: z10.boolean(),
|
|
2745
2847
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
2746
2848
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
2747
2849
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -2749,17 +2851,17 @@ var UserSettingsSchema = z9.object({
|
|
|
2749
2851
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
2750
2852
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
2751
2853
|
* write, 'hosted' on read (see store.ts). */
|
|
2752
|
-
voiceMode:
|
|
2854
|
+
voiceMode: z10.enum(["hosted", "on_device"]).optional(),
|
|
2753
2855
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
2754
2856
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
2755
2857
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
2756
2858
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2757
2859
|
* read (see store.ts). */
|
|
2758
|
-
talk:
|
|
2860
|
+
talk: z10.boolean().optional(),
|
|
2759
2861
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
2760
2862
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
2761
2863
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
2762
|
-
callDiagnostics:
|
|
2864
|
+
callDiagnostics: z10.boolean().optional(),
|
|
2763
2865
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2764
2866
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2765
2867
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2767,7 +2869,7 @@ var UserSettingsSchema = z9.object({
|
|
|
2767
2869
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2768
2870
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2769
2871
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2770
|
-
voiceTuning:
|
|
2872
|
+
voiceTuning: z10.record(z10.string(), z10.union([z10.number(), z10.string()])).optional(),
|
|
2771
2873
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2772
2874
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2773
2875
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2776,53 +2878,53 @@ var UserSettingsSchema = z9.object({
|
|
|
2776
2878
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2777
2879
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2778
2880
|
* call — honest about what we know. */
|
|
2779
|
-
timezone:
|
|
2881
|
+
timezone: z10.string().min(1).max(64).optional(),
|
|
2780
2882
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2781
2883
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2782
2884
|
broker: BrokerTuningSchema.optional()
|
|
2783
2885
|
});
|
|
2784
|
-
var HistoryWorkSchema =
|
|
2785
|
-
id:
|
|
2786
|
-
title:
|
|
2787
|
-
state:
|
|
2886
|
+
var HistoryWorkSchema = z10.object({
|
|
2887
|
+
id: z10.string(),
|
|
2888
|
+
title: z10.string(),
|
|
2889
|
+
state: z10.enum(["done", "cancelled"]),
|
|
2788
2890
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
2789
|
-
assignee:
|
|
2891
|
+
assignee: z10.string()
|
|
2790
2892
|
});
|
|
2791
|
-
var HistoryEntrySchema =
|
|
2792
|
-
|
|
2793
|
-
|
|
2893
|
+
var HistoryEntrySchema = z10.union([
|
|
2894
|
+
z10.object({ at: z10.string(), card: InboxItemSchema }),
|
|
2895
|
+
z10.object({ at: z10.string(), work: HistoryWorkSchema })
|
|
2794
2896
|
]);
|
|
2795
|
-
var HistoryPageSchema =
|
|
2796
|
-
entries:
|
|
2797
|
-
next:
|
|
2897
|
+
var HistoryPageSchema = z10.object({
|
|
2898
|
+
entries: z10.array(HistoryEntrySchema),
|
|
2899
|
+
next: z10.string().nullable()
|
|
2798
2900
|
});
|
|
2799
2901
|
var ACTIVITY_LINES = 2;
|
|
2800
2902
|
var ACTIVITY_LINE_MAX = 80;
|
|
2801
|
-
var AgentActivitySchema =
|
|
2903
|
+
var AgentActivitySchema = z10.object({
|
|
2802
2904
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
2803
|
-
lines:
|
|
2905
|
+
lines: z10.array(z10.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
2804
2906
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
2805
2907
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
2806
|
-
at:
|
|
2908
|
+
at: z10.string().datetime()
|
|
2807
2909
|
});
|
|
2808
|
-
var ConnectionSummarySchema =
|
|
2910
|
+
var ConnectionSummarySchema = z10.object({
|
|
2809
2911
|
/** The connection = the agent's token id (used to address a request). */
|
|
2810
|
-
id:
|
|
2912
|
+
id: z10.string(),
|
|
2811
2913
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
2812
2914
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
2813
2915
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
2814
2916
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
2815
|
-
kind:
|
|
2917
|
+
kind: z10.enum(["device", "agent"]).optional(),
|
|
2816
2918
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
2817
2919
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
2818
2920
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
2819
|
-
mintedByDevice:
|
|
2820
|
-
device:
|
|
2921
|
+
mintedByDevice: z10.string().nullable().optional(),
|
|
2922
|
+
device: z10.string().nullable(),
|
|
2821
2923
|
/** The agent's display name (the single pairing name). */
|
|
2822
|
-
name:
|
|
2924
|
+
name: z10.string(),
|
|
2823
2925
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
2824
2926
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
2825
|
-
provider:
|
|
2927
|
+
provider: z10.string().nullable(),
|
|
2826
2928
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
2827
2929
|
voice: VoiceKeySchema.nullable(),
|
|
2828
2930
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -2833,34 +2935,34 @@ var ConnectionSummarySchema = z9.object({
|
|
|
2833
2935
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
2834
2936
|
* set once must not overrule a rule they set about one agent. */
|
|
2835
2937
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
2836
|
-
createdAt:
|
|
2938
|
+
createdAt: z10.string().datetime(),
|
|
2837
2939
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
2838
2940
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
2839
|
-
lastContactAt:
|
|
2941
|
+
lastContactAt: z10.string().datetime().nullable(),
|
|
2840
2942
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
2841
2943
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
2842
|
-
lastSeenAt:
|
|
2944
|
+
lastSeenAt: z10.string().datetime().nullable().optional(),
|
|
2843
2945
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
2844
2946
|
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
2845
2947
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
2846
|
-
lastWorkedAt:
|
|
2948
|
+
lastWorkedAt: z10.string().datetime().nullable().optional(),
|
|
2847
2949
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
2848
2950
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
2849
|
-
oldestReadyAt:
|
|
2951
|
+
oldestReadyAt: z10.string().datetime().nullable().optional(),
|
|
2850
2952
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
2851
2953
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
2852
2954
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
2853
|
-
runtime:
|
|
2955
|
+
runtime: z10.object({
|
|
2854
2956
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
2855
2957
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
2856
|
-
version:
|
|
2857
|
-
harnesses:
|
|
2858
|
-
workspaces:
|
|
2958
|
+
version: z10.string().optional(),
|
|
2959
|
+
harnesses: z10.array(z10.object({ name: z10.string(), label: z10.string(), status: z10.string() })).optional(),
|
|
2960
|
+
workspaces: z10.array(z10.string()).optional(),
|
|
2859
2961
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
2860
2962
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
2861
2963
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
2862
2964
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
2863
|
-
repos:
|
|
2965
|
+
repos: z10.array(z10.object({ path: z10.string(), remote: z10.string() })).optional()
|
|
2864
2966
|
}).optional(),
|
|
2865
2967
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
2866
2968
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -2869,156 +2971,166 @@ var ConnectionSummarySchema = z9.object({
|
|
|
2869
2971
|
activity: AgentActivitySchema.optional(),
|
|
2870
2972
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
2871
2973
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
2872
|
-
managed:
|
|
2974
|
+
managed: z10.boolean()
|
|
2873
2975
|
});
|
|
2874
|
-
var LedgerItemSchema =
|
|
2875
|
-
var AgentLedgerSchema =
|
|
2976
|
+
var LedgerItemSchema = z10.object({ id: z10.string(), parentId: z10.string(), title: z10.string(), createdAt: z10.string() });
|
|
2977
|
+
var AgentLedgerSchema = z10.object({
|
|
2876
2978
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
2877
|
-
agent:
|
|
2979
|
+
agent: z10.object({ id: z10.string(), name: z10.string().nullable(), revokedAt: z10.string().nullable() }),
|
|
2878
2980
|
/** Its own questions you have not answered. */
|
|
2879
|
-
asks:
|
|
2981
|
+
asks: z10.array(LedgerItemSchema),
|
|
2880
2982
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
2881
|
-
answered:
|
|
2983
|
+
answered: z10.array(LedgerItemSchema),
|
|
2882
2984
|
/** Requests you sent it that it never took. */
|
|
2883
|
-
requests:
|
|
2884
|
-
goals:
|
|
2885
|
-
callbacks:
|
|
2985
|
+
requests: z10.array(LedgerItemSchema),
|
|
2986
|
+
goals: z10.array(z10.object({ id: z10.string(), outcome: z10.string(), state: z10.string() })),
|
|
2987
|
+
callbacks: z10.array(z10.object({ id: z10.string(), parentId: z10.string(), trigger: z10.string(), note: z10.string(), dueAt: z10.string().nullable() }))
|
|
2886
2988
|
});
|
|
2887
|
-
var ReassignResultSchema =
|
|
2888
|
-
moved:
|
|
2889
|
-
parentId:
|
|
2989
|
+
var ReassignResultSchema = z10.object({
|
|
2990
|
+
moved: z10.object({ asks: z10.number(), answered: z10.number(), requests: z10.number(), goals: z10.number(), callbacks: z10.number() }),
|
|
2991
|
+
parentId: z10.string().nullable()
|
|
2890
2992
|
});
|
|
2891
|
-
var LessonStateSchema =
|
|
2892
|
-
var LessonViewSchema =
|
|
2893
|
-
id:
|
|
2894
|
-
text:
|
|
2993
|
+
var LessonStateSchema = z10.enum(["active", "proposed", "retired"]);
|
|
2994
|
+
var LessonViewSchema = z10.object({
|
|
2995
|
+
id: z10.string(),
|
|
2996
|
+
text: z10.string(),
|
|
2895
2997
|
state: LessonStateSchema,
|
|
2896
2998
|
/** The Goal it is scoped to; null = the whole account. */
|
|
2897
|
-
scopeGoalId:
|
|
2898
|
-
goalTitle:
|
|
2899
|
-
version:
|
|
2900
|
-
pinned:
|
|
2999
|
+
scopeGoalId: z10.string().nullable(),
|
|
3000
|
+
goalTitle: z10.string().nullable(),
|
|
3001
|
+
version: z10.number(),
|
|
3002
|
+
pinned: z10.boolean(),
|
|
2901
3003
|
/** When the person last wrote its text themselves. */
|
|
2902
|
-
editedAt:
|
|
2903
|
-
createdAt:
|
|
2904
|
-
updatedAt:
|
|
3004
|
+
editedAt: z10.string().nullable(),
|
|
3005
|
+
createdAt: z10.string(),
|
|
3006
|
+
updatedAt: z10.string(),
|
|
2905
3007
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
2906
|
-
sources:
|
|
3008
|
+
sources: z10.array(z10.object({ entryId: z10.string(), words: z10.string().nullable(), at: z10.string() }))
|
|
2907
3009
|
});
|
|
2908
|
-
var QueueQuestionSchema =
|
|
3010
|
+
var QueueQuestionSchema = z10.object({
|
|
2909
3011
|
/** The decision need's id — what an answer is accepted against. */
|
|
2910
|
-
id:
|
|
3012
|
+
id: z10.string(),
|
|
2911
3013
|
/** The words that were asked, from the request Entry that asked them. */
|
|
2912
|
-
question:
|
|
3014
|
+
question: z10.string(),
|
|
2913
3015
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
2914
3016
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
2915
3017
|
* can answer. */
|
|
2916
|
-
deliveryId:
|
|
3018
|
+
deliveryId: z10.string().nullable().default(null),
|
|
2917
3019
|
/** The Entry the ruling is about. */
|
|
2918
|
-
aboutId:
|
|
3020
|
+
aboutId: z10.string().nullable().default(null),
|
|
2919
3021
|
/** Empty for a free-text question. */
|
|
2920
|
-
options:
|
|
2921
|
-
select:
|
|
2922
|
-
askedAt:
|
|
3022
|
+
options: z10.array(OptionSchema).default([]),
|
|
3023
|
+
select: z10.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
3024
|
+
askedAt: z10.string(),
|
|
2923
3025
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
2924
|
-
answeredAt:
|
|
3026
|
+
answeredAt: z10.string().nullable().default(null),
|
|
2925
3027
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
2926
3028
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
2927
3029
|
* for a settled question whose reply carried nothing readable. */
|
|
2928
|
-
answer:
|
|
3030
|
+
answer: z10.string().nullable().default(null),
|
|
2929
3031
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
2930
3032
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
2931
3033
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
2932
3034
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
2933
3035
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
2934
3036
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
2935
|
-
goalId:
|
|
3037
|
+
goalId: z10.string(),
|
|
2936
3038
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
2937
3039
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
2938
3040
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
2939
3041
|
* gate at all. */
|
|
2940
|
-
blocked:
|
|
3042
|
+
blocked: z10.boolean().default(false)
|
|
2941
3043
|
});
|
|
2942
|
-
var QueueReplySchema =
|
|
3044
|
+
var QueueReplySchema = z10.object({
|
|
2943
3045
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
2944
3046
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
2945
3047
|
* walk can name it in its zoom. */
|
|
2946
|
-
id:
|
|
3048
|
+
id: z10.string(),
|
|
2947
3049
|
/** The Goal the note is on. */
|
|
2948
|
-
goalId:
|
|
3050
|
+
goalId: z10.string(),
|
|
2949
3051
|
/** What the note said. */
|
|
2950
|
-
note:
|
|
3052
|
+
note: z10.string(),
|
|
2951
3053
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
2952
|
-
deliveryId:
|
|
2953
|
-
requestEntryId:
|
|
2954
|
-
askedAt:
|
|
3054
|
+
deliveryId: z10.string(),
|
|
3055
|
+
requestEntryId: z10.string(),
|
|
3056
|
+
askedAt: z10.string(),
|
|
2955
3057
|
/** When the person last replied — the window's start. */
|
|
2956
|
-
repliedAt:
|
|
3058
|
+
repliedAt: z10.string(),
|
|
2957
3059
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
2958
|
-
reply:
|
|
3060
|
+
reply: z10.string().nullable()
|
|
2959
3061
|
});
|
|
2960
|
-
var QueueItemSchema =
|
|
2961
|
-
id:
|
|
3062
|
+
var QueueItemSchema = z10.object({
|
|
3063
|
+
id: z10.string(),
|
|
2962
3064
|
/** One-line headline — the first sentence of the outcome. */
|
|
2963
|
-
title:
|
|
3065
|
+
title: z10.string(),
|
|
2964
3066
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
2965
|
-
intent:
|
|
3067
|
+
intent: z10.string(),
|
|
2966
3068
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
2967
|
-
state:
|
|
3069
|
+
state: z10.string(),
|
|
2968
3070
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
2969
|
-
assignee:
|
|
3071
|
+
assignee: z10.string().nullable().default(null),
|
|
2970
3072
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
2971
|
-
progress:
|
|
3073
|
+
progress: z10.string().nullable().default(null),
|
|
2972
3074
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
2973
3075
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
2974
|
-
progressLine:
|
|
2975
|
-
reviewPending:
|
|
2976
|
-
dueAt:
|
|
3076
|
+
progressLine: z10.string().nullable().optional(),
|
|
3077
|
+
reviewPending: z10.boolean().default(false),
|
|
3078
|
+
dueAt: z10.string().nullable().default(null),
|
|
2977
3079
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
2978
3080
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
2979
|
-
finishedAt:
|
|
3081
|
+
finishedAt: z10.string().nullable().optional(),
|
|
2980
3082
|
/** The Goal this one was opened under; null at the root. */
|
|
2981
|
-
parentGoalId:
|
|
3083
|
+
parentGoalId: z10.string().nullable().default(null),
|
|
2982
3084
|
/** Goals opened under this one — only those the same list holds. */
|
|
2983
|
-
childGoalIds:
|
|
3085
|
+
childGoalIds: z10.array(z10.string()).default([]),
|
|
2984
3086
|
/** Goals this one waits on (start or finish gates). */
|
|
2985
|
-
dependencyGoalIds:
|
|
3087
|
+
dependencyGoalIds: z10.array(z10.string()).default([]),
|
|
2986
3088
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
2987
|
-
blocked:
|
|
3089
|
+
blocked: z10.boolean().default(false),
|
|
2988
3090
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
2989
3091
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
2990
3092
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
2991
3093
|
* question is opened. */
|
|
2992
|
-
questions:
|
|
3094
|
+
questions: z10.array(QueueQuestionSchema).default([]),
|
|
2993
3095
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
2994
3096
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
2995
3097
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
2996
3098
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
2997
|
-
asked:
|
|
2998
|
-
answered:
|
|
3099
|
+
asked: z10.number().optional(),
|
|
3100
|
+
answered: z10.number().optional(),
|
|
2999
3101
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
3000
3102
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
3001
3103
|
* not spell an empty list. */
|
|
3002
|
-
replies:
|
|
3104
|
+
replies: z10.array(QueueReplySchema).optional(),
|
|
3003
3105
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
3004
|
-
repo:
|
|
3005
|
-
createdAt:
|
|
3006
|
-
updatedAt:
|
|
3106
|
+
repo: z10.string().nullable().optional(),
|
|
3107
|
+
createdAt: z10.string(),
|
|
3108
|
+
updatedAt: z10.string().nullable().default(null),
|
|
3007
3109
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
3008
3110
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
3009
3111
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
3010
|
-
lastProgressAt:
|
|
3112
|
+
lastProgressAt: z10.string().nullable().optional(),
|
|
3011
3113
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
3012
3114
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
3013
3115
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
3014
3116
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
3015
3117
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
3016
3118
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
3017
|
-
latest:
|
|
3018
|
-
from:
|
|
3019
|
-
said:
|
|
3020
|
-
at:
|
|
3021
|
-
entryId:
|
|
3119
|
+
latest: z10.object({
|
|
3120
|
+
from: z10.enum(["person", "agent"]),
|
|
3121
|
+
said: z10.string(),
|
|
3122
|
+
at: z10.string(),
|
|
3123
|
+
entryId: z10.string(),
|
|
3124
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
3125
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
3126
|
+
* none was. The Work row reads it before `said`. */
|
|
3127
|
+
summary: z10.string().optional(),
|
|
3128
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
3129
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
3130
|
+
titles: z10.array(z10.string()).optional(),
|
|
3131
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
3132
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
3133
|
+
line: z10.string().optional()
|
|
3022
3134
|
}).nullable().optional(),
|
|
3023
3135
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
3024
3136
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -3031,7 +3143,7 @@ var QueueItemSchema = z9.object({
|
|
|
3031
3143
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
3032
3144
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
3033
3145
|
* Entry with no `about_id` is in neither. */
|
|
3034
|
-
lastPersonAt:
|
|
3146
|
+
lastPersonAt: z10.string().nullable().optional(),
|
|
3035
3147
|
/** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
|
|
3036
3148
|
* by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
|
|
3037
3149
|
* incremental read knows a row has not moved: the phone echoes back the revs it holds
|
|
@@ -3042,40 +3154,40 @@ var QueueItemSchema = z9.object({
|
|
|
3042
3154
|
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
3043
3155
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
3044
3156
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
3045
|
-
rev:
|
|
3157
|
+
rev: z10.string().optional()
|
|
3046
3158
|
});
|
|
3047
|
-
var QueueDeltaSchema =
|
|
3048
|
-
ids:
|
|
3049
|
-
items:
|
|
3159
|
+
var QueueDeltaSchema = z10.object({
|
|
3160
|
+
ids: z10.array(z10.string()),
|
|
3161
|
+
items: z10.array(QueueItemSchema)
|
|
3050
3162
|
});
|
|
3051
3163
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
3052
|
-
var NoteSourceSchema =
|
|
3053
|
-
var NoteStatusSchema =
|
|
3054
|
-
var NoteRepeatSchema =
|
|
3055
|
-
var DecisionSchema =
|
|
3056
|
-
id:
|
|
3164
|
+
var NoteSourceSchema = z10.enum(["app", "call"]);
|
|
3165
|
+
var NoteStatusSchema = z10.enum(["open", "assigned", "in_progress", "done"]);
|
|
3166
|
+
var NoteRepeatSchema = z10.enum(["once", "until_done"]);
|
|
3167
|
+
var DecisionSchema = z10.object({
|
|
3168
|
+
id: z10.string(),
|
|
3057
3169
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
3058
3170
|
* extensibility seam — any conversation can accrue decisions). */
|
|
3059
|
-
noteId:
|
|
3171
|
+
noteId: z10.string().nullable(),
|
|
3060
3172
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
3061
|
-
question:
|
|
3173
|
+
question: z10.string(),
|
|
3062
3174
|
/** The user's ruling; null while the question is open. */
|
|
3063
|
-
answer:
|
|
3064
|
-
decidedAt:
|
|
3065
|
-
createdAt:
|
|
3175
|
+
answer: z10.string().nullable(),
|
|
3176
|
+
decidedAt: z10.string().nullable(),
|
|
3177
|
+
createdAt: z10.string()
|
|
3066
3178
|
});
|
|
3067
|
-
var NoteSchema =
|
|
3068
|
-
id:
|
|
3179
|
+
var NoteSchema = z10.object({
|
|
3180
|
+
id: z10.string(),
|
|
3069
3181
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
3070
|
-
title:
|
|
3182
|
+
title: z10.string(),
|
|
3071
3183
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
3072
|
-
intent:
|
|
3184
|
+
intent: z10.string(),
|
|
3073
3185
|
source: NoteSourceSchema,
|
|
3074
3186
|
status: NoteStatusSchema,
|
|
3075
3187
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
3076
|
-
assignee:
|
|
3188
|
+
assignee: z10.string().nullable(),
|
|
3077
3189
|
/** The request thread minted at assignment; null until assigned. */
|
|
3078
|
-
parentId:
|
|
3190
|
+
parentId: z10.string().nullable(),
|
|
3079
3191
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3080
3192
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
3081
3193
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -3084,154 +3196,154 @@ var NoteSchema = z9.object({
|
|
|
3084
3196
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3085
3197
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
3086
3198
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
3087
|
-
dueAt:
|
|
3199
|
+
dueAt: z10.string().nullable().default(null),
|
|
3088
3200
|
repeat: NoteRepeatSchema.default("once"),
|
|
3089
3201
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
3090
|
-
rides:
|
|
3091
|
-
lastRideAt:
|
|
3092
|
-
createdAt:
|
|
3202
|
+
rides: z10.number().int().default(0),
|
|
3203
|
+
lastRideAt: z10.string().nullable().default(null),
|
|
3204
|
+
createdAt: z10.string()
|
|
3093
3205
|
});
|
|
3094
|
-
var TriageItemSchema =
|
|
3095
|
-
noteId:
|
|
3206
|
+
var TriageItemSchema = z10.object({
|
|
3207
|
+
noteId: z10.string(),
|
|
3096
3208
|
/** The note's headline at run time. */
|
|
3097
|
-
title:
|
|
3209
|
+
title: z10.string(),
|
|
3098
3210
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
3099
3211
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
3100
3212
|
* Never a model's reasoning transcript, never an id. */
|
|
3101
|
-
why:
|
|
3213
|
+
why: z10.string()
|
|
3102
3214
|
});
|
|
3103
|
-
var TriageAssignmentSchema =
|
|
3215
|
+
var TriageAssignmentSchema = z10.object({
|
|
3104
3216
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
3105
|
-
agent:
|
|
3217
|
+
agent: z10.string(),
|
|
3106
3218
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
3107
3219
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
3108
|
-
agentName:
|
|
3109
|
-
notes:
|
|
3220
|
+
agentName: z10.string(),
|
|
3221
|
+
notes: z10.array(TriageItemSchema)
|
|
3110
3222
|
});
|
|
3111
|
-
var TriageStatusSchema =
|
|
3112
|
-
var SubmitTriageSchema =
|
|
3223
|
+
var TriageStatusSchema = z10.enum(["open", "superseded", "dismissed"]);
|
|
3224
|
+
var SubmitTriageSchema = z10.object({
|
|
3113
3225
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
3114
3226
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
3115
3227
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
3116
3228
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
3117
|
-
provider:
|
|
3229
|
+
provider: z10.string().min(1).max(60),
|
|
3118
3230
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
3119
|
-
model:
|
|
3231
|
+
model: z10.string().max(200).nullable().optional(),
|
|
3120
3232
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
3121
3233
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
3122
|
-
reviewed:
|
|
3123
|
-
close:
|
|
3124
|
-
stale:
|
|
3125
|
-
assign:
|
|
3234
|
+
reviewed: z10.number().int().min(0).max(1e4).default(0),
|
|
3235
|
+
close: z10.array(TriageItemSchema).max(200).default([]),
|
|
3236
|
+
stale: z10.array(TriageItemSchema).max(200).default([]),
|
|
3237
|
+
assign: z10.array(TriageAssignmentSchema).max(50).default([])
|
|
3126
3238
|
});
|
|
3127
3239
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
3128
|
-
id:
|
|
3129
|
-
runAt:
|
|
3240
|
+
id: z10.string(),
|
|
3241
|
+
runAt: z10.string(),
|
|
3130
3242
|
status: TriageStatusSchema,
|
|
3131
|
-
model:
|
|
3243
|
+
model: z10.string().nullable().default(null)
|
|
3132
3244
|
});
|
|
3133
|
-
var AcceptTriageSchema =
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
group:
|
|
3138
|
-
agent:
|
|
3139
|
-
noteIds:
|
|
3245
|
+
var AcceptTriageSchema = z10.discriminatedUnion("group", [
|
|
3246
|
+
z10.object({ group: z10.literal("close"), noteIds: z10.array(z10.string()).max(200).optional() }),
|
|
3247
|
+
z10.object({ group: z10.literal("stale"), noteIds: z10.array(z10.string()).max(200).optional() }),
|
|
3248
|
+
z10.object({
|
|
3249
|
+
group: z10.literal("assign"),
|
|
3250
|
+
agent: z10.string().min(1),
|
|
3251
|
+
noteIds: z10.array(z10.string()).max(200).optional()
|
|
3140
3252
|
})
|
|
3141
3253
|
]);
|
|
3142
|
-
var AcceptTriageResultSchema =
|
|
3143
|
-
accepted:
|
|
3144
|
-
failed:
|
|
3254
|
+
var AcceptTriageResultSchema = z10.object({
|
|
3255
|
+
accepted: z10.array(z10.string()),
|
|
3256
|
+
failed: z10.array(z10.object({ noteId: z10.string(), reason: z10.string() }))
|
|
3145
3257
|
});
|
|
3146
|
-
var DeliveryModeSchema =
|
|
3258
|
+
var DeliveryModeSchema = z10.enum(["poll", "self_hosted"]);
|
|
3147
3259
|
var WAKE_EVENT = "wake";
|
|
3148
3260
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
3149
|
-
var RegisterDeliverySchema =
|
|
3150
|
-
var OAuthStartSchema =
|
|
3151
|
-
provider:
|
|
3152
|
-
returnTo:
|
|
3261
|
+
var RegisterDeliverySchema = z10.object({ mode: DeliveryModeSchema });
|
|
3262
|
+
var OAuthStartSchema = z10.object({
|
|
3263
|
+
provider: z10.enum(["cma"]),
|
|
3264
|
+
returnTo: z10.string().min(1)
|
|
3153
3265
|
});
|
|
3154
|
-
var DeliveryConfigSchema =
|
|
3155
|
-
tokenId:
|
|
3266
|
+
var DeliveryConfigSchema = z10.object({
|
|
3267
|
+
tokenId: z10.string(),
|
|
3156
3268
|
mode: DeliveryModeSchema,
|
|
3157
3269
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
3158
3270
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
3159
3271
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
3160
|
-
realtime:
|
|
3272
|
+
realtime: z10.object({ url: z10.string(), anonKey: z10.string() }).nullable()
|
|
3161
3273
|
});
|
|
3162
|
-
var HostDecisionSchema =
|
|
3274
|
+
var HostDecisionSchema = z10.object({
|
|
3163
3275
|
/** The agent's token id: the row's `recipient`. */
|
|
3164
|
-
agent:
|
|
3165
|
-
decision:
|
|
3276
|
+
agent: z10.string().uuid(),
|
|
3277
|
+
decision: z10.enum(["stood_back", "took_over"]),
|
|
3166
3278
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
3167
|
-
goalId:
|
|
3279
|
+
goalId: z10.string().uuid().nullable().optional(),
|
|
3168
3280
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
3169
|
-
seenAt:
|
|
3281
|
+
seenAt: z10.string().datetime().nullable().optional(),
|
|
3170
3282
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
3171
|
-
since:
|
|
3283
|
+
since: z10.string().datetime().nullable().optional(),
|
|
3172
3284
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
3173
|
-
said:
|
|
3285
|
+
said: z10.string().max(300).optional()
|
|
3174
3286
|
});
|
|
3175
|
-
var WakeNudgeSchema =
|
|
3176
|
-
kind:
|
|
3177
|
-
notificationId:
|
|
3178
|
-
parentId:
|
|
3287
|
+
var WakeNudgeSchema = z10.object({
|
|
3288
|
+
kind: z10.enum(["reply", "request", "callback"]),
|
|
3289
|
+
notificationId: z10.string().optional(),
|
|
3290
|
+
parentId: z10.string()
|
|
3179
3291
|
});
|
|
3180
|
-
var PairingStatusSchema =
|
|
3181
|
-
var DeviceCodeSchema =
|
|
3182
|
-
device_code:
|
|
3183
|
-
user_code:
|
|
3184
|
-
verification_uri:
|
|
3185
|
-
verification_uri_complete:
|
|
3186
|
-
interval:
|
|
3187
|
-
expires_in:
|
|
3292
|
+
var PairingStatusSchema = z10.enum(["pending", "approved", "denied", "expired"]);
|
|
3293
|
+
var DeviceCodeSchema = z10.object({
|
|
3294
|
+
device_code: z10.string(),
|
|
3295
|
+
user_code: z10.string(),
|
|
3296
|
+
verification_uri: z10.string().url(),
|
|
3297
|
+
verification_uri_complete: z10.string().url(),
|
|
3298
|
+
interval: z10.number(),
|
|
3299
|
+
expires_in: z10.number()
|
|
3188
3300
|
});
|
|
3189
|
-
var DeviceInfoSchema =
|
|
3190
|
-
code:
|
|
3301
|
+
var DeviceInfoSchema = z10.object({
|
|
3302
|
+
code: z10.string(),
|
|
3191
3303
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3192
3304
|
* pre-filling the name field the human can edit. */
|
|
3193
|
-
name:
|
|
3305
|
+
name: z10.string(),
|
|
3194
3306
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3195
3307
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3196
3308
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3197
3309
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3198
|
-
agent:
|
|
3199
|
-
device:
|
|
3310
|
+
agent: z10.string().optional(),
|
|
3311
|
+
device: z10.string().nullable(),
|
|
3200
3312
|
status: PairingStatusSchema
|
|
3201
3313
|
});
|
|
3202
|
-
var DeviceTokenSchema =
|
|
3203
|
-
access_token:
|
|
3314
|
+
var DeviceTokenSchema = z10.object({
|
|
3315
|
+
access_token: z10.string(),
|
|
3204
3316
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3205
3317
|
* a default silly name). */
|
|
3206
|
-
name:
|
|
3207
|
-
device:
|
|
3318
|
+
name: z10.string(),
|
|
3319
|
+
device: z10.string().nullable(),
|
|
3208
3320
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3209
3321
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3210
|
-
voice:
|
|
3322
|
+
voice: z10.string().nullable().optional(),
|
|
3211
3323
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3212
3324
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3213
|
-
token_id:
|
|
3325
|
+
token_id: z10.string().nullable().optional(),
|
|
3214
3326
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3215
3327
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3216
3328
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3217
3329
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3218
|
-
workspace:
|
|
3330
|
+
workspace: z10.string().nullable().optional(),
|
|
3219
3331
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
3220
|
-
harness:
|
|
3221
|
-
session_id:
|
|
3332
|
+
harness: z10.enum(["claude", "codex", "agy"]).optional(),
|
|
3333
|
+
session_id: z10.string().uuid().optional(),
|
|
3222
3334
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
3223
3335
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
3224
3336
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
3225
3337
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
3226
|
-
full_session:
|
|
3227
|
-
uik_pub:
|
|
3338
|
+
full_session: z10.string().optional(),
|
|
3339
|
+
uik_pub: z10.string().nullable().optional()
|
|
3228
3340
|
});
|
|
3229
|
-
var SupportRequestSchema =
|
|
3230
|
-
email:
|
|
3231
|
-
message:
|
|
3232
|
-
name:
|
|
3341
|
+
var SupportRequestSchema = z10.object({
|
|
3342
|
+
email: z10.string().email().max(320),
|
|
3343
|
+
message: z10.string().trim().min(1).max(5e3),
|
|
3344
|
+
name: z10.string().trim().max(120).optional()
|
|
3233
3345
|
});
|
|
3234
|
-
var NotificationFeedbackKindSchema =
|
|
3346
|
+
var NotificationFeedbackKindSchema = z10.enum([
|
|
3235
3347
|
"break_down",
|
|
3236
3348
|
// "This should be more than one ask — break it down."
|
|
3237
3349
|
"regenerate_options",
|
|
@@ -3246,116 +3358,119 @@ var NotificationFeedbackKindSchema = z9.enum([
|
|
|
3246
3358
|
// anything else — the note carries it.
|
|
3247
3359
|
]);
|
|
3248
3360
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
3249
|
-
var QuestionRowSchema =
|
|
3361
|
+
var QuestionRowSchema = z10.object({
|
|
3250
3362
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
3251
|
-
id:
|
|
3252
|
-
deliveryId:
|
|
3253
|
-
entryId:
|
|
3363
|
+
id: z10.string(),
|
|
3364
|
+
deliveryId: z10.string(),
|
|
3365
|
+
entryId: z10.string(),
|
|
3254
3366
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
3255
|
-
needId:
|
|
3256
|
-
goalIds:
|
|
3367
|
+
needId: z10.string().nullable(),
|
|
3368
|
+
goalIds: z10.array(z10.string()),
|
|
3257
3369
|
/** The name of the work it is about, when the read could word it. */
|
|
3258
|
-
goalTitle:
|
|
3259
|
-
tokenId:
|
|
3260
|
-
name:
|
|
3261
|
-
title:
|
|
3262
|
-
body:
|
|
3263
|
-
select:
|
|
3264
|
-
options:
|
|
3265
|
-
hasPreview:
|
|
3266
|
-
blocking:
|
|
3267
|
-
askedAt:
|
|
3370
|
+
goalTitle: z10.string().optional(),
|
|
3371
|
+
tokenId: z10.string().optional(),
|
|
3372
|
+
name: z10.string(),
|
|
3373
|
+
title: z10.string(),
|
|
3374
|
+
body: z10.string(),
|
|
3375
|
+
select: z10.enum(["one", "many", "rank", "confirm", "text"]),
|
|
3376
|
+
options: z10.array(SlimOptionSchema),
|
|
3377
|
+
hasPreview: z10.boolean(),
|
|
3378
|
+
blocking: z10.boolean(),
|
|
3379
|
+
askedAt: z10.string().datetime(),
|
|
3268
3380
|
ring: InboxItemSchema.shape.ring,
|
|
3269
|
-
onCall:
|
|
3270
|
-
sealed:
|
|
3381
|
+
onCall: z10.literal(true).optional(),
|
|
3382
|
+
sealed: z10.boolean()
|
|
3271
3383
|
});
|
|
3272
|
-
var WorkStateSchema =
|
|
3273
|
-
var WorkRowSchema =
|
|
3274
|
-
id:
|
|
3275
|
-
parentId:
|
|
3276
|
-
title:
|
|
3384
|
+
var WorkStateSchema = z10.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
3385
|
+
var WorkRowSchema = z10.object({
|
|
3386
|
+
id: z10.string(),
|
|
3387
|
+
parentId: z10.string().nullable(),
|
|
3388
|
+
title: z10.string(),
|
|
3277
3389
|
/** Straight off the Goal. */
|
|
3278
3390
|
state: WorkStateSchema,
|
|
3279
|
-
owner:
|
|
3280
|
-
revision:
|
|
3391
|
+
owner: z10.string().nullable(),
|
|
3392
|
+
revision: z10.number().int(),
|
|
3281
3393
|
/** Open questions on it, counted to 100. */
|
|
3282
|
-
waiting:
|
|
3394
|
+
waiting: z10.number().int(),
|
|
3283
3395
|
/** Held by a gate on work that is not done. */
|
|
3284
|
-
blocked:
|
|
3285
|
-
lastProgressAt:
|
|
3396
|
+
blocked: z10.boolean(),
|
|
3397
|
+
lastProgressAt: z10.string().datetime().nullable(),
|
|
3286
3398
|
/** The line written for its newest progress note, else that note's first words. */
|
|
3287
|
-
line:
|
|
3399
|
+
line: z10.string().nullable(),
|
|
3288
3400
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
3289
|
-
children:
|
|
3290
|
-
createdAt:
|
|
3291
|
-
updatedAt:
|
|
3401
|
+
children: z10.number().int(),
|
|
3402
|
+
createdAt: z10.string().datetime(),
|
|
3403
|
+
updatedAt: z10.string().datetime(),
|
|
3292
3404
|
/** When anything at or under it last moved — the order the list is in. */
|
|
3293
|
-
activeAt:
|
|
3405
|
+
activeAt: z10.string().datetime(),
|
|
3294
3406
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
3295
|
-
sealed:
|
|
3407
|
+
sealed: z10.boolean()
|
|
3296
3408
|
});
|
|
3297
3409
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
3298
3410
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
3299
3411
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
3300
|
-
asking:
|
|
3301
|
-
oldestAskAt:
|
|
3412
|
+
asking: z10.number().int().nullable(),
|
|
3413
|
+
oldestAskAt: z10.string().datetime().nullable(),
|
|
3302
3414
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
3303
3415
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
3304
3416
|
* (`agent_holds`); null when that read failed. */
|
|
3305
|
-
holds:
|
|
3306
|
-
held:
|
|
3417
|
+
holds: z10.array(z10.object({ id: z10.string(), title: z10.string() })).nullable(),
|
|
3418
|
+
held: z10.number().int().nullable(),
|
|
3307
3419
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
3308
3420
|
* while none has, or when that read failed. */
|
|
3309
|
-
cold:
|
|
3421
|
+
cold: z10.string().datetime().nullable(),
|
|
3310
3422
|
/** The newest line of its working log, and when the harness saw it. */
|
|
3311
|
-
line:
|
|
3312
|
-
lineAt:
|
|
3423
|
+
line: z10.string().nullable(),
|
|
3424
|
+
lineAt: z10.string().datetime().nullable()
|
|
3313
3425
|
});
|
|
3314
|
-
var SnapshotSchema =
|
|
3426
|
+
var SnapshotSchema = z10.object({
|
|
3315
3427
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
3316
|
-
at:
|
|
3317
|
-
questions:
|
|
3428
|
+
at: z10.string().datetime(),
|
|
3429
|
+
questions: z10.object({
|
|
3318
3430
|
/** The newest 30 open cards, questions before updates. */
|
|
3319
|
-
items:
|
|
3431
|
+
items: z10.array(QuestionRowSchema),
|
|
3320
3432
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
3321
|
-
total:
|
|
3322
|
-
updates:
|
|
3323
|
-
putOff:
|
|
3433
|
+
total: z10.number().int(),
|
|
3434
|
+
updates: z10.number().int(),
|
|
3435
|
+
putOff: z10.number().int()
|
|
3324
3436
|
}).nullable(),
|
|
3325
|
-
agents:
|
|
3437
|
+
agents: z10.object({
|
|
3326
3438
|
/** Up to 60, most recently seen first. */
|
|
3327
|
-
items:
|
|
3328
|
-
more:
|
|
3439
|
+
items: z10.array(AgentRowSchema),
|
|
3440
|
+
more: z10.boolean()
|
|
3329
3441
|
}).nullable(),
|
|
3330
|
-
work:
|
|
3442
|
+
work: z10.object({
|
|
3331
3443
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
3332
|
-
items:
|
|
3444
|
+
items: z10.array(WorkRowSchema),
|
|
3333
3445
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
3334
3446
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
3335
3447
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
3336
3448
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
3337
|
-
counts:
|
|
3449
|
+
counts: z10.object({ live: z10.number().int(), waiting: z10.number().int(), notStarted: z10.number().int(), done: z10.number().int() })
|
|
3338
3450
|
}).nullable(),
|
|
3339
|
-
you:
|
|
3451
|
+
you: z10.object({
|
|
3340
3452
|
settings: UserSettingsSchema,
|
|
3341
|
-
callable:
|
|
3453
|
+
callable: z10.boolean(),
|
|
3342
3454
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
3343
|
-
computers:
|
|
3455
|
+
computers: z10.array(ComputerRowSchema).nullable()
|
|
3344
3456
|
}).nullable()
|
|
3345
3457
|
});
|
|
3346
|
-
var CallRecapSchema =
|
|
3347
|
-
call:
|
|
3348
|
-
status:
|
|
3349
|
-
startedAt:
|
|
3350
|
-
durationMs:
|
|
3351
|
-
agents:
|
|
3458
|
+
var CallRecapSchema = z10.object({
|
|
3459
|
+
call: z10.object({
|
|
3460
|
+
status: z10.string(),
|
|
3461
|
+
startedAt: z10.string(),
|
|
3462
|
+
durationMs: z10.number().nullable(),
|
|
3463
|
+
agents: z10.array(z10.object({ id: z10.string(), name: z10.string().nullable() }))
|
|
3352
3464
|
}),
|
|
3353
|
-
topics:
|
|
3354
|
-
goalId:
|
|
3355
|
-
title:
|
|
3356
|
-
owner:
|
|
3357
|
-
state:
|
|
3358
|
-
|
|
3465
|
+
topics: z10.array(z10.object({
|
|
3466
|
+
goalId: z10.string().uuid(),
|
|
3467
|
+
title: z10.string(),
|
|
3468
|
+
owner: z10.string(),
|
|
3469
|
+
state: z10.string(),
|
|
3470
|
+
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
3471
|
+
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
3472
|
+
presented: z10.boolean(),
|
|
3473
|
+
questions: z10.array(z10.object({ id: z10.string().uuid(), state: z10.string(), title: z10.string() })),
|
|
3359
3474
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
3360
3475
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
3361
3476
|
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
@@ -3365,18 +3480,33 @@ var CallRecapSchema = z9.object({
|
|
|
3365
3480
|
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
3366
3481
|
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
3367
3482
|
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
3368
|
-
lines:
|
|
3369
|
-
entryId:
|
|
3370
|
-
words:
|
|
3371
|
-
summary:
|
|
3372
|
-
about:
|
|
3373
|
-
reply:
|
|
3374
|
-
replyKind:
|
|
3375
|
-
at:
|
|
3483
|
+
lines: z10.array(z10.object({
|
|
3484
|
+
entryId: z10.string().uuid(),
|
|
3485
|
+
words: z10.string(),
|
|
3486
|
+
summary: z10.string().optional(),
|
|
3487
|
+
about: z10.string().nullable().optional(),
|
|
3488
|
+
reply: z10.string().nullable().optional(),
|
|
3489
|
+
replyKind: z10.string().nullable().optional(),
|
|
3490
|
+
at: z10.array(z10.number()).optional()
|
|
3376
3491
|
}))
|
|
3377
3492
|
})),
|
|
3378
|
-
unfiled:
|
|
3379
|
-
|
|
3493
|
+
unfiled: z10.array(z10.object({ lineId: z10.string().uuid(), words: z10.string(), atMs: z10.number() })),
|
|
3494
|
+
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
3495
|
+
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
3496
|
+
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
3497
|
+
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
3498
|
+
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
3499
|
+
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
3500
|
+
said: z10.array(z10.object({
|
|
3501
|
+
lineId: z10.string().uuid(),
|
|
3502
|
+
speaker: z10.enum(["user", "agent"]),
|
|
3503
|
+
agent: z10.object({ id: z10.string().nullable(), name: z10.string().nullable() }).nullable(),
|
|
3504
|
+
words: z10.string(),
|
|
3505
|
+
atMs: z10.number()
|
|
3506
|
+
})),
|
|
3507
|
+
more: z10.object({ lines: z10.number(), entries: z10.number(), topics: z10.number() }),
|
|
3508
|
+
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
3509
|
+
next: z10.number().int().nonnegative().nullable().optional()
|
|
3380
3510
|
});
|
|
3381
3511
|
function sessionSlot(sessionId2) {
|
|
3382
3512
|
const id2 = sessionId2 ?? sessionId();
|
|
@@ -3581,14 +3711,18 @@ async function hatch(name, voice = null, opts = {}) {
|
|
|
3581
3711
|
}
|
|
3582
3712
|
async function whoAmI(opts = {}) {
|
|
3583
3713
|
try {
|
|
3584
|
-
|
|
3585
|
-
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
3586
|
-
});
|
|
3587
|
-
return res.ok ? await res.json() : null;
|
|
3714
|
+
return await readIdentity(opts);
|
|
3588
3715
|
} catch {
|
|
3589
3716
|
return null;
|
|
3590
3717
|
}
|
|
3591
3718
|
}
|
|
3719
|
+
async function readIdentity(opts = {}) {
|
|
3720
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/identity`, {
|
|
3721
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
3722
|
+
}));
|
|
3723
|
+
if (!res.ok) await fail("read_identity", res);
|
|
3724
|
+
return await res.json();
|
|
3725
|
+
}
|
|
3592
3726
|
var NameTakenError = class extends Error {
|
|
3593
3727
|
code = "agent_name_taken";
|
|
3594
3728
|
};
|
|
@@ -4001,6 +4135,18 @@ var itemsView = (results) => (results ?? []).map((r) => compact({
|
|
|
4001
4135
|
error: r.error,
|
|
4002
4136
|
detail: r.detail
|
|
4003
4137
|
}));
|
|
4138
|
+
function deliveryReadView(d) {
|
|
4139
|
+
return compact({
|
|
4140
|
+
deliveryId: d.deliveryId,
|
|
4141
|
+
kind: d.kind,
|
|
4142
|
+
state: d.state,
|
|
4143
|
+
goalIds: d.goalIds,
|
|
4144
|
+
conversation: conversation(d),
|
|
4145
|
+
callState: d.callState,
|
|
4146
|
+
callOutcome: d.callOutcome ?? (d.kind === "call" ? d.message : void 0),
|
|
4147
|
+
next: d.callState && d.callState !== "ended" ? "Keep receiving with contact({wait:true}) while this Call is open, even after an empty window. Do not resend the ask." : "Answers and follow-ups are on their Goals; receive on your next wake. This read acknowledges nothing."
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4004
4150
|
function deliveryView(d) {
|
|
4005
4151
|
const answers = "Answers land on the Goal: collect them with contact({wait:true}) or get_goal. Do not resend it.";
|
|
4006
4152
|
const pending = d.state === "open" && d.decisionNeeds.some((n) => n.state === "open");
|
|
@@ -4023,6 +4169,7 @@ function deliveryView(d) {
|
|
|
4023
4169
|
// live (the person is on it now) or ended. A contact that joined a call already happening reads
|
|
4024
4170
|
// that call, so it says `live`.
|
|
4025
4171
|
call: d.callState,
|
|
4172
|
+
callOutcome: d.callOutcome,
|
|
4026
4173
|
waitOutcome: d.waitOutcome,
|
|
4027
4174
|
acknowledged: d.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
4028
4175
|
next: [
|
|
@@ -4045,20 +4192,21 @@ function quietDays(items, now) {
|
|
|
4045
4192
|
const oldest = Math.min(...items.map((i) => Date.parse(i.since ?? "")).filter(Number.isFinite));
|
|
4046
4193
|
return Number.isFinite(oldest) ? Math.max(3, Math.round((now - oldest) / 864e5)) : 3;
|
|
4047
4194
|
}
|
|
4048
|
-
var quietFor = (items, now) => {
|
|
4195
|
+
var quietFor = (items, now, count = items.length) => {
|
|
4049
4196
|
const days = quietDays(items, now);
|
|
4050
|
-
return
|
|
4197
|
+
return count === 1 ? `for ${days} days` : `, the longest for ${days} days`;
|
|
4051
4198
|
};
|
|
4052
4199
|
function receivedView(r, now = Date.now()) {
|
|
4053
4200
|
const w = r.waiting;
|
|
4054
4201
|
const assigned = w.assigned ?? [];
|
|
4055
4202
|
const stalled = w.stalled ?? [];
|
|
4056
|
-
const others = w.
|
|
4203
|
+
const others = w.others;
|
|
4057
4204
|
const rings = w.rings ?? [];
|
|
4058
4205
|
const owed = r.events.filter((e) => e.kind === "question");
|
|
4059
4206
|
const refused = (r.acknowledged ?? []).filter((a) => a.refused);
|
|
4060
4207
|
return compact({
|
|
4061
4208
|
events: r.events.map((e) => compact({ ...e })),
|
|
4209
|
+
calls: w.calls,
|
|
4062
4210
|
hasMore: r.hasMore || void 0,
|
|
4063
4211
|
waitOutcome: r.waitOutcome,
|
|
4064
4212
|
acknowledged: r.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
@@ -4068,9 +4216,10 @@ function receivedView(r, now = Date.now()) {
|
|
|
4068
4216
|
// THE WORK WAITING ON YOU NEXT (owner, 2026-09-27: "Wake on any work"): the predicate that starts a
|
|
4069
4217
|
// sleeping agent, so "nothing is waiting" is never said while work is.
|
|
4070
4218
|
claimable: w.claimable,
|
|
4071
|
-
// YOUR STALLED WORK (#2257), and OTHER AGENTS' (owner, 2026-
|
|
4219
|
+
// YOUR STALLED WORK (#2257), and HOW MUCH OF OTHER AGENTS' HAS GONE QUIET (owner, 2026-10-08:
|
|
4220
|
+
// "Keep a count and instructions on how to find them"), counted rather than listed.
|
|
4072
4221
|
stalled,
|
|
4073
|
-
|
|
4222
|
+
others,
|
|
4074
4223
|
// WHEN THE PHONE RINGS AGAIN FOR YOUR ASKS (owner, 2026-10-08): your Call went unheard and closed.
|
|
4075
4224
|
rings: rings.length ? rings : void 0,
|
|
4076
4225
|
// The person's standing rules for how their agents work (owner, 2026-10-08). On the read an agent makes
|
|
@@ -4078,24 +4227,38 @@ function receivedView(r, now = Date.now()) {
|
|
|
4078
4227
|
// held wait, which would repeat them window after window.
|
|
4079
4228
|
houseRules: r.waitOutcome === "not_waited" ? w.houseRules : void 0,
|
|
4080
4229
|
next: [
|
|
4081
|
-
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length || rings.length ? "No messages." : "Nothing is waiting.",
|
|
4230
|
+
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length || rings.length || w.calls?.some((c) => c.callState !== "ended") ? "No messages." : "Nothing is waiting.",
|
|
4082
4231
|
...owed.length ? [`You owe ${owed.length === 1 ? "an answer" : `${owed.length} answers`}: contact({ answers: [{ questionId: "<questionId>", answer: { text: <your answer> } }] }) for each question; a question is answered, never acknowledged.`] : [],
|
|
4083
4232
|
...r.hasMore ? ["More are waiting: acknowledge these, then receive again."] : [],
|
|
4084
4233
|
...r.waitOutcome === "expired" ? ["Nothing arrived in the window; receive again to keep waiting, without sending again."] : [],
|
|
4085
|
-
...
|
|
4234
|
+
...(w.calls ?? []).filter((c) => c.callState !== "ended").map((c) => `Read the whole ask with get_delivery({deliveryId: "${c.deliveryId}"}); follow its current calls[].callOutcome while receiving.`),
|
|
4235
|
+
...rings.map((r2) => rerings(r2, now)),
|
|
4086
4236
|
...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): read one with get_goal({goalId}); your first write to it starts it.`] : [],
|
|
4087
4237
|
...w.claimable && !assigned.length ? [`Next waiting on you: ${w.claimable.title ?? w.claimable.goalId} (get_goal({ goalId: "${w.claimable.goalId}" })).`] : [],
|
|
4088
4238
|
...stalled.length ? [`${stalled.length} of your Goals have had no progress${stalled.length === 1 ? " " : ""}${quietFor(stalled, now)}: report on each with a contact update, or finish or cancel it with manage_goals.`] : [],
|
|
4089
|
-
|
|
4239
|
+
// The COUNT, oldest three, and where to read them (owner, 2026-10-08): it was 34 rows of
|
|
4240
|
+
// other agents' work on every window, ahead of this agent's own next step.
|
|
4241
|
+
...others ? [`${others.count} of other agents' Goals have gone quiet${others.count === 1 ? " " : ""}${quietFor([{ since: others.oldestSince }], now, others.count)}: The oldest three are in others.goals: get_goal({goalId}) reads one in full. A write to it puts you on it without changing its owner.`] : [],
|
|
4090
4242
|
// No name of its own (#2525): the API's ask, verbatim.
|
|
4091
4243
|
...w.unnamed ? [w.unnamed] : []
|
|
4092
4244
|
].join(" ")
|
|
4093
4245
|
});
|
|
4094
4246
|
}
|
|
4095
|
-
function rerings(
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4247
|
+
function rerings(r, now) {
|
|
4248
|
+
const held = [r.held, r.said].filter(Boolean).join(" ");
|
|
4249
|
+
if (!r.ringAt) {
|
|
4250
|
+
return [
|
|
4251
|
+
held || "A Call of yours went unanswered.",
|
|
4252
|
+
"The ladder has no ring armed for its card: collect the answers on your next wake, and nothing needs resending."
|
|
4253
|
+
].join(" ");
|
|
4254
|
+
}
|
|
4255
|
+
const minutes = Math.max(0, Math.round((Date.parse(r.ringAt) - now) / 6e4));
|
|
4256
|
+
const when = `the phone rings again by itself at ${r.ringAt.slice(11, 16)} UTC (in ${minutes} min), carrying the same asks.`;
|
|
4257
|
+
return [
|
|
4258
|
+
held || "A Call of yours went unanswered;",
|
|
4259
|
+
held ? `Then ${when}` : when,
|
|
4260
|
+
"Nothing needs resending: schedule a wake-up for then and receive again."
|
|
4261
|
+
].join(" ");
|
|
4099
4262
|
}
|
|
4100
4263
|
function managedView(m, changes) {
|
|
4101
4264
|
const results = m.results ?? [];
|
|
@@ -4122,6 +4285,9 @@ async function runTool(name, args, opts) {
|
|
|
4122
4285
|
const { waits, signal, ...client } = opts;
|
|
4123
4286
|
const input = args ?? {};
|
|
4124
4287
|
switch (name) {
|
|
4288
|
+
case "configure":
|
|
4289
|
+
ConfigureIdentitySchema.parse(input);
|
|
4290
|
+
return readIdentity(client);
|
|
4125
4291
|
case "contact": {
|
|
4126
4292
|
const parsed = ContactSchema.parse(input);
|
|
4127
4293
|
if (!sendsAnything(parsed)) return receivedView(await receiveContact(parsed, { ...client, signal, waits }));
|
|
@@ -4144,6 +4310,8 @@ async function runTool(name, args, opts) {
|
|
|
4144
4310
|
for (const g of managed.goals ?? []) remember(client.token, g.goalId, g.revision);
|
|
4145
4311
|
return managedView(managed, changes);
|
|
4146
4312
|
}
|
|
4313
|
+
case "get_delivery":
|
|
4314
|
+
return deliveryReadView(await readDelivery(GetDeliverySchema.parse(input).deliveryId, { ...client, signal }));
|
|
4147
4315
|
case "get_goal": {
|
|
4148
4316
|
const { goalId, history, diagnose } = GetGoalSchema.parse(input);
|
|
4149
4317
|
const goal = read2(client.token, await getGoal(goalId, client, { history, diagnose }));
|
|
@@ -4387,6 +4555,7 @@ export {
|
|
|
4387
4555
|
CODEX_ENV,
|
|
4388
4556
|
BACKEND_URL,
|
|
4389
4557
|
NETWORK_MSG,
|
|
4558
|
+
parseSessionRecord,
|
|
4390
4559
|
sessionId,
|
|
4391
4560
|
isNetworkError,
|
|
4392
4561
|
reach,
|