@paigy/mcp 0.40.23 → 0.40.24
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 +29 -16
- package/dist/{chunk-HKODQKUV.js → chunk-J7T3EB5Y.js} +9 -9
- package/dist/chunk-RK5LT7NH.js +110 -0
- package/dist/{chunk-BNMMRKEY.js → chunk-TP7DM2MP.js} +1 -1
- package/dist/{chunk-JLRBZZNT.js → chunk-VUKR2E24.js} +676 -498
- package/dist/{chunk-PR7ROBN4.js → chunk-WR2M3RME.js} +720 -522
- package/dist/{dist-B7AOS5TG.js → dist-EKLTLANV.js} +7 -3
- package/dist/enable.js +3 -3
- package/dist/index.js +18 -7
- package/dist/listen.js +83 -15
- 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,13 +7,14 @@ import { homedir } from "os";
|
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9
9
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
10
|
-
import { z as
|
|
10
|
+
import { z as z5 } from "zod";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { z as z3 } from "zod";
|
|
13
13
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
14
14
|
import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
15
15
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
|
|
16
16
|
import { z as z2 } from "zod";
|
|
17
|
+
import { z as z4 } from "zod";
|
|
17
18
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
18
19
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
19
20
|
import { homedir as homedir2 } from "os";
|
|
@@ -22,7 +23,7 @@ import { execFileSync as execFileSync2 } from "child_process";
|
|
|
22
23
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
23
24
|
import { WebSocket } from "undici";
|
|
24
25
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
25
|
-
var CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID"];
|
|
26
|
+
var CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "CODEX_HOME", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID", "PAIGY_ON_WAKE"];
|
|
26
27
|
var BACKEND_URL = process.env.PAIGY_BACKEND_URL ?? "https://paigy.ai";
|
|
27
28
|
var NETWORK_MSG = `Can't reach ${BACKEND_URL} \u2014 the connection was blocked or dropped before an HTTP response. If this agent runs in a sandboxed environment with a network allowlist (e.g. Claude Code on the web, CI), ask the user to add the Paigy domain (paigy.ai) to the environment's allowed domains, then retry.`;
|
|
28
29
|
var PROXY_ENV = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
@@ -39,8 +40,8 @@ var WORKSPACE_ID = (() => {
|
|
|
39
40
|
return INSTANCE_ID;
|
|
40
41
|
}
|
|
41
42
|
})();
|
|
42
|
-
var envSession = (
|
|
43
|
-
const v = process.env[
|
|
43
|
+
var envSession = (key2) => {
|
|
44
|
+
const v = process.env[key2];
|
|
44
45
|
return v && v.trim() ? v : void 0;
|
|
45
46
|
};
|
|
46
47
|
function parentOf(pid) {
|
|
@@ -83,12 +84,17 @@ async function reach(url, init) {
|
|
|
83
84
|
function reachAs(session, instance) {
|
|
84
85
|
return (url, init) => send(url, init, session, instance);
|
|
85
86
|
}
|
|
87
|
+
var CLIENT;
|
|
88
|
+
function setClient(name) {
|
|
89
|
+
CLIENT = name;
|
|
90
|
+
}
|
|
86
91
|
async function send(url, init, session, instance = envSession("PAIGY_INSTANCE_ID") ?? INSTANCE_ID) {
|
|
87
92
|
try {
|
|
88
93
|
const headers = {
|
|
89
94
|
...init?.headers,
|
|
90
95
|
"x-paigy-instance": instance,
|
|
91
|
-
"x-paigy-session": session
|
|
96
|
+
"x-paigy-session": session,
|
|
97
|
+
...CLIENT ? { "x-paigy-client": CLIENT } : {}
|
|
92
98
|
};
|
|
93
99
|
return await fetch(url, { ...init, headers, dispatcher: await proxy() });
|
|
94
100
|
} catch (e) {
|
|
@@ -146,19 +152,19 @@ var getRefs = (options) => {
|
|
|
146
152
|
]))
|
|
147
153
|
};
|
|
148
154
|
};
|
|
149
|
-
function addErrorMessage(res,
|
|
155
|
+
function addErrorMessage(res, key2, errorMessage, refs) {
|
|
150
156
|
if (!refs?.errorMessages)
|
|
151
157
|
return;
|
|
152
158
|
if (errorMessage) {
|
|
153
159
|
res.errorMessage = {
|
|
154
160
|
...res.errorMessage,
|
|
155
|
-
[
|
|
161
|
+
[key2]: errorMessage
|
|
156
162
|
};
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
|
-
function setResponseValueAndErrors(res,
|
|
160
|
-
res[
|
|
161
|
-
addErrorMessage(res,
|
|
165
|
+
function setResponseValueAndErrors(res, key2, value, errorMessage, refs) {
|
|
166
|
+
res[key2] = value;
|
|
167
|
+
addErrorMessage(res, key2, errorMessage, refs);
|
|
162
168
|
}
|
|
163
169
|
var getRelativePath = (pathA, pathB) => {
|
|
164
170
|
let i = 0;
|
|
@@ -720,11 +726,11 @@ function parseRecordDef(def, refs) {
|
|
|
720
726
|
return {
|
|
721
727
|
type: "object",
|
|
722
728
|
required: def.keyType._def.values,
|
|
723
|
-
properties: def.keyType._def.values.reduce((acc,
|
|
729
|
+
properties: def.keyType._def.values.reduce((acc, key2) => ({
|
|
724
730
|
...acc,
|
|
725
|
-
[
|
|
731
|
+
[key2]: parseDef(def.valueType._def, {
|
|
726
732
|
...refs,
|
|
727
|
-
currentPath: [...refs.currentPath, "properties",
|
|
733
|
+
currentPath: [...refs.currentPath, "properties", key2]
|
|
728
734
|
}) ?? parseAnyDef(refs)
|
|
729
735
|
}), {}),
|
|
730
736
|
additionalProperties: refs.rejectedAdditionalProperties
|
|
@@ -787,10 +793,10 @@ function parseMapDef(def, refs) {
|
|
|
787
793
|
}
|
|
788
794
|
function parseNativeEnumDef(def) {
|
|
789
795
|
const object = def.values;
|
|
790
|
-
const actualKeys = Object.keys(def.values).filter((
|
|
791
|
-
return typeof object[object[
|
|
796
|
+
const actualKeys = Object.keys(def.values).filter((key2) => {
|
|
797
|
+
return typeof object[object[key2]] !== "number";
|
|
792
798
|
});
|
|
793
|
-
const actualValues = actualKeys.map((
|
|
799
|
+
const actualValues = actualKeys.map((key2) => object[key2]);
|
|
794
800
|
const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
|
|
795
801
|
return {
|
|
796
802
|
type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
|
|
@@ -1523,32 +1529,31 @@ function mcpInputSchema(s) {
|
|
|
1523
1529
|
}
|
|
1524
1530
|
var AskInputSchema = z2.object({
|
|
1525
1531
|
id: z2.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
|
|
1526
|
-
parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and
|
|
1532
|
+
parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and the question starts a new Goal of yours."),
|
|
1527
1533
|
repo: z2.string().optional().describe("Optional repository context."),
|
|
1528
1534
|
ask: z2.string().trim().min(1).max(1e4).describe(
|
|
1529
|
-
"ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy
|
|
1535
|
+
"ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy sends each ask exactly as you wrote it: a bundled ask arrives as one card. News, progress and findings are their own contact; ANY contact for a person already on a call JOINS that call, whatever channel you asked for, so several arrive as one call."
|
|
1530
1536
|
),
|
|
1531
1537
|
options: z2.array(OptionInputSchema).min(1).max(6).optional(),
|
|
1532
|
-
//
|
|
1533
|
-
//
|
|
1534
|
-
//
|
|
1535
|
-
//
|
|
1536
|
-
// shape (`goal/intake.ts` `answerOf`), and this reaches it as the agent's view, weighed, not obeyed.
|
|
1538
|
+
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
1539
|
+
// brain_prompts.md §1: structured agent questions need no model). Every card was pick-one while no
|
|
1540
|
+
// agent sent this; no read reshapes cards any more, so this is the card's shape, and the
|
|
1541
|
+
// description says so plainly.
|
|
1537
1542
|
select: z2.enum(["one", "many"]).optional().describe(
|
|
1538
|
-
|
|
1543
|
+
'How the options are answered: "many" makes a checklist (they may want several, e.g. independent fixes), "one" (the default) a pick (choosing one rules out the others). Send "many" whenever more than one option could be wanted at once. The person can always answer in their own words as well. Ignored without options.'
|
|
1539
1544
|
),
|
|
1540
1545
|
answers: z2.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/).optional().describe(
|
|
1541
|
-
"Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them.
|
|
1546
|
+
"Your reply answers a question the person asked you: the id the conversation shows for it (8 characters or whole). Their question closes with this reply as its answer, and any decision of yours it was holding back goes back to them. parentId is optional here: the question's own Goal is used. The reply is sent to them as it is; to also ask something new, send that as its own ask."
|
|
1542
1547
|
)
|
|
1543
1548
|
}).strict();
|
|
1544
1549
|
var StartContactSchema = z2.object({
|
|
1545
|
-
asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is;
|
|
1550
|
+
asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; an ask naming no Goal starts a new Goal of yours."),
|
|
1546
1551
|
waiting: z2.enum(["none", "hard"]).default("none").describe("hard requests a call even when channel is notification; user permissions and ring cooldowns still apply."),
|
|
1547
1552
|
channel: z2.enum(["notification", "call"]).default("notification")
|
|
1548
1553
|
}).strict();
|
|
1549
1554
|
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
|
|
1550
1555
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1551
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none
|
|
1556
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'select', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none starts a new Goal of yours. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. Each ask is sent exactly as written, with no reading in between: a bundled ask arrives as one card, so separate questions are separate asks. An ask with no options that is not blocking is a report, and a report is an UPDATE, never a claim on them: it reaches them and is listed apart from what waits on them, so no answer is owed and none should be awaited. On a Goal whose report card is still open it is added to that card, with no new push; Where your work stands while under way is progress: report it with update_goal, not contact. Send options (or waiting:'hard') when you actually need an answer. 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'). select:'many' makes the card a checklist, for independent options they may want several of; select:'one' (the default) a pick, for alternatives. The person can always answer in their own words, so never add an 'Other' option. If the person is already on a call, your contact joins that call automatically \u2014 whatever channel you asked for, with no ring \u2014 and the Delivery it returns IS that call: reread it with contact({deliveryId}) to see everything answered on it so far, and contact again while it is live to add information or a further question to the same call.";
|
|
1552
1557
|
var CreateGoalSchema = z3.object({
|
|
1553
1558
|
outcome: z3.string().trim().min(1).max(1e4),
|
|
1554
1559
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -1622,6 +1627,153 @@ var AGENT_TOOLS = [
|
|
|
1622
1627
|
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
1623
1628
|
];
|
|
1624
1629
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
1630
|
+
var id = z4.string().uuid();
|
|
1631
|
+
var key = z4.string().min(1).max(40);
|
|
1632
|
+
var participant = z4.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
|
|
1633
|
+
var SourceSchema = z4.object({ entryId: id }).strict();
|
|
1634
|
+
var ResultRefSchema = z4.union([z4.object({ id }).strict(), z4.object({ local: key }).strict()]);
|
|
1635
|
+
var NodeRefSchema = z4.object({ type: z4.enum(["goal", "question"]), id }).strict();
|
|
1636
|
+
var DependencySchema = z4.object({
|
|
1637
|
+
blocker: NodeRefSchema,
|
|
1638
|
+
blocked: NodeRefSchema,
|
|
1639
|
+
action: z4.enum(["start", "complete", "answer"]),
|
|
1640
|
+
reason: z4.string().max(500).optional()
|
|
1641
|
+
}).strict();
|
|
1642
|
+
var goalState = z4.enum(["open", "completed", "canceled"]);
|
|
1643
|
+
var existingGoalChanges = [
|
|
1644
|
+
z4.object({ kind: z4.literal("edit"), goalId: id, title: z4.string().min(1).max(120).optional(), outcome: z4.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
|
|
1645
|
+
z4.object({ kind: z4.literal("state"), goalId: id, state: goalState }).strict(),
|
|
1646
|
+
z4.object({ kind: z4.literal("assign"), goalId: id, ownerId: participant }).strict(),
|
|
1647
|
+
z4.object({ kind: z4.literal("defer"), goalId: id, until: z4.string().datetime().nullable() }).strict(),
|
|
1648
|
+
z4.object({ kind: z4.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
|
|
1649
|
+
z4.object({
|
|
1650
|
+
kind: z4.literal("dependency"),
|
|
1651
|
+
change: z4.enum(["add", "remove"]),
|
|
1652
|
+
goalId: id,
|
|
1653
|
+
dependsOnGoalId: id,
|
|
1654
|
+
action: z4.enum(["start", "complete"]),
|
|
1655
|
+
reason: z4.string().max(500).optional()
|
|
1656
|
+
}).strict()
|
|
1657
|
+
];
|
|
1658
|
+
var GoalChangeSchema = z4.union([
|
|
1659
|
+
z4.object({
|
|
1660
|
+
kind: z4.literal("create"),
|
|
1661
|
+
title: z4.string().min(1).max(120),
|
|
1662
|
+
outcome: z4.string().min(1),
|
|
1663
|
+
ownerId: participant,
|
|
1664
|
+
parentGoalId: id.optional(),
|
|
1665
|
+
sourceEntryIds: z4.array(id)
|
|
1666
|
+
}).strict(),
|
|
1667
|
+
...existingGoalChanges
|
|
1668
|
+
]);
|
|
1669
|
+
var BrainGoalChangeSchema = z4.union([
|
|
1670
|
+
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
1671
|
+
z4.object({
|
|
1672
|
+
kind: z4.literal("create"),
|
|
1673
|
+
outcome: z4.string().min(1),
|
|
1674
|
+
title: z4.string().min(1).max(120),
|
|
1675
|
+
ownerId: participant,
|
|
1676
|
+
parentGoal: ResultRefSchema.nullable(),
|
|
1677
|
+
newSession: z4.literal(true).optional()
|
|
1678
|
+
}).strict(),
|
|
1679
|
+
...existingGoalChanges
|
|
1680
|
+
]);
|
|
1681
|
+
var option = z4.object({ id: z4.string().min(1).max(40), label: z4.string().min(1), hint: z4.string().optional() }).strict();
|
|
1682
|
+
var QuestionChangeSchema = z4.union([
|
|
1683
|
+
z4.object({
|
|
1684
|
+
kind: z4.literal("create"),
|
|
1685
|
+
text: z4.string().min(1),
|
|
1686
|
+
answererId: participant,
|
|
1687
|
+
goal: ResultRefSchema.optional(),
|
|
1688
|
+
blocks: z4.array(z4.object({ question: ResultRefSchema }).strict()),
|
|
1689
|
+
options: z4.array(option).min(2).optional(),
|
|
1690
|
+
pickMode: z4.enum(["one", "many", "rank"]).optional()
|
|
1691
|
+
}).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
|
|
1692
|
+
z4.object({ kind: z4.literal("edit"), questionId: id, text: z4.string().min(1) }).strict(),
|
|
1693
|
+
z4.object({ kind: z4.literal("assign"), questionId: id, answererId: participant }).strict(),
|
|
1694
|
+
z4.object({ kind: z4.literal("withdraw"), questionId: id }).strict(),
|
|
1695
|
+
z4.object({ kind: z4.literal("dependency"), change: z4.enum(["add", "remove"]), edge: DependencySchema }).strict()
|
|
1696
|
+
]);
|
|
1697
|
+
var lessonScope = z4.union([
|
|
1698
|
+
z4.object({ kind: z4.literal("user") }).strict(),
|
|
1699
|
+
z4.object({ kind: z4.literal("goal"), goal: ResultRefSchema }).strict()
|
|
1700
|
+
]);
|
|
1701
|
+
var LessonChangeSchema = z4.union([
|
|
1702
|
+
z4.object({ kind: z4.literal("remember"), text: z4.string().min(1), scope: lessonScope }).strict(),
|
|
1703
|
+
z4.object({ kind: z4.literal("revise"), lessonId: id, text: z4.string().min(1), scope: lessonScope }).strict(),
|
|
1704
|
+
z4.object({ kind: z4.literal("withdraw"), lessonId: id }).strict()
|
|
1705
|
+
]);
|
|
1706
|
+
var BrainNextSchema = z4.object({
|
|
1707
|
+
instructions: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
|
|
1708
|
+
say: z4.array(key),
|
|
1709
|
+
then: z4.enum(["listen", "hold", "end", "none"]),
|
|
1710
|
+
waitFor: z4.array(ResultRefSchema),
|
|
1711
|
+
reason: z4.string()
|
|
1712
|
+
}).strict();
|
|
1713
|
+
var messageBase = {
|
|
1714
|
+
key,
|
|
1715
|
+
text: z4.string().min(1),
|
|
1716
|
+
goals: z4.array(ResultRefSchema),
|
|
1717
|
+
questions: z4.array(ResultRefSchema),
|
|
1718
|
+
entryKeys: z4.array(key),
|
|
1719
|
+
obligationIds: z4.array(id)
|
|
1720
|
+
};
|
|
1721
|
+
var BrainMessageSchema = z4.union([
|
|
1722
|
+
z4.object({ ...messageBase, to: z4.object({ kind: z4.literal("user") }).strict() }).strict(),
|
|
1723
|
+
z4.object({
|
|
1724
|
+
...messageBase,
|
|
1725
|
+
to: z4.object({ kind: z4.literal("agent"), agentId: z4.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
|
|
1726
|
+
call: z4.object({ callId: id, listen: z4.literal(true) }).strict().optional()
|
|
1727
|
+
}).strict()
|
|
1728
|
+
]);
|
|
1729
|
+
var BrainSearchSchema = z4.object({
|
|
1730
|
+
query: z4.string().min(1).max(500),
|
|
1731
|
+
within: z4.array(z4.enum(["entries", "goals", "answers"])).min(1),
|
|
1732
|
+
goalId: id.optional()
|
|
1733
|
+
}).strict();
|
|
1734
|
+
var BrainResultSchema = z4.object({
|
|
1735
|
+
messages: z4.array(BrainMessageSchema),
|
|
1736
|
+
entries: z4.array(z4.object({ key, sources: z4.array(SourceSchema).min(1), goals: z4.array(ResultRefSchema) }).strict()),
|
|
1737
|
+
questions: z4.array(z4.object({ key, entryKeys: z4.array(key), change: QuestionChangeSchema }).strict()),
|
|
1738
|
+
answers: z4.array(z4.object({
|
|
1739
|
+
question: ResultRefSchema,
|
|
1740
|
+
entryKeys: z4.array(key).min(1),
|
|
1741
|
+
summary: z4.string().min(1),
|
|
1742
|
+
selectedOptionIds: z4.array(z4.string().min(1)).optional()
|
|
1743
|
+
}).strict()),
|
|
1744
|
+
goals: z4.array(z4.object({ key, entryKeys: z4.array(key), change: BrainGoalChangeSchema }).strict()),
|
|
1745
|
+
feedback: z4.array(z4.object({ entryKeys: z4.array(key).min(1) }).strict()),
|
|
1746
|
+
lessons: z4.array(z4.object({ entryKeys: z4.array(key).min(1), change: LessonChangeSchema }).strict()),
|
|
1747
|
+
next: BrainNextSchema,
|
|
1748
|
+
search: BrainSearchSchema.optional()
|
|
1749
|
+
}).strict();
|
|
1750
|
+
var str = z4.string();
|
|
1751
|
+
var strs = z4.array(z4.string());
|
|
1752
|
+
var CompactResultSchema = z4.object({
|
|
1753
|
+
next: z4.object({ say: strs, then: z4.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
|
|
1754
|
+
messages: z4.array(z4.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z4.boolean() }).strict()),
|
|
1755
|
+
entries: z4.array(z4.object({ key: str, from: str, on: strs }).strict()),
|
|
1756
|
+
answers: z4.array(z4.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
|
|
1757
|
+
changes: z4.array(z4.object({
|
|
1758
|
+
key: str,
|
|
1759
|
+
what: z4.enum(["goal", "question", "lesson"]),
|
|
1760
|
+
op: z4.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
|
|
1761
|
+
id: str,
|
|
1762
|
+
text: str,
|
|
1763
|
+
title: str,
|
|
1764
|
+
who: str,
|
|
1765
|
+
under: str,
|
|
1766
|
+
gate: z4.enum(["start", "complete", "answer", "none"]),
|
|
1767
|
+
options: z4.array(z4.object({ id: str, label: str }).strict()),
|
|
1768
|
+
pick: z4.enum(["one", "many", "rank", "words"]),
|
|
1769
|
+
entries: strs
|
|
1770
|
+
}).strict()),
|
|
1771
|
+
feedback: z4.array(strs),
|
|
1772
|
+
search: z4.object({ query: str, within: strs, goal: str }).strict()
|
|
1773
|
+
}).strict();
|
|
1774
|
+
var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
1775
|
+
zodToJsonSchema(CompactResultSchema, { $refStrategy: "none" })
|
|
1776
|
+
);
|
|
1625
1777
|
function entryWords(entry) {
|
|
1626
1778
|
const content = entry.content;
|
|
1627
1779
|
if (content && "sealed" in content) return "";
|
|
@@ -1634,22 +1786,24 @@ function entryWords(entry) {
|
|
|
1634
1786
|
const description = plain.description;
|
|
1635
1787
|
const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
|
|
1636
1788
|
if (parts.length) return parts.join("\n\n");
|
|
1789
|
+
const line = plain.line;
|
|
1790
|
+
if (typeof line === "string" && line.trim()) return line;
|
|
1637
1791
|
}
|
|
1638
1792
|
return entry.sources.map((source) => source.text).join("\n");
|
|
1639
1793
|
}
|
|
1640
1794
|
var LIVE_MS = 3 * 6e4;
|
|
1641
1795
|
var WORKING_MS = 60 * 6e4;
|
|
1642
|
-
var ContextSchema =
|
|
1643
|
-
title:
|
|
1644
|
-
description:
|
|
1796
|
+
var ContextSchema = z5.object({
|
|
1797
|
+
title: z5.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
1798
|
+
description: z5.array(z5.string().min(1)).describe(
|
|
1645
1799
|
"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."
|
|
1646
1800
|
)
|
|
1647
1801
|
});
|
|
1648
|
-
var ParticipantSchema =
|
|
1649
|
-
kind:
|
|
1650
|
-
id:
|
|
1802
|
+
var ParticipantSchema = z5.object({
|
|
1803
|
+
kind: z5.enum(["human", "agent"]),
|
|
1804
|
+
id: z5.string()
|
|
1651
1805
|
});
|
|
1652
|
-
var TransformSchema =
|
|
1806
|
+
var TransformSchema = z5.enum([
|
|
1653
1807
|
"structure",
|
|
1654
1808
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
1655
1809
|
"request_more",
|
|
@@ -1672,13 +1826,13 @@ var PAIGY_SELF = { kind: "agent", id: "paigy" };
|
|
|
1672
1826
|
function isPaigy(ref) {
|
|
1673
1827
|
return ref === participantRef(PAIGY_SELF);
|
|
1674
1828
|
}
|
|
1675
|
-
var VisualSchema =
|
|
1676
|
-
url:
|
|
1677
|
-
label:
|
|
1829
|
+
var VisualSchema = z5.object({
|
|
1830
|
+
url: z5.string().url(),
|
|
1831
|
+
label: z5.string().optional()
|
|
1678
1832
|
});
|
|
1679
|
-
var NotifyLevelSchema =
|
|
1680
|
-
var SelectShapeSchema =
|
|
1681
|
-
var ReceiptEventSchema =
|
|
1833
|
+
var NotifyLevelSchema = z5.enum(["inbox", "push", "banner", "call"]);
|
|
1834
|
+
var SelectShapeSchema = z5.enum(["one", "many", "rank", "confirm", "text"]);
|
|
1835
|
+
var ReceiptEventSchema = z5.enum([
|
|
1682
1836
|
"delivered",
|
|
1683
1837
|
// the bundle reached the recipient at some level
|
|
1684
1838
|
"seen",
|
|
@@ -1708,47 +1862,47 @@ var ReceiptEventSchema = z4.enum([
|
|
|
1708
1862
|
// be rewound by a writer that forgot to advance it.
|
|
1709
1863
|
"restarted"
|
|
1710
1864
|
]);
|
|
1711
|
-
var AttentionSchema =
|
|
1865
|
+
var AttentionSchema = z5.object({
|
|
1712
1866
|
urgency: NotifyLevelSchema,
|
|
1713
1867
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
1714
1868
|
select: SelectShapeSchema.nullable(),
|
|
1715
1869
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
1716
|
-
points:
|
|
1870
|
+
points: z5.array(z5.string()).nullable(),
|
|
1717
1871
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
1718
|
-
blocking:
|
|
1872
|
+
blocking: z5.boolean(),
|
|
1719
1873
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
1720
1874
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
1721
|
-
deadline:
|
|
1875
|
+
deadline: z5.string().datetime().nullable().optional()
|
|
1722
1876
|
});
|
|
1723
|
-
var NotifyRequestFields =
|
|
1877
|
+
var NotifyRequestFields = z5.object({
|
|
1724
1878
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
1725
1879
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
1726
1880
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
1727
1881
|
context: ContextSchema.optional(),
|
|
1728
|
-
options:
|
|
1882
|
+
options: z5.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
1729
1883
|
"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)."
|
|
1730
1884
|
),
|
|
1731
|
-
points:
|
|
1885
|
+
points: z5.array(z5.string().min(1)).optional().describe(
|
|
1732
1886
|
"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."
|
|
1733
1887
|
),
|
|
1734
|
-
visuals:
|
|
1888
|
+
visuals: z5.array(VisualSchema).optional().describe(
|
|
1735
1889
|
"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."
|
|
1736
1890
|
),
|
|
1737
1891
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
1738
|
-
repo:
|
|
1892
|
+
repo: z5.string().optional(),
|
|
1739
1893
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
1740
|
-
branch:
|
|
1894
|
+
branch: z5.string().optional(),
|
|
1741
1895
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
1742
1896
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
1743
1897
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
1744
1898
|
* still accepts the old name from older clients. */
|
|
1745
|
-
parentId:
|
|
1899
|
+
parentId: z5.string().uuid().optional(),
|
|
1746
1900
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
1747
1901
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
1748
|
-
workId:
|
|
1902
|
+
workId: z5.string().uuid().optional(),
|
|
1749
1903
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
1750
1904
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
1751
|
-
goalId:
|
|
1905
|
+
goalId: z5.string().uuid().optional(),
|
|
1752
1906
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
1753
1907
|
"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."
|
|
1754
1908
|
),
|
|
@@ -1756,7 +1910,7 @@ var NotifyRequestFields = z4.object({
|
|
|
1756
1910
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
1757
1911
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
1758
1912
|
* says HOW. */
|
|
1759
|
-
clarifies:
|
|
1913
|
+
clarifies: z5.string().optional(),
|
|
1760
1914
|
select: SelectShapeSchema.optional().describe(
|
|
1761
1915
|
"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."
|
|
1762
1916
|
),
|
|
@@ -1770,20 +1924,20 @@ var NotifyRequestFields = z4.object({
|
|
|
1770
1924
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
1771
1925
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
1772
1926
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
1773
|
-
ask:
|
|
1927
|
+
ask: z5.string().min(1).max(1e4).optional().describe(
|
|
1774
1928
|
'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.'
|
|
1775
1929
|
),
|
|
1776
|
-
needs:
|
|
1930
|
+
needs: z5.array(z5.string().min(1)).optional().describe(
|
|
1777
1931
|
"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."
|
|
1778
1932
|
),
|
|
1779
|
-
urgencyHint:
|
|
1933
|
+
urgencyHint: z5.enum(["whenever", "soon", "now"]).optional().describe(
|
|
1780
1934
|
"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."
|
|
1781
1935
|
),
|
|
1782
1936
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
1783
1937
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
1784
1938
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
1785
1939
|
* urgencyHint/blocking win when both are sent. */
|
|
1786
|
-
waiting:
|
|
1940
|
+
waiting: z5.enum(["none", "soft", "hard"]).optional().describe(
|
|
1787
1941
|
"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."
|
|
1788
1942
|
),
|
|
1789
1943
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -1791,98 +1945,98 @@ var NotifyRequestFields = z4.object({
|
|
|
1791
1945
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
1792
1946
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
1793
1947
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
1794
|
-
confirm:
|
|
1948
|
+
confirm: z5.boolean().optional().describe(
|
|
1795
1949
|
"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'."
|
|
1796
1950
|
),
|
|
1797
1951
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
1798
1952
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
1799
1953
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
1800
1954
|
* waiting:'hard'. */
|
|
1801
|
-
channel:
|
|
1955
|
+
channel: z5.enum(["call", "message"]).optional().describe(
|
|
1802
1956
|
"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."
|
|
1803
1957
|
),
|
|
1804
|
-
confirmStyle:
|
|
1958
|
+
confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
|
|
1805
1959
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
1806
1960
|
),
|
|
1807
|
-
blocking:
|
|
1961
|
+
blocking: z5.boolean().default(false).describe(
|
|
1808
1962
|
"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."
|
|
1809
1963
|
)
|
|
1810
1964
|
});
|
|
1811
1965
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
1812
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
1966
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
1813
1967
|
if (r.ask !== void 0) {
|
|
1814
1968
|
for (const f of ["context", "select", "points"]) {
|
|
1815
1969
|
if (r[f] !== void 0)
|
|
1816
|
-
ctx.addIssue({ code:
|
|
1970
|
+
ctx.addIssue({ code: z5.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.` });
|
|
1817
1971
|
}
|
|
1818
1972
|
return;
|
|
1819
1973
|
}
|
|
1820
1974
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
1821
|
-
ctx.addIssue({ code:
|
|
1975
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
1822
1976
|
if (!r.context)
|
|
1823
|
-
ctx.addIssue({ code:
|
|
1977
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
1824
1978
|
if (!r.select)
|
|
1825
|
-
ctx.addIssue({ code:
|
|
1979
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
1826
1980
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
1827
1981
|
if (needsOptions && !r.options?.length)
|
|
1828
|
-
ctx.addIssue({ code:
|
|
1982
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
1829
1983
|
if (!needsOptions && r.options?.length)
|
|
1830
|
-
ctx.addIssue({ code:
|
|
1984
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
1831
1985
|
});
|
|
1832
|
-
var NotifyStatusSchema =
|
|
1833
|
-
var AgentStateSchema =
|
|
1834
|
-
var TurnSchema =
|
|
1835
|
-
prompt:
|
|
1836
|
-
reply:
|
|
1986
|
+
var NotifyStatusSchema = z5.enum(["pending", "answered", "ignored"]);
|
|
1987
|
+
var AgentStateSchema = z5.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
1988
|
+
var TurnSchema = z5.object({
|
|
1989
|
+
prompt: z5.string(),
|
|
1990
|
+
reply: z5.string()
|
|
1837
1991
|
});
|
|
1838
|
-
var UserAnswerSchema =
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1992
|
+
var UserAnswerSchema = z5.discriminatedUnion("kind", [
|
|
1993
|
+
z5.object({ kind: z5.literal("option"), optionId: z5.string(), label: z5.string().optional() }),
|
|
1994
|
+
z5.object({ kind: z5.literal("text"), text: z5.string() }),
|
|
1995
|
+
z5.object({ kind: z5.literal("ignored") }),
|
|
1996
|
+
z5.object({ kind: z5.literal("multi"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
|
|
1997
|
+
z5.object({ kind: z5.literal("ranked"), optionIds: z5.array(z5.string()), labels: z5.array(z5.string()).optional() }),
|
|
1998
|
+
z5.object({ kind: z5.literal("clarify"), chunks: z5.array(z5.string()).min(1) }),
|
|
1999
|
+
z5.object({ kind: z5.literal("confirm"), approved: z5.boolean() }),
|
|
2000
|
+
z5.object({ kind: z5.literal("turns"), turns: z5.array(TurnSchema).min(1) }),
|
|
1847
2001
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
1848
2002
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
1849
2003
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
1850
2004
|
* card the user can reply to — the broker never overrides the user. */
|
|
1851
|
-
|
|
2005
|
+
z5.object({ kind: z5.literal("precedent"), answer: z5.string(), derivation: z5.string(), sources: z5.array(z5.string()).min(1) })
|
|
1852
2006
|
]);
|
|
1853
|
-
var IntentSchema =
|
|
2007
|
+
var IntentSchema = z5.object({
|
|
1854
2008
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
1855
2009
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
1856
2010
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
1857
2011
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
1858
|
-
kind:
|
|
1859
|
-
detail:
|
|
2012
|
+
kind: z5.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2013
|
+
detail: z5.string(),
|
|
1860
2014
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
1861
2015
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
1862
2016
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
1863
2017
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
1864
|
-
dueInSeconds:
|
|
2018
|
+
dueInSeconds: z5.number().int().positive().optional(),
|
|
1865
2019
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
1866
|
-
* already read the utterance, so `
|
|
2020
|
+
* already read the utterance, so `signals.kind` stops defaulting to
|
|
1867
2021
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
1868
2022
|
* about what cannot answer "is the bot looping less this week?". */
|
|
1869
|
-
fault:
|
|
2023
|
+
fault: z5.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
1870
2024
|
});
|
|
1871
|
-
var RideAlongSchema =
|
|
2025
|
+
var RideAlongSchema = z5.object({
|
|
1872
2026
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
1873
|
-
noteId:
|
|
2027
|
+
noteId: z5.string(),
|
|
1874
2028
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
1875
|
-
text:
|
|
2029
|
+
text: z5.string(),
|
|
1876
2030
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
1877
|
-
parentId:
|
|
2031
|
+
parentId: z5.string().nullable()
|
|
1878
2032
|
});
|
|
1879
|
-
var AwaitItemSchema =
|
|
1880
|
-
|
|
1881
|
-
type:
|
|
1882
|
-
parentId:
|
|
1883
|
-
notificationId:
|
|
1884
|
-
workId:
|
|
1885
|
-
decisionId:
|
|
2033
|
+
var AwaitItemSchema = z5.discriminatedUnion("type", [
|
|
2034
|
+
z5.object({
|
|
2035
|
+
type: z5.literal("reply"),
|
|
2036
|
+
parentId: z5.string(),
|
|
2037
|
+
notificationId: z5.string(),
|
|
2038
|
+
workId: z5.string().uuid().optional(),
|
|
2039
|
+
decisionId: z5.string().uuid().optional(),
|
|
1886
2040
|
answer: UserAnswerSchema,
|
|
1887
2041
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
1888
2042
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -1892,103 +2046,103 @@ var AwaitItemSchema = z4.discriminatedUnion("type", [
|
|
|
1892
2046
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
1893
2047
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
1894
2048
|
* a sentence with a hole in it is worse than no sentence. */
|
|
1895
|
-
note:
|
|
2049
|
+
note: z5.string().optional(),
|
|
1896
2050
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
1897
2051
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
1898
2052
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
1899
2053
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
1900
|
-
transcript:
|
|
2054
|
+
transcript: z5.string().optional(),
|
|
1901
2055
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
1902
2056
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
1903
|
-
covered:
|
|
2057
|
+
covered: z5.array(z5.string()).optional(),
|
|
1904
2058
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
1905
2059
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
1906
2060
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
1907
2061
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
1908
2062
|
* dropped. Absent/empty = nothing owed. */
|
|
1909
|
-
also:
|
|
2063
|
+
also: z5.array(RideAlongSchema).optional()
|
|
1910
2064
|
}),
|
|
1911
|
-
|
|
1912
|
-
type:
|
|
1913
|
-
parentId:
|
|
1914
|
-
notificationId:
|
|
1915
|
-
remindAt:
|
|
2065
|
+
z5.object({
|
|
2066
|
+
type: z5.literal("remind"),
|
|
2067
|
+
parentId: z5.string(),
|
|
2068
|
+
notificationId: z5.string(),
|
|
2069
|
+
remindAt: z5.string().datetime({ offset: true }),
|
|
1916
2070
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
1917
|
-
remindInSeconds:
|
|
2071
|
+
remindInSeconds: z5.number()
|
|
1918
2072
|
}),
|
|
1919
2073
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
1920
2074
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
1921
2075
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
1922
2076
|
* re-orient via check_replies). */
|
|
1923
|
-
|
|
1924
|
-
type:
|
|
1925
|
-
parentId:
|
|
1926
|
-
notificationId:
|
|
2077
|
+
z5.object({
|
|
2078
|
+
type: z5.literal("superseded"),
|
|
2079
|
+
parentId: z5.string(),
|
|
2080
|
+
notificationId: z5.string()
|
|
1927
2081
|
}),
|
|
1928
2082
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
1929
2083
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
1930
2084
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
1931
2085
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
1932
2086
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
1933
|
-
|
|
1934
|
-
type:
|
|
1935
|
-
notificationId:
|
|
1936
|
-
inFlight:
|
|
1937
|
-
turn:
|
|
1938
|
-
idx:
|
|
1939
|
-
prompt:
|
|
1940
|
-
reply:
|
|
1941
|
-
acts:
|
|
2087
|
+
z5.object({
|
|
2088
|
+
type: z5.literal("partial"),
|
|
2089
|
+
notificationId: z5.string(),
|
|
2090
|
+
inFlight: z5.literal(true),
|
|
2091
|
+
turn: z5.object({
|
|
2092
|
+
idx: z5.number(),
|
|
2093
|
+
prompt: z5.string(),
|
|
2094
|
+
reply: z5.string(),
|
|
2095
|
+
acts: z5.array(IntentSchema).nullable().optional()
|
|
1942
2096
|
})
|
|
1943
2097
|
}),
|
|
1944
|
-
|
|
1945
|
-
type:
|
|
1946
|
-
also:
|
|
2098
|
+
z5.object({
|
|
2099
|
+
type: z5.literal("idle"),
|
|
2100
|
+
also: z5.array(RideAlongSchema).optional(),
|
|
1947
2101
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
1948
2102
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
1949
2103
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
1950
2104
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
1951
2105
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
1952
|
-
inFlight:
|
|
2106
|
+
inFlight: z5.boolean().optional()
|
|
1953
2107
|
})
|
|
1954
2108
|
]);
|
|
1955
|
-
var VoiceKeySchema =
|
|
1956
|
-
var AgendaTurnSchema =
|
|
2109
|
+
var VoiceKeySchema = z5.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
2110
|
+
var AgendaTurnSchema = z5.object({
|
|
1957
2111
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
1958
2112
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
1959
2113
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
1960
2114
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
1961
2115
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
1962
2116
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
1963
|
-
id:
|
|
2117
|
+
id: z5.string().optional(),
|
|
1964
2118
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
1965
2119
|
* the planner declares duplicates instead of asking them twice. */
|
|
1966
|
-
coveredIds:
|
|
2120
|
+
coveredIds: z5.array(z5.string()).optional(),
|
|
1967
2121
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
1968
2122
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
1969
|
-
info:
|
|
1970
|
-
question:
|
|
2123
|
+
info: z5.array(z5.string().min(1)).default([]),
|
|
2124
|
+
question: z5.string().min(1).nullable(),
|
|
1971
2125
|
/** True on the one turn carrying the agent's own declared question. */
|
|
1972
|
-
asks:
|
|
2126
|
+
asks: z5.boolean().optional(),
|
|
1973
2127
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
1974
2128
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
1975
2129
|
* which route nothing. */
|
|
1976
|
-
claimId:
|
|
2130
|
+
claimId: z5.string().optional(),
|
|
1977
2131
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
1978
|
-
voice:
|
|
2132
|
+
voice: z5.string().optional(),
|
|
1979
2133
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
1980
2134
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
1981
2135
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
1982
2136
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
1983
|
-
agent:
|
|
2137
|
+
agent: z5.string().optional(),
|
|
1984
2138
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
1985
2139
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
1986
2140
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
1987
2141
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
1988
2142
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
1989
|
-
agentId:
|
|
2143
|
+
agentId: z5.string().optional(),
|
|
1990
2144
|
select: SelectShapeSchema.optional(),
|
|
1991
|
-
options:
|
|
2145
|
+
options: z5.array(OptionSchema.omit({ id: true })).optional(),
|
|
1992
2146
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
1993
2147
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
1994
2148
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -1997,33 +2151,33 @@ var AgendaTurnSchema = z4.object({
|
|
|
1997
2151
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
1998
2152
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
1999
2153
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2000
|
-
blocking:
|
|
2154
|
+
blocking: z5.boolean().optional(),
|
|
2001
2155
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
2002
2156
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
2003
2157
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
2004
2158
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
2005
2159
|
* speak, it is dropped and their words are taken like any reply. */
|
|
2006
|
-
ifSilent:
|
|
2160
|
+
ifSilent: z5.boolean().optional()
|
|
2007
2161
|
});
|
|
2008
2162
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2009
|
-
var InboxItemSchema =
|
|
2010
|
-
id:
|
|
2011
|
-
tokenId:
|
|
2163
|
+
var InboxItemSchema = z5.object({
|
|
2164
|
+
id: z5.string(),
|
|
2165
|
+
tokenId: z5.string().optional(),
|
|
2012
2166
|
status: NotifyStatusSchema,
|
|
2013
2167
|
context: ContextSchema,
|
|
2014
|
-
options:
|
|
2168
|
+
options: z5.array(OptionSchema).optional(),
|
|
2015
2169
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2016
|
-
points:
|
|
2170
|
+
points: z5.array(z5.string()).optional(),
|
|
2017
2171
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
2018
2172
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2019
2173
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2020
2174
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2021
|
-
asks:
|
|
2175
|
+
asks: z5.boolean().optional(),
|
|
2022
2176
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2023
2177
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2024
2178
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2025
2179
|
* which must never CLAIM stalled. */
|
|
2026
|
-
lastSeenAt:
|
|
2180
|
+
lastSeenAt: z5.string().optional(),
|
|
2027
2181
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2028
2182
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2029
2183
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2033,7 +2187,7 @@ var InboxItemSchema = z4.object({
|
|
|
2033
2187
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2034
2188
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2035
2189
|
* `createdAt`. */
|
|
2036
|
-
agentStateAt:
|
|
2190
|
+
agentStateAt: z5.string().datetime().optional(),
|
|
2037
2191
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
2038
2192
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
2039
2193
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -2047,54 +2201,54 @@ var InboxItemSchema = z4.object({
|
|
|
2047
2201
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2048
2202
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2049
2203
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2050
|
-
questions:
|
|
2051
|
-
id:
|
|
2052
|
-
entryId:
|
|
2053
|
-
title:
|
|
2054
|
-
state:
|
|
2055
|
-
answer:
|
|
2204
|
+
questions: z5.array(z5.object({
|
|
2205
|
+
id: z5.string(),
|
|
2206
|
+
entryId: z5.string(),
|
|
2207
|
+
title: z5.string(),
|
|
2208
|
+
state: z5.enum(["open", "answered"]),
|
|
2209
|
+
answer: z5.string().nullable(),
|
|
2056
2210
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2057
2211
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2058
2212
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2059
2213
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2060
|
-
agent:
|
|
2214
|
+
agent: z5.object({ tokenId: z5.string(), name: z5.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
2061
2215
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
2062
2216
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
2063
2217
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
2064
2218
|
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
2065
2219
|
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
2066
|
-
options:
|
|
2220
|
+
options: z5.array(OptionSchema).optional()
|
|
2067
2221
|
})).optional(),
|
|
2068
|
-
visuals:
|
|
2222
|
+
visuals: z5.array(VisualSchema).optional(),
|
|
2069
2223
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2070
2224
|
* agent's suggestion, or a default silly name). */
|
|
2071
|
-
name:
|
|
2225
|
+
name: z5.string(),
|
|
2072
2226
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2073
2227
|
voice: VoiceKeySchema.optional(),
|
|
2074
|
-
repo:
|
|
2075
|
-
branch:
|
|
2076
|
-
createdAt:
|
|
2077
|
-
snoozedUntil:
|
|
2228
|
+
repo: z5.string().optional(),
|
|
2229
|
+
branch: z5.string().optional(),
|
|
2230
|
+
createdAt: z5.string().datetime(),
|
|
2231
|
+
snoozedUntil: z5.string().datetime().optional(),
|
|
2078
2232
|
agentState: AgentStateSchema.default("idle"),
|
|
2079
2233
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2080
2234
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2081
2235
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2082
|
-
turn:
|
|
2236
|
+
turn: z5.enum(["you", "agent"]).default("you"),
|
|
2083
2237
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2084
2238
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2085
2239
|
* error badge + Retry. */
|
|
2086
|
-
error:
|
|
2240
|
+
error: z5.string().optional(),
|
|
2087
2241
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
2088
2242
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2089
2243
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2090
2244
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2091
|
-
cold:
|
|
2092
|
-
clarifies:
|
|
2245
|
+
cold: z5.string().datetime().optional(),
|
|
2246
|
+
clarifies: z5.string().optional(),
|
|
2093
2247
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
2094
2248
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
2095
2249
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
2096
2250
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
2097
|
-
onCall:
|
|
2251
|
+
onCall: z5.literal(true).optional(),
|
|
2098
2252
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2099
2253
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2100
2254
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -2104,31 +2258,31 @@ var InboxItemSchema = z4.object({
|
|
|
2104
2258
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2105
2259
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2106
2260
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2107
|
-
ring:
|
|
2108
|
-
declinedAt:
|
|
2109
|
-
anchorAt:
|
|
2110
|
-
nextRingAt:
|
|
2111
|
-
step:
|
|
2261
|
+
ring: z5.object({
|
|
2262
|
+
declinedAt: z5.string().datetime(),
|
|
2263
|
+
anchorAt: z5.string().datetime(),
|
|
2264
|
+
nextRingAt: z5.string().datetime().nullable(),
|
|
2265
|
+
step: z5.number().int()
|
|
2112
2266
|
}).optional(),
|
|
2113
2267
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
2114
2268
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
2115
2269
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
2116
2270
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
2117
2271
|
* feedback ABOUT. */
|
|
2118
|
-
why:
|
|
2272
|
+
why: z5.object({
|
|
2119
2273
|
asked: NotifyLevelSchema,
|
|
2120
2274
|
got: NotifyLevelSchema,
|
|
2121
|
-
because:
|
|
2122
|
-
line:
|
|
2275
|
+
because: z5.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2276
|
+
line: z5.string()
|
|
2123
2277
|
}).optional(),
|
|
2124
|
-
select:
|
|
2125
|
-
confirmStyle:
|
|
2278
|
+
select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2279
|
+
confirmStyle: z5.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2126
2280
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2127
2281
|
),
|
|
2128
2282
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
2129
2283
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
2130
2284
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
2131
|
-
blocking:
|
|
2285
|
+
blocking: z5.boolean().default(false),
|
|
2132
2286
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
2133
2287
|
answer: UserAnswerSchema.optional(),
|
|
2134
2288
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -2136,38 +2290,49 @@ var InboxItemSchema = z4.object({
|
|
|
2136
2290
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
2137
2291
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
2138
2292
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
2139
|
-
communication:
|
|
2140
|
-
deliveryId:
|
|
2141
|
-
kind:
|
|
2142
|
-
entryId:
|
|
2143
|
-
goalIds:
|
|
2144
|
-
decisionNeedId:
|
|
2145
|
-
sealed:
|
|
2146
|
-
goalState:
|
|
2293
|
+
communication: z5.object({
|
|
2294
|
+
deliveryId: z5.string(),
|
|
2295
|
+
kind: z5.enum(["notification", "call"]),
|
|
2296
|
+
entryId: z5.string(),
|
|
2297
|
+
goalIds: z5.array(z5.string()),
|
|
2298
|
+
decisionNeedId: z5.string().optional(),
|
|
2299
|
+
sealed: z5.boolean(),
|
|
2300
|
+
goalState: z5.string().optional(),
|
|
2147
2301
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2148
2302
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2149
|
-
goalTitle:
|
|
2150
|
-
}).optional()
|
|
2303
|
+
goalTitle: z5.string().optional()
|
|
2304
|
+
}).optional(),
|
|
2305
|
+
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
2306
|
+
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
2307
|
+
* how the incremental read knows a card has not moved: the phone echoes back the revs it holds
|
|
2308
|
+
* (`POST /api/inbox/changes`) and is sent only the cards whose rev differs.
|
|
2309
|
+
*
|
|
2310
|
+
* THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK — the same mechanism as `QueueItem.rev`
|
|
2311
|
+
* (#2928) and an entry's (#3018), for the same reason: a card shows facts no `updated_at` of its
|
|
2312
|
+
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
2313
|
+
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
2314
|
+
* with no rev is simply always re-sent. */
|
|
2315
|
+
rev: z5.string().optional()
|
|
2151
2316
|
});
|
|
2152
2317
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
2153
|
-
var PushTokenSchema =
|
|
2154
|
-
voipToken:
|
|
2155
|
-
alertToken:
|
|
2156
|
-
fcmToken:
|
|
2157
|
-
platform:
|
|
2318
|
+
var PushTokenSchema = z5.object({
|
|
2319
|
+
voipToken: z5.string().min(1).optional(),
|
|
2320
|
+
alertToken: z5.string().min(1).optional(),
|
|
2321
|
+
fcmToken: z5.string().min(1).optional(),
|
|
2322
|
+
platform: z5.enum(["ios", "android"])
|
|
2158
2323
|
}).superRefine((v, ctx) => {
|
|
2159
2324
|
if (v.platform !== "ios") return;
|
|
2160
2325
|
for (const field of ["voipToken", "alertToken"]) {
|
|
2161
2326
|
const token = v[field];
|
|
2162
2327
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
2163
2328
|
ctx.addIssue({
|
|
2164
|
-
code:
|
|
2329
|
+
code: z5.ZodIssueCode.custom,
|
|
2165
2330
|
path: [field],
|
|
2166
2331
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
2167
2332
|
});
|
|
2168
2333
|
}
|
|
2169
2334
|
});
|
|
2170
|
-
var MissedCallSchema =
|
|
2335
|
+
var MissedCallSchema = z5.enum([
|
|
2171
2336
|
"retry_10m",
|
|
2172
2337
|
"retry_30m",
|
|
2173
2338
|
"retry_60m",
|
|
@@ -2179,31 +2344,31 @@ var MissedCallSchema = z4.enum([
|
|
|
2179
2344
|
]);
|
|
2180
2345
|
var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
2181
2346
|
var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
|
|
2182
|
-
var BrokerTuningSchema =
|
|
2347
|
+
var BrokerTuningSchema = z5.object({
|
|
2183
2348
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
2184
|
-
ackVerbosity:
|
|
2349
|
+
ackVerbosity: z5.enum(["normal", "none"]).optional(),
|
|
2185
2350
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
2186
2351
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
2187
|
-
clarifyEagerness:
|
|
2352
|
+
clarifyEagerness: z5.enum(["low", "normal", "high"]).optional(),
|
|
2188
2353
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
2189
|
-
phrasebook:
|
|
2354
|
+
phrasebook: z5.array(z5.object({ say: z5.string().min(1).max(60), mean: z5.string().min(1).max(120) })).max(24).optional(),
|
|
2190
2355
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
2191
2356
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
2192
2357
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
2193
2358
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
2194
2359
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
2195
|
-
language:
|
|
2360
|
+
language: z5.enum(["en", "es"]).optional()
|
|
2196
2361
|
});
|
|
2197
|
-
var UserSettingsSchema =
|
|
2198
|
-
permissions:
|
|
2199
|
-
call:
|
|
2200
|
-
banner:
|
|
2201
|
-
push:
|
|
2362
|
+
var UserSettingsSchema = z5.object({
|
|
2363
|
+
permissions: z5.object({
|
|
2364
|
+
call: z5.boolean(),
|
|
2365
|
+
banner: z5.boolean(),
|
|
2366
|
+
push: z5.boolean()
|
|
2202
2367
|
}),
|
|
2203
2368
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
2204
2369
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
2205
2370
|
* what people read (owner, 2026-09-30). */
|
|
2206
|
-
sessionMode:
|
|
2371
|
+
sessionMode: z5.enum(["default", "all_calls", "silent"]),
|
|
2207
2372
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
2208
2373
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
2209
2374
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -2212,7 +2377,7 @@ var UserSettingsSchema = z4.object({
|
|
|
2212
2377
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
2213
2378
|
* next dead field for the same reason. */
|
|
2214
2379
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
2215
|
-
improveConsent:
|
|
2380
|
+
improveConsent: z5.boolean(),
|
|
2216
2381
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
2217
2382
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
2218
2383
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -2220,17 +2385,17 @@ var UserSettingsSchema = z4.object({
|
|
|
2220
2385
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
2221
2386
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
2222
2387
|
* write, 'hosted' on read (see store.ts). */
|
|
2223
|
-
voiceMode:
|
|
2388
|
+
voiceMode: z5.enum(["hosted", "on_device"]).optional(),
|
|
2224
2389
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
2225
2390
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
2226
2391
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
2227
2392
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2228
2393
|
* read (see store.ts). */
|
|
2229
|
-
talk:
|
|
2394
|
+
talk: z5.boolean().optional(),
|
|
2230
2395
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
2231
2396
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
2232
2397
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
2233
|
-
callDiagnostics:
|
|
2398
|
+
callDiagnostics: z5.boolean().optional(),
|
|
2234
2399
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
2235
2400
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
2236
2401
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2238,7 +2403,7 @@ var UserSettingsSchema = z4.object({
|
|
|
2238
2403
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2239
2404
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2240
2405
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2241
|
-
voiceTuning:
|
|
2406
|
+
voiceTuning: z5.record(z5.string(), z5.union([z5.number(), z5.string()])).optional(),
|
|
2242
2407
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2243
2408
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2244
2409
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2247,53 +2412,53 @@ var UserSettingsSchema = z4.object({
|
|
|
2247
2412
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2248
2413
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2249
2414
|
* call — honest about what we know. */
|
|
2250
|
-
timezone:
|
|
2415
|
+
timezone: z5.string().min(1).max(64).optional(),
|
|
2251
2416
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2252
2417
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2253
2418
|
broker: BrokerTuningSchema.optional()
|
|
2254
2419
|
});
|
|
2255
|
-
var HistoryWorkSchema =
|
|
2256
|
-
id:
|
|
2257
|
-
title:
|
|
2258
|
-
state:
|
|
2420
|
+
var HistoryWorkSchema = z5.object({
|
|
2421
|
+
id: z5.string(),
|
|
2422
|
+
title: z5.string(),
|
|
2423
|
+
state: z5.enum(["done", "cancelled"]),
|
|
2259
2424
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
2260
|
-
assignee:
|
|
2425
|
+
assignee: z5.string()
|
|
2261
2426
|
});
|
|
2262
|
-
var HistoryEntrySchema =
|
|
2263
|
-
|
|
2264
|
-
|
|
2427
|
+
var HistoryEntrySchema = z5.union([
|
|
2428
|
+
z5.object({ at: z5.string(), card: InboxItemSchema }),
|
|
2429
|
+
z5.object({ at: z5.string(), work: HistoryWorkSchema })
|
|
2265
2430
|
]);
|
|
2266
|
-
var HistoryPageSchema =
|
|
2267
|
-
entries:
|
|
2268
|
-
next:
|
|
2431
|
+
var HistoryPageSchema = z5.object({
|
|
2432
|
+
entries: z5.array(HistoryEntrySchema),
|
|
2433
|
+
next: z5.string().nullable()
|
|
2269
2434
|
});
|
|
2270
2435
|
var ACTIVITY_LINES = 2;
|
|
2271
2436
|
var ACTIVITY_LINE_MAX = 80;
|
|
2272
|
-
var AgentActivitySchema =
|
|
2437
|
+
var AgentActivitySchema = z5.object({
|
|
2273
2438
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
2274
|
-
lines:
|
|
2439
|
+
lines: z5.array(z5.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
2275
2440
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
2276
2441
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
2277
|
-
at:
|
|
2442
|
+
at: z5.string().datetime()
|
|
2278
2443
|
});
|
|
2279
|
-
var ConnectionSummarySchema =
|
|
2444
|
+
var ConnectionSummarySchema = z5.object({
|
|
2280
2445
|
/** The connection = the agent's token id (used to address a request). */
|
|
2281
|
-
id:
|
|
2446
|
+
id: z5.string(),
|
|
2282
2447
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
2283
2448
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
2284
2449
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
2285
2450
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
2286
|
-
kind:
|
|
2451
|
+
kind: z5.enum(["device", "agent"]).optional(),
|
|
2287
2452
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
2288
2453
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
2289
2454
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
2290
|
-
mintedByDevice:
|
|
2291
|
-
device:
|
|
2455
|
+
mintedByDevice: z5.string().nullable().optional(),
|
|
2456
|
+
device: z5.string().nullable(),
|
|
2292
2457
|
/** The agent's display name (the single pairing name). */
|
|
2293
|
-
name:
|
|
2458
|
+
name: z5.string(),
|
|
2294
2459
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
2295
2460
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
2296
|
-
provider:
|
|
2461
|
+
provider: z5.string().nullable(),
|
|
2297
2462
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
2298
2463
|
voice: VoiceKeySchema.nullable(),
|
|
2299
2464
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -2304,34 +2469,34 @@ var ConnectionSummarySchema = z4.object({
|
|
|
2304
2469
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
2305
2470
|
* set once must not overrule a rule they set about one agent. */
|
|
2306
2471
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
2307
|
-
createdAt:
|
|
2472
|
+
createdAt: z5.string().datetime(),
|
|
2308
2473
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
2309
2474
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
2310
|
-
lastContactAt:
|
|
2475
|
+
lastContactAt: z5.string().datetime().nullable(),
|
|
2311
2476
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
2312
2477
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
2313
|
-
lastSeenAt:
|
|
2478
|
+
lastSeenAt: z5.string().datetime().nullable().optional(),
|
|
2314
2479
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
2315
2480
|
* its Goals — took its lease or recorded an operation (`tokens.last_worked_at`). Within
|
|
2316
2481
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
2317
|
-
lastWorkedAt:
|
|
2482
|
+
lastWorkedAt: z5.string().datetime().nullable().optional(),
|
|
2318
2483
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
2319
2484
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
2320
|
-
oldestReadyAt:
|
|
2485
|
+
oldestReadyAt: z5.string().datetime().nullable().optional(),
|
|
2321
2486
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
2322
2487
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
2323
2488
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
2324
|
-
runtime:
|
|
2489
|
+
runtime: z5.object({
|
|
2325
2490
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
2326
2491
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
2327
|
-
version:
|
|
2328
|
-
harnesses:
|
|
2329
|
-
workspaces:
|
|
2492
|
+
version: z5.string().optional(),
|
|
2493
|
+
harnesses: z5.array(z5.object({ name: z5.string(), label: z5.string(), status: z5.string() })).optional(),
|
|
2494
|
+
workspaces: z5.array(z5.string()).optional(),
|
|
2330
2495
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
2331
2496
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
2332
2497
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
2333
2498
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
2334
|
-
repos:
|
|
2499
|
+
repos: z5.array(z5.object({ path: z5.string(), remote: z5.string() })).optional()
|
|
2335
2500
|
}).optional(),
|
|
2336
2501
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
2337
2502
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -2340,173 +2505,156 @@ var ConnectionSummarySchema = z4.object({
|
|
|
2340
2505
|
activity: AgentActivitySchema.optional(),
|
|
2341
2506
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
2342
2507
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
2343
|
-
managed:
|
|
2508
|
+
managed: z5.boolean()
|
|
2344
2509
|
});
|
|
2345
|
-
var LedgerItemSchema =
|
|
2346
|
-
var AgentLedgerSchema =
|
|
2510
|
+
var LedgerItemSchema = z5.object({ id: z5.string(), parentId: z5.string(), title: z5.string(), createdAt: z5.string() });
|
|
2511
|
+
var AgentLedgerSchema = z5.object({
|
|
2347
2512
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
2348
|
-
agent:
|
|
2513
|
+
agent: z5.object({ id: z5.string(), name: z5.string().nullable(), revokedAt: z5.string().nullable() }),
|
|
2349
2514
|
/** Its own questions you have not answered. */
|
|
2350
|
-
asks:
|
|
2515
|
+
asks: z5.array(LedgerItemSchema),
|
|
2351
2516
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
2352
|
-
answered:
|
|
2517
|
+
answered: z5.array(LedgerItemSchema),
|
|
2353
2518
|
/** Requests you sent it that it never took. */
|
|
2354
|
-
requests:
|
|
2355
|
-
goals:
|
|
2356
|
-
callbacks:
|
|
2519
|
+
requests: z5.array(LedgerItemSchema),
|
|
2520
|
+
goals: z5.array(z5.object({ id: z5.string(), outcome: z5.string(), state: z5.string() })),
|
|
2521
|
+
callbacks: z5.array(z5.object({ id: z5.string(), parentId: z5.string(), trigger: z5.string(), note: z5.string(), dueAt: z5.string().nullable() }))
|
|
2357
2522
|
});
|
|
2358
|
-
var ReassignResultSchema =
|
|
2359
|
-
moved:
|
|
2360
|
-
parentId:
|
|
2523
|
+
var ReassignResultSchema = z5.object({
|
|
2524
|
+
moved: z5.object({ asks: z5.number(), answered: z5.number(), requests: z5.number(), goals: z5.number(), callbacks: z5.number() }),
|
|
2525
|
+
parentId: z5.string().nullable()
|
|
2361
2526
|
});
|
|
2362
|
-
var
|
|
2363
|
-
var
|
|
2364
|
-
id:
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
/** The
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
/**
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
* break is visible instead of a pin silently disappearing. */
|
|
2379
|
-
pinBroken: z4.boolean(),
|
|
2380
|
-
/** When the ruling was distilled. */
|
|
2381
|
-
learnedAt: z4.string(),
|
|
2382
|
-
/** Last time it answered an ask. Null = never fired. */
|
|
2383
|
-
lastUsedAt: z4.string().nullable(),
|
|
2384
|
-
/** How many asks it has answered. Instrumentation — deliberately NOT an input to the
|
|
2385
|
-
* evidence curve: firing says the question keeps arising, not that the ruling is right. */
|
|
2386
|
-
usedCount: z4.number(),
|
|
2387
|
-
/** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
|
|
2388
|
-
confirms: z4.number(),
|
|
2389
|
-
/** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
|
|
2390
|
-
* Linear and priced above the entire confirmation budget, so any full counter wins. */
|
|
2391
|
-
counters: z4.number(),
|
|
2392
|
-
/** The agent that asked the question this move came from, when known. Null for a move
|
|
2393
|
-
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
2394
|
-
learnedFrom: z4.object({ id: z4.string(), name: z4.string() }).nullable()
|
|
2527
|
+
var LessonStateSchema = z5.enum(["active", "proposed", "retired"]);
|
|
2528
|
+
var LessonViewSchema = z5.object({
|
|
2529
|
+
id: z5.string(),
|
|
2530
|
+
text: z5.string(),
|
|
2531
|
+
state: LessonStateSchema,
|
|
2532
|
+
/** The Goal it is scoped to; null = the whole account. */
|
|
2533
|
+
scopeGoalId: z5.string().nullable(),
|
|
2534
|
+
goalTitle: z5.string().nullable(),
|
|
2535
|
+
version: z5.number(),
|
|
2536
|
+
pinned: z5.boolean(),
|
|
2537
|
+
/** When the person last wrote its text themselves. */
|
|
2538
|
+
editedAt: z5.string().nullable(),
|
|
2539
|
+
createdAt: z5.string(),
|
|
2540
|
+
updatedAt: z5.string(),
|
|
2541
|
+
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
2542
|
+
sources: z5.array(z5.object({ entryId: z5.string(), words: z5.string().nullable(), at: z5.string() }))
|
|
2395
2543
|
});
|
|
2396
|
-
var QueueQuestionSchema =
|
|
2544
|
+
var QueueQuestionSchema = z5.object({
|
|
2397
2545
|
/** The decision need's id — what an answer is accepted against. */
|
|
2398
|
-
id:
|
|
2546
|
+
id: z5.string(),
|
|
2399
2547
|
/** The words that were asked, from the request Entry that asked them. */
|
|
2400
|
-
question:
|
|
2548
|
+
question: z5.string(),
|
|
2401
2549
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
2402
2550
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
2403
2551
|
* can answer. */
|
|
2404
|
-
deliveryId:
|
|
2552
|
+
deliveryId: z5.string().nullable().default(null),
|
|
2405
2553
|
/** The Entry the ruling is about. */
|
|
2406
|
-
aboutId:
|
|
2554
|
+
aboutId: z5.string().nullable().default(null),
|
|
2407
2555
|
/** Empty for a free-text question. */
|
|
2408
|
-
options:
|
|
2409
|
-
select:
|
|
2410
|
-
askedAt:
|
|
2556
|
+
options: z5.array(OptionSchema).default([]),
|
|
2557
|
+
select: z5.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
2558
|
+
askedAt: z5.string(),
|
|
2411
2559
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
2412
|
-
answeredAt:
|
|
2560
|
+
answeredAt: z5.string().nullable().default(null),
|
|
2413
2561
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
2414
2562
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
2415
2563
|
* for a settled question whose reply carried nothing readable. */
|
|
2416
|
-
answer:
|
|
2564
|
+
answer: z5.string().nullable().default(null),
|
|
2417
2565
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
2418
2566
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
2419
2567
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
2420
2568
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
2421
2569
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
2422
2570
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
2423
|
-
goalId:
|
|
2571
|
+
goalId: z5.string(),
|
|
2424
2572
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
2425
2573
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
2426
2574
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
2427
2575
|
* gate at all. */
|
|
2428
|
-
blocked:
|
|
2576
|
+
blocked: z5.boolean().default(false)
|
|
2429
2577
|
});
|
|
2430
|
-
var QueueReplySchema =
|
|
2578
|
+
var QueueReplySchema = z5.object({
|
|
2431
2579
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
2432
2580
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
2433
2581
|
* walk can name it in its zoom. */
|
|
2434
|
-
id:
|
|
2582
|
+
id: z5.string(),
|
|
2435
2583
|
/** The Goal the note is on. */
|
|
2436
|
-
goalId:
|
|
2584
|
+
goalId: z5.string(),
|
|
2437
2585
|
/** What the note said. */
|
|
2438
|
-
note:
|
|
2586
|
+
note: z5.string(),
|
|
2439
2587
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
2440
|
-
deliveryId:
|
|
2441
|
-
requestEntryId:
|
|
2442
|
-
askedAt:
|
|
2588
|
+
deliveryId: z5.string(),
|
|
2589
|
+
requestEntryId: z5.string(),
|
|
2590
|
+
askedAt: z5.string(),
|
|
2443
2591
|
/** When the person last replied — the window's start. */
|
|
2444
|
-
repliedAt:
|
|
2592
|
+
repliedAt: z5.string(),
|
|
2445
2593
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
2446
|
-
reply:
|
|
2594
|
+
reply: z5.string().nullable()
|
|
2447
2595
|
});
|
|
2448
|
-
var QueueItemSchema =
|
|
2449
|
-
id:
|
|
2596
|
+
var QueueItemSchema = z5.object({
|
|
2597
|
+
id: z5.string(),
|
|
2450
2598
|
/** One-line headline — the first sentence of the outcome. */
|
|
2451
|
-
title:
|
|
2599
|
+
title: z5.string(),
|
|
2452
2600
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
2453
|
-
intent:
|
|
2601
|
+
intent: z5.string(),
|
|
2454
2602
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
2455
|
-
state:
|
|
2603
|
+
state: z5.string(),
|
|
2456
2604
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
2457
|
-
assignee:
|
|
2605
|
+
assignee: z5.string().nullable().default(null),
|
|
2458
2606
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
2459
|
-
progress:
|
|
2607
|
+
progress: z5.string().nullable().default(null),
|
|
2460
2608
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
2461
2609
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
2462
|
-
progressLine:
|
|
2463
|
-
reviewPending:
|
|
2464
|
-
dueAt:
|
|
2610
|
+
progressLine: z5.string().nullable().optional(),
|
|
2611
|
+
reviewPending: z5.boolean().default(false),
|
|
2612
|
+
dueAt: z5.string().nullable().default(null),
|
|
2465
2613
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
2466
2614
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
2467
|
-
finishedAt:
|
|
2615
|
+
finishedAt: z5.string().nullable().optional(),
|
|
2468
2616
|
/** The Goal this one was opened under; null at the root. */
|
|
2469
|
-
parentGoalId:
|
|
2617
|
+
parentGoalId: z5.string().nullable().default(null),
|
|
2470
2618
|
/** Goals opened under this one — only those the same list holds. */
|
|
2471
|
-
childGoalIds:
|
|
2619
|
+
childGoalIds: z5.array(z5.string()).default([]),
|
|
2472
2620
|
/** Goals this one waits on (start or finish gates). */
|
|
2473
|
-
dependencyGoalIds:
|
|
2621
|
+
dependencyGoalIds: z5.array(z5.string()).default([]),
|
|
2474
2622
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
2475
|
-
blocked:
|
|
2623
|
+
blocked: z5.boolean().default(false),
|
|
2476
2624
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
2477
2625
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
2478
2626
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
2479
2627
|
* question is opened. */
|
|
2480
|
-
questions:
|
|
2628
|
+
questions: z5.array(QueueQuestionSchema).default([]),
|
|
2481
2629
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
2482
2630
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
2483
2631
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
2484
2632
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
2485
|
-
asked:
|
|
2486
|
-
answered:
|
|
2633
|
+
asked: z5.number().optional(),
|
|
2634
|
+
answered: z5.number().optional(),
|
|
2487
2635
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
2488
2636
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
2489
2637
|
* not spell an empty list. */
|
|
2490
|
-
replies:
|
|
2638
|
+
replies: z5.array(QueueReplySchema).optional(),
|
|
2491
2639
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
2492
|
-
repo:
|
|
2493
|
-
createdAt:
|
|
2494
|
-
updatedAt:
|
|
2640
|
+
repo: z5.string().nullable().optional(),
|
|
2641
|
+
createdAt: z5.string(),
|
|
2642
|
+
updatedAt: z5.string().nullable().default(null),
|
|
2495
2643
|
/** When its owner last SAID something about it (`goals.last_progress_at`, written by every
|
|
2496
2644
|
* `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
|
|
2497
2645
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
2498
|
-
lastProgressAt:
|
|
2646
|
+
lastProgressAt: z5.string().nullable().optional(),
|
|
2499
2647
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
2500
2648
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
2501
2649
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
2502
2650
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
2503
2651
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
2504
2652
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
2505
|
-
latest:
|
|
2506
|
-
from:
|
|
2507
|
-
said:
|
|
2508
|
-
at:
|
|
2509
|
-
entryId:
|
|
2653
|
+
latest: z5.object({
|
|
2654
|
+
from: z5.enum(["person", "agent"]),
|
|
2655
|
+
said: z5.string(),
|
|
2656
|
+
at: z5.string(),
|
|
2657
|
+
entryId: z5.string()
|
|
2510
2658
|
}).nullable().optional(),
|
|
2511
2659
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
2512
2660
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -2519,36 +2667,51 @@ var QueueItemSchema = z4.object({
|
|
|
2519
2667
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
2520
2668
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
2521
2669
|
* Entry with no `about_id` is in neither. */
|
|
2522
|
-
lastPersonAt:
|
|
2670
|
+
lastPersonAt: z5.string().nullable().optional(),
|
|
2671
|
+
/** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
|
|
2672
|
+
* by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
|
|
2673
|
+
* incremental read knows a row has not moved: the phone echoes back the revs it holds
|
|
2674
|
+
* (`POST /api/goals/changes`) and is sent only the rows whose rev differs.
|
|
2675
|
+
*
|
|
2676
|
+
* IT IS THE PAYLOAD'S OWN HASH, NEVER A STAMP ON THE WORK. Nothing here reasons about which
|
|
2677
|
+
* writes change which field — the comparison is over the bytes the phone is holding, so a fact
|
|
2678
|
+
* the row shows that no `updated_at` moves for (a lease lapsing, a dependency's state, a
|
|
2679
|
+
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
2680
|
+
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
2681
|
+
rev: z5.string().optional()
|
|
2682
|
+
});
|
|
2683
|
+
var QueueDeltaSchema = z5.object({
|
|
2684
|
+
ids: z5.array(z5.string()),
|
|
2685
|
+
items: z5.array(QueueItemSchema)
|
|
2523
2686
|
});
|
|
2524
2687
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
2525
|
-
var NoteSourceSchema =
|
|
2526
|
-
var NoteStatusSchema =
|
|
2527
|
-
var NoteRepeatSchema =
|
|
2528
|
-
var DecisionSchema =
|
|
2529
|
-
id:
|
|
2688
|
+
var NoteSourceSchema = z5.enum(["app", "call"]);
|
|
2689
|
+
var NoteStatusSchema = z5.enum(["open", "assigned", "in_progress", "done"]);
|
|
2690
|
+
var NoteRepeatSchema = z5.enum(["once", "until_done"]);
|
|
2691
|
+
var DecisionSchema = z5.object({
|
|
2692
|
+
id: z5.string(),
|
|
2530
2693
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
2531
2694
|
* extensibility seam — any conversation can accrue decisions). */
|
|
2532
|
-
noteId:
|
|
2695
|
+
noteId: z5.string().nullable(),
|
|
2533
2696
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
2534
|
-
question:
|
|
2697
|
+
question: z5.string(),
|
|
2535
2698
|
/** The user's ruling; null while the question is open. */
|
|
2536
|
-
answer:
|
|
2537
|
-
decidedAt:
|
|
2538
|
-
createdAt:
|
|
2699
|
+
answer: z5.string().nullable(),
|
|
2700
|
+
decidedAt: z5.string().nullable(),
|
|
2701
|
+
createdAt: z5.string()
|
|
2539
2702
|
});
|
|
2540
|
-
var NoteSchema =
|
|
2541
|
-
id:
|
|
2703
|
+
var NoteSchema = z5.object({
|
|
2704
|
+
id: z5.string(),
|
|
2542
2705
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
2543
|
-
title:
|
|
2706
|
+
title: z5.string(),
|
|
2544
2707
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
2545
|
-
intent:
|
|
2708
|
+
intent: z5.string(),
|
|
2546
2709
|
source: NoteSourceSchema,
|
|
2547
2710
|
status: NoteStatusSchema,
|
|
2548
2711
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
2549
|
-
assignee:
|
|
2712
|
+
assignee: z5.string().nullable(),
|
|
2550
2713
|
/** The request thread minted at assignment; null until assigned. */
|
|
2551
|
-
parentId:
|
|
2714
|
+
parentId: z5.string().nullable(),
|
|
2552
2715
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
2553
2716
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
2554
2717
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -2557,149 +2720,154 @@ var NoteSchema = z4.object({
|
|
|
2557
2720
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
2558
2721
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
2559
2722
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
2560
|
-
dueAt:
|
|
2723
|
+
dueAt: z5.string().nullable().default(null),
|
|
2561
2724
|
repeat: NoteRepeatSchema.default("once"),
|
|
2562
2725
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
2563
|
-
rides:
|
|
2564
|
-
lastRideAt:
|
|
2565
|
-
createdAt:
|
|
2726
|
+
rides: z5.number().int().default(0),
|
|
2727
|
+
lastRideAt: z5.string().nullable().default(null),
|
|
2728
|
+
createdAt: z5.string()
|
|
2566
2729
|
});
|
|
2567
|
-
var TriageItemSchema =
|
|
2568
|
-
noteId:
|
|
2730
|
+
var TriageItemSchema = z5.object({
|
|
2731
|
+
noteId: z5.string(),
|
|
2569
2732
|
/** The note's headline at run time. */
|
|
2570
|
-
title:
|
|
2733
|
+
title: z5.string(),
|
|
2571
2734
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
2572
2735
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
2573
2736
|
* Never a model's reasoning transcript, never an id. */
|
|
2574
|
-
why:
|
|
2737
|
+
why: z5.string()
|
|
2575
2738
|
});
|
|
2576
|
-
var TriageAssignmentSchema =
|
|
2739
|
+
var TriageAssignmentSchema = z5.object({
|
|
2577
2740
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
2578
|
-
agent:
|
|
2741
|
+
agent: z5.string(),
|
|
2579
2742
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
2580
2743
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
2581
|
-
agentName:
|
|
2582
|
-
notes:
|
|
2744
|
+
agentName: z5.string(),
|
|
2745
|
+
notes: z5.array(TriageItemSchema)
|
|
2583
2746
|
});
|
|
2584
|
-
var TriageStatusSchema =
|
|
2585
|
-
var SubmitTriageSchema =
|
|
2747
|
+
var TriageStatusSchema = z5.enum(["open", "superseded", "dismissed"]);
|
|
2748
|
+
var SubmitTriageSchema = z5.object({
|
|
2586
2749
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
2587
2750
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
2588
2751
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
2589
2752
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
2590
|
-
provider:
|
|
2753
|
+
provider: z5.string().min(1).max(60),
|
|
2591
2754
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
2592
|
-
model:
|
|
2755
|
+
model: z5.string().max(200).nullable().optional(),
|
|
2593
2756
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
2594
2757
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
2595
|
-
reviewed:
|
|
2596
|
-
close:
|
|
2597
|
-
stale:
|
|
2598
|
-
assign:
|
|
2758
|
+
reviewed: z5.number().int().min(0).max(1e4).default(0),
|
|
2759
|
+
close: z5.array(TriageItemSchema).max(200).default([]),
|
|
2760
|
+
stale: z5.array(TriageItemSchema).max(200).default([]),
|
|
2761
|
+
assign: z5.array(TriageAssignmentSchema).max(50).default([])
|
|
2599
2762
|
});
|
|
2600
2763
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
2601
|
-
id:
|
|
2602
|
-
runAt:
|
|
2764
|
+
id: z5.string(),
|
|
2765
|
+
runAt: z5.string(),
|
|
2603
2766
|
status: TriageStatusSchema,
|
|
2604
|
-
model:
|
|
2767
|
+
model: z5.string().nullable().default(null)
|
|
2605
2768
|
});
|
|
2606
|
-
var AcceptTriageSchema =
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
group:
|
|
2611
|
-
agent:
|
|
2612
|
-
noteIds:
|
|
2769
|
+
var AcceptTriageSchema = z5.discriminatedUnion("group", [
|
|
2770
|
+
z5.object({ group: z5.literal("close"), noteIds: z5.array(z5.string()).max(200).optional() }),
|
|
2771
|
+
z5.object({ group: z5.literal("stale"), noteIds: z5.array(z5.string()).max(200).optional() }),
|
|
2772
|
+
z5.object({
|
|
2773
|
+
group: z5.literal("assign"),
|
|
2774
|
+
agent: z5.string().min(1),
|
|
2775
|
+
noteIds: z5.array(z5.string()).max(200).optional()
|
|
2613
2776
|
})
|
|
2614
2777
|
]);
|
|
2615
|
-
var AcceptTriageResultSchema =
|
|
2616
|
-
accepted:
|
|
2617
|
-
failed:
|
|
2778
|
+
var AcceptTriageResultSchema = z5.object({
|
|
2779
|
+
accepted: z5.array(z5.string()),
|
|
2780
|
+
failed: z5.array(z5.object({ noteId: z5.string(), reason: z5.string() }))
|
|
2618
2781
|
});
|
|
2619
|
-
var DeliveryModeSchema =
|
|
2782
|
+
var DeliveryModeSchema = z5.enum(["poll", "self_hosted"]);
|
|
2620
2783
|
var WAKE_EVENT = "wake";
|
|
2621
2784
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
2622
|
-
var RegisterDeliverySchema =
|
|
2623
|
-
var OAuthStartSchema =
|
|
2624
|
-
provider:
|
|
2625
|
-
returnTo:
|
|
2785
|
+
var RegisterDeliverySchema = z5.object({ mode: DeliveryModeSchema });
|
|
2786
|
+
var OAuthStartSchema = z5.object({
|
|
2787
|
+
provider: z5.enum(["cma"]),
|
|
2788
|
+
returnTo: z5.string().min(1)
|
|
2626
2789
|
});
|
|
2627
|
-
var DeliveryConfigSchema =
|
|
2628
|
-
tokenId:
|
|
2790
|
+
var DeliveryConfigSchema = z5.object({
|
|
2791
|
+
tokenId: z5.string(),
|
|
2629
2792
|
mode: DeliveryModeSchema,
|
|
2630
2793
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
2631
2794
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
2632
2795
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
2633
|
-
realtime:
|
|
2796
|
+
realtime: z5.object({ url: z5.string(), anonKey: z5.string() }).nullable()
|
|
2634
2797
|
});
|
|
2635
|
-
var HostDecisionSchema =
|
|
2798
|
+
var HostDecisionSchema = z5.object({
|
|
2636
2799
|
/** The agent's token id: the row's `recipient`. */
|
|
2637
|
-
agent:
|
|
2638
|
-
decision:
|
|
2800
|
+
agent: z5.string().uuid(),
|
|
2801
|
+
decision: z5.enum(["stood_back", "took_over"]),
|
|
2639
2802
|
/** The work it was about: the Goal `claim_goal` would hand that agent next. */
|
|
2640
|
-
goalId:
|
|
2803
|
+
goalId: z5.string().uuid().nullable().optional(),
|
|
2641
2804
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
2642
|
-
seenAt:
|
|
2805
|
+
seenAt: z5.string().datetime().nullable().optional(),
|
|
2643
2806
|
/** When that work last moved (`claimable.since` on `check_replies`), the fact the bound is judged on. */
|
|
2644
|
-
since:
|
|
2807
|
+
since: z5.string().datetime().nullable().optional(),
|
|
2645
2808
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
2646
|
-
said:
|
|
2809
|
+
said: z5.string().max(300).optional()
|
|
2647
2810
|
});
|
|
2648
|
-
var WakeNudgeSchema =
|
|
2649
|
-
kind:
|
|
2650
|
-
notificationId:
|
|
2651
|
-
parentId:
|
|
2811
|
+
var WakeNudgeSchema = z5.object({
|
|
2812
|
+
kind: z5.enum(["reply", "request", "callback"]),
|
|
2813
|
+
notificationId: z5.string().optional(),
|
|
2814
|
+
parentId: z5.string()
|
|
2652
2815
|
});
|
|
2653
|
-
var PairingStatusSchema =
|
|
2654
|
-
var DeviceCodeSchema =
|
|
2655
|
-
device_code:
|
|
2656
|
-
user_code:
|
|
2657
|
-
verification_uri:
|
|
2658
|
-
verification_uri_complete:
|
|
2659
|
-
interval:
|
|
2660
|
-
expires_in:
|
|
2816
|
+
var PairingStatusSchema = z5.enum(["pending", "approved", "denied", "expired"]);
|
|
2817
|
+
var DeviceCodeSchema = z5.object({
|
|
2818
|
+
device_code: z5.string(),
|
|
2819
|
+
user_code: z5.string(),
|
|
2820
|
+
verification_uri: z5.string().url(),
|
|
2821
|
+
verification_uri_complete: z5.string().url(),
|
|
2822
|
+
interval: z5.number(),
|
|
2823
|
+
expires_in: z5.number()
|
|
2661
2824
|
});
|
|
2662
|
-
var DeviceInfoSchema =
|
|
2663
|
-
code:
|
|
2825
|
+
var DeviceInfoSchema = z5.object({
|
|
2826
|
+
code: z5.string(),
|
|
2664
2827
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
2665
2828
|
* pre-filling the name field the human can edit. */
|
|
2666
|
-
name:
|
|
2829
|
+
name: z5.string(),
|
|
2667
2830
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
2668
2831
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
2669
2832
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
2670
2833
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
2671
|
-
agent:
|
|
2672
|
-
device:
|
|
2834
|
+
agent: z5.string().optional(),
|
|
2835
|
+
device: z5.string().nullable(),
|
|
2673
2836
|
status: PairingStatusSchema
|
|
2674
2837
|
});
|
|
2675
|
-
var DeviceTokenSchema =
|
|
2676
|
-
access_token:
|
|
2838
|
+
var DeviceTokenSchema = z5.object({
|
|
2839
|
+
access_token: z5.string(),
|
|
2677
2840
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
2678
2841
|
* a default silly name). */
|
|
2679
|
-
name:
|
|
2680
|
-
device:
|
|
2842
|
+
name: z5.string(),
|
|
2843
|
+
device: z5.string().nullable(),
|
|
2681
2844
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
2682
2845
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
2683
|
-
voice:
|
|
2846
|
+
voice: z5.string().nullable().optional(),
|
|
2684
2847
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
2685
2848
|
* that survives a rename. Cached by the host's identity beat. */
|
|
2686
|
-
token_id:
|
|
2849
|
+
token_id: z5.string().nullable().optional(),
|
|
2687
2850
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
2688
2851
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
2689
2852
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
2690
2853
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
2691
|
-
workspace:
|
|
2854
|
+
workspace: z5.string().nullable().optional(),
|
|
2692
2855
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
2693
|
-
harness:
|
|
2694
|
-
session_id:
|
|
2695
|
-
|
|
2856
|
+
harness: z5.enum(["claude", "codex", "agy"]).optional(),
|
|
2857
|
+
session_id: z5.string().uuid().optional(),
|
|
2858
|
+
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
2859
|
+
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
2860
|
+
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
2861
|
+
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
2862
|
+
full_session: z5.string().optional(),
|
|
2863
|
+
uik_pub: z5.string().nullable().optional()
|
|
2696
2864
|
});
|
|
2697
|
-
var SupportRequestSchema =
|
|
2698
|
-
email:
|
|
2699
|
-
message:
|
|
2700
|
-
name:
|
|
2865
|
+
var SupportRequestSchema = z5.object({
|
|
2866
|
+
email: z5.string().email().max(320),
|
|
2867
|
+
message: z5.string().trim().min(1).max(5e3),
|
|
2868
|
+
name: z5.string().trim().max(120).optional()
|
|
2701
2869
|
});
|
|
2702
|
-
var NotificationFeedbackKindSchema =
|
|
2870
|
+
var NotificationFeedbackKindSchema = z5.enum([
|
|
2703
2871
|
"break_down",
|
|
2704
2872
|
// "This should be more than one ask — break it down."
|
|
2705
2873
|
"regenerate_options",
|
|
@@ -2714,116 +2882,116 @@ var NotificationFeedbackKindSchema = z4.enum([
|
|
|
2714
2882
|
// anything else — the note carries it.
|
|
2715
2883
|
]);
|
|
2716
2884
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
2717
|
-
var QuestionRowSchema =
|
|
2885
|
+
var QuestionRowSchema = z5.object({
|
|
2718
2886
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
2719
|
-
id:
|
|
2720
|
-
deliveryId:
|
|
2721
|
-
entryId:
|
|
2887
|
+
id: z5.string(),
|
|
2888
|
+
deliveryId: z5.string(),
|
|
2889
|
+
entryId: z5.string(),
|
|
2722
2890
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
2723
|
-
needId:
|
|
2724
|
-
goalIds:
|
|
2891
|
+
needId: z5.string().nullable(),
|
|
2892
|
+
goalIds: z5.array(z5.string()),
|
|
2725
2893
|
/** The name of the work it is about, when the read could word it. */
|
|
2726
|
-
goalTitle:
|
|
2727
|
-
tokenId:
|
|
2728
|
-
name:
|
|
2729
|
-
title:
|
|
2730
|
-
body:
|
|
2731
|
-
select:
|
|
2732
|
-
options:
|
|
2733
|
-
hasPreview:
|
|
2734
|
-
blocking:
|
|
2735
|
-
askedAt:
|
|
2894
|
+
goalTitle: z5.string().optional(),
|
|
2895
|
+
tokenId: z5.string().optional(),
|
|
2896
|
+
name: z5.string(),
|
|
2897
|
+
title: z5.string(),
|
|
2898
|
+
body: z5.string(),
|
|
2899
|
+
select: z5.enum(["one", "many", "rank", "confirm", "text"]),
|
|
2900
|
+
options: z5.array(SlimOptionSchema),
|
|
2901
|
+
hasPreview: z5.boolean(),
|
|
2902
|
+
blocking: z5.boolean(),
|
|
2903
|
+
askedAt: z5.string().datetime(),
|
|
2736
2904
|
ring: InboxItemSchema.shape.ring,
|
|
2737
|
-
onCall:
|
|
2738
|
-
sealed:
|
|
2905
|
+
onCall: z5.literal(true).optional(),
|
|
2906
|
+
sealed: z5.boolean()
|
|
2739
2907
|
});
|
|
2740
|
-
var WorkStateSchema =
|
|
2741
|
-
var WorkRowSchema =
|
|
2742
|
-
id:
|
|
2743
|
-
parentId:
|
|
2744
|
-
title:
|
|
2908
|
+
var WorkStateSchema = z5.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
2909
|
+
var WorkRowSchema = z5.object({
|
|
2910
|
+
id: z5.string(),
|
|
2911
|
+
parentId: z5.string().nullable(),
|
|
2912
|
+
title: z5.string(),
|
|
2745
2913
|
/** Straight off the Goal. */
|
|
2746
2914
|
state: WorkStateSchema,
|
|
2747
|
-
owner:
|
|
2748
|
-
revision:
|
|
2915
|
+
owner: z5.string().nullable(),
|
|
2916
|
+
revision: z5.number().int(),
|
|
2749
2917
|
/** Open questions on it, counted to 100. */
|
|
2750
|
-
waiting:
|
|
2918
|
+
waiting: z5.number().int(),
|
|
2751
2919
|
/** Held by a gate on work that is not done. */
|
|
2752
|
-
blocked:
|
|
2753
|
-
lastProgressAt:
|
|
2920
|
+
blocked: z5.boolean(),
|
|
2921
|
+
lastProgressAt: z5.string().datetime().nullable(),
|
|
2754
2922
|
/** The line written for its newest progress note, else that note's first words. */
|
|
2755
|
-
line:
|
|
2923
|
+
line: z5.string().nullable(),
|
|
2756
2924
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
2757
|
-
children:
|
|
2758
|
-
createdAt:
|
|
2759
|
-
updatedAt:
|
|
2925
|
+
children: z5.number().int(),
|
|
2926
|
+
createdAt: z5.string().datetime(),
|
|
2927
|
+
updatedAt: z5.string().datetime(),
|
|
2760
2928
|
/** When anything at or under it last moved — the order the list is in. */
|
|
2761
|
-
activeAt:
|
|
2929
|
+
activeAt: z5.string().datetime(),
|
|
2762
2930
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
2763
|
-
sealed:
|
|
2931
|
+
sealed: z5.boolean()
|
|
2764
2932
|
});
|
|
2765
2933
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
2766
2934
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
2767
2935
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
2768
|
-
asking:
|
|
2769
|
-
oldestAskAt:
|
|
2936
|
+
asking: z5.number().int().nullable(),
|
|
2937
|
+
oldestAskAt: z5.string().datetime().nullable(),
|
|
2770
2938
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
2771
2939
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
2772
2940
|
* (`agent_holds`); null when that read failed. */
|
|
2773
|
-
holds:
|
|
2774
|
-
held:
|
|
2941
|
+
holds: z5.array(z5.object({ id: z5.string(), title: z5.string() })).nullable(),
|
|
2942
|
+
held: z5.number().int().nullable(),
|
|
2775
2943
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
2776
2944
|
* while none has, or when that read failed. */
|
|
2777
|
-
cold:
|
|
2945
|
+
cold: z5.string().datetime().nullable(),
|
|
2778
2946
|
/** The newest line of its working log, and when the harness saw it. */
|
|
2779
|
-
line:
|
|
2780
|
-
lineAt:
|
|
2947
|
+
line: z5.string().nullable(),
|
|
2948
|
+
lineAt: z5.string().datetime().nullable()
|
|
2781
2949
|
});
|
|
2782
|
-
var SnapshotSchema =
|
|
2950
|
+
var SnapshotSchema = z5.object({
|
|
2783
2951
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
2784
|
-
at:
|
|
2785
|
-
questions:
|
|
2952
|
+
at: z5.string().datetime(),
|
|
2953
|
+
questions: z5.object({
|
|
2786
2954
|
/** The newest 30 open cards, questions before updates. */
|
|
2787
|
-
items:
|
|
2955
|
+
items: z5.array(QuestionRowSchema),
|
|
2788
2956
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
2789
|
-
total:
|
|
2790
|
-
updates:
|
|
2791
|
-
putOff:
|
|
2957
|
+
total: z5.number().int(),
|
|
2958
|
+
updates: z5.number().int(),
|
|
2959
|
+
putOff: z5.number().int()
|
|
2792
2960
|
}).nullable(),
|
|
2793
|
-
agents:
|
|
2961
|
+
agents: z5.object({
|
|
2794
2962
|
/** Up to 60, most recently seen first. */
|
|
2795
|
-
items:
|
|
2796
|
-
more:
|
|
2963
|
+
items: z5.array(AgentRowSchema),
|
|
2964
|
+
more: z5.boolean()
|
|
2797
2965
|
}).nullable(),
|
|
2798
|
-
work:
|
|
2966
|
+
work: z5.object({
|
|
2799
2967
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
2800
|
-
items:
|
|
2968
|
+
items: z5.array(WorkRowSchema),
|
|
2801
2969
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
2802
2970
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
2803
2971
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
2804
2972
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
2805
|
-
counts:
|
|
2973
|
+
counts: z5.object({ live: z5.number().int(), waiting: z5.number().int(), notStarted: z5.number().int(), done: z5.number().int() })
|
|
2806
2974
|
}).nullable(),
|
|
2807
|
-
you:
|
|
2975
|
+
you: z5.object({
|
|
2808
2976
|
settings: UserSettingsSchema,
|
|
2809
|
-
callable:
|
|
2977
|
+
callable: z5.boolean(),
|
|
2810
2978
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
2811
|
-
computers:
|
|
2979
|
+
computers: z5.array(ComputerRowSchema).nullable()
|
|
2812
2980
|
}).nullable()
|
|
2813
2981
|
});
|
|
2814
|
-
var CallRecapSchema =
|
|
2815
|
-
call:
|
|
2816
|
-
status:
|
|
2817
|
-
startedAt:
|
|
2818
|
-
durationMs:
|
|
2819
|
-
agents:
|
|
2982
|
+
var CallRecapSchema = z5.object({
|
|
2983
|
+
call: z5.object({
|
|
2984
|
+
status: z5.string(),
|
|
2985
|
+
startedAt: z5.string(),
|
|
2986
|
+
durationMs: z5.number().nullable(),
|
|
2987
|
+
agents: z5.array(z5.object({ id: z5.string(), name: z5.string().nullable() }))
|
|
2820
2988
|
}),
|
|
2821
|
-
topics:
|
|
2822
|
-
goalId:
|
|
2823
|
-
title:
|
|
2824
|
-
owner:
|
|
2825
|
-
state:
|
|
2826
|
-
questions:
|
|
2989
|
+
topics: z5.array(z5.object({
|
|
2990
|
+
goalId: z5.string().uuid(),
|
|
2991
|
+
title: z5.string(),
|
|
2992
|
+
owner: z5.string(),
|
|
2993
|
+
state: z5.string(),
|
|
2994
|
+
questions: z5.array(z5.object({ id: z5.string().uuid(), state: z5.string(), title: z5.string() })),
|
|
2827
2995
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `headline` is
|
|
2828
2996
|
* their answer on one line when the call's read wrote one (owner, 2026-10-01: "render them
|
|
2829
2997
|
* summarized like a pre-made option is"), so the row scans like a chosen option and their
|
|
@@ -2831,14 +2999,14 @@ var CallRecapSchema = z4.object({
|
|
|
2831
2999
|
* anything that is not an answer. */
|
|
2832
3000
|
/** `about` is the request the line answered (its question), null for words that answered none —
|
|
2833
3001
|
* the key the screen groups on, so one question is one row however many times it was answered. */
|
|
2834
|
-
lines:
|
|
3002
|
+
lines: z5.array(z5.object({ entryId: z5.string().uuid(), words: z5.string(), headline: z5.string().optional(), about: z5.string().nullable().optional() }))
|
|
2835
3003
|
})),
|
|
2836
|
-
unfiled:
|
|
2837
|
-
more:
|
|
3004
|
+
unfiled: z5.array(z5.object({ lineId: z5.string().uuid(), words: z5.string(), atMs: z5.number() })),
|
|
3005
|
+
more: z5.object({ lines: z5.number(), entries: z5.number(), topics: z5.number() })
|
|
2838
3006
|
});
|
|
2839
3007
|
function sessionSlot(sessionId2) {
|
|
2840
|
-
const
|
|
2841
|
-
return `session:${
|
|
3008
|
+
const id2 = sessionId2 ?? sessionId();
|
|
3009
|
+
return `session:${id2.slice(0, 8)}`;
|
|
2842
3010
|
}
|
|
2843
3011
|
function agentName() {
|
|
2844
3012
|
return process.env.PAIGY_AGENT || sessionSlot();
|
|
@@ -2896,7 +3064,7 @@ function updateSlot(agent2, patch) {
|
|
|
2896
3064
|
}
|
|
2897
3065
|
function slotIdentity(agent2) {
|
|
2898
3066
|
const t = readTokenFile()[agent2];
|
|
2899
|
-
return { name: t?.name ?? null, voice: t?.voice ?? null, tokenId: t?.token_id ?? null, workspace: t?.workspace ?? null, harness: t?.harness ?? null, sessionId: t?.session_id ?? null };
|
|
3067
|
+
return { name: t?.name ?? null, voice: t?.voice ?? null, tokenId: t?.token_id ?? null, workspace: t?.workspace ?? null, harness: t?.harness ?? null, sessionId: t?.session_id ?? null, fullSession: t?.full_session ?? null };
|
|
2900
3068
|
}
|
|
2901
3069
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2902
3070
|
function readToken(agent2 = agentName()) {
|
|
@@ -3118,7 +3286,9 @@ async function recordDecision(decision, opts = {}) {
|
|
|
3118
3286
|
async function heartbeat(runtime, opts = {}) {
|
|
3119
3287
|
const body = {
|
|
3120
3288
|
...runtime !== void 0 ? { runtime } : {},
|
|
3121
|
-
...opts.activity !== void 0 ? { activity: opts.activity } : {}
|
|
3289
|
+
...opts.activity !== void 0 ? { activity: opts.activity } : {},
|
|
3290
|
+
// A listener's beat (`paigy-listen`): stamps when it last swept, and is not presence.
|
|
3291
|
+
...opts.listening ? { listening: true } : {}
|
|
3122
3292
|
};
|
|
3123
3293
|
const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/presence`, {
|
|
3124
3294
|
method: "POST",
|
|
@@ -3217,6 +3387,7 @@ function currentRepo(cwd = process.cwd()) {
|
|
|
3217
3387
|
return asked;
|
|
3218
3388
|
}
|
|
3219
3389
|
var NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
|
|
3390
|
+
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, with or without a Goal.";
|
|
3220
3391
|
async function contact(input, opts = {}) {
|
|
3221
3392
|
const parsed = ContactSchema.parse(input);
|
|
3222
3393
|
opts.signal?.throwIfAborted();
|
|
@@ -3225,6 +3396,7 @@ async function contact(input, opts = {}) {
|
|
|
3225
3396
|
let deliveryId;
|
|
3226
3397
|
let deliveries = [];
|
|
3227
3398
|
let recorded = [];
|
|
3399
|
+
let demoted;
|
|
3228
3400
|
if ("deliveryId" in parsed) {
|
|
3229
3401
|
deliveryId = parsed.deliveryId;
|
|
3230
3402
|
} else {
|
|
@@ -3255,13 +3427,14 @@ async function contact(input, opts = {}) {
|
|
|
3255
3427
|
if (!res.ok) await fail("batch contact", res);
|
|
3256
3428
|
const receipt = await res.json();
|
|
3257
3429
|
deliveries = receipt.deliveries ?? [];
|
|
3430
|
+
if (parsed.channel === "call" && receipt.channel && receipt.channel !== "call") demoted = DEMOTED;
|
|
3258
3431
|
recorded = receipt.recorded ?? [];
|
|
3259
3432
|
if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
|
|
3260
3433
|
if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
|
|
3261
3434
|
deliveryId = deliveries[0]?.deliveryId ?? "";
|
|
3262
3435
|
if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
|
|
3263
3436
|
}
|
|
3264
|
-
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...
|
|
3437
|
+
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id: id2, deliveryId: deliveryId2, goalId }) => ({ ...id2 ? { id: id2 } : {}, deliveryId: deliveryId2, goalId }));
|
|
3265
3438
|
const joinedCard = deliveries[0]?.joinedCard === true;
|
|
3266
3439
|
const joinedCall = deliveries[0]?.joinedCall === true;
|
|
3267
3440
|
const read = async (signal2) => {
|
|
@@ -3273,7 +3446,8 @@ async function contact(input, opts = {}) {
|
|
|
3273
3446
|
...all.length > 1 ? { deliveries: all } : {},
|
|
3274
3447
|
...recorded.length ? { recorded, notSent: NOT_SENT } : {},
|
|
3275
3448
|
...joinedCard ? { joinedCard: true } : {},
|
|
3276
|
-
...joinedCall ? { joinedCall: true } : {}
|
|
3449
|
+
...joinedCall ? { joinedCall: true } : {},
|
|
3450
|
+
...demoted ? { demoted } : {}
|
|
3277
3451
|
};
|
|
3278
3452
|
};
|
|
3279
3453
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
@@ -3304,7 +3478,7 @@ async function checkReplies(opts = {}) {
|
|
|
3304
3478
|
function compact(o) {
|
|
3305
3479
|
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
|
|
3306
3480
|
}
|
|
3307
|
-
var who = (
|
|
3481
|
+
var who = (participant2) => participant2.startsWith("human:") ? "person" : isPaigy(participant2) ? "paigy" : participant2.startsWith("agent:") ? "agent" : participant2;
|
|
3308
3482
|
var at = (iso) => {
|
|
3309
3483
|
const t = Date.parse(iso);
|
|
3310
3484
|
return Number.isFinite(t) ? new Date(t).toISOString().replace(/\.\d{3}Z$/, "Z") : iso;
|
|
@@ -3364,7 +3538,7 @@ function goalView(g) {
|
|
|
3364
3538
|
if (!g.goalId) return compact({ state: g.state, next: g.message });
|
|
3365
3539
|
const lines2 = conversation(g);
|
|
3366
3540
|
const owed = lines2.filter((l) => l.from === "person" && l.decision?.state === "open" && l.id);
|
|
3367
|
-
const owes = owed.length ? `The person asked you ${owed.length === 1 ? "a question" : `${owed.length} questions`} (${owed.map((l) => `id ${l.id}: "${l.said.slice(0, 80)}"`).join("; ")}) \u2014 answer with contact({ asks: [{ ask: <your answer>,
|
|
3541
|
+
const owes = owed.length ? `The person asked you ${owed.length === 1 ? "a question" : `${owed.length} questions`} (${owed.map((l) => `id ${l.id}: "${l.said.slice(0, 80)}"`).join("; ")}) \u2014 answer with contact({ asks: [{ ask: <your answer>, answers: "<id>" }] }). ` : "";
|
|
3368
3542
|
const newest = [...lines2].reverse().find((l) => l.from === "agent");
|
|
3369
3543
|
return compact({
|
|
3370
3544
|
goalId: g.goalId,
|
|
@@ -3411,7 +3585,7 @@ function deliveryView(d) {
|
|
|
3411
3585
|
notSent: d.notSent,
|
|
3412
3586
|
joinedCard: d.joinedCard,
|
|
3413
3587
|
joinedCall: d.joinedCall,
|
|
3414
|
-
next: d.joinedCall ? `${JOINED_CALL}${d.message ? ` ${d.message}` : ""}` : d.kind === "notification" ? d.joinedCard ? `Added to the open report card on its Goal; no new push was sent. ${answers}` : answers : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with check_replies or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
|
|
3588
|
+
next: 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}` : d.state === "open" && d.decisionNeeds.some((n) => n.state === "open") ? `Decision pending. Call contact(${JSON.stringify({ deliveryId: d.deliveryId })}) to continue this same Call \u2014 each call is ONE bounded window, and never resend the ask. When a window comes back with nothing new said, they are not typing: stop rereading, leave the question open, and collect the answer with check_replies or claim_goal on your next wake. ${d.message ?? ""}`.trim() : d.message
|
|
3415
3589
|
});
|
|
3416
3590
|
}
|
|
3417
3591
|
function repliesView(r) {
|
|
@@ -3611,9 +3785,9 @@ function connect(line) {
|
|
|
3611
3785
|
ws.addEventListener("error", again);
|
|
3612
3786
|
}
|
|
3613
3787
|
function subscribeRealtime(args) {
|
|
3614
|
-
const
|
|
3615
|
-
let line = lines.get(
|
|
3616
|
-
if (!line) lines.set(
|
|
3788
|
+
const key2 = `${args.url} ${args.anonKey}`;
|
|
3789
|
+
let line = lines.get(key2);
|
|
3790
|
+
if (!line) lines.set(key2, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
|
|
3617
3791
|
let t = line.topics.get(args.topic);
|
|
3618
3792
|
if (!t) {
|
|
3619
3793
|
line.topics.set(args.topic, t = { listeners: /* @__PURE__ */ new Set(), joined: false, attempt: 0 });
|
|
@@ -3635,7 +3809,7 @@ function subscribeRealtime(args) {
|
|
|
3635
3809
|
if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
|
|
3636
3810
|
return;
|
|
3637
3811
|
}
|
|
3638
|
-
lines.delete(
|
|
3812
|
+
lines.delete(key2);
|
|
3639
3813
|
stop(line);
|
|
3640
3814
|
try {
|
|
3641
3815
|
line.ws?.close();
|
|
@@ -3679,6 +3853,28 @@ function machineZone() {
|
|
|
3679
3853
|
return null;
|
|
3680
3854
|
}
|
|
3681
3855
|
}
|
|
3856
|
+
function withCodexEnv(text) {
|
|
3857
|
+
const head = /^\[mcp_servers\.paigy\][ \t]*(?:#[^\n]*)?\r?\n/m.exec(text);
|
|
3858
|
+
if (!head) return null;
|
|
3859
|
+
const start = head.index + head[0].length;
|
|
3860
|
+
const next = /^\[/m.exec(text.slice(start));
|
|
3861
|
+
const end = next ? start + next.index : text.length;
|
|
3862
|
+
const block = text.slice(start, end);
|
|
3863
|
+
const assignment = /^[ \t]*env_vars[ \t]*=/m.exec(block);
|
|
3864
|
+
let updated;
|
|
3865
|
+
if (!assignment) updated = `env_vars = [${CODEX_ENV.map((v) => JSON.stringify(v)).join(", ")}]
|
|
3866
|
+
${block}`;
|
|
3867
|
+
else {
|
|
3868
|
+
const array = /^[ \t]*env_vars[ \t]*=[ \t]*\[((?:#[^\n]*|[^\]"'#]|"[^"\n]*"|'[^'\n]*')*)\]/m.exec(block);
|
|
3869
|
+
if (!array) return null;
|
|
3870
|
+
const existing = [...array[1].matchAll(/"([^"\n]*)"|'([^'\n]*)'|#[^\n]*/g)].flatMap((m) => m[0].startsWith("#") ? [] : [m[1] ?? m[2]]);
|
|
3871
|
+
const missing = CODEX_ENV.filter((v) => !existing.includes(v));
|
|
3872
|
+
if (!missing.length) return text;
|
|
3873
|
+
const opening = array.index + array[0].indexOf("[") + 1;
|
|
3874
|
+
updated = block.slice(0, opening) + missing.map((v) => JSON.stringify(v)).join(", ") + (existing.length ? ", " : "") + block.slice(opening);
|
|
3875
|
+
}
|
|
3876
|
+
return text.slice(0, start) + updated + text.slice(end);
|
|
3877
|
+
}
|
|
3682
3878
|
|
|
3683
3879
|
export {
|
|
3684
3880
|
CODEX_ENV,
|
|
@@ -3688,6 +3884,7 @@ export {
|
|
|
3688
3884
|
isNetworkError,
|
|
3689
3885
|
reach,
|
|
3690
3886
|
reachAs,
|
|
3887
|
+
setClient,
|
|
3691
3888
|
AGENT_TOOLS,
|
|
3692
3889
|
AGENT_TOOL_NAMES,
|
|
3693
3890
|
NotifyRequestSchema,
|
|
@@ -3741,5 +3938,6 @@ export {
|
|
|
3741
3938
|
checkReplies,
|
|
3742
3939
|
runTool,
|
|
3743
3940
|
subscribeWake,
|
|
3744
|
-
parseDue
|
|
3941
|
+
parseDue,
|
|
3942
|
+
withCodexEnv
|
|
3745
3943
|
};
|