@paigy/mcp 0.40.27 → 0.40.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UQDA7X3W.js → chunk-37NWW6N5.js} +1 -1
- package/dist/{chunk-4C6SDQ7A.js → chunk-5BSMUS7B.js} +634 -496
- package/dist/{chunk-X3GTDW5G.js → chunk-AWI252HQ.js} +615 -494
- package/dist/{chunk-A5FYZK6P.js → chunk-YY2AKPYC.js} +2 -2
- package/dist/{dist-N6ZJUJV7.js → dist-JPETGYUX.js} +1 -1
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +2 -2
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { homedir } from "os";
|
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9
9
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
10
|
-
import { z as
|
|
10
|
+
import { z as z9 } from "zod";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { z as z3 } from "zod";
|
|
13
13
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
@@ -17,6 +17,8 @@ import { z as z2 } from "zod";
|
|
|
17
17
|
import { z as z4 } from "zod";
|
|
18
18
|
import { z as z5 } from "zod";
|
|
19
19
|
import { z as z6 } from "zod";
|
|
20
|
+
import { z as z7 } from "zod";
|
|
21
|
+
import { z as z8 } from "zod";
|
|
20
22
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
21
23
|
import { homedir as homedir2 } from "os";
|
|
22
24
|
import { join as join2 } from "path";
|
|
@@ -1314,10 +1316,12 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
1314
1316
|
};
|
|
1315
1317
|
var OPTIONS_MIN = 2;
|
|
1316
1318
|
var OPTIONS_MAX = 6;
|
|
1319
|
+
var OPTION_LABEL_MAX = 50;
|
|
1320
|
+
var OPTION_HINT_MAX = 200;
|
|
1317
1321
|
var OptionSchema = z.object({
|
|
1318
1322
|
id: z.string(),
|
|
1319
1323
|
label: z.string(),
|
|
1320
|
-
hint: z.string().max(500).describe(
|
|
1324
|
+
hint: z.string().max(500).describe(`Optional: what choosing it does, at most ${OPTION_HINT_MAX} characters (e.g. 'Reruns test suite', 'Merges to main').`).optional(),
|
|
1321
1325
|
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
1322
1326
|
// the constraints below are what an agent reads when deciding to use these.
|
|
1323
1327
|
html: z.string().max(16384).describe(
|
|
@@ -1331,8 +1335,10 @@ var OptionInputSchema = z.object({
|
|
|
1331
1335
|
id: z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_.:-]+$/).optional().describe(
|
|
1332
1336
|
`Optional: this option's own id, which answers name it by (selectedOptionIds). Give every option one, or none: without them the options are numbered "1", "2", \u2026 in order.`
|
|
1333
1337
|
),
|
|
1334
|
-
|
|
1335
|
-
hint
|
|
1338
|
+
// SHORT CHOICES (owner, 2026-10-07: "options label should be maybe max 50 characters and the hint 200"):
|
|
1339
|
+
// a label is one choice the person reads, or hears on a call; a hint says its consequence in a line.
|
|
1340
|
+
label: z.string().trim().min(1).max(OPTION_LABEL_MAX, `option_label_too_long: an option's label is at most ${OPTION_LABEL_MAX} characters; say the choice, and put its consequence in the hint.`).describe(`The choice, at most ${OPTION_LABEL_MAX} characters, standing on its own.`),
|
|
1341
|
+
hint: z.string().max(OPTION_HINT_MAX, `option_hint_too_long: an option's hint is at most ${OPTION_HINT_MAX} characters.`).describe(`Optional: what choosing it does, at most ${OPTION_HINT_MAX} characters (e.g. 'Reruns test suite', 'Merges to main').`).optional(),
|
|
1336
1342
|
htmlPreview: OptionSchema.shape.html,
|
|
1337
1343
|
imgUrl: OptionSchema.shape.image
|
|
1338
1344
|
}).strict();
|
|
@@ -1602,15 +1608,48 @@ var UpdateInputSchema = z2.object({
|
|
|
1602
1608
|
message: z2.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
1603
1609
|
goalIds: z2.array(z2.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
1604
1610
|
units,
|
|
1611
|
+
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
1612
|
+
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
1613
|
+
// still waits to be acknowledged, and it carries the ONE tap that closes it. Never a decision: the
|
|
1614
|
+
// tap lands as a contribution, the person's own words on the Goal, because there is no Question
|
|
1615
|
+
// for it to answer.
|
|
1616
|
+
//
|
|
1617
|
+
// AND ITS WORDS ARE WRITTEN FOR THIS REPORT (owner, 2026-10-08, Goal 3c12d3c6: "Agent
|
|
1618
|
+
// acknowledgments vary (e.g. 'sounds good, I will test later') instead of always 'got it'"). `Got
|
|
1619
|
+
// it` and `Noted` say only that a card was cleared, where "Sounds good, I'll test later" says the
|
|
1620
|
+
// work was RECEIVED and not verified -- the distinction a session spent an hour recovering from a
|
|
1621
|
+
// call transcript on 2026-10-07, having read "not quite" as a defect report when it meant *not
|
|
1622
|
+
// tested yet*. The words are worth writing because the agent reads them back.
|
|
1623
|
+
//
|
|
1624
|
+
// ONE STRING, NOT A LIST (owner, 2026-10-08 15:47, Goal 31a1e2ac, asked whether an agent should
|
|
1625
|
+
// write up to four of them: "just on the cards, but it should be more like just they should only
|
|
1626
|
+
// get one basically string value that they can enter that replaces got it"). A report has exactly
|
|
1627
|
+
// one way to close, so the only thing an agent writes is what that button SAYS. #3260's 1..6
|
|
1628
|
+
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
1629
|
+
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
1630
|
+
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
1631
|
+
reply: z2.string().trim().min(1).describe(
|
|
1632
|
+
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer. Left out, the button says "Got it", as every card did.`
|
|
1633
|
+
).optional(),
|
|
1605
1634
|
userExplicitlyRequested
|
|
1606
|
-
}).strict().superRefine((u, ctx) =>
|
|
1635
|
+
}).strict().superRefine((u, ctx) => {
|
|
1636
|
+
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
1637
|
+
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
1638
|
+
ctx.addIssue({
|
|
1639
|
+
code: z2.ZodIssueCode.custom,
|
|
1640
|
+
path: ["reply"],
|
|
1641
|
+
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX },
|
|
1642
|
+
message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX}. It is a button's words, so say it in a breath.`
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1607
1646
|
var questionFields = {
|
|
1608
1647
|
question: z2.string().trim().min(1).describe(
|
|
1609
1648
|
`ONE question, in at most ${ASK_MAX} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
1610
1649
|
),
|
|
1611
1650
|
goalId: z2.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
1612
1651
|
units,
|
|
1613
|
-
options: z2.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own."),
|
|
1652
|
+
options: z2.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."),
|
|
1614
1653
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
1615
1654
|
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
1616
1655
|
pickMode: z2.enum(["one", "many", "rank"]).optional().describe(
|
|
@@ -1687,6 +1726,8 @@ QUESTIONS: one question per object; several questions are several objects in one
|
|
|
1687
1726
|
|
|
1688
1727
|
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
1689
1728
|
|
|
1729
|
+
GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. It is what their button says instead of "Got it", and what they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Nothing is owed on it either way, and a report that writes none offers "Got it".
|
|
1730
|
+
|
|
1690
1731
|
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
1691
1732
|
|
|
1692
1733
|
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
@@ -1730,7 +1771,8 @@ var UpdateGoalSchema = z3.object({
|
|
|
1730
1771
|
}).strict();
|
|
1731
1772
|
var changedGoal = z3.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
1732
1773
|
var goalTitle = z3.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
1733
|
-
var
|
|
1774
|
+
var GOAL_OUTCOME_MAX = 500;
|
|
1775
|
+
var goalOutcome = z3.string().trim().min(1).max(GOAL_OUTCOME_MAX, `outcome_too_long: a Goal's outcome is one paragraph, at most ${GOAL_OUTCOME_MAX} characters; split the rest into child Goals (create with parentGoalId).`).describe(`The desired result, one paragraph of at most ${GOAL_OUTCOME_MAX} characters. More than that is more work: create child Goals under it (parentGoalId). There is no third description field.`);
|
|
1734
1776
|
var CreateChange = z3.object({
|
|
1735
1777
|
kind: z3.literal("create"),
|
|
1736
1778
|
title: goalTitle,
|
|
@@ -1756,7 +1798,7 @@ var DependencyChange = z3.object({
|
|
|
1756
1798
|
goalId: changedGoal,
|
|
1757
1799
|
dependsOnGoalId: z3.string().uuid().describe("The Goal it waits on."),
|
|
1758
1800
|
action: z3.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
1759
|
-
reason: z3.string().trim().min(1).max(
|
|
1801
|
+
reason: z3.string().trim().min(1).max(250).optional().describe("Why, at most 250 characters, as an explanation, never as policy.")
|
|
1760
1802
|
}).strict();
|
|
1761
1803
|
var read = { revision: z3.number().int().positive().optional() };
|
|
1762
1804
|
var GoalChangeSchema = z3.discriminatedUnion("kind", [
|
|
@@ -1780,7 +1822,7 @@ var ManageGoalsSchema = z3.object({
|
|
|
1780
1822
|
var ManageGoalsToolSchema = z3.object({
|
|
1781
1823
|
changes: z3.array(z3.discriminatedUnion("kind", [CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
1782
1824
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1783
|
-
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls; edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
1825
|
+
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goal) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
1784
1826
|
var GetGoalSchema = z3.object({
|
|
1785
1827
|
goalId: z3.string().uuid(),
|
|
1786
1828
|
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
@@ -1798,7 +1840,7 @@ var SearchToolSchema = z3.object({
|
|
|
1798
1840
|
goalId: z3.string().uuid().optional(),
|
|
1799
1841
|
limit: z3.number().int().min(1).max(20).optional()
|
|
1800
1842
|
}).strict();
|
|
1801
|
-
var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and
|
|
1843
|
+
var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and replies -- the person's answers, acknowledgements and deferrals (type answer; found by their summary, by the question or report they reply to, or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, the replies to its items, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (a reply carries its kind -- answered, acknowledged or deferred -- whether it is closed (a deferral is not), its summary in under ten words, its question or report, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
1802
1844
|
var CheckActivitySchema = z3.object({}).strict();
|
|
1803
1845
|
var FEEDBACK_TEXT_MAX = 5e4;
|
|
1804
1846
|
var SendFeedbackSchema = z3.object({
|
|
@@ -1861,6 +1903,7 @@ var QuestionChangeSchema = z4.union([
|
|
|
1861
1903
|
z4.object({
|
|
1862
1904
|
kind: z4.literal("create"),
|
|
1863
1905
|
text: z4.string().min(1),
|
|
1906
|
+
summary: z4.string().optional(),
|
|
1864
1907
|
answererId: participant,
|
|
1865
1908
|
goal: ResultRefSchema.optional(),
|
|
1866
1909
|
blocks: z4.array(z4.object({ question: ResultRefSchema }).strict()),
|
|
@@ -1911,12 +1954,12 @@ var BrainSearchSchema = z4.object({
|
|
|
1911
1954
|
}).strict();
|
|
1912
1955
|
var BrainResultSchema = z4.object({
|
|
1913
1956
|
messages: z4.array(BrainMessageSchema),
|
|
1914
|
-
entries: z4.array(z4.object({ key, sources: z4.array(SourceSchema).min(1), goals: z4.array(ResultRefSchema) }).strict()),
|
|
1957
|
+
entries: z4.array(z4.object({ key, sources: z4.array(SourceSchema).min(1), goals: z4.array(ResultRefSchema), summary: z4.string().optional() }).strict()),
|
|
1915
1958
|
questions: z4.array(z4.object({ key, entryKeys: z4.array(key), change: QuestionChangeSchema }).strict()),
|
|
1916
1959
|
answers: z4.array(z4.object({
|
|
1917
1960
|
question: ResultRefSchema,
|
|
1918
1961
|
entryKeys: z4.array(key).min(1),
|
|
1919
|
-
summary: z4.string()
|
|
1962
|
+
summary: z4.string(),
|
|
1920
1963
|
selectedOptionIds: z4.array(z4.string().min(1)).optional()
|
|
1921
1964
|
}).strict()),
|
|
1922
1965
|
goals: z4.array(z4.object({ key, entryKeys: z4.array(key), change: BrainGoalChangeSchema }).strict()),
|
|
@@ -1961,12 +2004,15 @@ var TalkerReplySchema = z5.object({
|
|
|
1961
2004
|
messages: z5.array(z5.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
|
|
1962
2005
|
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
1963
2006
|
then: z5.enum(["listen", "hold", "end"]),
|
|
1964
|
-
/**
|
|
1965
|
-
|
|
1966
|
-
|
|
2007
|
+
/** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
|
|
2008
|
+
* IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
|
|
2009
|
+
* "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
|
|
2010
|
+
* end of this call), a question's handle (that question's answer) or an ISO time. */
|
|
2011
|
+
replies: z5.array(z5.object({ item: str2, options: strs2, lines: strs2, summary: str2, defer: str2 }).strict()),
|
|
2012
|
+
/** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
|
|
1967
2013
|
instruction: strs2,
|
|
1968
|
-
/** The line handles
|
|
1969
|
-
*
|
|
2014
|
+
/** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
|
|
2015
|
+
* work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
|
|
1970
2016
|
file: strs2,
|
|
1971
2017
|
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
1972
2018
|
need: z5.object({ kind: z5.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
|
|
@@ -1979,8 +2025,9 @@ var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
|
1979
2025
|
var str3 = z6.string();
|
|
1980
2026
|
var strs3 = z6.array(z6.string());
|
|
1981
2027
|
var FilerReplySchema = z6.object({
|
|
1982
|
-
/** Which Goals each new line goes on: a line's handle
|
|
1983
|
-
|
|
2028
|
+
/** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals), and what
|
|
2029
|
+
* the line says in at most ten words. */
|
|
2030
|
+
filed: z6.array(z6.object({ line: str3, goals: strs3, summary: str3 }).strict()),
|
|
1984
2031
|
goals: z6.array(z6.object({
|
|
1985
2032
|
op: z6.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
1986
2033
|
key: str3,
|
|
@@ -1999,6 +2046,7 @@ var FilerReplySchema = z6.object({
|
|
|
1999
2046
|
key: str3,
|
|
2000
2047
|
question: str3,
|
|
2001
2048
|
text: str3,
|
|
2049
|
+
summary: str3,
|
|
2002
2050
|
to: str3,
|
|
2003
2051
|
goal: str3,
|
|
2004
2052
|
options: z6.array(z6.object({ id: str3, label: str3 }).strict()),
|
|
@@ -2012,6 +2060,60 @@ var FilerReplySchema = z6.object({
|
|
|
2012
2060
|
var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2013
2061
|
zodToJsonSchema(FilerReplySchema, { $refStrategy: "none" })
|
|
2014
2062
|
);
|
|
2063
|
+
var ExplainerReplySchema = z7.object({
|
|
2064
|
+
/** The explanation, as sentences, spoken in order. */
|
|
2065
|
+
say: z7.array(z7.string()),
|
|
2066
|
+
/** Questions to an agent about gaps in what it read: the agent's handle and the question. */
|
|
2067
|
+
followUps: z7.array(z7.object({ to: z7.string(), text: z7.string() }).strict())
|
|
2068
|
+
}).strict();
|
|
2069
|
+
var EXPLAINER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2070
|
+
zodToJsonSchema(ExplainerReplySchema, { $refStrategy: "none" })
|
|
2071
|
+
);
|
|
2072
|
+
var DISTILLER_DEFECTS = [
|
|
2073
|
+
"reasked",
|
|
2074
|
+
// a question put to the person again after they answered it
|
|
2075
|
+
"unsupported",
|
|
2076
|
+
// a fact stated that nothing in the input supports
|
|
2077
|
+
"unsaid",
|
|
2078
|
+
// an agent's answer arrived during the call and was never said
|
|
2079
|
+
"skipped",
|
|
2080
|
+
// an agenda item never put to the person
|
|
2081
|
+
"unsaved",
|
|
2082
|
+
// something the person answered or asked for did not save, was refused or dropped
|
|
2083
|
+
"ignored",
|
|
2084
|
+
// the person asked or said something and was never answered (the critic's ignored_a_question)
|
|
2085
|
+
"promised",
|
|
2086
|
+
// said it did or would do something, and nothing shows it done (the critic's broken_promise)
|
|
2087
|
+
"dropped"
|
|
2088
|
+
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
2089
|
+
];
|
|
2090
|
+
var DistillerReplySchema = z8.object({
|
|
2091
|
+
defects: z8.array(z8.object({
|
|
2092
|
+
kind: z8.enum(DISTILLER_DEFECTS),
|
|
2093
|
+
/** The handles of the lines it shows in. */
|
|
2094
|
+
lines: z8.array(z8.string()),
|
|
2095
|
+
/** The handle of the record it concerns, or "". */
|
|
2096
|
+
about: z8.string(),
|
|
2097
|
+
/** One plain sentence, in words, never handles: it is what the feedback row keeps. */
|
|
2098
|
+
why: z8.string()
|
|
2099
|
+
}).strict())
|
|
2100
|
+
}).strict();
|
|
2101
|
+
var DISTILLER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2102
|
+
zodToJsonSchema(DistillerReplySchema, { $refStrategy: "none" })
|
|
2103
|
+
);
|
|
2104
|
+
var TIDY_AUDIENCES = ["talker", "filer", "agents"];
|
|
2105
|
+
var TidyReplySchema = z8.object({
|
|
2106
|
+
lessons: z8.array(z8.object({
|
|
2107
|
+
lesson: z8.string(),
|
|
2108
|
+
op: z8.enum(["keep", "revise", "withdraw"]),
|
|
2109
|
+
text: z8.string(),
|
|
2110
|
+
for: z8.enum([...TIDY_AUDIENCES, ""]),
|
|
2111
|
+
into: z8.string()
|
|
2112
|
+
}).strict())
|
|
2113
|
+
}).strict();
|
|
2114
|
+
var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2115
|
+
zodToJsonSchema(TidyReplySchema, { $refStrategy: "none" })
|
|
2116
|
+
);
|
|
2015
2117
|
function entryWords(entry) {
|
|
2016
2118
|
const content = entry.content;
|
|
2017
2119
|
if (content && "sealed" in content) return "";
|
|
@@ -2045,20 +2147,20 @@ function askUnits(request, entries) {
|
|
|
2045
2147
|
}
|
|
2046
2148
|
var LIVE_MS = 3 * 6e4;
|
|
2047
2149
|
var WORKING_MS = 60 * 6e4;
|
|
2048
|
-
var ContextSchema =
|
|
2049
|
-
title:
|
|
2050
|
-
description:
|
|
2150
|
+
var ContextSchema = z9.object({
|
|
2151
|
+
title: z9.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
2152
|
+
description: z9.array(z9.string().min(1)).describe(
|
|
2051
2153
|
"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."
|
|
2052
2154
|
),
|
|
2053
2155
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
2054
2156
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
2055
|
-
units:
|
|
2157
|
+
units: z9.array(z9.object({ title: z9.string(), body: z9.string() })).optional()
|
|
2056
2158
|
});
|
|
2057
|
-
var ParticipantSchema =
|
|
2058
|
-
kind:
|
|
2059
|
-
id:
|
|
2159
|
+
var ParticipantSchema = z9.object({
|
|
2160
|
+
kind: z9.enum(["human", "agent"]),
|
|
2161
|
+
id: z9.string()
|
|
2060
2162
|
});
|
|
2061
|
-
var TransformSchema =
|
|
2163
|
+
var TransformSchema = z9.enum([
|
|
2062
2164
|
"structure",
|
|
2063
2165
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
2064
2166
|
"request_more",
|
|
@@ -2081,13 +2183,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
|
|
|
2081
2183
|
function isPaigy(ref) {
|
|
2082
2184
|
return ref === participantRef(PAIGY_SELF);
|
|
2083
2185
|
}
|
|
2084
|
-
var VisualSchema =
|
|
2085
|
-
url:
|
|
2086
|
-
label:
|
|
2186
|
+
var VisualSchema = z9.object({
|
|
2187
|
+
url: z9.string().url(),
|
|
2188
|
+
label: z9.string().optional()
|
|
2087
2189
|
});
|
|
2088
|
-
var NotifyLevelSchema =
|
|
2089
|
-
var SelectShapeSchema =
|
|
2090
|
-
var ReceiptEventSchema =
|
|
2190
|
+
var NotifyLevelSchema = z9.enum(["inbox", "push", "banner", "call"]);
|
|
2191
|
+
var SelectShapeSchema = z9.enum(["one", "many", "rank", "confirm", "text"]);
|
|
2192
|
+
var ReceiptEventSchema = z9.enum([
|
|
2091
2193
|
"delivered",
|
|
2092
2194
|
// the bundle reached the recipient at some level
|
|
2093
2195
|
"seen",
|
|
@@ -2117,47 +2219,47 @@ var ReceiptEventSchema = z7.enum([
|
|
|
2117
2219
|
// be rewound by a writer that forgot to advance it.
|
|
2118
2220
|
"restarted"
|
|
2119
2221
|
]);
|
|
2120
|
-
var AttentionSchema =
|
|
2222
|
+
var AttentionSchema = z9.object({
|
|
2121
2223
|
urgency: NotifyLevelSchema,
|
|
2122
2224
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
2123
2225
|
select: SelectShapeSchema.nullable(),
|
|
2124
2226
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
2125
|
-
points:
|
|
2227
|
+
points: z9.array(z9.string()).nullable(),
|
|
2126
2228
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
2127
|
-
blocking:
|
|
2229
|
+
blocking: z9.boolean(),
|
|
2128
2230
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
2129
2231
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
2130
|
-
deadline:
|
|
2232
|
+
deadline: z9.string().datetime().nullable().optional()
|
|
2131
2233
|
});
|
|
2132
|
-
var NotifyRequestFields =
|
|
2234
|
+
var NotifyRequestFields = z9.object({
|
|
2133
2235
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
2134
2236
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
2135
2237
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
2136
2238
|
context: ContextSchema.optional(),
|
|
2137
|
-
options:
|
|
2239
|
+
options: z9.array(OptionSchema.omit({ id: true }).extend({ label: z9.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
2138
2240
|
"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)."
|
|
2139
2241
|
),
|
|
2140
|
-
points:
|
|
2242
|
+
points: z9.array(z9.string().min(1)).optional().describe(
|
|
2141
2243
|
"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."
|
|
2142
2244
|
),
|
|
2143
|
-
visuals:
|
|
2245
|
+
visuals: z9.array(VisualSchema).optional().describe(
|
|
2144
2246
|
"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."
|
|
2145
2247
|
),
|
|
2146
2248
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
2147
|
-
repo:
|
|
2249
|
+
repo: z9.string().optional(),
|
|
2148
2250
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
2149
|
-
branch:
|
|
2251
|
+
branch: z9.string().optional(),
|
|
2150
2252
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
2151
2253
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
2152
2254
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
2153
2255
|
* still accepts the old name from older clients. */
|
|
2154
|
-
parentId:
|
|
2256
|
+
parentId: z9.string().uuid().optional(),
|
|
2155
2257
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
2156
2258
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
2157
|
-
workId:
|
|
2259
|
+
workId: z9.string().uuid().optional(),
|
|
2158
2260
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
2159
2261
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
2160
|
-
goalId:
|
|
2262
|
+
goalId: z9.string().uuid().optional(),
|
|
2161
2263
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
2162
2264
|
"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."
|
|
2163
2265
|
),
|
|
@@ -2165,7 +2267,7 @@ var NotifyRequestFields = z7.object({
|
|
|
2165
2267
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
2166
2268
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
2167
2269
|
* says HOW. */
|
|
2168
|
-
clarifies:
|
|
2270
|
+
clarifies: z9.string().optional(),
|
|
2169
2271
|
select: SelectShapeSchema.optional().describe(
|
|
2170
2272
|
"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."
|
|
2171
2273
|
),
|
|
@@ -2179,20 +2281,20 @@ var NotifyRequestFields = z7.object({
|
|
|
2179
2281
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2180
2282
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2181
2283
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2182
|
-
ask:
|
|
2284
|
+
ask: z9.string().min(1).max(1e4).optional().describe(
|
|
2183
2285
|
'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.'
|
|
2184
2286
|
),
|
|
2185
|
-
needs:
|
|
2287
|
+
needs: z9.array(z9.string().min(1)).optional().describe(
|
|
2186
2288
|
"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."
|
|
2187
2289
|
),
|
|
2188
|
-
urgencyHint:
|
|
2290
|
+
urgencyHint: z9.enum(["whenever", "soon", "now"]).optional().describe(
|
|
2189
2291
|
"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."
|
|
2190
2292
|
),
|
|
2191
2293
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
2192
2294
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
2193
2295
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
2194
2296
|
* urgencyHint/blocking win when both are sent. */
|
|
2195
|
-
waiting:
|
|
2297
|
+
waiting: z9.enum(["none", "soft", "hard"]).optional().describe(
|
|
2196
2298
|
"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."
|
|
2197
2299
|
),
|
|
2198
2300
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -2200,98 +2302,98 @@ var NotifyRequestFields = z7.object({
|
|
|
2200
2302
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
2201
2303
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
2202
2304
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
2203
|
-
confirm:
|
|
2305
|
+
confirm: z9.boolean().optional().describe(
|
|
2204
2306
|
"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'."
|
|
2205
2307
|
),
|
|
2206
2308
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
2207
2309
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
2208
2310
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
2209
2311
|
* waiting:'hard'. */
|
|
2210
|
-
channel:
|
|
2312
|
+
channel: z9.enum(["call", "message"]).optional().describe(
|
|
2211
2313
|
"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."
|
|
2212
2314
|
),
|
|
2213
|
-
confirmStyle:
|
|
2315
|
+
confirmStyle: z9.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2214
2316
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2215
2317
|
),
|
|
2216
|
-
blocking:
|
|
2318
|
+
blocking: z9.boolean().default(false).describe(
|
|
2217
2319
|
"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."
|
|
2218
2320
|
)
|
|
2219
2321
|
});
|
|
2220
2322
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
2221
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
2323
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z9.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
2222
2324
|
if (r.ask !== void 0) {
|
|
2223
2325
|
for (const f of ["context", "select", "points"]) {
|
|
2224
2326
|
if (r[f] !== void 0)
|
|
2225
|
-
ctx.addIssue({ code:
|
|
2327
|
+
ctx.addIssue({ code: z9.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.` });
|
|
2226
2328
|
}
|
|
2227
2329
|
return;
|
|
2228
2330
|
}
|
|
2229
2331
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
2230
|
-
ctx.addIssue({ code:
|
|
2332
|
+
ctx.addIssue({ code: z9.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
2231
2333
|
if (!r.context)
|
|
2232
|
-
ctx.addIssue({ code:
|
|
2334
|
+
ctx.addIssue({ code: z9.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
2233
2335
|
if (!r.select)
|
|
2234
|
-
ctx.addIssue({ code:
|
|
2336
|
+
ctx.addIssue({ code: z9.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
2235
2337
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
2236
2338
|
if (needsOptions && !r.options?.length)
|
|
2237
|
-
ctx.addIssue({ code:
|
|
2339
|
+
ctx.addIssue({ code: z9.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
2238
2340
|
if (!needsOptions && r.options?.length)
|
|
2239
|
-
ctx.addIssue({ code:
|
|
2341
|
+
ctx.addIssue({ code: z9.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
2240
2342
|
});
|
|
2241
|
-
var NotifyStatusSchema =
|
|
2242
|
-
var AgentStateSchema =
|
|
2243
|
-
var TurnSchema =
|
|
2244
|
-
prompt:
|
|
2245
|
-
reply:
|
|
2343
|
+
var NotifyStatusSchema = z9.enum(["pending", "answered", "ignored"]);
|
|
2344
|
+
var AgentStateSchema = z9.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
2345
|
+
var TurnSchema = z9.object({
|
|
2346
|
+
prompt: z9.string(),
|
|
2347
|
+
reply: z9.string()
|
|
2246
2348
|
});
|
|
2247
|
-
var UserAnswerSchema =
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2349
|
+
var UserAnswerSchema = z9.discriminatedUnion("kind", [
|
|
2350
|
+
z9.object({ kind: z9.literal("option"), optionId: z9.string(), label: z9.string().optional() }),
|
|
2351
|
+
z9.object({ kind: z9.literal("text"), text: z9.string() }),
|
|
2352
|
+
z9.object({ kind: z9.literal("ignored") }),
|
|
2353
|
+
z9.object({ kind: z9.literal("multi"), optionIds: z9.array(z9.string()), labels: z9.array(z9.string()).optional() }),
|
|
2354
|
+
z9.object({ kind: z9.literal("ranked"), optionIds: z9.array(z9.string()), labels: z9.array(z9.string()).optional() }),
|
|
2355
|
+
z9.object({ kind: z9.literal("clarify"), chunks: z9.array(z9.string()).min(1) }),
|
|
2356
|
+
z9.object({ kind: z9.literal("confirm"), approved: z9.boolean() }),
|
|
2357
|
+
z9.object({ kind: z9.literal("turns"), turns: z9.array(TurnSchema).min(1) }),
|
|
2256
2358
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
2257
2359
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
2258
2360
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
2259
2361
|
* card the user can reply to — the broker never overrides the user. */
|
|
2260
|
-
|
|
2362
|
+
z9.object({ kind: z9.literal("precedent"), answer: z9.string(), derivation: z9.string(), sources: z9.array(z9.string()).min(1) })
|
|
2261
2363
|
]);
|
|
2262
|
-
var IntentSchema =
|
|
2364
|
+
var IntentSchema = z9.object({
|
|
2263
2365
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
2264
2366
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
2265
2367
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
2266
2368
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2267
|
-
kind:
|
|
2268
|
-
detail:
|
|
2369
|
+
kind: z9.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2370
|
+
detail: z9.string(),
|
|
2269
2371
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2270
2372
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2271
2373
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2272
2374
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2273
|
-
dueInSeconds:
|
|
2375
|
+
dueInSeconds: z9.number().int().positive().optional(),
|
|
2274
2376
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2275
2377
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
2276
2378
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
2277
2379
|
* about what cannot answer "is the bot looping less this week?". */
|
|
2278
|
-
fault:
|
|
2380
|
+
fault: z9.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
2279
2381
|
});
|
|
2280
|
-
var RideAlongSchema =
|
|
2382
|
+
var RideAlongSchema = z9.object({
|
|
2281
2383
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
2282
|
-
noteId:
|
|
2384
|
+
noteId: z9.string(),
|
|
2283
2385
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
2284
|
-
text:
|
|
2386
|
+
text: z9.string(),
|
|
2285
2387
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
2286
|
-
parentId:
|
|
2388
|
+
parentId: z9.string().nullable()
|
|
2287
2389
|
});
|
|
2288
|
-
var AwaitItemSchema =
|
|
2289
|
-
|
|
2290
|
-
type:
|
|
2291
|
-
parentId:
|
|
2292
|
-
notificationId:
|
|
2293
|
-
workId:
|
|
2294
|
-
decisionId:
|
|
2390
|
+
var AwaitItemSchema = z9.discriminatedUnion("type", [
|
|
2391
|
+
z9.object({
|
|
2392
|
+
type: z9.literal("reply"),
|
|
2393
|
+
parentId: z9.string(),
|
|
2394
|
+
notificationId: z9.string(),
|
|
2395
|
+
workId: z9.string().uuid().optional(),
|
|
2396
|
+
decisionId: z9.string().uuid().optional(),
|
|
2295
2397
|
answer: UserAnswerSchema,
|
|
2296
2398
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
2297
2399
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -2301,103 +2403,103 @@ var AwaitItemSchema = z7.discriminatedUnion("type", [
|
|
|
2301
2403
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
2302
2404
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
2303
2405
|
* a sentence with a hole in it is worse than no sentence. */
|
|
2304
|
-
note:
|
|
2406
|
+
note: z9.string().optional(),
|
|
2305
2407
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
2306
2408
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
2307
2409
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
2308
2410
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
2309
|
-
transcript:
|
|
2411
|
+
transcript: z9.string().optional(),
|
|
2310
2412
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
2311
2413
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
2312
|
-
covered:
|
|
2414
|
+
covered: z9.array(z9.string()).optional(),
|
|
2313
2415
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
2314
2416
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
2315
2417
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
2316
2418
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
2317
2419
|
* dropped. Absent/empty = nothing owed. */
|
|
2318
|
-
also:
|
|
2420
|
+
also: z9.array(RideAlongSchema).optional()
|
|
2319
2421
|
}),
|
|
2320
|
-
|
|
2321
|
-
type:
|
|
2322
|
-
parentId:
|
|
2323
|
-
notificationId:
|
|
2324
|
-
remindAt:
|
|
2422
|
+
z9.object({
|
|
2423
|
+
type: z9.literal("remind"),
|
|
2424
|
+
parentId: z9.string(),
|
|
2425
|
+
notificationId: z9.string(),
|
|
2426
|
+
remindAt: z9.string().datetime({ offset: true }),
|
|
2325
2427
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
2326
|
-
remindInSeconds:
|
|
2428
|
+
remindInSeconds: z9.number()
|
|
2327
2429
|
}),
|
|
2328
2430
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2329
2431
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2330
2432
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2331
2433
|
* re-orient via contact({})). */
|
|
2332
|
-
|
|
2333
|
-
type:
|
|
2334
|
-
parentId:
|
|
2335
|
-
notificationId:
|
|
2434
|
+
z9.object({
|
|
2435
|
+
type: z9.literal("superseded"),
|
|
2436
|
+
parentId: z9.string(),
|
|
2437
|
+
notificationId: z9.string()
|
|
2336
2438
|
}),
|
|
2337
2439
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2338
2440
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2339
2441
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2340
2442
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2341
2443
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2342
|
-
|
|
2343
|
-
type:
|
|
2344
|
-
notificationId:
|
|
2345
|
-
inFlight:
|
|
2346
|
-
turn:
|
|
2347
|
-
idx:
|
|
2348
|
-
prompt:
|
|
2349
|
-
reply:
|
|
2350
|
-
acts:
|
|
2444
|
+
z9.object({
|
|
2445
|
+
type: z9.literal("partial"),
|
|
2446
|
+
notificationId: z9.string(),
|
|
2447
|
+
inFlight: z9.literal(true),
|
|
2448
|
+
turn: z9.object({
|
|
2449
|
+
idx: z9.number(),
|
|
2450
|
+
prompt: z9.string(),
|
|
2451
|
+
reply: z9.string(),
|
|
2452
|
+
acts: z9.array(IntentSchema).nullable().optional()
|
|
2351
2453
|
})
|
|
2352
2454
|
}),
|
|
2353
|
-
|
|
2354
|
-
type:
|
|
2355
|
-
also:
|
|
2455
|
+
z9.object({
|
|
2456
|
+
type: z9.literal("idle"),
|
|
2457
|
+
also: z9.array(RideAlongSchema).optional(),
|
|
2356
2458
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
2357
2459
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
2358
2460
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
2359
2461
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
2360
2462
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
2361
|
-
inFlight:
|
|
2463
|
+
inFlight: z9.boolean().optional()
|
|
2362
2464
|
})
|
|
2363
2465
|
]);
|
|
2364
|
-
var VoiceKeySchema =
|
|
2365
|
-
var AgendaTurnSchema =
|
|
2466
|
+
var VoiceKeySchema = z9.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2467
|
+
var AgendaTurnSchema = z9.object({
|
|
2366
2468
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
2367
2469
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
2368
2470
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
2369
2471
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
2370
2472
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
2371
2473
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
2372
|
-
id:
|
|
2474
|
+
id: z9.string().optional(),
|
|
2373
2475
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
2374
2476
|
* the planner declares duplicates instead of asking them twice. */
|
|
2375
|
-
coveredIds:
|
|
2477
|
+
coveredIds: z9.array(z9.string()).optional(),
|
|
2376
2478
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
2377
2479
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
2378
|
-
info:
|
|
2379
|
-
question:
|
|
2480
|
+
info: z9.array(z9.string().min(1)).default([]),
|
|
2481
|
+
question: z9.string().min(1).nullable(),
|
|
2380
2482
|
/** True on the one turn carrying the agent's own declared question. */
|
|
2381
|
-
asks:
|
|
2483
|
+
asks: z9.boolean().optional(),
|
|
2382
2484
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
2383
2485
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
2384
2486
|
* which route nothing. */
|
|
2385
|
-
claimId:
|
|
2487
|
+
claimId: z9.string().optional(),
|
|
2386
2488
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
2387
|
-
voice:
|
|
2489
|
+
voice: z9.string().optional(),
|
|
2388
2490
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
2389
2491
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
2390
2492
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
2391
2493
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
2392
|
-
agent:
|
|
2494
|
+
agent: z9.string().optional(),
|
|
2393
2495
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
2394
2496
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
2395
2497
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
2396
2498
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
2397
2499
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
2398
|
-
agentId:
|
|
2500
|
+
agentId: z9.string().optional(),
|
|
2399
2501
|
select: SelectShapeSchema.optional(),
|
|
2400
|
-
options:
|
|
2502
|
+
options: z9.array(OptionSchema.omit({ id: true })).optional(),
|
|
2401
2503
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
2402
2504
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
2403
2505
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -2406,33 +2508,33 @@ var AgendaTurnSchema = z7.object({
|
|
|
2406
2508
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
2407
2509
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
2408
2510
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2409
|
-
blocking:
|
|
2511
|
+
blocking: z9.boolean().optional(),
|
|
2410
2512
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
2411
2513
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
2412
2514
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
2413
2515
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
2414
2516
|
* speak, it is dropped and their words are taken like any reply. */
|
|
2415
|
-
ifSilent:
|
|
2517
|
+
ifSilent: z9.boolean().optional()
|
|
2416
2518
|
});
|
|
2417
2519
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2418
|
-
var InboxItemSchema =
|
|
2419
|
-
id:
|
|
2420
|
-
tokenId:
|
|
2520
|
+
var InboxItemSchema = z9.object({
|
|
2521
|
+
id: z9.string(),
|
|
2522
|
+
tokenId: z9.string().optional(),
|
|
2421
2523
|
status: NotifyStatusSchema,
|
|
2422
2524
|
context: ContextSchema,
|
|
2423
|
-
options:
|
|
2525
|
+
options: z9.array(OptionSchema).optional(),
|
|
2424
2526
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2425
|
-
points:
|
|
2527
|
+
points: z9.array(z9.string()).optional(),
|
|
2426
2528
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
2427
2529
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2428
2530
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2429
2531
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2430
|
-
asks:
|
|
2532
|
+
asks: z9.boolean().optional(),
|
|
2431
2533
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2432
2534
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2433
2535
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2434
2536
|
* which must never CLAIM stalled. */
|
|
2435
|
-
lastSeenAt:
|
|
2537
|
+
lastSeenAt: z9.string().optional(),
|
|
2436
2538
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2437
2539
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2438
2540
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2442,7 +2544,7 @@ var InboxItemSchema = z7.object({
|
|
|
2442
2544
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2443
2545
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2444
2546
|
* `createdAt`. */
|
|
2445
|
-
agentStateAt:
|
|
2547
|
+
agentStateAt: z9.string().datetime().optional(),
|
|
2446
2548
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
2447
2549
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
2448
2550
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -2456,54 +2558,61 @@ var InboxItemSchema = z7.object({
|
|
|
2456
2558
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2457
2559
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2458
2560
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2459
|
-
questions:
|
|
2460
|
-
id:
|
|
2461
|
-
entryId:
|
|
2462
|
-
title:
|
|
2463
|
-
state:
|
|
2464
|
-
answer:
|
|
2561
|
+
questions: z9.array(z9.object({
|
|
2562
|
+
id: z9.string(),
|
|
2563
|
+
entryId: z9.string(),
|
|
2564
|
+
title: z9.string(),
|
|
2565
|
+
state: z9.enum(["open", "answered"]),
|
|
2566
|
+
answer: z9.string().nullable(),
|
|
2465
2567
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2466
2568
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2467
2569
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2468
2570
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2469
|
-
agent:
|
|
2571
|
+
agent: z9.object({ tokenId: z9.string(), name: z9.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
2470
2572
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
2471
2573
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
2472
2574
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
2473
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are
|
|
2474
|
-
|
|
2475
|
-
|
|
2575
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
2576
|
+
options: z9.array(OptionSchema).optional(),
|
|
2577
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
2578
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
2579
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
2580
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
2581
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
2582
|
+
choices: z9.array(OptionSchema).optional(),
|
|
2583
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
2584
|
+
select: z9.enum(["one", "many", "rank"]).optional()
|
|
2476
2585
|
})).optional(),
|
|
2477
|
-
visuals:
|
|
2586
|
+
visuals: z9.array(VisualSchema).optional(),
|
|
2478
2587
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2479
2588
|
* agent's suggestion, or a default silly name). */
|
|
2480
|
-
name:
|
|
2589
|
+
name: z9.string(),
|
|
2481
2590
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2482
2591
|
voice: VoiceKeySchema.optional(),
|
|
2483
|
-
repo:
|
|
2484
|
-
branch:
|
|
2485
|
-
createdAt:
|
|
2486
|
-
snoozedUntil:
|
|
2592
|
+
repo: z9.string().optional(),
|
|
2593
|
+
branch: z9.string().optional(),
|
|
2594
|
+
createdAt: z9.string().datetime(),
|
|
2595
|
+
snoozedUntil: z9.string().datetime().optional(),
|
|
2487
2596
|
agentState: AgentStateSchema.default("idle"),
|
|
2488
2597
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2489
2598
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2490
2599
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2491
|
-
turn:
|
|
2600
|
+
turn: z9.enum(["you", "agent"]).default("you"),
|
|
2492
2601
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2493
2602
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2494
2603
|
* error badge + Retry. */
|
|
2495
|
-
error:
|
|
2604
|
+
error: z9.string().optional(),
|
|
2496
2605
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
2497
2606
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2498
2607
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2499
2608
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2500
|
-
cold:
|
|
2501
|
-
clarifies:
|
|
2609
|
+
cold: z9.string().datetime().optional(),
|
|
2610
|
+
clarifies: z9.string().optional(),
|
|
2502
2611
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2503
2612
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2504
2613
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2505
2614
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2506
|
-
onCall:
|
|
2615
|
+
onCall: z9.literal(true).optional(),
|
|
2507
2616
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2508
2617
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2509
2618
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2513,31 +2622,31 @@ var InboxItemSchema = z7.object({
|
|
|
2513
2622
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2514
2623
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2515
2624
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2516
|
-
ring:
|
|
2517
|
-
declinedAt:
|
|
2518
|
-
anchorAt:
|
|
2519
|
-
nextRingAt:
|
|
2520
|
-
step:
|
|
2625
|
+
ring: z9.object({
|
|
2626
|
+
declinedAt: z9.string().datetime(),
|
|
2627
|
+
anchorAt: z9.string().datetime(),
|
|
2628
|
+
nextRingAt: z9.string().datetime().nullable(),
|
|
2629
|
+
step: z9.number().int()
|
|
2521
2630
|
}).optional(),
|
|
2522
2631
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
2523
2632
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
2524
2633
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
2525
2634
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
2526
2635
|
* feedback ABOUT. */
|
|
2527
|
-
why:
|
|
2636
|
+
why: z9.object({
|
|
2528
2637
|
asked: NotifyLevelSchema,
|
|
2529
2638
|
got: NotifyLevelSchema,
|
|
2530
|
-
because:
|
|
2531
|
-
line:
|
|
2639
|
+
because: z9.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2640
|
+
line: z9.string()
|
|
2532
2641
|
}).optional(),
|
|
2533
|
-
select:
|
|
2534
|
-
confirmStyle:
|
|
2642
|
+
select: z9.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2643
|
+
confirmStyle: z9.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2535
2644
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2536
2645
|
),
|
|
2537
2646
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
2538
2647
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
2539
2648
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
2540
|
-
blocking:
|
|
2649
|
+
blocking: z9.boolean().default(false),
|
|
2541
2650
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
2542
2651
|
answer: UserAnswerSchema.optional(),
|
|
2543
2652
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -2545,17 +2654,17 @@ var InboxItemSchema = z7.object({
|
|
|
2545
2654
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
2546
2655
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
2547
2656
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
2548
|
-
communication:
|
|
2549
|
-
deliveryId:
|
|
2550
|
-
kind:
|
|
2551
|
-
entryId:
|
|
2552
|
-
goalIds:
|
|
2553
|
-
decisionNeedId:
|
|
2554
|
-
sealed:
|
|
2555
|
-
goalState:
|
|
2657
|
+
communication: z9.object({
|
|
2658
|
+
deliveryId: z9.string(),
|
|
2659
|
+
kind: z9.enum(["notification", "call"]),
|
|
2660
|
+
entryId: z9.string(),
|
|
2661
|
+
goalIds: z9.array(z9.string()),
|
|
2662
|
+
decisionNeedId: z9.string().optional(),
|
|
2663
|
+
sealed: z9.boolean(),
|
|
2664
|
+
goalState: z9.string().optional(),
|
|
2556
2665
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2557
2666
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2558
|
-
goalTitle:
|
|
2667
|
+
goalTitle: z9.string().optional()
|
|
2559
2668
|
}).optional(),
|
|
2560
2669
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
2561
2670
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -2567,27 +2676,27 @@ var InboxItemSchema = z7.object({
|
|
|
2567
2676
|
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
2568
2677
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
2569
2678
|
* with no rev is simply always re-sent. */
|
|
2570
|
-
rev:
|
|
2679
|
+
rev: z9.string().optional()
|
|
2571
2680
|
});
|
|
2572
2681
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
2573
|
-
var PushTokenSchema =
|
|
2574
|
-
voipToken:
|
|
2575
|
-
alertToken:
|
|
2576
|
-
fcmToken:
|
|
2577
|
-
platform:
|
|
2682
|
+
var PushTokenSchema = z9.object({
|
|
2683
|
+
voipToken: z9.string().min(1).optional(),
|
|
2684
|
+
alertToken: z9.string().min(1).optional(),
|
|
2685
|
+
fcmToken: z9.string().min(1).optional(),
|
|
2686
|
+
platform: z9.enum(["ios", "android"])
|
|
2578
2687
|
}).superRefine((v, ctx) => {
|
|
2579
2688
|
if (v.platform !== "ios") return;
|
|
2580
2689
|
for (const field of ["voipToken", "alertToken"]) {
|
|
2581
2690
|
const token = v[field];
|
|
2582
2691
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
2583
2692
|
ctx.addIssue({
|
|
2584
|
-
code:
|
|
2693
|
+
code: z9.ZodIssueCode.custom,
|
|
2585
2694
|
path: [field],
|
|
2586
2695
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
2587
2696
|
});
|
|
2588
2697
|
}
|
|
2589
2698
|
});
|
|
2590
|
-
var MissedCallSchema =
|
|
2699
|
+
var MissedCallSchema = z9.enum([
|
|
2591
2700
|
"retry_10m",
|
|
2592
2701
|
"retry_30m",
|
|
2593
2702
|
"retry_60m",
|
|
@@ -2599,31 +2708,31 @@ var MissedCallSchema = z7.enum([
|
|
|
2599
2708
|
]);
|
|
2600
2709
|
var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
2601
2710
|
var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
|
|
2602
|
-
var BrokerTuningSchema =
|
|
2711
|
+
var BrokerTuningSchema = z9.object({
|
|
2603
2712
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
2604
|
-
ackVerbosity:
|
|
2713
|
+
ackVerbosity: z9.enum(["normal", "none"]).optional(),
|
|
2605
2714
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
2606
2715
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
2607
|
-
clarifyEagerness:
|
|
2716
|
+
clarifyEagerness: z9.enum(["low", "normal", "high"]).optional(),
|
|
2608
2717
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
2609
|
-
phrasebook:
|
|
2718
|
+
phrasebook: z9.array(z9.object({ say: z9.string().min(1).max(60), mean: z9.string().min(1).max(120) })).max(24).optional(),
|
|
2610
2719
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
2611
2720
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
2612
2721
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
2613
2722
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
2614
2723
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
2615
|
-
language:
|
|
2724
|
+
language: z9.enum(["en", "es"]).optional()
|
|
2616
2725
|
});
|
|
2617
|
-
var UserSettingsSchema =
|
|
2618
|
-
permissions:
|
|
2619
|
-
call:
|
|
2620
|
-
banner:
|
|
2621
|
-
push:
|
|
2726
|
+
var UserSettingsSchema = z9.object({
|
|
2727
|
+
permissions: z9.object({
|
|
2728
|
+
call: z9.boolean(),
|
|
2729
|
+
banner: z9.boolean(),
|
|
2730
|
+
push: z9.boolean()
|
|
2622
2731
|
}),
|
|
2623
2732
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
2624
2733
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
2625
2734
|
* what people read (owner, 2026-09-30). */
|
|
2626
|
-
sessionMode:
|
|
2735
|
+
sessionMode: z9.enum(["default", "all_calls", "silent"]),
|
|
2627
2736
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
2628
2737
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
2629
2738
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -2632,7 +2741,7 @@ var UserSettingsSchema = z7.object({
|
|
|
2632
2741
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
2633
2742
|
* next dead field for the same reason. */
|
|
2634
2743
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
2635
|
-
improveConsent:
|
|
2744
|
+
improveConsent: z9.boolean(),
|
|
2636
2745
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
2637
2746
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
2638
2747
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -2640,17 +2749,17 @@ var UserSettingsSchema = z7.object({
|
|
|
2640
2749
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
2641
2750
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
2642
2751
|
* write, 'hosted' on read (see store.ts). */
|
|
2643
|
-
voiceMode:
|
|
2752
|
+
voiceMode: z9.enum(["hosted", "on_device"]).optional(),
|
|
2644
2753
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
2645
2754
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
2646
2755
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
2647
2756
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2648
2757
|
* read (see store.ts). */
|
|
2649
|
-
talk:
|
|
2758
|
+
talk: z9.boolean().optional(),
|
|
2650
2759
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
2651
2760
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
2652
2761
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
2653
|
-
callDiagnostics:
|
|
2762
|
+
callDiagnostics: z9.boolean().optional(),
|
|
2654
2763
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2655
2764
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2656
2765
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2658,7 +2767,7 @@ var UserSettingsSchema = z7.object({
|
|
|
2658
2767
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2659
2768
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2660
2769
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2661
|
-
voiceTuning:
|
|
2770
|
+
voiceTuning: z9.record(z9.string(), z9.union([z9.number(), z9.string()])).optional(),
|
|
2662
2771
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2663
2772
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2664
2773
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2667,53 +2776,53 @@ var UserSettingsSchema = z7.object({
|
|
|
2667
2776
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2668
2777
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2669
2778
|
* call — honest about what we know. */
|
|
2670
|
-
timezone:
|
|
2779
|
+
timezone: z9.string().min(1).max(64).optional(),
|
|
2671
2780
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2672
2781
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2673
2782
|
broker: BrokerTuningSchema.optional()
|
|
2674
2783
|
});
|
|
2675
|
-
var HistoryWorkSchema =
|
|
2676
|
-
id:
|
|
2677
|
-
title:
|
|
2678
|
-
state:
|
|
2784
|
+
var HistoryWorkSchema = z9.object({
|
|
2785
|
+
id: z9.string(),
|
|
2786
|
+
title: z9.string(),
|
|
2787
|
+
state: z9.enum(["done", "cancelled"]),
|
|
2679
2788
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
2680
|
-
assignee:
|
|
2789
|
+
assignee: z9.string()
|
|
2681
2790
|
});
|
|
2682
|
-
var HistoryEntrySchema =
|
|
2683
|
-
|
|
2684
|
-
|
|
2791
|
+
var HistoryEntrySchema = z9.union([
|
|
2792
|
+
z9.object({ at: z9.string(), card: InboxItemSchema }),
|
|
2793
|
+
z9.object({ at: z9.string(), work: HistoryWorkSchema })
|
|
2685
2794
|
]);
|
|
2686
|
-
var HistoryPageSchema =
|
|
2687
|
-
entries:
|
|
2688
|
-
next:
|
|
2795
|
+
var HistoryPageSchema = z9.object({
|
|
2796
|
+
entries: z9.array(HistoryEntrySchema),
|
|
2797
|
+
next: z9.string().nullable()
|
|
2689
2798
|
});
|
|
2690
2799
|
var ACTIVITY_LINES = 2;
|
|
2691
2800
|
var ACTIVITY_LINE_MAX = 80;
|
|
2692
|
-
var AgentActivitySchema =
|
|
2801
|
+
var AgentActivitySchema = z9.object({
|
|
2693
2802
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
2694
|
-
lines:
|
|
2803
|
+
lines: z9.array(z9.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
2695
2804
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
2696
2805
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
2697
|
-
at:
|
|
2806
|
+
at: z9.string().datetime()
|
|
2698
2807
|
});
|
|
2699
|
-
var ConnectionSummarySchema =
|
|
2808
|
+
var ConnectionSummarySchema = z9.object({
|
|
2700
2809
|
/** The connection = the agent's token id (used to address a request). */
|
|
2701
|
-
id:
|
|
2810
|
+
id: z9.string(),
|
|
2702
2811
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
2703
2812
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
2704
2813
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
2705
2814
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
2706
|
-
kind:
|
|
2815
|
+
kind: z9.enum(["device", "agent"]).optional(),
|
|
2707
2816
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
2708
2817
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
2709
2818
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
2710
|
-
mintedByDevice:
|
|
2711
|
-
device:
|
|
2819
|
+
mintedByDevice: z9.string().nullable().optional(),
|
|
2820
|
+
device: z9.string().nullable(),
|
|
2712
2821
|
/** The agent's display name (the single pairing name). */
|
|
2713
|
-
name:
|
|
2822
|
+
name: z9.string(),
|
|
2714
2823
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
2715
2824
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
2716
|
-
provider:
|
|
2825
|
+
provider: z9.string().nullable(),
|
|
2717
2826
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
2718
2827
|
voice: VoiceKeySchema.nullable(),
|
|
2719
2828
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -2724,34 +2833,34 @@ var ConnectionSummarySchema = z7.object({
|
|
|
2724
2833
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
2725
2834
|
* set once must not overrule a rule they set about one agent. */
|
|
2726
2835
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
2727
|
-
createdAt:
|
|
2836
|
+
createdAt: z9.string().datetime(),
|
|
2728
2837
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
2729
2838
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
2730
|
-
lastContactAt:
|
|
2839
|
+
lastContactAt: z9.string().datetime().nullable(),
|
|
2731
2840
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
2732
2841
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
2733
|
-
lastSeenAt:
|
|
2842
|
+
lastSeenAt: z9.string().datetime().nullable().optional(),
|
|
2734
2843
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
2735
|
-
* its Goals —
|
|
2844
|
+
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
2736
2845
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
2737
|
-
lastWorkedAt:
|
|
2846
|
+
lastWorkedAt: z9.string().datetime().nullable().optional(),
|
|
2738
2847
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
2739
2848
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
2740
|
-
oldestReadyAt:
|
|
2849
|
+
oldestReadyAt: z9.string().datetime().nullable().optional(),
|
|
2741
2850
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
2742
2851
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
2743
2852
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
2744
|
-
runtime:
|
|
2853
|
+
runtime: z9.object({
|
|
2745
2854
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
2746
2855
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
2747
|
-
version:
|
|
2748
|
-
harnesses:
|
|
2749
|
-
workspaces:
|
|
2856
|
+
version: z9.string().optional(),
|
|
2857
|
+
harnesses: z9.array(z9.object({ name: z9.string(), label: z9.string(), status: z9.string() })).optional(),
|
|
2858
|
+
workspaces: z9.array(z9.string()).optional(),
|
|
2750
2859
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
2751
2860
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
2752
2861
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
2753
2862
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
2754
|
-
repos:
|
|
2863
|
+
repos: z9.array(z9.object({ path: z9.string(), remote: z9.string() })).optional()
|
|
2755
2864
|
}).optional(),
|
|
2756
2865
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
2757
2866
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -2760,156 +2869,156 @@ var ConnectionSummarySchema = z7.object({
|
|
|
2760
2869
|
activity: AgentActivitySchema.optional(),
|
|
2761
2870
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
2762
2871
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
2763
|
-
managed:
|
|
2872
|
+
managed: z9.boolean()
|
|
2764
2873
|
});
|
|
2765
|
-
var LedgerItemSchema =
|
|
2766
|
-
var AgentLedgerSchema =
|
|
2874
|
+
var LedgerItemSchema = z9.object({ id: z9.string(), parentId: z9.string(), title: z9.string(), createdAt: z9.string() });
|
|
2875
|
+
var AgentLedgerSchema = z9.object({
|
|
2767
2876
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
2768
|
-
agent:
|
|
2877
|
+
agent: z9.object({ id: z9.string(), name: z9.string().nullable(), revokedAt: z9.string().nullable() }),
|
|
2769
2878
|
/** Its own questions you have not answered. */
|
|
2770
|
-
asks:
|
|
2879
|
+
asks: z9.array(LedgerItemSchema),
|
|
2771
2880
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
2772
|
-
answered:
|
|
2881
|
+
answered: z9.array(LedgerItemSchema),
|
|
2773
2882
|
/** Requests you sent it that it never took. */
|
|
2774
|
-
requests:
|
|
2775
|
-
goals:
|
|
2776
|
-
callbacks:
|
|
2883
|
+
requests: z9.array(LedgerItemSchema),
|
|
2884
|
+
goals: z9.array(z9.object({ id: z9.string(), outcome: z9.string(), state: z9.string() })),
|
|
2885
|
+
callbacks: z9.array(z9.object({ id: z9.string(), parentId: z9.string(), trigger: z9.string(), note: z9.string(), dueAt: z9.string().nullable() }))
|
|
2777
2886
|
});
|
|
2778
|
-
var ReassignResultSchema =
|
|
2779
|
-
moved:
|
|
2780
|
-
parentId:
|
|
2887
|
+
var ReassignResultSchema = z9.object({
|
|
2888
|
+
moved: z9.object({ asks: z9.number(), answered: z9.number(), requests: z9.number(), goals: z9.number(), callbacks: z9.number() }),
|
|
2889
|
+
parentId: z9.string().nullable()
|
|
2781
2890
|
});
|
|
2782
|
-
var LessonStateSchema =
|
|
2783
|
-
var LessonViewSchema =
|
|
2784
|
-
id:
|
|
2785
|
-
text:
|
|
2891
|
+
var LessonStateSchema = z9.enum(["active", "proposed", "retired"]);
|
|
2892
|
+
var LessonViewSchema = z9.object({
|
|
2893
|
+
id: z9.string(),
|
|
2894
|
+
text: z9.string(),
|
|
2786
2895
|
state: LessonStateSchema,
|
|
2787
2896
|
/** The Goal it is scoped to; null = the whole account. */
|
|
2788
|
-
scopeGoalId:
|
|
2789
|
-
goalTitle:
|
|
2790
|
-
version:
|
|
2791
|
-
pinned:
|
|
2897
|
+
scopeGoalId: z9.string().nullable(),
|
|
2898
|
+
goalTitle: z9.string().nullable(),
|
|
2899
|
+
version: z9.number(),
|
|
2900
|
+
pinned: z9.boolean(),
|
|
2792
2901
|
/** When the person last wrote its text themselves. */
|
|
2793
|
-
editedAt:
|
|
2794
|
-
createdAt:
|
|
2795
|
-
updatedAt:
|
|
2902
|
+
editedAt: z9.string().nullable(),
|
|
2903
|
+
createdAt: z9.string(),
|
|
2904
|
+
updatedAt: z9.string(),
|
|
2796
2905
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
2797
|
-
sources:
|
|
2906
|
+
sources: z9.array(z9.object({ entryId: z9.string(), words: z9.string().nullable(), at: z9.string() }))
|
|
2798
2907
|
});
|
|
2799
|
-
var QueueQuestionSchema =
|
|
2908
|
+
var QueueQuestionSchema = z9.object({
|
|
2800
2909
|
/** The decision need's id — what an answer is accepted against. */
|
|
2801
|
-
id:
|
|
2910
|
+
id: z9.string(),
|
|
2802
2911
|
/** The words that were asked, from the request Entry that asked them. */
|
|
2803
|
-
question:
|
|
2912
|
+
question: z9.string(),
|
|
2804
2913
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
2805
2914
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
2806
2915
|
* can answer. */
|
|
2807
|
-
deliveryId:
|
|
2916
|
+
deliveryId: z9.string().nullable().default(null),
|
|
2808
2917
|
/** The Entry the ruling is about. */
|
|
2809
|
-
aboutId:
|
|
2918
|
+
aboutId: z9.string().nullable().default(null),
|
|
2810
2919
|
/** Empty for a free-text question. */
|
|
2811
|
-
options:
|
|
2812
|
-
select:
|
|
2813
|
-
askedAt:
|
|
2920
|
+
options: z9.array(OptionSchema).default([]),
|
|
2921
|
+
select: z9.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
2922
|
+
askedAt: z9.string(),
|
|
2814
2923
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
2815
|
-
answeredAt:
|
|
2924
|
+
answeredAt: z9.string().nullable().default(null),
|
|
2816
2925
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
2817
2926
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
2818
2927
|
* for a settled question whose reply carried nothing readable. */
|
|
2819
|
-
answer:
|
|
2928
|
+
answer: z9.string().nullable().default(null),
|
|
2820
2929
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
2821
2930
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
2822
2931
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
2823
2932
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
2824
2933
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
2825
2934
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
2826
|
-
goalId:
|
|
2935
|
+
goalId: z9.string(),
|
|
2827
2936
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
2828
2937
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
2829
2938
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
2830
2939
|
* gate at all. */
|
|
2831
|
-
blocked:
|
|
2940
|
+
blocked: z9.boolean().default(false)
|
|
2832
2941
|
});
|
|
2833
|
-
var QueueReplySchema =
|
|
2942
|
+
var QueueReplySchema = z9.object({
|
|
2834
2943
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
2835
2944
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
2836
2945
|
* walk can name it in its zoom. */
|
|
2837
|
-
id:
|
|
2946
|
+
id: z9.string(),
|
|
2838
2947
|
/** The Goal the note is on. */
|
|
2839
|
-
goalId:
|
|
2948
|
+
goalId: z9.string(),
|
|
2840
2949
|
/** What the note said. */
|
|
2841
|
-
note:
|
|
2950
|
+
note: z9.string(),
|
|
2842
2951
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
2843
|
-
deliveryId:
|
|
2844
|
-
requestEntryId:
|
|
2845
|
-
askedAt:
|
|
2952
|
+
deliveryId: z9.string(),
|
|
2953
|
+
requestEntryId: z9.string(),
|
|
2954
|
+
askedAt: z9.string(),
|
|
2846
2955
|
/** When the person last replied — the window's start. */
|
|
2847
|
-
repliedAt:
|
|
2956
|
+
repliedAt: z9.string(),
|
|
2848
2957
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
2849
|
-
reply:
|
|
2958
|
+
reply: z9.string().nullable()
|
|
2850
2959
|
});
|
|
2851
|
-
var QueueItemSchema =
|
|
2852
|
-
id:
|
|
2960
|
+
var QueueItemSchema = z9.object({
|
|
2961
|
+
id: z9.string(),
|
|
2853
2962
|
/** One-line headline — the first sentence of the outcome. */
|
|
2854
|
-
title:
|
|
2963
|
+
title: z9.string(),
|
|
2855
2964
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
2856
|
-
intent:
|
|
2965
|
+
intent: z9.string(),
|
|
2857
2966
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
2858
|
-
state:
|
|
2967
|
+
state: z9.string(),
|
|
2859
2968
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
2860
|
-
assignee:
|
|
2969
|
+
assignee: z9.string().nullable().default(null),
|
|
2861
2970
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
2862
|
-
progress:
|
|
2971
|
+
progress: z9.string().nullable().default(null),
|
|
2863
2972
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
2864
2973
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
2865
|
-
progressLine:
|
|
2866
|
-
reviewPending:
|
|
2867
|
-
dueAt:
|
|
2974
|
+
progressLine: z9.string().nullable().optional(),
|
|
2975
|
+
reviewPending: z9.boolean().default(false),
|
|
2976
|
+
dueAt: z9.string().nullable().default(null),
|
|
2868
2977
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
2869
2978
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
2870
|
-
finishedAt:
|
|
2979
|
+
finishedAt: z9.string().nullable().optional(),
|
|
2871
2980
|
/** The Goal this one was opened under; null at the root. */
|
|
2872
|
-
parentGoalId:
|
|
2981
|
+
parentGoalId: z9.string().nullable().default(null),
|
|
2873
2982
|
/** Goals opened under this one — only those the same list holds. */
|
|
2874
|
-
childGoalIds:
|
|
2983
|
+
childGoalIds: z9.array(z9.string()).default([]),
|
|
2875
2984
|
/** Goals this one waits on (start or finish gates). */
|
|
2876
|
-
dependencyGoalIds:
|
|
2985
|
+
dependencyGoalIds: z9.array(z9.string()).default([]),
|
|
2877
2986
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
2878
|
-
blocked:
|
|
2987
|
+
blocked: z9.boolean().default(false),
|
|
2879
2988
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
2880
2989
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
2881
2990
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
2882
2991
|
* question is opened. */
|
|
2883
|
-
questions:
|
|
2992
|
+
questions: z9.array(QueueQuestionSchema).default([]),
|
|
2884
2993
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
2885
2994
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
2886
2995
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
2887
2996
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
2888
|
-
asked:
|
|
2889
|
-
answered:
|
|
2997
|
+
asked: z9.number().optional(),
|
|
2998
|
+
answered: z9.number().optional(),
|
|
2890
2999
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
2891
3000
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
2892
3001
|
* not spell an empty list. */
|
|
2893
|
-
replies:
|
|
3002
|
+
replies: z9.array(QueueReplySchema).optional(),
|
|
2894
3003
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
2895
|
-
repo:
|
|
2896
|
-
createdAt:
|
|
2897
|
-
updatedAt:
|
|
3004
|
+
repo: z9.string().nullable().optional(),
|
|
3005
|
+
createdAt: z9.string(),
|
|
3006
|
+
updatedAt: z9.string().nullable().default(null),
|
|
2898
3007
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
2899
3008
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
2900
3009
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
2901
|
-
lastProgressAt:
|
|
3010
|
+
lastProgressAt: z9.string().nullable().optional(),
|
|
2902
3011
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
2903
3012
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
2904
3013
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
2905
3014
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
2906
3015
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
2907
3016
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
2908
|
-
latest:
|
|
2909
|
-
from:
|
|
2910
|
-
said:
|
|
2911
|
-
at:
|
|
2912
|
-
entryId:
|
|
3017
|
+
latest: z9.object({
|
|
3018
|
+
from: z9.enum(["person", "agent"]),
|
|
3019
|
+
said: z9.string(),
|
|
3020
|
+
at: z9.string(),
|
|
3021
|
+
entryId: z9.string()
|
|
2913
3022
|
}).nullable().optional(),
|
|
2914
3023
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
2915
3024
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -2922,7 +3031,7 @@ var QueueItemSchema = z7.object({
|
|
|
2922
3031
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
2923
3032
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
2924
3033
|
* Entry with no `about_id` is in neither. */
|
|
2925
|
-
lastPersonAt:
|
|
3034
|
+
lastPersonAt: z9.string().nullable().optional(),
|
|
2926
3035
|
/** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
|
|
2927
3036
|
* by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
|
|
2928
3037
|
* incremental read knows a row has not moved: the phone echoes back the revs it holds
|
|
@@ -2930,43 +3039,43 @@ var QueueItemSchema = z7.object({
|
|
|
2930
3039
|
*
|
|
2931
3040
|
* IT IS THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK. Nothing here reasons about which
|
|
2932
3041
|
* writes change which field — the comparison is over the bytes the phone is holding, so a fact
|
|
2933
|
-
* the row shows that no `updated_at` moves for (
|
|
3042
|
+
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
2934
3043
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
2935
3044
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
2936
|
-
rev:
|
|
3045
|
+
rev: z9.string().optional()
|
|
2937
3046
|
});
|
|
2938
|
-
var QueueDeltaSchema =
|
|
2939
|
-
ids:
|
|
2940
|
-
items:
|
|
3047
|
+
var QueueDeltaSchema = z9.object({
|
|
3048
|
+
ids: z9.array(z9.string()),
|
|
3049
|
+
items: z9.array(QueueItemSchema)
|
|
2941
3050
|
});
|
|
2942
3051
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
2943
|
-
var NoteSourceSchema =
|
|
2944
|
-
var NoteStatusSchema =
|
|
2945
|
-
var NoteRepeatSchema =
|
|
2946
|
-
var DecisionSchema =
|
|
2947
|
-
id:
|
|
3052
|
+
var NoteSourceSchema = z9.enum(["app", "call"]);
|
|
3053
|
+
var NoteStatusSchema = z9.enum(["open", "assigned", "in_progress", "done"]);
|
|
3054
|
+
var NoteRepeatSchema = z9.enum(["once", "until_done"]);
|
|
3055
|
+
var DecisionSchema = z9.object({
|
|
3056
|
+
id: z9.string(),
|
|
2948
3057
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
2949
3058
|
* extensibility seam — any conversation can accrue decisions). */
|
|
2950
|
-
noteId:
|
|
3059
|
+
noteId: z9.string().nullable(),
|
|
2951
3060
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
2952
|
-
question:
|
|
3061
|
+
question: z9.string(),
|
|
2953
3062
|
/** The user's ruling; null while the question is open. */
|
|
2954
|
-
answer:
|
|
2955
|
-
decidedAt:
|
|
2956
|
-
createdAt:
|
|
3063
|
+
answer: z9.string().nullable(),
|
|
3064
|
+
decidedAt: z9.string().nullable(),
|
|
3065
|
+
createdAt: z9.string()
|
|
2957
3066
|
});
|
|
2958
|
-
var NoteSchema =
|
|
2959
|
-
id:
|
|
3067
|
+
var NoteSchema = z9.object({
|
|
3068
|
+
id: z9.string(),
|
|
2960
3069
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
2961
|
-
title:
|
|
3070
|
+
title: z9.string(),
|
|
2962
3071
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
2963
|
-
intent:
|
|
3072
|
+
intent: z9.string(),
|
|
2964
3073
|
source: NoteSourceSchema,
|
|
2965
3074
|
status: NoteStatusSchema,
|
|
2966
3075
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
2967
|
-
assignee:
|
|
3076
|
+
assignee: z9.string().nullable(),
|
|
2968
3077
|
/** The request thread minted at assignment; null until assigned. */
|
|
2969
|
-
parentId:
|
|
3078
|
+
parentId: z9.string().nullable(),
|
|
2970
3079
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
2971
3080
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
2972
3081
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -2975,154 +3084,154 @@ var NoteSchema = z7.object({
|
|
|
2975
3084
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
2976
3085
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
2977
3086
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
2978
|
-
dueAt:
|
|
3087
|
+
dueAt: z9.string().nullable().default(null),
|
|
2979
3088
|
repeat: NoteRepeatSchema.default("once"),
|
|
2980
3089
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
2981
|
-
rides:
|
|
2982
|
-
lastRideAt:
|
|
2983
|
-
createdAt:
|
|
3090
|
+
rides: z9.number().int().default(0),
|
|
3091
|
+
lastRideAt: z9.string().nullable().default(null),
|
|
3092
|
+
createdAt: z9.string()
|
|
2984
3093
|
});
|
|
2985
|
-
var TriageItemSchema =
|
|
2986
|
-
noteId:
|
|
3094
|
+
var TriageItemSchema = z9.object({
|
|
3095
|
+
noteId: z9.string(),
|
|
2987
3096
|
/** The note's headline at run time. */
|
|
2988
|
-
title:
|
|
3097
|
+
title: z9.string(),
|
|
2989
3098
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
2990
3099
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
2991
3100
|
* Never a model's reasoning transcript, never an id. */
|
|
2992
|
-
why:
|
|
3101
|
+
why: z9.string()
|
|
2993
3102
|
});
|
|
2994
|
-
var TriageAssignmentSchema =
|
|
3103
|
+
var TriageAssignmentSchema = z9.object({
|
|
2995
3104
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
2996
|
-
agent:
|
|
3105
|
+
agent: z9.string(),
|
|
2997
3106
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
2998
3107
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
2999
|
-
agentName:
|
|
3000
|
-
notes:
|
|
3108
|
+
agentName: z9.string(),
|
|
3109
|
+
notes: z9.array(TriageItemSchema)
|
|
3001
3110
|
});
|
|
3002
|
-
var TriageStatusSchema =
|
|
3003
|
-
var SubmitTriageSchema =
|
|
3111
|
+
var TriageStatusSchema = z9.enum(["open", "superseded", "dismissed"]);
|
|
3112
|
+
var SubmitTriageSchema = z9.object({
|
|
3004
3113
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
3005
3114
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
3006
3115
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
3007
3116
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
3008
|
-
provider:
|
|
3117
|
+
provider: z9.string().min(1).max(60),
|
|
3009
3118
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
3010
|
-
model:
|
|
3119
|
+
model: z9.string().max(200).nullable().optional(),
|
|
3011
3120
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
3012
3121
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
3013
|
-
reviewed:
|
|
3014
|
-
close:
|
|
3015
|
-
stale:
|
|
3016
|
-
assign:
|
|
3122
|
+
reviewed: z9.number().int().min(0).max(1e4).default(0),
|
|
3123
|
+
close: z9.array(TriageItemSchema).max(200).default([]),
|
|
3124
|
+
stale: z9.array(TriageItemSchema).max(200).default([]),
|
|
3125
|
+
assign: z9.array(TriageAssignmentSchema).max(50).default([])
|
|
3017
3126
|
});
|
|
3018
3127
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
3019
|
-
id:
|
|
3020
|
-
runAt:
|
|
3128
|
+
id: z9.string(),
|
|
3129
|
+
runAt: z9.string(),
|
|
3021
3130
|
status: TriageStatusSchema,
|
|
3022
|
-
model:
|
|
3131
|
+
model: z9.string().nullable().default(null)
|
|
3023
3132
|
});
|
|
3024
|
-
var AcceptTriageSchema =
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
group:
|
|
3029
|
-
agent:
|
|
3030
|
-
noteIds:
|
|
3133
|
+
var AcceptTriageSchema = z9.discriminatedUnion("group", [
|
|
3134
|
+
z9.object({ group: z9.literal("close"), noteIds: z9.array(z9.string()).max(200).optional() }),
|
|
3135
|
+
z9.object({ group: z9.literal("stale"), noteIds: z9.array(z9.string()).max(200).optional() }),
|
|
3136
|
+
z9.object({
|
|
3137
|
+
group: z9.literal("assign"),
|
|
3138
|
+
agent: z9.string().min(1),
|
|
3139
|
+
noteIds: z9.array(z9.string()).max(200).optional()
|
|
3031
3140
|
})
|
|
3032
3141
|
]);
|
|
3033
|
-
var AcceptTriageResultSchema =
|
|
3034
|
-
accepted:
|
|
3035
|
-
failed:
|
|
3142
|
+
var AcceptTriageResultSchema = z9.object({
|
|
3143
|
+
accepted: z9.array(z9.string()),
|
|
3144
|
+
failed: z9.array(z9.object({ noteId: z9.string(), reason: z9.string() }))
|
|
3036
3145
|
});
|
|
3037
|
-
var DeliveryModeSchema =
|
|
3146
|
+
var DeliveryModeSchema = z9.enum(["poll", "self_hosted"]);
|
|
3038
3147
|
var WAKE_EVENT = "wake";
|
|
3039
3148
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
3040
|
-
var RegisterDeliverySchema =
|
|
3041
|
-
var OAuthStartSchema =
|
|
3042
|
-
provider:
|
|
3043
|
-
returnTo:
|
|
3149
|
+
var RegisterDeliverySchema = z9.object({ mode: DeliveryModeSchema });
|
|
3150
|
+
var OAuthStartSchema = z9.object({
|
|
3151
|
+
provider: z9.enum(["cma"]),
|
|
3152
|
+
returnTo: z9.string().min(1)
|
|
3044
3153
|
});
|
|
3045
|
-
var DeliveryConfigSchema =
|
|
3046
|
-
tokenId:
|
|
3154
|
+
var DeliveryConfigSchema = z9.object({
|
|
3155
|
+
tokenId: z9.string(),
|
|
3047
3156
|
mode: DeliveryModeSchema,
|
|
3048
3157
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
3049
3158
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
3050
3159
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
3051
|
-
realtime:
|
|
3160
|
+
realtime: z9.object({ url: z9.string(), anonKey: z9.string() }).nullable()
|
|
3052
3161
|
});
|
|
3053
|
-
var HostDecisionSchema =
|
|
3162
|
+
var HostDecisionSchema = z9.object({
|
|
3054
3163
|
/** The agent's token id: the row's `recipient`. */
|
|
3055
|
-
agent:
|
|
3056
|
-
decision:
|
|
3164
|
+
agent: z9.string().uuid(),
|
|
3165
|
+
decision: z9.enum(["stood_back", "took_over"]),
|
|
3057
3166
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
3058
|
-
goalId:
|
|
3167
|
+
goalId: z9.string().uuid().nullable().optional(),
|
|
3059
3168
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
3060
|
-
seenAt:
|
|
3169
|
+
seenAt: z9.string().datetime().nullable().optional(),
|
|
3061
3170
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
3062
|
-
since:
|
|
3171
|
+
since: z9.string().datetime().nullable().optional(),
|
|
3063
3172
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
3064
|
-
said:
|
|
3173
|
+
said: z9.string().max(300).optional()
|
|
3065
3174
|
});
|
|
3066
|
-
var WakeNudgeSchema =
|
|
3067
|
-
kind:
|
|
3068
|
-
notificationId:
|
|
3069
|
-
parentId:
|
|
3175
|
+
var WakeNudgeSchema = z9.object({
|
|
3176
|
+
kind: z9.enum(["reply", "request", "callback"]),
|
|
3177
|
+
notificationId: z9.string().optional(),
|
|
3178
|
+
parentId: z9.string()
|
|
3070
3179
|
});
|
|
3071
|
-
var PairingStatusSchema =
|
|
3072
|
-
var DeviceCodeSchema =
|
|
3073
|
-
device_code:
|
|
3074
|
-
user_code:
|
|
3075
|
-
verification_uri:
|
|
3076
|
-
verification_uri_complete:
|
|
3077
|
-
interval:
|
|
3078
|
-
expires_in:
|
|
3180
|
+
var PairingStatusSchema = z9.enum(["pending", "approved", "denied", "expired"]);
|
|
3181
|
+
var DeviceCodeSchema = z9.object({
|
|
3182
|
+
device_code: z9.string(),
|
|
3183
|
+
user_code: z9.string(),
|
|
3184
|
+
verification_uri: z9.string().url(),
|
|
3185
|
+
verification_uri_complete: z9.string().url(),
|
|
3186
|
+
interval: z9.number(),
|
|
3187
|
+
expires_in: z9.number()
|
|
3079
3188
|
});
|
|
3080
|
-
var DeviceInfoSchema =
|
|
3081
|
-
code:
|
|
3189
|
+
var DeviceInfoSchema = z9.object({
|
|
3190
|
+
code: z9.string(),
|
|
3082
3191
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3083
3192
|
* pre-filling the name field the human can edit. */
|
|
3084
|
-
name:
|
|
3193
|
+
name: z9.string(),
|
|
3085
3194
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3086
3195
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3087
3196
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3088
3197
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3089
|
-
agent:
|
|
3090
|
-
device:
|
|
3198
|
+
agent: z9.string().optional(),
|
|
3199
|
+
device: z9.string().nullable(),
|
|
3091
3200
|
status: PairingStatusSchema
|
|
3092
3201
|
});
|
|
3093
|
-
var DeviceTokenSchema =
|
|
3094
|
-
access_token:
|
|
3202
|
+
var DeviceTokenSchema = z9.object({
|
|
3203
|
+
access_token: z9.string(),
|
|
3095
3204
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3096
3205
|
* a default silly name). */
|
|
3097
|
-
name:
|
|
3098
|
-
device:
|
|
3206
|
+
name: z9.string(),
|
|
3207
|
+
device: z9.string().nullable(),
|
|
3099
3208
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3100
3209
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3101
|
-
voice:
|
|
3210
|
+
voice: z9.string().nullable().optional(),
|
|
3102
3211
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3103
3212
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3104
|
-
token_id:
|
|
3213
|
+
token_id: z9.string().nullable().optional(),
|
|
3105
3214
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3106
3215
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3107
3216
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3108
3217
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3109
|
-
workspace:
|
|
3218
|
+
workspace: z9.string().nullable().optional(),
|
|
3110
3219
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
3111
|
-
harness:
|
|
3112
|
-
session_id:
|
|
3220
|
+
harness: z9.enum(["claude", "codex", "agy"]).optional(),
|
|
3221
|
+
session_id: z9.string().uuid().optional(),
|
|
3113
3222
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
3114
3223
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
3115
3224
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
3116
3225
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
3117
|
-
full_session:
|
|
3118
|
-
uik_pub:
|
|
3226
|
+
full_session: z9.string().optional(),
|
|
3227
|
+
uik_pub: z9.string().nullable().optional()
|
|
3119
3228
|
});
|
|
3120
|
-
var SupportRequestSchema =
|
|
3121
|
-
email:
|
|
3122
|
-
message:
|
|
3123
|
-
name:
|
|
3229
|
+
var SupportRequestSchema = z9.object({
|
|
3230
|
+
email: z9.string().email().max(320),
|
|
3231
|
+
message: z9.string().trim().min(1).max(5e3),
|
|
3232
|
+
name: z9.string().trim().max(120).optional()
|
|
3124
3233
|
});
|
|
3125
|
-
var NotificationFeedbackKindSchema =
|
|
3234
|
+
var NotificationFeedbackKindSchema = z9.enum([
|
|
3126
3235
|
"break_down",
|
|
3127
3236
|
// "This should be more than one ask — break it down."
|
|
3128
3237
|
"regenerate_options",
|
|
@@ -3137,127 +3246,137 @@ var NotificationFeedbackKindSchema = z7.enum([
|
|
|
3137
3246
|
// anything else — the note carries it.
|
|
3138
3247
|
]);
|
|
3139
3248
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
3140
|
-
var QuestionRowSchema =
|
|
3249
|
+
var QuestionRowSchema = z9.object({
|
|
3141
3250
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
3142
|
-
id:
|
|
3143
|
-
deliveryId:
|
|
3144
|
-
entryId:
|
|
3251
|
+
id: z9.string(),
|
|
3252
|
+
deliveryId: z9.string(),
|
|
3253
|
+
entryId: z9.string(),
|
|
3145
3254
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
3146
|
-
needId:
|
|
3147
|
-
goalIds:
|
|
3255
|
+
needId: z9.string().nullable(),
|
|
3256
|
+
goalIds: z9.array(z9.string()),
|
|
3148
3257
|
/** The name of the work it is about, when the read could word it. */
|
|
3149
|
-
goalTitle:
|
|
3150
|
-
tokenId:
|
|
3151
|
-
name:
|
|
3152
|
-
title:
|
|
3153
|
-
body:
|
|
3154
|
-
select:
|
|
3155
|
-
options:
|
|
3156
|
-
hasPreview:
|
|
3157
|
-
blocking:
|
|
3158
|
-
askedAt:
|
|
3258
|
+
goalTitle: z9.string().optional(),
|
|
3259
|
+
tokenId: z9.string().optional(),
|
|
3260
|
+
name: z9.string(),
|
|
3261
|
+
title: z9.string(),
|
|
3262
|
+
body: z9.string(),
|
|
3263
|
+
select: z9.enum(["one", "many", "rank", "confirm", "text"]),
|
|
3264
|
+
options: z9.array(SlimOptionSchema),
|
|
3265
|
+
hasPreview: z9.boolean(),
|
|
3266
|
+
blocking: z9.boolean(),
|
|
3267
|
+
askedAt: z9.string().datetime(),
|
|
3159
3268
|
ring: InboxItemSchema.shape.ring,
|
|
3160
|
-
onCall:
|
|
3161
|
-
sealed:
|
|
3269
|
+
onCall: z9.literal(true).optional(),
|
|
3270
|
+
sealed: z9.boolean()
|
|
3162
3271
|
});
|
|
3163
|
-
var WorkStateSchema =
|
|
3164
|
-
var WorkRowSchema =
|
|
3165
|
-
id:
|
|
3166
|
-
parentId:
|
|
3167
|
-
title:
|
|
3272
|
+
var WorkStateSchema = z9.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
3273
|
+
var WorkRowSchema = z9.object({
|
|
3274
|
+
id: z9.string(),
|
|
3275
|
+
parentId: z9.string().nullable(),
|
|
3276
|
+
title: z9.string(),
|
|
3168
3277
|
/** Straight off the Goal. */
|
|
3169
3278
|
state: WorkStateSchema,
|
|
3170
|
-
owner:
|
|
3171
|
-
revision:
|
|
3279
|
+
owner: z9.string().nullable(),
|
|
3280
|
+
revision: z9.number().int(),
|
|
3172
3281
|
/** Open questions on it, counted to 100. */
|
|
3173
|
-
waiting:
|
|
3282
|
+
waiting: z9.number().int(),
|
|
3174
3283
|
/** Held by a gate on work that is not done. */
|
|
3175
|
-
blocked:
|
|
3176
|
-
lastProgressAt:
|
|
3284
|
+
blocked: z9.boolean(),
|
|
3285
|
+
lastProgressAt: z9.string().datetime().nullable(),
|
|
3177
3286
|
/** The line written for its newest progress note, else that note's first words. */
|
|
3178
|
-
line:
|
|
3287
|
+
line: z9.string().nullable(),
|
|
3179
3288
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
3180
|
-
children:
|
|
3181
|
-
createdAt:
|
|
3182
|
-
updatedAt:
|
|
3289
|
+
children: z9.number().int(),
|
|
3290
|
+
createdAt: z9.string().datetime(),
|
|
3291
|
+
updatedAt: z9.string().datetime(),
|
|
3183
3292
|
/** When anything at or under it last moved — the order the list is in. */
|
|
3184
|
-
activeAt:
|
|
3293
|
+
activeAt: z9.string().datetime(),
|
|
3185
3294
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
3186
|
-
sealed:
|
|
3295
|
+
sealed: z9.boolean()
|
|
3187
3296
|
});
|
|
3188
3297
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
3189
3298
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
3190
3299
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
3191
|
-
asking:
|
|
3192
|
-
oldestAskAt:
|
|
3300
|
+
asking: z9.number().int().nullable(),
|
|
3301
|
+
oldestAskAt: z9.string().datetime().nullable(),
|
|
3193
3302
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
3194
3303
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
3195
3304
|
* (`agent_holds`); null when that read failed. */
|
|
3196
|
-
holds:
|
|
3197
|
-
held:
|
|
3305
|
+
holds: z9.array(z9.object({ id: z9.string(), title: z9.string() })).nullable(),
|
|
3306
|
+
held: z9.number().int().nullable(),
|
|
3198
3307
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
3199
3308
|
* while none has, or when that read failed. */
|
|
3200
|
-
cold:
|
|
3309
|
+
cold: z9.string().datetime().nullable(),
|
|
3201
3310
|
/** The newest line of its working log, and when the harness saw it. */
|
|
3202
|
-
line:
|
|
3203
|
-
lineAt:
|
|
3311
|
+
line: z9.string().nullable(),
|
|
3312
|
+
lineAt: z9.string().datetime().nullable()
|
|
3204
3313
|
});
|
|
3205
|
-
var SnapshotSchema =
|
|
3314
|
+
var SnapshotSchema = z9.object({
|
|
3206
3315
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
3207
|
-
at:
|
|
3208
|
-
questions:
|
|
3316
|
+
at: z9.string().datetime(),
|
|
3317
|
+
questions: z9.object({
|
|
3209
3318
|
/** The newest 30 open cards, questions before updates. */
|
|
3210
|
-
items:
|
|
3319
|
+
items: z9.array(QuestionRowSchema),
|
|
3211
3320
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
3212
|
-
total:
|
|
3213
|
-
updates:
|
|
3214
|
-
putOff:
|
|
3321
|
+
total: z9.number().int(),
|
|
3322
|
+
updates: z9.number().int(),
|
|
3323
|
+
putOff: z9.number().int()
|
|
3215
3324
|
}).nullable(),
|
|
3216
|
-
agents:
|
|
3325
|
+
agents: z9.object({
|
|
3217
3326
|
/** Up to 60, most recently seen first. */
|
|
3218
|
-
items:
|
|
3219
|
-
more:
|
|
3327
|
+
items: z9.array(AgentRowSchema),
|
|
3328
|
+
more: z9.boolean()
|
|
3220
3329
|
}).nullable(),
|
|
3221
|
-
work:
|
|
3330
|
+
work: z9.object({
|
|
3222
3331
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
3223
|
-
items:
|
|
3332
|
+
items: z9.array(WorkRowSchema),
|
|
3224
3333
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
3225
3334
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
3226
3335
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
3227
3336
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
3228
|
-
counts:
|
|
3337
|
+
counts: z9.object({ live: z9.number().int(), waiting: z9.number().int(), notStarted: z9.number().int(), done: z9.number().int() })
|
|
3229
3338
|
}).nullable(),
|
|
3230
|
-
you:
|
|
3339
|
+
you: z9.object({
|
|
3231
3340
|
settings: UserSettingsSchema,
|
|
3232
|
-
callable:
|
|
3341
|
+
callable: z9.boolean(),
|
|
3233
3342
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
3234
|
-
computers:
|
|
3343
|
+
computers: z9.array(ComputerRowSchema).nullable()
|
|
3235
3344
|
}).nullable()
|
|
3236
3345
|
});
|
|
3237
|
-
var CallRecapSchema =
|
|
3238
|
-
call:
|
|
3239
|
-
status:
|
|
3240
|
-
startedAt:
|
|
3241
|
-
durationMs:
|
|
3242
|
-
agents:
|
|
3346
|
+
var CallRecapSchema = z9.object({
|
|
3347
|
+
call: z9.object({
|
|
3348
|
+
status: z9.string(),
|
|
3349
|
+
startedAt: z9.string(),
|
|
3350
|
+
durationMs: z9.number().nullable(),
|
|
3351
|
+
agents: z9.array(z9.object({ id: z9.string(), name: z9.string().nullable() }))
|
|
3243
3352
|
}),
|
|
3244
|
-
topics:
|
|
3245
|
-
goalId:
|
|
3246
|
-
title:
|
|
3247
|
-
owner:
|
|
3248
|
-
state:
|
|
3249
|
-
questions:
|
|
3250
|
-
/** `words` is always what they SAID, verbatim — the record, never replaced. `
|
|
3251
|
-
*
|
|
3252
|
-
*
|
|
3253
|
-
*
|
|
3254
|
-
* anything that is not an answer. */
|
|
3353
|
+
topics: z9.array(z9.object({
|
|
3354
|
+
goalId: z9.string().uuid(),
|
|
3355
|
+
title: z9.string(),
|
|
3356
|
+
owner: z9.string(),
|
|
3357
|
+
state: z9.string(),
|
|
3358
|
+
questions: z9.array(z9.object({ id: z9.string().uuid(), state: z9.string(), title: z9.string() })),
|
|
3359
|
+
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
3360
|
+
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
3361
|
+
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
3362
|
+
* neither wrote one. */
|
|
3255
3363
|
/** `about` is the request the line answered (its question), null for words that answered none —
|
|
3256
3364
|
* the key the screen groups on, so one question is one row however many times it was answered. */
|
|
3257
|
-
|
|
3365
|
+
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
3366
|
+
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
3367
|
+
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
3368
|
+
lines: z9.array(z9.object({
|
|
3369
|
+
entryId: z9.string().uuid(),
|
|
3370
|
+
words: z9.string(),
|
|
3371
|
+
summary: z9.string().optional(),
|
|
3372
|
+
about: z9.string().nullable().optional(),
|
|
3373
|
+
reply: z9.string().nullable().optional(),
|
|
3374
|
+
replyKind: z9.string().nullable().optional(),
|
|
3375
|
+
at: z9.array(z9.number()).optional()
|
|
3376
|
+
}))
|
|
3258
3377
|
})),
|
|
3259
|
-
unfiled:
|
|
3260
|
-
more:
|
|
3378
|
+
unfiled: z9.array(z9.object({ lineId: z9.string().uuid(), words: z9.string(), atMs: z9.number() })),
|
|
3379
|
+
more: z9.object({ lines: z9.number(), entries: z9.number(), topics: z9.number() })
|
|
3261
3380
|
});
|
|
3262
3381
|
function sessionSlot(sessionId2) {
|
|
3263
3382
|
const id2 = sessionId2 ?? sessionId();
|
|
@@ -3852,7 +3971,9 @@ function goalView(g) {
|
|
|
3852
3971
|
})) : void 0,
|
|
3853
3972
|
blockedBy: g.blockedDependencyGoalIds,
|
|
3854
3973
|
dueAt: g.dueAt,
|
|
3855
|
-
|
|
3974
|
+
// Who last wrote on it, and when: any agent can work on any Goal, and working on it is a write.
|
|
3975
|
+
workedBy: g.workedBy,
|
|
3976
|
+
workedAt: g.workedAt,
|
|
3856
3977
|
conversation: lines2,
|
|
3857
3978
|
next: owes ? `${owes}${g.message ?? ""}`.trim() : g.message,
|
|
3858
3979
|
more: more(g)
|
|
@@ -3892,11 +4013,15 @@ function deliveryView(d) {
|
|
|
3892
4013
|
notSent: d.notSent,
|
|
3893
4014
|
joinedCard: d.joinedCard,
|
|
3894
4015
|
joinedCall: d.joinedCall,
|
|
4016
|
+
// WHERE THE CALL STANDS, as one word beside the advice (owner, 2026-10-08): starting, ringing,
|
|
4017
|
+
// live (the person is on it now) or ended. A contact that joined a call already happening reads
|
|
4018
|
+
// that call, so it says `live`.
|
|
4019
|
+
call: d.callState,
|
|
3895
4020
|
waitOutcome: d.waitOutcome,
|
|
3896
4021
|
acknowledged: d.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
3897
4022
|
next: [
|
|
3898
4023
|
failures(d.results),
|
|
3899
|
-
d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : pending ? `${d.demoted ? `${d.demoted} ` : ""}Decision pending on this call. ${d.waitOutcome === "expired" ? "Nothing was said in the window. " : ""}Wait for the answer with contact({wait:true}) \u2014 one bounded window each time, and never resend the question.
|
|
4024
|
+
d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : pending ? `${d.demoted ? `${d.demoted} ` : ""}Decision pending on this call. ${d.waitOutcome === "expired" ? "Nothing was said in the window. " : ""}Wait for the answer with contact({wait:true}) \u2014 one bounded window each time, and never resend the question. While the Call is open, keep waiting window after window: a quiet window is the call still going, and the person may give you follow-ups or instructions on it. Once the Call has ended, stop waiting, leave any question open, and collect the answer with contact({wait:false}) or get_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
|
|
3900
4025
|
].filter(Boolean).join(" ")
|
|
3901
4026
|
});
|
|
3902
4027
|
}
|
|
@@ -3923,6 +4048,7 @@ function receivedView(r, now = Date.now()) {
|
|
|
3923
4048
|
const assigned = w.assigned ?? [];
|
|
3924
4049
|
const stalled = w.stalled ?? [];
|
|
3925
4050
|
const others = w.stalledOthers ?? [];
|
|
4051
|
+
const rings = w.rings ?? [];
|
|
3926
4052
|
const owed = r.events.filter((e) => e.kind === "question");
|
|
3927
4053
|
const refused = (r.acknowledged ?? []).filter((a) => a.refused);
|
|
3928
4054
|
return compact({
|
|
@@ -3939,11 +4065,18 @@ function receivedView(r, now = Date.now()) {
|
|
|
3939
4065
|
// YOUR STALLED WORK (#2257), and OTHER AGENTS' (owner, 2026-09-23): any agent may take it over.
|
|
3940
4066
|
stalled,
|
|
3941
4067
|
stalledOthers: others,
|
|
4068
|
+
// WHEN THE PHONE RINGS AGAIN FOR YOUR ASKS (owner, 2026-10-08): your Call went unheard and closed.
|
|
4069
|
+
rings: rings.length ? rings : void 0,
|
|
4070
|
+
// The person's standing rules for how their agents work (owner, 2026-10-08). On the read an agent makes
|
|
4071
|
+
// on startup and after a wake (contact({wait:false}), and every hosted receive), never on a window of a
|
|
4072
|
+
// held wait, which would repeat them window after window.
|
|
4073
|
+
houseRules: r.waitOutcome === "not_waited" ? w.houseRules : void 0,
|
|
3942
4074
|
next: [
|
|
3943
|
-
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length ? "No messages." : "Nothing is waiting.",
|
|
4075
|
+
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length || rings.length ? "No messages." : "Nothing is waiting.",
|
|
3944
4076
|
...owed.length ? [`You owe ${owed.length === 1 ? "an answer" : `${owed.length} answers`}: contact({ answers: [{ questionId: "<questionId>", answer: { text: <your answer> } }] }) for each question; a question is answered, never acknowledged.`] : [],
|
|
3945
4077
|
...r.hasMore ? ["More are waiting: acknowledge these, then receive again."] : [],
|
|
3946
4078
|
...r.waitOutcome === "expired" ? ["Nothing arrived in the window; receive again to keep waiting, without sending again."] : [],
|
|
4079
|
+
...rings.map((r2) => rerings(r2.ringAt, now)),
|
|
3947
4080
|
...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): read one with get_goal({goalId}); your first write to it starts it.`] : [],
|
|
3948
4081
|
...w.claimable && !assigned.length ? [`Next waiting on you: ${w.claimable.title ?? w.claimable.goalId} (get_goal({ goalId: "${w.claimable.goalId}" })).`] : [],
|
|
3949
4082
|
...stalled.length ? [`${stalled.length} of your Goals have had no progress${stalled.length === 1 ? " " : ""}${quietFor(stalled, now)}: report on each with a contact update, or finish or cancel it with manage_goals.`] : [],
|
|
@@ -3953,6 +4086,11 @@ function receivedView(r, now = Date.now()) {
|
|
|
3953
4086
|
].join(" ")
|
|
3954
4087
|
});
|
|
3955
4088
|
}
|
|
4089
|
+
function rerings(ringAt, now) {
|
|
4090
|
+
if (!ringAt) return "A Call of yours went unanswered and will not ring again: its questions wait on the person's card. Collect the answers on your next wake; nothing needs resending.";
|
|
4091
|
+
const minutes = Math.max(0, Math.round((Date.parse(ringAt) - now) / 6e4));
|
|
4092
|
+
return `A Call of yours went unanswered; the phone rings again by itself at ${ringAt.slice(11, 16)} UTC (in ${minutes} min), carrying the same asks. Nothing needs resending: schedule a wake-up for then and receive again.`;
|
|
4093
|
+
}
|
|
3956
4094
|
function managedView(m, changes) {
|
|
3957
4095
|
const results = m.results ?? [];
|
|
3958
4096
|
const failed = results.filter((r) => r.status !== "applied");
|