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