@paigy/mcp 0.40.32 → 0.40.34
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 +8 -6
- package/dist/{chunk-QJCRISSA.js → chunk-5YTQF6KQ.js} +933 -808
- package/dist/{chunk-4YUPH4JS.js → chunk-7ZM5RGW4.js} +1 -1
- package/dist/{chunk-5F3ND6WV.js → chunk-VVQ4OQOB.js} +6 -3
- package/dist/{chunk-EU6NEQ2W.js → chunk-YIMNBJEF.js} +853 -775
- package/dist/{dist-Z4KVBUBO.js → dist-HRRH3KO4.js} +3 -3
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +7 -7
- 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,18 +7,21 @@ import { homedir } from "os";
|
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9
9
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
10
|
-
import { z as
|
|
10
|
+
import { z as z12 } from "zod";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { z as z3 } from "zod";
|
|
13
|
+
import { z as z2 } from "zod";
|
|
14
|
+
import { z as z5 } from "zod";
|
|
13
15
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
14
16
|
import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
15
17
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
|
|
16
|
-
import { z as z2 } from "zod";
|
|
17
18
|
import { z as z4 } from "zod";
|
|
18
|
-
import { z as z5 } from "zod";
|
|
19
19
|
import { z as z6 } from "zod";
|
|
20
20
|
import { z as z7 } from "zod";
|
|
21
21
|
import { z as z8 } from "zod";
|
|
22
|
+
import { z as z9 } from "zod";
|
|
23
|
+
import { z as z10 } from "zod";
|
|
24
|
+
import { z as z11 } from "zod";
|
|
22
25
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
23
26
|
import { homedir as homedir2 } from "os";
|
|
24
27
|
import { join as join2 } from "path";
|
|
@@ -1495,16 +1498,16 @@ function parseWhen(said2, tz, nowMs, read22 = "earliest") {
|
|
|
1495
1498
|
let hour = null;
|
|
1496
1499
|
let minute = 0;
|
|
1497
1500
|
const spoken = new RegExp(`\\bat\\s+(${Object.keys(SPOKEN_HOUR).join("|")})\\b`, "i").exec(text);
|
|
1498
|
-
const
|
|
1501
|
+
const clock3 = CLOCK.exec(text);
|
|
1499
1502
|
if (spoken) {
|
|
1500
1503
|
hour = SPOKEN_HOUR[spoken[1].toLowerCase()];
|
|
1501
|
-
} else if (
|
|
1502
|
-
hour = Number(
|
|
1503
|
-
minute =
|
|
1504
|
+
} else if (clock3 && (clock3[3] || /\bat\s/i.test(clock3[0]))) {
|
|
1505
|
+
hour = Number(clock3[1]);
|
|
1506
|
+
minute = clock3[2] ? Number(clock3[2]) : 0;
|
|
1504
1507
|
if (hour > 23 || minute > 59) return null;
|
|
1505
1508
|
}
|
|
1506
1509
|
if (hour !== null) {
|
|
1507
|
-
const mer =
|
|
1510
|
+
const mer = clock3?.[3]?.toLowerCase().replace(/\./g, "") ?? "";
|
|
1508
1511
|
const pmWord = /\b(p\.?m\.?|afternoon|evening|tonight|at night)\b/i.test(text);
|
|
1509
1512
|
const amWord = /\b(a\.?m\.?|morning)\b/i.test(text);
|
|
1510
1513
|
if (mer.startsWith("p") || pmWord && !amWord) {
|
|
@@ -1548,35 +1551,6 @@ function parseDelay(said2, tz, nowMs, read22 = "earliest") {
|
|
|
1548
1551
|
return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
|
|
1549
1552
|
}
|
|
1550
1553
|
var NIGHT = { from: 23, to: 7 };
|
|
1551
|
-
function draft2020(node) {
|
|
1552
|
-
if (Array.isArray(node)) return node.map(draft2020);
|
|
1553
|
-
if (node && typeof node === "object") {
|
|
1554
|
-
const o = node;
|
|
1555
|
-
if (Array.isArray(o.items)) {
|
|
1556
|
-
o.prefixItems = o.items;
|
|
1557
|
-
if ("additionalItems" in o) {
|
|
1558
|
-
o.items = o.additionalItems;
|
|
1559
|
-
delete o.additionalItems;
|
|
1560
|
-
} else delete o.items;
|
|
1561
|
-
}
|
|
1562
|
-
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
1563
|
-
if (typeof o[excl] === "boolean") {
|
|
1564
|
-
if (o[excl] === true && typeof o[lim] === "number") {
|
|
1565
|
-
o[excl] = o[lim];
|
|
1566
|
-
delete o[lim];
|
|
1567
|
-
} else delete o[excl];
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
for (const k of Object.keys(o)) o[k] = draft2020(o[k]);
|
|
1571
|
-
return o;
|
|
1572
|
-
}
|
|
1573
|
-
return node;
|
|
1574
|
-
}
|
|
1575
|
-
function mcpInputSchema(s) {
|
|
1576
|
-
const schema = zodToJsonSchema(s, { target: "jsonSchema2019-09", $refStrategy: "none" });
|
|
1577
|
-
delete schema.$schema;
|
|
1578
|
-
return draft2020(schema);
|
|
1579
|
-
}
|
|
1580
1554
|
var ASK_MAX = 250;
|
|
1581
1555
|
var UNITS_MAX = 12;
|
|
1582
1556
|
var UNIT_TITLE_MAX = 50;
|
|
@@ -1626,24 +1600,78 @@ function capRefusals(text, units2, field) {
|
|
|
1626
1600
|
function refuseOverCaps(text, units2, field, ctx) {
|
|
1627
1601
|
for (const { path, message, ...params } of capRefusals(text, units2, field)) ctx.addIssue({ code: z2.ZodIssueCode.custom, path, message, params });
|
|
1628
1602
|
}
|
|
1629
|
-
var
|
|
1630
|
-
var
|
|
1631
|
-
|
|
1632
|
-
|
|
1603
|
+
var COMPLETION_MESSAGE_MAX = 50;
|
|
1604
|
+
var CompletionSchema = z3.object({
|
|
1605
|
+
goalId: z3.string().uuid().describe("The existing Goal to complete."),
|
|
1606
|
+
message: z3.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."),
|
|
1607
|
+
units: z3.array(UnitInputSchema.extend({
|
|
1608
|
+
title: z3.string().trim().min(1).max(UNIT_TITLE_MAX),
|
|
1609
|
+
body: z3.string().trim().min(1).max(UNIT_BODY_MAX)
|
|
1610
|
+
})).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."),
|
|
1611
|
+
reply: z3.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."),
|
|
1612
|
+
options: z3.array(z3.object({
|
|
1613
|
+
label: z3.string().trim().min(1).max(OPTION_LABEL_MAX),
|
|
1614
|
+
hint: z3.string().trim().min(1).max(OPTION_HINT_MAX)
|
|
1615
|
+
}).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.")
|
|
1616
|
+
}).strict();
|
|
1617
|
+
var CompleteChangeSchema = CompletionSchema.extend({ kind: z3.literal("complete") });
|
|
1618
|
+
var ASKING_RULES = `HOW TO ASK:
|
|
1619
|
+
1. Write one question per object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
1620
|
+
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
1621
|
+
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
1622
|
+
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. While a Call you placed is open, keep receiving window after window until it ends, even when a window is empty: the person may give follow-ups or instructions. With no open Call, STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goals\` on that wake.
|
|
1623
|
+
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
1624
|
+
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
1625
|
+
7. A question carries its options when you have them; a free-text question may omit them. Each option\u2019s label must stand on its own and name its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Each question is sent exactly as written, with no reading in between. They can always answer in their own words, so never add an 'Other' option.
|
|
1626
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
|
|
1627
|
+
9. GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. What they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Requested delivery and completed-Goal reports require a reply (reply_required); ordinary progress may omit it.`;
|
|
1628
|
+
function draft2020(node) {
|
|
1629
|
+
if (Array.isArray(node)) return node.map(draft2020);
|
|
1630
|
+
if (node && typeof node === "object") {
|
|
1631
|
+
const o = node;
|
|
1632
|
+
if (Array.isArray(o.items)) {
|
|
1633
|
+
o.prefixItems = o.items;
|
|
1634
|
+
if ("additionalItems" in o) {
|
|
1635
|
+
o.items = o.additionalItems;
|
|
1636
|
+
delete o.additionalItems;
|
|
1637
|
+
} else delete o.items;
|
|
1638
|
+
}
|
|
1639
|
+
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
1640
|
+
if (typeof o[excl] === "boolean") {
|
|
1641
|
+
if (o[excl] === true && typeof o[lim] === "number") {
|
|
1642
|
+
o[excl] = o[lim];
|
|
1643
|
+
delete o[lim];
|
|
1644
|
+
} else delete o[excl];
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
for (const k of Object.keys(o)) o[k] = draft2020(o[k]);
|
|
1648
|
+
return o;
|
|
1649
|
+
}
|
|
1650
|
+
return node;
|
|
1651
|
+
}
|
|
1652
|
+
function mcpInputSchema(s) {
|
|
1653
|
+
const schema = zodToJsonSchema(s, { target: "jsonSchema2019-09", $refStrategy: "none" });
|
|
1654
|
+
delete schema.$schema;
|
|
1655
|
+
return draft2020(schema);
|
|
1656
|
+
}
|
|
1657
|
+
var questionHandle = z4.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
|
|
1658
|
+
var NodeRef = z4.object({
|
|
1659
|
+
type: z4.enum(["goal", "question"]),
|
|
1660
|
+
id: z4.string().uuid()
|
|
1633
1661
|
}).strict().describe("A Goal or a Question, by its id.");
|
|
1634
|
-
var BlockedAction =
|
|
1635
|
-
var reason =
|
|
1662
|
+
var BlockedAction = z4.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
|
|
1663
|
+
var reason = z4.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
|
|
1636
1664
|
var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
|
|
1637
1665
|
var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
|
|
1638
|
-
var userExplicitlyRequested =
|
|
1666
|
+
var userExplicitlyRequested = z4.enum(["any", "call"]).optional().describe(
|
|
1639
1667
|
`Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
|
|
1640
1668
|
);
|
|
1641
|
-
var units =
|
|
1669
|
+
var units = z4.array(UnitInputSchema).optional().describe(
|
|
1642
1670
|
`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.`
|
|
1643
1671
|
);
|
|
1644
|
-
var UpdateInputSchema =
|
|
1645
|
-
message:
|
|
1646
|
-
goalIds:
|
|
1672
|
+
var UpdateInputSchema = z4.object({
|
|
1673
|
+
message: z4.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
1674
|
+
goalIds: z4.array(z4.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
1647
1675
|
units,
|
|
1648
1676
|
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
1649
1677
|
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
@@ -1665,15 +1693,23 @@ var UpdateInputSchema = z2.object({
|
|
|
1665
1693
|
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
1666
1694
|
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
1667
1695
|
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
1668
|
-
reply:
|
|
1669
|
-
`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.
|
|
1696
|
+
reply: z4.string().trim().min(1).describe(
|
|
1697
|
+
`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.`
|
|
1670
1698
|
).optional(),
|
|
1671
1699
|
userExplicitlyRequested
|
|
1672
1700
|
}).strict().superRefine((u, ctx) => {
|
|
1701
|
+
if (u.userExplicitlyRequested && !u.reply) {
|
|
1702
|
+
ctx.addIssue({
|
|
1703
|
+
code: z4.ZodIssueCode.custom,
|
|
1704
|
+
path: ["reply"],
|
|
1705
|
+
params: { refusal: "reply_required" },
|
|
1706
|
+
message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1673
1709
|
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
1674
1710
|
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
1675
1711
|
ctx.addIssue({
|
|
1676
|
-
code:
|
|
1712
|
+
code: z4.ZodIssueCode.custom,
|
|
1677
1713
|
path: ["reply"],
|
|
1678
1714
|
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX },
|
|
1679
1715
|
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.`
|
|
@@ -1681,100 +1717,99 @@ var UpdateInputSchema = z2.object({
|
|
|
1681
1717
|
}
|
|
1682
1718
|
});
|
|
1683
1719
|
var questionFields = {
|
|
1684
|
-
question:
|
|
1720
|
+
question: z4.string().trim().min(1).describe(
|
|
1685
1721
|
`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.`
|
|
1686
1722
|
),
|
|
1687
|
-
goalId:
|
|
1723
|
+
goalId: z4.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
1688
1724
|
units,
|
|
1689
|
-
options:
|
|
1725
|
+
options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200."),
|
|
1690
1726
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
1691
1727
|
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
1692
|
-
pickMode:
|
|
1728
|
+
pickMode: z4.enum(["one", "many", "rank"]).optional().describe(
|
|
1693
1729
|
'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.'
|
|
1694
1730
|
),
|
|
1695
1731
|
// WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
|
|
1696
1732
|
// blocks work asks for a call (the person's settings and ring cooldowns still decide).
|
|
1697
|
-
workItBlocks:
|
|
1733
|
+
workItBlocks: z4.array(z4.object({ blocked: NodeRef, action: BlockedAction, reason: reason.optional() }).strict().refine(actionFits, ACTION_FITS)).min(1).max(10).optional().describe(
|
|
1698
1734
|
"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."
|
|
1699
1735
|
),
|
|
1700
1736
|
userExplicitlyRequested
|
|
1701
1737
|
};
|
|
1702
1738
|
var refineQuestion = (q, ctx) => {
|
|
1703
1739
|
refuseOverCaps(q.question, q.units, "question", ctx);
|
|
1704
|
-
if (q.pickMode && !q.options) ctx.addIssue({ code:
|
|
1740
|
+
if (q.pickMode && !q.options) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
|
|
1705
1741
|
const ids = (q.options ?? []).map((o) => o.id);
|
|
1706
1742
|
if (ids.some((id2) => id2) && (ids.some((id2) => !id2) || new Set(ids).size !== ids.length)) {
|
|
1707
|
-
ctx.addIssue({ code:
|
|
1743
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
|
|
1708
1744
|
}
|
|
1709
1745
|
};
|
|
1710
|
-
var QuestionInputSchema =
|
|
1711
|
-
questionId:
|
|
1746
|
+
var QuestionInputSchema = z4.object({
|
|
1747
|
+
questionId: z4.string().uuid().optional().describe("Optional: your own stable id for this question (a UUID), so a resend is the same question. Minted for you when omitted; the result returns it."),
|
|
1712
1748
|
...questionFields
|
|
1713
1749
|
}).strict().superRefine(refineQuestion);
|
|
1714
|
-
var QuestionWireSchema =
|
|
1715
|
-
var AnswerInputSchema =
|
|
1750
|
+
var QuestionWireSchema = z4.object({ questionId: z4.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
|
|
1751
|
+
var AnswerInputSchema = z4.object({
|
|
1716
1752
|
questionId: questionHandle.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
|
|
1717
|
-
answer:
|
|
1718
|
-
text:
|
|
1719
|
-
selectedOptionIds:
|
|
1753
|
+
answer: z4.object({
|
|
1754
|
+
text: z4.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
1755
|
+
selectedOptionIds: z4.array(z4.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).")
|
|
1720
1756
|
}).strict(),
|
|
1721
1757
|
units
|
|
1722
1758
|
}).strict().superRefine((a, ctx) => refuseOverCaps(a.answer.text, a.units, "text", ctx));
|
|
1723
|
-
var QuestionDependencySchema =
|
|
1759
|
+
var QuestionDependencySchema = z4.object({
|
|
1724
1760
|
questionId: questionHandle.describe("A Question you asked."),
|
|
1725
|
-
change:
|
|
1761
|
+
change: z4.enum(["add", "remove"]),
|
|
1726
1762
|
blocked: NodeRef,
|
|
1727
1763
|
action: BlockedAction,
|
|
1728
1764
|
reason: reason.optional()
|
|
1729
1765
|
}).strict().refine(actionFits, ACTION_FITS);
|
|
1730
1766
|
var sendFields = {
|
|
1731
|
-
|
|
1767
|
+
completions: z4.array(CompletionSchema).min(1).max(20).optional().describe("Close existing Goals and deliver their completion reports atomically. Ordinary updates never close work."),
|
|
1768
|
+
updates: z4.array(UpdateInputSchema).min(1).max(20).optional().describe(
|
|
1732
1769
|
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
|
|
1733
1770
|
),
|
|
1734
|
-
answers:
|
|
1735
|
-
withdrawQuestionIds:
|
|
1771
|
+
answers: z4.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
|
|
1772
|
+
withdrawQuestionIds: z4.array(questionHandle).min(1).max(10).optional().describe(
|
|
1736
1773
|
"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."
|
|
1737
1774
|
),
|
|
1738
|
-
questionDependencies:
|
|
1775
|
+
questionDependencies: z4.array(QuestionDependencySchema).min(1).max(20).optional().describe(
|
|
1739
1776
|
"Add or remove what a Question you asked holds up, after asking it."
|
|
1740
1777
|
)
|
|
1741
1778
|
};
|
|
1742
|
-
var ContactSchema =
|
|
1779
|
+
var ContactSchema = z4.object({
|
|
1743
1780
|
...sendFields,
|
|
1744
|
-
questions:
|
|
1781
|
+
questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
|
|
1745
1782
|
/** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
|
|
1746
|
-
ackEventIds:
|
|
1783
|
+
ackEventIds: z4.array(z4.string().uuid()).min(1).max(100).optional().describe(
|
|
1747
1784
|
"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."
|
|
1748
1785
|
),
|
|
1749
|
-
wait:
|
|
1786
|
+
wait: z4.boolean().optional().describe(
|
|
1750
1787
|
"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`."
|
|
1751
1788
|
)
|
|
1752
1789
|
}).strict();
|
|
1753
|
-
var sendsAnything = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
1754
|
-
var ContactWireSchema =
|
|
1755
|
-
operationId:
|
|
1790
|
+
var sendsAnything = (c) => !!(c.completions?.length || c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
1791
|
+
var ContactWireSchema = z4.object({
|
|
1792
|
+
operationId: z4.string().uuid(),
|
|
1756
1793
|
...sendFields,
|
|
1757
|
-
questions:
|
|
1794
|
+
questions: z4.array(QuestionWireSchema).min(1).max(20).optional()
|
|
1758
1795
|
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
1759
1796
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1760
|
-
var CONTACT_DESCRIPTION =
|
|
1761
|
-
|
|
1762
|
-
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
1797
|
+
var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read `results`, where a failed item names why while the rest still apply.\n\n" + ASKING_RULES + `
|
|
1763
1798
|
|
|
1764
1799
|
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
1765
1800
|
|
|
1766
|
-
|
|
1801
|
+
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. Ordinary updates never close a Goal. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
1767
1802
|
|
|
1768
|
-
|
|
1803
|
+
COMPLETIONS close and report in one operation: completions:[{goalId,message,units?,reply,options?}]. The same contract works in manage_goals changes with kind:complete. message is at most 50 characters; use units when useful for initial state, changes, current state (verification and limits), and next steps / leftover work with its owner. reply is one acknowledgement in the person's voice. Optional options:[{label,hint}] suggest next steps: a tap acknowledges the report and requests that step from its author. It never reopens the completed Goal; follow-up work needs a new Goal. Closure and the report commit together or neither does. Required children and dependencies must already be finished. No Reviewer rejudges your completion; say only what your evidence supports.
|
|
1769
1804
|
|
|
1770
|
-
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with
|
|
1771
|
-
var CreateGoalSchema =
|
|
1772
|
-
outcome:
|
|
1805
|
+
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goals); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
1806
|
+
var CreateGoalSchema = z5.object({
|
|
1807
|
+
outcome: z5.string().trim().min(1).max(1e4),
|
|
1773
1808
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
1774
1809
|
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
1775
|
-
title:
|
|
1776
|
-
ownerParticipant:
|
|
1777
|
-
idempotencyKey:
|
|
1810
|
+
title: z5.string().trim().min(1).max(80).optional(),
|
|
1811
|
+
ownerParticipant: z5.string().trim().min(1).optional(),
|
|
1812
|
+
idempotencyKey: z5.string().trim().min(1).max(200),
|
|
1778
1813
|
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
1779
1814
|
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
1780
1815
|
* worked"). It could not have been: this door took no parent, so the only route was
|
|
@@ -1782,64 +1817,65 @@ var CreateGoalSchema = z3.object({
|
|
|
1782
1817
|
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
1783
1818
|
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
1784
1819
|
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
1785
|
-
parentGoalId:
|
|
1820
|
+
parentGoalId: z5.string().uuid().optional(),
|
|
1786
1821
|
/** The repository or project identifier this Goal belongs to (#2280) — e.g. "owner/repo" or
|
|
1787
1822
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
1788
|
-
repo:
|
|
1823
|
+
repo: z5.string().trim().min(1).max(200).optional()
|
|
1789
1824
|
});
|
|
1790
|
-
var UpdateGoalSchema =
|
|
1791
|
-
revision:
|
|
1792
|
-
changes:
|
|
1793
|
-
outcome:
|
|
1825
|
+
var UpdateGoalSchema = z5.object({
|
|
1826
|
+
revision: z5.number().int().positive(),
|
|
1827
|
+
changes: z5.object({
|
|
1828
|
+
outcome: z5.string().trim().min(1).max(1e4).optional(),
|
|
1794
1829
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
1795
1830
|
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
1796
|
-
title:
|
|
1797
|
-
ownerParticipant:
|
|
1798
|
-
parentGoalId:
|
|
1799
|
-
dependencies:
|
|
1800
|
-
children:
|
|
1801
|
-
state:
|
|
1802
|
-
progress:
|
|
1803
|
-
reviewed:
|
|
1804
|
-
dueAt:
|
|
1831
|
+
title: z5.string().trim().min(1).max(80).nullable().optional(),
|
|
1832
|
+
ownerParticipant: z5.string().trim().min(1).optional(),
|
|
1833
|
+
parentGoalId: z5.string().uuid().nullable().optional(),
|
|
1834
|
+
dependencies: z5.array(z5.object({ goalId: z5.string().uuid(), gate: z5.enum(["start", "finish"]) }).strict()).optional(),
|
|
1835
|
+
children: z5.array(z5.object({ outcome: z5.string().trim().min(1).max(1e4), ownerParticipant: z5.string().trim().min(1), gate: z5.enum(["start", "finish"]).optional() }).strict()).optional(),
|
|
1836
|
+
state: z5.enum(["active", "done", "cancelled"]).optional(),
|
|
1837
|
+
progress: z5.string().trim().min(1).max(1e4).optional(),
|
|
1838
|
+
reviewed: z5.literal(true).optional(),
|
|
1839
|
+
dueAt: z5.string().datetime({ offset: true }).nullable().optional()
|
|
1805
1840
|
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
1806
|
-
reason:
|
|
1807
|
-
operationId:
|
|
1841
|
+
reason: z5.string().trim().min(1).max(2e3),
|
|
1842
|
+
operationId: z5.string().uuid().optional()
|
|
1808
1843
|
}).strict();
|
|
1809
|
-
var changedGoal =
|
|
1810
|
-
var goalTitle =
|
|
1844
|
+
var changedGoal = z5.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
1845
|
+
var goalTitle = z5.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.");
|
|
1811
1846
|
var GOAL_OUTCOME_MAX = 500;
|
|
1812
|
-
var goalOutcome =
|
|
1813
|
-
var CreateChange =
|
|
1814
|
-
kind:
|
|
1847
|
+
var goalOutcome = z5.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.`);
|
|
1848
|
+
var CreateChange = z5.object({
|
|
1849
|
+
kind: z5.literal("create"),
|
|
1815
1850
|
title: goalTitle,
|
|
1816
1851
|
outcome: goalOutcome,
|
|
1817
|
-
ownerId:
|
|
1818
|
-
parentGoalId:
|
|
1819
|
-
sourceEntryIds:
|
|
1852
|
+
ownerId: z5.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."),
|
|
1853
|
+
parentGoalId: z5.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
1854
|
+
sourceEntryIds: z5.array(z5.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
1820
1855
|
}).strict();
|
|
1821
|
-
var EditChange =
|
|
1822
|
-
var StateChange =
|
|
1856
|
+
var EditChange = z5.object({ kind: z5.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
|
|
1857
|
+
var StateChange = z5.object({ kind: z5.literal("state"), goalId: changedGoal, state: z5.enum(["open", "completed", "canceled"]).describe(
|
|
1823
1858
|
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
1824
1859
|
) }).strict();
|
|
1825
|
-
var AssignChange =
|
|
1826
|
-
var DeferChange =
|
|
1860
|
+
var AssignChange = z5.object({ kind: z5.literal("assign"), goalId: changedGoal, ownerId: z5.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
1861
|
+
var DeferChange = z5.object({ kind: z5.literal("defer"), goalId: changedGoal, until: z5.string().datetime({ offset: true }).nullable().describe(
|
|
1827
1862
|
"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."
|
|
1828
1863
|
) }).strict();
|
|
1829
|
-
var MoveChange =
|
|
1864
|
+
var MoveChange = z5.object({ kind: z5.literal("move"), goalId: changedGoal, parentGoalId: z5.string().uuid().nullable().describe(
|
|
1830
1865
|
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
1831
1866
|
) }).strict();
|
|
1832
|
-
var DependencyChange =
|
|
1833
|
-
kind:
|
|
1834
|
-
change:
|
|
1867
|
+
var DependencyChange = z5.object({
|
|
1868
|
+
kind: z5.literal("dependency"),
|
|
1869
|
+
change: z5.enum(["add", "remove"]),
|
|
1835
1870
|
goalId: changedGoal,
|
|
1836
|
-
dependsOnGoalId:
|
|
1837
|
-
action:
|
|
1838
|
-
reason:
|
|
1871
|
+
dependsOnGoalId: z5.string().uuid().describe("The Goal it waits on."),
|
|
1872
|
+
action: z5.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
1873
|
+
reason: z5.string().trim().min(1).max(250).optional().describe("Why, at most 250 characters, as an explanation, never as policy.")
|
|
1839
1874
|
}).strict();
|
|
1840
|
-
var read = { revision:
|
|
1841
|
-
var GoalChangeSchema =
|
|
1875
|
+
var read = { revision: z5.number().int().positive().optional() };
|
|
1876
|
+
var GoalChangeSchema = z5.discriminatedUnion("kind", [
|
|
1842
1877
|
CreateChange,
|
|
1878
|
+
CompleteChangeSchema.extend(read),
|
|
1843
1879
|
EditChange.extend(read),
|
|
1844
1880
|
StateChange.extend(read),
|
|
1845
1881
|
AssignChange.extend(read),
|
|
@@ -1849,224 +1885,228 @@ var GoalChangeSchema = z3.discriminatedUnion("kind", [
|
|
|
1849
1885
|
]);
|
|
1850
1886
|
var editNames = (changes, ctx) => changes.forEach((c, i) => {
|
|
1851
1887
|
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
1852
|
-
ctx.addIssue({ code:
|
|
1888
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
1853
1889
|
}
|
|
1854
1890
|
});
|
|
1855
|
-
var ManageGoalsSchema =
|
|
1856
|
-
requestId:
|
|
1857
|
-
changes:
|
|
1891
|
+
var ManageGoalsSchema = z5.object({
|
|
1892
|
+
requestId: z5.string().uuid().optional(),
|
|
1893
|
+
changes: z5.array(GoalChangeSchema).min(1).max(50)
|
|
1858
1894
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1859
|
-
var ManageGoalsToolSchema =
|
|
1860
|
-
changes:
|
|
1895
|
+
var ManageGoalsToolSchema = z5.object({
|
|
1896
|
+
changes: z5.array(z5.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
1861
1897
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
1862
|
-
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (
|
|
1863
|
-
var
|
|
1864
|
-
|
|
1898
|
+
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.";
|
|
1899
|
+
var GetGoalsSchema = z5.object({
|
|
1900
|
+
goalIds: z5.array(z5.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
|
|
1865
1901
|
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
1866
1902
|
* newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
|
|
1867
|
-
history:
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
diagnose: z3.boolean().optional()
|
|
1903
|
+
history: z5.boolean().optional()
|
|
1904
|
+
}).strict();
|
|
1905
|
+
var GetDeliverySchema = z5.object({
|
|
1906
|
+
deliveryId: z5.string().uuid().describe("The Delivery id returned by contact or receive.")
|
|
1872
1907
|
}).strict();
|
|
1873
|
-
var
|
|
1874
|
-
var
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1908
|
+
var GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
|
|
1909
|
+
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.";
|
|
1910
|
+
var SearchToolSchema = z5.object({
|
|
1911
|
+
query: z5.string().trim().min(1).max(500),
|
|
1912
|
+
types: z5.array(z5.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
1913
|
+
goalId: z5.string().uuid().optional(),
|
|
1914
|
+
limit: z5.number().int().min(1).max(20).optional()
|
|
1879
1915
|
}).strict();
|
|
1880
1916
|
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.";
|
|
1881
|
-
var
|
|
1917
|
+
var ConfigureIdentitySchema = z5.object({}).strict();
|
|
1918
|
+
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.";
|
|
1919
|
+
var CheckActivitySchema = z5.object({}).strict();
|
|
1882
1920
|
var FEEDBACK_TEXT_MAX = 5e4;
|
|
1883
|
-
var SendFeedbackSchema =
|
|
1884
|
-
text:
|
|
1885
|
-
title:
|
|
1886
|
-
kind:
|
|
1921
|
+
var SendFeedbackSchema = z5.object({
|
|
1922
|
+
text: z5.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.`),
|
|
1923
|
+
title: z5.string().trim().min(1).max(200).optional().describe("One line naming it."),
|
|
1924
|
+
kind: z5.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
|
|
1887
1925
|
}).strict();
|
|
1888
1926
|
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.";
|
|
1889
1927
|
var AGENT_TOOLS = [
|
|
1928
|
+
{ name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
|
|
1890
1929
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
1891
1930
|
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
1892
|
-
{ name: "
|
|
1931
|
+
{ name: "get_delivery", description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
|
|
1932
|
+
{ name: "get_goals", description: GET_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalsSchema) },
|
|
1893
1933
|
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
1894
|
-
{ 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
|
|
1934
|
+
{ 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) },
|
|
1895
1935
|
{ name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) }
|
|
1896
1936
|
];
|
|
1897
1937
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
1898
|
-
var id =
|
|
1899
|
-
var key =
|
|
1900
|
-
var participant =
|
|
1901
|
-
var SourceSchema =
|
|
1902
|
-
var ResultRefSchema =
|
|
1903
|
-
var NodeRefSchema =
|
|
1904
|
-
var DependencySchema =
|
|
1938
|
+
var id = z6.string().uuid();
|
|
1939
|
+
var key = z6.string().min(1).max(40);
|
|
1940
|
+
var participant = z6.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
|
|
1941
|
+
var SourceSchema = z6.object({ entryId: id }).strict();
|
|
1942
|
+
var ResultRefSchema = z6.union([z6.object({ id }).strict(), z6.object({ local: key }).strict()]);
|
|
1943
|
+
var NodeRefSchema = z6.object({ type: z6.enum(["goal", "question"]), id }).strict();
|
|
1944
|
+
var DependencySchema = z6.object({
|
|
1905
1945
|
blocker: NodeRefSchema,
|
|
1906
1946
|
blocked: NodeRefSchema,
|
|
1907
|
-
action:
|
|
1908
|
-
reason:
|
|
1947
|
+
action: z6.enum(["start", "complete", "answer"]),
|
|
1948
|
+
reason: z6.string().max(500).optional()
|
|
1909
1949
|
}).strict();
|
|
1910
|
-
var goalState =
|
|
1950
|
+
var goalState = z6.enum(["open", "completed", "canceled"]);
|
|
1911
1951
|
var existingGoalChanges = [
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
kind:
|
|
1919
|
-
change:
|
|
1952
|
+
z6.object({ kind: z6.literal("edit"), goalId: id, title: z6.string().min(1).max(120).optional(), outcome: z6.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
|
|
1953
|
+
z6.object({ kind: z6.literal("state"), goalId: id, state: goalState }).strict(),
|
|
1954
|
+
z6.object({ kind: z6.literal("assign"), goalId: id, ownerId: participant }).strict(),
|
|
1955
|
+
z6.object({ kind: z6.literal("defer"), goalId: id, until: z6.string().datetime().nullable() }).strict(),
|
|
1956
|
+
z6.object({ kind: z6.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
|
|
1957
|
+
z6.object({
|
|
1958
|
+
kind: z6.literal("dependency"),
|
|
1959
|
+
change: z6.enum(["add", "remove"]),
|
|
1920
1960
|
goalId: id,
|
|
1921
1961
|
dependsOnGoalId: id,
|
|
1922
|
-
action:
|
|
1923
|
-
reason:
|
|
1962
|
+
action: z6.enum(["start", "complete"]),
|
|
1963
|
+
reason: z6.string().max(500).optional()
|
|
1924
1964
|
}).strict()
|
|
1925
1965
|
];
|
|
1926
|
-
var BrainGoalChangeSchema =
|
|
1966
|
+
var BrainGoalChangeSchema = z6.union([
|
|
1927
1967
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
1928
|
-
|
|
1929
|
-
kind:
|
|
1930
|
-
outcome:
|
|
1931
|
-
title:
|
|
1968
|
+
z6.object({
|
|
1969
|
+
kind: z6.literal("create"),
|
|
1970
|
+
outcome: z6.string().min(1),
|
|
1971
|
+
title: z6.string().min(1).max(120),
|
|
1932
1972
|
ownerId: participant,
|
|
1933
1973
|
parentGoal: ResultRefSchema.nullable(),
|
|
1934
|
-
newSession:
|
|
1974
|
+
newSession: z6.literal(true).optional()
|
|
1935
1975
|
}).strict(),
|
|
1936
1976
|
...existingGoalChanges
|
|
1937
1977
|
]);
|
|
1938
|
-
var option =
|
|
1939
|
-
var QuestionChangeSchema =
|
|
1940
|
-
|
|
1941
|
-
kind:
|
|
1942
|
-
text:
|
|
1943
|
-
summary:
|
|
1978
|
+
var option = z6.object({ id: z6.string().min(1).max(40), label: z6.string().min(1), hint: z6.string().optional() }).strict();
|
|
1979
|
+
var QuestionChangeSchema = z6.union([
|
|
1980
|
+
z6.object({
|
|
1981
|
+
kind: z6.literal("create"),
|
|
1982
|
+
text: z6.string().min(1),
|
|
1983
|
+
summary: z6.string().optional(),
|
|
1944
1984
|
answererId: participant,
|
|
1945
1985
|
goal: ResultRefSchema.optional(),
|
|
1946
|
-
blocks:
|
|
1947
|
-
options:
|
|
1948
|
-
pickMode:
|
|
1986
|
+
blocks: z6.array(z6.object({ question: ResultRefSchema }).strict()),
|
|
1987
|
+
options: z6.array(option).min(2).optional(),
|
|
1988
|
+
pickMode: z6.enum(["one", "many", "rank"]).optional()
|
|
1949
1989
|
}).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1990
|
+
z6.object({ kind: z6.literal("edit"), questionId: id, text: z6.string().min(1) }).strict(),
|
|
1991
|
+
z6.object({ kind: z6.literal("assign"), questionId: id, answererId: participant }).strict(),
|
|
1992
|
+
z6.object({ kind: z6.literal("withdraw"), questionId: id }).strict(),
|
|
1993
|
+
z6.object({ kind: z6.literal("dependency"), change: z6.enum(["add", "remove"]), edge: DependencySchema }).strict()
|
|
1954
1994
|
]);
|
|
1955
|
-
var lessonScope =
|
|
1956
|
-
|
|
1957
|
-
|
|
1995
|
+
var lessonScope = z6.union([
|
|
1996
|
+
z6.object({ kind: z6.literal("user") }).strict(),
|
|
1997
|
+
z6.object({ kind: z6.literal("goal"), goal: ResultRefSchema }).strict()
|
|
1958
1998
|
]);
|
|
1959
|
-
var LessonChangeSchema =
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1999
|
+
var LessonChangeSchema = z6.union([
|
|
2000
|
+
z6.object({ kind: z6.literal("remember"), text: z6.string().min(1), scope: lessonScope }).strict(),
|
|
2001
|
+
z6.object({ kind: z6.literal("revise"), lessonId: id, text: z6.string().min(1), scope: lessonScope }).strict(),
|
|
2002
|
+
z6.object({ kind: z6.literal("withdraw"), lessonId: id }).strict()
|
|
1963
2003
|
]);
|
|
1964
|
-
var BrainNextSchema =
|
|
1965
|
-
instructions:
|
|
1966
|
-
say:
|
|
1967
|
-
then:
|
|
1968
|
-
waitFor:
|
|
1969
|
-
reason:
|
|
2004
|
+
var BrainNextSchema = z6.object({
|
|
2005
|
+
instructions: z6.array(z6.object({ entryKeys: z6.array(key).min(1) }).strict()),
|
|
2006
|
+
say: z6.array(key),
|
|
2007
|
+
then: z6.enum(["listen", "hold", "end", "none"]),
|
|
2008
|
+
waitFor: z6.array(ResultRefSchema),
|
|
2009
|
+
reason: z6.string()
|
|
1970
2010
|
}).strict();
|
|
1971
2011
|
var messageBase = {
|
|
1972
2012
|
key,
|
|
1973
|
-
text:
|
|
1974
|
-
goals:
|
|
1975
|
-
questions:
|
|
1976
|
-
entryKeys:
|
|
1977
|
-
obligationIds:
|
|
2013
|
+
text: z6.string().min(1),
|
|
2014
|
+
goals: z6.array(ResultRefSchema),
|
|
2015
|
+
questions: z6.array(ResultRefSchema),
|
|
2016
|
+
entryKeys: z6.array(key),
|
|
2017
|
+
obligationIds: z6.array(id)
|
|
1978
2018
|
};
|
|
1979
|
-
var BrainMessageSchema =
|
|
1980
|
-
|
|
1981
|
-
|
|
2019
|
+
var BrainMessageSchema = z6.union([
|
|
2020
|
+
z6.object({ ...messageBase, to: z6.object({ kind: z6.literal("user") }).strict() }).strict(),
|
|
2021
|
+
z6.object({
|
|
1982
2022
|
...messageBase,
|
|
1983
|
-
to:
|
|
1984
|
-
call:
|
|
2023
|
+
to: z6.object({ kind: z6.literal("agent"), agentId: z6.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
|
|
2024
|
+
call: z6.object({ callId: id, listen: z6.literal(true) }).strict().optional()
|
|
1985
2025
|
}).strict()
|
|
1986
2026
|
]);
|
|
1987
|
-
var BrainSearchSchema =
|
|
1988
|
-
query:
|
|
1989
|
-
within:
|
|
2027
|
+
var BrainSearchSchema = z6.object({
|
|
2028
|
+
query: z6.string().min(1).max(500),
|
|
2029
|
+
within: z6.array(z6.enum(["entries", "goals", "answers"])).min(1),
|
|
1990
2030
|
goalId: id.optional()
|
|
1991
2031
|
}).strict();
|
|
1992
|
-
var BrainResultSchema =
|
|
1993
|
-
messages:
|
|
1994
|
-
entries:
|
|
1995
|
-
questions:
|
|
1996
|
-
answers:
|
|
2032
|
+
var BrainResultSchema = z6.object({
|
|
2033
|
+
messages: z6.array(BrainMessageSchema),
|
|
2034
|
+
entries: z6.array(z6.object({ key, sources: z6.array(SourceSchema).min(1), goals: z6.array(ResultRefSchema), summary: z6.string().optional() }).strict()),
|
|
2035
|
+
questions: z6.array(z6.object({ key, entryKeys: z6.array(key), change: QuestionChangeSchema }).strict()),
|
|
2036
|
+
answers: z6.array(z6.object({
|
|
1997
2037
|
question: ResultRefSchema,
|
|
1998
|
-
entryKeys:
|
|
1999
|
-
summary:
|
|
2000
|
-
selectedOptionIds:
|
|
2038
|
+
entryKeys: z6.array(key).min(1),
|
|
2039
|
+
summary: z6.string(),
|
|
2040
|
+
selectedOptionIds: z6.array(z6.string().min(1)).optional()
|
|
2001
2041
|
}).strict()),
|
|
2002
|
-
goals:
|
|
2003
|
-
feedback:
|
|
2004
|
-
lessons:
|
|
2042
|
+
goals: z6.array(z6.object({ key, entryKeys: z6.array(key), change: BrainGoalChangeSchema }).strict()),
|
|
2043
|
+
feedback: z6.array(z6.object({ entryKeys: z6.array(key).min(1) }).strict()),
|
|
2044
|
+
lessons: z6.array(z6.object({ entryKeys: z6.array(key).min(1), change: LessonChangeSchema }).strict()),
|
|
2005
2045
|
next: BrainNextSchema,
|
|
2006
2046
|
search: BrainSearchSchema.optional()
|
|
2007
2047
|
}).strict();
|
|
2008
|
-
var str =
|
|
2009
|
-
var strs =
|
|
2010
|
-
var CompactResultSchema =
|
|
2011
|
-
next:
|
|
2012
|
-
messages:
|
|
2013
|
-
entries:
|
|
2014
|
-
answers:
|
|
2015
|
-
changes:
|
|
2048
|
+
var str = z6.string();
|
|
2049
|
+
var strs = z6.array(z6.string());
|
|
2050
|
+
var CompactResultSchema = z6.object({
|
|
2051
|
+
next: z6.object({ say: strs, then: z6.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
|
|
2052
|
+
messages: z6.array(z6.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z6.boolean() }).strict()),
|
|
2053
|
+
entries: z6.array(z6.object({ key: str, from: str, on: strs }).strict()),
|
|
2054
|
+
answers: z6.array(z6.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
|
|
2055
|
+
changes: z6.array(z6.object({
|
|
2016
2056
|
key: str,
|
|
2017
|
-
what:
|
|
2018
|
-
op:
|
|
2057
|
+
what: z6.enum(["goal", "question", "lesson"]),
|
|
2058
|
+
op: z6.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
|
|
2019
2059
|
id: str,
|
|
2020
2060
|
text: str,
|
|
2021
2061
|
title: str,
|
|
2022
2062
|
who: str,
|
|
2023
2063
|
under: str,
|
|
2024
|
-
gate:
|
|
2025
|
-
options:
|
|
2026
|
-
pick:
|
|
2064
|
+
gate: z6.enum(["start", "complete", "answer", "none"]),
|
|
2065
|
+
options: z6.array(z6.object({ id: str, label: str }).strict()),
|
|
2066
|
+
pick: z6.enum(["one", "many", "rank", "words"]),
|
|
2027
2067
|
entries: strs
|
|
2028
2068
|
}).strict()),
|
|
2029
|
-
feedback:
|
|
2030
|
-
search:
|
|
2069
|
+
feedback: z6.array(strs),
|
|
2070
|
+
search: z6.object({ query: str, within: strs, goal: str }).strict()
|
|
2031
2071
|
}).strict();
|
|
2032
2072
|
var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2033
2073
|
zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
|
|
2034
2074
|
);
|
|
2035
|
-
var str2 =
|
|
2036
|
-
var strs2 =
|
|
2037
|
-
var TalkerReplySchema =
|
|
2075
|
+
var str2 = z7.string();
|
|
2076
|
+
var strs2 = z7.array(z7.string());
|
|
2077
|
+
var TalkerReplySchema = z7.object({
|
|
2038
2078
|
/** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
|
|
2039
2079
|
* or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
|
|
2040
2080
|
* answered before ("" for none). */
|
|
2041
|
-
messages:
|
|
2081
|
+
messages: z7.array(z7.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
|
|
2042
2082
|
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
2043
|
-
then:
|
|
2083
|
+
then: z7.enum(["listen", "hold", "end"]),
|
|
2044
2084
|
/** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
|
|
2045
2085
|
* IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
|
|
2046
2086
|
* "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
|
|
2047
2087
|
* end of this call), a question's handle (that question's answer) or an ISO time. */
|
|
2048
|
-
replies:
|
|
2088
|
+
replies: z7.array(z7.object({ item: str2, options: strs2, lines: strs2, summary: str2, defer: str2 }).strict()),
|
|
2049
2089
|
/** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
|
|
2050
2090
|
instruction: strs2,
|
|
2051
2091
|
/** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
|
|
2052
2092
|
* work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
|
|
2053
2093
|
file: strs2,
|
|
2054
2094
|
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
2055
|
-
need:
|
|
2095
|
+
need: z7.object({ kind: z7.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
|
|
2056
2096
|
/** The handle of the item to explain ("" for none). */
|
|
2057
2097
|
explain: str2
|
|
2058
2098
|
}).strict();
|
|
2059
2099
|
var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2060
2100
|
zodToJsonSchema(TalkerReplySchema, { $refStrategy: "none" })
|
|
2061
2101
|
);
|
|
2062
|
-
var str3 =
|
|
2063
|
-
var strs3 =
|
|
2064
|
-
var FilerReplySchema =
|
|
2102
|
+
var str3 = z8.string();
|
|
2103
|
+
var strs3 = z8.array(z8.string());
|
|
2104
|
+
var FilerReplySchema = z8.object({
|
|
2065
2105
|
/** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals), and what
|
|
2066
2106
|
* the line says in at most ten words. */
|
|
2067
|
-
filed:
|
|
2068
|
-
goals:
|
|
2069
|
-
op:
|
|
2107
|
+
filed: z8.array(z8.object({ line: str3, goals: strs3, summary: str3 }).strict()),
|
|
2108
|
+
goals: z8.array(z8.object({
|
|
2109
|
+
op: z8.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
2070
2110
|
key: str3,
|
|
2071
2111
|
goal: str3,
|
|
2072
2112
|
title: str3,
|
|
@@ -2075,33 +2115,33 @@ var FilerReplySchema = z6.object({
|
|
|
2075
2115
|
under: str3,
|
|
2076
2116
|
until: str3,
|
|
2077
2117
|
on: str3,
|
|
2078
|
-
gate:
|
|
2118
|
+
gate: z8.enum(["start", "complete", "answer", ""]),
|
|
2079
2119
|
why: str3
|
|
2080
2120
|
}).strict()),
|
|
2081
|
-
questions:
|
|
2082
|
-
op:
|
|
2121
|
+
questions: z8.array(z8.object({
|
|
2122
|
+
op: z8.enum(["create", "edit", "assign", "withdraw"]),
|
|
2083
2123
|
key: str3,
|
|
2084
2124
|
question: str3,
|
|
2085
2125
|
text: str3,
|
|
2086
2126
|
summary: str3,
|
|
2087
2127
|
to: str3,
|
|
2088
2128
|
goal: str3,
|
|
2089
|
-
options:
|
|
2090
|
-
pick:
|
|
2129
|
+
options: z8.array(z8.object({ id: str3, label: str3 }).strict()),
|
|
2130
|
+
pick: z8.enum(["one", "many", "rank", "words", ""]),
|
|
2091
2131
|
blocks: str3
|
|
2092
2132
|
}).strict()),
|
|
2093
|
-
lessons:
|
|
2133
|
+
lessons: z8.array(z8.object({ op: z8.enum(["remember", "revise", "withdraw"]), lesson: str3, text: str3, scope: str3 }).strict()),
|
|
2094
2134
|
/** Line handles whose words comment on Paigy itself. */
|
|
2095
2135
|
feedback: strs3
|
|
2096
2136
|
}).strict();
|
|
2097
2137
|
var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2098
2138
|
zodToJsonSchema(FilerReplySchema, { $refStrategy: "none" })
|
|
2099
2139
|
);
|
|
2100
|
-
var ExplainerReplySchema =
|
|
2140
|
+
var ExplainerReplySchema = z9.object({
|
|
2101
2141
|
/** The explanation, as sentences, spoken in order. */
|
|
2102
|
-
say:
|
|
2142
|
+
say: z9.array(z9.string()),
|
|
2103
2143
|
/** Questions to an agent about gaps in what it read: the agent's handle and the question. */
|
|
2104
|
-
followUps:
|
|
2144
|
+
followUps: z9.array(z9.object({ to: z9.string(), text: z9.string() }).strict())
|
|
2105
2145
|
}).strict();
|
|
2106
2146
|
var EXPLAINER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2107
2147
|
zodToJsonSchema(ExplainerReplySchema, { $refStrategy: "none" })
|
|
@@ -2124,28 +2164,28 @@ var DISTILLER_DEFECTS = [
|
|
|
2124
2164
|
"dropped"
|
|
2125
2165
|
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
2126
2166
|
];
|
|
2127
|
-
var DistillerReplySchema =
|
|
2128
|
-
defects:
|
|
2129
|
-
kind:
|
|
2167
|
+
var DistillerReplySchema = z10.object({
|
|
2168
|
+
defects: z10.array(z10.object({
|
|
2169
|
+
kind: z10.enum(DISTILLER_DEFECTS),
|
|
2130
2170
|
/** The handles of the lines it shows in. */
|
|
2131
|
-
lines:
|
|
2171
|
+
lines: z10.array(z10.string()),
|
|
2132
2172
|
/** The handle of the record it concerns, or "". */
|
|
2133
|
-
about:
|
|
2173
|
+
about: z10.string(),
|
|
2134
2174
|
/** One plain sentence, in words, never handles: it is what the feedback row keeps. */
|
|
2135
|
-
why:
|
|
2175
|
+
why: z10.string()
|
|
2136
2176
|
}).strict())
|
|
2137
2177
|
}).strict();
|
|
2138
2178
|
var DISTILLER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
2139
2179
|
zodToJsonSchema(DistillerReplySchema, { $refStrategy: "none" })
|
|
2140
2180
|
);
|
|
2141
2181
|
var TIDY_AUDIENCES = ["talker", "filer", "agents"];
|
|
2142
|
-
var TidyReplySchema =
|
|
2143
|
-
lessons:
|
|
2144
|
-
lesson:
|
|
2145
|
-
op:
|
|
2146
|
-
text:
|
|
2147
|
-
for:
|
|
2148
|
-
into:
|
|
2182
|
+
var TidyReplySchema = z10.object({
|
|
2183
|
+
lessons: z10.array(z10.object({
|
|
2184
|
+
lesson: z10.string(),
|
|
2185
|
+
op: z10.enum(["keep", "revise", "withdraw"]),
|
|
2186
|
+
text: z10.string(),
|
|
2187
|
+
for: z10.enum([...TIDY_AUDIENCES, ""]),
|
|
2188
|
+
into: z10.string()
|
|
2149
2189
|
}).strict())
|
|
2150
2190
|
}).strict();
|
|
2151
2191
|
var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
@@ -2186,20 +2226,56 @@ var LIVE_MS = 3 * 6e4;
|
|
|
2186
2226
|
var WORKING_MS = 60 * 6e4;
|
|
2187
2227
|
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
2188
2228
|
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
2189
|
-
var
|
|
2190
|
-
|
|
2191
|
-
|
|
2229
|
+
var json = z11.lazy(() => z11.union([
|
|
2230
|
+
z11.string(),
|
|
2231
|
+
z11.number().finite(),
|
|
2232
|
+
z11.boolean(),
|
|
2233
|
+
z11.null(),
|
|
2234
|
+
z11.array(json),
|
|
2235
|
+
z11.record(json)
|
|
2236
|
+
]));
|
|
2237
|
+
var clock = z11.number().int().nonnegative().safe();
|
|
2238
|
+
var HeardEventSchema = z11.object({
|
|
2239
|
+
eventId: z11.string().uuid(),
|
|
2240
|
+
source: z11.enum(["phone", "bot"]),
|
|
2241
|
+
sequence: clock,
|
|
2242
|
+
kind: z11.string().min(1),
|
|
2243
|
+
phoneAtMs: clock.optional(),
|
|
2244
|
+
botAtMs: clock.optional(),
|
|
2245
|
+
payload: z11.record(json)
|
|
2246
|
+
}).strict();
|
|
2247
|
+
var HeardCursorSchema = HeardEventSchema.pick({ source: true, sequence: true, eventId: true });
|
|
2248
|
+
var HeardPageSchema = z11.object({
|
|
2249
|
+
events: z11.array(HeardEventSchema).max(200),
|
|
2250
|
+
next: HeardCursorSchema.nullable()
|
|
2251
|
+
}).strict();
|
|
2252
|
+
var MAX_EVENTS = 50;
|
|
2253
|
+
var MAX_BYTES = 8e4;
|
|
2254
|
+
var batchBytes = (events) => {
|
|
2255
|
+
let bytes = 0;
|
|
2256
|
+
for (const char of JSON.stringify({ events })) {
|
|
2257
|
+
const point = char.codePointAt(0);
|
|
2258
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
2259
|
+
}
|
|
2260
|
+
return bytes;
|
|
2261
|
+
};
|
|
2262
|
+
var HeardBatchSchema = z11.object({
|
|
2263
|
+
events: z11.array(HeardEventSchema).min(1).max(MAX_EVENTS)
|
|
2264
|
+
}).strict().refine((value) => batchBytes(value.events) <= MAX_BYTES, "heard_batch_too_large");
|
|
2265
|
+
var ContextSchema = z12.object({
|
|
2266
|
+
title: z12.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
2267
|
+
description: z12.array(z12.string().min(1)).describe(
|
|
2192
2268
|
"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."
|
|
2193
2269
|
),
|
|
2194
2270
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
2195
2271
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
2196
|
-
units:
|
|
2272
|
+
units: z12.array(z12.object({ title: z12.string(), body: z12.string() })).optional()
|
|
2197
2273
|
});
|
|
2198
|
-
var ParticipantSchema =
|
|
2199
|
-
kind:
|
|
2200
|
-
id:
|
|
2274
|
+
var ParticipantSchema = z12.object({
|
|
2275
|
+
kind: z12.enum(["human", "agent"]),
|
|
2276
|
+
id: z12.string()
|
|
2201
2277
|
});
|
|
2202
|
-
var TransformSchema =
|
|
2278
|
+
var TransformSchema = z12.enum([
|
|
2203
2279
|
"structure",
|
|
2204
2280
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
2205
2281
|
"request_more",
|
|
@@ -2222,13 +2298,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
|
|
|
2222
2298
|
function isPaigy(ref) {
|
|
2223
2299
|
return ref === participantRef(PAIGY_SELF);
|
|
2224
2300
|
}
|
|
2225
|
-
var VisualSchema =
|
|
2226
|
-
url:
|
|
2227
|
-
label:
|
|
2301
|
+
var VisualSchema = z12.object({
|
|
2302
|
+
url: z12.string().url(),
|
|
2303
|
+
label: z12.string().optional()
|
|
2228
2304
|
});
|
|
2229
|
-
var NotifyLevelSchema =
|
|
2230
|
-
var SelectShapeSchema =
|
|
2231
|
-
var ReceiptEventSchema =
|
|
2305
|
+
var NotifyLevelSchema = z12.enum(["inbox", "push", "banner", "call"]);
|
|
2306
|
+
var SelectShapeSchema = z12.enum(["one", "many", "rank", "confirm", "text"]);
|
|
2307
|
+
var ReceiptEventSchema = z12.enum([
|
|
2232
2308
|
"delivered",
|
|
2233
2309
|
// the bundle reached the recipient at some level
|
|
2234
2310
|
"seen",
|
|
@@ -2258,47 +2334,47 @@ var ReceiptEventSchema = z9.enum([
|
|
|
2258
2334
|
// be rewound by a writer that forgot to advance it.
|
|
2259
2335
|
"restarted"
|
|
2260
2336
|
]);
|
|
2261
|
-
var AttentionSchema =
|
|
2337
|
+
var AttentionSchema = z12.object({
|
|
2262
2338
|
urgency: NotifyLevelSchema,
|
|
2263
2339
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
2264
2340
|
select: SelectShapeSchema.nullable(),
|
|
2265
2341
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
2266
|
-
points:
|
|
2342
|
+
points: z12.array(z12.string()).nullable(),
|
|
2267
2343
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
2268
|
-
blocking:
|
|
2344
|
+
blocking: z12.boolean(),
|
|
2269
2345
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
2270
2346
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
2271
|
-
deadline:
|
|
2347
|
+
deadline: z12.string().datetime().nullable().optional()
|
|
2272
2348
|
});
|
|
2273
|
-
var NotifyRequestFields =
|
|
2349
|
+
var NotifyRequestFields = z12.object({
|
|
2274
2350
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
2275
2351
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
2276
2352
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
2277
2353
|
context: ContextSchema.optional(),
|
|
2278
|
-
options:
|
|
2354
|
+
options: z12.array(OptionSchema.omit({ id: true }).extend({ label: z12.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
2279
2355
|
"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)."
|
|
2280
2356
|
),
|
|
2281
|
-
points:
|
|
2357
|
+
points: z12.array(z12.string().min(1)).optional().describe(
|
|
2282
2358
|
"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."
|
|
2283
2359
|
),
|
|
2284
|
-
visuals:
|
|
2360
|
+
visuals: z12.array(VisualSchema).optional().describe(
|
|
2285
2361
|
"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."
|
|
2286
2362
|
),
|
|
2287
2363
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
2288
|
-
repo:
|
|
2364
|
+
repo: z12.string().optional(),
|
|
2289
2365
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
2290
|
-
branch:
|
|
2366
|
+
branch: z12.string().optional(),
|
|
2291
2367
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
2292
2368
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
2293
2369
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
2294
2370
|
* still accepts the old name from older clients. */
|
|
2295
|
-
parentId:
|
|
2371
|
+
parentId: z12.string().uuid().optional(),
|
|
2296
2372
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
2297
2373
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
2298
|
-
workId:
|
|
2374
|
+
workId: z12.string().uuid().optional(),
|
|
2299
2375
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
2300
2376
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
2301
|
-
goalId:
|
|
2377
|
+
goalId: z12.string().uuid().optional(),
|
|
2302
2378
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
2303
2379
|
"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."
|
|
2304
2380
|
),
|
|
@@ -2306,7 +2382,7 @@ var NotifyRequestFields = z9.object({
|
|
|
2306
2382
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
2307
2383
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
2308
2384
|
* says HOW. */
|
|
2309
|
-
clarifies:
|
|
2385
|
+
clarifies: z12.string().optional(),
|
|
2310
2386
|
select: SelectShapeSchema.optional().describe(
|
|
2311
2387
|
"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."
|
|
2312
2388
|
),
|
|
@@ -2320,20 +2396,20 @@ var NotifyRequestFields = z9.object({
|
|
|
2320
2396
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2321
2397
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2322
2398
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2323
|
-
ask:
|
|
2399
|
+
ask: z12.string().min(1).max(1e4).optional().describe(
|
|
2324
2400
|
'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.'
|
|
2325
2401
|
),
|
|
2326
|
-
needs:
|
|
2402
|
+
needs: z12.array(z12.string().min(1)).optional().describe(
|
|
2327
2403
|
"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."
|
|
2328
2404
|
),
|
|
2329
|
-
urgencyHint:
|
|
2405
|
+
urgencyHint: z12.enum(["whenever", "soon", "now"]).optional().describe(
|
|
2330
2406
|
"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."
|
|
2331
2407
|
),
|
|
2332
2408
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
2333
2409
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
2334
2410
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
2335
2411
|
* urgencyHint/blocking win when both are sent. */
|
|
2336
|
-
waiting:
|
|
2412
|
+
waiting: z12.enum(["none", "soft", "hard"]).optional().describe(
|
|
2337
2413
|
"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."
|
|
2338
2414
|
),
|
|
2339
2415
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -2341,98 +2417,98 @@ var NotifyRequestFields = z9.object({
|
|
|
2341
2417
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
2342
2418
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
2343
2419
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
2344
|
-
confirm:
|
|
2420
|
+
confirm: z12.boolean().optional().describe(
|
|
2345
2421
|
"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'."
|
|
2346
2422
|
),
|
|
2347
2423
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
2348
2424
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
2349
2425
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
2350
2426
|
* waiting:'hard'. */
|
|
2351
|
-
channel:
|
|
2427
|
+
channel: z12.enum(["call", "message"]).optional().describe(
|
|
2352
2428
|
"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."
|
|
2353
2429
|
),
|
|
2354
|
-
confirmStyle:
|
|
2430
|
+
confirmStyle: z12.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2355
2431
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2356
2432
|
),
|
|
2357
|
-
blocking:
|
|
2433
|
+
blocking: z12.boolean().default(false).describe(
|
|
2358
2434
|
"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."
|
|
2359
2435
|
)
|
|
2360
2436
|
});
|
|
2361
2437
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
2362
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
2438
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
2363
2439
|
if (r.ask !== void 0) {
|
|
2364
2440
|
for (const f of ["context", "select", "points"]) {
|
|
2365
2441
|
if (r[f] !== void 0)
|
|
2366
|
-
ctx.addIssue({ code:
|
|
2442
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
|
|
2367
2443
|
}
|
|
2368
2444
|
return;
|
|
2369
2445
|
}
|
|
2370
2446
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
2371
|
-
ctx.addIssue({ code:
|
|
2447
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
2372
2448
|
if (!r.context)
|
|
2373
|
-
ctx.addIssue({ code:
|
|
2449
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
2374
2450
|
if (!r.select)
|
|
2375
|
-
ctx.addIssue({ code:
|
|
2451
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
2376
2452
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
2377
2453
|
if (needsOptions && !r.options?.length)
|
|
2378
|
-
ctx.addIssue({ code:
|
|
2454
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
2379
2455
|
if (!needsOptions && r.options?.length)
|
|
2380
|
-
ctx.addIssue({ code:
|
|
2456
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
2381
2457
|
});
|
|
2382
|
-
var NotifyStatusSchema =
|
|
2383
|
-
var AgentStateSchema =
|
|
2384
|
-
var TurnSchema =
|
|
2385
|
-
prompt:
|
|
2386
|
-
reply:
|
|
2458
|
+
var NotifyStatusSchema = z12.enum(["pending", "answered", "ignored"]);
|
|
2459
|
+
var AgentStateSchema = z12.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
2460
|
+
var TurnSchema = z12.object({
|
|
2461
|
+
prompt: z12.string(),
|
|
2462
|
+
reply: z12.string()
|
|
2387
2463
|
});
|
|
2388
|
-
var UserAnswerSchema =
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2464
|
+
var UserAnswerSchema = z12.discriminatedUnion("kind", [
|
|
2465
|
+
z12.object({ kind: z12.literal("option"), optionId: z12.string(), label: z12.string().optional() }),
|
|
2466
|
+
z12.object({ kind: z12.literal("text"), text: z12.string() }),
|
|
2467
|
+
z12.object({ kind: z12.literal("ignored") }),
|
|
2468
|
+
z12.object({ kind: z12.literal("multi"), optionIds: z12.array(z12.string()), labels: z12.array(z12.string()).optional() }),
|
|
2469
|
+
z12.object({ kind: z12.literal("ranked"), optionIds: z12.array(z12.string()), labels: z12.array(z12.string()).optional() }),
|
|
2470
|
+
z12.object({ kind: z12.literal("clarify"), chunks: z12.array(z12.string()).min(1) }),
|
|
2471
|
+
z12.object({ kind: z12.literal("confirm"), approved: z12.boolean() }),
|
|
2472
|
+
z12.object({ kind: z12.literal("turns"), turns: z12.array(TurnSchema).min(1) }),
|
|
2397
2473
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
2398
2474
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
2399
2475
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
2400
2476
|
* card the user can reply to — the broker never overrides the user. */
|
|
2401
|
-
|
|
2477
|
+
z12.object({ kind: z12.literal("precedent"), answer: z12.string(), derivation: z12.string(), sources: z12.array(z12.string()).min(1) })
|
|
2402
2478
|
]);
|
|
2403
|
-
var IntentSchema =
|
|
2479
|
+
var IntentSchema = z12.object({
|
|
2404
2480
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
2405
2481
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
2406
2482
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
2407
2483
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2408
|
-
kind:
|
|
2409
|
-
detail:
|
|
2484
|
+
kind: z12.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2485
|
+
detail: z12.string(),
|
|
2410
2486
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2411
2487
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2412
2488
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2413
2489
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2414
|
-
dueInSeconds:
|
|
2490
|
+
dueInSeconds: z12.number().int().positive().optional(),
|
|
2415
2491
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2416
2492
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
2417
2493
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
2418
2494
|
* about what cannot answer "is the bot looping less this week?". */
|
|
2419
|
-
fault:
|
|
2495
|
+
fault: z12.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
2420
2496
|
});
|
|
2421
|
-
var RideAlongSchema =
|
|
2497
|
+
var RideAlongSchema = z12.object({
|
|
2422
2498
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
2423
|
-
noteId:
|
|
2499
|
+
noteId: z12.string(),
|
|
2424
2500
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
2425
|
-
text:
|
|
2501
|
+
text: z12.string(),
|
|
2426
2502
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
2427
|
-
parentId:
|
|
2503
|
+
parentId: z12.string().nullable()
|
|
2428
2504
|
});
|
|
2429
|
-
var AwaitItemSchema =
|
|
2430
|
-
|
|
2431
|
-
type:
|
|
2432
|
-
parentId:
|
|
2433
|
-
notificationId:
|
|
2434
|
-
workId:
|
|
2435
|
-
decisionId:
|
|
2505
|
+
var AwaitItemSchema = z12.discriminatedUnion("type", [
|
|
2506
|
+
z12.object({
|
|
2507
|
+
type: z12.literal("reply"),
|
|
2508
|
+
parentId: z12.string(),
|
|
2509
|
+
notificationId: z12.string(),
|
|
2510
|
+
workId: z12.string().uuid().optional(),
|
|
2511
|
+
decisionId: z12.string().uuid().optional(),
|
|
2436
2512
|
answer: UserAnswerSchema,
|
|
2437
2513
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
2438
2514
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -2442,107 +2518,107 @@ var AwaitItemSchema = z9.discriminatedUnion("type", [
|
|
|
2442
2518
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
2443
2519
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
2444
2520
|
* a sentence with a hole in it is worse than no sentence. */
|
|
2445
|
-
note:
|
|
2521
|
+
note: z12.string().optional(),
|
|
2446
2522
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
2447
2523
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
2448
2524
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
2449
2525
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
2450
|
-
transcript:
|
|
2526
|
+
transcript: z12.string().optional(),
|
|
2451
2527
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
2452
2528
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
2453
|
-
covered:
|
|
2529
|
+
covered: z12.array(z12.string()).optional(),
|
|
2454
2530
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
2455
2531
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
2456
2532
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
2457
2533
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
2458
2534
|
* dropped. Absent/empty = nothing owed. */
|
|
2459
|
-
also:
|
|
2535
|
+
also: z12.array(RideAlongSchema).optional()
|
|
2460
2536
|
}),
|
|
2461
|
-
|
|
2462
|
-
type:
|
|
2463
|
-
parentId:
|
|
2464
|
-
notificationId:
|
|
2465
|
-
remindAt:
|
|
2537
|
+
z12.object({
|
|
2538
|
+
type: z12.literal("remind"),
|
|
2539
|
+
parentId: z12.string(),
|
|
2540
|
+
notificationId: z12.string(),
|
|
2541
|
+
remindAt: z12.string().datetime({ offset: true }),
|
|
2466
2542
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
2467
|
-
remindInSeconds:
|
|
2543
|
+
remindInSeconds: z12.number()
|
|
2468
2544
|
}),
|
|
2469
2545
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2470
2546
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2471
2547
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2472
2548
|
* re-orient via contact({})). */
|
|
2473
|
-
|
|
2474
|
-
type:
|
|
2475
|
-
parentId:
|
|
2476
|
-
notificationId:
|
|
2549
|
+
z12.object({
|
|
2550
|
+
type: z12.literal("superseded"),
|
|
2551
|
+
parentId: z12.string(),
|
|
2552
|
+
notificationId: z12.string()
|
|
2477
2553
|
}),
|
|
2478
2554
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2479
2555
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2480
2556
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2481
2557
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2482
2558
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2483
|
-
|
|
2484
|
-
type:
|
|
2485
|
-
notificationId:
|
|
2486
|
-
inFlight:
|
|
2487
|
-
turn:
|
|
2488
|
-
idx:
|
|
2489
|
-
prompt:
|
|
2490
|
-
reply:
|
|
2491
|
-
acts:
|
|
2559
|
+
z12.object({
|
|
2560
|
+
type: z12.literal("partial"),
|
|
2561
|
+
notificationId: z12.string(),
|
|
2562
|
+
inFlight: z12.literal(true),
|
|
2563
|
+
turn: z12.object({
|
|
2564
|
+
idx: z12.number(),
|
|
2565
|
+
prompt: z12.string(),
|
|
2566
|
+
reply: z12.string(),
|
|
2567
|
+
acts: z12.array(IntentSchema).nullable().optional()
|
|
2492
2568
|
})
|
|
2493
2569
|
}),
|
|
2494
|
-
|
|
2495
|
-
type:
|
|
2496
|
-
also:
|
|
2570
|
+
z12.object({
|
|
2571
|
+
type: z12.literal("idle"),
|
|
2572
|
+
also: z12.array(RideAlongSchema).optional(),
|
|
2497
2573
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
2498
2574
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
2499
2575
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
2500
2576
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
2501
2577
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
2502
|
-
inFlight:
|
|
2578
|
+
inFlight: z12.boolean().optional()
|
|
2503
2579
|
})
|
|
2504
2580
|
]);
|
|
2505
|
-
var VoiceKeySchema =
|
|
2506
|
-
var AgendaTurnSchema =
|
|
2581
|
+
var VoiceKeySchema = z12.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2582
|
+
var AgendaTurnSchema = z12.object({
|
|
2507
2583
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
2508
2584
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
2509
2585
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
2510
2586
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
2511
2587
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
2512
2588
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
2513
|
-
id:
|
|
2589
|
+
id: z12.string().optional(),
|
|
2514
2590
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
2515
2591
|
* the planner declares duplicates instead of asking them twice. */
|
|
2516
|
-
coveredIds:
|
|
2592
|
+
coveredIds: z12.array(z12.string()).optional(),
|
|
2517
2593
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
2518
2594
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
2519
|
-
info:
|
|
2520
|
-
question:
|
|
2595
|
+
info: z12.array(z12.string().min(1)).default([]),
|
|
2596
|
+
question: z12.string().min(1).nullable(),
|
|
2521
2597
|
/** True on the one turn carrying the agent's own declared question. */
|
|
2522
|
-
asks:
|
|
2598
|
+
asks: z12.boolean().optional(),
|
|
2523
2599
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
2524
2600
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
2525
2601
|
* which route nothing. */
|
|
2526
|
-
claimId:
|
|
2602
|
+
claimId: z12.string().optional(),
|
|
2527
2603
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
2528
|
-
voice:
|
|
2604
|
+
voice: z12.string().optional(),
|
|
2529
2605
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
2530
2606
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
2531
2607
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
2532
2608
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
2533
|
-
agent:
|
|
2609
|
+
agent: z12.string().optional(),
|
|
2534
2610
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
2535
2611
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
2536
2612
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
2537
2613
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
2538
2614
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
2539
|
-
agentId:
|
|
2615
|
+
agentId: z12.string().optional(),
|
|
2540
2616
|
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
2541
2617
|
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
2542
2618
|
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
2543
|
-
onTable:
|
|
2619
|
+
onTable: z12.boolean().optional(),
|
|
2544
2620
|
select: SelectShapeSchema.optional(),
|
|
2545
|
-
options:
|
|
2621
|
+
options: z12.array(OptionSchema.omit({ id: true })).optional(),
|
|
2546
2622
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
2547
2623
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
2548
2624
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -2551,33 +2627,33 @@ var AgendaTurnSchema = z9.object({
|
|
|
2551
2627
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
2552
2628
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
2553
2629
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2554
|
-
blocking:
|
|
2630
|
+
blocking: z12.boolean().optional(),
|
|
2555
2631
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
2556
2632
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
2557
2633
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
2558
2634
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
2559
2635
|
* speak, it is dropped and their words are taken like any reply. */
|
|
2560
|
-
ifSilent:
|
|
2636
|
+
ifSilent: z12.boolean().optional()
|
|
2561
2637
|
});
|
|
2562
2638
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2563
|
-
var InboxItemSchema =
|
|
2564
|
-
id:
|
|
2565
|
-
tokenId:
|
|
2639
|
+
var InboxItemSchema = z12.object({
|
|
2640
|
+
id: z12.string(),
|
|
2641
|
+
tokenId: z12.string().optional(),
|
|
2566
2642
|
status: NotifyStatusSchema,
|
|
2567
2643
|
context: ContextSchema,
|
|
2568
|
-
options:
|
|
2644
|
+
options: z12.array(OptionSchema).optional(),
|
|
2569
2645
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2570
|
-
points:
|
|
2646
|
+
points: z12.array(z12.string()).optional(),
|
|
2571
2647
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
2572
2648
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2573
2649
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2574
2650
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2575
|
-
asks:
|
|
2651
|
+
asks: z12.boolean().optional(),
|
|
2576
2652
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2577
2653
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2578
2654
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2579
2655
|
* which must never CLAIM stalled. */
|
|
2580
|
-
lastSeenAt:
|
|
2656
|
+
lastSeenAt: z12.string().optional(),
|
|
2581
2657
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2582
2658
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2583
2659
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2587,7 +2663,7 @@ var InboxItemSchema = z9.object({
|
|
|
2587
2663
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2588
2664
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2589
2665
|
* `createdAt`. */
|
|
2590
|
-
agentStateAt:
|
|
2666
|
+
agentStateAt: z12.string().datetime().optional(),
|
|
2591
2667
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
2592
2668
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
2593
2669
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -2601,61 +2677,61 @@ var InboxItemSchema = z9.object({
|
|
|
2601
2677
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2602
2678
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2603
2679
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2604
|
-
questions:
|
|
2605
|
-
id:
|
|
2606
|
-
entryId:
|
|
2607
|
-
title:
|
|
2608
|
-
state:
|
|
2609
|
-
answer:
|
|
2680
|
+
questions: z12.array(z12.object({
|
|
2681
|
+
id: z12.string(),
|
|
2682
|
+
entryId: z12.string(),
|
|
2683
|
+
title: z12.string(),
|
|
2684
|
+
state: z12.enum(["open", "answered"]),
|
|
2685
|
+
answer: z12.string().nullable(),
|
|
2610
2686
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2611
2687
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2612
2688
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2613
2689
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2614
|
-
agent:
|
|
2690
|
+
agent: z12.object({ tokenId: z12.string(), name: z12.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
2615
2691
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
2616
2692
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
2617
2693
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
2618
2694
|
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
2619
|
-
options:
|
|
2695
|
+
options: z12.array(OptionSchema).optional(),
|
|
2620
2696
|
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
2621
2697
|
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
2622
2698
|
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
2623
2699
|
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
2624
2700
|
* preview layout for any `options`, and must see exactly what it did. */
|
|
2625
|
-
choices:
|
|
2701
|
+
choices: z12.array(OptionSchema).optional(),
|
|
2626
2702
|
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
2627
|
-
select:
|
|
2703
|
+
select: z12.enum(["one", "many", "rank"]).optional()
|
|
2628
2704
|
})).optional(),
|
|
2629
|
-
visuals:
|
|
2705
|
+
visuals: z12.array(VisualSchema).optional(),
|
|
2630
2706
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2631
2707
|
* agent's suggestion, or a default silly name). */
|
|
2632
|
-
name:
|
|
2708
|
+
name: z12.string(),
|
|
2633
2709
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2634
2710
|
voice: VoiceKeySchema.optional(),
|
|
2635
|
-
repo:
|
|
2636
|
-
branch:
|
|
2637
|
-
createdAt:
|
|
2638
|
-
snoozedUntil:
|
|
2711
|
+
repo: z12.string().optional(),
|
|
2712
|
+
branch: z12.string().optional(),
|
|
2713
|
+
createdAt: z12.string().datetime(),
|
|
2714
|
+
snoozedUntil: z12.string().datetime().optional(),
|
|
2639
2715
|
agentState: AgentStateSchema.default("idle"),
|
|
2640
2716
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2641
2717
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2642
2718
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2643
|
-
turn:
|
|
2719
|
+
turn: z12.enum(["you", "agent"]).default("you"),
|
|
2644
2720
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2645
2721
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2646
2722
|
* error badge + Retry. */
|
|
2647
|
-
error:
|
|
2723
|
+
error: z12.string().optional(),
|
|
2648
2724
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
2649
2725
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2650
2726
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2651
2727
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2652
|
-
cold:
|
|
2653
|
-
clarifies:
|
|
2728
|
+
cold: z12.string().datetime().optional(),
|
|
2729
|
+
clarifies: z12.string().optional(),
|
|
2654
2730
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2655
2731
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2656
2732
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2657
2733
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2658
|
-
onCall:
|
|
2734
|
+
onCall: z12.literal(true).optional(),
|
|
2659
2735
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2660
2736
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2661
2737
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2665,31 +2741,31 @@ var InboxItemSchema = z9.object({
|
|
|
2665
2741
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2666
2742
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2667
2743
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2668
|
-
ring:
|
|
2669
|
-
declinedAt:
|
|
2670
|
-
anchorAt:
|
|
2671
|
-
nextRingAt:
|
|
2672
|
-
step:
|
|
2744
|
+
ring: z12.object({
|
|
2745
|
+
declinedAt: z12.string().datetime(),
|
|
2746
|
+
anchorAt: z12.string().datetime(),
|
|
2747
|
+
nextRingAt: z12.string().datetime().nullable(),
|
|
2748
|
+
step: z12.number().int()
|
|
2673
2749
|
}).optional(),
|
|
2674
2750
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
2675
2751
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
2676
2752
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
2677
2753
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
2678
2754
|
* feedback ABOUT. */
|
|
2679
|
-
why:
|
|
2755
|
+
why: z12.object({
|
|
2680
2756
|
asked: NotifyLevelSchema,
|
|
2681
2757
|
got: NotifyLevelSchema,
|
|
2682
|
-
because:
|
|
2683
|
-
line:
|
|
2758
|
+
because: z12.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2759
|
+
line: z12.string()
|
|
2684
2760
|
}).optional(),
|
|
2685
|
-
select:
|
|
2686
|
-
confirmStyle:
|
|
2761
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2762
|
+
confirmStyle: z12.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2687
2763
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2688
2764
|
),
|
|
2689
2765
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
2690
2766
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
2691
2767
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
2692
|
-
blocking:
|
|
2768
|
+
blocking: z12.boolean().default(false),
|
|
2693
2769
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
2694
2770
|
answer: UserAnswerSchema.optional(),
|
|
2695
2771
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -2697,17 +2773,17 @@ var InboxItemSchema = z9.object({
|
|
|
2697
2773
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
2698
2774
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
2699
2775
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
2700
|
-
communication:
|
|
2701
|
-
deliveryId:
|
|
2702
|
-
kind:
|
|
2703
|
-
entryId:
|
|
2704
|
-
goalIds:
|
|
2705
|
-
decisionNeedId:
|
|
2706
|
-
sealed:
|
|
2707
|
-
goalState:
|
|
2776
|
+
communication: z12.object({
|
|
2777
|
+
deliveryId: z12.string(),
|
|
2778
|
+
kind: z12.enum(["notification", "call"]),
|
|
2779
|
+
entryId: z12.string(),
|
|
2780
|
+
goalIds: z12.array(z12.string()),
|
|
2781
|
+
decisionNeedId: z12.string().optional(),
|
|
2782
|
+
sealed: z12.boolean(),
|
|
2783
|
+
goalState: z12.string().optional(),
|
|
2708
2784
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2709
2785
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2710
|
-
goalTitle:
|
|
2786
|
+
goalTitle: z12.string().optional()
|
|
2711
2787
|
}).optional(),
|
|
2712
2788
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
2713
2789
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -2719,27 +2795,27 @@ var InboxItemSchema = z9.object({
|
|
|
2719
2795
|
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
2720
2796
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
2721
2797
|
* with no rev is simply always re-sent. */
|
|
2722
|
-
rev:
|
|
2798
|
+
rev: z12.string().optional()
|
|
2723
2799
|
});
|
|
2724
2800
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
2725
|
-
var PushTokenSchema =
|
|
2726
|
-
voipToken:
|
|
2727
|
-
alertToken:
|
|
2728
|
-
fcmToken:
|
|
2729
|
-
platform:
|
|
2801
|
+
var PushTokenSchema = z12.object({
|
|
2802
|
+
voipToken: z12.string().min(1).optional(),
|
|
2803
|
+
alertToken: z12.string().min(1).optional(),
|
|
2804
|
+
fcmToken: z12.string().min(1).optional(),
|
|
2805
|
+
platform: z12.enum(["ios", "android"])
|
|
2730
2806
|
}).superRefine((v, ctx) => {
|
|
2731
2807
|
if (v.platform !== "ios") return;
|
|
2732
2808
|
for (const field of ["voipToken", "alertToken"]) {
|
|
2733
2809
|
const token = v[field];
|
|
2734
2810
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
2735
2811
|
ctx.addIssue({
|
|
2736
|
-
code:
|
|
2812
|
+
code: z12.ZodIssueCode.custom,
|
|
2737
2813
|
path: [field],
|
|
2738
2814
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
2739
2815
|
});
|
|
2740
2816
|
}
|
|
2741
2817
|
});
|
|
2742
|
-
var MissedCallSchema =
|
|
2818
|
+
var MissedCallSchema = z12.enum([
|
|
2743
2819
|
"retry_10m",
|
|
2744
2820
|
"retry_30m",
|
|
2745
2821
|
"retry_60m",
|
|
@@ -2749,33 +2825,33 @@ var MissedCallSchema = z9.enum([
|
|
|
2749
2825
|
"inbox",
|
|
2750
2826
|
"dismiss"
|
|
2751
2827
|
]);
|
|
2752
|
-
var
|
|
2753
|
-
var QUIET = ` Nothing rings from ${
|
|
2754
|
-
var BrokerTuningSchema =
|
|
2828
|
+
var clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
2829
|
+
var QUIET = ` Nothing rings from ${clock2(NIGHT.from)} to ${clock2(NIGHT.to)} your time; the count waits for morning.`;
|
|
2830
|
+
var BrokerTuningSchema = z12.object({
|
|
2755
2831
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
2756
|
-
ackVerbosity:
|
|
2832
|
+
ackVerbosity: z12.enum(["normal", "none"]).optional(),
|
|
2757
2833
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
2758
2834
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
2759
|
-
clarifyEagerness:
|
|
2835
|
+
clarifyEagerness: z12.enum(["low", "normal", "high"]).optional(),
|
|
2760
2836
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
2761
|
-
phrasebook:
|
|
2837
|
+
phrasebook: z12.array(z12.object({ say: z12.string().min(1).max(60), mean: z12.string().min(1).max(120) })).max(24).optional(),
|
|
2762
2838
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
2763
2839
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
2764
2840
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
2765
2841
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
2766
2842
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
2767
|
-
language:
|
|
2843
|
+
language: z12.enum(["en", "es"]).optional()
|
|
2768
2844
|
});
|
|
2769
|
-
var UserSettingsSchema =
|
|
2770
|
-
permissions:
|
|
2771
|
-
call:
|
|
2772
|
-
banner:
|
|
2773
|
-
push:
|
|
2845
|
+
var UserSettingsSchema = z12.object({
|
|
2846
|
+
permissions: z12.object({
|
|
2847
|
+
call: z12.boolean(),
|
|
2848
|
+
banner: z12.boolean(),
|
|
2849
|
+
push: z12.boolean()
|
|
2774
2850
|
}),
|
|
2775
2851
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
2776
2852
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
2777
2853
|
* what people read (owner, 2026-09-30). */
|
|
2778
|
-
sessionMode:
|
|
2854
|
+
sessionMode: z12.enum(["default", "all_calls", "silent"]),
|
|
2779
2855
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
2780
2856
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
2781
2857
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -2784,7 +2860,7 @@ var UserSettingsSchema = z9.object({
|
|
|
2784
2860
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
2785
2861
|
* next dead field for the same reason. */
|
|
2786
2862
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
2787
|
-
improveConsent:
|
|
2863
|
+
improveConsent: z12.boolean(),
|
|
2788
2864
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
2789
2865
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
2790
2866
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -2792,17 +2868,17 @@ var UserSettingsSchema = z9.object({
|
|
|
2792
2868
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
2793
2869
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
2794
2870
|
* write, 'hosted' on read (see store.ts). */
|
|
2795
|
-
voiceMode:
|
|
2871
|
+
voiceMode: z12.enum(["hosted", "on_device"]).optional(),
|
|
2796
2872
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
2797
2873
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
2798
2874
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
2799
2875
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2800
2876
|
* read (see store.ts). */
|
|
2801
|
-
talk:
|
|
2877
|
+
talk: z12.boolean().optional(),
|
|
2802
2878
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
2803
2879
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
2804
2880
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
2805
|
-
callDiagnostics:
|
|
2881
|
+
callDiagnostics: z12.boolean().optional(),
|
|
2806
2882
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2807
2883
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2808
2884
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2810,7 +2886,7 @@ var UserSettingsSchema = z9.object({
|
|
|
2810
2886
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2811
2887
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2812
2888
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2813
|
-
voiceTuning:
|
|
2889
|
+
voiceTuning: z12.record(z12.string(), z12.union([z12.number(), z12.string()])).optional(),
|
|
2814
2890
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2815
2891
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2816
2892
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2819,53 +2895,53 @@ var UserSettingsSchema = z9.object({
|
|
|
2819
2895
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2820
2896
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2821
2897
|
* call — honest about what we know. */
|
|
2822
|
-
timezone:
|
|
2898
|
+
timezone: z12.string().min(1).max(64).optional(),
|
|
2823
2899
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2824
2900
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2825
2901
|
broker: BrokerTuningSchema.optional()
|
|
2826
2902
|
});
|
|
2827
|
-
var HistoryWorkSchema =
|
|
2828
|
-
id:
|
|
2829
|
-
title:
|
|
2830
|
-
state:
|
|
2903
|
+
var HistoryWorkSchema = z12.object({
|
|
2904
|
+
id: z12.string(),
|
|
2905
|
+
title: z12.string(),
|
|
2906
|
+
state: z12.enum(["done", "cancelled"]),
|
|
2831
2907
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
2832
|
-
assignee:
|
|
2908
|
+
assignee: z12.string()
|
|
2833
2909
|
});
|
|
2834
|
-
var HistoryEntrySchema =
|
|
2835
|
-
|
|
2836
|
-
|
|
2910
|
+
var HistoryEntrySchema = z12.union([
|
|
2911
|
+
z12.object({ at: z12.string(), card: InboxItemSchema }),
|
|
2912
|
+
z12.object({ at: z12.string(), work: HistoryWorkSchema })
|
|
2837
2913
|
]);
|
|
2838
|
-
var HistoryPageSchema =
|
|
2839
|
-
entries:
|
|
2840
|
-
next:
|
|
2914
|
+
var HistoryPageSchema = z12.object({
|
|
2915
|
+
entries: z12.array(HistoryEntrySchema),
|
|
2916
|
+
next: z12.string().nullable()
|
|
2841
2917
|
});
|
|
2842
2918
|
var ACTIVITY_LINES = 2;
|
|
2843
2919
|
var ACTIVITY_LINE_MAX = 80;
|
|
2844
|
-
var AgentActivitySchema =
|
|
2920
|
+
var AgentActivitySchema = z12.object({
|
|
2845
2921
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
2846
|
-
lines:
|
|
2922
|
+
lines: z12.array(z12.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
2847
2923
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
2848
2924
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
2849
|
-
at:
|
|
2925
|
+
at: z12.string().datetime()
|
|
2850
2926
|
});
|
|
2851
|
-
var ConnectionSummarySchema =
|
|
2927
|
+
var ConnectionSummarySchema = z12.object({
|
|
2852
2928
|
/** The connection = the agent's token id (used to address a request). */
|
|
2853
|
-
id:
|
|
2929
|
+
id: z12.string(),
|
|
2854
2930
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
2855
2931
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
2856
2932
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
2857
2933
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
2858
|
-
kind:
|
|
2934
|
+
kind: z12.enum(["device", "agent"]).optional(),
|
|
2859
2935
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
2860
2936
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
2861
2937
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
2862
|
-
mintedByDevice:
|
|
2863
|
-
device:
|
|
2938
|
+
mintedByDevice: z12.string().nullable().optional(),
|
|
2939
|
+
device: z12.string().nullable(),
|
|
2864
2940
|
/** The agent's display name (the single pairing name). */
|
|
2865
|
-
name:
|
|
2941
|
+
name: z12.string(),
|
|
2866
2942
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
2867
2943
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
2868
|
-
provider:
|
|
2944
|
+
provider: z12.string().nullable(),
|
|
2869
2945
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
2870
2946
|
voice: VoiceKeySchema.nullable(),
|
|
2871
2947
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -2876,34 +2952,34 @@ var ConnectionSummarySchema = z9.object({
|
|
|
2876
2952
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
2877
2953
|
* set once must not overrule a rule they set about one agent. */
|
|
2878
2954
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
2879
|
-
createdAt:
|
|
2955
|
+
createdAt: z12.string().datetime(),
|
|
2880
2956
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
2881
2957
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
2882
|
-
lastContactAt:
|
|
2958
|
+
lastContactAt: z12.string().datetime().nullable(),
|
|
2883
2959
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
2884
2960
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
2885
|
-
lastSeenAt:
|
|
2961
|
+
lastSeenAt: z12.string().datetime().nullable().optional(),
|
|
2886
2962
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
2887
2963
|
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
2888
2964
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
2889
|
-
lastWorkedAt:
|
|
2965
|
+
lastWorkedAt: z12.string().datetime().nullable().optional(),
|
|
2890
2966
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
2891
2967
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
2892
|
-
oldestReadyAt:
|
|
2968
|
+
oldestReadyAt: z12.string().datetime().nullable().optional(),
|
|
2893
2969
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
2894
2970
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
2895
2971
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
2896
|
-
runtime:
|
|
2972
|
+
runtime: z12.object({
|
|
2897
2973
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
2898
2974
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
2899
|
-
version:
|
|
2900
|
-
harnesses:
|
|
2901
|
-
workspaces:
|
|
2975
|
+
version: z12.string().optional(),
|
|
2976
|
+
harnesses: z12.array(z12.object({ name: z12.string(), label: z12.string(), status: z12.string() })).optional(),
|
|
2977
|
+
workspaces: z12.array(z12.string()).optional(),
|
|
2902
2978
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
2903
2979
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
2904
2980
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
2905
2981
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
2906
|
-
repos:
|
|
2982
|
+
repos: z12.array(z12.object({ path: z12.string(), remote: z12.string() })).optional()
|
|
2907
2983
|
}).optional(),
|
|
2908
2984
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
2909
2985
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -2912,160 +2988,166 @@ var ConnectionSummarySchema = z9.object({
|
|
|
2912
2988
|
activity: AgentActivitySchema.optional(),
|
|
2913
2989
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
2914
2990
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
2915
|
-
managed:
|
|
2991
|
+
managed: z12.boolean()
|
|
2916
2992
|
});
|
|
2917
|
-
var LedgerItemSchema =
|
|
2918
|
-
var AgentLedgerSchema =
|
|
2993
|
+
var LedgerItemSchema = z12.object({ id: z12.string(), parentId: z12.string(), title: z12.string(), createdAt: z12.string() });
|
|
2994
|
+
var AgentLedgerSchema = z12.object({
|
|
2919
2995
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
2920
|
-
agent:
|
|
2996
|
+
agent: z12.object({ id: z12.string(), name: z12.string().nullable(), revokedAt: z12.string().nullable() }),
|
|
2921
2997
|
/** Its own questions you have not answered. */
|
|
2922
|
-
asks:
|
|
2998
|
+
asks: z12.array(LedgerItemSchema),
|
|
2923
2999
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
2924
|
-
answered:
|
|
3000
|
+
answered: z12.array(LedgerItemSchema),
|
|
2925
3001
|
/** Requests you sent it that it never took. */
|
|
2926
|
-
requests:
|
|
2927
|
-
goals:
|
|
2928
|
-
callbacks:
|
|
3002
|
+
requests: z12.array(LedgerItemSchema),
|
|
3003
|
+
goals: z12.array(z12.object({ id: z12.string(), outcome: z12.string(), state: z12.string() })),
|
|
3004
|
+
callbacks: z12.array(z12.object({ id: z12.string(), parentId: z12.string(), trigger: z12.string(), note: z12.string(), dueAt: z12.string().nullable() }))
|
|
2929
3005
|
});
|
|
2930
|
-
var ReassignResultSchema =
|
|
2931
|
-
moved:
|
|
2932
|
-
parentId:
|
|
3006
|
+
var ReassignResultSchema = z12.object({
|
|
3007
|
+
moved: z12.object({ asks: z12.number(), answered: z12.number(), requests: z12.number(), goals: z12.number(), callbacks: z12.number() }),
|
|
3008
|
+
parentId: z12.string().nullable()
|
|
2933
3009
|
});
|
|
2934
|
-
var LessonStateSchema =
|
|
2935
|
-
var LessonViewSchema =
|
|
2936
|
-
id:
|
|
2937
|
-
text:
|
|
3010
|
+
var LessonStateSchema = z12.enum(["active", "proposed", "retired"]);
|
|
3011
|
+
var LessonViewSchema = z12.object({
|
|
3012
|
+
id: z12.string(),
|
|
3013
|
+
text: z12.string(),
|
|
2938
3014
|
state: LessonStateSchema,
|
|
2939
3015
|
/** The Goal it is scoped to; null = the whole account. */
|
|
2940
|
-
scopeGoalId:
|
|
2941
|
-
goalTitle:
|
|
2942
|
-
version:
|
|
2943
|
-
pinned:
|
|
3016
|
+
scopeGoalId: z12.string().nullable(),
|
|
3017
|
+
goalTitle: z12.string().nullable(),
|
|
3018
|
+
version: z12.number(),
|
|
3019
|
+
pinned: z12.boolean(),
|
|
2944
3020
|
/** When the person last wrote its text themselves. */
|
|
2945
|
-
editedAt:
|
|
2946
|
-
createdAt:
|
|
2947
|
-
updatedAt:
|
|
3021
|
+
editedAt: z12.string().nullable(),
|
|
3022
|
+
createdAt: z12.string(),
|
|
3023
|
+
updatedAt: z12.string(),
|
|
2948
3024
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
2949
|
-
sources:
|
|
3025
|
+
sources: z12.array(z12.object({ entryId: z12.string(), words: z12.string().nullable(), at: z12.string() }))
|
|
2950
3026
|
});
|
|
2951
|
-
var QueueQuestionSchema =
|
|
3027
|
+
var QueueQuestionSchema = z12.object({
|
|
2952
3028
|
/** The decision need's id — what an answer is accepted against. */
|
|
2953
|
-
id:
|
|
3029
|
+
id: z12.string(),
|
|
2954
3030
|
/** The words that were asked, from the request Entry that asked them. */
|
|
2955
|
-
question:
|
|
3031
|
+
question: z12.string(),
|
|
2956
3032
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
2957
3033
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
2958
3034
|
* can answer. */
|
|
2959
|
-
deliveryId:
|
|
3035
|
+
deliveryId: z12.string().nullable().default(null),
|
|
2960
3036
|
/** The Entry the ruling is about. */
|
|
2961
|
-
aboutId:
|
|
3037
|
+
aboutId: z12.string().nullable().default(null),
|
|
2962
3038
|
/** Empty for a free-text question. */
|
|
2963
|
-
options:
|
|
2964
|
-
select:
|
|
2965
|
-
askedAt:
|
|
3039
|
+
options: z12.array(OptionSchema).default([]),
|
|
3040
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
3041
|
+
askedAt: z12.string(),
|
|
2966
3042
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
2967
|
-
answeredAt:
|
|
3043
|
+
answeredAt: z12.string().nullable().default(null),
|
|
2968
3044
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
2969
3045
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
2970
3046
|
* for a settled question whose reply carried nothing readable. */
|
|
2971
|
-
answer:
|
|
3047
|
+
answer: z12.string().nullable().default(null),
|
|
2972
3048
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
2973
3049
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
2974
3050
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
2975
3051
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
2976
3052
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
2977
3053
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
2978
|
-
goalId:
|
|
3054
|
+
goalId: z12.string(),
|
|
2979
3055
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
2980
3056
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
2981
3057
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
2982
3058
|
* gate at all. */
|
|
2983
|
-
blocked:
|
|
3059
|
+
blocked: z12.boolean().default(false)
|
|
2984
3060
|
});
|
|
2985
|
-
var QueueReplySchema =
|
|
3061
|
+
var QueueReplySchema = z12.object({
|
|
2986
3062
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
2987
3063
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
2988
3064
|
* walk can name it in its zoom. */
|
|
2989
|
-
id:
|
|
3065
|
+
id: z12.string(),
|
|
2990
3066
|
/** The Goal the note is on. */
|
|
2991
|
-
goalId:
|
|
3067
|
+
goalId: z12.string(),
|
|
2992
3068
|
/** What the note said. */
|
|
2993
|
-
note:
|
|
3069
|
+
note: z12.string(),
|
|
2994
3070
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
2995
|
-
deliveryId:
|
|
2996
|
-
requestEntryId:
|
|
2997
|
-
askedAt:
|
|
3071
|
+
deliveryId: z12.string(),
|
|
3072
|
+
requestEntryId: z12.string(),
|
|
3073
|
+
askedAt: z12.string(),
|
|
2998
3074
|
/** When the person last replied — the window's start. */
|
|
2999
|
-
repliedAt:
|
|
3075
|
+
repliedAt: z12.string(),
|
|
3000
3076
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
3001
|
-
reply:
|
|
3077
|
+
reply: z12.string().nullable()
|
|
3002
3078
|
});
|
|
3003
|
-
var QueueItemSchema =
|
|
3004
|
-
id:
|
|
3079
|
+
var QueueItemSchema = z12.object({
|
|
3080
|
+
id: z12.string(),
|
|
3005
3081
|
/** One-line headline — the first sentence of the outcome. */
|
|
3006
|
-
title:
|
|
3082
|
+
title: z12.string(),
|
|
3007
3083
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
3008
|
-
intent:
|
|
3084
|
+
intent: z12.string(),
|
|
3009
3085
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
3010
|
-
state:
|
|
3086
|
+
state: z12.string(),
|
|
3011
3087
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
3012
|
-
assignee:
|
|
3088
|
+
assignee: z12.string().nullable().default(null),
|
|
3013
3089
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
3014
|
-
progress:
|
|
3090
|
+
progress: z12.string().nullable().default(null),
|
|
3015
3091
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
3016
3092
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
3017
|
-
progressLine:
|
|
3018
|
-
reviewPending:
|
|
3019
|
-
dueAt:
|
|
3093
|
+
progressLine: z12.string().nullable().optional(),
|
|
3094
|
+
reviewPending: z12.boolean().default(false),
|
|
3095
|
+
dueAt: z12.string().nullable().default(null),
|
|
3020
3096
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
3021
3097
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
3022
|
-
finishedAt:
|
|
3098
|
+
finishedAt: z12.string().nullable().optional(),
|
|
3023
3099
|
/** The Goal this one was opened under; null at the root. */
|
|
3024
|
-
parentGoalId:
|
|
3100
|
+
parentGoalId: z12.string().nullable().default(null),
|
|
3025
3101
|
/** Goals opened under this one — only those the same list holds. */
|
|
3026
|
-
childGoalIds:
|
|
3102
|
+
childGoalIds: z12.array(z12.string()).default([]),
|
|
3027
3103
|
/** Goals this one waits on (start or finish gates). */
|
|
3028
|
-
dependencyGoalIds:
|
|
3104
|
+
dependencyGoalIds: z12.array(z12.string()).default([]),
|
|
3029
3105
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
3030
|
-
blocked:
|
|
3106
|
+
blocked: z12.boolean().default(false),
|
|
3031
3107
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
3032
3108
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
3033
3109
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
3034
3110
|
* question is opened. */
|
|
3035
|
-
questions:
|
|
3111
|
+
questions: z12.array(QueueQuestionSchema).default([]),
|
|
3036
3112
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
3037
3113
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
3038
3114
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
3039
3115
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
3040
|
-
asked:
|
|
3041
|
-
answered:
|
|
3116
|
+
asked: z12.number().optional(),
|
|
3117
|
+
answered: z12.number().optional(),
|
|
3042
3118
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
3043
3119
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
3044
3120
|
* not spell an empty list. */
|
|
3045
|
-
replies:
|
|
3121
|
+
replies: z12.array(QueueReplySchema).optional(),
|
|
3046
3122
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
3047
|
-
repo:
|
|
3048
|
-
createdAt:
|
|
3049
|
-
updatedAt:
|
|
3123
|
+
repo: z12.string().nullable().optional(),
|
|
3124
|
+
createdAt: z12.string(),
|
|
3125
|
+
updatedAt: z12.string().nullable().default(null),
|
|
3050
3126
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
3051
3127
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
3052
3128
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
3053
|
-
lastProgressAt:
|
|
3129
|
+
lastProgressAt: z12.string().nullable().optional(),
|
|
3054
3130
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
3055
3131
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
3056
3132
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
3057
3133
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
3058
3134
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
3059
3135
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
3060
|
-
latest:
|
|
3061
|
-
from:
|
|
3062
|
-
said:
|
|
3063
|
-
at:
|
|
3064
|
-
entryId:
|
|
3136
|
+
latest: z12.object({
|
|
3137
|
+
from: z12.enum(["person", "agent"]),
|
|
3138
|
+
said: z12.string(),
|
|
3139
|
+
at: z12.string(),
|
|
3140
|
+
entryId: z12.string(),
|
|
3065
3141
|
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
3066
3142
|
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
3067
3143
|
* none was. The Work row reads it before `said`. */
|
|
3068
|
-
summary:
|
|
3144
|
+
summary: z12.string().optional(),
|
|
3145
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
3146
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
3147
|
+
titles: z12.array(z12.string()).optional(),
|
|
3148
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
3149
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
3150
|
+
line: z12.string().optional()
|
|
3069
3151
|
}).nullable().optional(),
|
|
3070
3152
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
3071
3153
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -3078,7 +3160,7 @@ var QueueItemSchema = z9.object({
|
|
|
3078
3160
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
3079
3161
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
3080
3162
|
* Entry with no `about_id` is in neither. */
|
|
3081
|
-
lastPersonAt:
|
|
3163
|
+
lastPersonAt: z12.string().nullable().optional(),
|
|
3082
3164
|
/** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
|
|
3083
3165
|
* by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
|
|
3084
3166
|
* incremental read knows a row has not moved: the phone echoes back the revs it holds
|
|
@@ -3089,40 +3171,40 @@ var QueueItemSchema = z9.object({
|
|
|
3089
3171
|
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
3090
3172
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
3091
3173
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
3092
|
-
rev:
|
|
3174
|
+
rev: z12.string().optional()
|
|
3093
3175
|
});
|
|
3094
|
-
var QueueDeltaSchema =
|
|
3095
|
-
ids:
|
|
3096
|
-
items:
|
|
3176
|
+
var QueueDeltaSchema = z12.object({
|
|
3177
|
+
ids: z12.array(z12.string()),
|
|
3178
|
+
items: z12.array(QueueItemSchema)
|
|
3097
3179
|
});
|
|
3098
3180
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
3099
|
-
var NoteSourceSchema =
|
|
3100
|
-
var NoteStatusSchema =
|
|
3101
|
-
var NoteRepeatSchema =
|
|
3102
|
-
var DecisionSchema =
|
|
3103
|
-
id:
|
|
3181
|
+
var NoteSourceSchema = z12.enum(["app", "call"]);
|
|
3182
|
+
var NoteStatusSchema = z12.enum(["open", "assigned", "in_progress", "done"]);
|
|
3183
|
+
var NoteRepeatSchema = z12.enum(["once", "until_done"]);
|
|
3184
|
+
var DecisionSchema = z12.object({
|
|
3185
|
+
id: z12.string(),
|
|
3104
3186
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
3105
3187
|
* extensibility seam — any conversation can accrue decisions). */
|
|
3106
|
-
noteId:
|
|
3188
|
+
noteId: z12.string().nullable(),
|
|
3107
3189
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
3108
|
-
question:
|
|
3190
|
+
question: z12.string(),
|
|
3109
3191
|
/** The user's ruling; null while the question is open. */
|
|
3110
|
-
answer:
|
|
3111
|
-
decidedAt:
|
|
3112
|
-
createdAt:
|
|
3192
|
+
answer: z12.string().nullable(),
|
|
3193
|
+
decidedAt: z12.string().nullable(),
|
|
3194
|
+
createdAt: z12.string()
|
|
3113
3195
|
});
|
|
3114
|
-
var NoteSchema =
|
|
3115
|
-
id:
|
|
3196
|
+
var NoteSchema = z12.object({
|
|
3197
|
+
id: z12.string(),
|
|
3116
3198
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
3117
|
-
title:
|
|
3199
|
+
title: z12.string(),
|
|
3118
3200
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
3119
|
-
intent:
|
|
3201
|
+
intent: z12.string(),
|
|
3120
3202
|
source: NoteSourceSchema,
|
|
3121
3203
|
status: NoteStatusSchema,
|
|
3122
3204
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
3123
|
-
assignee:
|
|
3205
|
+
assignee: z12.string().nullable(),
|
|
3124
3206
|
/** The request thread minted at assignment; null until assigned. */
|
|
3125
|
-
parentId:
|
|
3207
|
+
parentId: z12.string().nullable(),
|
|
3126
3208
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3127
3209
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
3128
3210
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -3131,154 +3213,154 @@ var NoteSchema = z9.object({
|
|
|
3131
3213
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3132
3214
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
3133
3215
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
3134
|
-
dueAt:
|
|
3216
|
+
dueAt: z12.string().nullable().default(null),
|
|
3135
3217
|
repeat: NoteRepeatSchema.default("once"),
|
|
3136
3218
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
3137
|
-
rides:
|
|
3138
|
-
lastRideAt:
|
|
3139
|
-
createdAt:
|
|
3219
|
+
rides: z12.number().int().default(0),
|
|
3220
|
+
lastRideAt: z12.string().nullable().default(null),
|
|
3221
|
+
createdAt: z12.string()
|
|
3140
3222
|
});
|
|
3141
|
-
var TriageItemSchema =
|
|
3142
|
-
noteId:
|
|
3223
|
+
var TriageItemSchema = z12.object({
|
|
3224
|
+
noteId: z12.string(),
|
|
3143
3225
|
/** The note's headline at run time. */
|
|
3144
|
-
title:
|
|
3226
|
+
title: z12.string(),
|
|
3145
3227
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
3146
3228
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
3147
3229
|
* Never a model's reasoning transcript, never an id. */
|
|
3148
|
-
why:
|
|
3230
|
+
why: z12.string()
|
|
3149
3231
|
});
|
|
3150
|
-
var TriageAssignmentSchema =
|
|
3232
|
+
var TriageAssignmentSchema = z12.object({
|
|
3151
3233
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
3152
|
-
agent:
|
|
3234
|
+
agent: z12.string(),
|
|
3153
3235
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
3154
3236
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
3155
|
-
agentName:
|
|
3156
|
-
notes:
|
|
3237
|
+
agentName: z12.string(),
|
|
3238
|
+
notes: z12.array(TriageItemSchema)
|
|
3157
3239
|
});
|
|
3158
|
-
var TriageStatusSchema =
|
|
3159
|
-
var SubmitTriageSchema =
|
|
3240
|
+
var TriageStatusSchema = z12.enum(["open", "superseded", "dismissed"]);
|
|
3241
|
+
var SubmitTriageSchema = z12.object({
|
|
3160
3242
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
3161
3243
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
3162
3244
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
3163
3245
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
3164
|
-
provider:
|
|
3246
|
+
provider: z12.string().min(1).max(60),
|
|
3165
3247
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
3166
|
-
model:
|
|
3248
|
+
model: z12.string().max(200).nullable().optional(),
|
|
3167
3249
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
3168
3250
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
3169
|
-
reviewed:
|
|
3170
|
-
close:
|
|
3171
|
-
stale:
|
|
3172
|
-
assign:
|
|
3251
|
+
reviewed: z12.number().int().min(0).max(1e4).default(0),
|
|
3252
|
+
close: z12.array(TriageItemSchema).max(200).default([]),
|
|
3253
|
+
stale: z12.array(TriageItemSchema).max(200).default([]),
|
|
3254
|
+
assign: z12.array(TriageAssignmentSchema).max(50).default([])
|
|
3173
3255
|
});
|
|
3174
3256
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
3175
|
-
id:
|
|
3176
|
-
runAt:
|
|
3257
|
+
id: z12.string(),
|
|
3258
|
+
runAt: z12.string(),
|
|
3177
3259
|
status: TriageStatusSchema,
|
|
3178
|
-
model:
|
|
3260
|
+
model: z12.string().nullable().default(null)
|
|
3179
3261
|
});
|
|
3180
|
-
var AcceptTriageSchema =
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
group:
|
|
3185
|
-
agent:
|
|
3186
|
-
noteIds:
|
|
3262
|
+
var AcceptTriageSchema = z12.discriminatedUnion("group", [
|
|
3263
|
+
z12.object({ group: z12.literal("close"), noteIds: z12.array(z12.string()).max(200).optional() }),
|
|
3264
|
+
z12.object({ group: z12.literal("stale"), noteIds: z12.array(z12.string()).max(200).optional() }),
|
|
3265
|
+
z12.object({
|
|
3266
|
+
group: z12.literal("assign"),
|
|
3267
|
+
agent: z12.string().min(1),
|
|
3268
|
+
noteIds: z12.array(z12.string()).max(200).optional()
|
|
3187
3269
|
})
|
|
3188
3270
|
]);
|
|
3189
|
-
var AcceptTriageResultSchema =
|
|
3190
|
-
accepted:
|
|
3191
|
-
failed:
|
|
3271
|
+
var AcceptTriageResultSchema = z12.object({
|
|
3272
|
+
accepted: z12.array(z12.string()),
|
|
3273
|
+
failed: z12.array(z12.object({ noteId: z12.string(), reason: z12.string() }))
|
|
3192
3274
|
});
|
|
3193
|
-
var DeliveryModeSchema =
|
|
3275
|
+
var DeliveryModeSchema = z12.enum(["poll", "self_hosted"]);
|
|
3194
3276
|
var WAKE_EVENT = "wake";
|
|
3195
3277
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
3196
|
-
var RegisterDeliverySchema =
|
|
3197
|
-
var OAuthStartSchema =
|
|
3198
|
-
provider:
|
|
3199
|
-
returnTo:
|
|
3278
|
+
var RegisterDeliverySchema = z12.object({ mode: DeliveryModeSchema });
|
|
3279
|
+
var OAuthStartSchema = z12.object({
|
|
3280
|
+
provider: z12.enum(["cma"]),
|
|
3281
|
+
returnTo: z12.string().min(1)
|
|
3200
3282
|
});
|
|
3201
|
-
var DeliveryConfigSchema =
|
|
3202
|
-
tokenId:
|
|
3283
|
+
var DeliveryConfigSchema = z12.object({
|
|
3284
|
+
tokenId: z12.string(),
|
|
3203
3285
|
mode: DeliveryModeSchema,
|
|
3204
3286
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
3205
3287
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
3206
3288
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
3207
|
-
realtime:
|
|
3289
|
+
realtime: z12.object({ url: z12.string(), anonKey: z12.string() }).nullable()
|
|
3208
3290
|
});
|
|
3209
|
-
var HostDecisionSchema =
|
|
3291
|
+
var HostDecisionSchema = z12.object({
|
|
3210
3292
|
/** The agent's token id: the row's `recipient`. */
|
|
3211
|
-
agent:
|
|
3212
|
-
decision:
|
|
3293
|
+
agent: z12.string().uuid(),
|
|
3294
|
+
decision: z12.enum(["stood_back", "took_over"]),
|
|
3213
3295
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
3214
|
-
goalId:
|
|
3296
|
+
goalId: z12.string().uuid().nullable().optional(),
|
|
3215
3297
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
3216
|
-
seenAt:
|
|
3298
|
+
seenAt: z12.string().datetime().nullable().optional(),
|
|
3217
3299
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
3218
|
-
since:
|
|
3300
|
+
since: z12.string().datetime().nullable().optional(),
|
|
3219
3301
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
3220
|
-
said:
|
|
3302
|
+
said: z12.string().max(300).optional()
|
|
3221
3303
|
});
|
|
3222
|
-
var WakeNudgeSchema =
|
|
3223
|
-
kind:
|
|
3224
|
-
notificationId:
|
|
3225
|
-
parentId:
|
|
3304
|
+
var WakeNudgeSchema = z12.object({
|
|
3305
|
+
kind: z12.enum(["reply", "request", "callback"]),
|
|
3306
|
+
notificationId: z12.string().optional(),
|
|
3307
|
+
parentId: z12.string()
|
|
3226
3308
|
});
|
|
3227
|
-
var PairingStatusSchema =
|
|
3228
|
-
var DeviceCodeSchema =
|
|
3229
|
-
device_code:
|
|
3230
|
-
user_code:
|
|
3231
|
-
verification_uri:
|
|
3232
|
-
verification_uri_complete:
|
|
3233
|
-
interval:
|
|
3234
|
-
expires_in:
|
|
3309
|
+
var PairingStatusSchema = z12.enum(["pending", "approved", "denied", "expired"]);
|
|
3310
|
+
var DeviceCodeSchema = z12.object({
|
|
3311
|
+
device_code: z12.string(),
|
|
3312
|
+
user_code: z12.string(),
|
|
3313
|
+
verification_uri: z12.string().url(),
|
|
3314
|
+
verification_uri_complete: z12.string().url(),
|
|
3315
|
+
interval: z12.number(),
|
|
3316
|
+
expires_in: z12.number()
|
|
3235
3317
|
});
|
|
3236
|
-
var DeviceInfoSchema =
|
|
3237
|
-
code:
|
|
3318
|
+
var DeviceInfoSchema = z12.object({
|
|
3319
|
+
code: z12.string(),
|
|
3238
3320
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3239
3321
|
* pre-filling the name field the human can edit. */
|
|
3240
|
-
name:
|
|
3322
|
+
name: z12.string(),
|
|
3241
3323
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3242
3324
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3243
3325
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3244
3326
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3245
|
-
agent:
|
|
3246
|
-
device:
|
|
3327
|
+
agent: z12.string().optional(),
|
|
3328
|
+
device: z12.string().nullable(),
|
|
3247
3329
|
status: PairingStatusSchema
|
|
3248
3330
|
});
|
|
3249
|
-
var DeviceTokenSchema =
|
|
3250
|
-
access_token:
|
|
3331
|
+
var DeviceTokenSchema = z12.object({
|
|
3332
|
+
access_token: z12.string(),
|
|
3251
3333
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3252
3334
|
* a default silly name). */
|
|
3253
|
-
name:
|
|
3254
|
-
device:
|
|
3335
|
+
name: z12.string(),
|
|
3336
|
+
device: z12.string().nullable(),
|
|
3255
3337
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3256
3338
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3257
|
-
voice:
|
|
3339
|
+
voice: z12.string().nullable().optional(),
|
|
3258
3340
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3259
3341
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3260
|
-
token_id:
|
|
3342
|
+
token_id: z12.string().nullable().optional(),
|
|
3261
3343
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3262
3344
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3263
3345
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3264
3346
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3265
|
-
workspace:
|
|
3347
|
+
workspace: z12.string().nullable().optional(),
|
|
3266
3348
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
3267
|
-
harness:
|
|
3268
|
-
session_id:
|
|
3349
|
+
harness: z12.enum(["claude", "codex", "agy"]).optional(),
|
|
3350
|
+
session_id: z12.string().uuid().optional(),
|
|
3269
3351
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
3270
3352
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
3271
3353
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
3272
3354
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
3273
|
-
full_session:
|
|
3274
|
-
uik_pub:
|
|
3355
|
+
full_session: z12.string().optional(),
|
|
3356
|
+
uik_pub: z12.string().nullable().optional()
|
|
3275
3357
|
});
|
|
3276
|
-
var SupportRequestSchema =
|
|
3277
|
-
email:
|
|
3278
|
-
message:
|
|
3279
|
-
name:
|
|
3358
|
+
var SupportRequestSchema = z12.object({
|
|
3359
|
+
email: z12.string().email().max(320),
|
|
3360
|
+
message: z12.string().trim().min(1).max(5e3),
|
|
3361
|
+
name: z12.string().trim().max(120).optional()
|
|
3280
3362
|
});
|
|
3281
|
-
var NotificationFeedbackKindSchema =
|
|
3363
|
+
var NotificationFeedbackKindSchema = z12.enum([
|
|
3282
3364
|
"break_down",
|
|
3283
3365
|
// "This should be more than one ask — break it down."
|
|
3284
3366
|
"regenerate_options",
|
|
@@ -3293,119 +3375,121 @@ var NotificationFeedbackKindSchema = z9.enum([
|
|
|
3293
3375
|
// anything else — the note carries it.
|
|
3294
3376
|
]);
|
|
3295
3377
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
3296
|
-
var QuestionRowSchema =
|
|
3378
|
+
var QuestionRowSchema = z12.object({
|
|
3297
3379
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
3298
|
-
id:
|
|
3299
|
-
deliveryId:
|
|
3300
|
-
entryId:
|
|
3380
|
+
id: z12.string(),
|
|
3381
|
+
deliveryId: z12.string(),
|
|
3382
|
+
entryId: z12.string(),
|
|
3301
3383
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
3302
|
-
needId:
|
|
3303
|
-
goalIds:
|
|
3384
|
+
needId: z12.string().nullable(),
|
|
3385
|
+
goalIds: z12.array(z12.string()),
|
|
3304
3386
|
/** The name of the work it is about, when the read could word it. */
|
|
3305
|
-
goalTitle:
|
|
3306
|
-
tokenId:
|
|
3307
|
-
name:
|
|
3308
|
-
title:
|
|
3309
|
-
body:
|
|
3310
|
-
select:
|
|
3311
|
-
options:
|
|
3312
|
-
hasPreview:
|
|
3313
|
-
blocking:
|
|
3314
|
-
askedAt:
|
|
3387
|
+
goalTitle: z12.string().optional(),
|
|
3388
|
+
tokenId: z12.string().optional(),
|
|
3389
|
+
name: z12.string(),
|
|
3390
|
+
title: z12.string(),
|
|
3391
|
+
body: z12.string(),
|
|
3392
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]),
|
|
3393
|
+
options: z12.array(SlimOptionSchema),
|
|
3394
|
+
hasPreview: z12.boolean(),
|
|
3395
|
+
blocking: z12.boolean(),
|
|
3396
|
+
askedAt: z12.string().datetime(),
|
|
3315
3397
|
ring: InboxItemSchema.shape.ring,
|
|
3316
|
-
onCall:
|
|
3317
|
-
sealed:
|
|
3398
|
+
onCall: z12.literal(true).optional(),
|
|
3399
|
+
sealed: z12.boolean()
|
|
3318
3400
|
});
|
|
3319
|
-
var WorkStateSchema =
|
|
3320
|
-
var WorkRowSchema =
|
|
3321
|
-
id:
|
|
3322
|
-
parentId:
|
|
3323
|
-
title:
|
|
3401
|
+
var WorkStateSchema = z12.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
3402
|
+
var WorkRowSchema = z12.object({
|
|
3403
|
+
id: z12.string(),
|
|
3404
|
+
parentId: z12.string().nullable(),
|
|
3405
|
+
title: z12.string(),
|
|
3324
3406
|
/** Straight off the Goal. */
|
|
3325
3407
|
state: WorkStateSchema,
|
|
3326
|
-
owner:
|
|
3327
|
-
revision:
|
|
3408
|
+
owner: z12.string().nullable(),
|
|
3409
|
+
revision: z12.number().int(),
|
|
3328
3410
|
/** Open questions on it, counted to 100. */
|
|
3329
|
-
waiting:
|
|
3411
|
+
waiting: z12.number().int(),
|
|
3330
3412
|
/** Held by a gate on work that is not done. */
|
|
3331
|
-
blocked:
|
|
3332
|
-
lastProgressAt:
|
|
3413
|
+
blocked: z12.boolean(),
|
|
3414
|
+
lastProgressAt: z12.string().datetime().nullable(),
|
|
3333
3415
|
/** The line written for its newest progress note, else that note's first words. */
|
|
3334
|
-
line:
|
|
3416
|
+
line: z12.string().nullable(),
|
|
3335
3417
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
3336
|
-
children:
|
|
3337
|
-
createdAt:
|
|
3338
|
-
updatedAt:
|
|
3418
|
+
children: z12.number().int(),
|
|
3419
|
+
createdAt: z12.string().datetime(),
|
|
3420
|
+
updatedAt: z12.string().datetime(),
|
|
3339
3421
|
/** When anything at or under it last moved — the order the list is in. */
|
|
3340
|
-
activeAt:
|
|
3422
|
+
activeAt: z12.string().datetime(),
|
|
3341
3423
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
3342
|
-
sealed:
|
|
3424
|
+
sealed: z12.boolean()
|
|
3343
3425
|
});
|
|
3344
3426
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
3345
3427
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
3346
3428
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
3347
|
-
asking:
|
|
3348
|
-
oldestAskAt:
|
|
3429
|
+
asking: z12.number().int().nullable(),
|
|
3430
|
+
oldestAskAt: z12.string().datetime().nullable(),
|
|
3349
3431
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
3350
3432
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
3351
3433
|
* (`agent_holds`); null when that read failed. */
|
|
3352
|
-
holds:
|
|
3353
|
-
held:
|
|
3434
|
+
holds: z12.array(z12.object({ id: z12.string(), title: z12.string() })).nullable(),
|
|
3435
|
+
held: z12.number().int().nullable(),
|
|
3354
3436
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
3355
3437
|
* while none has, or when that read failed. */
|
|
3356
|
-
cold:
|
|
3438
|
+
cold: z12.string().datetime().nullable(),
|
|
3357
3439
|
/** The newest line of its working log, and when the harness saw it. */
|
|
3358
|
-
line:
|
|
3359
|
-
lineAt:
|
|
3440
|
+
line: z12.string().nullable(),
|
|
3441
|
+
lineAt: z12.string().datetime().nullable()
|
|
3360
3442
|
});
|
|
3361
|
-
var SnapshotSchema =
|
|
3443
|
+
var SnapshotSchema = z12.object({
|
|
3362
3444
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
3363
|
-
at:
|
|
3364
|
-
questions:
|
|
3445
|
+
at: z12.string().datetime(),
|
|
3446
|
+
questions: z12.object({
|
|
3365
3447
|
/** The newest 30 open cards, questions before updates. */
|
|
3366
|
-
items:
|
|
3448
|
+
items: z12.array(QuestionRowSchema),
|
|
3367
3449
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
3368
|
-
total:
|
|
3369
|
-
updates:
|
|
3370
|
-
putOff:
|
|
3450
|
+
total: z12.number().int(),
|
|
3451
|
+
updates: z12.number().int(),
|
|
3452
|
+
putOff: z12.number().int()
|
|
3371
3453
|
}).nullable(),
|
|
3372
|
-
agents:
|
|
3454
|
+
agents: z12.object({
|
|
3373
3455
|
/** Up to 60, most recently seen first. */
|
|
3374
|
-
items:
|
|
3375
|
-
more:
|
|
3456
|
+
items: z12.array(AgentRowSchema),
|
|
3457
|
+
more: z12.boolean()
|
|
3376
3458
|
}).nullable(),
|
|
3377
|
-
work:
|
|
3459
|
+
work: z12.object({
|
|
3378
3460
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
3379
|
-
items:
|
|
3461
|
+
items: z12.array(WorkRowSchema),
|
|
3380
3462
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
3381
3463
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
3382
3464
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
3383
3465
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
3384
|
-
counts:
|
|
3466
|
+
counts: z12.object({ live: z12.number().int(), waiting: z12.number().int(), notStarted: z12.number().int(), done: z12.number().int() })
|
|
3385
3467
|
}).nullable(),
|
|
3386
|
-
you:
|
|
3468
|
+
you: z12.object({
|
|
3387
3469
|
settings: UserSettingsSchema,
|
|
3388
|
-
callable:
|
|
3470
|
+
callable: z12.boolean(),
|
|
3389
3471
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
3390
|
-
computers:
|
|
3472
|
+
computers: z12.array(ComputerRowSchema).nullable()
|
|
3391
3473
|
}).nullable()
|
|
3392
3474
|
});
|
|
3393
|
-
var CallRecapSchema =
|
|
3394
|
-
call:
|
|
3395
|
-
status:
|
|
3396
|
-
startedAt:
|
|
3397
|
-
durationMs:
|
|
3398
|
-
|
|
3475
|
+
var CallRecapSchema = z12.object({
|
|
3476
|
+
call: z12.object({
|
|
3477
|
+
status: z12.string(),
|
|
3478
|
+
startedAt: z12.string(),
|
|
3479
|
+
durationMs: z12.number().nullable(),
|
|
3480
|
+
/** Bot-clock pickup offset; absent before the timestamp release, null before its report lands. */
|
|
3481
|
+
pickupMs: z12.number().nullable().optional(),
|
|
3482
|
+
agents: z12.array(z12.object({ id: z12.string(), name: z12.string().nullable() }))
|
|
3399
3483
|
}),
|
|
3400
|
-
topics:
|
|
3401
|
-
goalId:
|
|
3402
|
-
title:
|
|
3403
|
-
owner:
|
|
3404
|
-
state:
|
|
3484
|
+
topics: z12.array(z12.object({
|
|
3485
|
+
goalId: z12.string().uuid(),
|
|
3486
|
+
title: z12.string(),
|
|
3487
|
+
owner: z12.string(),
|
|
3488
|
+
state: z12.string(),
|
|
3405
3489
|
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
3406
3490
|
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
3407
|
-
presented:
|
|
3408
|
-
questions:
|
|
3491
|
+
presented: z12.boolean(),
|
|
3492
|
+
questions: z12.array(z12.object({ id: z12.string().uuid(), state: z12.string(), title: z12.string() })),
|
|
3409
3493
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
3410
3494
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
3411
3495
|
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
@@ -3415,31 +3499,33 @@ var CallRecapSchema = z9.object({
|
|
|
3415
3499
|
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
3416
3500
|
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
3417
3501
|
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
3418
|
-
lines:
|
|
3419
|
-
entryId:
|
|
3420
|
-
words:
|
|
3421
|
-
summary:
|
|
3422
|
-
about:
|
|
3423
|
-
reply:
|
|
3424
|
-
replyKind:
|
|
3425
|
-
at:
|
|
3502
|
+
lines: z12.array(z12.object({
|
|
3503
|
+
entryId: z12.string().uuid(),
|
|
3504
|
+
words: z12.string(),
|
|
3505
|
+
summary: z12.string().optional(),
|
|
3506
|
+
about: z12.string().nullable().optional(),
|
|
3507
|
+
reply: z12.string().nullable().optional(),
|
|
3508
|
+
replyKind: z12.string().nullable().optional(),
|
|
3509
|
+
at: z12.array(z12.number()).optional()
|
|
3426
3510
|
}))
|
|
3427
3511
|
})),
|
|
3428
|
-
unfiled:
|
|
3512
|
+
unfiled: z12.array(z12.object({ lineId: z12.string().uuid(), words: z12.string(), atMs: z12.number() })),
|
|
3429
3513
|
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
3430
3514
|
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
3431
3515
|
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
3432
3516
|
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
3433
3517
|
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
3434
3518
|
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
3435
|
-
said:
|
|
3436
|
-
lineId:
|
|
3437
|
-
speaker:
|
|
3438
|
-
agent:
|
|
3439
|
-
words:
|
|
3440
|
-
atMs:
|
|
3519
|
+
said: z12.array(z12.object({
|
|
3520
|
+
lineId: z12.string().uuid(),
|
|
3521
|
+
speaker: z12.enum(["user", "agent"]),
|
|
3522
|
+
agent: z12.object({ id: z12.string().nullable(), name: z12.string().nullable() }).nullable(),
|
|
3523
|
+
words: z12.string(),
|
|
3524
|
+
atMs: z12.number()
|
|
3441
3525
|
})),
|
|
3442
|
-
more:
|
|
3526
|
+
more: z12.object({ lines: z12.number(), entries: z12.number(), topics: z12.number() }),
|
|
3527
|
+
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
3528
|
+
next: z12.number().int().nonnegative().nullable().optional()
|
|
3443
3529
|
});
|
|
3444
3530
|
function sessionSlot(sessionId2) {
|
|
3445
3531
|
const id2 = sessionId2 ?? sessionId();
|
|
@@ -3617,15 +3703,15 @@ async function manageGoals(input, opts = {}) {
|
|
|
3617
3703
|
if (!res.ok) await fail("manage_goals", res);
|
|
3618
3704
|
return await res.json();
|
|
3619
3705
|
}
|
|
3620
|
-
async function
|
|
3706
|
+
async function getGoals(goalIds, opts = {}, read3 = {}) {
|
|
3621
3707
|
const token = authToken(opts.token) ?? "";
|
|
3622
|
-
const
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3708
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/read`, {
|
|
3709
|
+
method: "POST",
|
|
3710
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
3711
|
+
body: JSON.stringify({ goalIds, ...read3 })
|
|
3626
3712
|
}));
|
|
3627
|
-
if (!res.ok) await fail("
|
|
3628
|
-
return await res.json();
|
|
3713
|
+
if (!res.ok) await fail("get_goals", res);
|
|
3714
|
+
return (await res.json()).goals;
|
|
3629
3715
|
}
|
|
3630
3716
|
var AWAIT_WINDOW_MS = 45e3;
|
|
3631
3717
|
async function hatch(name, voice = null, opts = {}) {
|
|
@@ -3644,14 +3730,18 @@ async function hatch(name, voice = null, opts = {}) {
|
|
|
3644
3730
|
}
|
|
3645
3731
|
async function whoAmI(opts = {}) {
|
|
3646
3732
|
try {
|
|
3647
|
-
|
|
3648
|
-
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
3649
|
-
});
|
|
3650
|
-
return res.ok ? await res.json() : null;
|
|
3733
|
+
return await readIdentity(opts);
|
|
3651
3734
|
} catch {
|
|
3652
3735
|
return null;
|
|
3653
3736
|
}
|
|
3654
3737
|
}
|
|
3738
|
+
async function readIdentity(opts = {}) {
|
|
3739
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/identity`, {
|
|
3740
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
3741
|
+
}));
|
|
3742
|
+
if (!res.ok) await fail("read_identity", res);
|
|
3743
|
+
return await res.json();
|
|
3744
|
+
}
|
|
3655
3745
|
var NameTakenError = class extends Error {
|
|
3656
3746
|
code = "agent_name_taken";
|
|
3657
3747
|
};
|
|
@@ -3802,7 +3892,7 @@ async function checkActivity(opts = {}) {
|
|
|
3802
3892
|
if (!res.ok) await fail("check_activity", res);
|
|
3803
3893
|
return await res.json();
|
|
3804
3894
|
}
|
|
3805
|
-
var NOT_SENT = `Recorded as the Goal's progress, not sent: nobody was notified. 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,
|
|
3895
|
+
var NOT_SENT = `Recorded as the Goal's progress, not sent: nobody was notified. 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.`;
|
|
3806
3896
|
var APPLIED = "Applied; nothing was sent to the person.";
|
|
3807
3897
|
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.";
|
|
3808
3898
|
async function readDelivery(deliveryId, opts = {}) {
|
|
@@ -3827,6 +3917,7 @@ async function contact(input, opts = {}) {
|
|
|
3827
3917
|
body: JSON.stringify({
|
|
3828
3918
|
operationId: opts.operationId ?? randomUUID2(),
|
|
3829
3919
|
...parsed.updates ? { updates: parsed.updates } : {},
|
|
3920
|
+
...parsed.completions ? { completions: parsed.completions } : {},
|
|
3830
3921
|
...questions ? { questions } : {},
|
|
3831
3922
|
...parsed.answers ? { answers: parsed.answers } : {},
|
|
3832
3923
|
...parsed.withdrawQuestionIds ? { withdrawQuestionIds: parsed.withdrawQuestionIds } : {},
|
|
@@ -4001,11 +4092,11 @@ function conversation(e) {
|
|
|
4001
4092
|
});
|
|
4002
4093
|
}
|
|
4003
4094
|
function more(g) {
|
|
4004
|
-
const earlier = g.earlier && g.earlier.omitted + g.earlier.shortened ? `${[g.earlier.omitted ? `${g.earlier.omitted} earlier entries left out` : "", g.earlier.shortened ? `${g.earlier.shortened} shortened to their headline` : ""].filter(Boolean).join(", ")}:
|
|
4095
|
+
const earlier = g.earlier && g.earlier.omitted + g.earlier.shortened ? `${[g.earlier.omitted ? `${g.earlier.omitted} earlier entries left out` : "", g.earlier.shortened ? `${g.earlier.shortened} shortened to their headline` : ""].filter(Boolean).join(", ")}: get_goals({ goalIds: ["${g.goalId}"], history: true }) reads every one in full.` : null;
|
|
4005
4096
|
return [
|
|
4006
4097
|
earlier,
|
|
4007
|
-
g.subtree?.length ? "A Goal below, in full:
|
|
4008
|
-
g.parentGoalId ? `The Goal above:
|
|
4098
|
+
g.subtree?.length ? "A Goal below, in full: get_goals({ goalIds: [goalId] }) with its goalId above." : null,
|
|
4099
|
+
g.parentGoalId ? `The Goal above: get_goals({ goalIds: ["${g.parentGoalId}"] }).` : null,
|
|
4009
4100
|
"Anything else addressed to you (requests, answers, questions you owe): contact({ wait: false })."
|
|
4010
4101
|
].filter((line) => !!line);
|
|
4011
4102
|
}
|
|
@@ -4048,11 +4139,11 @@ function goalView(g) {
|
|
|
4048
4139
|
more: more(g)
|
|
4049
4140
|
});
|
|
4050
4141
|
}
|
|
4051
|
-
var JOINED_CALL = "This joined the call the person is ALREADY ON \u2014 no ring, no card of its own. `conversation` above is that whole call, so the answers already given on it before yours arrived are in it. Yours is queued for the bot's next turn: wait for the answer with contact({wait:true}) and read it on its Goal with
|
|
4142
|
+
var JOINED_CALL = "This joined the call the person is ALREADY ON \u2014 no ring, no card of its own. `conversation` above is that whole call, so the answers already given on it before yours arrived are in it. Yours is queued for the bot's next turn: wait for the answer with contact({wait:true}) and read it on its Goal with get_goals \u2014 do not resend it. To add information or a further question to the same call, contact again while it is live; that joins it too.";
|
|
4052
4143
|
function failures(results) {
|
|
4053
4144
|
const failed = (results ?? []).filter((r) => r.status === "failed");
|
|
4054
4145
|
if (!failed.length) return void 0;
|
|
4055
|
-
const plural = { update: "updates", question: "questions", answer: "answers", withdraw: "withdrawQuestionIds", dependency: "questionDependencies" };
|
|
4146
|
+
const plural = { update: "updates", completion: "completions", question: "questions", answer: "answers", withdraw: "withdrawQuestionIds", dependency: "questionDependencies" };
|
|
4056
4147
|
return `${failed.length} of ${(results ?? []).length} items did not apply; the rest did: ` + failed.map((r) => `${plural[r.kind]}[${r.index}] ${r.error}${r.detail ? ` (${r.detail})` : ""}`).join("; ") + ".";
|
|
4057
4148
|
}
|
|
4058
4149
|
var itemsView = (results) => (results ?? []).map((r) => compact({
|
|
@@ -4064,8 +4155,20 @@ var itemsView = (results) => (results ?? []).map((r) => compact({
|
|
|
4064
4155
|
error: r.error,
|
|
4065
4156
|
detail: r.detail
|
|
4066
4157
|
}));
|
|
4158
|
+
function deliveryReadView(d) {
|
|
4159
|
+
return compact({
|
|
4160
|
+
deliveryId: d.deliveryId,
|
|
4161
|
+
kind: d.kind,
|
|
4162
|
+
state: d.state,
|
|
4163
|
+
goalIds: d.goalIds,
|
|
4164
|
+
conversation: conversation(d),
|
|
4165
|
+
callState: d.callState,
|
|
4166
|
+
callOutcome: d.callOutcome ?? (d.kind === "call" ? d.message : void 0),
|
|
4167
|
+
next: d.callState && d.callState !== "ended" ? "Keep receiving with contact({wait:true}) while this Call is open, even after an empty window. Do not resend the ask." : "Answers and follow-ups are on their Goals; receive on your next wake. This read acknowledges nothing."
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4067
4170
|
function deliveryView(d) {
|
|
4068
|
-
const answers = "Answers land on the Goal: collect them with contact({wait:true}) or
|
|
4171
|
+
const answers = "Answers land on the Goal: collect them with contact({wait:true}) or get_goals. Do not resend it.";
|
|
4069
4172
|
const pending = d.state === "open" && d.decisionNeeds.some((n) => n.state === "open");
|
|
4070
4173
|
return compact({
|
|
4071
4174
|
ok: d.ok,
|
|
@@ -4086,11 +4189,12 @@ function deliveryView(d) {
|
|
|
4086
4189
|
// live (the person is on it now) or ended. A contact that joined a call already happening reads
|
|
4087
4190
|
// that call, so it says `live`.
|
|
4088
4191
|
call: d.callState,
|
|
4192
|
+
callOutcome: d.callOutcome,
|
|
4089
4193
|
waitOutcome: d.waitOutcome,
|
|
4090
4194
|
acknowledged: d.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
4091
4195
|
next: [
|
|
4092
4196
|
failures(d.results),
|
|
4093
|
-
d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : pending ? `${d.demoted ? `${d.demoted} ` : ""}Decision pending on this call. ${d.waitOutcome === "expired" ? "Nothing was said in the window. " : ""}Wait for the answer with contact({wait:true}) \u2014 one bounded window each time, and never resend the question. While the Call is open, keep waiting window after window: a quiet window is the call still going, and the person may give you follow-ups or instructions on it. Once the Call has ended, stop waiting, leave any question open, and collect the answer with contact({wait:false}) or
|
|
4197
|
+
d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? `${d.demoted ? `${d.demoted} ` : ""}${d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers}` : pending ? `${d.demoted ? `${d.demoted} ` : ""}Decision pending on this call. ${d.waitOutcome === "expired" ? "Nothing was said in the window. " : ""}Wait for the answer with contact({wait:true}) \u2014 one bounded window each time, and never resend the question. While the Call is open, keep waiting window after window: a quiet window is the call still going, and the person may give you follow-ups or instructions on it. Once the Call has ended, stop waiting, leave any question open, and collect the answer with contact({wait:false}) or get_goals on your next wake. ${d.message ?? ""}`.trim() : d.message
|
|
4094
4198
|
].filter(Boolean).join(" ")
|
|
4095
4199
|
});
|
|
4096
4200
|
}
|
|
@@ -4108,20 +4212,21 @@ function quietDays(items, now) {
|
|
|
4108
4212
|
const oldest = Math.min(...items.map((i) => Date.parse(i.since ?? "")).filter(Number.isFinite));
|
|
4109
4213
|
return Number.isFinite(oldest) ? Math.max(3, Math.round((now - oldest) / 864e5)) : 3;
|
|
4110
4214
|
}
|
|
4111
|
-
var quietFor = (items, now) => {
|
|
4215
|
+
var quietFor = (items, now, count = items.length) => {
|
|
4112
4216
|
const days = quietDays(items, now);
|
|
4113
|
-
return
|
|
4217
|
+
return count === 1 ? `for ${days} days` : `, the longest for ${days} days`;
|
|
4114
4218
|
};
|
|
4115
4219
|
function receivedView(r, now = Date.now()) {
|
|
4116
4220
|
const w = r.waiting;
|
|
4117
4221
|
const assigned = w.assigned ?? [];
|
|
4118
4222
|
const stalled = w.stalled ?? [];
|
|
4119
|
-
const others = w.
|
|
4223
|
+
const others = w.others;
|
|
4120
4224
|
const rings = w.rings ?? [];
|
|
4121
4225
|
const owed = r.events.filter((e) => e.kind === "question");
|
|
4122
4226
|
const refused = (r.acknowledged ?? []).filter((a) => a.refused);
|
|
4123
4227
|
return compact({
|
|
4124
4228
|
events: r.events.map((e) => compact({ ...e })),
|
|
4229
|
+
calls: w.calls,
|
|
4125
4230
|
hasMore: r.hasMore || void 0,
|
|
4126
4231
|
waitOutcome: r.waitOutcome,
|
|
4127
4232
|
acknowledged: r.acknowledged?.filter((a) => a.acknowledged).map((a) => a.eventId),
|
|
@@ -4131,9 +4236,10 @@ function receivedView(r, now = Date.now()) {
|
|
|
4131
4236
|
// THE WORK WAITING ON YOU NEXT (owner, 2026-09-27: "Wake on any work"): the predicate that starts a
|
|
4132
4237
|
// sleeping agent, so "nothing is waiting" is never said while work is.
|
|
4133
4238
|
claimable: w.claimable,
|
|
4134
|
-
// YOUR STALLED WORK (#2257), and OTHER AGENTS' (owner, 2026-
|
|
4239
|
+
// YOUR STALLED WORK (#2257), and HOW MUCH OF OTHER AGENTS' HAS GONE QUIET (owner, 2026-10-08:
|
|
4240
|
+
// "Keep a count and instructions on how to find them"), counted rather than listed.
|
|
4135
4241
|
stalled,
|
|
4136
|
-
|
|
4242
|
+
others,
|
|
4137
4243
|
// WHEN THE PHONE RINGS AGAIN FOR YOUR ASKS (owner, 2026-10-08): your Call went unheard and closed.
|
|
4138
4244
|
rings: rings.length ? rings : void 0,
|
|
4139
4245
|
// The person's standing rules for how their agents work (owner, 2026-10-08). On the read an agent makes
|
|
@@ -4141,33 +4247,47 @@ function receivedView(r, now = Date.now()) {
|
|
|
4141
4247
|
// held wait, which would repeat them window after window.
|
|
4142
4248
|
houseRules: r.waitOutcome === "not_waited" ? w.houseRules : void 0,
|
|
4143
4249
|
next: [
|
|
4144
|
-
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length || rings.length ? "No messages." : "Nothing is waiting.",
|
|
4250
|
+
r.events.length ? `Handle each event, then acknowledge the ones you handled: contact({ ackEventIds: [${r.events.filter((e) => e.kind !== "question").map((e) => `"${e.eventId}"`).join(", ")}] }).` : w.claimable || assigned.length || rings.length || w.calls?.some((c) => c.callState !== "ended") ? "No messages." : "Nothing is waiting.",
|
|
4145
4251
|
...owed.length ? [`You owe ${owed.length === 1 ? "an answer" : `${owed.length} answers`}: contact({ answers: [{ questionId: "<questionId>", answer: { text: <your answer> } }] }) for each question; a question is answered, never acknowledged.`] : [],
|
|
4146
4252
|
...r.hasMore ? ["More are waiting: acknowledge these, then receive again."] : [],
|
|
4147
4253
|
...r.waitOutcome === "expired" ? ["Nothing arrived in the window; receive again to keep waiting, without sending again."] : [],
|
|
4148
|
-
...
|
|
4149
|
-
...
|
|
4150
|
-
...
|
|
4254
|
+
...(w.calls ?? []).filter((c) => c.callState !== "ended").map((c) => `Read the whole ask with get_delivery({deliveryId: "${c.deliveryId}"}); follow its current calls[].callOutcome while receiving.`),
|
|
4255
|
+
...rings.map((r2) => rerings(r2, now)),
|
|
4256
|
+
...assigned.length ? [`${assigned.length} of your Goals are assigned to you and not started (assigned): read one with get_goals({goalIds:[goalId]}); your first write to it starts it.`] : [],
|
|
4257
|
+
...w.claimable && !assigned.length ? [`Next waiting on you: ${w.claimable.title ?? w.claimable.goalId} (get_goals({ goalIds: ["${w.claimable.goalId}"] })).`] : [],
|
|
4151
4258
|
...stalled.length ? [`${stalled.length} of your Goals have had no progress${stalled.length === 1 ? " " : ""}${quietFor(stalled, now)}: report on each with a contact update, or finish or cancel it with manage_goals.`] : [],
|
|
4152
|
-
|
|
4259
|
+
// The COUNT, oldest three, and where to read them (owner, 2026-10-08): it was 34 rows of
|
|
4260
|
+
// other agents' work on every window, ahead of this agent's own next step.
|
|
4261
|
+
...others ? [`${others.count} of other agents' Goals have gone quiet${others.count === 1 ? " " : ""}${quietFor([{ since: others.oldestSince }], now, others.count)}: The oldest three are in others.goals: get_goals({goalIds:[goalId]}) reads one in full. A write to it puts you on it without changing its owner.`] : [],
|
|
4153
4262
|
// No name of its own (#2525): the API's ask, verbatim.
|
|
4154
4263
|
...w.unnamed ? [w.unnamed] : []
|
|
4155
4264
|
].join(" ")
|
|
4156
4265
|
});
|
|
4157
4266
|
}
|
|
4158
|
-
function rerings(
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4267
|
+
function rerings(r, now) {
|
|
4268
|
+
const held = [r.held, r.said].filter(Boolean).join(" ");
|
|
4269
|
+
if (!r.ringAt) {
|
|
4270
|
+
return [
|
|
4271
|
+
held || "A Call of yours went unanswered.",
|
|
4272
|
+
"The ladder has no ring armed for its card: collect the answers on your next wake, and nothing needs resending."
|
|
4273
|
+
].join(" ");
|
|
4274
|
+
}
|
|
4275
|
+
const minutes = Math.max(0, Math.round((Date.parse(r.ringAt) - now) / 6e4));
|
|
4276
|
+
const when = `the phone rings again by itself at ${r.ringAt.slice(11, 16)} UTC (in ${minutes} min), carrying the same asks.`;
|
|
4277
|
+
return [
|
|
4278
|
+
held || "A Call of yours went unanswered;",
|
|
4279
|
+
held ? `Then ${when}` : when,
|
|
4280
|
+
"Nothing needs resending: schedule a wake-up for then and receive again."
|
|
4281
|
+
].join(" ");
|
|
4162
4282
|
}
|
|
4163
4283
|
function managedView(m, changes) {
|
|
4164
4284
|
const results = m.results ?? [];
|
|
4165
4285
|
const failed = results.filter((r) => r.status !== "applied");
|
|
4166
4286
|
return compact({
|
|
4167
4287
|
ok: m.ok,
|
|
4168
|
-
results: results.map((r) => compact({ index: r.index, kind: changes[r.index]?.kind, status: r.status, goalId: r.goalId, error: r.error })),
|
|
4288
|
+
results: results.map((r) => compact({ index: r.index, kind: changes[r.index]?.kind, status: r.status, goalId: r.goalId, error: r.error, completion: r.contact })),
|
|
4169
4289
|
goals: m.goals,
|
|
4170
|
-
next: failed.length ? `${failed.length} of ${results.length} changes did not apply (see results[].error); the rest did. A goal_revision_conflict means the Goal changed since you read it: read it with
|
|
4290
|
+
next: failed.length ? `${failed.length} of ${results.length} changes did not apply (see results[].error); the rest did. A goal_revision_conflict means the Goal changed since you read it: read it with get_goals and decide again.` : void 0
|
|
4171
4291
|
});
|
|
4172
4292
|
}
|
|
4173
4293
|
var lastRead = /* @__PURE__ */ new Map();
|
|
@@ -4185,6 +4305,9 @@ async function runTool(name, args, opts) {
|
|
|
4185
4305
|
const { waits, signal, ...client } = opts;
|
|
4186
4306
|
const input = args ?? {};
|
|
4187
4307
|
switch (name) {
|
|
4308
|
+
case "configure":
|
|
4309
|
+
ConfigureIdentitySchema.parse(input);
|
|
4310
|
+
return readIdentity(client);
|
|
4188
4311
|
case "contact": {
|
|
4189
4312
|
const parsed = ContactSchema.parse(input);
|
|
4190
4313
|
if (!sendsAnything(parsed)) return receivedView(await receiveContact(parsed, { ...client, signal, waits }));
|
|
@@ -4207,12 +4330,14 @@ async function runTool(name, args, opts) {
|
|
|
4207
4330
|
for (const g of managed.goals ?? []) remember(client.token, g.goalId, g.revision);
|
|
4208
4331
|
return managedView(managed, changes);
|
|
4209
4332
|
}
|
|
4210
|
-
case "
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
const
|
|
4214
|
-
const
|
|
4215
|
-
|
|
4333
|
+
case "get_delivery":
|
|
4334
|
+
return deliveryReadView(await readDelivery(GetDeliverySchema.parse(input).deliveryId, { ...client, signal }));
|
|
4335
|
+
case "get_goals": {
|
|
4336
|
+
const { goalIds, history } = GetGoalsSchema.parse(input);
|
|
4337
|
+
const rows = await getGoals(goalIds, client, { history });
|
|
4338
|
+
const goals = rows.map(goalView);
|
|
4339
|
+
for (const goal of rows) read2(client.token, goal);
|
|
4340
|
+
return { goals };
|
|
4216
4341
|
}
|
|
4217
4342
|
case "search":
|
|
4218
4343
|
return searchRecords(SearchToolSchema.parse(input), client);
|
|
@@ -4482,7 +4607,7 @@ export {
|
|
|
4482
4607
|
overrideToken,
|
|
4483
4608
|
authToken,
|
|
4484
4609
|
manageGoals,
|
|
4485
|
-
|
|
4610
|
+
getGoals,
|
|
4486
4611
|
AWAIT_WINDOW_MS,
|
|
4487
4612
|
hatch,
|
|
4488
4613
|
whoAmI,
|