@paigy/mcp 0.40.40 → 0.40.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -14
- package/dist/{chunk-6FG3PZR2.js → chunk-6V4X3EFA.js} +1 -1
- package/dist/{chunk-PKJJSHLW.js → chunk-I3WDI2JV.js} +3 -3
- package/dist/{chunk-H2EBILBE.js → chunk-NNB3HQTF.js} +925 -745
- package/dist/{chunk-V4OVLFMP.js → chunk-W3FK4FD6.js} +879 -738
- package/dist/{dist-EJWDUF5J.js → dist-ZBYGV7QX.js} +5 -1
- package/dist/enable.js +6 -3
- package/dist/index.js +23 -6
- package/dist/listen.js +98 -29
- 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 +2 -1
|
@@ -7,21 +7,22 @@ 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 z13 } from "zod";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { z as z3 } from "zod";
|
|
13
13
|
import { z as z2 } from "zod";
|
|
14
|
-
import { z as
|
|
14
|
+
import { z as z6 } from "zod";
|
|
15
15
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
16
16
|
import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
17
17
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
|
|
18
18
|
import { z as z4 } from "zod";
|
|
19
|
-
import { z as
|
|
19
|
+
import { z as z5 } from "zod";
|
|
20
20
|
import { z as z7 } from "zod";
|
|
21
21
|
import { z as z8 } from "zod";
|
|
22
22
|
import { z as z9 } from "zod";
|
|
23
23
|
import { z as z10 } from "zod";
|
|
24
24
|
import { z as z11 } from "zod";
|
|
25
|
+
import { z as z12 } from "zod";
|
|
25
26
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
26
27
|
import { homedir as homedir2 } from "os";
|
|
27
28
|
import { join as join2 } from "path";
|
|
@@ -1517,16 +1518,16 @@ function parseWhen(said2, tz, nowMs, read22 = "earliest") {
|
|
|
1517
1518
|
let hour = null;
|
|
1518
1519
|
let minute = 0;
|
|
1519
1520
|
const spoken = new RegExp(`\\bat\\s+(${Object.keys(SPOKEN_HOUR).join("|")})\\b`, "i").exec(text);
|
|
1520
|
-
const
|
|
1521
|
+
const clock22 = CLOCK.exec(text);
|
|
1521
1522
|
if (spoken) {
|
|
1522
1523
|
hour = SPOKEN_HOUR[spoken[1].toLowerCase()];
|
|
1523
|
-
} else if (
|
|
1524
|
-
hour = Number(
|
|
1525
|
-
minute =
|
|
1524
|
+
} else if (clock22 && (clock22[3] || /\bat\s/i.test(clock22[0]))) {
|
|
1525
|
+
hour = Number(clock22[1]);
|
|
1526
|
+
minute = clock22[2] ? Number(clock22[2]) : 0;
|
|
1526
1527
|
if (hour > 23 || minute > 59) return null;
|
|
1527
1528
|
}
|
|
1528
1529
|
if (hour !== null) {
|
|
1529
|
-
const mer =
|
|
1530
|
+
const mer = clock22?.[3]?.toLowerCase().replace(/\./g, "") ?? "";
|
|
1530
1531
|
const pmWord = /\b(p\.?m\.?|afternoon|evening|tonight|at night)\b/i.test(text);
|
|
1531
1532
|
const amWord = /\b(a\.?m\.?|morning)\b/i.test(text);
|
|
1532
1533
|
if (mer.startsWith("p") || pmWord && !amWord) {
|
|
@@ -1643,7 +1644,7 @@ var ASKING_RULES = `HOW TO ASK:
|
|
|
1643
1644
|
6. Read the Goal before asking; never re-ask what was answered or shipped. \`replied\` means they chose no option: it is still yours to settle.
|
|
1644
1645
|
7. Give options when you have them. Each label names its choice in full (never "Option 2"); never add "Other". \`pickMode\`: one (default), many, rank.
|
|
1645
1646
|
8. A diagnosis says when, why and how it happens, then proposes one fix.
|
|
1646
|
-
9. Give a report its \`reply\`: one line in THEIR voice for THAT report, so you learn whether they verified it or only received it. Required when it
|
|
1647
|
+
9. Give a report its \`reply\`: one line in THEIR voice for THAT report, so you learn whether they verified it or only received it. Required when it is delivered or its Goal is done.`;
|
|
1647
1648
|
function draft2020(node) {
|
|
1648
1649
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
1649
1650
|
if (node && typeof node === "object") {
|
|
@@ -1741,11 +1742,11 @@ var questionFields = {
|
|
|
1741
1742
|
),
|
|
1742
1743
|
goalId: z4.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
1743
1744
|
units,
|
|
1744
|
-
options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200. They can always answer in their own words, so no 'Other' or 'None of these'."),
|
|
1745
|
+
options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own and differs from the others (duplicate_option_label), at most 50 characters; a hint, at most 200. They can always answer in their own words, so no 'Other' or 'None of these'."),
|
|
1745
1746
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
1746
1747
|
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
1747
1748
|
pickMode: z4.enum(["one", "many", "rank"]).optional().describe(
|
|
1748
|
-
'How the options are answered: "one" (the default) a pick, for alternatives; "many" a checklist, for independent options they may want several of; "rank" an order of all of them. Only with options.'
|
|
1749
|
+
'How the options are answered: "one" (the default) a pick, for alternatives; "many" a checklist, for independent options they may want several of; "rank" an order of all of them. Only with options; "many" and "rank" need at least two (too_few_options).'
|
|
1749
1750
|
),
|
|
1750
1751
|
// WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
|
|
1751
1752
|
// blocks work asks for a call (the person's settings and ring cooldowns still decide).
|
|
@@ -1757,11 +1758,39 @@ var questionFields = {
|
|
|
1757
1758
|
var refineQuestion = (q, ctx) => {
|
|
1758
1759
|
refuseOverCaps(q.question, q.units, "question", ctx);
|
|
1759
1760
|
if (q.pickMode && !q.options) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
|
|
1761
|
+
refuseThinOptions(q, ctx);
|
|
1760
1762
|
const ids = (q.options ?? []).map((o) => o.id);
|
|
1761
1763
|
if (ids.some((id2) => id2) && (ids.some((id2) => !id2) || new Set(ids).size !== ids.length)) {
|
|
1762
1764
|
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
|
|
1763
1765
|
}
|
|
1764
1766
|
};
|
|
1767
|
+
var MIN_LIST_OPTIONS = 2;
|
|
1768
|
+
function refuseThinOptions(q, ctx) {
|
|
1769
|
+
const options = q.options ?? [];
|
|
1770
|
+
if ((q.pickMode === "many" || q.pickMode === "rank") && options.length < MIN_LIST_OPTIONS) {
|
|
1771
|
+
ctx.addIssue({
|
|
1772
|
+
code: z4.ZodIssueCode.custom,
|
|
1773
|
+
path: ["options"],
|
|
1774
|
+
params: { refusal: "too_few_options", length: options.length, min: MIN_LIST_OPTIONS },
|
|
1775
|
+
message: `too_few_options: pickMode "${q.pickMode}" needs at least ${MIN_LIST_OPTIONS} options; this question has ${options.length}. Add the other choices, or leave pickMode out.`
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1779
|
+
options.forEach((o, index) => {
|
|
1780
|
+
const key2 = o.label.trim().toLowerCase();
|
|
1781
|
+
const first = seen.get(key2);
|
|
1782
|
+
if (first === void 0) {
|
|
1783
|
+
seen.set(key2, index);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
ctx.addIssue({
|
|
1787
|
+
code: z4.ZodIssueCode.custom,
|
|
1788
|
+
path: ["options", index, "label"],
|
|
1789
|
+
params: { refusal: "duplicate_option_label", index, first },
|
|
1790
|
+
message: `duplicate_option_label: options[${index}] repeats the label of options[${first}]. Each option names a different choice.`
|
|
1791
|
+
});
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1765
1794
|
var QuestionInputSchema = z4.object({
|
|
1766
1795
|
questionId: z4.string().uuid().optional().describe("Optional: your own stable id for this question (a UUID), so a resend is the same question. Minted for you when omitted; the result returns it."),
|
|
1767
1796
|
...questionFields
|
|
@@ -1787,12 +1816,15 @@ var sendFields = {
|
|
|
1787
1816
|
"COMPLETIONS close and report in one operation, the same contract as manage_goals kind:complete: closure and the report commit together or neither does. Required children and dependencies must already be finished. options suggest next steps: a tap acknowledges the report and requests that step, and never reopens the Goal (follow-up work needs a new one). No reviewer rejudges it: say only what your evidence supports. Ordinary updates never close a Goal."
|
|
1788
1817
|
),
|
|
1789
1818
|
updates: z4.array(UpdateInputSchema).min(1).max(20).optional().describe(
|
|
1790
|
-
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress
|
|
1819
|
+
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress, which the person is not told. Every update reaches the other agents that wrote on its Goal in the last hour: to tell another agent something, write it as an update on that agent's Goal."
|
|
1791
1820
|
),
|
|
1792
1821
|
answers: z4.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
|
|
1793
1822
|
withdrawQuestionIds: z4.array(questionHandle).min(1).max(10).optional().describe(
|
|
1794
1823
|
"Questions you asked that no longer need an answer: you acted on them yourself, they no longer matter, or you asked wrongly. Each is cancelled, not answered: its card closes and nothing rings for it."
|
|
1795
1824
|
),
|
|
1825
|
+
bumpQuestionIds: z4.array(questionHandle).min(1).max(10).optional().describe(
|
|
1826
|
+
"Your own questions still waiting on the person that you want them to notice again. Each moves back to the top of its card with one new notification; nothing is resent and no second card is made."
|
|
1827
|
+
),
|
|
1796
1828
|
questionDependencies: z4.array(QuestionDependencySchema).min(1).max(20).optional().describe(
|
|
1797
1829
|
"Add or remove what a Question you asked holds up, after asking it."
|
|
1798
1830
|
)
|
|
@@ -1808,7 +1840,7 @@ var ContactSchema = z4.object({
|
|
|
1808
1840
|
"Only controls when this returns. Sending: false (the default) returns at once; true waits up to about 45 seconds for a response to what you sent. Sending nothing: true (the default) waits up to about 45 seconds for anything addressed to you; false returns what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived; wait again without sending again), or `not_waited`."
|
|
1809
1841
|
)
|
|
1810
1842
|
}).strict();
|
|
1811
|
-
var sendsAnything = (c) => !!(c.completions?.length || c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
1843
|
+
var sendsAnything = (c) => !!(c.completions?.length || c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.bumpQuestionIds?.length || c.questionDependencies?.length);
|
|
1812
1844
|
var ContactWireSchema = z4.object({
|
|
1813
1845
|
operationId: z4.string().uuid(),
|
|
1814
1846
|
...sendFields,
|
|
@@ -1822,13 +1854,42 @@ LENGTH: the question, message or answer is the one point, at most ${ASK_MAX} cha
|
|
|
1822
1854
|
UPDATES ask nothing. One reaches the person only when they asked for it, it answers something they said, or its Goal is done; else it is kept as progress. One that reaches them joins an open report card or a live call.
|
|
1823
1855
|
|
|
1824
1856
|
RECEIVING: contact({}) waits ~45 s; contact({wait:false}) returns \`events\` at once. Confirm the ones you handled with contact({ackEventIds}).`;
|
|
1825
|
-
var
|
|
1826
|
-
|
|
1857
|
+
var GUIDE = '# Paigy\n\nThe user is remote. Reach them only through Paigy\'s tools, never the terminal. The server\'s own instructions and each tool\'s description are the contract: read them. Every read ends in `next`, the one step to take.\n\nThis same guide is inside the MCP: the `help` tool (whole, or one `topic`) and the `/mcp__paigy__help` prompt. `/mcp__paigy__welcome` starts a session.\n\nHosted (no machine): skip Pairing and Listening; answers wake you.\n\n## Pairing\n\n- Call `configure` first. With a device credential on this machine it hatches you at once.\n- Otherwise print its `user_message` to the user, then do what the result says:\n - `awaiting_approval`: call `configure` again with its `device_code` in the same turn. On `pending`, call again.\n - `awaiting_confirmation`: end your turn with the code as your whole reply; call `configure` with the `device_code` next turn. Calling it in the same turn drops the code.\n- When it returns `enable_command`, print it for the user to run. Do not run it yourself.\n- `configure({ logOut: true })` logs out only this agent, and only when the user asks.\n\n## Listening\n\n- Call `listen` once per session.\n - `listening`: done.\n - `receiving`: a listener runs but cannot wake you. Do not start a second.\n - `unpaired`: call `configure`.\n - `start`: run its `command` exactly, now.\n- Claude Code: run it with the **Monitor** tool, never a background Bash command. Start it again whenever the monitor expires.\n- Codex: run its `command` as a long-running background process. It queues each answer into this thread (`CODEX_THREAD_ID`).\n- With no `listen` tool, run `npx -y -p @paigy/mcp@latest paigy-listen --brief` the same way.\n- One listener per session. A `paigy-listen` in `ps` belongs to another session; ask `listen`, never `ps`.\n- Each line it prints is work: receive, read the Goal, act.\n\n## Receiving\n\n- On startup and after every wake or listener line: `contact({ wait: false })`.\n- Events: `question` (you owe an answer: send `answers`), `update` (something new on your Goal: an answer, a reply, a review), `instruction` (work sent to you).\n- Read the Goal with `get_goals` before acting. An answer arrives as an `update` event and lands on the question\'s Goal. A question reading `replied` means they chose no option: settle it from their words.\n- Acknowledge each event you handled: `contact({ ackEventIds })`. Reading acknowledges nothing; events repeat until acknowledged. A `question` event is answered, never acknowledged away.\n- `contact({ wait: true })` waits about 45 s. On `expired`, wait again without re-sending, or stop. No while-loops: while any Call is open, keep receiving through empty windows; otherwise stop at one empty window.\n\n## Asking\n\n- One question per object in `questions`, at most 80 characters. Context goes in `units`: at most 12, title \u226450, body \u2264200, one idea each.\n- `goalId` names an existing Goal. Create it with `manage_goals` first.\n- Give options when you have them: up to 6, label \u226450 naming the choice in full, `hint` \u2264200 saying what it does. Never "Other" or "Option 2". `pickMode`: `one` (default), `many`, `rank`.\n- `workItBlocks` only when you are truly stopped: it asks for a call.\n- Read the Goal before asking. Never re-ask an open or answered question, or one whose work shipped.\n- Take back a question that no longer matters: `withdrawQuestionIds`.\n- An answer you give (`answers`) is at most 80 characters.\n\n## Updates and completions\n\n- An update (`updates`: `message` \u226480, `goalIds`) asks nothing. It reaches the user only when they asked for it, when it answers something they said, or when its Goal is done; otherwise it stays on the Goal as progress.\n- Updates never complete a Goal.\n- `reply` is the button in the user\'s voice instead of "Got it", \u226450 characters ("Sounds good, I\'ll test later"). Required (`reply_required`) when the update is delivered on request (`userExplicitlyRequested`) or any Goal it names is already done.\n- Set `userExplicitlyRequested` only to relay the user\'s own words: `"any"` for "send it to me", `"call"` for "call me". Never to make a message louder.\n- Finish with a completion (`contact.completions`, or `manage_goals` kind `complete`): `goalId`, `message` \u226450, `reply` \u226450, optional `units`, optional next-step `options` {label, hint}. It closes and reports in one step. Say only what your evidence supports.\n- Read `results`: each item applies on its own, and a failed one names why.\n\n## Goals\n\n- `manage_goals` `changes`, applied in order:\n - `create`: `title` (1-5 words) and `outcome` (one paragraph, \u2264500). More work is child Goals: `parentGoalId`. New ids come back in `results[i].goalId`.\n - `assign`: `ownerId`, a participant as `check_activity` shows it.\n - `defer`: `until`. You are woken when it passes. This is how you follow up later.\n - `dependency`: `add`/`remove`; `goalId` waits on `dependsOnGoalId` to `start` or `complete`.\n - `edit`, `move`, `state` (open, completed, canceled).\n- Any open question on a Goal, anyone\'s, blocks its completion (`goal_blocked`). Before completing, withdraw yours, finish or move its required children, and clear its dependencies.\n- On `goal_revision_conflict`, read the Goal again and reconsider.\n- Before overlapping work, read `check_activity` and the Goal\'s other contributors. Every Goal of your person is open to you; your first write puts you on it.\n- `search` finds earlier words, Goals and answers. Nothing found is not proof of absence.\n\n## Calls\n\n- Call only when the user asked: `userExplicitlyRequested: "call"` on that item. A question with `workItBlocks` may ring too.\n- Any contact while the user is on a call joins that call (`joinedCall` in `results`). Several questions arrive as one call.\n- While any Call is open, keep receiving.\n- An open Call Delivery is not proof it rang. `get_delivery` reads a Call\'s state, and with `transcript: true` what was said.\n';
|
|
1858
|
+
var HELP_TOPICS = ["pairing", "listening", "receiving", "asking", "updates", "goals", "calls"];
|
|
1859
|
+
var unknownTopic = (topic) => `unknown_topic: ${JSON.stringify(topic)} is not a section of the guide. Topics: ${HELP_TOPICS.join(", ")}; or omit topic for the whole guide.`;
|
|
1860
|
+
var HelpSchema = z5.object({
|
|
1861
|
+
topic: z5.enum(HELP_TOPICS, { errorMap: (_issue, ctx) => ({ message: unknownTopic(ctx.data) }) }).optional().describe("One section of the guide. Omit it for the whole guide.")
|
|
1862
|
+
}).strict();
|
|
1863
|
+
var HELP_DESCRIPTION = "Start here: how to use Paigy, in full \u2014 pairing, listening, receiving, asking, updates and completions, Goals and calls. Pass a topic for one section. Read-only; needs no setup.";
|
|
1864
|
+
function section(topic) {
|
|
1865
|
+
const parts = GUIDE.split(/^(?=## )/m);
|
|
1866
|
+
const found = parts.find((p) => p.slice(3).toLowerCase().startsWith(topic));
|
|
1867
|
+
if (!found) throw new Error(`help_section_missing: the guide has no "## ${topic}" section`);
|
|
1868
|
+
return found.trim() + "\n";
|
|
1869
|
+
}
|
|
1870
|
+
function helpView({ topic }) {
|
|
1871
|
+
return {
|
|
1872
|
+
topic: topic ?? "all",
|
|
1873
|
+
guide: topic ? section(topic) : GUIDE,
|
|
1874
|
+
topics: [...HELP_TOPICS],
|
|
1875
|
+
next: "Not set up? Call configure. Then contact({wait:false}) for the work addressed to you."
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
var AGENT_PROMPTS = [
|
|
1879
|
+
{
|
|
1880
|
+
name: "help",
|
|
1881
|
+
description: "How to use Paigy: the whole guide, or one topic of it.",
|
|
1882
|
+
arguments: [{ name: "topic", description: `One section: ${HELP_TOPICS.join(", ")}. Omit it for the whole guide.`, required: false }]
|
|
1883
|
+
},
|
|
1884
|
+
{ name: "welcome", description: "Start here: who you are on Paigy, then listen and collect your open work." }
|
|
1885
|
+
];
|
|
1886
|
+
var CreateGoalSchema = z6.object({
|
|
1887
|
+
outcome: z6.string().trim().min(1).max(1e4),
|
|
1827
1888
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
1828
1889
|
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
1829
|
-
title:
|
|
1830
|
-
ownerParticipant:
|
|
1831
|
-
idempotencyKey:
|
|
1890
|
+
title: z6.string().trim().min(1).max(80).optional(),
|
|
1891
|
+
ownerParticipant: z6.string().trim().min(1).optional(),
|
|
1892
|
+
idempotencyKey: z6.string().trim().min(1).max(200),
|
|
1832
1893
|
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
1833
1894
|
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
1834
1895
|
* worked"). It could not have been: this door took no parent, so the only route was
|
|
@@ -1836,63 +1897,63 @@ var CreateGoalSchema = z5.object({
|
|
|
1836
1897
|
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
1837
1898
|
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
1838
1899
|
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
1839
|
-
parentGoalId:
|
|
1900
|
+
parentGoalId: z6.string().uuid().optional(),
|
|
1840
1901
|
/** The repository or project identifier this Goal belongs to (#2280) — e.g. "owner/repo" or
|
|
1841
1902
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
1842
|
-
repo:
|
|
1903
|
+
repo: z6.string().trim().min(1).max(200).optional()
|
|
1843
1904
|
});
|
|
1844
|
-
var UpdateGoalSchema =
|
|
1845
|
-
revision:
|
|
1846
|
-
changes:
|
|
1847
|
-
outcome:
|
|
1905
|
+
var UpdateGoalSchema = z6.object({
|
|
1906
|
+
revision: z6.number().int().positive(),
|
|
1907
|
+
changes: z6.object({
|
|
1908
|
+
outcome: z6.string().trim().min(1).max(1e4).optional(),
|
|
1848
1909
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
1849
1910
|
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
1850
|
-
title:
|
|
1851
|
-
ownerParticipant:
|
|
1852
|
-
parentGoalId:
|
|
1853
|
-
dependencies:
|
|
1854
|
-
children:
|
|
1855
|
-
state:
|
|
1856
|
-
progress:
|
|
1857
|
-
reviewed:
|
|
1858
|
-
dueAt:
|
|
1911
|
+
title: z6.string().trim().min(1).max(80).nullable().optional(),
|
|
1912
|
+
ownerParticipant: z6.string().trim().min(1).optional(),
|
|
1913
|
+
parentGoalId: z6.string().uuid().nullable().optional(),
|
|
1914
|
+
dependencies: z6.array(z6.object({ goalId: z6.string().uuid(), gate: z6.enum(["start", "finish"]) }).strict()).optional(),
|
|
1915
|
+
children: z6.array(z6.object({ outcome: z6.string().trim().min(1).max(1e4), ownerParticipant: z6.string().trim().min(1), gate: z6.enum(["start", "finish"]).optional() }).strict()).optional(),
|
|
1916
|
+
state: z6.enum(["active", "done", "cancelled"]).optional(),
|
|
1917
|
+
progress: z6.string().trim().min(1).max(1e4).optional(),
|
|
1918
|
+
reviewed: z6.literal(true).optional(),
|
|
1919
|
+
dueAt: z6.string().datetime({ offset: true }).nullable().optional()
|
|
1859
1920
|
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
1860
|
-
reason:
|
|
1861
|
-
operationId:
|
|
1921
|
+
reason: z6.string().trim().min(1).max(2e3),
|
|
1922
|
+
operationId: z6.string().uuid().optional()
|
|
1862
1923
|
}).strict();
|
|
1863
|
-
var changedGoal =
|
|
1864
|
-
var goalTitle =
|
|
1924
|
+
var changedGoal = z6.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
1925
|
+
var goalTitle = z6.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
1865
1926
|
var GOAL_OUTCOME_MAX = 500;
|
|
1866
|
-
var goalOutcome =
|
|
1867
|
-
var CreateChange =
|
|
1868
|
-
kind:
|
|
1927
|
+
var goalOutcome = z6.string().trim().min(1).max(GOAL_OUTCOME_MAX, `outcome_too_long: a Goal's outcome is one paragraph, at most ${GOAL_OUTCOME_MAX} characters; split the rest into child Goals (create with parentGoalId).`).describe(`The desired result, one paragraph of at most ${GOAL_OUTCOME_MAX} characters. More than that is more work: create child Goals under it (parentGoalId). There is no third description field.`);
|
|
1928
|
+
var CreateChange = z6.object({
|
|
1929
|
+
kind: z6.literal("create"),
|
|
1869
1930
|
title: goalTitle,
|
|
1870
1931
|
outcome: goalOutcome,
|
|
1871
|
-
ownerId:
|
|
1872
|
-
parentGoalId:
|
|
1873
|
-
sourceEntryIds:
|
|
1932
|
+
ownerId: z6.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
|
|
1933
|
+
parentGoalId: z6.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
1934
|
+
sourceEntryIds: z6.array(z6.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
1874
1935
|
}).strict();
|
|
1875
|
-
var EditChange =
|
|
1876
|
-
var StateChange =
|
|
1936
|
+
var EditChange = z6.object({ kind: z6.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
|
|
1937
|
+
var StateChange = z6.object({ kind: z6.literal("state"), goalId: changedGoal, state: z6.enum(["open", "completed", "canceled"]).describe(
|
|
1877
1938
|
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
1878
1939
|
) }).strict();
|
|
1879
|
-
var AssignChange =
|
|
1880
|
-
var DeferChange =
|
|
1940
|
+
var AssignChange = z6.object({ kind: z6.literal("assign"), goalId: changedGoal, ownerId: z6.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
1941
|
+
var DeferChange = z6.object({ kind: z6.literal("defer"), goalId: changedGoal, until: z6.string().datetime({ offset: true }).nullable().describe(
|
|
1881
1942
|
"Postpone it until this instant: it waits until then, and its owner is woken when it passes (a promise to follow up later). null takes the postponement back."
|
|
1882
1943
|
) }).strict();
|
|
1883
|
-
var MoveChange =
|
|
1944
|
+
var MoveChange = z6.object({ kind: z6.literal("move"), goalId: changedGoal, parentGoalId: z6.string().uuid().nullable().describe(
|
|
1884
1945
|
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
1885
1946
|
) }).strict();
|
|
1886
|
-
var DependencyChange =
|
|
1887
|
-
kind:
|
|
1888
|
-
change:
|
|
1947
|
+
var DependencyChange = z6.object({
|
|
1948
|
+
kind: z6.literal("dependency"),
|
|
1949
|
+
change: z6.enum(["add", "remove"]),
|
|
1889
1950
|
goalId: changedGoal,
|
|
1890
|
-
dependsOnGoalId:
|
|
1891
|
-
action:
|
|
1892
|
-
reason:
|
|
1951
|
+
dependsOnGoalId: z6.string().uuid().describe("The Goal it waits on."),
|
|
1952
|
+
action: z6.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
1953
|
+
reason: z6.string().trim().min(1).max(250).optional().describe("Why, at most 250 characters, as an explanation, never as policy.")
|
|
1893
1954
|
}).strict();
|
|
1894
|
-
var read = { revision:
|
|
1895
|
-
var GoalChangeSchema =
|
|
1955
|
+
var read = { revision: z6.number().int().positive().optional() };
|
|
1956
|
+
var GoalChangeSchema = z6.discriminatedUnion("kind", [
|
|
1896
1957
|
CreateChange,
|
|
1897
1958
|
CompleteChangeSchema.extend(read),
|
|
1898
1959
|
EditChange.extend(read),
|
|
@@ -1904,43 +1965,46 @@ var GoalChangeSchema = z5.discriminatedUnion("kind", [
|
|
|
1904
1965
|
]);
|
|
1905
1966
|
var editNames = (changes, ctx) => changes.forEach((c, i) => {
|
|
1906
1967
|
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
1907
|
-
ctx.addIssue({ code:
|
|
1968
|
+
ctx.addIssue({ code: z6.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
1908
1969
|
}
|
|
1909
1970
|
});
|
|
1910
|
-
var ManageGoalsSchema =
|
|
1911
|
-
requestId:
|
|
1912
|
-
changes:
|
|
1971
|
+
var ManageGoalsSchema = z6.object({
|
|
1972
|
+
requestId: z6.string().uuid().optional(),
|
|
1973
|
+
changes: z6.array(GoalChangeSchema).min(1).max(50)
|
|
1913
1974
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1914
|
-
var ManageGoalsToolSchema =
|
|
1915
|
-
changes:
|
|
1975
|
+
var ManageGoalsToolSchema = z6.object({
|
|
1976
|
+
changes: z6.array(z6.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
1916
1977
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1917
1978
|
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goals) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
1918
|
-
var GetGoalsSchema =
|
|
1919
|
-
goalIds:
|
|
1979
|
+
var GetGoalsSchema = z6.object({
|
|
1980
|
+
goalIds: z6.array(z6.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
|
|
1920
1981
|
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
1921
1982
|
* newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
|
|
1922
|
-
history:
|
|
1983
|
+
history: z6.boolean().optional()
|
|
1923
1984
|
}).strict();
|
|
1924
|
-
var GetDeliverySchema =
|
|
1925
|
-
deliveryId:
|
|
1985
|
+
var GetDeliverySchema = z6.object({
|
|
1986
|
+
deliveryId: z6.string().uuid().describe("The Delivery id returned by contact or receive."),
|
|
1987
|
+
// WHAT WAS SAID ON A CALL (owner, 2026-10-26, Goal c743f4a4: "A flag on get_delivery", "Any of my agents").
|
|
1988
|
+
transcript: z6.boolean().optional().describe("For a Call: also return what was said on it, one page of spoken lines."),
|
|
1989
|
+
after: z6.string().max(40).optional().describe("With transcript: the next value the previous page returned, to read the next page.")
|
|
1926
1990
|
}).strict();
|
|
1927
|
-
var GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
|
|
1991
|
+
var GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. For a Call, transcript: true adds what was said: each spoken line in order, who said it, and when from pickup, a page at a time. A line from the room, not the person, is marked room. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
|
|
1928
1992
|
var GET_GOALS_DESCRIPTION = "Read one to ten Goals by goalIds, returned in the same order under goals (a read never puts you on them). history applies to every requested Goal. A refused read fails the call with its original refusal; no partial result is presented as complete. Each Goal includes its ten most recent other contributors (names, latest entry headlines and times), outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.";
|
|
1929
|
-
var SearchToolSchema =
|
|
1930
|
-
query:
|
|
1931
|
-
types:
|
|
1932
|
-
goalId:
|
|
1933
|
-
limit:
|
|
1993
|
+
var SearchToolSchema = z6.object({
|
|
1994
|
+
query: z6.string().trim().min(1).max(500),
|
|
1995
|
+
types: z6.array(z6.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
1996
|
+
goalId: z6.string().uuid().optional(),
|
|
1997
|
+
limit: z6.number().int().min(1).max(20).optional()
|
|
1934
1998
|
}).strict();
|
|
1935
1999
|
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.";
|
|
1936
|
-
var ConfigureIdentitySchema =
|
|
2000
|
+
var ConfigureIdentitySchema = z6.object({}).strict();
|
|
1937
2001
|
var CONFIGURE_IDENTITY_DESCRIPTION = "Read this authenticated agent\u2019s current name, voice and token ID. Read-only; no arguments. Pairing, renaming and logout are available only through the local configure driver.";
|
|
1938
|
-
var CheckActivitySchema =
|
|
2002
|
+
var CheckActivitySchema = z6.object({}).strict();
|
|
1939
2003
|
var FEEDBACK_TEXT_MAX = 5e4;
|
|
1940
|
-
var SendFeedbackSchema =
|
|
1941
|
-
text:
|
|
1942
|
-
title:
|
|
1943
|
-
kind:
|
|
2004
|
+
var SendFeedbackSchema = z6.object({
|
|
2005
|
+
text: z6.string().trim().min(1).max(FEEDBACK_TEXT_MAX).describe(`The report, in markdown: what happened, what was expected, how to reproduce it, versions, evidence. At most ${FEEDBACK_TEXT_MAX} characters.`),
|
|
2006
|
+
title: z6.string().trim().min(1).max(200).optional().describe("One line naming it."),
|
|
2007
|
+
kind: z6.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
|
|
1944
2008
|
}).strict();
|
|
1945
2009
|
var SEND_FEEDBACK_DESCRIPTION = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
|
|
1946
2010
|
var AGENT_TOOLS = [
|
|
@@ -1951,166 +2015,168 @@ var AGENT_TOOLS = [
|
|
|
1951
2015
|
{ name: "get_goals", description: GET_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalsSchema) },
|
|
1952
2016
|
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
1953
2017
|
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goals to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
|
|
1954
|
-
{ name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) }
|
|
2018
|
+
{ name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) },
|
|
2019
|
+
// THE GUIDE FROM INSIDE THE MCP (owner, 2026-10-27): the skill's text, needing no identity (`help.ts`).
|
|
2020
|
+
{ name: "help", description: HELP_DESCRIPTION, inputSchema: mcpInputSchema(HelpSchema) }
|
|
1955
2021
|
];
|
|
1956
2022
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
1957
|
-
var id =
|
|
1958
|
-
var key =
|
|
1959
|
-
var participant =
|
|
1960
|
-
var SourceSchema =
|
|
1961
|
-
var ResultRefSchema =
|
|
1962
|
-
var NodeRefSchema =
|
|
1963
|
-
var DependencySchema =
|
|
2023
|
+
var id = z7.string().uuid();
|
|
2024
|
+
var key = z7.string().min(1).max(40);
|
|
2025
|
+
var participant = z7.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
|
|
2026
|
+
var SourceSchema = z7.object({ entryId: id }).strict();
|
|
2027
|
+
var ResultRefSchema = z7.union([z7.object({ id }).strict(), z7.object({ local: key }).strict()]);
|
|
2028
|
+
var NodeRefSchema = z7.object({ type: z7.enum(["goal", "question"]), id }).strict();
|
|
2029
|
+
var DependencySchema = z7.object({
|
|
1964
2030
|
blocker: NodeRefSchema,
|
|
1965
2031
|
blocked: NodeRefSchema,
|
|
1966
|
-
action:
|
|
1967
|
-
reason:
|
|
2032
|
+
action: z7.enum(["start", "complete", "answer"]),
|
|
2033
|
+
reason: z7.string().max(500).optional()
|
|
1968
2034
|
}).strict();
|
|
1969
|
-
var goalState =
|
|
2035
|
+
var goalState = z7.enum(["open", "completed", "canceled"]);
|
|
1970
2036
|
var existingGoalChanges = [
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
kind:
|
|
1978
|
-
change:
|
|
2037
|
+
z7.object({ kind: z7.literal("edit"), goalId: id, title: z7.string().min(1).max(120).optional(), outcome: z7.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
|
|
2038
|
+
z7.object({ kind: z7.literal("state"), goalId: id, state: goalState }).strict(),
|
|
2039
|
+
z7.object({ kind: z7.literal("assign"), goalId: id, ownerId: participant }).strict(),
|
|
2040
|
+
z7.object({ kind: z7.literal("defer"), goalId: id, until: z7.string().datetime().nullable() }).strict(),
|
|
2041
|
+
z7.object({ kind: z7.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
|
|
2042
|
+
z7.object({
|
|
2043
|
+
kind: z7.literal("dependency"),
|
|
2044
|
+
change: z7.enum(["add", "remove"]),
|
|
1979
2045
|
goalId: id,
|
|
1980
2046
|
dependsOnGoalId: id,
|
|
1981
|
-
action:
|
|
1982
|
-
reason:
|
|
2047
|
+
action: z7.enum(["start", "complete"]),
|
|
2048
|
+
reason: z7.string().max(500).optional()
|
|
1983
2049
|
}).strict()
|
|
1984
2050
|
];
|
|
1985
|
-
var BrainGoalChangeSchema =
|
|
2051
|
+
var BrainGoalChangeSchema = z7.union([
|
|
1986
2052
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
1987
|
-
|
|
1988
|
-
kind:
|
|
1989
|
-
outcome:
|
|
1990
|
-
title:
|
|
2053
|
+
z7.object({
|
|
2054
|
+
kind: z7.literal("create"),
|
|
2055
|
+
outcome: z7.string().min(1),
|
|
2056
|
+
title: z7.string().min(1).max(120),
|
|
1991
2057
|
ownerId: participant,
|
|
1992
2058
|
parentGoal: ResultRefSchema.nullable(),
|
|
1993
|
-
newSession:
|
|
2059
|
+
newSession: z7.literal(true).optional()
|
|
1994
2060
|
}).strict(),
|
|
1995
2061
|
...existingGoalChanges
|
|
1996
2062
|
]);
|
|
1997
|
-
var option =
|
|
1998
|
-
var QuestionChangeSchema =
|
|
1999
|
-
|
|
2000
|
-
kind:
|
|
2001
|
-
text:
|
|
2002
|
-
summary:
|
|
2063
|
+
var option = z7.object({ id: z7.string().min(1).max(40), label: z7.string().min(1), hint: z7.string().optional() }).strict();
|
|
2064
|
+
var QuestionChangeSchema = z7.union([
|
|
2065
|
+
z7.object({
|
|
2066
|
+
kind: z7.literal("create"),
|
|
2067
|
+
text: z7.string().min(1),
|
|
2068
|
+
summary: z7.string().optional(),
|
|
2003
2069
|
answererId: participant,
|
|
2004
2070
|
goal: ResultRefSchema.optional(),
|
|
2005
|
-
blocks:
|
|
2006
|
-
options:
|
|
2007
|
-
pickMode:
|
|
2071
|
+
blocks: z7.array(z7.object({ question: ResultRefSchema }).strict()),
|
|
2072
|
+
options: z7.array(option).min(2).optional(),
|
|
2073
|
+
pickMode: z7.enum(["one", "many", "rank"]).optional()
|
|
2008
2074
|
}).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2075
|
+
z7.object({ kind: z7.literal("edit"), questionId: id, text: z7.string().min(1) }).strict(),
|
|
2076
|
+
z7.object({ kind: z7.literal("assign"), questionId: id, answererId: participant }).strict(),
|
|
2077
|
+
z7.object({ kind: z7.literal("withdraw"), questionId: id }).strict(),
|
|
2078
|
+
z7.object({ kind: z7.literal("dependency"), change: z7.enum(["add", "remove"]), edge: DependencySchema }).strict()
|
|
2013
2079
|
]);
|
|
2014
|
-
var lessonScope =
|
|
2015
|
-
|
|
2016
|
-
|
|
2080
|
+
var lessonScope = z7.union([
|
|
2081
|
+
z7.object({ kind: z7.literal("user") }).strict(),
|
|
2082
|
+
z7.object({ kind: z7.literal("goal"), goal: ResultRefSchema }).strict()
|
|
2017
2083
|
]);
|
|
2018
|
-
var LessonChangeSchema =
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2084
|
+
var LessonChangeSchema = z7.union([
|
|
2085
|
+
z7.object({ kind: z7.literal("remember"), text: z7.string().min(1), scope: lessonScope }).strict(),
|
|
2086
|
+
z7.object({ kind: z7.literal("revise"), lessonId: id, text: z7.string().min(1), scope: lessonScope }).strict(),
|
|
2087
|
+
z7.object({ kind: z7.literal("withdraw"), lessonId: id }).strict()
|
|
2022
2088
|
]);
|
|
2023
|
-
var BrainNextSchema =
|
|
2024
|
-
instructions:
|
|
2025
|
-
say:
|
|
2026
|
-
then:
|
|
2027
|
-
waitFor:
|
|
2028
|
-
reason:
|
|
2089
|
+
var BrainNextSchema = z7.object({
|
|
2090
|
+
instructions: z7.array(z7.object({ entryKeys: z7.array(key).min(1) }).strict()),
|
|
2091
|
+
say: z7.array(key),
|
|
2092
|
+
then: z7.enum(["listen", "hold", "end", "none"]),
|
|
2093
|
+
waitFor: z7.array(ResultRefSchema),
|
|
2094
|
+
reason: z7.string()
|
|
2029
2095
|
}).strict();
|
|
2030
2096
|
var messageBase = {
|
|
2031
2097
|
key,
|
|
2032
|
-
text:
|
|
2033
|
-
goals:
|
|
2034
|
-
questions:
|
|
2035
|
-
entryKeys:
|
|
2036
|
-
obligationIds:
|
|
2098
|
+
text: z7.string().min(1),
|
|
2099
|
+
goals: z7.array(ResultRefSchema),
|
|
2100
|
+
questions: z7.array(ResultRefSchema),
|
|
2101
|
+
entryKeys: z7.array(key),
|
|
2102
|
+
obligationIds: z7.array(id)
|
|
2037
2103
|
};
|
|
2038
|
-
var BrainMessageSchema =
|
|
2104
|
+
var BrainMessageSchema = z7.union([
|
|
2039
2105
|
// A MESSAGE TO THE PERSON KEEPS ITS SENTENCES (turns.md): the talker writes them one by one, and each is
|
|
2040
2106
|
// its own unit of speech (`<cycle>:<key>:<n>`). `text` is the same words joined, for every reader of a
|
|
2041
2107
|
// message's words. Absent on results written before sentences.
|
|
2042
|
-
|
|
2108
|
+
z7.object({
|
|
2043
2109
|
...messageBase,
|
|
2044
|
-
to:
|
|
2045
|
-
sentences:
|
|
2110
|
+
to: z7.object({ kind: z7.literal("user") }).strict(),
|
|
2111
|
+
sentences: z7.array(z7.string().min(1)).min(1).optional(),
|
|
2046
2112
|
// THE REPORTS IT TELLS, BY THEIR OWN IDENTITY (call 9d1c096c, 2026-10-15): the talker's message names the report
|
|
2047
2113
|
// Entries it speaks of, so the plan's line names that report rather than re-finding it by its Goal. Absent on
|
|
2048
2114
|
// results written before it.
|
|
2049
|
-
tells:
|
|
2115
|
+
tells: z7.array(z7.object({ id }).strict()).optional()
|
|
2050
2116
|
}).strict(),
|
|
2051
|
-
|
|
2117
|
+
z7.object({
|
|
2052
2118
|
...messageBase,
|
|
2053
|
-
to:
|
|
2054
|
-
call:
|
|
2119
|
+
to: z7.object({ kind: z7.literal("agent"), agentId: z7.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
|
|
2120
|
+
call: z7.object({ callId: id, listen: z7.literal(true) }).strict().optional(),
|
|
2055
2121
|
// WHAT IT ASKS THE AGENT, IN UNDER TEN WORDS (owner, 2026-10-15: "Yes, add the summary"): the talker's own, stored
|
|
2056
2122
|
// as the sent Entry's summary, which the recap's "Sent to <agent>: …" row shows. Absent on results written before it.
|
|
2057
|
-
summary:
|
|
2123
|
+
summary: z7.string().optional(),
|
|
2058
2124
|
// THE PERSON ASKED TO BE CALLED BACK ONCE THIS AGENT'S WORK IS DONE (turns.md, step 0): the agent owes
|
|
2059
2125
|
// the call, and its next contact about it rings them (`public.callbacks`).
|
|
2060
|
-
callback:
|
|
2126
|
+
callback: z7.literal(true).optional()
|
|
2061
2127
|
}).strict()
|
|
2062
2128
|
]);
|
|
2063
|
-
var BrainSearchSchema =
|
|
2064
|
-
query:
|
|
2065
|
-
within:
|
|
2129
|
+
var BrainSearchSchema = z7.object({
|
|
2130
|
+
query: z7.string().min(1).max(500),
|
|
2131
|
+
within: z7.array(z7.enum(["entries", "goals", "answers"])).min(1),
|
|
2066
2132
|
goalId: id.optional()
|
|
2067
2133
|
}).strict();
|
|
2068
|
-
var BrainResultSchema =
|
|
2069
|
-
messages:
|
|
2070
|
-
entries:
|
|
2071
|
-
questions:
|
|
2072
|
-
answers:
|
|
2134
|
+
var BrainResultSchema = z7.object({
|
|
2135
|
+
messages: z7.array(BrainMessageSchema),
|
|
2136
|
+
entries: z7.array(z7.object({ key, sources: z7.array(SourceSchema).min(1), goals: z7.array(ResultRefSchema), summary: z7.string().optional() }).strict()),
|
|
2137
|
+
questions: z7.array(z7.object({ key, entryKeys: z7.array(key), change: QuestionChangeSchema }).strict()),
|
|
2138
|
+
answers: z7.array(z7.object({
|
|
2073
2139
|
question: ResultRefSchema,
|
|
2074
|
-
entryKeys:
|
|
2075
|
-
summary:
|
|
2076
|
-
selectedOptionIds:
|
|
2140
|
+
entryKeys: z7.array(key).min(1),
|
|
2141
|
+
summary: z7.string(),
|
|
2142
|
+
selectedOptionIds: z7.array(z7.string().min(1)).optional()
|
|
2077
2143
|
}).strict()),
|
|
2078
|
-
goals:
|
|
2079
|
-
feedback:
|
|
2080
|
-
lessons:
|
|
2144
|
+
goals: z7.array(z7.object({ key, entryKeys: z7.array(key), change: BrainGoalChangeSchema }).strict()),
|
|
2145
|
+
feedback: z7.array(z7.object({ entryKeys: z7.array(key).min(1) }).strict()),
|
|
2146
|
+
lessons: z7.array(z7.object({ entryKeys: z7.array(key).min(1), change: LessonChangeSchema }).strict()),
|
|
2081
2147
|
next: BrainNextSchema,
|
|
2082
2148
|
search: BrainSearchSchema.optional()
|
|
2083
2149
|
}).strict();
|
|
2084
|
-
var str2 =
|
|
2085
|
-
var strs =
|
|
2086
|
-
var CompactResultSchema =
|
|
2087
|
-
next:
|
|
2088
|
-
messages:
|
|
2089
|
-
entries:
|
|
2090
|
-
answers:
|
|
2091
|
-
changes:
|
|
2150
|
+
var str2 = z7.string();
|
|
2151
|
+
var strs = z7.array(z7.string());
|
|
2152
|
+
var CompactResultSchema = z7.object({
|
|
2153
|
+
next: z7.object({ say: strs, then: z7.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str2, instructions: strs }).strict(),
|
|
2154
|
+
messages: z7.array(z7.object({ key: str2, to: str2, text: str2, about: strs, entries: strs, owed: strs, inviteToCall: z7.boolean() }).strict()),
|
|
2155
|
+
entries: z7.array(z7.object({ key: str2, from: str2, on: strs }).strict()),
|
|
2156
|
+
answers: z7.array(z7.object({ question: str2, entries: strs, summary: str2, picked: strs }).strict()),
|
|
2157
|
+
changes: z7.array(z7.object({
|
|
2092
2158
|
key: str2,
|
|
2093
|
-
what:
|
|
2094
|
-
op:
|
|
2159
|
+
what: z7.enum(["goal", "question", "lesson"]),
|
|
2160
|
+
op: z7.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
|
|
2095
2161
|
id: str2,
|
|
2096
2162
|
text: str2,
|
|
2097
2163
|
title: str2,
|
|
2098
2164
|
who: str2,
|
|
2099
2165
|
under: str2,
|
|
2100
|
-
gate:
|
|
2101
|
-
options:
|
|
2102
|
-
pick:
|
|
2166
|
+
gate: z7.enum(["start", "complete", "answer", "none"]),
|
|
2167
|
+
options: z7.array(z7.object({ id: str2, label: str2 }).strict()),
|
|
2168
|
+
pick: z7.enum(["one", "many", "rank", "words"]),
|
|
2103
2169
|
entries: strs
|
|
2104
2170
|
}).strict()),
|
|
2105
|
-
feedback:
|
|
2106
|
-
search:
|
|
2171
|
+
feedback: z7.array(strs),
|
|
2172
|
+
search: z7.object({ query: str2, within: strs, goal: str2 }).strict()
|
|
2107
2173
|
}).strict();
|
|
2108
2174
|
var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2109
2175
|
zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
|
|
2110
2176
|
);
|
|
2111
|
-
var str3 =
|
|
2112
|
-
var strs2 =
|
|
2113
|
-
var TalkerReplySchema =
|
|
2177
|
+
var str3 = z8.string();
|
|
2178
|
+
var strs2 = z8.array(z8.string());
|
|
2179
|
+
var TalkerReplySchema = z8.object({
|
|
2114
2180
|
/** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
|
|
2115
2181
|
* or an agent's handle; `sentences` is what a message to the person says, one short sentence each
|
|
2116
2182
|
* ([] to an agent); `text` is what a message to an agent says ("" to the person); `summary` is what it
|
|
@@ -2119,35 +2185,36 @@ var TalkerReplySchema = z7.object({
|
|
|
2119
2185
|
* handles; `blocks` is the item a question to an agent must be answered before ("" for none);
|
|
2120
2186
|
* `callback` is true on a message to the agent doing work the person asked to be called back about
|
|
2121
2187
|
* once it is done (false otherwise): that agent then owes the call (turns.md, step 0). */
|
|
2122
|
-
messages:
|
|
2188
|
+
messages: z8.array(z8.object({ key: str3, to: str3, sentences: strs2, text: str3, summary: str3, about: strs2, blocks: str3, callback: z8.boolean() }).strict()),
|
|
2123
2189
|
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
2124
|
-
then:
|
|
2190
|
+
then: z8.enum(["listen", "hold", "end"]),
|
|
2125
2191
|
/** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
|
|
2126
2192
|
* IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
|
|
2127
2193
|
* "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
|
|
2128
2194
|
* end of this call), a question's handle (that question's answer) or an ISO time. */
|
|
2129
|
-
replies:
|
|
2195
|
+
replies: z8.array(z8.object({ item: str3, options: strs2, lines: strs2, summary: str3, defer: str3 }).strict()),
|
|
2130
2196
|
/** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
|
|
2131
2197
|
instruction: strs2,
|
|
2132
2198
|
/** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
|
|
2133
2199
|
* work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
|
|
2134
2200
|
file: strs2,
|
|
2135
2201
|
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
2136
|
-
need:
|
|
2202
|
+
need: z8.object({ kind: z8.enum(["search", "item", "agent", ""]), text: str3 }).strict(),
|
|
2137
2203
|
/** The handle of the item to explain ("" for none). */
|
|
2138
2204
|
explain: str3
|
|
2139
2205
|
}).strict();
|
|
2140
2206
|
var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2141
2207
|
zodToJsonSchema(TalkerReplySchema, { $refStrategy: "none" })
|
|
2142
2208
|
);
|
|
2143
|
-
var str4 =
|
|
2144
|
-
var strs3 =
|
|
2145
|
-
var FilerReplySchema =
|
|
2146
|
-
/** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals
|
|
2147
|
-
* the line says in at most
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2209
|
+
var str4 = z9.string();
|
|
2210
|
+
var strs3 = z9.array(z9.string());
|
|
2211
|
+
var FilerReplySchema = z9.object({
|
|
2212
|
+
/** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals: a line that asks
|
|
2213
|
+
* for new work goes on the Goal it creates, owner 2026-10-26), and what the line says in at most twenty
|
|
2214
|
+
* words (owner, 2026-10-26: "bump it from 10 to 20 words"). */
|
|
2215
|
+
filed: z9.array(z9.object({ line: str4, goals: strs3, summary: str4 }).strict()),
|
|
2216
|
+
goals: z9.array(z9.object({
|
|
2217
|
+
op: z9.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
2151
2218
|
key: str4,
|
|
2152
2219
|
goal: str4,
|
|
2153
2220
|
title: str4,
|
|
@@ -2156,33 +2223,33 @@ var FilerReplySchema = z8.object({
|
|
|
2156
2223
|
under: str4,
|
|
2157
2224
|
until: str4,
|
|
2158
2225
|
on: str4,
|
|
2159
|
-
gate:
|
|
2226
|
+
gate: z9.enum(["start", "complete", "answer", ""]),
|
|
2160
2227
|
why: str4
|
|
2161
2228
|
}).strict()),
|
|
2162
|
-
questions:
|
|
2163
|
-
op:
|
|
2229
|
+
questions: z9.array(z9.object({
|
|
2230
|
+
op: z9.enum(["create", "edit", "assign", "withdraw"]),
|
|
2164
2231
|
key: str4,
|
|
2165
2232
|
question: str4,
|
|
2166
2233
|
text: str4,
|
|
2167
2234
|
summary: str4,
|
|
2168
2235
|
to: str4,
|
|
2169
2236
|
goal: str4,
|
|
2170
|
-
options:
|
|
2171
|
-
pick:
|
|
2237
|
+
options: z9.array(z9.object({ id: str4, label: str4 }).strict()),
|
|
2238
|
+
pick: z9.enum(["one", "many", "rank", "words", ""]),
|
|
2172
2239
|
blocks: str4
|
|
2173
2240
|
}).strict()),
|
|
2174
|
-
lessons:
|
|
2241
|
+
lessons: z9.array(z9.object({ op: z9.enum(["remember", "revise", "withdraw"]), lesson: str4, text: str4, scope: str4 }).strict()),
|
|
2175
2242
|
/** Line handles whose words comment on Paigy itself. */
|
|
2176
2243
|
feedback: strs3
|
|
2177
2244
|
}).strict();
|
|
2178
2245
|
var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2179
2246
|
zodToJsonSchema(FilerReplySchema, { $refStrategy: "none" })
|
|
2180
2247
|
);
|
|
2181
|
-
var ExplainerReplySchema =
|
|
2248
|
+
var ExplainerReplySchema = z10.object({
|
|
2182
2249
|
/** The explanation, as sentences, spoken in order. */
|
|
2183
|
-
say:
|
|
2250
|
+
say: z10.array(z10.string()),
|
|
2184
2251
|
/** Questions to an agent about gaps in what it read: the agent's handle and the question. */
|
|
2185
|
-
followUps:
|
|
2252
|
+
followUps: z10.array(z10.object({ to: z10.string(), text: z10.string() }).strict())
|
|
2186
2253
|
}).strict();
|
|
2187
2254
|
var EXPLAINER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2188
2255
|
zodToJsonSchema(ExplainerReplySchema, { $refStrategy: "none" })
|
|
@@ -2205,49 +2272,49 @@ var DISTILLER_DEFECTS = [
|
|
|
2205
2272
|
"dropped"
|
|
2206
2273
|
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
2207
2274
|
];
|
|
2208
|
-
var DistillerReplySchema =
|
|
2209
|
-
defects:
|
|
2210
|
-
kind:
|
|
2275
|
+
var DistillerReplySchema = z11.object({
|
|
2276
|
+
defects: z11.array(z11.object({
|
|
2277
|
+
kind: z11.enum(DISTILLER_DEFECTS),
|
|
2211
2278
|
/** The handles of the lines it shows in. */
|
|
2212
|
-
lines:
|
|
2279
|
+
lines: z11.array(z11.string()),
|
|
2213
2280
|
/** The handle of the record it concerns, or "". */
|
|
2214
|
-
about:
|
|
2281
|
+
about: z11.string(),
|
|
2215
2282
|
/** One plain sentence, in words, never handles: it is what the feedback row keeps. */
|
|
2216
|
-
why:
|
|
2283
|
+
why: z11.string()
|
|
2217
2284
|
}).strict())
|
|
2218
2285
|
}).strict();
|
|
2219
2286
|
var DISTILLER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2220
2287
|
zodToJsonSchema(DistillerReplySchema, { $refStrategy: "none" })
|
|
2221
2288
|
);
|
|
2222
2289
|
var TIDY_AUDIENCES = ["talker", "filer", "agents"];
|
|
2223
|
-
var TidyReplySchema =
|
|
2224
|
-
lessons:
|
|
2225
|
-
lesson:
|
|
2226
|
-
op:
|
|
2227
|
-
text:
|
|
2228
|
-
for:
|
|
2229
|
-
into:
|
|
2290
|
+
var TidyReplySchema = z11.object({
|
|
2291
|
+
lessons: z11.array(z11.object({
|
|
2292
|
+
lesson: z11.string(),
|
|
2293
|
+
op: z11.enum(["keep", "revise", "withdraw"]),
|
|
2294
|
+
text: z11.string(),
|
|
2295
|
+
for: z11.enum([...TIDY_AUDIENCES, ""]),
|
|
2296
|
+
into: z11.string()
|
|
2230
2297
|
}).strict())
|
|
2231
2298
|
}).strict();
|
|
2232
2299
|
var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2233
2300
|
zodToJsonSchema(TidyReplySchema, { $refStrategy: "none" })
|
|
2234
2301
|
);
|
|
2235
|
-
var HandoutReplySchema =
|
|
2236
|
-
assign:
|
|
2302
|
+
var HandoutReplySchema = z12.object({
|
|
2303
|
+
assign: z12.array(z12.object({
|
|
2237
2304
|
/** The Goal's handle (G). */
|
|
2238
|
-
goal:
|
|
2305
|
+
goal: z12.string(),
|
|
2239
2306
|
/** The agent's handle (A). */
|
|
2240
|
-
to:
|
|
2307
|
+
to: z12.string(),
|
|
2241
2308
|
/** At most ten words, naming the Goal of theirs it is most like: the reason the hand-out records. */
|
|
2242
|
-
why:
|
|
2309
|
+
why: z12.string()
|
|
2243
2310
|
}).strict()),
|
|
2244
2311
|
/** The Goals no listed agent's work is even loosely like: Paigy starts a session for them (owner, 2026-10-24,
|
|
2245
2312
|
* Goal 37603006, "One per family of related Goals"). */
|
|
2246
|
-
new:
|
|
2313
|
+
new: z12.array(z12.object({
|
|
2247
2314
|
/** The Goal's handle (G). */
|
|
2248
|
-
goal:
|
|
2315
|
+
goal: z12.string(),
|
|
2249
2316
|
/** At most ten words: why nothing active fits it. */
|
|
2250
|
-
why:
|
|
2317
|
+
why: z12.string()
|
|
2251
2318
|
}).strict())
|
|
2252
2319
|
}).strict();
|
|
2253
2320
|
var HANDOUT_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
@@ -2288,20 +2355,20 @@ var LIVE_MS = 3 * 6e4;
|
|
|
2288
2355
|
var WORKING_MS = 60 * 6e4;
|
|
2289
2356
|
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
2290
2357
|
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
2291
|
-
var ContextSchema =
|
|
2292
|
-
title:
|
|
2293
|
-
description:
|
|
2358
|
+
var ContextSchema = z13.object({
|
|
2359
|
+
title: z13.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
2360
|
+
description: z13.array(z13.string().min(1)).describe(
|
|
2294
2361
|
"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."
|
|
2295
2362
|
),
|
|
2296
2363
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
2297
2364
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
2298
|
-
units:
|
|
2365
|
+
units: z13.array(z13.object({ title: z13.string(), body: z13.string() })).optional()
|
|
2299
2366
|
});
|
|
2300
|
-
var ParticipantSchema =
|
|
2301
|
-
kind:
|
|
2302
|
-
id:
|
|
2367
|
+
var ParticipantSchema = z13.object({
|
|
2368
|
+
kind: z13.enum(["human", "agent"]),
|
|
2369
|
+
id: z13.string()
|
|
2303
2370
|
});
|
|
2304
|
-
var TransformSchema =
|
|
2371
|
+
var TransformSchema = z13.enum([
|
|
2305
2372
|
"structure",
|
|
2306
2373
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
2307
2374
|
"request_more",
|
|
@@ -2324,13 +2391,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
|
|
|
2324
2391
|
function isPaigy(ref) {
|
|
2325
2392
|
return ref === participantRef(PAIGY_SELF);
|
|
2326
2393
|
}
|
|
2327
|
-
var VisualSchema =
|
|
2328
|
-
url:
|
|
2329
|
-
label:
|
|
2394
|
+
var VisualSchema = z13.object({
|
|
2395
|
+
url: z13.string().url(),
|
|
2396
|
+
label: z13.string().optional()
|
|
2330
2397
|
});
|
|
2331
|
-
var NotifyLevelSchema =
|
|
2332
|
-
var SelectShapeSchema =
|
|
2333
|
-
var ReceiptEventSchema =
|
|
2398
|
+
var NotifyLevelSchema = z13.enum(["inbox", "push", "banner", "call"]);
|
|
2399
|
+
var SelectShapeSchema = z13.enum(["one", "many", "rank", "confirm", "text"]);
|
|
2400
|
+
var ReceiptEventSchema = z13.enum([
|
|
2334
2401
|
"delivered",
|
|
2335
2402
|
// the bundle reached the recipient at some level
|
|
2336
2403
|
"seen",
|
|
@@ -2360,47 +2427,47 @@ var ReceiptEventSchema = z12.enum([
|
|
|
2360
2427
|
// be rewound by a writer that forgot to advance it.
|
|
2361
2428
|
"restarted"
|
|
2362
2429
|
]);
|
|
2363
|
-
var AttentionSchema =
|
|
2430
|
+
var AttentionSchema = z13.object({
|
|
2364
2431
|
urgency: NotifyLevelSchema,
|
|
2365
2432
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
2366
2433
|
select: SelectShapeSchema.nullable(),
|
|
2367
2434
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
2368
|
-
points:
|
|
2435
|
+
points: z13.array(z13.string()).nullable(),
|
|
2369
2436
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
2370
|
-
blocking:
|
|
2437
|
+
blocking: z13.boolean(),
|
|
2371
2438
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
2372
2439
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
2373
|
-
deadline:
|
|
2440
|
+
deadline: z13.string().datetime().nullable().optional()
|
|
2374
2441
|
});
|
|
2375
|
-
var NotifyRequestFields =
|
|
2442
|
+
var NotifyRequestFields = z13.object({
|
|
2376
2443
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
2377
2444
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
2378
2445
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
2379
2446
|
context: ContextSchema.optional(),
|
|
2380
|
-
options:
|
|
2447
|
+
options: z13.array(OptionSchema.omit({ id: true }).extend({ label: z13.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
2381
2448
|
"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)."
|
|
2382
2449
|
),
|
|
2383
|
-
points:
|
|
2450
|
+
points: z13.array(z13.string().min(1)).optional().describe(
|
|
2384
2451
|
"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."
|
|
2385
2452
|
),
|
|
2386
|
-
visuals:
|
|
2453
|
+
visuals: z13.array(VisualSchema).optional().describe(
|
|
2387
2454
|
"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."
|
|
2388
2455
|
),
|
|
2389
2456
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
2390
|
-
repo:
|
|
2457
|
+
repo: z13.string().optional(),
|
|
2391
2458
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
2392
|
-
branch:
|
|
2459
|
+
branch: z13.string().optional(),
|
|
2393
2460
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
2394
2461
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
2395
2462
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
2396
2463
|
* still accepts the old name from older clients. */
|
|
2397
|
-
parentId:
|
|
2464
|
+
parentId: z13.string().uuid().optional(),
|
|
2398
2465
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
2399
2466
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
2400
|
-
workId:
|
|
2467
|
+
workId: z13.string().uuid().optional(),
|
|
2401
2468
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
2402
2469
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
2403
|
-
goalId:
|
|
2470
|
+
goalId: z13.string().uuid().optional(),
|
|
2404
2471
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
2405
2472
|
"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."
|
|
2406
2473
|
),
|
|
@@ -2408,7 +2475,7 @@ var NotifyRequestFields = z12.object({
|
|
|
2408
2475
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
2409
2476
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
2410
2477
|
* says HOW. */
|
|
2411
|
-
clarifies:
|
|
2478
|
+
clarifies: z13.string().optional(),
|
|
2412
2479
|
select: SelectShapeSchema.optional().describe(
|
|
2413
2480
|
"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."
|
|
2414
2481
|
),
|
|
@@ -2419,23 +2486,23 @@ var NotifyRequestFields = z12.object({
|
|
|
2419
2486
|
* derive agent-side before sealing, so the server only ever shapes plaintext). */
|
|
2420
2487
|
// 10k, not a sentence budget. What the human hears is bounded by the BROKER — it splits
|
|
2421
2488
|
// the ask into topics and gives each one at most three sentences and one question
|
|
2422
|
-
// (docs/
|
|
2489
|
+
// (docs/history/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2423
2490
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2424
2491
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2425
|
-
ask:
|
|
2492
|
+
ask: z13.string().min(1).max(1e4).optional().describe(
|
|
2426
2493
|
'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.'
|
|
2427
2494
|
),
|
|
2428
|
-
needs:
|
|
2495
|
+
needs: z13.array(z13.string().min(1)).optional().describe(
|
|
2429
2496
|
"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."
|
|
2430
2497
|
),
|
|
2431
|
-
urgencyHint:
|
|
2498
|
+
urgencyHint: z13.enum(["whenever", "soon", "now"]).optional().describe(
|
|
2432
2499
|
"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."
|
|
2433
2500
|
),
|
|
2434
2501
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
2435
2502
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
2436
2503
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
2437
2504
|
* urgencyHint/blocking win when both are sent. */
|
|
2438
|
-
waiting:
|
|
2505
|
+
waiting: z13.enum(["none", "soft", "hard"]).optional().describe(
|
|
2439
2506
|
"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."
|
|
2440
2507
|
),
|
|
2441
2508
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -2443,98 +2510,98 @@ var NotifyRequestFields = z12.object({
|
|
|
2443
2510
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
2444
2511
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
2445
2512
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
2446
|
-
confirm:
|
|
2513
|
+
confirm: z13.boolean().optional().describe(
|
|
2447
2514
|
"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'."
|
|
2448
2515
|
),
|
|
2449
2516
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
2450
2517
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
2451
2518
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
2452
2519
|
* waiting:'hard'. */
|
|
2453
|
-
channel:
|
|
2520
|
+
channel: z13.enum(["call", "message"]).optional().describe(
|
|
2454
2521
|
"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."
|
|
2455
2522
|
),
|
|
2456
|
-
confirmStyle:
|
|
2523
|
+
confirmStyle: z13.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2457
2524
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2458
2525
|
),
|
|
2459
|
-
blocking:
|
|
2526
|
+
blocking: z13.boolean().default(false).describe(
|
|
2460
2527
|
"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."
|
|
2461
2528
|
)
|
|
2462
2529
|
});
|
|
2463
2530
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
2464
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
2531
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
2465
2532
|
if (r.ask !== void 0) {
|
|
2466
2533
|
for (const f of ["context", "select", "points"]) {
|
|
2467
2534
|
if (r[f] !== void 0)
|
|
2468
|
-
ctx.addIssue({ code:
|
|
2535
|
+
ctx.addIssue({ code: z13.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
|
|
2469
2536
|
}
|
|
2470
2537
|
return;
|
|
2471
2538
|
}
|
|
2472
2539
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
2473
|
-
ctx.addIssue({ code:
|
|
2540
|
+
ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
2474
2541
|
if (!r.context)
|
|
2475
|
-
ctx.addIssue({ code:
|
|
2542
|
+
ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
2476
2543
|
if (!r.select)
|
|
2477
|
-
ctx.addIssue({ code:
|
|
2544
|
+
ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
2478
2545
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
2479
2546
|
if (needsOptions && !r.options?.length)
|
|
2480
|
-
ctx.addIssue({ code:
|
|
2547
|
+
ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
2481
2548
|
if (!needsOptions && r.options?.length)
|
|
2482
|
-
ctx.addIssue({ code:
|
|
2549
|
+
ctx.addIssue({ code: z13.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
2483
2550
|
});
|
|
2484
|
-
var NotifyStatusSchema =
|
|
2485
|
-
var AgentStateSchema =
|
|
2486
|
-
var TurnSchema =
|
|
2487
|
-
prompt:
|
|
2488
|
-
reply:
|
|
2551
|
+
var NotifyStatusSchema = z13.enum(["pending", "answered", "ignored"]);
|
|
2552
|
+
var AgentStateSchema = z13.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
2553
|
+
var TurnSchema = z13.object({
|
|
2554
|
+
prompt: z13.string(),
|
|
2555
|
+
reply: z13.string()
|
|
2489
2556
|
});
|
|
2490
|
-
var UserAnswerSchema =
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
/** An auto-answer derived from the user's PAST decisions (docs/
|
|
2557
|
+
var UserAnswerSchema = z13.discriminatedUnion("kind", [
|
|
2558
|
+
z13.object({ kind: z13.literal("option"), optionId: z13.string(), label: z13.string().optional() }),
|
|
2559
|
+
z13.object({ kind: z13.literal("text"), text: z13.string() }),
|
|
2560
|
+
z13.object({ kind: z13.literal("ignored") }),
|
|
2561
|
+
z13.object({ kind: z13.literal("multi"), optionIds: z13.array(z13.string()), labels: z13.array(z13.string()).optional() }),
|
|
2562
|
+
z13.object({ kind: z13.literal("ranked"), optionIds: z13.array(z13.string()), labels: z13.array(z13.string()).optional() }),
|
|
2563
|
+
z13.object({ kind: z13.literal("clarify"), chunks: z13.array(z13.string()).min(1) }),
|
|
2564
|
+
z13.object({ kind: z13.literal("confirm"), approved: z13.boolean() }),
|
|
2565
|
+
z13.object({ kind: z13.literal("turns"), turns: z13.array(TurnSchema).min(1) }),
|
|
2566
|
+
/** An auto-answer derived from the user's PAST decisions (docs/history/precedent-design.md §2):
|
|
2500
2567
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
2501
2568
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
2502
2569
|
* card the user can reply to — the broker never overrides the user. */
|
|
2503
|
-
|
|
2570
|
+
z13.object({ kind: z13.literal("precedent"), answer: z13.string(), derivation: z13.string(), sources: z13.array(z13.string()).min(1) })
|
|
2504
2571
|
]);
|
|
2505
|
-
var IntentSchema =
|
|
2572
|
+
var IntentSchema = z13.object({
|
|
2506
2573
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
2507
2574
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
2508
2575
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
2509
2576
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2510
|
-
kind:
|
|
2511
|
-
detail:
|
|
2577
|
+
kind: z13.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2578
|
+
detail: z13.string(),
|
|
2512
2579
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2513
2580
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2514
2581
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2515
2582
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2516
|
-
dueInSeconds:
|
|
2583
|
+
dueInSeconds: z13.number().int().positive().optional(),
|
|
2517
2584
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2518
2585
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
2519
2586
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
2520
2587
|
* about what cannot answer "is the bot looping less this week?". */
|
|
2521
|
-
fault:
|
|
2588
|
+
fault: z13.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
2522
2589
|
});
|
|
2523
|
-
var RideAlongSchema =
|
|
2590
|
+
var RideAlongSchema = z13.object({
|
|
2524
2591
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
2525
|
-
noteId:
|
|
2592
|
+
noteId: z13.string(),
|
|
2526
2593
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
2527
|
-
text:
|
|
2594
|
+
text: z13.string(),
|
|
2528
2595
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
2529
|
-
parentId:
|
|
2596
|
+
parentId: z13.string().nullable()
|
|
2530
2597
|
});
|
|
2531
|
-
var AwaitItemSchema =
|
|
2532
|
-
|
|
2533
|
-
type:
|
|
2534
|
-
parentId:
|
|
2535
|
-
notificationId:
|
|
2536
|
-
workId:
|
|
2537
|
-
decisionId:
|
|
2598
|
+
var AwaitItemSchema = z13.discriminatedUnion("type", [
|
|
2599
|
+
z13.object({
|
|
2600
|
+
type: z13.literal("reply"),
|
|
2601
|
+
parentId: z13.string(),
|
|
2602
|
+
notificationId: z13.string(),
|
|
2603
|
+
workId: z13.string().uuid().optional(),
|
|
2604
|
+
decisionId: z13.string().uuid().optional(),
|
|
2538
2605
|
answer: UserAnswerSchema,
|
|
2539
2606
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
2540
2607
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -2544,109 +2611,109 @@ var AwaitItemSchema = z12.discriminatedUnion("type", [
|
|
|
2544
2611
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
2545
2612
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
2546
2613
|
* a sentence with a hole in it is worse than no sentence. */
|
|
2547
|
-
note:
|
|
2614
|
+
note: z13.string().optional(),
|
|
2548
2615
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
2549
2616
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
2550
2617
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
2551
2618
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
2552
|
-
transcript:
|
|
2619
|
+
transcript: z13.string().optional(),
|
|
2553
2620
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
2554
2621
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
2555
|
-
covered:
|
|
2622
|
+
covered: z13.array(z13.string()).optional(),
|
|
2556
2623
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
2557
2624
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
2558
2625
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
2559
2626
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
2560
2627
|
* dropped. Absent/empty = nothing owed. */
|
|
2561
|
-
also:
|
|
2628
|
+
also: z13.array(RideAlongSchema).optional()
|
|
2562
2629
|
}),
|
|
2563
|
-
|
|
2564
|
-
type:
|
|
2565
|
-
parentId:
|
|
2566
|
-
notificationId:
|
|
2567
|
-
remindAt:
|
|
2630
|
+
z13.object({
|
|
2631
|
+
type: z13.literal("remind"),
|
|
2632
|
+
parentId: z13.string(),
|
|
2633
|
+
notificationId: z13.string(),
|
|
2634
|
+
remindAt: z13.string().datetime({ offset: true }),
|
|
2568
2635
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
2569
|
-
remindInSeconds:
|
|
2636
|
+
remindInSeconds: z13.number()
|
|
2570
2637
|
}),
|
|
2571
2638
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2572
2639
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2573
2640
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2574
2641
|
* re-orient via contact({})). */
|
|
2575
|
-
|
|
2576
|
-
type:
|
|
2577
|
-
parentId:
|
|
2578
|
-
notificationId:
|
|
2642
|
+
z13.object({
|
|
2643
|
+
type: z13.literal("superseded"),
|
|
2644
|
+
parentId: z13.string(),
|
|
2645
|
+
notificationId: z13.string()
|
|
2579
2646
|
}),
|
|
2580
2647
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2581
2648
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2582
2649
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2583
2650
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2584
2651
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2585
|
-
|
|
2586
|
-
type:
|
|
2587
|
-
notificationId:
|
|
2588
|
-
inFlight:
|
|
2589
|
-
turn:
|
|
2590
|
-
idx:
|
|
2591
|
-
prompt:
|
|
2592
|
-
reply:
|
|
2593
|
-
acts:
|
|
2652
|
+
z13.object({
|
|
2653
|
+
type: z13.literal("partial"),
|
|
2654
|
+
notificationId: z13.string(),
|
|
2655
|
+
inFlight: z13.literal(true),
|
|
2656
|
+
turn: z13.object({
|
|
2657
|
+
idx: z13.number(),
|
|
2658
|
+
prompt: z13.string(),
|
|
2659
|
+
reply: z13.string(),
|
|
2660
|
+
acts: z13.array(IntentSchema).nullable().optional()
|
|
2594
2661
|
})
|
|
2595
2662
|
}),
|
|
2596
|
-
|
|
2597
|
-
type:
|
|
2598
|
-
also:
|
|
2663
|
+
z13.object({
|
|
2664
|
+
type: z13.literal("idle"),
|
|
2665
|
+
also: z13.array(RideAlongSchema).optional(),
|
|
2599
2666
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
2600
2667
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
2601
2668
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
2602
2669
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
2603
2670
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
2604
|
-
inFlight:
|
|
2671
|
+
inFlight: z13.boolean().optional()
|
|
2605
2672
|
})
|
|
2606
2673
|
]);
|
|
2607
|
-
var VoiceKeySchema =
|
|
2608
|
-
var AgendaTurnSchema =
|
|
2674
|
+
var VoiceKeySchema = z13.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2675
|
+
var AgendaTurnSchema = z13.object({
|
|
2609
2676
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
2610
2677
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
2611
2678
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
2612
2679
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
2613
2680
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
2614
2681
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
2615
|
-
id:
|
|
2682
|
+
id: z13.string().optional(),
|
|
2616
2683
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
2617
2684
|
* the planner declares duplicates instead of asking them twice. */
|
|
2618
|
-
coveredIds:
|
|
2685
|
+
coveredIds: z13.array(z13.string()).optional(),
|
|
2619
2686
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
2620
2687
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
2621
|
-
info:
|
|
2688
|
+
info: z13.array(z13.string().min(1)).default([]),
|
|
2622
2689
|
/** THE ID OF EACH SENTENCE, when the plan names them (turns.md): a talker message's sentences are its
|
|
2623
2690
|
* `info`, one piece each, and `units[i]` is the id the bot's line receipt carries for `info[i]`
|
|
2624
2691
|
* (`<cycle>:<key>:<n>`), so what was heard is known per sentence. Absent on every other turn. */
|
|
2625
|
-
units:
|
|
2626
|
-
question:
|
|
2692
|
+
units: z13.array(z13.string().min(1)).optional(),
|
|
2693
|
+
question: z13.string().min(1).nullable(),
|
|
2627
2694
|
/** True on the one turn carrying the agent's own declared question. */
|
|
2628
|
-
asks:
|
|
2695
|
+
asks: z13.boolean().optional(),
|
|
2629
2696
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
2630
2697
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
2631
2698
|
* which route nothing. */
|
|
2632
|
-
claimId:
|
|
2699
|
+
claimId: z13.string().optional(),
|
|
2633
2700
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
2634
|
-
voice:
|
|
2701
|
+
voice: z13.string().optional(),
|
|
2635
2702
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
2636
2703
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
2637
2704
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
2638
2705
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
2639
|
-
agent:
|
|
2706
|
+
agent: z13.string().optional(),
|
|
2640
2707
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
2641
2708
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
2642
2709
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
2643
2710
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
2644
2711
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
2645
|
-
agentId:
|
|
2712
|
+
agentId: z13.string().optional(),
|
|
2646
2713
|
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
2647
2714
|
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
2648
2715
|
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
2649
|
-
onTable:
|
|
2716
|
+
onTable: z13.boolean().optional(),
|
|
2650
2717
|
/** THE CARRIED ITEM THIS LINE TELLS (Goal 2a1e50eb, owner 2026-10-14: "Build the whole design") — a
|
|
2651
2718
|
* request the Call carries that asks NOTHING: a report, a progress note, another agent's waiting news.
|
|
2652
2719
|
* Its request Entry id, so the phone can make that agenda row the current one while it is read
|
|
@@ -2656,14 +2723,14 @@ var AgendaTurnSchema = z12.object({
|
|
|
2656
2723
|
* record tags the line with it (`workId`) — and an answer must never route to an Entry that asks
|
|
2657
2724
|
* nothing. Absent on a turn speaking for a question (`claimId` says that) and on one speaking for no
|
|
2658
2725
|
* carried item at all (an opener, a sign-off, Paigy's own line). */
|
|
2659
|
-
telling:
|
|
2726
|
+
telling: z13.string().optional(),
|
|
2660
2727
|
/** PAIGY'S OWN LINE (owner, 2026-10-15; calls ed2cbdce, ad50ad25): a turn that speaks for no item -- an
|
|
2661
2728
|
* opener, a sign-off, a reply about nothing an agent asked or told -- is recorded as Paigy's, not as the
|
|
2662
2729
|
* agent that rang. Absent on every turn that speaks for an item (`agentId` names whose), and on plans
|
|
2663
2730
|
* from before it: a bot that does not know it records the ringing agent, as before. */
|
|
2664
|
-
speaksFor:
|
|
2731
|
+
speaksFor: z13.literal("paigy").optional(),
|
|
2665
2732
|
select: SelectShapeSchema.optional(),
|
|
2666
|
-
options:
|
|
2733
|
+
options: z13.array(OptionSchema.omit({ id: true })).optional(),
|
|
2667
2734
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
2668
2735
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
2669
2736
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -2672,58 +2739,58 @@ var AgendaTurnSchema = z12.object({
|
|
|
2672
2739
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
2673
2740
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
2674
2741
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2675
|
-
blocking:
|
|
2742
|
+
blocking: z13.boolean().optional(),
|
|
2676
2743
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
2677
2744
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
2678
2745
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
2679
2746
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
2680
2747
|
* speak, it is dropped and their words are taken like any reply. */
|
|
2681
|
-
ifSilent:
|
|
2748
|
+
ifSilent: z13.boolean().optional()
|
|
2682
2749
|
});
|
|
2683
2750
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2684
|
-
var CallQuestionSchema =
|
|
2685
|
-
id:
|
|
2686
|
-
entryId:
|
|
2687
|
-
title:
|
|
2688
|
-
state:
|
|
2689
|
-
answer:
|
|
2751
|
+
var CallQuestionSchema = z13.object({
|
|
2752
|
+
id: z13.string(),
|
|
2753
|
+
entryId: z13.string(),
|
|
2754
|
+
title: z13.string(),
|
|
2755
|
+
state: z13.enum(["open", "answered"]),
|
|
2756
|
+
answer: z13.string().nullable(),
|
|
2690
2757
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2691
2758
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2692
2759
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2693
2760
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2694
|
-
agent:
|
|
2761
|
+
agent: z13.object({ tokenId: z13.string(), name: z13.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
2695
2762
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
2696
2763
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
2697
2764
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
2698
2765
|
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
2699
|
-
options:
|
|
2766
|
+
options: z13.array(OptionSchema).optional(),
|
|
2700
2767
|
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
2701
2768
|
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
2702
2769
|
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
2703
2770
|
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
2704
2771
|
* preview layout for any `options`, and must see exactly what it did. */
|
|
2705
|
-
choices:
|
|
2772
|
+
choices: z13.array(OptionSchema).optional(),
|
|
2706
2773
|
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
2707
|
-
select:
|
|
2774
|
+
select: z13.enum(["one", "many", "rank"]).optional()
|
|
2708
2775
|
});
|
|
2709
|
-
var InboxItemSchema =
|
|
2710
|
-
id:
|
|
2711
|
-
tokenId:
|
|
2776
|
+
var InboxItemSchema = z13.object({
|
|
2777
|
+
id: z13.string(),
|
|
2778
|
+
tokenId: z13.string().optional(),
|
|
2712
2779
|
status: NotifyStatusSchema,
|
|
2713
2780
|
context: ContextSchema,
|
|
2714
|
-
options:
|
|
2781
|
+
options: z13.array(OptionSchema).optional(),
|
|
2715
2782
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2716
|
-
points:
|
|
2783
|
+
points: z13.array(z13.string()).optional(),
|
|
2717
2784
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
2718
2785
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2719
2786
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2720
2787
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2721
|
-
asks:
|
|
2788
|
+
asks: z13.boolean().optional(),
|
|
2722
2789
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2723
2790
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2724
2791
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2725
2792
|
* which must never CLAIM stalled. */
|
|
2726
|
-
lastSeenAt:
|
|
2793
|
+
lastSeenAt: z13.string().optional(),
|
|
2727
2794
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2728
2795
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2729
2796
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2733,7 +2800,7 @@ var InboxItemSchema = z12.object({
|
|
|
2733
2800
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2734
2801
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2735
2802
|
* `createdAt`. */
|
|
2736
|
-
agentStateAt:
|
|
2803
|
+
agentStateAt: z13.string().datetime().optional(),
|
|
2737
2804
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
2738
2805
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
2739
2806
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -2747,40 +2814,35 @@ var InboxItemSchema = z12.object({
|
|
|
2747
2814
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2748
2815
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2749
2816
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2750
|
-
questions:
|
|
2817
|
+
questions: z13.array(CallQuestionSchema).optional(),
|
|
2751
2818
|
/** The same call carrying order, including request Entries with no DecisionNeed as reports.
|
|
2752
2819
|
* Additive: questions stays decision-only for every other surface and older bundles. */
|
|
2753
|
-
callItems:
|
|
2754
|
-
visuals:
|
|
2820
|
+
callItems: z13.array(CallQuestionSchema.extend({ state: z13.enum(["open", "answered", "report"]), acknowledged: z13.boolean().optional() })).optional(),
|
|
2821
|
+
visuals: z13.array(VisualSchema).optional(),
|
|
2755
2822
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2756
2823
|
* agent's suggestion, or a default silly name). */
|
|
2757
|
-
name:
|
|
2824
|
+
name: z13.string(),
|
|
2758
2825
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2759
2826
|
voice: VoiceKeySchema.optional(),
|
|
2760
|
-
repo:
|
|
2761
|
-
branch:
|
|
2762
|
-
createdAt:
|
|
2763
|
-
snoozedUntil:
|
|
2827
|
+
repo: z13.string().optional(),
|
|
2828
|
+
branch: z13.string().optional(),
|
|
2829
|
+
createdAt: z13.string().datetime(),
|
|
2830
|
+
snoozedUntil: z13.string().datetime().optional(),
|
|
2764
2831
|
agentState: AgentStateSchema.default("idle"),
|
|
2765
2832
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2766
2833
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2767
2834
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2768
|
-
turn:
|
|
2835
|
+
turn: z13.enum(["you", "agent"]).default("you"),
|
|
2769
2836
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2770
2837
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2771
2838
|
* error badge + Retry. */
|
|
2772
|
-
error:
|
|
2773
|
-
|
|
2774
|
-
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2775
|
-
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2776
|
-
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2777
|
-
cold: z12.string().datetime().optional(),
|
|
2778
|
-
clarifies: z12.string().optional(),
|
|
2839
|
+
error: z13.string().optional(),
|
|
2840
|
+
clarifies: z13.string().optional(),
|
|
2779
2841
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2780
2842
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2781
2843
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2782
2844
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2783
|
-
onCall:
|
|
2845
|
+
onCall: z13.literal(true).optional(),
|
|
2784
2846
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2785
2847
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2786
2848
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2790,31 +2852,31 @@ var InboxItemSchema = z12.object({
|
|
|
2790
2852
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2791
2853
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2792
2854
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2793
|
-
ring:
|
|
2794
|
-
declinedAt:
|
|
2795
|
-
anchorAt:
|
|
2796
|
-
nextRingAt:
|
|
2797
|
-
step:
|
|
2855
|
+
ring: z13.object({
|
|
2856
|
+
declinedAt: z13.string().datetime(),
|
|
2857
|
+
anchorAt: z13.string().datetime(),
|
|
2858
|
+
nextRingAt: z13.string().datetime().nullable(),
|
|
2859
|
+
step: z13.number().int()
|
|
2798
2860
|
}).optional(),
|
|
2799
2861
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
2800
2862
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
2801
2863
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
2802
2864
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
2803
2865
|
* feedback ABOUT. */
|
|
2804
|
-
why:
|
|
2866
|
+
why: z13.object({
|
|
2805
2867
|
asked: NotifyLevelSchema,
|
|
2806
2868
|
got: NotifyLevelSchema,
|
|
2807
|
-
because:
|
|
2808
|
-
line:
|
|
2869
|
+
because: z13.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2870
|
+
line: z13.string()
|
|
2809
2871
|
}).optional(),
|
|
2810
|
-
select:
|
|
2811
|
-
confirmStyle:
|
|
2872
|
+
select: z13.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2873
|
+
confirmStyle: z13.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2812
2874
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2813
2875
|
),
|
|
2814
2876
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
2815
|
-
* urgency (see the main README's "premier use case" + docs/
|
|
2877
|
+
* urgency (see the main README's "premier use case" + docs/history/notify-states.md). Drives the
|
|
2816
2878
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
2817
|
-
blocking:
|
|
2879
|
+
blocking: z13.boolean().default(false),
|
|
2818
2880
|
/** WHAT FREED THIS QUESTION (owner, 2026-10-16, Goal 2383ef14: "we don't really show the question
|
|
2819
2881
|
* at the top until the question it could reply to with is resolved. And then we show it again
|
|
2820
2882
|
* with the context of the other thing that was resolved"; asked how it should carry that, "The
|
|
@@ -2822,14 +2884,25 @@ var InboxItemSchema = z12.object({
|
|
|
2822
2884
|
* (`private.ask_back`) and drops out of the front while it waits; when it comes back, the card
|
|
2823
2885
|
* draws their question and the answer it got ABOVE the ask, as one card. Absent on a question
|
|
2824
2886
|
* nothing ever held back. */
|
|
2825
|
-
freedBy:
|
|
2826
|
-
questionId:
|
|
2827
|
-
asked:
|
|
2828
|
-
answer:
|
|
2829
|
-
answeredAt:
|
|
2887
|
+
freedBy: z13.object({
|
|
2888
|
+
questionId: z13.string(),
|
|
2889
|
+
asked: z13.string(),
|
|
2890
|
+
answer: z13.string(),
|
|
2891
|
+
answeredAt: z13.string(),
|
|
2830
2892
|
/** The answer in ten words, when the talker wrote one beside it. */
|
|
2831
|
-
summary:
|
|
2893
|
+
summary: z13.string().optional()
|
|
2832
2894
|
}).optional(),
|
|
2895
|
+
/** WHAT THIS CARD ANSWERS (owner, 2026-10-27, Goal 81b6a9d6: "this reads very clearly as an answer to
|
|
2896
|
+
* something I asked, but I don't see the question… To show the question near the top of the card").
|
|
2897
|
+
* An agent's card that is its answer to one of the person's own questions carries that question's
|
|
2898
|
+
* words, drawn above it as "You asked" (`public.questions_answered_by`, read beside the card read).
|
|
2899
|
+
* Absent on every card that answers nothing of theirs, and on a held question, which has `freedBy`. */
|
|
2900
|
+
answering: z13.object({ asked: z13.string() }).optional(),
|
|
2901
|
+
/** WHEN ITS AGENT LAST BUMPED THIS QUESTION (#2855, `contact`'s `bumpQuestionIds`): the agent asked
|
|
2902
|
+
* the person to notice it again, and For you puts it first, the latest bump on top
|
|
2903
|
+
* (`question/order.ts`). Absent on a question never bumped, and on every card from an API older
|
|
2904
|
+
* than the bump. */
|
|
2905
|
+
bumpedAt: z13.string().optional(),
|
|
2833
2906
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
2834
2907
|
answer: UserAnswerSchema.optional(),
|
|
2835
2908
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -2837,17 +2910,17 @@ var InboxItemSchema = z12.object({
|
|
|
2837
2910
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
2838
2911
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
2839
2912
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
2840
|
-
communication:
|
|
2841
|
-
deliveryId:
|
|
2842
|
-
kind:
|
|
2843
|
-
entryId:
|
|
2844
|
-
goalIds:
|
|
2845
|
-
decisionNeedId:
|
|
2846
|
-
sealed:
|
|
2847
|
-
goalState:
|
|
2913
|
+
communication: z13.object({
|
|
2914
|
+
deliveryId: z13.string(),
|
|
2915
|
+
kind: z13.enum(["notification", "call"]),
|
|
2916
|
+
entryId: z13.string(),
|
|
2917
|
+
goalIds: z13.array(z13.string()),
|
|
2918
|
+
decisionNeedId: z13.string().optional(),
|
|
2919
|
+
sealed: z13.boolean(),
|
|
2920
|
+
goalState: z13.string().optional(),
|
|
2848
2921
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2849
2922
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2850
|
-
goalTitle:
|
|
2923
|
+
goalTitle: z13.string().optional()
|
|
2851
2924
|
}).optional(),
|
|
2852
2925
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
2853
2926
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -2856,30 +2929,30 @@ var InboxItemSchema = z12.object({
|
|
|
2856
2929
|
*
|
|
2857
2930
|
* THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK — the same mechanism as `QueueItem.rev`
|
|
2858
2931
|
* (#2928) and an entry's (#3018), for the same reason: a card shows facts no `updated_at` of its
|
|
2859
|
-
* own moves (its Goal's state and name,
|
|
2932
|
+
* own moves (its Goal's state and name, whether a ring is
|
|
2860
2933
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
2861
2934
|
* with no rev is simply always re-sent. */
|
|
2862
|
-
rev:
|
|
2935
|
+
rev: z13.string().optional()
|
|
2863
2936
|
});
|
|
2864
2937
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
2865
|
-
var PushTokenSchema =
|
|
2866
|
-
voipToken:
|
|
2867
|
-
alertToken:
|
|
2868
|
-
fcmToken:
|
|
2869
|
-
platform:
|
|
2938
|
+
var PushTokenSchema = z13.object({
|
|
2939
|
+
voipToken: z13.string().min(1).optional(),
|
|
2940
|
+
alertToken: z13.string().min(1).optional(),
|
|
2941
|
+
fcmToken: z13.string().min(1).optional(),
|
|
2942
|
+
platform: z13.enum(["ios", "android"])
|
|
2870
2943
|
}).superRefine((v, ctx) => {
|
|
2871
2944
|
if (v.platform !== "ios") return;
|
|
2872
2945
|
for (const field of ["voipToken", "alertToken"]) {
|
|
2873
2946
|
const token = v[field];
|
|
2874
2947
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
2875
2948
|
ctx.addIssue({
|
|
2876
|
-
code:
|
|
2949
|
+
code: z13.ZodIssueCode.custom,
|
|
2877
2950
|
path: [field],
|
|
2878
2951
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
2879
2952
|
});
|
|
2880
2953
|
}
|
|
2881
2954
|
});
|
|
2882
|
-
var MissedCallSchema =
|
|
2955
|
+
var MissedCallSchema = z13.enum([
|
|
2883
2956
|
"retry_10m",
|
|
2884
2957
|
"retry_30m",
|
|
2885
2958
|
"retry_60m",
|
|
@@ -2891,31 +2964,32 @@ var MissedCallSchema = z12.enum([
|
|
|
2891
2964
|
]);
|
|
2892
2965
|
var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
2893
2966
|
var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
|
|
2894
|
-
var
|
|
2967
|
+
var THEIR_QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} their time; the count waits for morning.`;
|
|
2968
|
+
var BrokerTuningSchema = z13.object({
|
|
2895
2969
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
2896
|
-
ackVerbosity:
|
|
2970
|
+
ackVerbosity: z13.enum(["normal", "none"]).optional(),
|
|
2897
2971
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
2898
2972
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
2899
|
-
clarifyEagerness:
|
|
2973
|
+
clarifyEagerness: z13.enum(["low", "normal", "high"]).optional(),
|
|
2900
2974
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
2901
|
-
phrasebook:
|
|
2975
|
+
phrasebook: z13.array(z13.object({ say: z13.string().min(1).max(60), mean: z13.string().min(1).max(120) })).max(24).optional(),
|
|
2902
2976
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
2903
2977
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
2904
2978
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
2905
2979
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
2906
2980
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
2907
|
-
language:
|
|
2981
|
+
language: z13.enum(["en", "es"]).optional()
|
|
2908
2982
|
});
|
|
2909
|
-
var UserSettingsSchema =
|
|
2910
|
-
permissions:
|
|
2911
|
-
call:
|
|
2912
|
-
banner:
|
|
2913
|
-
push:
|
|
2983
|
+
var UserSettingsSchema = z13.object({
|
|
2984
|
+
permissions: z13.object({
|
|
2985
|
+
call: z13.boolean(),
|
|
2986
|
+
banner: z13.boolean(),
|
|
2987
|
+
push: z13.boolean()
|
|
2914
2988
|
}),
|
|
2915
2989
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
2916
2990
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
2917
2991
|
* what people read (owner, 2026-09-30). */
|
|
2918
|
-
sessionMode:
|
|
2992
|
+
sessionMode: z13.enum(["default", "all_calls", "silent"]),
|
|
2919
2993
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
2920
2994
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
2921
2995
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -2924,7 +2998,7 @@ var UserSettingsSchema = z12.object({
|
|
|
2924
2998
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
2925
2999
|
* next dead field for the same reason. */
|
|
2926
3000
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
2927
|
-
improveConsent:
|
|
3001
|
+
improveConsent: z13.boolean(),
|
|
2928
3002
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
2929
3003
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
2930
3004
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -2932,17 +3006,14 @@ var UserSettingsSchema = z12.object({
|
|
|
2932
3006
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
2933
3007
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
2934
3008
|
* write, 'hosted' on read (see store.ts). */
|
|
2935
|
-
voiceMode:
|
|
2936
|
-
/**
|
|
2937
|
-
*
|
|
2938
|
-
*
|
|
2939
|
-
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2940
|
-
* read (see store.ts). */
|
|
2941
|
-
talk: z12.boolean().optional(),
|
|
3009
|
+
voiceMode: z13.enum(["hosted", "on_device"]).optional(),
|
|
3010
|
+
/** `talk` lived here until #2373: it gated auto-play, which went in #2374. An older bundle's
|
|
3011
|
+
* `talk` is stripped like `silentPush` was; the `user_settings.talk` column outlives it until
|
|
3012
|
+
* its own drop migration (#2373, step two). */
|
|
2942
3013
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
2943
3014
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
2944
3015
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
2945
|
-
callDiagnostics:
|
|
3016
|
+
callDiagnostics: z13.boolean().optional(),
|
|
2946
3017
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2947
3018
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2948
3019
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2950,7 +3021,7 @@ var UserSettingsSchema = z12.object({
|
|
|
2950
3021
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2951
3022
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2952
3023
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2953
|
-
voiceTuning:
|
|
3024
|
+
voiceTuning: z13.record(z13.string(), z13.union([z13.number(), z13.string()])).optional(),
|
|
2954
3025
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2955
3026
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2956
3027
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2959,108 +3030,109 @@ var UserSettingsSchema = z12.object({
|
|
|
2959
3030
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2960
3031
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2961
3032
|
* call — honest about what we know. */
|
|
2962
|
-
timezone:
|
|
3033
|
+
timezone: z13.string().min(1).max(64).optional(),
|
|
2963
3034
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2964
3035
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2965
3036
|
broker: BrokerTuningSchema.optional()
|
|
2966
3037
|
});
|
|
2967
|
-
var GoalLatestSchema =
|
|
2968
|
-
from:
|
|
2969
|
-
said:
|
|
2970
|
-
at:
|
|
2971
|
-
entryId:
|
|
3038
|
+
var GoalLatestSchema = z13.object({
|
|
3039
|
+
from: z13.enum(["person", "agent"]),
|
|
3040
|
+
said: z13.string(),
|
|
3041
|
+
at: z13.string(),
|
|
3042
|
+
entryId: z13.string(),
|
|
2972
3043
|
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
2973
3044
|
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
2974
3045
|
* none was. The Work row reads it before `said`. */
|
|
2975
|
-
summary:
|
|
3046
|
+
summary: z13.string().optional(),
|
|
2976
3047
|
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
2977
3048
|
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
2978
|
-
titles:
|
|
3049
|
+
titles: z13.array(z13.string()).optional(),
|
|
2979
3050
|
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
2980
3051
|
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
2981
|
-
line:
|
|
3052
|
+
line: z13.string().optional()
|
|
2982
3053
|
});
|
|
2983
|
-
var HistoryWorkSchema =
|
|
2984
|
-
id:
|
|
2985
|
-
title:
|
|
2986
|
-
state:
|
|
3054
|
+
var HistoryWorkSchema = z13.object({
|
|
3055
|
+
id: z13.string(),
|
|
3056
|
+
title: z13.string(),
|
|
3057
|
+
state: z13.enum(["done", "cancelled"]),
|
|
2987
3058
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
2988
|
-
assignee:
|
|
3059
|
+
assignee: z13.string(),
|
|
2989
3060
|
latest: GoalLatestSchema.nullable().optional()
|
|
2990
3061
|
});
|
|
2991
|
-
var HistoryEntrySchema =
|
|
2992
|
-
|
|
2993
|
-
|
|
3062
|
+
var HistoryEntrySchema = z13.union([
|
|
3063
|
+
z13.object({ at: z13.string(), card: InboxItemSchema }),
|
|
3064
|
+
z13.object({ at: z13.string(), work: HistoryWorkSchema })
|
|
2994
3065
|
]);
|
|
2995
|
-
var HistoryPageSchema =
|
|
2996
|
-
entries:
|
|
2997
|
-
next:
|
|
3066
|
+
var HistoryPageSchema = z13.object({
|
|
3067
|
+
entries: z13.array(HistoryEntrySchema),
|
|
3068
|
+
next: z13.string().nullable()
|
|
2998
3069
|
});
|
|
2999
3070
|
var ACTIVITY_LINES = 2;
|
|
3000
3071
|
var ACTIVITY_LINE_MAX = 80;
|
|
3001
|
-
var AgentActivitySchema =
|
|
3072
|
+
var AgentActivitySchema = z13.object({
|
|
3002
3073
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
3003
|
-
lines:
|
|
3074
|
+
lines: z13.array(z13.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
3004
3075
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
3005
3076
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
3006
|
-
at:
|
|
3077
|
+
at: z13.string().datetime()
|
|
3007
3078
|
});
|
|
3008
|
-
var ConnectionSummarySchema =
|
|
3079
|
+
var ConnectionSummarySchema = z13.object({
|
|
3009
3080
|
/** The connection = the agent's token id (used to address a request). */
|
|
3010
|
-
id:
|
|
3081
|
+
id: z13.string(),
|
|
3011
3082
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
3012
3083
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
3013
3084
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
3014
3085
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
3015
|
-
kind:
|
|
3086
|
+
kind: z13.enum(["device", "agent"]).optional(),
|
|
3016
3087
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
3017
3088
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
3018
|
-
* agents (
|
|
3019
|
-
|
|
3020
|
-
|
|
3089
|
+
* agents (provider-managed, hatched by another agent, or minted before the link existed);
|
|
3090
|
+
* a phone-launched agent is linked to the computer it runs on since #1602. */
|
|
3091
|
+
mintedByDevice: z13.string().nullable().optional(),
|
|
3092
|
+
device: z13.string().nullable(),
|
|
3021
3093
|
/** The agent's display name (the single pairing name). */
|
|
3022
|
-
name:
|
|
3094
|
+
name: z13.string(),
|
|
3023
3095
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
3024
3096
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
3025
|
-
provider:
|
|
3097
|
+
provider: z13.string().nullable(),
|
|
3026
3098
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
3027
3099
|
voice: VoiceKeySchema.nullable(),
|
|
3028
3100
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
3029
3101
|
* user on the agent's own page. null = no ceiling (today's behaviour for every
|
|
3030
3102
|
* connection). Android binds importance to a relationship rather than to each message,
|
|
3031
3103
|
* and that is the thing our roster could not say: "Marlow may always call me; Otto
|
|
3032
|
-
* never may" (docs/
|
|
3104
|
+
* never may" (docs/history/party-navigation-design.md, gap 2). Clamped in `arbitrateLevel`, so it binds
|
|
3033
3105
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
3034
3106
|
* set once must not overrule a rule they set about one agent. */
|
|
3035
3107
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
3036
|
-
createdAt:
|
|
3108
|
+
createdAt: z13.string().datetime(),
|
|
3037
3109
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
3038
3110
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
3039
|
-
lastContactAt:
|
|
3111
|
+
lastContactAt: z13.string().datetime().nullable(),
|
|
3040
3112
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
3041
3113
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
3042
|
-
lastSeenAt:
|
|
3114
|
+
lastSeenAt: z13.string().datetime().nullable().optional(),
|
|
3043
3115
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
3044
3116
|
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
3045
3117
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
3046
|
-
lastWorkedAt:
|
|
3118
|
+
lastWorkedAt: z13.string().datetime().nullable().optional(),
|
|
3047
3119
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
3048
3120
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
3049
|
-
oldestReadyAt:
|
|
3121
|
+
oldestReadyAt: z13.string().datetime().nullable().optional(),
|
|
3050
3122
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
3051
3123
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
3052
3124
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
3053
|
-
runtime:
|
|
3125
|
+
runtime: z13.object({
|
|
3054
3126
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
3055
3127
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
3056
|
-
version:
|
|
3057
|
-
harnesses:
|
|
3058
|
-
workspaces:
|
|
3128
|
+
version: z13.string().optional(),
|
|
3129
|
+
harnesses: z13.array(z13.object({ name: z13.string(), label: z13.string(), status: z13.string(), models: z13.array(z13.string()).optional() })).optional(),
|
|
3130
|
+
workspaces: z13.array(z13.string()).optional(),
|
|
3059
3131
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
3060
3132
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
3061
3133
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
3062
3134
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
3063
|
-
repos:
|
|
3135
|
+
repos: z13.array(z13.object({ path: z13.string(), remote: z13.string() })).optional()
|
|
3064
3136
|
}).optional(),
|
|
3065
3137
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
3066
3138
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -3069,145 +3141,151 @@ var ConnectionSummarySchema = z12.object({
|
|
|
3069
3141
|
activity: AgentActivitySchema.optional(),
|
|
3070
3142
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
3071
3143
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
3072
|
-
managed:
|
|
3144
|
+
managed: z13.boolean()
|
|
3073
3145
|
});
|
|
3074
|
-
var LedgerItemSchema =
|
|
3075
|
-
var AgentLedgerSchema =
|
|
3146
|
+
var LedgerItemSchema = z13.object({ id: z13.string(), parentId: z13.string(), title: z13.string(), createdAt: z13.string() });
|
|
3147
|
+
var AgentLedgerSchema = z13.object({
|
|
3076
3148
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
3077
|
-
agent:
|
|
3149
|
+
agent: z13.object({ id: z13.string(), name: z13.string().nullable(), revokedAt: z13.string().nullable() }),
|
|
3078
3150
|
/** Its own questions you have not answered. */
|
|
3079
|
-
asks:
|
|
3151
|
+
asks: z13.array(LedgerItemSchema),
|
|
3080
3152
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
3081
|
-
answered:
|
|
3153
|
+
answered: z13.array(LedgerItemSchema),
|
|
3082
3154
|
/** Requests you sent it that it never took. */
|
|
3083
|
-
requests:
|
|
3084
|
-
goals:
|
|
3085
|
-
callbacks:
|
|
3155
|
+
requests: z13.array(LedgerItemSchema),
|
|
3156
|
+
goals: z13.array(z13.object({ id: z13.string(), outcome: z13.string(), state: z13.string() })),
|
|
3157
|
+
callbacks: z13.array(z13.object({ id: z13.string(), parentId: z13.string(), trigger: z13.string(), note: z13.string(), dueAt: z13.string().nullable() }))
|
|
3086
3158
|
});
|
|
3087
|
-
var ReassignResultSchema =
|
|
3088
|
-
moved:
|
|
3089
|
-
parentId:
|
|
3159
|
+
var ReassignResultSchema = z13.object({
|
|
3160
|
+
moved: z13.object({ asks: z13.number(), answered: z13.number(), requests: z13.number(), goals: z13.number(), callbacks: z13.number() }),
|
|
3161
|
+
parentId: z13.string().nullable()
|
|
3090
3162
|
});
|
|
3091
|
-
var LessonStateSchema =
|
|
3092
|
-
var LessonViewSchema =
|
|
3093
|
-
id:
|
|
3094
|
-
text:
|
|
3163
|
+
var LessonStateSchema = z13.enum(["active", "proposed", "retired"]);
|
|
3164
|
+
var LessonViewSchema = z13.object({
|
|
3165
|
+
id: z13.string(),
|
|
3166
|
+
text: z13.string(),
|
|
3095
3167
|
state: LessonStateSchema,
|
|
3096
3168
|
/** The Goal it is scoped to; null = the whole account. */
|
|
3097
|
-
scopeGoalId:
|
|
3098
|
-
goalTitle:
|
|
3099
|
-
version:
|
|
3100
|
-
pinned:
|
|
3169
|
+
scopeGoalId: z13.string().nullable(),
|
|
3170
|
+
goalTitle: z13.string().nullable(),
|
|
3171
|
+
version: z13.number(),
|
|
3172
|
+
pinned: z13.boolean(),
|
|
3101
3173
|
/** When the person last wrote its text themselves. */
|
|
3102
|
-
editedAt:
|
|
3103
|
-
createdAt:
|
|
3104
|
-
updatedAt:
|
|
3174
|
+
editedAt: z13.string().nullable(),
|
|
3175
|
+
createdAt: z13.string(),
|
|
3176
|
+
updatedAt: z13.string(),
|
|
3105
3177
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
3106
|
-
sources:
|
|
3178
|
+
sources: z13.array(z13.object({ entryId: z13.string(), words: z13.string().nullable(), at: z13.string() }))
|
|
3107
3179
|
});
|
|
3108
|
-
var QueueQuestionSchema =
|
|
3180
|
+
var QueueQuestionSchema = z13.object({
|
|
3109
3181
|
/** The decision need's id — what an answer is accepted against. */
|
|
3110
|
-
id:
|
|
3182
|
+
id: z13.string(),
|
|
3111
3183
|
/** The words that were asked, from the request Entry that asked them. */
|
|
3112
|
-
question:
|
|
3184
|
+
question: z13.string(),
|
|
3113
3185
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
3114
3186
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
3115
3187
|
* can answer. */
|
|
3116
|
-
deliveryId:
|
|
3188
|
+
deliveryId: z13.string().nullable().default(null),
|
|
3117
3189
|
/** The Entry the ruling is about. */
|
|
3118
|
-
aboutId:
|
|
3190
|
+
aboutId: z13.string().nullable().default(null),
|
|
3119
3191
|
/** Empty for a free-text question. */
|
|
3120
|
-
options:
|
|
3121
|
-
select:
|
|
3122
|
-
askedAt:
|
|
3192
|
+
options: z13.array(OptionSchema).default([]),
|
|
3193
|
+
select: z13.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
3194
|
+
askedAt: z13.string(),
|
|
3123
3195
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
3124
|
-
answeredAt:
|
|
3196
|
+
answeredAt: z13.string().nullable().default(null),
|
|
3125
3197
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
3126
3198
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
3127
3199
|
* for a settled question whose reply carried nothing readable. */
|
|
3128
|
-
answer:
|
|
3200
|
+
answer: z13.string().nullable().default(null),
|
|
3129
3201
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
3130
3202
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
3131
3203
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
3132
3204
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
3133
3205
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
3134
3206
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
3135
|
-
goalId:
|
|
3207
|
+
goalId: z13.string(),
|
|
3136
3208
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
3137
3209
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
3138
3210
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
3139
3211
|
* gate at all. */
|
|
3140
|
-
blocked:
|
|
3212
|
+
blocked: z13.boolean().default(false)
|
|
3141
3213
|
});
|
|
3142
|
-
var QueueReplySchema =
|
|
3214
|
+
var QueueReplySchema = z13.object({
|
|
3143
3215
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
3144
3216
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
3145
3217
|
* walk can name it in its zoom. */
|
|
3146
|
-
id:
|
|
3218
|
+
id: z13.string(),
|
|
3147
3219
|
/** The Goal the note is on. */
|
|
3148
|
-
goalId:
|
|
3220
|
+
goalId: z13.string(),
|
|
3149
3221
|
/** What the note said. */
|
|
3150
|
-
note:
|
|
3222
|
+
note: z13.string(),
|
|
3151
3223
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
3152
|
-
deliveryId:
|
|
3153
|
-
requestEntryId:
|
|
3154
|
-
askedAt:
|
|
3224
|
+
deliveryId: z13.string(),
|
|
3225
|
+
requestEntryId: z13.string(),
|
|
3226
|
+
askedAt: z13.string(),
|
|
3155
3227
|
/** When the person last replied — the window's start. */
|
|
3156
|
-
repliedAt:
|
|
3228
|
+
repliedAt: z13.string(),
|
|
3157
3229
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
3158
|
-
reply:
|
|
3230
|
+
reply: z13.string().nullable()
|
|
3159
3231
|
});
|
|
3160
|
-
var QueueItemSchema =
|
|
3161
|
-
id:
|
|
3232
|
+
var QueueItemSchema = z13.object({
|
|
3233
|
+
id: z13.string(),
|
|
3162
3234
|
/** One-line headline — the first sentence of the outcome. */
|
|
3163
|
-
title:
|
|
3235
|
+
title: z13.string(),
|
|
3164
3236
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
3165
|
-
intent:
|
|
3237
|
+
intent: z13.string(),
|
|
3166
3238
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
3167
|
-
state:
|
|
3239
|
+
state: z13.string(),
|
|
3168
3240
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
3169
|
-
assignee:
|
|
3241
|
+
assignee: z13.string().nullable().default(null),
|
|
3170
3242
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
3171
|
-
progress:
|
|
3243
|
+
progress: z13.string().nullable().default(null),
|
|
3172
3244
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
3173
3245
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
3174
|
-
progressLine:
|
|
3175
|
-
reviewPending:
|
|
3176
|
-
dueAt:
|
|
3246
|
+
progressLine: z13.string().nullable().optional(),
|
|
3247
|
+
reviewPending: z13.boolean().default(false),
|
|
3248
|
+
dueAt: z13.string().nullable().default(null),
|
|
3177
3249
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
3178
3250
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
3179
|
-
finishedAt:
|
|
3251
|
+
finishedAt: z13.string().nullable().optional(),
|
|
3180
3252
|
/** The Goal this one was opened under; null at the root. */
|
|
3181
|
-
parentGoalId:
|
|
3253
|
+
parentGoalId: z13.string().nullable().default(null),
|
|
3182
3254
|
/** Goals opened under this one — only those the same list holds. */
|
|
3183
|
-
childGoalIds:
|
|
3255
|
+
childGoalIds: z13.array(z13.string()).default([]),
|
|
3184
3256
|
/** Goals this one waits on (start or finish gates). */
|
|
3185
|
-
dependencyGoalIds:
|
|
3257
|
+
dependencyGoalIds: z13.array(z13.string()).default([]),
|
|
3186
3258
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
3187
|
-
blocked:
|
|
3259
|
+
blocked: z13.boolean().default(false),
|
|
3188
3260
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
3189
3261
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
3190
3262
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
3191
|
-
* question is opened.
|
|
3192
|
-
|
|
3263
|
+
* question is opened. Never a CANCELLED one (withdrawn, dismissed, or its Goal or call ended): it
|
|
3264
|
+
* asks nothing of anyone (#3723). */
|
|
3265
|
+
questions: z13.array(QueueQuestionSchema).default([]),
|
|
3193
3266
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
3194
3267
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
3195
3268
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
3196
|
-
* by the projection, so hand-built queues (fixtures, the demo) need not spell them.
|
|
3197
|
-
asked
|
|
3198
|
-
|
|
3269
|
+
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. A cancelled
|
|
3270
|
+
* question is not asked (#3725). */
|
|
3271
|
+
asked: z13.number().optional(),
|
|
3272
|
+
answered: z13.number().optional(),
|
|
3199
3273
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
3200
3274
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
3201
3275
|
* not spell an empty list. */
|
|
3202
|
-
replies:
|
|
3276
|
+
replies: z13.array(QueueReplySchema).optional(),
|
|
3203
3277
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
3204
|
-
repo:
|
|
3205
|
-
createdAt:
|
|
3206
|
-
updatedAt:
|
|
3278
|
+
repo: z13.string().nullable().optional(),
|
|
3279
|
+
createdAt: z13.string(),
|
|
3280
|
+
updatedAt: z13.string().nullable().default(null),
|
|
3207
3281
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
3208
3282
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
3209
3283
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
3210
|
-
lastProgressAt:
|
|
3284
|
+
lastProgressAt: z13.string().nullable().optional(),
|
|
3285
|
+
/** WHEN AN AGENT LAST ACTED ON IT (owner, 2026-10-27: "Move it now", Goal 567258dc): its newest agent write, an
|
|
3286
|
+
* Entry or a Goal change, Paigy's excluded (`goals.worked_at`). What keeps work an agent is writing on from
|
|
3287
|
+
* reading as gone quiet (`coldSince`); absent on a door that does not carry it. */
|
|
3288
|
+
workedAt: z13.string().nullable().optional(),
|
|
3211
3289
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
3212
3290
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
3213
3291
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
@@ -3226,7 +3304,7 @@ var QueueItemSchema = z12.object({
|
|
|
3226
3304
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
3227
3305
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
3228
3306
|
* Entry with no `about_id` is in neither. */
|
|
3229
|
-
lastPersonAt:
|
|
3307
|
+
lastPersonAt: z13.string().nullable().optional(),
|
|
3230
3308
|
/** EACH SIDE'S NEWEST WORD IN THE LAST DAY (owner, 2026-10-13, the Work tab's order): the person's
|
|
3231
3309
|
* (`yours`) and the agents' (`theirs`), each null when that side wrote nothing on it in 24 hours.
|
|
3232
3310
|
* `latest` holds one Entry, so whichever side wrote first is hidden there; Work orders a Goal by
|
|
@@ -3244,40 +3322,40 @@ var QueueItemSchema = z12.object({
|
|
|
3244
3322
|
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
3245
3323
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
3246
3324
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
3247
|
-
rev:
|
|
3325
|
+
rev: z13.string().optional()
|
|
3248
3326
|
});
|
|
3249
|
-
var QueueDeltaSchema =
|
|
3250
|
-
ids:
|
|
3251
|
-
items:
|
|
3327
|
+
var QueueDeltaSchema = z13.object({
|
|
3328
|
+
ids: z13.array(z13.string()),
|
|
3329
|
+
items: z13.array(QueueItemSchema)
|
|
3252
3330
|
});
|
|
3253
3331
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
3254
|
-
var NoteSourceSchema =
|
|
3255
|
-
var NoteStatusSchema =
|
|
3256
|
-
var NoteRepeatSchema =
|
|
3257
|
-
var DecisionSchema =
|
|
3258
|
-
id:
|
|
3332
|
+
var NoteSourceSchema = z13.enum(["app", "call"]);
|
|
3333
|
+
var NoteStatusSchema = z13.enum(["open", "assigned", "in_progress", "done"]);
|
|
3334
|
+
var NoteRepeatSchema = z13.enum(["once", "until_done"]);
|
|
3335
|
+
var DecisionSchema = z13.object({
|
|
3336
|
+
id: z13.string(),
|
|
3259
3337
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
3260
3338
|
* extensibility seam — any conversation can accrue decisions). */
|
|
3261
|
-
noteId:
|
|
3339
|
+
noteId: z13.string().nullable(),
|
|
3262
3340
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
3263
|
-
question:
|
|
3341
|
+
question: z13.string(),
|
|
3264
3342
|
/** The user's ruling; null while the question is open. */
|
|
3265
|
-
answer:
|
|
3266
|
-
decidedAt:
|
|
3267
|
-
createdAt:
|
|
3343
|
+
answer: z13.string().nullable(),
|
|
3344
|
+
decidedAt: z13.string().nullable(),
|
|
3345
|
+
createdAt: z13.string()
|
|
3268
3346
|
});
|
|
3269
|
-
var NoteSchema =
|
|
3270
|
-
id:
|
|
3347
|
+
var NoteSchema = z13.object({
|
|
3348
|
+
id: z13.string(),
|
|
3271
3349
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
3272
|
-
title:
|
|
3350
|
+
title: z13.string(),
|
|
3273
3351
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
3274
|
-
intent:
|
|
3352
|
+
intent: z13.string(),
|
|
3275
3353
|
source: NoteSourceSchema,
|
|
3276
3354
|
status: NoteStatusSchema,
|
|
3277
3355
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
3278
|
-
assignee:
|
|
3356
|
+
assignee: z13.string().nullable(),
|
|
3279
3357
|
/** The request thread minted at assignment; null until assigned. */
|
|
3280
|
-
parentId:
|
|
3358
|
+
parentId: z13.string().nullable(),
|
|
3281
3359
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3282
3360
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
3283
3361
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -3286,102 +3364,102 @@ var NoteSchema = z12.object({
|
|
|
3286
3364
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3287
3365
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
3288
3366
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
3289
|
-
dueAt:
|
|
3367
|
+
dueAt: z13.string().nullable().default(null),
|
|
3290
3368
|
repeat: NoteRepeatSchema.default("once"),
|
|
3291
3369
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
3292
|
-
rides:
|
|
3293
|
-
lastRideAt:
|
|
3294
|
-
createdAt:
|
|
3370
|
+
rides: z13.number().int().default(0),
|
|
3371
|
+
lastRideAt: z13.string().nullable().default(null),
|
|
3372
|
+
createdAt: z13.string()
|
|
3295
3373
|
});
|
|
3296
|
-
var DeliveryModeSchema =
|
|
3374
|
+
var DeliveryModeSchema = z13.enum(["poll", "self_hosted"]);
|
|
3297
3375
|
var WAKE_EVENT = "wake";
|
|
3298
3376
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
3299
|
-
var RegisterDeliverySchema =
|
|
3300
|
-
var OAuthStartSchema =
|
|
3301
|
-
provider:
|
|
3302
|
-
returnTo:
|
|
3377
|
+
var RegisterDeliverySchema = z13.object({ mode: DeliveryModeSchema });
|
|
3378
|
+
var OAuthStartSchema = z13.object({
|
|
3379
|
+
provider: z13.enum(["cma"]),
|
|
3380
|
+
returnTo: z13.string().min(1)
|
|
3303
3381
|
});
|
|
3304
|
-
var DeliveryConfigSchema =
|
|
3305
|
-
tokenId:
|
|
3382
|
+
var DeliveryConfigSchema = z13.object({
|
|
3383
|
+
tokenId: z13.string(),
|
|
3306
3384
|
mode: DeliveryModeSchema,
|
|
3307
3385
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
3308
3386
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
3309
3387
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
3310
|
-
realtime:
|
|
3388
|
+
realtime: z13.object({ url: z13.string(), anonKey: z13.string() }).nullable()
|
|
3311
3389
|
});
|
|
3312
|
-
var HostDecisionSchema =
|
|
3390
|
+
var HostDecisionSchema = z13.object({
|
|
3313
3391
|
/** The agent's token id: the row's `recipient`. */
|
|
3314
|
-
agent:
|
|
3315
|
-
decision:
|
|
3392
|
+
agent: z13.string().uuid(),
|
|
3393
|
+
decision: z13.enum(["stood_back", "took_over"]),
|
|
3316
3394
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
3317
|
-
goalId:
|
|
3395
|
+
goalId: z13.string().uuid().nullable().optional(),
|
|
3318
3396
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
3319
|
-
seenAt:
|
|
3397
|
+
seenAt: z13.string().datetime().nullable().optional(),
|
|
3320
3398
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
3321
|
-
since:
|
|
3399
|
+
since: z13.string().datetime().nullable().optional(),
|
|
3322
3400
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
3323
|
-
said:
|
|
3401
|
+
said: z13.string().max(300).optional()
|
|
3324
3402
|
});
|
|
3325
|
-
var WakeNudgeSchema =
|
|
3326
|
-
kind:
|
|
3327
|
-
notificationId:
|
|
3328
|
-
parentId:
|
|
3403
|
+
var WakeNudgeSchema = z13.object({
|
|
3404
|
+
kind: z13.enum(["reply", "request", "callback"]),
|
|
3405
|
+
notificationId: z13.string().optional(),
|
|
3406
|
+
parentId: z13.string()
|
|
3329
3407
|
});
|
|
3330
|
-
var PairingStatusSchema =
|
|
3331
|
-
var DeviceCodeSchema =
|
|
3332
|
-
device_code:
|
|
3333
|
-
user_code:
|
|
3334
|
-
verification_uri:
|
|
3335
|
-
verification_uri_complete:
|
|
3336
|
-
interval:
|
|
3337
|
-
expires_in:
|
|
3408
|
+
var PairingStatusSchema = z13.enum(["pending", "approved", "denied", "expired"]);
|
|
3409
|
+
var DeviceCodeSchema = z13.object({
|
|
3410
|
+
device_code: z13.string(),
|
|
3411
|
+
user_code: z13.string(),
|
|
3412
|
+
verification_uri: z13.string().url(),
|
|
3413
|
+
verification_uri_complete: z13.string().url(),
|
|
3414
|
+
interval: z13.number(),
|
|
3415
|
+
expires_in: z13.number()
|
|
3338
3416
|
});
|
|
3339
|
-
var DeviceInfoSchema =
|
|
3340
|
-
code:
|
|
3417
|
+
var DeviceInfoSchema = z13.object({
|
|
3418
|
+
code: z13.string(),
|
|
3341
3419
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3342
3420
|
* pre-filling the name field the human can edit. */
|
|
3343
|
-
name:
|
|
3421
|
+
name: z13.string(),
|
|
3344
3422
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3345
3423
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3346
3424
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3347
3425
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3348
|
-
agent:
|
|
3349
|
-
device:
|
|
3426
|
+
agent: z13.string().optional(),
|
|
3427
|
+
device: z13.string().nullable(),
|
|
3350
3428
|
status: PairingStatusSchema
|
|
3351
3429
|
});
|
|
3352
|
-
var DeviceTokenSchema =
|
|
3353
|
-
access_token:
|
|
3430
|
+
var DeviceTokenSchema = z13.object({
|
|
3431
|
+
access_token: z13.string(),
|
|
3354
3432
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3355
3433
|
* a default silly name). */
|
|
3356
|
-
name:
|
|
3357
|
-
device:
|
|
3434
|
+
name: z13.string(),
|
|
3435
|
+
device: z13.string().nullable(),
|
|
3358
3436
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3359
3437
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3360
|
-
voice:
|
|
3438
|
+
voice: z13.string().nullable().optional(),
|
|
3361
3439
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3362
3440
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3363
|
-
token_id:
|
|
3441
|
+
token_id: z13.string().nullable().optional(),
|
|
3364
3442
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3365
3443
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3366
3444
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3367
3445
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3368
|
-
workspace:
|
|
3446
|
+
workspace: z13.string().nullable().optional(),
|
|
3369
3447
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
3370
|
-
harness:
|
|
3371
|
-
session_id:
|
|
3448
|
+
harness: z13.enum(["claude", "codex", "agy"]).optional(),
|
|
3449
|
+
session_id: z13.string().uuid().optional(),
|
|
3372
3450
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
3373
3451
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
3374
3452
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
3375
3453
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
3376
|
-
full_session:
|
|
3377
|
-
uik_pub:
|
|
3454
|
+
full_session: z13.string().optional(),
|
|
3455
|
+
uik_pub: z13.string().nullable().optional()
|
|
3378
3456
|
});
|
|
3379
|
-
var SupportRequestSchema =
|
|
3380
|
-
email:
|
|
3381
|
-
message:
|
|
3382
|
-
name:
|
|
3457
|
+
var SupportRequestSchema = z13.object({
|
|
3458
|
+
email: z13.string().email().max(320),
|
|
3459
|
+
message: z13.string().trim().min(1).max(5e3),
|
|
3460
|
+
name: z13.string().trim().max(120).optional()
|
|
3383
3461
|
});
|
|
3384
|
-
var NotificationFeedbackKindSchema =
|
|
3462
|
+
var NotificationFeedbackKindSchema = z13.enum([
|
|
3385
3463
|
"break_down",
|
|
3386
3464
|
// "This should be more than one ask — break it down."
|
|
3387
3465
|
"regenerate_options",
|
|
@@ -3396,131 +3474,133 @@ var NotificationFeedbackKindSchema = z12.enum([
|
|
|
3396
3474
|
// anything else — the note carries it.
|
|
3397
3475
|
]);
|
|
3398
3476
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
3399
|
-
var QuestionRowSchema =
|
|
3477
|
+
var QuestionRowSchema = z13.object({
|
|
3400
3478
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
3401
|
-
id:
|
|
3402
|
-
deliveryId:
|
|
3403
|
-
entryId:
|
|
3479
|
+
id: z13.string(),
|
|
3480
|
+
deliveryId: z13.string(),
|
|
3481
|
+
entryId: z13.string(),
|
|
3404
3482
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
3405
|
-
needId:
|
|
3406
|
-
goalIds:
|
|
3483
|
+
needId: z13.string().nullable(),
|
|
3484
|
+
goalIds: z13.array(z13.string()),
|
|
3407
3485
|
/** The name of the work it is about, when the read could word it. */
|
|
3408
|
-
goalTitle:
|
|
3409
|
-
tokenId:
|
|
3410
|
-
name:
|
|
3411
|
-
title:
|
|
3412
|
-
body:
|
|
3413
|
-
select:
|
|
3414
|
-
options:
|
|
3415
|
-
hasPreview:
|
|
3416
|
-
blocking:
|
|
3417
|
-
askedAt:
|
|
3486
|
+
goalTitle: z13.string().optional(),
|
|
3487
|
+
tokenId: z13.string().optional(),
|
|
3488
|
+
name: z13.string(),
|
|
3489
|
+
title: z13.string(),
|
|
3490
|
+
body: z13.string(),
|
|
3491
|
+
select: z13.enum(["one", "many", "rank", "confirm", "text"]),
|
|
3492
|
+
options: z13.array(SlimOptionSchema),
|
|
3493
|
+
hasPreview: z13.boolean(),
|
|
3494
|
+
blocking: z13.boolean(),
|
|
3495
|
+
askedAt: z13.string().datetime(),
|
|
3418
3496
|
ring: InboxItemSchema.shape.ring,
|
|
3419
|
-
onCall:
|
|
3420
|
-
sealed:
|
|
3497
|
+
onCall: z13.literal(true).optional(),
|
|
3498
|
+
sealed: z13.boolean()
|
|
3421
3499
|
});
|
|
3422
|
-
var WorkStateSchema =
|
|
3423
|
-
var WorkRowSchema =
|
|
3424
|
-
id:
|
|
3425
|
-
parentId:
|
|
3426
|
-
title:
|
|
3500
|
+
var WorkStateSchema = z13.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
3501
|
+
var WorkRowSchema = z13.object({
|
|
3502
|
+
id: z13.string(),
|
|
3503
|
+
parentId: z13.string().nullable(),
|
|
3504
|
+
title: z13.string(),
|
|
3427
3505
|
/** Straight off the Goal. */
|
|
3428
3506
|
state: WorkStateSchema,
|
|
3429
|
-
owner:
|
|
3430
|
-
revision:
|
|
3507
|
+
owner: z13.string().nullable(),
|
|
3508
|
+
revision: z13.number().int(),
|
|
3431
3509
|
/** Open questions on it, counted to 100. */
|
|
3432
|
-
waiting:
|
|
3510
|
+
waiting: z13.number().int(),
|
|
3433
3511
|
/** Held by a gate on work that is not done. */
|
|
3434
|
-
blocked:
|
|
3435
|
-
lastProgressAt:
|
|
3512
|
+
blocked: z13.boolean(),
|
|
3513
|
+
lastProgressAt: z13.string().datetime().nullable(),
|
|
3436
3514
|
/** The line written for its newest progress note, else that note's first words. */
|
|
3437
|
-
line:
|
|
3515
|
+
line: z13.string().nullable(),
|
|
3438
3516
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
3439
|
-
children:
|
|
3440
|
-
createdAt:
|
|
3441
|
-
updatedAt:
|
|
3517
|
+
children: z13.number().int(),
|
|
3518
|
+
createdAt: z13.string().datetime(),
|
|
3519
|
+
updatedAt: z13.string().datetime(),
|
|
3442
3520
|
/** When anything at or under it last moved — the order the list is in. */
|
|
3443
|
-
activeAt:
|
|
3521
|
+
activeAt: z13.string().datetime(),
|
|
3444
3522
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
3445
|
-
sealed:
|
|
3523
|
+
sealed: z13.boolean()
|
|
3446
3524
|
});
|
|
3447
3525
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
3448
3526
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
3449
3527
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
3450
|
-
asking:
|
|
3451
|
-
oldestAskAt:
|
|
3528
|
+
asking: z13.number().int().nullable(),
|
|
3529
|
+
oldestAskAt: z13.string().datetime().nullable(),
|
|
3452
3530
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
3453
3531
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
3454
3532
|
* (`agent_holds`); null when that read failed. */
|
|
3455
|
-
holds:
|
|
3456
|
-
held:
|
|
3457
|
-
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
3458
|
-
* while none has, or when that read failed. */
|
|
3459
|
-
cold: z12.string().datetime().nullable(),
|
|
3533
|
+
holds: z13.array(z13.object({ id: z13.string(), title: z13.string() })).nullable(),
|
|
3534
|
+
held: z13.number().int().nullable(),
|
|
3460
3535
|
/** The newest line of its working log, and when the harness saw it. */
|
|
3461
|
-
line:
|
|
3462
|
-
lineAt:
|
|
3536
|
+
line: z13.string().nullable(),
|
|
3537
|
+
lineAt: z13.string().datetime().nullable()
|
|
3463
3538
|
});
|
|
3464
|
-
var SnapshotSchema =
|
|
3539
|
+
var SnapshotSchema = z13.object({
|
|
3465
3540
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
3466
|
-
at:
|
|
3467
|
-
questions:
|
|
3541
|
+
at: z13.string().datetime(),
|
|
3542
|
+
questions: z13.object({
|
|
3468
3543
|
/** The newest 30 open cards, questions before updates. */
|
|
3469
|
-
items:
|
|
3544
|
+
items: z13.array(QuestionRowSchema),
|
|
3470
3545
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
3471
|
-
total:
|
|
3472
|
-
updates:
|
|
3473
|
-
putOff:
|
|
3546
|
+
total: z13.number().int(),
|
|
3547
|
+
updates: z13.number().int(),
|
|
3548
|
+
putOff: z13.number().int()
|
|
3474
3549
|
}).nullable(),
|
|
3475
|
-
agents:
|
|
3550
|
+
agents: z13.object({
|
|
3476
3551
|
/** Up to 60, most recently seen first. */
|
|
3477
|
-
items:
|
|
3478
|
-
more:
|
|
3552
|
+
items: z13.array(AgentRowSchema),
|
|
3553
|
+
more: z13.boolean()
|
|
3479
3554
|
}).nullable(),
|
|
3480
|
-
work:
|
|
3555
|
+
work: z13.object({
|
|
3481
3556
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
3482
|
-
items:
|
|
3557
|
+
items: z13.array(WorkRowSchema),
|
|
3483
3558
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
3484
3559
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
3485
3560
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
3486
3561
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
3487
|
-
counts:
|
|
3562
|
+
counts: z13.object({ live: z13.number().int(), waiting: z13.number().int(), notStarted: z13.number().int(), done: z13.number().int() })
|
|
3488
3563
|
}).nullable(),
|
|
3489
|
-
you:
|
|
3564
|
+
you: z13.object({
|
|
3490
3565
|
settings: UserSettingsSchema,
|
|
3491
|
-
callable:
|
|
3566
|
+
callable: z13.boolean(),
|
|
3492
3567
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
3493
|
-
computers:
|
|
3568
|
+
computers: z13.array(ComputerRowSchema).nullable()
|
|
3494
3569
|
}).nullable()
|
|
3495
3570
|
});
|
|
3496
|
-
var CallRecapSchema =
|
|
3497
|
-
call:
|
|
3498
|
-
status:
|
|
3499
|
-
startedAt:
|
|
3500
|
-
durationMs:
|
|
3571
|
+
var CallRecapSchema = z13.object({
|
|
3572
|
+
call: z13.object({
|
|
3573
|
+
status: z13.string(),
|
|
3574
|
+
startedAt: z13.string(),
|
|
3575
|
+
durationMs: z13.number().nullable(),
|
|
3501
3576
|
/** Bot-clock pickup offset; absent before the timestamp release, null before its report lands. */
|
|
3502
|
-
pickupMs:
|
|
3503
|
-
|
|
3577
|
+
pickupMs: z13.number().nullable().optional(),
|
|
3578
|
+
/** STILL FILING (20261026182117): the call has not ended on the server, or it has and a brain cycle on it is
|
|
3579
|
+
* still pending, the next writer, which may still settle, file or start something. What the close itself
|
|
3580
|
+
* waits for (`private.close_call`). `status` cannot say it: nothing writes "finalizing", and a call reads
|
|
3581
|
+
* "open" until the sweep closes it, after nothing is pending. Absent from an API before it. */
|
|
3582
|
+
filing: z13.boolean().optional(),
|
|
3583
|
+
agents: z13.array(z13.object({ id: z13.string(), name: z13.string().nullable() }))
|
|
3504
3584
|
}),
|
|
3505
|
-
topics:
|
|
3506
|
-
goalId:
|
|
3507
|
-
title:
|
|
3508
|
-
owner:
|
|
3509
|
-
state:
|
|
3585
|
+
topics: z13.array(z13.object({
|
|
3586
|
+
goalId: z13.string().uuid(),
|
|
3587
|
+
title: z13.string(),
|
|
3588
|
+
owner: z13.string(),
|
|
3589
|
+
state: z13.string(),
|
|
3510
3590
|
/** Presented Entry content and its author, separately from the current Goal owner. */
|
|
3511
|
-
items:
|
|
3591
|
+
items: z13.array(z13.object({ entryId: z13.string(), author: z13.string(), text: z13.string(), summary: z13.string().nullable(), acknowledged: z13.boolean().optional() })).optional(),
|
|
3512
3592
|
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
3513
3593
|
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
3514
|
-
presented:
|
|
3594
|
+
presented: z13.boolean(),
|
|
3515
3595
|
/** Whether THIS call created it — the third way a call touches work, beside carrying a request
|
|
3516
3596
|
* there and filing words there (owner, 2026-10-08, Goal `5b4b1a0a`: "Show the new work, keep my
|
|
3517
3597
|
* words under it"). Created work is never carried, so it is never `presented`; without this the
|
|
3518
3598
|
* row is dropped by the "only what happened on the call" filter. False on a database before
|
|
3519
3599
|
* 20261013080133. */
|
|
3520
|
-
created:
|
|
3600
|
+
created: z13.boolean(),
|
|
3521
3601
|
/** `request` is the Entry that asked it — what a line's `about` names when the line answered or put
|
|
3522
3602
|
* it off. Optional: an older API sent none. */
|
|
3523
|
-
questions:
|
|
3603
|
+
questions: z13.array(z13.object({ id: z13.string().uuid(), state: z13.string(), title: z13.string(), request: z13.string().optional() })),
|
|
3524
3604
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
3525
3605
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
3526
3606
|
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
@@ -3530,33 +3610,34 @@ var CallRecapSchema = z12.object({
|
|
|
3530
3610
|
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
3531
3611
|
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
3532
3612
|
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
3533
|
-
lines:
|
|
3534
|
-
entryId:
|
|
3535
|
-
words:
|
|
3536
|
-
summary:
|
|
3537
|
-
about:
|
|
3538
|
-
reply:
|
|
3539
|
-
replyKind:
|
|
3540
|
-
at:
|
|
3613
|
+
lines: z13.array(z13.object({
|
|
3614
|
+
entryId: z13.string().uuid(),
|
|
3615
|
+
words: z13.string(),
|
|
3616
|
+
summary: z13.string().optional(),
|
|
3617
|
+
about: z13.string().nullable().optional(),
|
|
3618
|
+
reply: z13.string().nullable().optional(),
|
|
3619
|
+
replyKind: z13.string().nullable().optional(),
|
|
3620
|
+
at: z13.array(z13.number()).optional()
|
|
3541
3621
|
}))
|
|
3542
3622
|
})),
|
|
3543
|
-
unfiled:
|
|
3623
|
+
unfiled: z13.array(z13.object({ lineId: z13.string().uuid(), words: z13.string(), atMs: z13.number() })),
|
|
3544
3624
|
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
3545
3625
|
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
3546
3626
|
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
3547
3627
|
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
3548
3628
|
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
3549
3629
|
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
3550
|
-
said:
|
|
3551
|
-
lineId:
|
|
3552
|
-
speaker:
|
|
3553
|
-
agent:
|
|
3554
|
-
words:
|
|
3555
|
-
atMs:
|
|
3630
|
+
said: z13.array(z13.object({
|
|
3631
|
+
lineId: z13.string().uuid(),
|
|
3632
|
+
speaker: z13.enum(["user", "agent"]),
|
|
3633
|
+
agent: z13.object({ id: z13.string().nullable(), name: z13.string().nullable() }).nullable(),
|
|
3634
|
+
words: z13.string(),
|
|
3635
|
+
atMs: z13.number()
|
|
3556
3636
|
})),
|
|
3557
|
-
|
|
3637
|
+
/** What did not fit, counted. `settles` counts items past the recap's 100; absent before 20261026165518. */
|
|
3638
|
+
more: z13.object({ lines: z13.number(), entries: z13.number(), topics: z13.number(), settles: z13.number().optional() }),
|
|
3558
3639
|
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
3559
|
-
next:
|
|
3640
|
+
next: z13.number().int().nonnegative().nullable().optional(),
|
|
3560
3641
|
/** THE CALL AS ONE LIST OF ROWS (owner, 2026-10-14: "we only really want a thing like transcript but
|
|
3561
3642
|
* built from the summaries", then "the transcript tabs should show the same but with full words").
|
|
3562
3643
|
* Both tabs draw these rows, in this order: Summary from `summary`, Transcript from `words`.
|
|
@@ -3567,32 +3648,50 @@ var CallRecapSchema = z12.object({
|
|
|
3567
3648
|
* `by` is the row's real author, never inferred from `kind`: a relay Paigy sent to an agent is
|
|
3568
3649
|
* Paigy's, not the person's. Optional and additive (owner: "make sure whatever you do is backward
|
|
3569
3650
|
* compatible"): an API before it sends none, and the screen falls back to `topics` and `said`. */
|
|
3570
|
-
timeline:
|
|
3571
|
-
id:
|
|
3572
|
-
position:
|
|
3573
|
-
atMs:
|
|
3574
|
-
kind:
|
|
3575
|
-
by:
|
|
3576
|
-
goalId:
|
|
3577
|
-
requestId:
|
|
3578
|
-
summary:
|
|
3579
|
-
words:
|
|
3651
|
+
timeline: z13.array(z13.object({
|
|
3652
|
+
id: z13.string(),
|
|
3653
|
+
position: z13.number(),
|
|
3654
|
+
atMs: z13.number(),
|
|
3655
|
+
kind: z13.enum(["put", "said", "did"]),
|
|
3656
|
+
by: z13.object({ participant: z13.string().nullable(), agentId: z13.string().nullable(), name: z13.string().nullable() }),
|
|
3657
|
+
goalId: z13.string().uuid().nullable(),
|
|
3658
|
+
requestId: z13.string().nullable(),
|
|
3659
|
+
summary: z13.string().nullable(),
|
|
3660
|
+
words: z13.string(),
|
|
3580
3661
|
/** Paigy's sentences folded into this row: on a `put` row, what it said putting the item to the
|
|
3581
3662
|
* person (before their next row); on a `said` row, what it said in reply (until their next row).
|
|
3582
3663
|
* `atMs` is when each was said; optional, absent before 2026-10-14's fix. */
|
|
3583
|
-
spoken:
|
|
3664
|
+
spoken: z13.array(z13.object({ lineId: z13.string(), words: z13.string(), atMs: z13.number().optional() })),
|
|
3584
3665
|
/** On a `said` row: the work these words were filed on, and whether they answered (recorded) a
|
|
3585
3666
|
* question. They replace the "Filed on ..." and "Recorded: ..." rows that repeated the person's own
|
|
3586
3667
|
* summary (Brain, 2026-10-14). Optional: absent from an API before it. */
|
|
3587
|
-
filedOn:
|
|
3588
|
-
recorded:
|
|
3589
|
-
settled:
|
|
3590
|
-
acknowledged:
|
|
3668
|
+
filedOn: z13.array(z13.object({ goalId: z13.string().uuid(), title: z13.string() })).optional(),
|
|
3669
|
+
recorded: z13.boolean().optional(),
|
|
3670
|
+
settled: z13.object({ requestId: z13.string(), answer: z13.string() }).nullable().optional(),
|
|
3671
|
+
acknowledged: z13.boolean().optional(),
|
|
3591
3672
|
/** On a `put` row: still waiting for the person's answer (not settled, not answered, not put off).
|
|
3592
3673
|
* The server's judgement; the screen draws "Still waiting for your answer" from it. Optional: absent
|
|
3593
3674
|
* from an API before it, where the screen falls back to its own reading. */
|
|
3594
|
-
waiting:
|
|
3595
|
-
lineIds:
|
|
3675
|
+
waiting: z13.boolean().optional(),
|
|
3676
|
+
lineIds: z13.array(z13.string())
|
|
3677
|
+
})).optional(),
|
|
3678
|
+
/** THE RECAP (owner, 2026-10-26: "the transcript should just be the entries that settled or
|
|
3679
|
+
* acknowledged the items; that's it"). Each item the person's words settled on this call, in the
|
|
3680
|
+
* order they said them: who asked it (`by`), the item in its agent's words (`title`), and only the
|
|
3681
|
+
* words of theirs that settled it (`lines`: each line once, verbatim, with when it was said on the
|
|
3682
|
+
* bot clock like `said[].atMs`). Built from their replies, not from what the call put, so an item
|
|
3683
|
+
* settled without being put is still here. Beside them (owner: "List them too, with the words that
|
|
3684
|
+
* said them"), each Goal his words told something (`kind` "told") or the call started ("started"),
|
|
3685
|
+
* with `requestId` null and the Goal's name as `title`; a started Goal may have no `lines` yet. `kind`
|
|
3686
|
+
* is a string, not an enum, so a later kind never fails an older build's parse; `requestId` is the
|
|
3687
|
+
* request the words settled. On the first page only; absent from an API before it. */
|
|
3688
|
+
settles: z13.array(z13.object({
|
|
3689
|
+
requestId: z13.string().nullable(),
|
|
3690
|
+
kind: z13.string(),
|
|
3691
|
+
by: z13.object({ participant: z13.string(), agentId: z13.string().nullable(), name: z13.string() }),
|
|
3692
|
+
goalId: z13.string().uuid().nullable(),
|
|
3693
|
+
title: z13.string(),
|
|
3694
|
+
lines: z13.array(z13.object({ lineId: z13.string(), words: z13.string(), atMs: z13.number() }))
|
|
3596
3695
|
})).optional()
|
|
3597
3696
|
});
|
|
3598
3697
|
function sessionSlot(sessionId2) {
|
|
@@ -3936,11 +4035,12 @@ async function checkActivity(opts = {}) {
|
|
|
3936
4035
|
if (!res.ok) await fail("check_activity", res);
|
|
3937
4036
|
return await res.json();
|
|
3938
4037
|
}
|
|
3939
|
-
var NOT_SENT = `Recorded as the Goal's progress, not sent
|
|
4038
|
+
var NOT_SENT = `Recorded as the Goal's progress, not sent to the person; the other agents that wrote on the Goal in the last hour are told. The person is told only a question, an answer to one they asked you, and what you say once the Goal is done. To close the work, send contact.completions or manage_goals kind:complete with the shared completion report, closing and reporting together. If the person asked you, in so many words, to send them this, send it again with userExplicitlyRequested: "any" on that update.`;
|
|
3940
4039
|
var APPLIED = "Applied; nothing was sent to the person.";
|
|
3941
4040
|
var DEMOTED = "Delivered as a card, not a call: the person's settings do not allow a call right now (silent, quiet hours, or a mode). It has reached them; do not send it again.";
|
|
3942
4041
|
async function readDelivery(deliveryId, opts = {}) {
|
|
3943
|
-
const
|
|
4042
|
+
const query = opts.transcript ? `?transcript=1${opts.after ? `&after=${encodeURIComponent(opts.after)}` : ""}` : "";
|
|
4043
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}${query}`, {
|
|
3944
4044
|
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}` },
|
|
3945
4045
|
signal: opts.signal
|
|
3946
4046
|
}));
|
|
@@ -3965,6 +4065,7 @@ async function contact(input, opts = {}) {
|
|
|
3965
4065
|
...questions ? { questions } : {},
|
|
3966
4066
|
...parsed.answers ? { answers: parsed.answers } : {},
|
|
3967
4067
|
...parsed.withdrawQuestionIds ? { withdrawQuestionIds: parsed.withdrawQuestionIds } : {},
|
|
4068
|
+
...parsed.bumpQuestionIds ? { bumpQuestionIds: parsed.bumpQuestionIds } : {},
|
|
3968
4069
|
...parsed.questionDependencies ? { questionDependencies: parsed.questionDependencies } : {}
|
|
3969
4070
|
})
|
|
3970
4071
|
}));
|
|
@@ -3993,7 +4094,9 @@ async function contact(input, opts = {}) {
|
|
|
3993
4094
|
// IT WENT ONTO A CALL ALREADY HAPPENING (owner, 2026-09-28): the view words it as that call.
|
|
3994
4095
|
...lead.joinedCall ? { joinedCall: true } : {},
|
|
3995
4096
|
...demoted ? { demoted } : {},
|
|
3996
|
-
...acked
|
|
4097
|
+
...acked,
|
|
4098
|
+
...receipt.ringPolicy ? { ringPolicy: receipt.ringPolicy, ...receipt.message ? { ringHeld: receipt.message } : {} } : {},
|
|
4099
|
+
...receipt.recheckAt ? { recheckAt: receipt.recheckAt } : {}
|
|
3997
4100
|
};
|
|
3998
4101
|
};
|
|
3999
4102
|
const settled = (d) => d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution" && !entryUnit(e));
|
|
@@ -4177,7 +4280,11 @@ function goalView(g) {
|
|
|
4177
4280
|
subtree: g.subtree?.length ? g.subtree.map((c) => compact({
|
|
4178
4281
|
goal: `${" ".repeat(c.depth - 1)}${c.title} (${c.state})`,
|
|
4179
4282
|
goalId: c.goalId,
|
|
4180
|
-
|
|
4283
|
+
// AN ANSWERED QUESTION NEVER READS OPEN (2026-10-27, Goal b5f1fc27): an answer given on a call,
|
|
4284
|
+
// an option pick or a spoken reply, has an instant on the list's row and no words there, and
|
|
4285
|
+
// it read "(open)". Seven answered questions read that way, and an agent reading them would
|
|
4286
|
+
// ask the owner again. The words are on the Goal's own read.
|
|
4287
|
+
questions: c.questions.length ? c.questions.map((q) => `${q.question} \u2192 ${q.answer || (q.answeredAt ? "(answered: read this Goal for the answer)" : "(open)")}`) : void 0
|
|
4181
4288
|
})) : void 0,
|
|
4182
4289
|
blockedBy: g.blockedDependencyGoalIds,
|
|
4183
4290
|
dueAt: g.dueAt,
|
|
@@ -4193,7 +4300,7 @@ var JOINED_CALL = "This joined the call the person is ALREADY ON \u2014 no ring,
|
|
|
4193
4300
|
function failures(results) {
|
|
4194
4301
|
const failed = (results ?? []).filter((r) => r.status === "failed");
|
|
4195
4302
|
if (!failed.length) return void 0;
|
|
4196
|
-
const plural = { update: "updates", completion: "completions", question: "questions", answer: "answers", withdraw: "withdrawQuestionIds", dependency: "questionDependencies" };
|
|
4303
|
+
const plural = { update: "updates", completion: "completions", question: "questions", answer: "answers", withdraw: "withdrawQuestionIds", bump: "bumpQuestionIds", dependency: "questionDependencies" };
|
|
4197
4304
|
return `${failed.length} of ${(results ?? []).length} items did not apply; the rest did: ` + failed.map((r) => `${plural[r.kind]}[${r.index}] ${r.error}${r.detail ? ` (${r.detail})` : ""}`).join("; ") + ".";
|
|
4198
4305
|
}
|
|
4199
4306
|
var itemsView = (results) => (results ?? []).map((r) => compact({
|
|
@@ -4205,7 +4312,8 @@ var itemsView = (results) => (results ?? []).map((r) => compact({
|
|
|
4205
4312
|
error: r.error,
|
|
4206
4313
|
detail: r.detail
|
|
4207
4314
|
}));
|
|
4208
|
-
function deliveryReadView(d) {
|
|
4315
|
+
function deliveryReadView(d, asked2 = {}) {
|
|
4316
|
+
const t = d.transcript;
|
|
4209
4317
|
return compact({
|
|
4210
4318
|
deliveryId: d.deliveryId,
|
|
4211
4319
|
kind: d.kind,
|
|
@@ -4214,9 +4322,20 @@ function deliveryReadView(d) {
|
|
|
4214
4322
|
conversation: conversation(d),
|
|
4215
4323
|
callState: d.callState,
|
|
4216
4324
|
callOutcome: d.callOutcome ?? (d.kind === "call" ? d.message : void 0),
|
|
4217
|
-
|
|
4325
|
+
// WHAT WAS SAID (owner, 2026-10-26): each line as "m:ss", who said it, and the words; `for` and `room` only
|
|
4326
|
+
// when they are true of the line, so a short call reads as the call did.
|
|
4327
|
+
transcript: t ? compact({
|
|
4328
|
+
timedFrom: t.pickupKnown ? "pickup" : "the call's start",
|
|
4329
|
+
lines: t.lines.map((l) => compact({ at: clock2(l.atMs), by: l.by, for: l.for ?? void 0, room: l.room || void 0, words: l.words })),
|
|
4330
|
+
next: t.next ?? void 0
|
|
4331
|
+
}) : void 0,
|
|
4332
|
+
next: t?.next ? `Read the next page of the transcript with get_delivery({deliveryId: "${d.deliveryId}", transcript: true, after: "${t.next}"}).` : asked2.transcript && !t ? d.kind === "call" ? "This API sent no transcript: it is older than the transcript, or the call has none it can show." : "Only a Call has a transcript." : d.callState && d.callState !== "ended" ? "Keep receiving with contact({wait:true}) while this Call is open, even after an empty window. Do not resend the ask." : "Answers and follow-ups are on their Goals; receive on your next wake. This read acknowledges nothing."
|
|
4218
4333
|
});
|
|
4219
4334
|
}
|
|
4335
|
+
function clock2(ms) {
|
|
4336
|
+
const s = Math.max(0, Math.floor(ms / 1e3));
|
|
4337
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
4338
|
+
}
|
|
4220
4339
|
function deliveryView(d) {
|
|
4221
4340
|
const answers = "Answers land on the Goal: collect them with contact({wait:true}) or get_goals. Do not resend it.";
|
|
4222
4341
|
const pending = d.state === "open" && d.decisionNeeds.some((n) => n.state === "open");
|
|
@@ -4242,9 +4361,14 @@ function deliveryView(d) {
|
|
|
4242
4361
|
callOutcome: d.callOutcome,
|
|
4243
4362
|
waitOutcome: d.waitOutcome,
|
|
4244
4363
|
acknowledged: d.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
4364
|
+
// THE CALL IT PLACED WILL BE HELD (owner, 2026-10-27): the person's ring policy, and when the phone rings.
|
|
4365
|
+
ringPolicy: d.ringPolicy,
|
|
4366
|
+
// NO CALL WAS PLACED; THE ASKS WAIT ON THEIR CARDS (owner, 2026-10-27, Goal 2d147edd): when to check back.
|
|
4367
|
+
recheckAt: d.recheckAt,
|
|
4245
4368
|
next: [
|
|
4246
4369
|
failures(d.results),
|
|
4247
|
-
d.
|
|
4370
|
+
d.ringHeld,
|
|
4371
|
+
d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.recheckAt && d.ringHeld ? void 0 : 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_goals on your next wake. ${d.message ?? ""}`.trim() : d.message
|
|
4248
4372
|
].filter(Boolean).join(" ")
|
|
4249
4373
|
});
|
|
4250
4374
|
}
|
|
@@ -4296,6 +4420,8 @@ function receivedView(r, now = Date.now()) {
|
|
|
4296
4420
|
// on startup and after a wake (contact({wait:false}), and every hosted receive), never on a window of a
|
|
4297
4421
|
// held wait, which would repeat them window after window.
|
|
4298
4422
|
houseRules: r.waitOutcome === "not_waited" ? w.houseRules : void 0,
|
|
4423
|
+
// THEIR RING POLICY, ONE FOR EVERY AGENT (owner, 2026-10-27, Goal 6faa6a51), on the same reads as the house rules.
|
|
4424
|
+
ringPolicy: r.waitOutcome === "not_waited" ? w.ringPolicy : void 0,
|
|
4299
4425
|
next: [
|
|
4300
4426
|
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length || rings.length || w.calls?.some((c) => c.callState !== "ended") ? "No messages." : "Nothing is waiting.",
|
|
4301
4427
|
...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.`] : [],
|
|
@@ -4380,8 +4506,16 @@ async function runTool(name, args, opts) {
|
|
|
4380
4506
|
for (const g of managed.goals ?? []) remember(client.token, g.goalId, g.revision);
|
|
4381
4507
|
return managedView(managed, changes);
|
|
4382
4508
|
}
|
|
4383
|
-
case "get_delivery":
|
|
4384
|
-
|
|
4509
|
+
case "get_delivery": {
|
|
4510
|
+
const asked2 = GetDeliverySchema.parse(input);
|
|
4511
|
+
return deliveryReadView(
|
|
4512
|
+
await readDelivery(
|
|
4513
|
+
asked2.deliveryId,
|
|
4514
|
+
{ ...client, signal, ...asked2.transcript ? { transcript: true, ...asked2.after ? { after: asked2.after } : {} } : {} }
|
|
4515
|
+
),
|
|
4516
|
+
{ transcript: asked2.transcript === true }
|
|
4517
|
+
);
|
|
4518
|
+
}
|
|
4385
4519
|
case "get_goals": {
|
|
4386
4520
|
const { goalIds, history } = GetGoalsSchema.parse(input);
|
|
4387
4521
|
const rows = await getGoals(goalIds, client, { history });
|
|
@@ -4391,6 +4525,11 @@ async function runTool(name, args, opts) {
|
|
|
4391
4525
|
}
|
|
4392
4526
|
case "search":
|
|
4393
4527
|
return searchRecords(SearchToolSchema.parse(input), client);
|
|
4528
|
+
case "help": {
|
|
4529
|
+
const asked2 = HelpSchema.safeParse(input);
|
|
4530
|
+
if (!asked2.success) throw new Error(asked2.error.issues[0].message);
|
|
4531
|
+
return helpView(asked2.data);
|
|
4532
|
+
}
|
|
4394
4533
|
default:
|
|
4395
4534
|
throw new Error(`Unknown tool: ${name}`);
|
|
4396
4535
|
}
|
|
@@ -4680,6 +4819,8 @@ export {
|
|
|
4680
4819
|
acknowledge,
|
|
4681
4820
|
pendingEvents,
|
|
4682
4821
|
receiveContact,
|
|
4822
|
+
viaLine,
|
|
4823
|
+
decided,
|
|
4683
4824
|
runTool,
|
|
4684
4825
|
subscribeWake,
|
|
4685
4826
|
parseDue,
|