@paigy/mcp 0.40.39 → 0.40.40
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/README.md +11 -12
- package/dist/{chunk-PMZBYLLJ.js → chunk-6FG3PZR2.js} +30 -7
- package/dist/{chunk-QE33A5XK.js → chunk-H2EBILBE.js} +750 -535
- package/dist/{chunk-4IAK4ATB.js → chunk-PKJJSHLW.js} +41 -44
- package/dist/{chunk-X4UIGUEX.js → chunk-V4OVLFMP.js} +644 -527
- package/dist/{dist-DOFR2565.js → dist-EJWDUF5J.js} +3 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +45 -21
- package/dist/listen.js +32 -6
- package/dist/onboard.js +18 -6
- package/dist/slot.js +1 -1
- package/dist/stalled.js +10 -5
- 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 z12 } from "zod";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { z as z3 } from "zod";
|
|
13
13
|
import { z as z2 } from "zod";
|
|
@@ -21,6 +21,7 @@ import { z as z7 } from "zod";
|
|
|
21
21
|
import { z as z8 } from "zod";
|
|
22
22
|
import { z as z9 } from "zod";
|
|
23
23
|
import { z as z10 } from "zod";
|
|
24
|
+
import { z as z11 } from "zod";
|
|
24
25
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
25
26
|
import { homedir as homedir2 } from "os";
|
|
26
27
|
import { join as join2 } from "path";
|
|
@@ -1569,7 +1570,7 @@ function parseDelay(said2, tz, nowMs, read22 = "earliest") {
|
|
|
1569
1570
|
return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
|
|
1570
1571
|
}
|
|
1571
1572
|
var NIGHT = { from: 23, to: 7 };
|
|
1572
|
-
var ASK_MAX =
|
|
1573
|
+
var ASK_MAX = 80;
|
|
1573
1574
|
var UNITS_MAX = 12;
|
|
1574
1575
|
var UNIT_TITLE_MAX = 50;
|
|
1575
1576
|
var UNIT_BODY_MAX = 200;
|
|
@@ -1634,15 +1635,15 @@ var CompletionSchema = z3.object({
|
|
|
1634
1635
|
}).strict();
|
|
1635
1636
|
var CompleteChangeSchema = CompletionSchema.extend({ kind: z3.literal("complete") });
|
|
1636
1637
|
var ASKING_RULES = `HOW TO ASK:
|
|
1637
|
-
1.
|
|
1638
|
-
2.
|
|
1639
|
-
3. \`workItBlocks\` only
|
|
1640
|
-
4.
|
|
1641
|
-
5. One question, one row
|
|
1642
|
-
6. Read the Goal
|
|
1643
|
-
7.
|
|
1644
|
-
8. A diagnosis says when, why and how it happens, then proposes one fix.
|
|
1645
|
-
9.
|
|
1638
|
+
1. One question per object in \`questions\`, so each is answered on its own.
|
|
1639
|
+
2. \`goalId\` names the existing Goal it is about (create one with manage_goals first); the answer comes back there.
|
|
1640
|
+
3. \`workItBlocks\` only when you are truly blocked: it asks for a call.
|
|
1641
|
+
4. No while-loops. While any Call is open, even one your question joined from their items, keep receiving through empty windows; otherwise stop at one: the answer reaches you on the Goal. Yield only with a listener or wakeup.
|
|
1642
|
+
5. One question, one row: never re-send one still waiting; take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
1643
|
+
6. Read the Goal before asking; never re-ask what was answered or shipped. \`replied\` means they chose no option: it is still yours to settle.
|
|
1644
|
+
7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other". \`pickMode\`: one (default), many, rank.
|
|
1645
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix.
|
|
1646
|
+
9. Give a report its \`reply\`: one line in THEIR voice for THAT report, so you learn whether they verified it or only received it. Required when it reaches them or completes a Goal.`;
|
|
1646
1647
|
function draft2020(node) {
|
|
1647
1648
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
1648
1649
|
if (node && typeof node === "object") {
|
|
@@ -1685,7 +1686,7 @@ var userExplicitlyRequested = z4.enum(["any", "call"]).optional().describe(
|
|
|
1685
1686
|
`Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
|
|
1686
1687
|
);
|
|
1687
1688
|
var units = z4.array(UnitInputSchema).optional().describe(
|
|
1688
|
-
`The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit. Leave it out when the main text says it all.`
|
|
1689
|
+
`The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit. Leave it out when the main text says it all. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.`
|
|
1689
1690
|
);
|
|
1690
1691
|
var UpdateInputSchema = z4.object({
|
|
1691
1692
|
message: z4.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
@@ -1740,7 +1741,7 @@ var questionFields = {
|
|
|
1740
1741
|
),
|
|
1741
1742
|
goalId: z4.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
1742
1743
|
units,
|
|
1743
|
-
options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200."),
|
|
1744
|
+
options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200. They can always answer in their own words, so no 'Other' or 'None of these'."),
|
|
1744
1745
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
1745
1746
|
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
1746
1747
|
pickMode: z4.enum(["one", "many", "rank"]).optional().describe(
|
|
@@ -1782,7 +1783,9 @@ var QuestionDependencySchema = z4.object({
|
|
|
1782
1783
|
reason: reason.optional()
|
|
1783
1784
|
}).strict().refine(actionFits, ACTION_FITS);
|
|
1784
1785
|
var sendFields = {
|
|
1785
|
-
completions: z4.array(CompletionSchema).min(1).max(20).optional().describe(
|
|
1786
|
+
completions: z4.array(CompletionSchema).min(1).max(20).optional().describe(
|
|
1787
|
+
"COMPLETIONS close and report in one operation, the same contract as manage_goals kind:complete: closure and the report commit together or neither does. Required children and dependencies must already be finished. options suggest next steps: a tap acknowledges the report and requests that step, and never reopens the Goal (follow-up work needs a new one). No reviewer rejudges it: say only what your evidence supports. Ordinary updates never close a Goal."
|
|
1788
|
+
),
|
|
1786
1789
|
updates: z4.array(UpdateInputSchema).min(1).max(20).optional().describe(
|
|
1787
1790
|
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
|
|
1788
1791
|
),
|
|
@@ -1796,7 +1799,7 @@ var sendFields = {
|
|
|
1796
1799
|
};
|
|
1797
1800
|
var ContactSchema = z4.object({
|
|
1798
1801
|
...sendFields,
|
|
1799
|
-
questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
|
|
1802
|
+
questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object, each sent exactly as written, with no reading in between."),
|
|
1800
1803
|
/** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
|
|
1801
1804
|
ackEventIds: z4.array(z4.string().uuid()).min(1).max(100).optional().describe(
|
|
1802
1805
|
"The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (answers), never acknowledged away."
|
|
@@ -1812,15 +1815,13 @@ var ContactWireSchema = z4.object({
|
|
|
1812
1815
|
questions: z4.array(QuestionWireSchema).min(1).max(20).optional()
|
|
1813
1816
|
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
1814
1817
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1815
|
-
var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals
|
|
1818
|
+
var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals, and what a question holds up. Every Goal named must exist: an unknown one refuses the whole contact. Each item applies on its own: read `results`, where a failed one names why.\n\n" + ASKING_RULES + `
|
|
1816
1819
|
|
|
1817
|
-
|
|
1820
|
+
LENGTH: the question, message or answer is the one point, at most ${ASK_MAX} characters; the rest goes in \`units\`, one idea each. Over a cap is refused by name and nothing is sent. Several results are several updates.
|
|
1818
1821
|
|
|
1819
|
-
UPDATES ask nothing
|
|
1822
|
+
UPDATES ask nothing. One reaches the person only when they asked for it, it answers something they said, or its Goal is done; else it is kept as progress. One that reaches them joins an open report card or a live call.
|
|
1820
1823
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
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_goals); \`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\`).`;
|
|
1824
|
+
RECEIVING: contact({}) waits ~45 s; contact({wait:false}) returns \`events\` at once. Confirm the ones you handled with contact({ackEventIds}).`;
|
|
1824
1825
|
var CreateGoalSchema = z5.object({
|
|
1825
1826
|
outcome: z5.string().trim().min(1).max(1e4),
|
|
1826
1827
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -2038,11 +2039,22 @@ var BrainMessageSchema = z6.union([
|
|
|
2038
2039
|
// A MESSAGE TO THE PERSON KEEPS ITS SENTENCES (turns.md): the talker writes them one by one, and each is
|
|
2039
2040
|
// its own unit of speech (`<cycle>:<key>:<n>`). `text` is the same words joined, for every reader of a
|
|
2040
2041
|
// message's words. Absent on results written before sentences.
|
|
2041
|
-
z6.object({
|
|
2042
|
+
z6.object({
|
|
2043
|
+
...messageBase,
|
|
2044
|
+
to: z6.object({ kind: z6.literal("user") }).strict(),
|
|
2045
|
+
sentences: z6.array(z6.string().min(1)).min(1).optional(),
|
|
2046
|
+
// THE REPORTS IT TELLS, BY THEIR OWN IDENTITY (call 9d1c096c, 2026-10-15): the talker's message names the report
|
|
2047
|
+
// Entries it speaks of, so the plan's line names that report rather than re-finding it by its Goal. Absent on
|
|
2048
|
+
// results written before it.
|
|
2049
|
+
tells: z6.array(z6.object({ id }).strict()).optional()
|
|
2050
|
+
}).strict(),
|
|
2042
2051
|
z6.object({
|
|
2043
2052
|
...messageBase,
|
|
2044
2053
|
to: z6.object({ kind: z6.literal("agent"), agentId: z6.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
|
|
2045
2054
|
call: z6.object({ callId: id, listen: z6.literal(true) }).strict().optional(),
|
|
2055
|
+
// WHAT IT ASKS THE AGENT, IN UNDER TEN WORDS (owner, 2026-10-15: "Yes, add the summary"): the talker's own, stored
|
|
2056
|
+
// as the sent Entry's summary, which the recap's "Sent to <agent>: …" row shows. Absent on results written before it.
|
|
2057
|
+
summary: z6.string().optional(),
|
|
2046
2058
|
// THE PERSON ASKED TO BE CALLED BACK ONCE THIS AGENT'S WORK IS DONE (turns.md, step 0): the agent owes
|
|
2047
2059
|
// the call, and its next contact about it rings them (`public.callbacks`).
|
|
2048
2060
|
callback: z6.literal(true).optional()
|
|
@@ -2069,95 +2081,97 @@ var BrainResultSchema = z6.object({
|
|
|
2069
2081
|
next: BrainNextSchema,
|
|
2070
2082
|
search: BrainSearchSchema.optional()
|
|
2071
2083
|
}).strict();
|
|
2072
|
-
var
|
|
2084
|
+
var str2 = z6.string();
|
|
2073
2085
|
var strs = z6.array(z6.string());
|
|
2074
2086
|
var CompactResultSchema = z6.object({
|
|
2075
|
-
next: z6.object({ say: strs, then: z6.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason:
|
|
2076
|
-
messages: z6.array(z6.object({ key:
|
|
2077
|
-
entries: z6.array(z6.object({ key:
|
|
2078
|
-
answers: z6.array(z6.object({ question:
|
|
2087
|
+
next: z6.object({ say: strs, then: z6.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str2, instructions: strs }).strict(),
|
|
2088
|
+
messages: z6.array(z6.object({ key: str2, to: str2, text: str2, about: strs, entries: strs, owed: strs, inviteToCall: z6.boolean() }).strict()),
|
|
2089
|
+
entries: z6.array(z6.object({ key: str2, from: str2, on: strs }).strict()),
|
|
2090
|
+
answers: z6.array(z6.object({ question: str2, entries: strs, summary: str2, picked: strs }).strict()),
|
|
2079
2091
|
changes: z6.array(z6.object({
|
|
2080
|
-
key:
|
|
2092
|
+
key: str2,
|
|
2081
2093
|
what: z6.enum(["goal", "question", "lesson"]),
|
|
2082
2094
|
op: z6.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
|
|
2083
|
-
id:
|
|
2084
|
-
text:
|
|
2085
|
-
title:
|
|
2086
|
-
who:
|
|
2087
|
-
under:
|
|
2095
|
+
id: str2,
|
|
2096
|
+
text: str2,
|
|
2097
|
+
title: str2,
|
|
2098
|
+
who: str2,
|
|
2099
|
+
under: str2,
|
|
2088
2100
|
gate: z6.enum(["start", "complete", "answer", "none"]),
|
|
2089
|
-
options: z6.array(z6.object({ id:
|
|
2101
|
+
options: z6.array(z6.object({ id: str2, label: str2 }).strict()),
|
|
2090
2102
|
pick: z6.enum(["one", "many", "rank", "words"]),
|
|
2091
2103
|
entries: strs
|
|
2092
2104
|
}).strict()),
|
|
2093
2105
|
feedback: z6.array(strs),
|
|
2094
|
-
search: z6.object({ query:
|
|
2106
|
+
search: z6.object({ query: str2, within: strs, goal: str2 }).strict()
|
|
2095
2107
|
}).strict();
|
|
2096
2108
|
var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2097
2109
|
zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
|
|
2098
2110
|
);
|
|
2099
|
-
var
|
|
2111
|
+
var str3 = z7.string();
|
|
2100
2112
|
var strs2 = z7.array(z7.string());
|
|
2101
2113
|
var TalkerReplySchema = z7.object({
|
|
2102
2114
|
/** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
|
|
2103
2115
|
* or an agent's handle; `sentences` is what a message to the person says, one short sentence each
|
|
2104
|
-
* ([] to an agent); `text` is what a message to an agent says ("" to the person); `
|
|
2116
|
+
* ([] to an agent); `text` is what a message to an agent says ("" to the person); `summary` is what it
|
|
2117
|
+
* asks the agent, in under ten words, as the person would put it ("" to the person; owner, 2026-10-15:
|
|
2118
|
+
* "Yes, add the summary"), which the recap's "Sent to <agent>: …" row shows; `about` names
|
|
2105
2119
|
* handles; `blocks` is the item a question to an agent must be answered before ("" for none);
|
|
2106
2120
|
* `callback` is true on a message to the agent doing work the person asked to be called back about
|
|
2107
2121
|
* once it is done (false otherwise): that agent then owes the call (turns.md, step 0). */
|
|
2108
|
-
messages: z7.array(z7.object({ key:
|
|
2122
|
+
messages: z7.array(z7.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z7.boolean() }).strict()),
|
|
2109
2123
|
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
2110
2124
|
then: z7.enum(["listen", "hold", "end"]),
|
|
2111
2125
|
/** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
|
|
2112
2126
|
* IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
|
|
2113
2127
|
* "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
|
|
2114
2128
|
* end of this call), a question's handle (that question's answer) or an ISO time. */
|
|
2115
|
-
replies: z7.array(z7.object({ item:
|
|
2129
|
+
replies: z7.array(z7.object({ item: str3, options: strs2, lines: strs2, summary: str3, defer: str3 }).strict()),
|
|
2116
2130
|
/** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
|
|
2117
2131
|
instruction: strs2,
|
|
2118
2132
|
/** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
|
|
2119
2133
|
* work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
|
|
2120
2134
|
file: strs2,
|
|
2121
2135
|
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
2122
|
-
need: z7.object({ kind: z7.enum(["search", "item", "agent", ""]), text:
|
|
2136
|
+
need: z7.object({ kind: z7.enum(["search", "item", "agent", ""]), text: str3 }).strict(),
|
|
2123
2137
|
/** The handle of the item to explain ("" for none). */
|
|
2124
|
-
explain:
|
|
2138
|
+
explain: str3
|
|
2125
2139
|
}).strict();
|
|
2126
2140
|
var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2127
2141
|
zodToJsonSchema(TalkerReplySchema, { $refStrategy: "none" })
|
|
2128
2142
|
);
|
|
2129
|
-
var
|
|
2143
|
+
var str4 = z8.string();
|
|
2130
2144
|
var strs3 = z8.array(z8.string());
|
|
2131
2145
|
var FilerReplySchema = z8.object({
|
|
2132
2146
|
/** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals), and what
|
|
2133
2147
|
* the line says in at most ten words. */
|
|
2134
|
-
filed: z8.array(z8.object({ line:
|
|
2148
|
+
filed: z8.array(z8.object({ line: str4, goals: strs3, summary: str4 }).strict()),
|
|
2135
2149
|
goals: z8.array(z8.object({
|
|
2136
2150
|
op: z8.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
2137
|
-
key:
|
|
2138
|
-
goal:
|
|
2139
|
-
title:
|
|
2140
|
-
outcome:
|
|
2141
|
-
owner:
|
|
2142
|
-
under:
|
|
2143
|
-
until:
|
|
2144
|
-
on:
|
|
2151
|
+
key: str4,
|
|
2152
|
+
goal: str4,
|
|
2153
|
+
title: str4,
|
|
2154
|
+
outcome: str4,
|
|
2155
|
+
owner: str4,
|
|
2156
|
+
under: str4,
|
|
2157
|
+
until: str4,
|
|
2158
|
+
on: str4,
|
|
2145
2159
|
gate: z8.enum(["start", "complete", "answer", ""]),
|
|
2146
|
-
why:
|
|
2160
|
+
why: str4
|
|
2147
2161
|
}).strict()),
|
|
2148
2162
|
questions: z8.array(z8.object({
|
|
2149
2163
|
op: z8.enum(["create", "edit", "assign", "withdraw"]),
|
|
2150
|
-
key:
|
|
2151
|
-
question:
|
|
2152
|
-
text:
|
|
2153
|
-
summary:
|
|
2154
|
-
to:
|
|
2155
|
-
goal:
|
|
2156
|
-
options: z8.array(z8.object({ id:
|
|
2164
|
+
key: str4,
|
|
2165
|
+
question: str4,
|
|
2166
|
+
text: str4,
|
|
2167
|
+
summary: str4,
|
|
2168
|
+
to: str4,
|
|
2169
|
+
goal: str4,
|
|
2170
|
+
options: z8.array(z8.object({ id: str4, label: str4 }).strict()),
|
|
2157
2171
|
pick: z8.enum(["one", "many", "rank", "words", ""]),
|
|
2158
|
-
blocks:
|
|
2172
|
+
blocks: str4
|
|
2159
2173
|
}).strict()),
|
|
2160
|
-
lessons: z8.array(z8.object({ op: z8.enum(["remember", "revise", "withdraw"]), lesson:
|
|
2174
|
+
lessons: z8.array(z8.object({ op: z8.enum(["remember", "revise", "withdraw"]), lesson: str4, text: str4, scope: str4 }).strict()),
|
|
2161
2175
|
/** Line handles whose words comment on Paigy itself. */
|
|
2162
2176
|
feedback: strs3
|
|
2163
2177
|
}).strict();
|
|
@@ -2218,6 +2232,27 @@ var TidyReplySchema = z10.object({
|
|
|
2218
2232
|
var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2219
2233
|
zodToJsonSchema(TidyReplySchema, { $refStrategy: "none" })
|
|
2220
2234
|
);
|
|
2235
|
+
var HandoutReplySchema = z11.object({
|
|
2236
|
+
assign: z11.array(z11.object({
|
|
2237
|
+
/** The Goal's handle (G). */
|
|
2238
|
+
goal: z11.string(),
|
|
2239
|
+
/** The agent's handle (A). */
|
|
2240
|
+
to: z11.string(),
|
|
2241
|
+
/** At most ten words, naming the Goal of theirs it is most like: the reason the hand-out records. */
|
|
2242
|
+
why: z11.string()
|
|
2243
|
+
}).strict()),
|
|
2244
|
+
/** The Goals no listed agent's work is even loosely like: Paigy starts a session for them (owner, 2026-10-24,
|
|
2245
|
+
* Goal 37603006, "One per family of related Goals"). */
|
|
2246
|
+
new: z11.array(z11.object({
|
|
2247
|
+
/** The Goal's handle (G). */
|
|
2248
|
+
goal: z11.string(),
|
|
2249
|
+
/** At most ten words: why nothing active fits it. */
|
|
2250
|
+
why: z11.string()
|
|
2251
|
+
}).strict())
|
|
2252
|
+
}).strict();
|
|
2253
|
+
var HANDOUT_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2254
|
+
zodToJsonSchema(HandoutReplySchema, { $refStrategy: "none" })
|
|
2255
|
+
);
|
|
2221
2256
|
function entryWords(entry) {
|
|
2222
2257
|
const content = entry.content;
|
|
2223
2258
|
if (content && "sealed" in content) return "";
|
|
@@ -2253,20 +2288,20 @@ var LIVE_MS = 3 * 6e4;
|
|
|
2253
2288
|
var WORKING_MS = 60 * 6e4;
|
|
2254
2289
|
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
2255
2290
|
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
2256
|
-
var ContextSchema =
|
|
2257
|
-
title:
|
|
2258
|
-
description:
|
|
2291
|
+
var ContextSchema = z12.object({
|
|
2292
|
+
title: z12.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
2293
|
+
description: z12.array(z12.string().min(1)).describe(
|
|
2259
2294
|
"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."
|
|
2260
2295
|
),
|
|
2261
2296
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
2262
2297
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
2263
|
-
units:
|
|
2298
|
+
units: z12.array(z12.object({ title: z12.string(), body: z12.string() })).optional()
|
|
2264
2299
|
});
|
|
2265
|
-
var ParticipantSchema =
|
|
2266
|
-
kind:
|
|
2267
|
-
id:
|
|
2300
|
+
var ParticipantSchema = z12.object({
|
|
2301
|
+
kind: z12.enum(["human", "agent"]),
|
|
2302
|
+
id: z12.string()
|
|
2268
2303
|
});
|
|
2269
|
-
var TransformSchema =
|
|
2304
|
+
var TransformSchema = z12.enum([
|
|
2270
2305
|
"structure",
|
|
2271
2306
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
2272
2307
|
"request_more",
|
|
@@ -2289,13 +2324,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
|
|
|
2289
2324
|
function isPaigy(ref) {
|
|
2290
2325
|
return ref === participantRef(PAIGY_SELF);
|
|
2291
2326
|
}
|
|
2292
|
-
var VisualSchema =
|
|
2293
|
-
url:
|
|
2294
|
-
label:
|
|
2327
|
+
var VisualSchema = z12.object({
|
|
2328
|
+
url: z12.string().url(),
|
|
2329
|
+
label: z12.string().optional()
|
|
2295
2330
|
});
|
|
2296
|
-
var NotifyLevelSchema =
|
|
2297
|
-
var SelectShapeSchema =
|
|
2298
|
-
var ReceiptEventSchema =
|
|
2331
|
+
var NotifyLevelSchema = z12.enum(["inbox", "push", "banner", "call"]);
|
|
2332
|
+
var SelectShapeSchema = z12.enum(["one", "many", "rank", "confirm", "text"]);
|
|
2333
|
+
var ReceiptEventSchema = z12.enum([
|
|
2299
2334
|
"delivered",
|
|
2300
2335
|
// the bundle reached the recipient at some level
|
|
2301
2336
|
"seen",
|
|
@@ -2325,47 +2360,47 @@ var ReceiptEventSchema = z11.enum([
|
|
|
2325
2360
|
// be rewound by a writer that forgot to advance it.
|
|
2326
2361
|
"restarted"
|
|
2327
2362
|
]);
|
|
2328
|
-
var AttentionSchema =
|
|
2363
|
+
var AttentionSchema = z12.object({
|
|
2329
2364
|
urgency: NotifyLevelSchema,
|
|
2330
2365
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
2331
2366
|
select: SelectShapeSchema.nullable(),
|
|
2332
2367
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
2333
|
-
points:
|
|
2368
|
+
points: z12.array(z12.string()).nullable(),
|
|
2334
2369
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
2335
|
-
blocking:
|
|
2370
|
+
blocking: z12.boolean(),
|
|
2336
2371
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
2337
2372
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
2338
|
-
deadline:
|
|
2373
|
+
deadline: z12.string().datetime().nullable().optional()
|
|
2339
2374
|
});
|
|
2340
|
-
var NotifyRequestFields =
|
|
2375
|
+
var NotifyRequestFields = z12.object({
|
|
2341
2376
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
2342
2377
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
2343
2378
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
2344
2379
|
context: ContextSchema.optional(),
|
|
2345
|
-
options:
|
|
2380
|
+
options: z12.array(OptionSchema.omit({ id: true }).extend({ label: z12.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
2346
2381
|
"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)."
|
|
2347
2382
|
),
|
|
2348
|
-
points:
|
|
2383
|
+
points: z12.array(z12.string().min(1)).optional().describe(
|
|
2349
2384
|
"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."
|
|
2350
2385
|
),
|
|
2351
|
-
visuals:
|
|
2386
|
+
visuals: z12.array(VisualSchema).optional().describe(
|
|
2352
2387
|
"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."
|
|
2353
2388
|
),
|
|
2354
2389
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
2355
|
-
repo:
|
|
2390
|
+
repo: z12.string().optional(),
|
|
2356
2391
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
2357
|
-
branch:
|
|
2392
|
+
branch: z12.string().optional(),
|
|
2358
2393
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
2359
2394
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
2360
2395
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
2361
2396
|
* still accepts the old name from older clients. */
|
|
2362
|
-
parentId:
|
|
2397
|
+
parentId: z12.string().uuid().optional(),
|
|
2363
2398
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
2364
2399
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
2365
|
-
workId:
|
|
2400
|
+
workId: z12.string().uuid().optional(),
|
|
2366
2401
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
2367
2402
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
2368
|
-
goalId:
|
|
2403
|
+
goalId: z12.string().uuid().optional(),
|
|
2369
2404
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
2370
2405
|
"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."
|
|
2371
2406
|
),
|
|
@@ -2373,7 +2408,7 @@ var NotifyRequestFields = z11.object({
|
|
|
2373
2408
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
2374
2409
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
2375
2410
|
* says HOW. */
|
|
2376
|
-
clarifies:
|
|
2411
|
+
clarifies: z12.string().optional(),
|
|
2377
2412
|
select: SelectShapeSchema.optional().describe(
|
|
2378
2413
|
"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."
|
|
2379
2414
|
),
|
|
@@ -2387,20 +2422,20 @@ var NotifyRequestFields = z11.object({
|
|
|
2387
2422
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2388
2423
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2389
2424
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2390
|
-
ask:
|
|
2425
|
+
ask: z12.string().min(1).max(1e4).optional().describe(
|
|
2391
2426
|
'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.'
|
|
2392
2427
|
),
|
|
2393
|
-
needs:
|
|
2428
|
+
needs: z12.array(z12.string().min(1)).optional().describe(
|
|
2394
2429
|
"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."
|
|
2395
2430
|
),
|
|
2396
|
-
urgencyHint:
|
|
2431
|
+
urgencyHint: z12.enum(["whenever", "soon", "now"]).optional().describe(
|
|
2397
2432
|
"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."
|
|
2398
2433
|
),
|
|
2399
2434
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
2400
2435
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
2401
2436
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
2402
2437
|
* urgencyHint/blocking win when both are sent. */
|
|
2403
|
-
waiting:
|
|
2438
|
+
waiting: z12.enum(["none", "soft", "hard"]).optional().describe(
|
|
2404
2439
|
"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."
|
|
2405
2440
|
),
|
|
2406
2441
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -2408,98 +2443,98 @@ var NotifyRequestFields = z11.object({
|
|
|
2408
2443
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
2409
2444
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
2410
2445
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
2411
|
-
confirm:
|
|
2446
|
+
confirm: z12.boolean().optional().describe(
|
|
2412
2447
|
"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'."
|
|
2413
2448
|
),
|
|
2414
2449
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
2415
2450
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
2416
2451
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
2417
2452
|
* waiting:'hard'. */
|
|
2418
|
-
channel:
|
|
2453
|
+
channel: z12.enum(["call", "message"]).optional().describe(
|
|
2419
2454
|
"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."
|
|
2420
2455
|
),
|
|
2421
|
-
confirmStyle:
|
|
2456
|
+
confirmStyle: z12.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2422
2457
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2423
2458
|
),
|
|
2424
|
-
blocking:
|
|
2459
|
+
blocking: z12.boolean().default(false).describe(
|
|
2425
2460
|
"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."
|
|
2426
2461
|
)
|
|
2427
2462
|
});
|
|
2428
2463
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
2429
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
2464
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
2430
2465
|
if (r.ask !== void 0) {
|
|
2431
2466
|
for (const f of ["context", "select", "points"]) {
|
|
2432
2467
|
if (r[f] !== void 0)
|
|
2433
|
-
ctx.addIssue({ code:
|
|
2468
|
+
ctx.addIssue({ code: z12.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.` });
|
|
2434
2469
|
}
|
|
2435
2470
|
return;
|
|
2436
2471
|
}
|
|
2437
2472
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
2438
|
-
ctx.addIssue({ code:
|
|
2473
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
2439
2474
|
if (!r.context)
|
|
2440
|
-
ctx.addIssue({ code:
|
|
2475
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
2441
2476
|
if (!r.select)
|
|
2442
|
-
ctx.addIssue({ code:
|
|
2477
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
2443
2478
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
2444
2479
|
if (needsOptions && !r.options?.length)
|
|
2445
|
-
ctx.addIssue({ code:
|
|
2480
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
2446
2481
|
if (!needsOptions && r.options?.length)
|
|
2447
|
-
ctx.addIssue({ code:
|
|
2482
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
2448
2483
|
});
|
|
2449
|
-
var NotifyStatusSchema =
|
|
2450
|
-
var AgentStateSchema =
|
|
2451
|
-
var TurnSchema =
|
|
2452
|
-
prompt:
|
|
2453
|
-
reply:
|
|
2484
|
+
var NotifyStatusSchema = z12.enum(["pending", "answered", "ignored"]);
|
|
2485
|
+
var AgentStateSchema = z12.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
2486
|
+
var TurnSchema = z12.object({
|
|
2487
|
+
prompt: z12.string(),
|
|
2488
|
+
reply: z12.string()
|
|
2454
2489
|
});
|
|
2455
|
-
var UserAnswerSchema =
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2490
|
+
var UserAnswerSchema = z12.discriminatedUnion("kind", [
|
|
2491
|
+
z12.object({ kind: z12.literal("option"), optionId: z12.string(), label: z12.string().optional() }),
|
|
2492
|
+
z12.object({ kind: z12.literal("text"), text: z12.string() }),
|
|
2493
|
+
z12.object({ kind: z12.literal("ignored") }),
|
|
2494
|
+
z12.object({ kind: z12.literal("multi"), optionIds: z12.array(z12.string()), labels: z12.array(z12.string()).optional() }),
|
|
2495
|
+
z12.object({ kind: z12.literal("ranked"), optionIds: z12.array(z12.string()), labels: z12.array(z12.string()).optional() }),
|
|
2496
|
+
z12.object({ kind: z12.literal("clarify"), chunks: z12.array(z12.string()).min(1) }),
|
|
2497
|
+
z12.object({ kind: z12.literal("confirm"), approved: z12.boolean() }),
|
|
2498
|
+
z12.object({ kind: z12.literal("turns"), turns: z12.array(TurnSchema).min(1) }),
|
|
2464
2499
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
2465
2500
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
2466
2501
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
2467
2502
|
* card the user can reply to — the broker never overrides the user. */
|
|
2468
|
-
|
|
2503
|
+
z12.object({ kind: z12.literal("precedent"), answer: z12.string(), derivation: z12.string(), sources: z12.array(z12.string()).min(1) })
|
|
2469
2504
|
]);
|
|
2470
|
-
var IntentSchema =
|
|
2505
|
+
var IntentSchema = z12.object({
|
|
2471
2506
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
2472
2507
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
2473
2508
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
2474
2509
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2475
|
-
kind:
|
|
2476
|
-
detail:
|
|
2510
|
+
kind: z12.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2511
|
+
detail: z12.string(),
|
|
2477
2512
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2478
2513
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2479
2514
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2480
2515
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2481
|
-
dueInSeconds:
|
|
2516
|
+
dueInSeconds: z12.number().int().positive().optional(),
|
|
2482
2517
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2483
2518
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
2484
2519
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
2485
2520
|
* about what cannot answer "is the bot looping less this week?". */
|
|
2486
|
-
fault:
|
|
2521
|
+
fault: z12.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
2487
2522
|
});
|
|
2488
|
-
var RideAlongSchema =
|
|
2523
|
+
var RideAlongSchema = z12.object({
|
|
2489
2524
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
2490
|
-
noteId:
|
|
2525
|
+
noteId: z12.string(),
|
|
2491
2526
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
2492
|
-
text:
|
|
2527
|
+
text: z12.string(),
|
|
2493
2528
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
2494
|
-
parentId:
|
|
2529
|
+
parentId: z12.string().nullable()
|
|
2495
2530
|
});
|
|
2496
|
-
var AwaitItemSchema =
|
|
2497
|
-
|
|
2498
|
-
type:
|
|
2499
|
-
parentId:
|
|
2500
|
-
notificationId:
|
|
2501
|
-
workId:
|
|
2502
|
-
decisionId:
|
|
2531
|
+
var AwaitItemSchema = z12.discriminatedUnion("type", [
|
|
2532
|
+
z12.object({
|
|
2533
|
+
type: z12.literal("reply"),
|
|
2534
|
+
parentId: z12.string(),
|
|
2535
|
+
notificationId: z12.string(),
|
|
2536
|
+
workId: z12.string().uuid().optional(),
|
|
2537
|
+
decisionId: z12.string().uuid().optional(),
|
|
2503
2538
|
answer: UserAnswerSchema,
|
|
2504
2539
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
2505
2540
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -2509,111 +2544,126 @@ var AwaitItemSchema = z11.discriminatedUnion("type", [
|
|
|
2509
2544
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
2510
2545
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
2511
2546
|
* a sentence with a hole in it is worse than no sentence. */
|
|
2512
|
-
note:
|
|
2547
|
+
note: z12.string().optional(),
|
|
2513
2548
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
2514
2549
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
2515
2550
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
2516
2551
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
2517
|
-
transcript:
|
|
2552
|
+
transcript: z12.string().optional(),
|
|
2518
2553
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
2519
2554
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
2520
|
-
covered:
|
|
2555
|
+
covered: z12.array(z12.string()).optional(),
|
|
2521
2556
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
2522
2557
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
2523
2558
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
2524
2559
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
2525
2560
|
* dropped. Absent/empty = nothing owed. */
|
|
2526
|
-
also:
|
|
2561
|
+
also: z12.array(RideAlongSchema).optional()
|
|
2527
2562
|
}),
|
|
2528
|
-
|
|
2529
|
-
type:
|
|
2530
|
-
parentId:
|
|
2531
|
-
notificationId:
|
|
2532
|
-
remindAt:
|
|
2563
|
+
z12.object({
|
|
2564
|
+
type: z12.literal("remind"),
|
|
2565
|
+
parentId: z12.string(),
|
|
2566
|
+
notificationId: z12.string(),
|
|
2567
|
+
remindAt: z12.string().datetime({ offset: true }),
|
|
2533
2568
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
2534
|
-
remindInSeconds:
|
|
2569
|
+
remindInSeconds: z12.number()
|
|
2535
2570
|
}),
|
|
2536
2571
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2537
2572
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2538
2573
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2539
2574
|
* re-orient via contact({})). */
|
|
2540
|
-
|
|
2541
|
-
type:
|
|
2542
|
-
parentId:
|
|
2543
|
-
notificationId:
|
|
2575
|
+
z12.object({
|
|
2576
|
+
type: z12.literal("superseded"),
|
|
2577
|
+
parentId: z12.string(),
|
|
2578
|
+
notificationId: z12.string()
|
|
2544
2579
|
}),
|
|
2545
2580
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2546
2581
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2547
2582
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2548
2583
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2549
2584
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2550
|
-
|
|
2551
|
-
type:
|
|
2552
|
-
notificationId:
|
|
2553
|
-
inFlight:
|
|
2554
|
-
turn:
|
|
2555
|
-
idx:
|
|
2556
|
-
prompt:
|
|
2557
|
-
reply:
|
|
2558
|
-
acts:
|
|
2585
|
+
z12.object({
|
|
2586
|
+
type: z12.literal("partial"),
|
|
2587
|
+
notificationId: z12.string(),
|
|
2588
|
+
inFlight: z12.literal(true),
|
|
2589
|
+
turn: z12.object({
|
|
2590
|
+
idx: z12.number(),
|
|
2591
|
+
prompt: z12.string(),
|
|
2592
|
+
reply: z12.string(),
|
|
2593
|
+
acts: z12.array(IntentSchema).nullable().optional()
|
|
2559
2594
|
})
|
|
2560
2595
|
}),
|
|
2561
|
-
|
|
2562
|
-
type:
|
|
2563
|
-
also:
|
|
2596
|
+
z12.object({
|
|
2597
|
+
type: z12.literal("idle"),
|
|
2598
|
+
also: z12.array(RideAlongSchema).optional(),
|
|
2564
2599
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
2565
2600
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
2566
2601
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
2567
2602
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
2568
2603
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
2569
|
-
inFlight:
|
|
2604
|
+
inFlight: z12.boolean().optional()
|
|
2570
2605
|
})
|
|
2571
2606
|
]);
|
|
2572
|
-
var VoiceKeySchema =
|
|
2573
|
-
var AgendaTurnSchema =
|
|
2607
|
+
var VoiceKeySchema = z12.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2608
|
+
var AgendaTurnSchema = z12.object({
|
|
2574
2609
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
2575
2610
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
2576
2611
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
2577
2612
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
2578
2613
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
2579
2614
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
2580
|
-
id:
|
|
2615
|
+
id: z12.string().optional(),
|
|
2581
2616
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
2582
2617
|
* the planner declares duplicates instead of asking them twice. */
|
|
2583
|
-
coveredIds:
|
|
2618
|
+
coveredIds: z12.array(z12.string()).optional(),
|
|
2584
2619
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
2585
2620
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
2586
|
-
info:
|
|
2621
|
+
info: z12.array(z12.string().min(1)).default([]),
|
|
2587
2622
|
/** THE ID OF EACH SENTENCE, when the plan names them (turns.md): a talker message's sentences are its
|
|
2588
2623
|
* `info`, one piece each, and `units[i]` is the id the bot's line receipt carries for `info[i]`
|
|
2589
2624
|
* (`<cycle>:<key>:<n>`), so what was heard is known per sentence. Absent on every other turn. */
|
|
2590
|
-
units:
|
|
2591
|
-
question:
|
|
2625
|
+
units: z12.array(z12.string().min(1)).optional(),
|
|
2626
|
+
question: z12.string().min(1).nullable(),
|
|
2592
2627
|
/** True on the one turn carrying the agent's own declared question. */
|
|
2593
|
-
asks:
|
|
2628
|
+
asks: z12.boolean().optional(),
|
|
2594
2629
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
2595
2630
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
2596
2631
|
* which route nothing. */
|
|
2597
|
-
claimId:
|
|
2632
|
+
claimId: z12.string().optional(),
|
|
2598
2633
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
2599
|
-
voice:
|
|
2634
|
+
voice: z12.string().optional(),
|
|
2600
2635
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
2601
2636
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
2602
2637
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
2603
2638
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
2604
|
-
agent:
|
|
2639
|
+
agent: z12.string().optional(),
|
|
2605
2640
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
2606
2641
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
2607
2642
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
2608
2643
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
2609
2644
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
2610
|
-
agentId:
|
|
2645
|
+
agentId: z12.string().optional(),
|
|
2611
2646
|
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
2612
2647
|
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
2613
2648
|
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
2614
|
-
onTable:
|
|
2649
|
+
onTable: z12.boolean().optional(),
|
|
2650
|
+
/** THE CARRIED ITEM THIS LINE TELLS (Goal 2a1e50eb, owner 2026-10-14: "Build the whole design") — a
|
|
2651
|
+
* request the Call carries that asks NOTHING: a report, a progress note, another agent's waiting news.
|
|
2652
|
+
* Its request Entry id, so the phone can make that agenda row the current one while it is read
|
|
2653
|
+
* (`telling` on the `turn` topic, `docs/clients/app/walk/design.md` §11).
|
|
2654
|
+
*
|
|
2655
|
+
* A FIELD OF ITS OWN, NEVER `claimId`: the claim is the RETURN identity — answers route by it and the
|
|
2656
|
+
* record tags the line with it (`workId`) — and an answer must never route to an Entry that asks
|
|
2657
|
+
* nothing. Absent on a turn speaking for a question (`claimId` says that) and on one speaking for no
|
|
2658
|
+
* carried item at all (an opener, a sign-off, Paigy's own line). */
|
|
2659
|
+
telling: z12.string().optional(),
|
|
2660
|
+
/** PAIGY'S OWN LINE (owner, 2026-10-15; calls ed2cbdce, ad50ad25): a turn that speaks for no item -- an
|
|
2661
|
+
* opener, a sign-off, a reply about nothing an agent asked or told -- is recorded as Paigy's, not as the
|
|
2662
|
+
* agent that rang. Absent on every turn that speaks for an item (`agentId` names whose), and on plans
|
|
2663
|
+
* from before it: a bot that does not know it records the ringing agent, as before. */
|
|
2664
|
+
speaksFor: z12.literal("paigy").optional(),
|
|
2615
2665
|
select: SelectShapeSchema.optional(),
|
|
2616
|
-
options:
|
|
2666
|
+
options: z12.array(OptionSchema.omit({ id: true })).optional(),
|
|
2617
2667
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
2618
2668
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
2619
2669
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -2622,58 +2672,58 @@ var AgendaTurnSchema = z11.object({
|
|
|
2622
2672
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
2623
2673
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
2624
2674
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2625
|
-
blocking:
|
|
2675
|
+
blocking: z12.boolean().optional(),
|
|
2626
2676
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
2627
2677
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
2628
2678
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
2629
2679
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
2630
2680
|
* speak, it is dropped and their words are taken like any reply. */
|
|
2631
|
-
ifSilent:
|
|
2681
|
+
ifSilent: z12.boolean().optional()
|
|
2632
2682
|
});
|
|
2633
2683
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2634
|
-
var CallQuestionSchema =
|
|
2635
|
-
id:
|
|
2636
|
-
entryId:
|
|
2637
|
-
title:
|
|
2638
|
-
state:
|
|
2639
|
-
answer:
|
|
2684
|
+
var CallQuestionSchema = z12.object({
|
|
2685
|
+
id: z12.string(),
|
|
2686
|
+
entryId: z12.string(),
|
|
2687
|
+
title: z12.string(),
|
|
2688
|
+
state: z12.enum(["open", "answered"]),
|
|
2689
|
+
answer: z12.string().nullable(),
|
|
2640
2690
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2641
2691
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2642
2692
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2643
2693
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2644
|
-
agent:
|
|
2694
|
+
agent: z12.object({ tokenId: z12.string(), name: z12.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
2645
2695
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
2646
2696
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
2647
2697
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
2648
2698
|
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
2649
|
-
options:
|
|
2699
|
+
options: z12.array(OptionSchema).optional(),
|
|
2650
2700
|
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
2651
2701
|
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
2652
2702
|
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
2653
2703
|
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
2654
2704
|
* preview layout for any `options`, and must see exactly what it did. */
|
|
2655
|
-
choices:
|
|
2705
|
+
choices: z12.array(OptionSchema).optional(),
|
|
2656
2706
|
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
2657
|
-
select:
|
|
2707
|
+
select: z12.enum(["one", "many", "rank"]).optional()
|
|
2658
2708
|
});
|
|
2659
|
-
var InboxItemSchema =
|
|
2660
|
-
id:
|
|
2661
|
-
tokenId:
|
|
2709
|
+
var InboxItemSchema = z12.object({
|
|
2710
|
+
id: z12.string(),
|
|
2711
|
+
tokenId: z12.string().optional(),
|
|
2662
2712
|
status: NotifyStatusSchema,
|
|
2663
2713
|
context: ContextSchema,
|
|
2664
|
-
options:
|
|
2714
|
+
options: z12.array(OptionSchema).optional(),
|
|
2665
2715
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2666
|
-
points:
|
|
2716
|
+
points: z12.array(z12.string()).optional(),
|
|
2667
2717
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
2668
2718
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2669
2719
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2670
2720
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2671
|
-
asks:
|
|
2721
|
+
asks: z12.boolean().optional(),
|
|
2672
2722
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2673
2723
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2674
2724
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2675
2725
|
* which must never CLAIM stalled. */
|
|
2676
|
-
lastSeenAt:
|
|
2726
|
+
lastSeenAt: z12.string().optional(),
|
|
2677
2727
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2678
2728
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2679
2729
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2683,7 +2733,7 @@ var InboxItemSchema = z11.object({
|
|
|
2683
2733
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2684
2734
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2685
2735
|
* `createdAt`. */
|
|
2686
|
-
agentStateAt:
|
|
2736
|
+
agentStateAt: z12.string().datetime().optional(),
|
|
2687
2737
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
2688
2738
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
2689
2739
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -2697,40 +2747,40 @@ var InboxItemSchema = z11.object({
|
|
|
2697
2747
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2698
2748
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2699
2749
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2700
|
-
questions:
|
|
2750
|
+
questions: z12.array(CallQuestionSchema).optional(),
|
|
2701
2751
|
/** The same call carrying order, including request Entries with no DecisionNeed as reports.
|
|
2702
2752
|
* Additive: questions stays decision-only for every other surface and older bundles. */
|
|
2703
|
-
callItems:
|
|
2704
|
-
visuals:
|
|
2753
|
+
callItems: z12.array(CallQuestionSchema.extend({ state: z12.enum(["open", "answered", "report"]), acknowledged: z12.boolean().optional() })).optional(),
|
|
2754
|
+
visuals: z12.array(VisualSchema).optional(),
|
|
2705
2755
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2706
2756
|
* agent's suggestion, or a default silly name). */
|
|
2707
|
-
name:
|
|
2757
|
+
name: z12.string(),
|
|
2708
2758
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2709
2759
|
voice: VoiceKeySchema.optional(),
|
|
2710
|
-
repo:
|
|
2711
|
-
branch:
|
|
2712
|
-
createdAt:
|
|
2713
|
-
snoozedUntil:
|
|
2760
|
+
repo: z12.string().optional(),
|
|
2761
|
+
branch: z12.string().optional(),
|
|
2762
|
+
createdAt: z12.string().datetime(),
|
|
2763
|
+
snoozedUntil: z12.string().datetime().optional(),
|
|
2714
2764
|
agentState: AgentStateSchema.default("idle"),
|
|
2715
2765
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2716
2766
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2717
2767
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2718
|
-
turn:
|
|
2768
|
+
turn: z12.enum(["you", "agent"]).default("you"),
|
|
2719
2769
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2720
2770
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2721
2771
|
* error badge + Retry. */
|
|
2722
|
-
error:
|
|
2772
|
+
error: z12.string().optional(),
|
|
2723
2773
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
2724
2774
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2725
2775
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2726
2776
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2727
|
-
cold:
|
|
2728
|
-
clarifies:
|
|
2777
|
+
cold: z12.string().datetime().optional(),
|
|
2778
|
+
clarifies: z12.string().optional(),
|
|
2729
2779
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2730
2780
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2731
2781
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2732
2782
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2733
|
-
onCall:
|
|
2783
|
+
onCall: z12.literal(true).optional(),
|
|
2734
2784
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2735
2785
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2736
2786
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2740,31 +2790,46 @@ var InboxItemSchema = z11.object({
|
|
|
2740
2790
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2741
2791
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2742
2792
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2743
|
-
ring:
|
|
2744
|
-
declinedAt:
|
|
2745
|
-
anchorAt:
|
|
2746
|
-
nextRingAt:
|
|
2747
|
-
step:
|
|
2793
|
+
ring: z12.object({
|
|
2794
|
+
declinedAt: z12.string().datetime(),
|
|
2795
|
+
anchorAt: z12.string().datetime(),
|
|
2796
|
+
nextRingAt: z12.string().datetime().nullable(),
|
|
2797
|
+
step: z12.number().int()
|
|
2748
2798
|
}).optional(),
|
|
2749
2799
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
2750
2800
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
2751
2801
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
2752
2802
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
2753
2803
|
* feedback ABOUT. */
|
|
2754
|
-
why:
|
|
2804
|
+
why: z12.object({
|
|
2755
2805
|
asked: NotifyLevelSchema,
|
|
2756
2806
|
got: NotifyLevelSchema,
|
|
2757
|
-
because:
|
|
2758
|
-
line:
|
|
2807
|
+
because: z12.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2808
|
+
line: z12.string()
|
|
2759
2809
|
}).optional(),
|
|
2760
|
-
select:
|
|
2761
|
-
confirmStyle:
|
|
2810
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2811
|
+
confirmStyle: z12.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2762
2812
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2763
2813
|
),
|
|
2764
2814
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
2765
2815
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
2766
2816
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
2767
|
-
blocking:
|
|
2817
|
+
blocking: z12.boolean().default(false),
|
|
2818
|
+
/** WHAT FREED THIS QUESTION (owner, 2026-10-16, Goal 2383ef14: "we don't really show the question
|
|
2819
|
+
* at the top until the question it could reply to with is resolved. And then we show it again
|
|
2820
|
+
* with the context of the other thing that was resolved"; asked how it should carry that, "The
|
|
2821
|
+
* answer above it"). A question they answered with a question of their own waits for theirs
|
|
2822
|
+
* (`private.ask_back`) and drops out of the front while it waits; when it comes back, the card
|
|
2823
|
+
* draws their question and the answer it got ABOVE the ask, as one card. Absent on a question
|
|
2824
|
+
* nothing ever held back. */
|
|
2825
|
+
freedBy: z12.object({
|
|
2826
|
+
questionId: z12.string(),
|
|
2827
|
+
asked: z12.string(),
|
|
2828
|
+
answer: z12.string(),
|
|
2829
|
+
answeredAt: z12.string(),
|
|
2830
|
+
/** The answer in ten words, when the talker wrote one beside it. */
|
|
2831
|
+
summary: z12.string().optional()
|
|
2832
|
+
}).optional(),
|
|
2768
2833
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
2769
2834
|
answer: UserAnswerSchema.optional(),
|
|
2770
2835
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -2772,17 +2837,17 @@ var InboxItemSchema = z11.object({
|
|
|
2772
2837
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
2773
2838
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
2774
2839
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
2775
|
-
communication:
|
|
2776
|
-
deliveryId:
|
|
2777
|
-
kind:
|
|
2778
|
-
entryId:
|
|
2779
|
-
goalIds:
|
|
2780
|
-
decisionNeedId:
|
|
2781
|
-
sealed:
|
|
2782
|
-
goalState:
|
|
2840
|
+
communication: z12.object({
|
|
2841
|
+
deliveryId: z12.string(),
|
|
2842
|
+
kind: z12.enum(["notification", "call"]),
|
|
2843
|
+
entryId: z12.string(),
|
|
2844
|
+
goalIds: z12.array(z12.string()),
|
|
2845
|
+
decisionNeedId: z12.string().optional(),
|
|
2846
|
+
sealed: z12.boolean(),
|
|
2847
|
+
goalState: z12.string().optional(),
|
|
2783
2848
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2784
2849
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2785
|
-
goalTitle:
|
|
2850
|
+
goalTitle: z12.string().optional()
|
|
2786
2851
|
}).optional(),
|
|
2787
2852
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
2788
2853
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -2794,27 +2859,27 @@ var InboxItemSchema = z11.object({
|
|
|
2794
2859
|
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
2795
2860
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
2796
2861
|
* with no rev is simply always re-sent. */
|
|
2797
|
-
rev:
|
|
2862
|
+
rev: z12.string().optional()
|
|
2798
2863
|
});
|
|
2799
2864
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
2800
|
-
var PushTokenSchema =
|
|
2801
|
-
voipToken:
|
|
2802
|
-
alertToken:
|
|
2803
|
-
fcmToken:
|
|
2804
|
-
platform:
|
|
2865
|
+
var PushTokenSchema = z12.object({
|
|
2866
|
+
voipToken: z12.string().min(1).optional(),
|
|
2867
|
+
alertToken: z12.string().min(1).optional(),
|
|
2868
|
+
fcmToken: z12.string().min(1).optional(),
|
|
2869
|
+
platform: z12.enum(["ios", "android"])
|
|
2805
2870
|
}).superRefine((v, ctx) => {
|
|
2806
2871
|
if (v.platform !== "ios") return;
|
|
2807
2872
|
for (const field of ["voipToken", "alertToken"]) {
|
|
2808
2873
|
const token = v[field];
|
|
2809
2874
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
2810
2875
|
ctx.addIssue({
|
|
2811
|
-
code:
|
|
2876
|
+
code: z12.ZodIssueCode.custom,
|
|
2812
2877
|
path: [field],
|
|
2813
2878
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
2814
2879
|
});
|
|
2815
2880
|
}
|
|
2816
2881
|
});
|
|
2817
|
-
var MissedCallSchema =
|
|
2882
|
+
var MissedCallSchema = z12.enum([
|
|
2818
2883
|
"retry_10m",
|
|
2819
2884
|
"retry_30m",
|
|
2820
2885
|
"retry_60m",
|
|
@@ -2826,31 +2891,31 @@ var MissedCallSchema = z11.enum([
|
|
|
2826
2891
|
]);
|
|
2827
2892
|
var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
2828
2893
|
var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
|
|
2829
|
-
var BrokerTuningSchema =
|
|
2894
|
+
var BrokerTuningSchema = z12.object({
|
|
2830
2895
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
2831
|
-
ackVerbosity:
|
|
2896
|
+
ackVerbosity: z12.enum(["normal", "none"]).optional(),
|
|
2832
2897
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
2833
2898
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
2834
|
-
clarifyEagerness:
|
|
2899
|
+
clarifyEagerness: z12.enum(["low", "normal", "high"]).optional(),
|
|
2835
2900
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
2836
|
-
phrasebook:
|
|
2901
|
+
phrasebook: z12.array(z12.object({ say: z12.string().min(1).max(60), mean: z12.string().min(1).max(120) })).max(24).optional(),
|
|
2837
2902
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
2838
2903
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
2839
2904
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
2840
2905
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
2841
2906
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
2842
|
-
language:
|
|
2907
|
+
language: z12.enum(["en", "es"]).optional()
|
|
2843
2908
|
});
|
|
2844
|
-
var UserSettingsSchema =
|
|
2845
|
-
permissions:
|
|
2846
|
-
call:
|
|
2847
|
-
banner:
|
|
2848
|
-
push:
|
|
2909
|
+
var UserSettingsSchema = z12.object({
|
|
2910
|
+
permissions: z12.object({
|
|
2911
|
+
call: z12.boolean(),
|
|
2912
|
+
banner: z12.boolean(),
|
|
2913
|
+
push: z12.boolean()
|
|
2849
2914
|
}),
|
|
2850
2915
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
2851
2916
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
2852
2917
|
* what people read (owner, 2026-09-30). */
|
|
2853
|
-
sessionMode:
|
|
2918
|
+
sessionMode: z12.enum(["default", "all_calls", "silent"]),
|
|
2854
2919
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
2855
2920
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
2856
2921
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -2859,7 +2924,7 @@ var UserSettingsSchema = z11.object({
|
|
|
2859
2924
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
2860
2925
|
* next dead field for the same reason. */
|
|
2861
2926
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
2862
|
-
improveConsent:
|
|
2927
|
+
improveConsent: z12.boolean(),
|
|
2863
2928
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
2864
2929
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
2865
2930
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -2867,17 +2932,17 @@ var UserSettingsSchema = z11.object({
|
|
|
2867
2932
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
2868
2933
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
2869
2934
|
* write, 'hosted' on read (see store.ts). */
|
|
2870
|
-
voiceMode:
|
|
2935
|
+
voiceMode: z12.enum(["hosted", "on_device"]).optional(),
|
|
2871
2936
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
2872
2937
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
2873
2938
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
2874
2939
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2875
2940
|
* read (see store.ts). */
|
|
2876
|
-
talk:
|
|
2941
|
+
talk: z12.boolean().optional(),
|
|
2877
2942
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
2878
2943
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
2879
2944
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
2880
|
-
callDiagnostics:
|
|
2945
|
+
callDiagnostics: z12.boolean().optional(),
|
|
2881
2946
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2882
2947
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2883
2948
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2885,7 +2950,7 @@ var UserSettingsSchema = z11.object({
|
|
|
2885
2950
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2886
2951
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2887
2952
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2888
|
-
voiceTuning:
|
|
2953
|
+
voiceTuning: z12.record(z12.string(), z12.union([z12.number(), z12.string()])).optional(),
|
|
2889
2954
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2890
2955
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2891
2956
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2894,70 +2959,70 @@ var UserSettingsSchema = z11.object({
|
|
|
2894
2959
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2895
2960
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2896
2961
|
* call — honest about what we know. */
|
|
2897
|
-
timezone:
|
|
2962
|
+
timezone: z12.string().min(1).max(64).optional(),
|
|
2898
2963
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2899
2964
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2900
2965
|
broker: BrokerTuningSchema.optional()
|
|
2901
2966
|
});
|
|
2902
|
-
var GoalLatestSchema =
|
|
2903
|
-
from:
|
|
2904
|
-
said:
|
|
2905
|
-
at:
|
|
2906
|
-
entryId:
|
|
2967
|
+
var GoalLatestSchema = z12.object({
|
|
2968
|
+
from: z12.enum(["person", "agent"]),
|
|
2969
|
+
said: z12.string(),
|
|
2970
|
+
at: z12.string(),
|
|
2971
|
+
entryId: z12.string(),
|
|
2907
2972
|
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
2908
2973
|
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
2909
2974
|
* none was. The Work row reads it before `said`. */
|
|
2910
|
-
summary:
|
|
2975
|
+
summary: z12.string().optional(),
|
|
2911
2976
|
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
2912
2977
|
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
2913
|
-
titles:
|
|
2978
|
+
titles: z12.array(z12.string()).optional(),
|
|
2914
2979
|
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
2915
2980
|
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
2916
|
-
line:
|
|
2981
|
+
line: z12.string().optional()
|
|
2917
2982
|
});
|
|
2918
|
-
var HistoryWorkSchema =
|
|
2919
|
-
id:
|
|
2920
|
-
title:
|
|
2921
|
-
state:
|
|
2983
|
+
var HistoryWorkSchema = z12.object({
|
|
2984
|
+
id: z12.string(),
|
|
2985
|
+
title: z12.string(),
|
|
2986
|
+
state: z12.enum(["done", "cancelled"]),
|
|
2922
2987
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
2923
|
-
assignee:
|
|
2988
|
+
assignee: z12.string(),
|
|
2924
2989
|
latest: GoalLatestSchema.nullable().optional()
|
|
2925
2990
|
});
|
|
2926
|
-
var HistoryEntrySchema =
|
|
2927
|
-
|
|
2928
|
-
|
|
2991
|
+
var HistoryEntrySchema = z12.union([
|
|
2992
|
+
z12.object({ at: z12.string(), card: InboxItemSchema }),
|
|
2993
|
+
z12.object({ at: z12.string(), work: HistoryWorkSchema })
|
|
2929
2994
|
]);
|
|
2930
|
-
var HistoryPageSchema =
|
|
2931
|
-
entries:
|
|
2932
|
-
next:
|
|
2995
|
+
var HistoryPageSchema = z12.object({
|
|
2996
|
+
entries: z12.array(HistoryEntrySchema),
|
|
2997
|
+
next: z12.string().nullable()
|
|
2933
2998
|
});
|
|
2934
2999
|
var ACTIVITY_LINES = 2;
|
|
2935
3000
|
var ACTIVITY_LINE_MAX = 80;
|
|
2936
|
-
var AgentActivitySchema =
|
|
3001
|
+
var AgentActivitySchema = z12.object({
|
|
2937
3002
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
2938
|
-
lines:
|
|
3003
|
+
lines: z12.array(z12.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
2939
3004
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
2940
3005
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
2941
|
-
at:
|
|
3006
|
+
at: z12.string().datetime()
|
|
2942
3007
|
});
|
|
2943
|
-
var ConnectionSummarySchema =
|
|
3008
|
+
var ConnectionSummarySchema = z12.object({
|
|
2944
3009
|
/** The connection = the agent's token id (used to address a request). */
|
|
2945
|
-
id:
|
|
3010
|
+
id: z12.string(),
|
|
2946
3011
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
2947
3012
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
2948
3013
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
2949
3014
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
2950
|
-
kind:
|
|
3015
|
+
kind: z12.enum(["device", "agent"]).optional(),
|
|
2951
3016
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
2952
3017
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
2953
3018
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
2954
|
-
mintedByDevice:
|
|
2955
|
-
device:
|
|
3019
|
+
mintedByDevice: z12.string().nullable().optional(),
|
|
3020
|
+
device: z12.string().nullable(),
|
|
2956
3021
|
/** The agent's display name (the single pairing name). */
|
|
2957
|
-
name:
|
|
3022
|
+
name: z12.string(),
|
|
2958
3023
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
2959
3024
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
2960
|
-
provider:
|
|
3025
|
+
provider: z12.string().nullable(),
|
|
2961
3026
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
2962
3027
|
voice: VoiceKeySchema.nullable(),
|
|
2963
3028
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -2968,34 +3033,34 @@ var ConnectionSummarySchema = z11.object({
|
|
|
2968
3033
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
2969
3034
|
* set once must not overrule a rule they set about one agent. */
|
|
2970
3035
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
2971
|
-
createdAt:
|
|
3036
|
+
createdAt: z12.string().datetime(),
|
|
2972
3037
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
2973
3038
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
2974
|
-
lastContactAt:
|
|
3039
|
+
lastContactAt: z12.string().datetime().nullable(),
|
|
2975
3040
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
2976
3041
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
2977
|
-
lastSeenAt:
|
|
3042
|
+
lastSeenAt: z12.string().datetime().nullable().optional(),
|
|
2978
3043
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
2979
3044
|
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
2980
3045
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
2981
|
-
lastWorkedAt:
|
|
3046
|
+
lastWorkedAt: z12.string().datetime().nullable().optional(),
|
|
2982
3047
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
2983
3048
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
2984
|
-
oldestReadyAt:
|
|
3049
|
+
oldestReadyAt: z12.string().datetime().nullable().optional(),
|
|
2985
3050
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
2986
3051
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
2987
3052
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
2988
|
-
runtime:
|
|
3053
|
+
runtime: z12.object({
|
|
2989
3054
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
2990
3055
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
2991
|
-
version:
|
|
2992
|
-
harnesses:
|
|
2993
|
-
workspaces:
|
|
3056
|
+
version: z12.string().optional(),
|
|
3057
|
+
harnesses: z12.array(z12.object({ name: z12.string(), label: z12.string(), status: z12.string(), models: z12.array(z12.string()).optional() })).optional(),
|
|
3058
|
+
workspaces: z12.array(z12.string()).optional(),
|
|
2994
3059
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
2995
3060
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
2996
3061
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
2997
3062
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
2998
|
-
repos:
|
|
3063
|
+
repos: z12.array(z12.object({ path: z12.string(), remote: z12.string() })).optional()
|
|
2999
3064
|
}).optional(),
|
|
3000
3065
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
3001
3066
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -3004,145 +3069,145 @@ var ConnectionSummarySchema = z11.object({
|
|
|
3004
3069
|
activity: AgentActivitySchema.optional(),
|
|
3005
3070
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
3006
3071
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
3007
|
-
managed:
|
|
3072
|
+
managed: z12.boolean()
|
|
3008
3073
|
});
|
|
3009
|
-
var LedgerItemSchema =
|
|
3010
|
-
var AgentLedgerSchema =
|
|
3074
|
+
var LedgerItemSchema = z12.object({ id: z12.string(), parentId: z12.string(), title: z12.string(), createdAt: z12.string() });
|
|
3075
|
+
var AgentLedgerSchema = z12.object({
|
|
3011
3076
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
3012
|
-
agent:
|
|
3077
|
+
agent: z12.object({ id: z12.string(), name: z12.string().nullable(), revokedAt: z12.string().nullable() }),
|
|
3013
3078
|
/** Its own questions you have not answered. */
|
|
3014
|
-
asks:
|
|
3079
|
+
asks: z12.array(LedgerItemSchema),
|
|
3015
3080
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
3016
|
-
answered:
|
|
3081
|
+
answered: z12.array(LedgerItemSchema),
|
|
3017
3082
|
/** Requests you sent it that it never took. */
|
|
3018
|
-
requests:
|
|
3019
|
-
goals:
|
|
3020
|
-
callbacks:
|
|
3083
|
+
requests: z12.array(LedgerItemSchema),
|
|
3084
|
+
goals: z12.array(z12.object({ id: z12.string(), outcome: z12.string(), state: z12.string() })),
|
|
3085
|
+
callbacks: z12.array(z12.object({ id: z12.string(), parentId: z12.string(), trigger: z12.string(), note: z12.string(), dueAt: z12.string().nullable() }))
|
|
3021
3086
|
});
|
|
3022
|
-
var ReassignResultSchema =
|
|
3023
|
-
moved:
|
|
3024
|
-
parentId:
|
|
3087
|
+
var ReassignResultSchema = z12.object({
|
|
3088
|
+
moved: z12.object({ asks: z12.number(), answered: z12.number(), requests: z12.number(), goals: z12.number(), callbacks: z12.number() }),
|
|
3089
|
+
parentId: z12.string().nullable()
|
|
3025
3090
|
});
|
|
3026
|
-
var LessonStateSchema =
|
|
3027
|
-
var LessonViewSchema =
|
|
3028
|
-
id:
|
|
3029
|
-
text:
|
|
3091
|
+
var LessonStateSchema = z12.enum(["active", "proposed", "retired"]);
|
|
3092
|
+
var LessonViewSchema = z12.object({
|
|
3093
|
+
id: z12.string(),
|
|
3094
|
+
text: z12.string(),
|
|
3030
3095
|
state: LessonStateSchema,
|
|
3031
3096
|
/** The Goal it is scoped to; null = the whole account. */
|
|
3032
|
-
scopeGoalId:
|
|
3033
|
-
goalTitle:
|
|
3034
|
-
version:
|
|
3035
|
-
pinned:
|
|
3097
|
+
scopeGoalId: z12.string().nullable(),
|
|
3098
|
+
goalTitle: z12.string().nullable(),
|
|
3099
|
+
version: z12.number(),
|
|
3100
|
+
pinned: z12.boolean(),
|
|
3036
3101
|
/** When the person last wrote its text themselves. */
|
|
3037
|
-
editedAt:
|
|
3038
|
-
createdAt:
|
|
3039
|
-
updatedAt:
|
|
3102
|
+
editedAt: z12.string().nullable(),
|
|
3103
|
+
createdAt: z12.string(),
|
|
3104
|
+
updatedAt: z12.string(),
|
|
3040
3105
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
3041
|
-
sources:
|
|
3106
|
+
sources: z12.array(z12.object({ entryId: z12.string(), words: z12.string().nullable(), at: z12.string() }))
|
|
3042
3107
|
});
|
|
3043
|
-
var QueueQuestionSchema =
|
|
3108
|
+
var QueueQuestionSchema = z12.object({
|
|
3044
3109
|
/** The decision need's id — what an answer is accepted against. */
|
|
3045
|
-
id:
|
|
3110
|
+
id: z12.string(),
|
|
3046
3111
|
/** The words that were asked, from the request Entry that asked them. */
|
|
3047
|
-
question:
|
|
3112
|
+
question: z12.string(),
|
|
3048
3113
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
3049
3114
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
3050
3115
|
* can answer. */
|
|
3051
|
-
deliveryId:
|
|
3116
|
+
deliveryId: z12.string().nullable().default(null),
|
|
3052
3117
|
/** The Entry the ruling is about. */
|
|
3053
|
-
aboutId:
|
|
3118
|
+
aboutId: z12.string().nullable().default(null),
|
|
3054
3119
|
/** Empty for a free-text question. */
|
|
3055
|
-
options:
|
|
3056
|
-
select:
|
|
3057
|
-
askedAt:
|
|
3120
|
+
options: z12.array(OptionSchema).default([]),
|
|
3121
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
3122
|
+
askedAt: z12.string(),
|
|
3058
3123
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
3059
|
-
answeredAt:
|
|
3124
|
+
answeredAt: z12.string().nullable().default(null),
|
|
3060
3125
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
3061
3126
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
3062
3127
|
* for a settled question whose reply carried nothing readable. */
|
|
3063
|
-
answer:
|
|
3128
|
+
answer: z12.string().nullable().default(null),
|
|
3064
3129
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
3065
3130
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
3066
3131
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
3067
3132
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
3068
3133
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
3069
3134
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
3070
|
-
goalId:
|
|
3135
|
+
goalId: z12.string(),
|
|
3071
3136
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
3072
3137
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
3073
3138
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
3074
3139
|
* gate at all. */
|
|
3075
|
-
blocked:
|
|
3140
|
+
blocked: z12.boolean().default(false)
|
|
3076
3141
|
});
|
|
3077
|
-
var QueueReplySchema =
|
|
3142
|
+
var QueueReplySchema = z12.object({
|
|
3078
3143
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
3079
3144
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
3080
3145
|
* walk can name it in its zoom. */
|
|
3081
|
-
id:
|
|
3146
|
+
id: z12.string(),
|
|
3082
3147
|
/** The Goal the note is on. */
|
|
3083
|
-
goalId:
|
|
3148
|
+
goalId: z12.string(),
|
|
3084
3149
|
/** What the note said. */
|
|
3085
|
-
note:
|
|
3150
|
+
note: z12.string(),
|
|
3086
3151
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
3087
|
-
deliveryId:
|
|
3088
|
-
requestEntryId:
|
|
3089
|
-
askedAt:
|
|
3152
|
+
deliveryId: z12.string(),
|
|
3153
|
+
requestEntryId: z12.string(),
|
|
3154
|
+
askedAt: z12.string(),
|
|
3090
3155
|
/** When the person last replied — the window's start. */
|
|
3091
|
-
repliedAt:
|
|
3156
|
+
repliedAt: z12.string(),
|
|
3092
3157
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
3093
|
-
reply:
|
|
3158
|
+
reply: z12.string().nullable()
|
|
3094
3159
|
});
|
|
3095
|
-
var QueueItemSchema =
|
|
3096
|
-
id:
|
|
3160
|
+
var QueueItemSchema = z12.object({
|
|
3161
|
+
id: z12.string(),
|
|
3097
3162
|
/** One-line headline — the first sentence of the outcome. */
|
|
3098
|
-
title:
|
|
3163
|
+
title: z12.string(),
|
|
3099
3164
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
3100
|
-
intent:
|
|
3165
|
+
intent: z12.string(),
|
|
3101
3166
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
3102
|
-
state:
|
|
3167
|
+
state: z12.string(),
|
|
3103
3168
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
3104
|
-
assignee:
|
|
3169
|
+
assignee: z12.string().nullable().default(null),
|
|
3105
3170
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
3106
|
-
progress:
|
|
3171
|
+
progress: z12.string().nullable().default(null),
|
|
3107
3172
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
3108
3173
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
3109
|
-
progressLine:
|
|
3110
|
-
reviewPending:
|
|
3111
|
-
dueAt:
|
|
3174
|
+
progressLine: z12.string().nullable().optional(),
|
|
3175
|
+
reviewPending: z12.boolean().default(false),
|
|
3176
|
+
dueAt: z12.string().nullable().default(null),
|
|
3112
3177
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
3113
3178
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
3114
|
-
finishedAt:
|
|
3179
|
+
finishedAt: z12.string().nullable().optional(),
|
|
3115
3180
|
/** The Goal this one was opened under; null at the root. */
|
|
3116
|
-
parentGoalId:
|
|
3181
|
+
parentGoalId: z12.string().nullable().default(null),
|
|
3117
3182
|
/** Goals opened under this one — only those the same list holds. */
|
|
3118
|
-
childGoalIds:
|
|
3183
|
+
childGoalIds: z12.array(z12.string()).default([]),
|
|
3119
3184
|
/** Goals this one waits on (start or finish gates). */
|
|
3120
|
-
dependencyGoalIds:
|
|
3185
|
+
dependencyGoalIds: z12.array(z12.string()).default([]),
|
|
3121
3186
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
3122
|
-
blocked:
|
|
3187
|
+
blocked: z12.boolean().default(false),
|
|
3123
3188
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
3124
3189
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
3125
3190
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
3126
3191
|
* question is opened. */
|
|
3127
|
-
questions:
|
|
3192
|
+
questions: z12.array(QueueQuestionSchema).default([]),
|
|
3128
3193
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
3129
3194
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
3130
3195
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
3131
3196
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
3132
|
-
asked:
|
|
3133
|
-
answered:
|
|
3197
|
+
asked: z12.number().optional(),
|
|
3198
|
+
answered: z12.number().optional(),
|
|
3134
3199
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
3135
3200
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
3136
3201
|
* not spell an empty list. */
|
|
3137
|
-
replies:
|
|
3202
|
+
replies: z12.array(QueueReplySchema).optional(),
|
|
3138
3203
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
3139
|
-
repo:
|
|
3140
|
-
createdAt:
|
|
3141
|
-
updatedAt:
|
|
3204
|
+
repo: z12.string().nullable().optional(),
|
|
3205
|
+
createdAt: z12.string(),
|
|
3206
|
+
updatedAt: z12.string().nullable().default(null),
|
|
3142
3207
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
3143
3208
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
3144
3209
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
3145
|
-
lastProgressAt:
|
|
3210
|
+
lastProgressAt: z12.string().nullable().optional(),
|
|
3146
3211
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
3147
3212
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
3148
3213
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
@@ -3161,7 +3226,7 @@ var QueueItemSchema = z11.object({
|
|
|
3161
3226
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
3162
3227
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
3163
3228
|
* Entry with no `about_id` is in neither. */
|
|
3164
|
-
lastPersonAt:
|
|
3229
|
+
lastPersonAt: z12.string().nullable().optional(),
|
|
3165
3230
|
/** EACH SIDE'S NEWEST WORD IN THE LAST DAY (owner, 2026-10-13, the Work tab's order): the person's
|
|
3166
3231
|
* (`yours`) and the agents' (`theirs`), each null when that side wrote nothing on it in 24 hours.
|
|
3167
3232
|
* `latest` holds one Entry, so whichever side wrote first is hidden there; Work orders a Goal by
|
|
@@ -3179,40 +3244,40 @@ var QueueItemSchema = z11.object({
|
|
|
3179
3244
|
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
3180
3245
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
3181
3246
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
3182
|
-
rev:
|
|
3247
|
+
rev: z12.string().optional()
|
|
3183
3248
|
});
|
|
3184
|
-
var QueueDeltaSchema =
|
|
3185
|
-
ids:
|
|
3186
|
-
items:
|
|
3249
|
+
var QueueDeltaSchema = z12.object({
|
|
3250
|
+
ids: z12.array(z12.string()),
|
|
3251
|
+
items: z12.array(QueueItemSchema)
|
|
3187
3252
|
});
|
|
3188
3253
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
3189
|
-
var NoteSourceSchema =
|
|
3190
|
-
var NoteStatusSchema =
|
|
3191
|
-
var NoteRepeatSchema =
|
|
3192
|
-
var DecisionSchema =
|
|
3193
|
-
id:
|
|
3254
|
+
var NoteSourceSchema = z12.enum(["app", "call"]);
|
|
3255
|
+
var NoteStatusSchema = z12.enum(["open", "assigned", "in_progress", "done"]);
|
|
3256
|
+
var NoteRepeatSchema = z12.enum(["once", "until_done"]);
|
|
3257
|
+
var DecisionSchema = z12.object({
|
|
3258
|
+
id: z12.string(),
|
|
3194
3259
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
3195
3260
|
* extensibility seam — any conversation can accrue decisions). */
|
|
3196
|
-
noteId:
|
|
3261
|
+
noteId: z12.string().nullable(),
|
|
3197
3262
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
3198
|
-
question:
|
|
3263
|
+
question: z12.string(),
|
|
3199
3264
|
/** The user's ruling; null while the question is open. */
|
|
3200
|
-
answer:
|
|
3201
|
-
decidedAt:
|
|
3202
|
-
createdAt:
|
|
3265
|
+
answer: z12.string().nullable(),
|
|
3266
|
+
decidedAt: z12.string().nullable(),
|
|
3267
|
+
createdAt: z12.string()
|
|
3203
3268
|
});
|
|
3204
|
-
var NoteSchema =
|
|
3205
|
-
id:
|
|
3269
|
+
var NoteSchema = z12.object({
|
|
3270
|
+
id: z12.string(),
|
|
3206
3271
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
3207
|
-
title:
|
|
3272
|
+
title: z12.string(),
|
|
3208
3273
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
3209
|
-
intent:
|
|
3274
|
+
intent: z12.string(),
|
|
3210
3275
|
source: NoteSourceSchema,
|
|
3211
3276
|
status: NoteStatusSchema,
|
|
3212
3277
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
3213
|
-
assignee:
|
|
3278
|
+
assignee: z12.string().nullable(),
|
|
3214
3279
|
/** The request thread minted at assignment; null until assigned. */
|
|
3215
|
-
parentId:
|
|
3280
|
+
parentId: z12.string().nullable(),
|
|
3216
3281
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3217
3282
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
3218
3283
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -3221,102 +3286,102 @@ var NoteSchema = z11.object({
|
|
|
3221
3286
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3222
3287
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
3223
3288
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
3224
|
-
dueAt:
|
|
3289
|
+
dueAt: z12.string().nullable().default(null),
|
|
3225
3290
|
repeat: NoteRepeatSchema.default("once"),
|
|
3226
3291
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
3227
|
-
rides:
|
|
3228
|
-
lastRideAt:
|
|
3229
|
-
createdAt:
|
|
3292
|
+
rides: z12.number().int().default(0),
|
|
3293
|
+
lastRideAt: z12.string().nullable().default(null),
|
|
3294
|
+
createdAt: z12.string()
|
|
3230
3295
|
});
|
|
3231
|
-
var DeliveryModeSchema =
|
|
3296
|
+
var DeliveryModeSchema = z12.enum(["poll", "self_hosted"]);
|
|
3232
3297
|
var WAKE_EVENT = "wake";
|
|
3233
3298
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
3234
|
-
var RegisterDeliverySchema =
|
|
3235
|
-
var OAuthStartSchema =
|
|
3236
|
-
provider:
|
|
3237
|
-
returnTo:
|
|
3299
|
+
var RegisterDeliverySchema = z12.object({ mode: DeliveryModeSchema });
|
|
3300
|
+
var OAuthStartSchema = z12.object({
|
|
3301
|
+
provider: z12.enum(["cma"]),
|
|
3302
|
+
returnTo: z12.string().min(1)
|
|
3238
3303
|
});
|
|
3239
|
-
var DeliveryConfigSchema =
|
|
3240
|
-
tokenId:
|
|
3304
|
+
var DeliveryConfigSchema = z12.object({
|
|
3305
|
+
tokenId: z12.string(),
|
|
3241
3306
|
mode: DeliveryModeSchema,
|
|
3242
3307
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
3243
3308
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
3244
3309
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
3245
|
-
realtime:
|
|
3310
|
+
realtime: z12.object({ url: z12.string(), anonKey: z12.string() }).nullable()
|
|
3246
3311
|
});
|
|
3247
|
-
var HostDecisionSchema =
|
|
3312
|
+
var HostDecisionSchema = z12.object({
|
|
3248
3313
|
/** The agent's token id: the row's `recipient`. */
|
|
3249
|
-
agent:
|
|
3250
|
-
decision:
|
|
3314
|
+
agent: z12.string().uuid(),
|
|
3315
|
+
decision: z12.enum(["stood_back", "took_over"]),
|
|
3251
3316
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
3252
|
-
goalId:
|
|
3317
|
+
goalId: z12.string().uuid().nullable().optional(),
|
|
3253
3318
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
3254
|
-
seenAt:
|
|
3319
|
+
seenAt: z12.string().datetime().nullable().optional(),
|
|
3255
3320
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
3256
|
-
since:
|
|
3321
|
+
since: z12.string().datetime().nullable().optional(),
|
|
3257
3322
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
3258
|
-
said:
|
|
3323
|
+
said: z12.string().max(300).optional()
|
|
3259
3324
|
});
|
|
3260
|
-
var WakeNudgeSchema =
|
|
3261
|
-
kind:
|
|
3262
|
-
notificationId:
|
|
3263
|
-
parentId:
|
|
3325
|
+
var WakeNudgeSchema = z12.object({
|
|
3326
|
+
kind: z12.enum(["reply", "request", "callback"]),
|
|
3327
|
+
notificationId: z12.string().optional(),
|
|
3328
|
+
parentId: z12.string()
|
|
3264
3329
|
});
|
|
3265
|
-
var PairingStatusSchema =
|
|
3266
|
-
var DeviceCodeSchema =
|
|
3267
|
-
device_code:
|
|
3268
|
-
user_code:
|
|
3269
|
-
verification_uri:
|
|
3270
|
-
verification_uri_complete:
|
|
3271
|
-
interval:
|
|
3272
|
-
expires_in:
|
|
3330
|
+
var PairingStatusSchema = z12.enum(["pending", "approved", "denied", "expired"]);
|
|
3331
|
+
var DeviceCodeSchema = z12.object({
|
|
3332
|
+
device_code: z12.string(),
|
|
3333
|
+
user_code: z12.string(),
|
|
3334
|
+
verification_uri: z12.string().url(),
|
|
3335
|
+
verification_uri_complete: z12.string().url(),
|
|
3336
|
+
interval: z12.number(),
|
|
3337
|
+
expires_in: z12.number()
|
|
3273
3338
|
});
|
|
3274
|
-
var DeviceInfoSchema =
|
|
3275
|
-
code:
|
|
3339
|
+
var DeviceInfoSchema = z12.object({
|
|
3340
|
+
code: z12.string(),
|
|
3276
3341
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3277
3342
|
* pre-filling the name field the human can edit. */
|
|
3278
|
-
name:
|
|
3343
|
+
name: z12.string(),
|
|
3279
3344
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3280
3345
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3281
3346
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3282
3347
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3283
|
-
agent:
|
|
3284
|
-
device:
|
|
3348
|
+
agent: z12.string().optional(),
|
|
3349
|
+
device: z12.string().nullable(),
|
|
3285
3350
|
status: PairingStatusSchema
|
|
3286
3351
|
});
|
|
3287
|
-
var DeviceTokenSchema =
|
|
3288
|
-
access_token:
|
|
3352
|
+
var DeviceTokenSchema = z12.object({
|
|
3353
|
+
access_token: z12.string(),
|
|
3289
3354
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3290
3355
|
* a default silly name). */
|
|
3291
|
-
name:
|
|
3292
|
-
device:
|
|
3356
|
+
name: z12.string(),
|
|
3357
|
+
device: z12.string().nullable(),
|
|
3293
3358
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3294
3359
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3295
|
-
voice:
|
|
3360
|
+
voice: z12.string().nullable().optional(),
|
|
3296
3361
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3297
3362
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3298
|
-
token_id:
|
|
3363
|
+
token_id: z12.string().nullable().optional(),
|
|
3299
3364
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3300
3365
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3301
3366
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3302
3367
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3303
|
-
workspace:
|
|
3368
|
+
workspace: z12.string().nullable().optional(),
|
|
3304
3369
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
3305
|
-
harness:
|
|
3306
|
-
session_id:
|
|
3370
|
+
harness: z12.enum(["claude", "codex", "agy"]).optional(),
|
|
3371
|
+
session_id: z12.string().uuid().optional(),
|
|
3307
3372
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
3308
3373
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
3309
3374
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
3310
3375
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
3311
|
-
full_session:
|
|
3312
|
-
uik_pub:
|
|
3376
|
+
full_session: z12.string().optional(),
|
|
3377
|
+
uik_pub: z12.string().nullable().optional()
|
|
3313
3378
|
});
|
|
3314
|
-
var SupportRequestSchema =
|
|
3315
|
-
email:
|
|
3316
|
-
message:
|
|
3317
|
-
name:
|
|
3379
|
+
var SupportRequestSchema = z12.object({
|
|
3380
|
+
email: z12.string().email().max(320),
|
|
3381
|
+
message: z12.string().trim().min(1).max(5e3),
|
|
3382
|
+
name: z12.string().trim().max(120).optional()
|
|
3318
3383
|
});
|
|
3319
|
-
var NotificationFeedbackKindSchema =
|
|
3384
|
+
var NotificationFeedbackKindSchema = z12.enum([
|
|
3320
3385
|
"break_down",
|
|
3321
3386
|
// "This should be more than one ask — break it down."
|
|
3322
3387
|
"regenerate_options",
|
|
@@ -3331,131 +3396,131 @@ var NotificationFeedbackKindSchema = z11.enum([
|
|
|
3331
3396
|
// anything else — the note carries it.
|
|
3332
3397
|
]);
|
|
3333
3398
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
3334
|
-
var QuestionRowSchema =
|
|
3399
|
+
var QuestionRowSchema = z12.object({
|
|
3335
3400
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
3336
|
-
id:
|
|
3337
|
-
deliveryId:
|
|
3338
|
-
entryId:
|
|
3401
|
+
id: z12.string(),
|
|
3402
|
+
deliveryId: z12.string(),
|
|
3403
|
+
entryId: z12.string(),
|
|
3339
3404
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
3340
|
-
needId:
|
|
3341
|
-
goalIds:
|
|
3405
|
+
needId: z12.string().nullable(),
|
|
3406
|
+
goalIds: z12.array(z12.string()),
|
|
3342
3407
|
/** The name of the work it is about, when the read could word it. */
|
|
3343
|
-
goalTitle:
|
|
3344
|
-
tokenId:
|
|
3345
|
-
name:
|
|
3346
|
-
title:
|
|
3347
|
-
body:
|
|
3348
|
-
select:
|
|
3349
|
-
options:
|
|
3350
|
-
hasPreview:
|
|
3351
|
-
blocking:
|
|
3352
|
-
askedAt:
|
|
3408
|
+
goalTitle: z12.string().optional(),
|
|
3409
|
+
tokenId: z12.string().optional(),
|
|
3410
|
+
name: z12.string(),
|
|
3411
|
+
title: z12.string(),
|
|
3412
|
+
body: z12.string(),
|
|
3413
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]),
|
|
3414
|
+
options: z12.array(SlimOptionSchema),
|
|
3415
|
+
hasPreview: z12.boolean(),
|
|
3416
|
+
blocking: z12.boolean(),
|
|
3417
|
+
askedAt: z12.string().datetime(),
|
|
3353
3418
|
ring: InboxItemSchema.shape.ring,
|
|
3354
|
-
onCall:
|
|
3355
|
-
sealed:
|
|
3419
|
+
onCall: z12.literal(true).optional(),
|
|
3420
|
+
sealed: z12.boolean()
|
|
3356
3421
|
});
|
|
3357
|
-
var WorkStateSchema =
|
|
3358
|
-
var WorkRowSchema =
|
|
3359
|
-
id:
|
|
3360
|
-
parentId:
|
|
3361
|
-
title:
|
|
3422
|
+
var WorkStateSchema = z12.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
3423
|
+
var WorkRowSchema = z12.object({
|
|
3424
|
+
id: z12.string(),
|
|
3425
|
+
parentId: z12.string().nullable(),
|
|
3426
|
+
title: z12.string(),
|
|
3362
3427
|
/** Straight off the Goal. */
|
|
3363
3428
|
state: WorkStateSchema,
|
|
3364
|
-
owner:
|
|
3365
|
-
revision:
|
|
3429
|
+
owner: z12.string().nullable(),
|
|
3430
|
+
revision: z12.number().int(),
|
|
3366
3431
|
/** Open questions on it, counted to 100. */
|
|
3367
|
-
waiting:
|
|
3432
|
+
waiting: z12.number().int(),
|
|
3368
3433
|
/** Held by a gate on work that is not done. */
|
|
3369
|
-
blocked:
|
|
3370
|
-
lastProgressAt:
|
|
3434
|
+
blocked: z12.boolean(),
|
|
3435
|
+
lastProgressAt: z12.string().datetime().nullable(),
|
|
3371
3436
|
/** The line written for its newest progress note, else that note's first words. */
|
|
3372
|
-
line:
|
|
3437
|
+
line: z12.string().nullable(),
|
|
3373
3438
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
3374
|
-
children:
|
|
3375
|
-
createdAt:
|
|
3376
|
-
updatedAt:
|
|
3439
|
+
children: z12.number().int(),
|
|
3440
|
+
createdAt: z12.string().datetime(),
|
|
3441
|
+
updatedAt: z12.string().datetime(),
|
|
3377
3442
|
/** When anything at or under it last moved — the order the list is in. */
|
|
3378
|
-
activeAt:
|
|
3443
|
+
activeAt: z12.string().datetime(),
|
|
3379
3444
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
3380
|
-
sealed:
|
|
3445
|
+
sealed: z12.boolean()
|
|
3381
3446
|
});
|
|
3382
3447
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
3383
3448
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
3384
3449
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
3385
|
-
asking:
|
|
3386
|
-
oldestAskAt:
|
|
3450
|
+
asking: z12.number().int().nullable(),
|
|
3451
|
+
oldestAskAt: z12.string().datetime().nullable(),
|
|
3387
3452
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
3388
3453
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
3389
3454
|
* (`agent_holds`); null when that read failed. */
|
|
3390
|
-
holds:
|
|
3391
|
-
held:
|
|
3455
|
+
holds: z12.array(z12.object({ id: z12.string(), title: z12.string() })).nullable(),
|
|
3456
|
+
held: z12.number().int().nullable(),
|
|
3392
3457
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
3393
3458
|
* while none has, or when that read failed. */
|
|
3394
|
-
cold:
|
|
3459
|
+
cold: z12.string().datetime().nullable(),
|
|
3395
3460
|
/** The newest line of its working log, and when the harness saw it. */
|
|
3396
|
-
line:
|
|
3397
|
-
lineAt:
|
|
3461
|
+
line: z12.string().nullable(),
|
|
3462
|
+
lineAt: z12.string().datetime().nullable()
|
|
3398
3463
|
});
|
|
3399
|
-
var SnapshotSchema =
|
|
3464
|
+
var SnapshotSchema = z12.object({
|
|
3400
3465
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
3401
|
-
at:
|
|
3402
|
-
questions:
|
|
3466
|
+
at: z12.string().datetime(),
|
|
3467
|
+
questions: z12.object({
|
|
3403
3468
|
/** The newest 30 open cards, questions before updates. */
|
|
3404
|
-
items:
|
|
3469
|
+
items: z12.array(QuestionRowSchema),
|
|
3405
3470
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
3406
|
-
total:
|
|
3407
|
-
updates:
|
|
3408
|
-
putOff:
|
|
3471
|
+
total: z12.number().int(),
|
|
3472
|
+
updates: z12.number().int(),
|
|
3473
|
+
putOff: z12.number().int()
|
|
3409
3474
|
}).nullable(),
|
|
3410
|
-
agents:
|
|
3475
|
+
agents: z12.object({
|
|
3411
3476
|
/** Up to 60, most recently seen first. */
|
|
3412
|
-
items:
|
|
3413
|
-
more:
|
|
3477
|
+
items: z12.array(AgentRowSchema),
|
|
3478
|
+
more: z12.boolean()
|
|
3414
3479
|
}).nullable(),
|
|
3415
|
-
work:
|
|
3480
|
+
work: z12.object({
|
|
3416
3481
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
3417
|
-
items:
|
|
3482
|
+
items: z12.array(WorkRowSchema),
|
|
3418
3483
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
3419
3484
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
3420
3485
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
3421
3486
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
3422
|
-
counts:
|
|
3487
|
+
counts: z12.object({ live: z12.number().int(), waiting: z12.number().int(), notStarted: z12.number().int(), done: z12.number().int() })
|
|
3423
3488
|
}).nullable(),
|
|
3424
|
-
you:
|
|
3489
|
+
you: z12.object({
|
|
3425
3490
|
settings: UserSettingsSchema,
|
|
3426
|
-
callable:
|
|
3491
|
+
callable: z12.boolean(),
|
|
3427
3492
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
3428
|
-
computers:
|
|
3493
|
+
computers: z12.array(ComputerRowSchema).nullable()
|
|
3429
3494
|
}).nullable()
|
|
3430
3495
|
});
|
|
3431
|
-
var CallRecapSchema =
|
|
3432
|
-
call:
|
|
3433
|
-
status:
|
|
3434
|
-
startedAt:
|
|
3435
|
-
durationMs:
|
|
3496
|
+
var CallRecapSchema = z12.object({
|
|
3497
|
+
call: z12.object({
|
|
3498
|
+
status: z12.string(),
|
|
3499
|
+
startedAt: z12.string(),
|
|
3500
|
+
durationMs: z12.number().nullable(),
|
|
3436
3501
|
/** Bot-clock pickup offset; absent before the timestamp release, null before its report lands. */
|
|
3437
|
-
pickupMs:
|
|
3438
|
-
agents:
|
|
3502
|
+
pickupMs: z12.number().nullable().optional(),
|
|
3503
|
+
agents: z12.array(z12.object({ id: z12.string(), name: z12.string().nullable() }))
|
|
3439
3504
|
}),
|
|
3440
|
-
topics:
|
|
3441
|
-
goalId:
|
|
3442
|
-
title:
|
|
3443
|
-
owner:
|
|
3444
|
-
state:
|
|
3505
|
+
topics: z12.array(z12.object({
|
|
3506
|
+
goalId: z12.string().uuid(),
|
|
3507
|
+
title: z12.string(),
|
|
3508
|
+
owner: z12.string(),
|
|
3509
|
+
state: z12.string(),
|
|
3445
3510
|
/** Presented Entry content and its author, separately from the current Goal owner. */
|
|
3446
|
-
items:
|
|
3511
|
+
items: z12.array(z12.object({ entryId: z12.string(), author: z12.string(), text: z12.string(), summary: z12.string().nullable(), acknowledged: z12.boolean().optional() })).optional(),
|
|
3447
3512
|
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
3448
3513
|
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
3449
|
-
presented:
|
|
3514
|
+
presented: z12.boolean(),
|
|
3450
3515
|
/** Whether THIS call created it — the third way a call touches work, beside carrying a request
|
|
3451
3516
|
* there and filing words there (owner, 2026-10-08, Goal `5b4b1a0a`: "Show the new work, keep my
|
|
3452
3517
|
* words under it"). Created work is never carried, so it is never `presented`; without this the
|
|
3453
3518
|
* row is dropped by the "only what happened on the call" filter. False on a database before
|
|
3454
3519
|
* 20261013080133. */
|
|
3455
|
-
created:
|
|
3520
|
+
created: z12.boolean(),
|
|
3456
3521
|
/** `request` is the Entry that asked it — what a line's `about` names when the line answered or put
|
|
3457
3522
|
* it off. Optional: an older API sent none. */
|
|
3458
|
-
questions:
|
|
3523
|
+
questions: z12.array(z12.object({ id: z12.string().uuid(), state: z12.string(), title: z12.string(), request: z12.string().optional() })),
|
|
3459
3524
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
3460
3525
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
3461
3526
|
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
@@ -3465,33 +3530,70 @@ var CallRecapSchema = z11.object({
|
|
|
3465
3530
|
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
3466
3531
|
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
3467
3532
|
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
3468
|
-
lines:
|
|
3469
|
-
entryId:
|
|
3470
|
-
words:
|
|
3471
|
-
summary:
|
|
3472
|
-
about:
|
|
3473
|
-
reply:
|
|
3474
|
-
replyKind:
|
|
3475
|
-
at:
|
|
3533
|
+
lines: z12.array(z12.object({
|
|
3534
|
+
entryId: z12.string().uuid(),
|
|
3535
|
+
words: z12.string(),
|
|
3536
|
+
summary: z12.string().optional(),
|
|
3537
|
+
about: z12.string().nullable().optional(),
|
|
3538
|
+
reply: z12.string().nullable().optional(),
|
|
3539
|
+
replyKind: z12.string().nullable().optional(),
|
|
3540
|
+
at: z12.array(z12.number()).optional()
|
|
3476
3541
|
}))
|
|
3477
3542
|
})),
|
|
3478
|
-
unfiled:
|
|
3543
|
+
unfiled: z12.array(z12.object({ lineId: z12.string().uuid(), words: z12.string(), atMs: z12.number() })),
|
|
3479
3544
|
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
3480
3545
|
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
3481
3546
|
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
3482
3547
|
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
3483
3548
|
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
3484
3549
|
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
3485
|
-
said:
|
|
3486
|
-
lineId:
|
|
3487
|
-
speaker:
|
|
3488
|
-
agent:
|
|
3489
|
-
words:
|
|
3490
|
-
atMs:
|
|
3550
|
+
said: z12.array(z12.object({
|
|
3551
|
+
lineId: z12.string().uuid(),
|
|
3552
|
+
speaker: z12.enum(["user", "agent"]),
|
|
3553
|
+
agent: z12.object({ id: z12.string().nullable(), name: z12.string().nullable() }).nullable(),
|
|
3554
|
+
words: z12.string(),
|
|
3555
|
+
atMs: z12.number()
|
|
3491
3556
|
})),
|
|
3492
|
-
more:
|
|
3557
|
+
more: z12.object({ lines: z12.number(), entries: z12.number(), topics: z12.number() }),
|
|
3493
3558
|
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
3494
|
-
next:
|
|
3559
|
+
next: z12.number().int().nonnegative().nullable().optional(),
|
|
3560
|
+
/** THE CALL AS ONE LIST OF ROWS (owner, 2026-10-14: "we only really want a thing like transcript but
|
|
3561
|
+
* built from the summaries", then "the transcript tabs should show the same but with full words").
|
|
3562
|
+
* Both tabs draw these rows, in this order: Summary from `summary`, Transcript from `words`.
|
|
3563
|
+
* - `put`: an item put to the person, at the first agent line that voiced it. Paigy's sentences
|
|
3564
|
+
* that voiced it are folded into `spoken`, never rows of their own.
|
|
3565
|
+
* - `said`: the person's words, one row per reply or per filed line no reply covers.
|
|
3566
|
+
* - `did`: what Paigy did ("Sent to Mouth: …", "Recorded: …", "Filed on …").
|
|
3567
|
+
* `by` is the row's real author, never inferred from `kind`: a relay Paigy sent to an agent is
|
|
3568
|
+
* Paigy's, not the person's. Optional and additive (owner: "make sure whatever you do is backward
|
|
3569
|
+
* compatible"): an API before it sends none, and the screen falls back to `topics` and `said`. */
|
|
3570
|
+
timeline: z12.array(z12.object({
|
|
3571
|
+
id: z12.string(),
|
|
3572
|
+
position: z12.number(),
|
|
3573
|
+
atMs: z12.number(),
|
|
3574
|
+
kind: z12.enum(["put", "said", "did"]),
|
|
3575
|
+
by: z12.object({ participant: z12.string().nullable(), agentId: z12.string().nullable(), name: z12.string().nullable() }),
|
|
3576
|
+
goalId: z12.string().uuid().nullable(),
|
|
3577
|
+
requestId: z12.string().nullable(),
|
|
3578
|
+
summary: z12.string().nullable(),
|
|
3579
|
+
words: z12.string(),
|
|
3580
|
+
/** Paigy's sentences folded into this row: on a `put` row, what it said putting the item to the
|
|
3581
|
+
* person (before their next row); on a `said` row, what it said in reply (until their next row).
|
|
3582
|
+
* `atMs` is when each was said; optional, absent before 2026-10-14's fix. */
|
|
3583
|
+
spoken: z12.array(z12.object({ lineId: z12.string(), words: z12.string(), atMs: z12.number().optional() })),
|
|
3584
|
+
/** On a `said` row: the work these words were filed on, and whether they answered (recorded) a
|
|
3585
|
+
* question. They replace the "Filed on ..." and "Recorded: ..." rows that repeated the person's own
|
|
3586
|
+
* summary (Brain, 2026-10-14). Optional: absent from an API before it. */
|
|
3587
|
+
filedOn: z12.array(z12.object({ goalId: z12.string().uuid(), title: z12.string() })).optional(),
|
|
3588
|
+
recorded: z12.boolean().optional(),
|
|
3589
|
+
settled: z12.object({ requestId: z12.string(), answer: z12.string() }).nullable().optional(),
|
|
3590
|
+
acknowledged: z12.boolean().optional(),
|
|
3591
|
+
/** On a `put` row: still waiting for the person's answer (not settled, not answered, not put off).
|
|
3592
|
+
* The server's judgement; the screen draws "Still waiting for your answer" from it. Optional: absent
|
|
3593
|
+
* from an API before it, where the screen falls back to its own reading. */
|
|
3594
|
+
waiting: z12.boolean().optional(),
|
|
3595
|
+
lineIds: z12.array(z12.string())
|
|
3596
|
+
})).optional()
|
|
3495
3597
|
});
|
|
3496
3598
|
function sessionSlot(sessionId2) {
|
|
3497
3599
|
const id2 = sessionId2 ?? sessionId();
|
|
@@ -3598,6 +3700,14 @@ async function requestCode(suggestedName = agentName(), device) {
|
|
|
3598
3700
|
if (!res.ok) throw new Error(`/api/device/code failed: ${res.status} ${await res.text()}`);
|
|
3599
3701
|
return await res.json();
|
|
3600
3702
|
}
|
|
3703
|
+
var ServerFault = class extends Error {
|
|
3704
|
+
constructor(status) {
|
|
3705
|
+
super(`Paigy's server failed (HTTP ${status}) while checking the code.`);
|
|
3706
|
+
this.status = status;
|
|
3707
|
+
this.name = "ServerFault";
|
|
3708
|
+
}
|
|
3709
|
+
status;
|
|
3710
|
+
};
|
|
3601
3711
|
async function pollToken(deviceCode) {
|
|
3602
3712
|
const res = await reach(`${BACKEND_URL}/api/device/token`, {
|
|
3603
3713
|
method: "POST",
|
|
@@ -3605,6 +3715,7 @@ async function pollToken(deviceCode) {
|
|
|
3605
3715
|
body: JSON.stringify({ device_code: deviceCode })
|
|
3606
3716
|
});
|
|
3607
3717
|
if (res.ok) return await res.json();
|
|
3718
|
+
if (res.status >= 500) throw new ServerFault(res.status);
|
|
3608
3719
|
const body = await res.json().catch(() => ({ error: "unknown" }));
|
|
3609
3720
|
const err = body.error ?? "unknown";
|
|
3610
3721
|
if (err === "authorization_pending") return null;
|
|
@@ -3966,6 +4077,9 @@ async function receiveContact(input, opts = {}) {
|
|
|
3966
4077
|
throw error;
|
|
3967
4078
|
}
|
|
3968
4079
|
}
|
|
4080
|
+
function viaLine(via) {
|
|
4081
|
+
return via ? `a card they shared by link${via.decidedBy ? `, decided by ${via.decidedBy}` : ""}` : void 0;
|
|
4082
|
+
}
|
|
3969
4083
|
function compact(o) {
|
|
3970
4084
|
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
|
|
3971
4085
|
}
|
|
@@ -3989,7 +4103,8 @@ function decided(result) {
|
|
|
3989
4103
|
return void 0;
|
|
3990
4104
|
}
|
|
3991
4105
|
}
|
|
3992
|
-
function stateOf(state, answer, decided2) {
|
|
4106
|
+
function stateOf(state, answer, decided2, withdrawn) {
|
|
4107
|
+
if (withdrawn && state === "answered") return "closed";
|
|
3993
4108
|
return state === "answered" && answer && decided2 === void 0 ? "replied" : state;
|
|
3994
4109
|
}
|
|
3995
4110
|
var handle = (entryId) => entryId.slice(0, 8);
|
|
@@ -4018,10 +4133,11 @@ function conversation(e) {
|
|
|
4018
4133
|
at: at(entry.createdAt),
|
|
4019
4134
|
said: sealed ? "[encrypted]" : entryWords(entry).trim(),
|
|
4020
4135
|
caveat: entry.callOngoing ? "Call ongoing; what the person decides may change." : void 0,
|
|
4136
|
+
via: viaLine(entry.via),
|
|
4021
4137
|
units: askUnits(entry, e.entries ?? []).map((u) => `${u.title}: ${u.body}`),
|
|
4022
4138
|
re: re ? handle(re) : void 0,
|
|
4023
4139
|
options,
|
|
4024
|
-
decision: need ? compact({ state: stateOf(need.state, answer, decision), answer: decision }) : void 0
|
|
4140
|
+
decision: need ? compact({ state: stateOf(need.state, answer, decision, need.withdrawn), answer: decision, via: viaLine(answer?.via) }) : void 0
|
|
4025
4141
|
});
|
|
4026
4142
|
});
|
|
4027
4143
|
}
|
|
@@ -4189,7 +4305,7 @@ function receivedView(r, now = Date.now()) {
|
|
|
4189
4305
|
...rings.map((r2) => rerings(r2, now)),
|
|
4190
4306
|
...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): read one with get_goals({goalIds:[goalId]}); your first write to it starts it.`] : [],
|
|
4191
4307
|
...w.claimable && !assigned.length ? [`Next waiting on you: ${w.claimable.title ?? w.claimable.goalId} (get_goals({ goalIds: ["${w.claimable.goalId}"] })).`] : [],
|
|
4192
|
-
...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
|
|
4308
|
+
...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, cancel or defer it with manage_goals.`] : [],
|
|
4193
4309
|
// The COUNT, oldest three, and where to read them (owner, 2026-10-08): it was 34 rows of
|
|
4194
4310
|
// other agents' work on every window, ahead of this agent's own next step.
|
|
4195
4311
|
...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_goals({goalIds:[goalId]}) reads one in full. A write to it puts you on it without changing its owner.`] : [],
|
|
@@ -4534,6 +4650,7 @@ export {
|
|
|
4534
4650
|
deleteToken,
|
|
4535
4651
|
revokeToken,
|
|
4536
4652
|
requestCode,
|
|
4653
|
+
ServerFault,
|
|
4537
4654
|
pollToken,
|
|
4538
4655
|
UnpairedError,
|
|
4539
4656
|
RevokedError,
|