@paigy/mcp 0.40.32 → 0.40.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/{chunk-QJCRISSA.js → chunk-5YTQF6KQ.js} +933 -808
- package/dist/{chunk-4YUPH4JS.js → chunk-7ZM5RGW4.js} +1 -1
- package/dist/{chunk-5F3ND6WV.js → chunk-VVQ4OQOB.js} +6 -3
- package/dist/{chunk-EU6NEQ2W.js → chunk-YIMNBJEF.js} +853 -775
- package/dist/{dist-Z4KVBUBO.js → dist-HRRH3KO4.js} +3 -3
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +7 -7
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
// ../../packages/schema/dist/index.js
|
|
2
|
-
import { z as
|
|
2
|
+
import { z as z12 } from "zod";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { z as z3 } from "zod";
|
|
5
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
6
5
|
import { z as z2 } from "zod";
|
|
6
|
+
import { z as z5 } from "zod";
|
|
7
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
7
8
|
import { z as z4 } from "zod";
|
|
9
|
+
import { z as z6 } from "zod";
|
|
8
10
|
import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
|
|
9
|
-
import { z as
|
|
11
|
+
import { z as z7 } from "zod";
|
|
10
12
|
import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
|
|
11
|
-
import { z as
|
|
13
|
+
import { z as z8 } from "zod";
|
|
12
14
|
import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
|
|
13
|
-
import { z as
|
|
15
|
+
import { z as z9 } from "zod";
|
|
14
16
|
import { zodToJsonSchema as zodToJsonSchema5 } from "zod-to-json-schema";
|
|
15
|
-
import { z as
|
|
17
|
+
import { z as z10 } from "zod";
|
|
16
18
|
import { zodToJsonSchema as zodToJsonSchema6 } from "zod-to-json-schema";
|
|
19
|
+
import { z as z11 } from "zod";
|
|
17
20
|
var OPTIONS_MIN = 2;
|
|
18
21
|
var OPTIONS_MAX = 6;
|
|
19
22
|
var OPTION_LABEL_MAX = 50;
|
|
@@ -43,35 +46,6 @@ var OptionInputSchema = z.object({
|
|
|
43
46
|
imgUrl: OptionSchema.shape.image
|
|
44
47
|
}).strict();
|
|
45
48
|
var NIGHT = { from: 23, to: 7 };
|
|
46
|
-
function draft2020(node) {
|
|
47
|
-
if (Array.isArray(node)) return node.map(draft2020);
|
|
48
|
-
if (node && typeof node === "object") {
|
|
49
|
-
const o = node;
|
|
50
|
-
if (Array.isArray(o.items)) {
|
|
51
|
-
o.prefixItems = o.items;
|
|
52
|
-
if ("additionalItems" in o) {
|
|
53
|
-
o.items = o.additionalItems;
|
|
54
|
-
delete o.additionalItems;
|
|
55
|
-
} else delete o.items;
|
|
56
|
-
}
|
|
57
|
-
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
58
|
-
if (typeof o[excl] === "boolean") {
|
|
59
|
-
if (o[excl] === true && typeof o[lim] === "number") {
|
|
60
|
-
o[excl] = o[lim];
|
|
61
|
-
delete o[lim];
|
|
62
|
-
} else delete o[excl];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
for (const k of Object.keys(o)) o[k] = draft2020(o[k]);
|
|
66
|
-
return o;
|
|
67
|
-
}
|
|
68
|
-
return node;
|
|
69
|
-
}
|
|
70
|
-
function mcpInputSchema(s) {
|
|
71
|
-
const schema = zodToJsonSchema(s, { target: "jsonSchema2019-09", $refStrategy: "none" });
|
|
72
|
-
delete schema.$schema;
|
|
73
|
-
return draft2020(schema);
|
|
74
|
-
}
|
|
75
49
|
var ASK_MAX = 250;
|
|
76
50
|
var UNITS_MAX = 12;
|
|
77
51
|
var UNIT_TITLE_MAX = 50;
|
|
@@ -121,24 +95,78 @@ function capRefusals(text, units2, field) {
|
|
|
121
95
|
function refuseOverCaps(text, units2, field, ctx) {
|
|
122
96
|
for (const { path, message, ...params } of capRefusals(text, units2, field)) ctx.addIssue({ code: z2.ZodIssueCode.custom, path, message, params });
|
|
123
97
|
}
|
|
124
|
-
var
|
|
125
|
-
var
|
|
126
|
-
|
|
127
|
-
|
|
98
|
+
var COMPLETION_MESSAGE_MAX = 50;
|
|
99
|
+
var CompletionSchema = z3.object({
|
|
100
|
+
goalId: z3.string().uuid().describe("The existing Goal to complete."),
|
|
101
|
+
message: z3.string().trim().min(1).max(COMPLETION_MESSAGE_MAX, "completion_message_too_long: at most 50 characters; put detail in units.").describe("Completion headline, at most 50 characters."),
|
|
102
|
+
units: z3.array(UnitInputSchema.extend({
|
|
103
|
+
title: z3.string().trim().min(1).max(UNIT_TITLE_MAX),
|
|
104
|
+
body: z3.string().trim().min(1).max(UNIT_BODY_MAX)
|
|
105
|
+
})).max(UNITS_MAX).optional().describe("Suggest initial state, changes, current state with verification and limits, and next steps / leftover work with its owner when useful. One idea per unit; omit unnecessary sections."),
|
|
106
|
+
reply: z3.string().trim().min(1).max(OPTION_LABEL_MAX).describe("One acknowledgement in the person's voice, at most 50 characters. Acknowledgement alone requests no follow-up and does not claim they tested the work."),
|
|
107
|
+
options: z3.array(z3.object({
|
|
108
|
+
label: z3.string().trim().min(1).max(OPTION_LABEL_MAX),
|
|
109
|
+
hint: z3.string().trim().min(1).max(OPTION_HINT_MAX)
|
|
110
|
+
}).strict()).max(5).optional().describe("Optional next steps the person may initiate: label (50 characters) and consequence in hint (200). Selecting one acknowledges the report and requests that step from its author; it never reopens this Goal.")
|
|
111
|
+
}).strict();
|
|
112
|
+
var CompleteChangeSchema = CompletionSchema.extend({ kind: z3.literal("complete") });
|
|
113
|
+
var ASKING_RULES = `HOW TO ASK:
|
|
114
|
+
1. Write one question per object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
115
|
+
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
116
|
+
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
117
|
+
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. While a Call you placed is open, keep receiving window after window until it ends, even when a window is empty: the person may give follow-ups or instructions. With no open Call, STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goals\` on that wake.
|
|
118
|
+
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
119
|
+
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
120
|
+
7. A question carries its options when you have them; a free-text question may omit them. Each option\u2019s label must stand on its own and name its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Each question is sent exactly as written, with no reading in between. They can always answer in their own words, so never add an 'Other' option.
|
|
121
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
|
|
122
|
+
9. GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. What they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Requested delivery and completed-Goal reports require a reply (reply_required); ordinary progress may omit it.`;
|
|
123
|
+
function draft2020(node) {
|
|
124
|
+
if (Array.isArray(node)) return node.map(draft2020);
|
|
125
|
+
if (node && typeof node === "object") {
|
|
126
|
+
const o = node;
|
|
127
|
+
if (Array.isArray(o.items)) {
|
|
128
|
+
o.prefixItems = o.items;
|
|
129
|
+
if ("additionalItems" in o) {
|
|
130
|
+
o.items = o.additionalItems;
|
|
131
|
+
delete o.additionalItems;
|
|
132
|
+
} else delete o.items;
|
|
133
|
+
}
|
|
134
|
+
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
135
|
+
if (typeof o[excl] === "boolean") {
|
|
136
|
+
if (o[excl] === true && typeof o[lim] === "number") {
|
|
137
|
+
o[excl] = o[lim];
|
|
138
|
+
delete o[lim];
|
|
139
|
+
} else delete o[excl];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const k of Object.keys(o)) o[k] = draft2020(o[k]);
|
|
143
|
+
return o;
|
|
144
|
+
}
|
|
145
|
+
return node;
|
|
146
|
+
}
|
|
147
|
+
function mcpInputSchema(s) {
|
|
148
|
+
const schema = zodToJsonSchema(s, { target: "jsonSchema2019-09", $refStrategy: "none" });
|
|
149
|
+
delete schema.$schema;
|
|
150
|
+
return draft2020(schema);
|
|
151
|
+
}
|
|
152
|
+
var questionHandle = z4.string().trim().regex(/^[0-9a-fA-F-]{8,36}$/, "a question id, whole or its first eight characters");
|
|
153
|
+
var NodeRef = z4.object({
|
|
154
|
+
type: z4.enum(["goal", "question"]),
|
|
155
|
+
id: z4.string().uuid()
|
|
128
156
|
}).strict().describe("A Goal or a Question, by its id.");
|
|
129
|
-
var BlockedAction =
|
|
130
|
-
var reason =
|
|
157
|
+
var BlockedAction = z4.enum(["start", "complete", "answer"]).describe("What waits: starting or completing a Goal, or answering a Question.");
|
|
158
|
+
var reason = z4.string().trim().min(1).max(500).describe("Why, as an explanation, never as policy.");
|
|
131
159
|
var actionFits = (b) => b.blocked.type === "goal" === (b.action !== "answer");
|
|
132
160
|
var ACTION_FITS = "a goal is blocked from start or complete; a question from answer";
|
|
133
|
-
var userExplicitlyRequested =
|
|
161
|
+
var userExplicitlyRequested = z4.enum(["any", "call"]).optional().describe(
|
|
134
162
|
`Only what the person said about THIS message, in so many words: "any" when they asked you to send it to them ("send it to my phone", "message me the result"), "call" when they asked to be called about it. It is then delivered (a call rings for the whole contact, and carries everything waiting on them); omitted, an update is only recorded as the Goal's progress unless the person needs it. Never set it to make a message louder.`
|
|
135
163
|
);
|
|
136
|
-
var units =
|
|
164
|
+
var units = z4.array(UnitInputSchema).optional().describe(
|
|
137
165
|
`The context this message needs, as up to ${UNITS_MAX} units in reading order, each {title, body}: a title of at most ${UNIT_TITLE_MAX} characters and a body of at most ${UNIT_BODY_MAX}. One idea per unit. Leave it out when the main text says it all.`
|
|
138
166
|
);
|
|
139
|
-
var UpdateInputSchema =
|
|
140
|
-
message:
|
|
141
|
-
goalIds:
|
|
167
|
+
var UpdateInputSchema = z4.object({
|
|
168
|
+
message: z4.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
169
|
+
goalIds: z4.array(z4.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
142
170
|
units,
|
|
143
171
|
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
144
172
|
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
@@ -160,15 +188,23 @@ var UpdateInputSchema = z2.object({
|
|
|
160
188
|
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
161
189
|
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
162
190
|
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
163
|
-
reply:
|
|
164
|
-
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer.
|
|
191
|
+
reply: z4.string().trim().min(1).describe(
|
|
192
|
+
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer. The reply is required for requested delivery and completed Goals; ordinary progress may omit it.`
|
|
165
193
|
).optional(),
|
|
166
194
|
userExplicitlyRequested
|
|
167
195
|
}).strict().superRefine((u, ctx) => {
|
|
196
|
+
if (u.userExplicitlyRequested && !u.reply) {
|
|
197
|
+
ctx.addIssue({
|
|
198
|
+
code: z4.ZodIssueCode.custom,
|
|
199
|
+
path: ["reply"],
|
|
200
|
+
params: { refusal: "reply_required" },
|
|
201
|
+
message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
|
|
202
|
+
});
|
|
203
|
+
}
|
|
168
204
|
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
169
205
|
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
170
206
|
ctx.addIssue({
|
|
171
|
-
code:
|
|
207
|
+
code: z4.ZodIssueCode.custom,
|
|
172
208
|
path: ["reply"],
|
|
173
209
|
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX },
|
|
174
210
|
message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX}. It is a button's words, so say it in a breath.`
|
|
@@ -176,100 +212,99 @@ var UpdateInputSchema = z2.object({
|
|
|
176
212
|
}
|
|
177
213
|
});
|
|
178
214
|
var questionFields = {
|
|
179
|
-
question:
|
|
215
|
+
question: z4.string().trim().min(1).describe(
|
|
180
216
|
`ONE question, in at most ${ASK_MAX} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
181
217
|
),
|
|
182
|
-
goalId:
|
|
218
|
+
goalId: z4.string().uuid().describe("The Goal the question is about, which must exist (create it with manage_goals first). Its answer comes back there."),
|
|
183
219
|
units,
|
|
184
|
-
options:
|
|
220
|
+
options: z4.array(OptionInputSchema).min(1).max(6).optional().describe("The choices, when you have them. Each label stands on its own, at most 50 characters; a hint, at most 200."),
|
|
185
221
|
// HOW THE OPTIONS ARE ANSWERED, SAID BY THE AGENT (owner, 2026-10-02: "one and many makes sense";
|
|
186
222
|
// brain_prompts.md §1: structured agent questions need no model). The app draws all three.
|
|
187
|
-
pickMode:
|
|
223
|
+
pickMode: z4.enum(["one", "many", "rank"]).optional().describe(
|
|
188
224
|
'How the options are answered: "one" (the default) a pick, for alternatives; "many" a checklist, for independent options they may want several of; "rank" an order of all of them. Only with options.'
|
|
189
225
|
),
|
|
190
226
|
// WHAT THE QUESTION HOLDS UP (brain_prompts.md §2.1): replaces `waiting: 'hard'`. A question that
|
|
191
227
|
// blocks work asks for a call (the person's settings and ring cooldowns still decide).
|
|
192
|
-
workItBlocks:
|
|
228
|
+
workItBlocks: z4.array(z4.object({ blocked: NodeRef, action: BlockedAction, reason: reason.optional() }).strict().refine(actionFits, ACTION_FITS)).min(1).max(10).optional().describe(
|
|
193
229
|
"The work that cannot go on until this is answered: a Goal's start or completion, or another Question's answer. Only for work truly blocked; it asks for a call. Leave it out for a question you can keep working around."
|
|
194
230
|
),
|
|
195
231
|
userExplicitlyRequested
|
|
196
232
|
};
|
|
197
233
|
var refineQuestion = (q, ctx) => {
|
|
198
234
|
refuseOverCaps(q.question, q.units, "question", ctx);
|
|
199
|
-
if (q.pickMode && !q.options) ctx.addIssue({ code:
|
|
235
|
+
if (q.pickMode && !q.options) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["pickMode"], message: "pickMode comes only with options" });
|
|
200
236
|
const ids = (q.options ?? []).map((o) => o.id);
|
|
201
237
|
if (ids.some((id2) => id2) && (ids.some((id2) => !id2) || new Set(ids).size !== ids.length)) {
|
|
202
|
-
ctx.addIssue({ code:
|
|
238
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: "give every option a distinct id, or none" });
|
|
203
239
|
}
|
|
204
240
|
};
|
|
205
|
-
var QuestionInputSchema =
|
|
206
|
-
questionId:
|
|
241
|
+
var QuestionInputSchema = z4.object({
|
|
242
|
+
questionId: z4.string().uuid().optional().describe("Optional: your own stable id for this question (a UUID), so a resend is the same question. Minted for you when omitted; the result returns it."),
|
|
207
243
|
...questionFields
|
|
208
244
|
}).strict().superRefine(refineQuestion);
|
|
209
|
-
var QuestionWireSchema =
|
|
210
|
-
var AnswerInputSchema =
|
|
245
|
+
var QuestionWireSchema = z4.object({ questionId: z4.string().uuid(), ...questionFields }).strict().superRefine(refineQuestion);
|
|
246
|
+
var AnswerInputSchema = z4.object({
|
|
211
247
|
questionId: questionHandle.describe("The Question you answer: its id as the event or the conversation shows it (8 characters or whole)."),
|
|
212
|
-
answer:
|
|
213
|
-
text:
|
|
214
|
-
selectedOptionIds:
|
|
248
|
+
answer: z4.object({
|
|
249
|
+
text: z4.string().trim().min(1).describe(`Your answer in your own words, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
250
|
+
selectedOptionIds: z4.array(z4.string().trim().min(1)).min(1).max(6).optional().describe("The ids of the options you choose, when the Question offered options (in order, for a ranking).")
|
|
215
251
|
}).strict(),
|
|
216
252
|
units
|
|
217
253
|
}).strict().superRefine((a, ctx) => refuseOverCaps(a.answer.text, a.units, "text", ctx));
|
|
218
|
-
var QuestionDependencySchema =
|
|
254
|
+
var QuestionDependencySchema = z4.object({
|
|
219
255
|
questionId: questionHandle.describe("A Question you asked."),
|
|
220
|
-
change:
|
|
256
|
+
change: z4.enum(["add", "remove"]),
|
|
221
257
|
blocked: NodeRef,
|
|
222
258
|
action: BlockedAction,
|
|
223
259
|
reason: reason.optional()
|
|
224
260
|
}).strict().refine(actionFits, ACTION_FITS);
|
|
225
261
|
var sendFields = {
|
|
226
|
-
|
|
262
|
+
completions: z4.array(CompletionSchema).min(1).max(20).optional().describe("Close existing Goals and deliver their completion reports atomically. Ordinary updates never close work."),
|
|
263
|
+
updates: z4.array(UpdateInputSchema).min(1).max(20).optional().describe(
|
|
227
264
|
"News: progress, findings, a result. An update asks nothing. It reaches the person only when they asked for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; otherwise it is recorded as the Goal's progress and nobody is notified."
|
|
228
265
|
),
|
|
229
|
-
answers:
|
|
230
|
-
withdrawQuestionIds:
|
|
266
|
+
answers: z4.array(AnswerInputSchema).min(1).max(20).optional().describe("Answers to Questions addressed to you."),
|
|
267
|
+
withdrawQuestionIds: z4.array(questionHandle).min(1).max(10).optional().describe(
|
|
231
268
|
"Questions you asked that no longer need an answer: you acted on them yourself, they no longer matter, or you asked wrongly. Each is cancelled, not answered: its card closes and nothing rings for it."
|
|
232
269
|
),
|
|
233
|
-
questionDependencies:
|
|
270
|
+
questionDependencies: z4.array(QuestionDependencySchema).min(1).max(20).optional().describe(
|
|
234
271
|
"Add or remove what a Question you asked holds up, after asking it."
|
|
235
272
|
)
|
|
236
273
|
};
|
|
237
|
-
var ContactSchema =
|
|
274
|
+
var ContactSchema = z4.object({
|
|
238
275
|
...sendFields,
|
|
239
|
-
questions:
|
|
276
|
+
questions: z4.array(QuestionInputSchema).min(1).max(20).optional().describe("Questions for the person, one per object."),
|
|
240
277
|
/** RECEIVING (2I, I2; brain_prompts.md §2.2): the events this agent handled. */
|
|
241
|
-
ackEventIds:
|
|
278
|
+
ackEventIds: z4.array(z4.string().uuid()).min(1).max(100).optional().describe(
|
|
242
279
|
"The exact eventIds of the events you have handled: an acknowledgment (ACK). Reading alone acknowledges nothing, so an event comes back until you acknowledge it; after that the next batch can come. Each recipient acknowledges separately, for itself only. A Question you owe is answered (answers), never acknowledged away."
|
|
243
280
|
),
|
|
244
|
-
wait:
|
|
281
|
+
wait: z4.boolean().optional().describe(
|
|
245
282
|
"Only controls when this returns. Sending: false (the default) returns at once; true waits up to about 45 seconds for a response to what you sent. Sending nothing: true (the default) waits up to about 45 seconds for anything addressed to you; false returns what is waiting now. It does not mean work is blocked, set urgency, or ask for a call. `waitOutcome` says which happened: `available`, `expired` (nothing arrived; wait again without sending again), or `not_waited`."
|
|
246
283
|
)
|
|
247
284
|
}).strict();
|
|
248
|
-
var sendsAnything = (c) => !!(c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
249
|
-
var ContactWireSchema =
|
|
250
|
-
operationId:
|
|
285
|
+
var sendsAnything = (c) => !!(c.completions?.length || c.updates?.length || c.questions?.length || c.answers?.length || c.withdrawQuestionIds?.length || c.questionDependencies?.length);
|
|
286
|
+
var ContactWireSchema = z4.object({
|
|
287
|
+
operationId: z4.string().uuid(),
|
|
251
288
|
...sendFields,
|
|
252
|
-
questions:
|
|
289
|
+
questions: z4.array(QuestionWireSchema).min(1).max(20).optional()
|
|
253
290
|
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
254
291
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
255
|
-
var CONTACT_DESCRIPTION =
|
|
256
|
-
|
|
257
|
-
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
292
|
+
var CONTACT_DESCRIPTION = "Send to and receive from the person: completions, updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read `results`, where a failed item names why while the rest still apply.\n\n" + ASKING_RULES + `
|
|
258
293
|
|
|
259
294
|
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
260
295
|
|
|
261
|
-
|
|
296
|
+
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. Ordinary updates never close a Goal. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
262
297
|
|
|
263
|
-
|
|
298
|
+
COMPLETIONS close and report in one operation: completions:[{goalId,message,units?,reply,options?}]. The same contract works in manage_goals changes with kind:complete. message is at most 50 characters; use units when useful for initial state, changes, current state (verification and limits), and next steps / leftover work with its owner. reply is one acknowledgement in the person's voice. Optional options:[{label,hint}] suggest next steps: a tap acknowledges the report and requests that step from its author. It never reopens the completed Goal; follow-up work needs a new Goal. Closure and the report commit together or neither does. Required children and dependencies must already be finished. No Reviewer rejudges your completion; say only what your evidence supports.
|
|
264
299
|
|
|
265
|
-
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with
|
|
266
|
-
var CreateGoalSchema =
|
|
267
|
-
outcome:
|
|
300
|
+
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goals); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
301
|
+
var CreateGoalSchema = z5.object({
|
|
302
|
+
outcome: z5.string().trim().min(1).max(1e4),
|
|
268
303
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
269
304
|
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
270
|
-
title:
|
|
271
|
-
ownerParticipant:
|
|
272
|
-
idempotencyKey:
|
|
305
|
+
title: z5.string().trim().min(1).max(80).optional(),
|
|
306
|
+
ownerParticipant: z5.string().trim().min(1).optional(),
|
|
307
|
+
idempotencyKey: z5.string().trim().min(1).max(200),
|
|
273
308
|
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
274
309
|
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
275
310
|
* worked"). It could not have been: this door took no parent, so the only route was
|
|
@@ -277,64 +312,65 @@ var CreateGoalSchema = z3.object({
|
|
|
277
312
|
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
278
313
|
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
279
314
|
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
280
|
-
parentGoalId:
|
|
315
|
+
parentGoalId: z5.string().uuid().optional(),
|
|
281
316
|
/** The repository or project identifier this Goal belongs to (#2280) — e.g. "owner/repo" or
|
|
282
317
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
283
|
-
repo:
|
|
318
|
+
repo: z5.string().trim().min(1).max(200).optional()
|
|
284
319
|
});
|
|
285
|
-
var UpdateGoalSchema =
|
|
286
|
-
revision:
|
|
287
|
-
changes:
|
|
288
|
-
outcome:
|
|
320
|
+
var UpdateGoalSchema = z5.object({
|
|
321
|
+
revision: z5.number().int().positive(),
|
|
322
|
+
changes: z5.object({
|
|
323
|
+
outcome: z5.string().trim().min(1).max(1e4).optional(),
|
|
289
324
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
290
325
|
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
291
|
-
title:
|
|
292
|
-
ownerParticipant:
|
|
293
|
-
parentGoalId:
|
|
294
|
-
dependencies:
|
|
295
|
-
children:
|
|
296
|
-
state:
|
|
297
|
-
progress:
|
|
298
|
-
reviewed:
|
|
299
|
-
dueAt:
|
|
326
|
+
title: z5.string().trim().min(1).max(80).nullable().optional(),
|
|
327
|
+
ownerParticipant: z5.string().trim().min(1).optional(),
|
|
328
|
+
parentGoalId: z5.string().uuid().nullable().optional(),
|
|
329
|
+
dependencies: z5.array(z5.object({ goalId: z5.string().uuid(), gate: z5.enum(["start", "finish"]) }).strict()).optional(),
|
|
330
|
+
children: z5.array(z5.object({ outcome: z5.string().trim().min(1).max(1e4), ownerParticipant: z5.string().trim().min(1), gate: z5.enum(["start", "finish"]).optional() }).strict()).optional(),
|
|
331
|
+
state: z5.enum(["active", "done", "cancelled"]).optional(),
|
|
332
|
+
progress: z5.string().trim().min(1).max(1e4).optional(),
|
|
333
|
+
reviewed: z5.literal(true).optional(),
|
|
334
|
+
dueAt: z5.string().datetime({ offset: true }).nullable().optional()
|
|
300
335
|
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
301
|
-
reason:
|
|
302
|
-
operationId:
|
|
336
|
+
reason: z5.string().trim().min(1).max(2e3),
|
|
337
|
+
operationId: z5.string().uuid().optional()
|
|
303
338
|
}).strict();
|
|
304
|
-
var changedGoal =
|
|
305
|
-
var goalTitle =
|
|
339
|
+
var changedGoal = z5.string().uuid().describe("The Goal to change, as a read shows it.");
|
|
340
|
+
var goalTitle = z5.string().trim().min(1).max(80).describe("The Goal's short display name: one to five words, how a person refers to it out loud.");
|
|
306
341
|
var GOAL_OUTCOME_MAX = 500;
|
|
307
|
-
var goalOutcome =
|
|
308
|
-
var CreateChange =
|
|
309
|
-
kind:
|
|
342
|
+
var goalOutcome = z5.string().trim().min(1).max(GOAL_OUTCOME_MAX, `outcome_too_long: a Goal's outcome is one paragraph, at most ${GOAL_OUTCOME_MAX} characters; split the rest into child Goals (create with parentGoalId).`).describe(`The desired result, one paragraph of at most ${GOAL_OUTCOME_MAX} characters. More than that is more work: create child Goals under it (parentGoalId). There is no third description field.`);
|
|
343
|
+
var CreateChange = z5.object({
|
|
344
|
+
kind: z5.literal("create"),
|
|
310
345
|
title: goalTitle,
|
|
311
346
|
outcome: goalOutcome,
|
|
312
|
-
ownerId:
|
|
313
|
-
parentGoalId:
|
|
314
|
-
sourceEntryIds:
|
|
347
|
+
ownerId: z5.string().trim().min(1).optional().describe("Who owns the work: an agent's participant (as check_activity shows it) or the person's. Omitted: you."),
|
|
348
|
+
parentGoalId: z5.string().uuid().optional().describe("The Goal it belongs under. Omitted: a root."),
|
|
349
|
+
sourceEntryIds: z5.array(z5.string().uuid()).max(20).optional().describe("The whole Entries the work came from, ones you can read.")
|
|
315
350
|
}).strict();
|
|
316
|
-
var EditChange =
|
|
317
|
-
var StateChange =
|
|
351
|
+
var EditChange = z5.object({ kind: z5.literal("edit"), goalId: changedGoal, title: goalTitle.optional(), outcome: goalOutcome.optional() }).strict();
|
|
352
|
+
var StateChange = z5.object({ kind: z5.literal("state"), goalId: changedGoal, state: z5.enum(["open", "completed", "canceled"]).describe(
|
|
318
353
|
"completed: the Goal's own work is done now (refused while required children are open); open: reopen it; canceled: it will not be done."
|
|
319
354
|
) }).strict();
|
|
320
|
-
var AssignChange =
|
|
321
|
-
var DeferChange =
|
|
355
|
+
var AssignChange = z5.object({ kind: z5.literal("assign"), goalId: changedGoal, ownerId: z5.string().trim().min(1).describe("The new owner's participant.") }).strict();
|
|
356
|
+
var DeferChange = z5.object({ kind: z5.literal("defer"), goalId: changedGoal, until: z5.string().datetime({ offset: true }).nullable().describe(
|
|
322
357
|
"Postpone it until this instant: it waits until then, and its owner is woken when it passes (a promise to follow up later). null takes the postponement back."
|
|
323
358
|
) }).strict();
|
|
324
|
-
var MoveChange =
|
|
359
|
+
var MoveChange = z5.object({ kind: z5.literal("move"), goalId: changedGoal, parentGoalId: z5.string().uuid().nullable().describe(
|
|
325
360
|
"Its new parent, or null for a root. Only this Goal moves; its new parent's other children stay."
|
|
326
361
|
) }).strict();
|
|
327
|
-
var DependencyChange =
|
|
328
|
-
kind:
|
|
329
|
-
change:
|
|
362
|
+
var DependencyChange = z5.object({
|
|
363
|
+
kind: z5.literal("dependency"),
|
|
364
|
+
change: z5.enum(["add", "remove"]),
|
|
330
365
|
goalId: changedGoal,
|
|
331
|
-
dependsOnGoalId:
|
|
332
|
-
action:
|
|
333
|
-
reason:
|
|
366
|
+
dependsOnGoalId: z5.string().uuid().describe("The Goal it waits on."),
|
|
367
|
+
action: z5.enum(["start", "complete"]).describe("What waits: starting goalId, or completing it."),
|
|
368
|
+
reason: z5.string().trim().min(1).max(250).optional().describe("Why, at most 250 characters, as an explanation, never as policy.")
|
|
334
369
|
}).strict();
|
|
335
|
-
var read = { revision:
|
|
336
|
-
var GoalChangeSchema =
|
|
370
|
+
var read = { revision: z5.number().int().positive().optional() };
|
|
371
|
+
var GoalChangeSchema = z5.discriminatedUnion("kind", [
|
|
337
372
|
CreateChange,
|
|
373
|
+
CompleteChangeSchema.extend(read),
|
|
338
374
|
EditChange.extend(read),
|
|
339
375
|
StateChange.extend(read),
|
|
340
376
|
AssignChange.extend(read),
|
|
@@ -344,238 +380,234 @@ var GoalChangeSchema = z3.discriminatedUnion("kind", [
|
|
|
344
380
|
]);
|
|
345
381
|
var editNames = (changes, ctx) => changes.forEach((c, i) => {
|
|
346
382
|
if (c.kind === "edit" && c.title === void 0 && c.outcome === void 0) {
|
|
347
|
-
ctx.addIssue({ code:
|
|
383
|
+
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["changes", i], message: "an edit supplies title, outcome, or both" });
|
|
348
384
|
}
|
|
349
385
|
});
|
|
350
|
-
var ManageGoalsSchema =
|
|
351
|
-
requestId:
|
|
352
|
-
changes:
|
|
386
|
+
var ManageGoalsSchema = z5.object({
|
|
387
|
+
requestId: z5.string().uuid().optional(),
|
|
388
|
+
changes: z5.array(GoalChangeSchema).min(1).max(50)
|
|
353
389
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
354
|
-
var ManageGoalsToolSchema =
|
|
355
|
-
changes:
|
|
390
|
+
var ManageGoalsToolSchema = z5.object({
|
|
391
|
+
changes: z5.array(z5.discriminatedUnion("kind", [CreateChange, CompleteChangeSchema, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange])).min(1).max(50).describe("The changes, applied in order, each on its own.")
|
|
356
392
|
}).strict().superRefine((v, ctx) => editNames(v.changes, ctx));
|
|
357
|
-
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (
|
|
358
|
-
var
|
|
359
|
-
|
|
393
|
+
var MANAGE_GOALS_DESCRIPTION = "Create, edit, assign, organize or close work. Every change here changes Goals; answering, withdrawing or asking Questions stays in contact. Each change applies independently: a refused one names why (`results[i].error`, e.g. goal_revision_conflict, goal_children_open, goal_not_found) and the rest still apply, so read every result: a partial result is never a complete success. Kinds: complete (goalId, message capped at 50 characters, units, reply, optional next-step options {label, hint}) closes and reports atomically, exactly as contact.completions; create (title, outcome, ownerId, parentGoalId, sourceEntryIds) returns the new Goal's id in `results[i].goalId`, in the order requested, to use in later calls (an outcome is one paragraph, at most 500 characters: split more into child Goals with parentGoalId); edit (title and/or outcome); state (open, completed, canceled); assign (ownerId); defer (until: the Goal waits until then and its owner is woken when it passes; null takes it back); move (parentGoalId, or null for a root: only this Goal moves); dependency (add or remove: goalId waits on dependsOnGoalId to start or to complete). A Goal cannot be completed while its required children or dependencies remain open: finish or move them first. Finishing the children does not prove the parent's own work is done. An edit applies at the version you last read: if someone changed the Goal since, it is refused as goal_revision_conflict, so read it again (get_goals) and reconsider. You may change any Goal of your person; a change puts you on it. Returns `ok` (every change applied), `results` per change (applied or failed), and `goals`, each changed Goal's id, state, title, outcome and revision.";
|
|
394
|
+
var GetGoalsSchema = z5.object({
|
|
395
|
+
goalIds: z5.array(z5.string().uuid()).min(1).max(10).describe("The Goals to read, in order; one to ten IDs."),
|
|
360
396
|
/** Every entry in full. Without it the read carries the person's words, open questions and your
|
|
361
397
|
* newest entry, and counts what it left out (`apps/api/src/goal/collapse.ts`). */
|
|
362
|
-
history:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
diagnose: z3.boolean().optional()
|
|
398
|
+
history: z5.boolean().optional()
|
|
399
|
+
}).strict();
|
|
400
|
+
var GetDeliverySchema = z5.object({
|
|
401
|
+
deliveryId: z5.string().uuid().describe("The Delivery id returned by contact or receive.")
|
|
367
402
|
}).strict();
|
|
368
|
-
var
|
|
369
|
-
var
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
403
|
+
var GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
|
|
404
|
+
var GET_GOALS_DESCRIPTION = "Read one to ten Goals by goalIds, returned in the same order under goals (a read never puts you on them). history applies to every requested Goal. A refused read fails the call with its original refusal; no partial result is presented as complete. Each Goal includes its ten most recent other contributors (names, latest entry headlines and times), outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.";
|
|
405
|
+
var SearchToolSchema = z5.object({
|
|
406
|
+
query: z5.string().trim().min(1).max(500),
|
|
407
|
+
types: z5.array(z5.enum(["entry", "goal", "answer"])).min(1).optional(),
|
|
408
|
+
goalId: z5.string().uuid().optional(),
|
|
409
|
+
limit: z5.number().int().min(1).max(20).optional()
|
|
374
410
|
}).strict();
|
|
375
411
|
var SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and replies -- the person's answers, acknowledgements and deferrals (type answer; found by their summary, by the question or report they reply to, or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, the replies to its items, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (a reply carries its kind -- answered, acknowledged or deferred -- whether it is closed (a deferral is not), its summary in under ten words, its question or report, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
376
|
-
var
|
|
412
|
+
var ConfigureIdentitySchema = z5.object({}).strict();
|
|
413
|
+
var CONFIGURE_IDENTITY_DESCRIPTION = "Read this authenticated agent\u2019s current name, voice and token ID. Read-only; no arguments. Pairing, renaming and logout are available only through the local configure driver.";
|
|
414
|
+
var CheckActivitySchema = z5.object({}).strict();
|
|
377
415
|
var FEEDBACK_TEXT_MAX = 5e4;
|
|
378
|
-
var SendFeedbackSchema =
|
|
379
|
-
text:
|
|
380
|
-
title:
|
|
381
|
-
kind:
|
|
416
|
+
var SendFeedbackSchema = z5.object({
|
|
417
|
+
text: z5.string().trim().min(1).max(FEEDBACK_TEXT_MAX).describe(`The report, in markdown: what happened, what was expected, how to reproduce it, versions, evidence. At most ${FEEDBACK_TEXT_MAX} characters.`),
|
|
418
|
+
title: z5.string().trim().min(1).max(200).optional().describe("One line naming it."),
|
|
419
|
+
kind: z5.enum(["bug", "idea", "other"]).default("other").describe("bug: something Paigy does wrong. idea: something it should do. other: anything else.")
|
|
382
420
|
}).strict();
|
|
383
421
|
var SEND_FEEDBACK_DESCRIPTION = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
|
|
384
422
|
var AGENT_TOOLS = [
|
|
423
|
+
{ name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
|
|
385
424
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
386
425
|
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
387
|
-
{ name: "
|
|
426
|
+
{ name: "get_delivery", description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
|
|
427
|
+
{ name: "get_goals", description: GET_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalsSchema) },
|
|
388
428
|
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
389
|
-
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use
|
|
429
|
+
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goals to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
|
|
390
430
|
{ name: "send_feedback", description: SEND_FEEDBACK_DESCRIPTION, inputSchema: mcpInputSchema(SendFeedbackSchema) }
|
|
391
431
|
];
|
|
392
432
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
393
433
|
function serverInstructions(opts) {
|
|
394
434
|
const waits = opts.waits ? "contact({..., wait:true}) holds one bounded ~45 s window for a response to what you sent; contact({}) holds one for anything addressed to you." : "A contact here returns after one read (`waitOutcome: not_waited`); answers wake you, and contact({wait:false}) collects them.";
|
|
395
|
-
return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), which hand you your work: answers, assignments, reviews and due Goals.
|
|
435
|
+
return `On startup and after a wake, call contact({wait:false}) for the events addressed to you (acknowledge the ones you handled with contact({ackEventIds})), which hand you your work: answers, assignments, reviews and due Goals. get_goals reads a Goal and the conversation on it; a read never puts you on it. You are on a Goal from your first write to it (a contact update, question or answer naming it, or a manage_goals change); there is nothing to claim or join. Every Goal of your person is open to you, whichever of their agents owns it: read it with get_goals, write on it (a contact naming its id) and change it (manage_goals). Create, edit, assign and organize work with manage_goals. Complete and report atomically with contact.completions or manage_goals kind:complete; both take the same completion contract. A contact only names Goals that already exist. Every read ends in \`next\`, the one step to take. Sending returns immediately unless you ask it to wait: keep working and collect answers by receiving or through get_goals. ${waits} Events repeat until you acknowledge them: reading alone acknowledges nothing; an \`update\` event acknowledged is a review cleared. Report progress as a contact update: one the person does not need yet is kept as the Goal's progress and reaches no one. To follow up later, defer the Goal with manage_goals: you are woken when its time comes. Never infer ringing from an open Call Delivery. Soft waiting and re-presentation are unsupported. Your user is remote. Always interact with the user through Paigy. For decisions, approvals, or questions, contact them with structured options. Never assume anyone is reading the terminal stdout.
|
|
396
436
|
|
|
397
|
-
|
|
398
|
-
1. One question per question object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
399
|
-
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
400
|
-
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
401
|
-
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goal\` on that wake.
|
|
402
|
-
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
403
|
-
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
404
|
-
7. A question carries its options. Without them it reaches the person as a bare title nobody can answer. Each option names its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Your question is sent exactly as you wrote it. They can always answer in their own words, so no "Other" option.
|
|
405
|
-
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.`;
|
|
437
|
+
${ASKING_RULES}`;
|
|
406
438
|
}
|
|
407
|
-
var id =
|
|
408
|
-
var key =
|
|
409
|
-
var participant =
|
|
410
|
-
var SourceSchema =
|
|
411
|
-
var ResultRefSchema =
|
|
412
|
-
var NodeRefSchema =
|
|
413
|
-
var DependencySchema =
|
|
439
|
+
var id = z6.string().uuid();
|
|
440
|
+
var key = z6.string().min(1).max(40);
|
|
441
|
+
var participant = z6.string().regex(/^(human|agent):.+$/, "a participant ID such as agent:<uuid>");
|
|
442
|
+
var SourceSchema = z6.object({ entryId: id }).strict();
|
|
443
|
+
var ResultRefSchema = z6.union([z6.object({ id }).strict(), z6.object({ local: key }).strict()]);
|
|
444
|
+
var NodeRefSchema = z6.object({ type: z6.enum(["goal", "question"]), id }).strict();
|
|
445
|
+
var DependencySchema = z6.object({
|
|
414
446
|
blocker: NodeRefSchema,
|
|
415
447
|
blocked: NodeRefSchema,
|
|
416
|
-
action:
|
|
417
|
-
reason:
|
|
448
|
+
action: z6.enum(["start", "complete", "answer"]),
|
|
449
|
+
reason: z6.string().max(500).optional()
|
|
418
450
|
}).strict();
|
|
419
|
-
var goalState =
|
|
451
|
+
var goalState = z6.enum(["open", "completed", "canceled"]);
|
|
420
452
|
var existingGoalChanges = [
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
kind:
|
|
428
|
-
change:
|
|
453
|
+
z6.object({ kind: z6.literal("edit"), goalId: id, title: z6.string().min(1).max(120).optional(), outcome: z6.string().min(1).optional() }).strict().refine((c) => c.title !== void 0 || c.outcome !== void 0, "an edit changes the title or the outcome"),
|
|
454
|
+
z6.object({ kind: z6.literal("state"), goalId: id, state: goalState }).strict(),
|
|
455
|
+
z6.object({ kind: z6.literal("assign"), goalId: id, ownerId: participant }).strict(),
|
|
456
|
+
z6.object({ kind: z6.literal("defer"), goalId: id, until: z6.string().datetime().nullable() }).strict(),
|
|
457
|
+
z6.object({ kind: z6.literal("move"), goalId: id, parentGoalId: id.nullable() }).strict(),
|
|
458
|
+
z6.object({
|
|
459
|
+
kind: z6.literal("dependency"),
|
|
460
|
+
change: z6.enum(["add", "remove"]),
|
|
429
461
|
goalId: id,
|
|
430
462
|
dependsOnGoalId: id,
|
|
431
|
-
action:
|
|
432
|
-
reason:
|
|
463
|
+
action: z6.enum(["start", "complete"]),
|
|
464
|
+
reason: z6.string().max(500).optional()
|
|
433
465
|
}).strict()
|
|
434
466
|
];
|
|
435
|
-
var BrainGoalChangeSchema =
|
|
467
|
+
var BrainGoalChangeSchema = z6.union([
|
|
436
468
|
// newSession: Paigy starts a session to own it (owner, 2026-10-06); until it has, the person owns it.
|
|
437
|
-
|
|
438
|
-
kind:
|
|
439
|
-
outcome:
|
|
440
|
-
title:
|
|
469
|
+
z6.object({
|
|
470
|
+
kind: z6.literal("create"),
|
|
471
|
+
outcome: z6.string().min(1),
|
|
472
|
+
title: z6.string().min(1).max(120),
|
|
441
473
|
ownerId: participant,
|
|
442
474
|
parentGoal: ResultRefSchema.nullable(),
|
|
443
|
-
newSession:
|
|
475
|
+
newSession: z6.literal(true).optional()
|
|
444
476
|
}).strict(),
|
|
445
477
|
...existingGoalChanges
|
|
446
478
|
]);
|
|
447
|
-
var option =
|
|
448
|
-
var QuestionChangeSchema =
|
|
449
|
-
|
|
450
|
-
kind:
|
|
451
|
-
text:
|
|
452
|
-
summary:
|
|
479
|
+
var option = z6.object({ id: z6.string().min(1).max(40), label: z6.string().min(1), hint: z6.string().optional() }).strict();
|
|
480
|
+
var QuestionChangeSchema = z6.union([
|
|
481
|
+
z6.object({
|
|
482
|
+
kind: z6.literal("create"),
|
|
483
|
+
text: z6.string().min(1),
|
|
484
|
+
summary: z6.string().optional(),
|
|
453
485
|
answererId: participant,
|
|
454
486
|
goal: ResultRefSchema.optional(),
|
|
455
|
-
blocks:
|
|
456
|
-
options:
|
|
457
|
-
pickMode:
|
|
487
|
+
blocks: z6.array(z6.object({ question: ResultRefSchema }).strict()),
|
|
488
|
+
options: z6.array(option).min(2).optional(),
|
|
489
|
+
pickMode: z6.enum(["one", "many", "rank"]).optional()
|
|
458
490
|
}).strict().refine((c) => c.options === void 0 === (c.pickMode === void 0), "options and pickMode come together"),
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
491
|
+
z6.object({ kind: z6.literal("edit"), questionId: id, text: z6.string().min(1) }).strict(),
|
|
492
|
+
z6.object({ kind: z6.literal("assign"), questionId: id, answererId: participant }).strict(),
|
|
493
|
+
z6.object({ kind: z6.literal("withdraw"), questionId: id }).strict(),
|
|
494
|
+
z6.object({ kind: z6.literal("dependency"), change: z6.enum(["add", "remove"]), edge: DependencySchema }).strict()
|
|
463
495
|
]);
|
|
464
|
-
var lessonScope =
|
|
465
|
-
|
|
466
|
-
|
|
496
|
+
var lessonScope = z6.union([
|
|
497
|
+
z6.object({ kind: z6.literal("user") }).strict(),
|
|
498
|
+
z6.object({ kind: z6.literal("goal"), goal: ResultRefSchema }).strict()
|
|
467
499
|
]);
|
|
468
|
-
var LessonChangeSchema =
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
500
|
+
var LessonChangeSchema = z6.union([
|
|
501
|
+
z6.object({ kind: z6.literal("remember"), text: z6.string().min(1), scope: lessonScope }).strict(),
|
|
502
|
+
z6.object({ kind: z6.literal("revise"), lessonId: id, text: z6.string().min(1), scope: lessonScope }).strict(),
|
|
503
|
+
z6.object({ kind: z6.literal("withdraw"), lessonId: id }).strict()
|
|
472
504
|
]);
|
|
473
|
-
var BrainNextSchema =
|
|
474
|
-
instructions:
|
|
475
|
-
say:
|
|
476
|
-
then:
|
|
477
|
-
waitFor:
|
|
478
|
-
reason:
|
|
505
|
+
var BrainNextSchema = z6.object({
|
|
506
|
+
instructions: z6.array(z6.object({ entryKeys: z6.array(key).min(1) }).strict()),
|
|
507
|
+
say: z6.array(key),
|
|
508
|
+
then: z6.enum(["listen", "hold", "end", "none"]),
|
|
509
|
+
waitFor: z6.array(ResultRefSchema),
|
|
510
|
+
reason: z6.string()
|
|
479
511
|
}).strict();
|
|
480
512
|
var messageBase = {
|
|
481
513
|
key,
|
|
482
|
-
text:
|
|
483
|
-
goals:
|
|
484
|
-
questions:
|
|
485
|
-
entryKeys:
|
|
486
|
-
obligationIds:
|
|
514
|
+
text: z6.string().min(1),
|
|
515
|
+
goals: z6.array(ResultRefSchema),
|
|
516
|
+
questions: z6.array(ResultRefSchema),
|
|
517
|
+
entryKeys: z6.array(key),
|
|
518
|
+
obligationIds: z6.array(id)
|
|
487
519
|
};
|
|
488
|
-
var BrainMessageSchema =
|
|
489
|
-
|
|
490
|
-
|
|
520
|
+
var BrainMessageSchema = z6.union([
|
|
521
|
+
z6.object({ ...messageBase, to: z6.object({ kind: z6.literal("user") }).strict() }).strict(),
|
|
522
|
+
z6.object({
|
|
491
523
|
...messageBase,
|
|
492
|
-
to:
|
|
493
|
-
call:
|
|
524
|
+
to: z6.object({ kind: z6.literal("agent"), agentId: z6.string().regex(/^agent:(?!paigy$)\S+$/) }).strict(),
|
|
525
|
+
call: z6.object({ callId: id, listen: z6.literal(true) }).strict().optional()
|
|
494
526
|
}).strict()
|
|
495
527
|
]);
|
|
496
|
-
var BrainSearchSchema =
|
|
497
|
-
query:
|
|
498
|
-
within:
|
|
528
|
+
var BrainSearchSchema = z6.object({
|
|
529
|
+
query: z6.string().min(1).max(500),
|
|
530
|
+
within: z6.array(z6.enum(["entries", "goals", "answers"])).min(1),
|
|
499
531
|
goalId: id.optional()
|
|
500
532
|
}).strict();
|
|
501
|
-
var BrainResultSchema =
|
|
502
|
-
messages:
|
|
503
|
-
entries:
|
|
504
|
-
questions:
|
|
505
|
-
answers:
|
|
533
|
+
var BrainResultSchema = z6.object({
|
|
534
|
+
messages: z6.array(BrainMessageSchema),
|
|
535
|
+
entries: z6.array(z6.object({ key, sources: z6.array(SourceSchema).min(1), goals: z6.array(ResultRefSchema), summary: z6.string().optional() }).strict()),
|
|
536
|
+
questions: z6.array(z6.object({ key, entryKeys: z6.array(key), change: QuestionChangeSchema }).strict()),
|
|
537
|
+
answers: z6.array(z6.object({
|
|
506
538
|
question: ResultRefSchema,
|
|
507
|
-
entryKeys:
|
|
508
|
-
summary:
|
|
509
|
-
selectedOptionIds:
|
|
539
|
+
entryKeys: z6.array(key).min(1),
|
|
540
|
+
summary: z6.string(),
|
|
541
|
+
selectedOptionIds: z6.array(z6.string().min(1)).optional()
|
|
510
542
|
}).strict()),
|
|
511
|
-
goals:
|
|
512
|
-
feedback:
|
|
513
|
-
lessons:
|
|
543
|
+
goals: z6.array(z6.object({ key, entryKeys: z6.array(key), change: BrainGoalChangeSchema }).strict()),
|
|
544
|
+
feedback: z6.array(z6.object({ entryKeys: z6.array(key).min(1) }).strict()),
|
|
545
|
+
lessons: z6.array(z6.object({ entryKeys: z6.array(key).min(1), change: LessonChangeSchema }).strict()),
|
|
514
546
|
next: BrainNextSchema,
|
|
515
547
|
search: BrainSearchSchema.optional()
|
|
516
548
|
}).strict();
|
|
517
|
-
var str =
|
|
518
|
-
var strs =
|
|
519
|
-
var CompactResultSchema =
|
|
520
|
-
next:
|
|
521
|
-
messages:
|
|
522
|
-
entries:
|
|
523
|
-
answers:
|
|
524
|
-
changes:
|
|
549
|
+
var str = z6.string();
|
|
550
|
+
var strs = z6.array(z6.string());
|
|
551
|
+
var CompactResultSchema = z6.object({
|
|
552
|
+
next: z6.object({ say: strs, then: z6.enum(["listen", "hold", "end", "none"]), waitFor: strs, reason: str, instructions: strs }).strict(),
|
|
553
|
+
messages: z6.array(z6.object({ key: str, to: str, text: str, about: strs, entries: strs, owed: strs, inviteToCall: z6.boolean() }).strict()),
|
|
554
|
+
entries: z6.array(z6.object({ key: str, from: str, on: strs }).strict()),
|
|
555
|
+
answers: z6.array(z6.object({ question: str, entries: strs, summary: str, picked: strs }).strict()),
|
|
556
|
+
changes: z6.array(z6.object({
|
|
525
557
|
key: str,
|
|
526
|
-
what:
|
|
527
|
-
op:
|
|
558
|
+
what: z6.enum(["goal", "question", "lesson"]),
|
|
559
|
+
op: z6.enum(["create", "edit", "assign", "open", "complete", "cancel", "defer", "move", "block", "unblock", "withdraw", "remember", "revise"]),
|
|
528
560
|
id: str,
|
|
529
561
|
text: str,
|
|
530
562
|
title: str,
|
|
531
563
|
who: str,
|
|
532
564
|
under: str,
|
|
533
|
-
gate:
|
|
534
|
-
options:
|
|
535
|
-
pick:
|
|
565
|
+
gate: z6.enum(["start", "complete", "answer", "none"]),
|
|
566
|
+
options: z6.array(z6.object({ id: str, label: str }).strict()),
|
|
567
|
+
pick: z6.enum(["one", "many", "rank", "words"]),
|
|
536
568
|
entries: strs
|
|
537
569
|
}).strict()),
|
|
538
|
-
feedback:
|
|
539
|
-
search:
|
|
570
|
+
feedback: z6.array(strs),
|
|
571
|
+
search: z6.object({ query: str, within: strs, goal: str }).strict()
|
|
540
572
|
}).strict();
|
|
541
573
|
var COMPACT_RESULT_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
542
574
|
zodToJsonSchema2(CompactResultSchema, { $refStrategy: "none" })
|
|
543
575
|
);
|
|
544
|
-
var str2 =
|
|
545
|
-
var strs2 =
|
|
546
|
-
var TalkerReplySchema =
|
|
576
|
+
var str2 = z7.string();
|
|
577
|
+
var strs2 = z7.array(z7.string());
|
|
578
|
+
var TalkerReplySchema = z7.object({
|
|
547
579
|
/** What to say and send, messages to the person first: spoken in the order written. `to` is "user"
|
|
548
580
|
* or an agent's handle; `about` names handles; `blocks` is the item a question to an agent must be
|
|
549
581
|
* answered before ("" for none). */
|
|
550
|
-
messages:
|
|
582
|
+
messages: z7.array(z7.object({ key: str2, to: str2, text: str2, about: strs2, blocks: str2 }).strict()),
|
|
551
583
|
/** What the call does next: listen, hold (the person asked for a moment) or end (they asked to). */
|
|
552
|
-
then:
|
|
584
|
+
then: z7.enum(["listen", "hold", "end"]),
|
|
553
585
|
/** The person's replies to items (replies, owner 2026-10-07): the item's handle, the chosen option
|
|
554
586
|
* IDs, the line handles, how they replied in under ten words (keeping any condition), and `defer`:
|
|
555
587
|
* "" when their words settle the item, else what a reply that puts it off waits for -- "call" (the
|
|
556
588
|
* end of this call), a question's handle (that question's answer) or an ISO time. */
|
|
557
|
-
replies:
|
|
589
|
+
replies: z7.array(z7.object({ item: str2, options: strs2, lines: strs2, summary: str2, defer: str2 }).strict()),
|
|
558
590
|
/** The line handles that tell the talker how to run this call; they last until it ends and reach no agent. */
|
|
559
591
|
instruction: strs2,
|
|
560
592
|
/** The line handles the filer acts on, and the only ones it reads (3e): new work, a change to existing
|
|
561
593
|
* work, feedback on Paigy, anything said to an agent, and any rule meant to outlast the call. */
|
|
562
594
|
file: strs2,
|
|
563
595
|
/** Evidence to fetch for a second round: a search, an item's full text, or an agent by name. */
|
|
564
|
-
need:
|
|
596
|
+
need: z7.object({ kind: z7.enum(["search", "item", "agent", ""]), text: str2 }).strict(),
|
|
565
597
|
/** The handle of the item to explain ("" for none). */
|
|
566
598
|
explain: str2
|
|
567
599
|
}).strict();
|
|
568
600
|
var TALKER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
569
601
|
zodToJsonSchema3(TalkerReplySchema, { $refStrategy: "none" })
|
|
570
602
|
);
|
|
571
|
-
var str3 =
|
|
572
|
-
var strs3 =
|
|
573
|
-
var FilerReplySchema =
|
|
603
|
+
var str3 = z8.string();
|
|
604
|
+
var strs3 = z8.array(z8.string());
|
|
605
|
+
var FilerReplySchema = z8.object({
|
|
574
606
|
/** Which Goals each new line goes on: a line's handle, Goal handles (or keys of new Goals), and what
|
|
575
607
|
* the line says in at most ten words. */
|
|
576
|
-
filed:
|
|
577
|
-
goals:
|
|
578
|
-
op:
|
|
608
|
+
filed: z8.array(z8.object({ line: str3, goals: strs3, summary: str3 }).strict()),
|
|
609
|
+
goals: z8.array(z8.object({
|
|
610
|
+
op: z8.enum(["create", "edit", "complete", "cancel", "reopen", "assign", "defer", "move", "block", "unblock"]),
|
|
579
611
|
key: str3,
|
|
580
612
|
goal: str3,
|
|
581
613
|
title: str3,
|
|
@@ -584,33 +616,33 @@ var FilerReplySchema = z6.object({
|
|
|
584
616
|
under: str3,
|
|
585
617
|
until: str3,
|
|
586
618
|
on: str3,
|
|
587
|
-
gate:
|
|
619
|
+
gate: z8.enum(["start", "complete", "answer", ""]),
|
|
588
620
|
why: str3
|
|
589
621
|
}).strict()),
|
|
590
|
-
questions:
|
|
591
|
-
op:
|
|
622
|
+
questions: z8.array(z8.object({
|
|
623
|
+
op: z8.enum(["create", "edit", "assign", "withdraw"]),
|
|
592
624
|
key: str3,
|
|
593
625
|
question: str3,
|
|
594
626
|
text: str3,
|
|
595
627
|
summary: str3,
|
|
596
628
|
to: str3,
|
|
597
629
|
goal: str3,
|
|
598
|
-
options:
|
|
599
|
-
pick:
|
|
630
|
+
options: z8.array(z8.object({ id: str3, label: str3 }).strict()),
|
|
631
|
+
pick: z8.enum(["one", "many", "rank", "words", ""]),
|
|
600
632
|
blocks: str3
|
|
601
633
|
}).strict()),
|
|
602
|
-
lessons:
|
|
634
|
+
lessons: z8.array(z8.object({ op: z8.enum(["remember", "revise", "withdraw"]), lesson: str3, text: str3, scope: str3 }).strict()),
|
|
603
635
|
/** Line handles whose words comment on Paigy itself. */
|
|
604
636
|
feedback: strs3
|
|
605
637
|
}).strict();
|
|
606
638
|
var FILER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
607
639
|
zodToJsonSchema4(FilerReplySchema, { $refStrategy: "none" })
|
|
608
640
|
);
|
|
609
|
-
var ExplainerReplySchema =
|
|
641
|
+
var ExplainerReplySchema = z9.object({
|
|
610
642
|
/** The explanation, as sentences, spoken in order. */
|
|
611
|
-
say:
|
|
643
|
+
say: z9.array(z9.string()),
|
|
612
644
|
/** Questions to an agent about gaps in what it read: the agent's handle and the question. */
|
|
613
|
-
followUps:
|
|
645
|
+
followUps: z9.array(z9.object({ to: z9.string(), text: z9.string() }).strict())
|
|
614
646
|
}).strict();
|
|
615
647
|
var EXPLAINER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
616
648
|
zodToJsonSchema5(ExplainerReplySchema, { $refStrategy: "none" })
|
|
@@ -633,28 +665,28 @@ var DISTILLER_DEFECTS = [
|
|
|
633
665
|
"dropped"
|
|
634
666
|
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
635
667
|
];
|
|
636
|
-
var DistillerReplySchema =
|
|
637
|
-
defects:
|
|
638
|
-
kind:
|
|
668
|
+
var DistillerReplySchema = z10.object({
|
|
669
|
+
defects: z10.array(z10.object({
|
|
670
|
+
kind: z10.enum(DISTILLER_DEFECTS),
|
|
639
671
|
/** The handles of the lines it shows in. */
|
|
640
|
-
lines:
|
|
672
|
+
lines: z10.array(z10.string()),
|
|
641
673
|
/** The handle of the record it concerns, or "". */
|
|
642
|
-
about:
|
|
674
|
+
about: z10.string(),
|
|
643
675
|
/** One plain sentence, in words, never handles: it is what the feedback row keeps. */
|
|
644
|
-
why:
|
|
676
|
+
why: z10.string()
|
|
645
677
|
}).strict())
|
|
646
678
|
}).strict();
|
|
647
679
|
var DISTILLER_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
648
680
|
zodToJsonSchema6(DistillerReplySchema, { $refStrategy: "none" })
|
|
649
681
|
);
|
|
650
682
|
var TIDY_AUDIENCES = ["talker", "filer", "agents"];
|
|
651
|
-
var TidyReplySchema =
|
|
652
|
-
lessons:
|
|
653
|
-
lesson:
|
|
654
|
-
op:
|
|
655
|
-
text:
|
|
656
|
-
for:
|
|
657
|
-
into:
|
|
683
|
+
var TidyReplySchema = z10.object({
|
|
684
|
+
lessons: z10.array(z10.object({
|
|
685
|
+
lesson: z10.string(),
|
|
686
|
+
op: z10.enum(["keep", "revise", "withdraw"]),
|
|
687
|
+
text: z10.string(),
|
|
688
|
+
for: z10.enum([...TIDY_AUDIENCES, ""]),
|
|
689
|
+
into: z10.string()
|
|
658
690
|
}).strict())
|
|
659
691
|
}).strict();
|
|
660
692
|
var TIDY_REPLY_JSON_SCHEMA = (({ $schema: _, ...rest }) => rest)(
|
|
@@ -688,20 +720,56 @@ var LIVE_MS = 3 * 6e4;
|
|
|
688
720
|
var WORKING_MS = 60 * 6e4;
|
|
689
721
|
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
690
722
|
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
691
|
-
var
|
|
692
|
-
|
|
693
|
-
|
|
723
|
+
var json = z11.lazy(() => z11.union([
|
|
724
|
+
z11.string(),
|
|
725
|
+
z11.number().finite(),
|
|
726
|
+
z11.boolean(),
|
|
727
|
+
z11.null(),
|
|
728
|
+
z11.array(json),
|
|
729
|
+
z11.record(json)
|
|
730
|
+
]));
|
|
731
|
+
var clock = z11.number().int().nonnegative().safe();
|
|
732
|
+
var HeardEventSchema = z11.object({
|
|
733
|
+
eventId: z11.string().uuid(),
|
|
734
|
+
source: z11.enum(["phone", "bot"]),
|
|
735
|
+
sequence: clock,
|
|
736
|
+
kind: z11.string().min(1),
|
|
737
|
+
phoneAtMs: clock.optional(),
|
|
738
|
+
botAtMs: clock.optional(),
|
|
739
|
+
payload: z11.record(json)
|
|
740
|
+
}).strict();
|
|
741
|
+
var HeardCursorSchema = HeardEventSchema.pick({ source: true, sequence: true, eventId: true });
|
|
742
|
+
var HeardPageSchema = z11.object({
|
|
743
|
+
events: z11.array(HeardEventSchema).max(200),
|
|
744
|
+
next: HeardCursorSchema.nullable()
|
|
745
|
+
}).strict();
|
|
746
|
+
var MAX_EVENTS = 50;
|
|
747
|
+
var MAX_BYTES = 8e4;
|
|
748
|
+
var batchBytes = (events) => {
|
|
749
|
+
let bytes = 0;
|
|
750
|
+
for (const char of JSON.stringify({ events })) {
|
|
751
|
+
const point = char.codePointAt(0);
|
|
752
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
753
|
+
}
|
|
754
|
+
return bytes;
|
|
755
|
+
};
|
|
756
|
+
var HeardBatchSchema = z11.object({
|
|
757
|
+
events: z11.array(HeardEventSchema).min(1).max(MAX_EVENTS)
|
|
758
|
+
}).strict().refine((value) => batchBytes(value.events) <= MAX_BYTES, "heard_batch_too_large");
|
|
759
|
+
var ContextSchema = z12.object({
|
|
760
|
+
title: z12.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
761
|
+
description: z12.array(z12.string().min(1)).describe(
|
|
694
762
|
"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."
|
|
695
763
|
),
|
|
696
764
|
/** THE ASK'S UNITS (3c): its context as titled units, in order. The app lists the titles and opens
|
|
697
765
|
* a body on a tap. Absent on an ask sent without units, which renders as before. */
|
|
698
|
-
units:
|
|
766
|
+
units: z12.array(z12.object({ title: z12.string(), body: z12.string() })).optional()
|
|
699
767
|
});
|
|
700
|
-
var ParticipantSchema =
|
|
701
|
-
kind:
|
|
702
|
-
id:
|
|
768
|
+
var ParticipantSchema = z12.object({
|
|
769
|
+
kind: z12.enum(["human", "agent"]),
|
|
770
|
+
id: z12.string()
|
|
703
771
|
});
|
|
704
|
-
var TransformSchema =
|
|
772
|
+
var TransformSchema = z12.enum([
|
|
705
773
|
"structure",
|
|
706
774
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
707
775
|
"request_more",
|
|
@@ -717,13 +785,13 @@ var TransformSchema = z9.enum([
|
|
|
717
785
|
"summarize"
|
|
718
786
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
719
787
|
]);
|
|
720
|
-
var VisualSchema =
|
|
721
|
-
url:
|
|
722
|
-
label:
|
|
788
|
+
var VisualSchema = z12.object({
|
|
789
|
+
url: z12.string().url(),
|
|
790
|
+
label: z12.string().optional()
|
|
723
791
|
});
|
|
724
|
-
var NotifyLevelSchema =
|
|
725
|
-
var SelectShapeSchema =
|
|
726
|
-
var ReceiptEventSchema =
|
|
792
|
+
var NotifyLevelSchema = z12.enum(["inbox", "push", "banner", "call"]);
|
|
793
|
+
var SelectShapeSchema = z12.enum(["one", "many", "rank", "confirm", "text"]);
|
|
794
|
+
var ReceiptEventSchema = z12.enum([
|
|
727
795
|
"delivered",
|
|
728
796
|
// the bundle reached the recipient at some level
|
|
729
797
|
"seen",
|
|
@@ -753,47 +821,47 @@ var ReceiptEventSchema = z9.enum([
|
|
|
753
821
|
// be rewound by a writer that forgot to advance it.
|
|
754
822
|
"restarted"
|
|
755
823
|
]);
|
|
756
|
-
var AttentionSchema =
|
|
824
|
+
var AttentionSchema = z12.object({
|
|
757
825
|
urgency: NotifyLevelSchema,
|
|
758
826
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
759
827
|
select: SelectShapeSchema.nullable(),
|
|
760
828
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
761
|
-
points:
|
|
829
|
+
points: z12.array(z12.string()).nullable(),
|
|
762
830
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
763
|
-
blocking:
|
|
831
|
+
blocking: z12.boolean(),
|
|
764
832
|
/** Reserved (docs/model/model.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
765
833
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
766
|
-
deadline:
|
|
834
|
+
deadline: z12.string().datetime().nullable().optional()
|
|
767
835
|
});
|
|
768
|
-
var NotifyRequestFields =
|
|
836
|
+
var NotifyRequestFields = z12.object({
|
|
769
837
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
770
838
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
771
839
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
772
840
|
context: ContextSchema.optional(),
|
|
773
|
-
options:
|
|
841
|
+
options: z12.array(OptionSchema.omit({ id: true }).extend({ label: z12.string().trim().min(1).max(1e3) }).strict()).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
774
842
|
"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)."
|
|
775
843
|
),
|
|
776
|
-
points:
|
|
844
|
+
points: z12.array(z12.string().min(1)).optional().describe(
|
|
777
845
|
"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."
|
|
778
846
|
),
|
|
779
|
-
visuals:
|
|
847
|
+
visuals: z12.array(VisualSchema).optional().describe(
|
|
780
848
|
"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."
|
|
781
849
|
),
|
|
782
850
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
783
|
-
repo:
|
|
851
|
+
repo: z12.string().optional(),
|
|
784
852
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
785
|
-
branch:
|
|
853
|
+
branch: z12.string().optional(),
|
|
786
854
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
787
855
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
788
856
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
789
857
|
* still accepts the old name from older clients. */
|
|
790
|
-
parentId:
|
|
858
|
+
parentId: z12.string().uuid().optional(),
|
|
791
859
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
792
860
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
793
|
-
workId:
|
|
861
|
+
workId: z12.string().uuid().optional(),
|
|
794
862
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
795
863
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
796
|
-
goalId:
|
|
864
|
+
goalId: z12.string().uuid().optional(),
|
|
797
865
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
798
866
|
"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."
|
|
799
867
|
),
|
|
@@ -801,7 +869,7 @@ var NotifyRequestFields = z9.object({
|
|
|
801
869
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
802
870
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
803
871
|
* says HOW. */
|
|
804
|
-
clarifies:
|
|
872
|
+
clarifies: z12.string().optional(),
|
|
805
873
|
select: SelectShapeSchema.optional().describe(
|
|
806
874
|
"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."
|
|
807
875
|
),
|
|
@@ -815,20 +883,20 @@ var NotifyRequestFields = z9.object({
|
|
|
815
883
|
// (docs/brain/broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
816
884
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
817
885
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
818
|
-
ask:
|
|
886
|
+
ask: z12.string().min(1).max(1e4).optional().describe(
|
|
819
887
|
'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.'
|
|
820
888
|
),
|
|
821
|
-
needs:
|
|
889
|
+
needs: z12.array(z12.string().min(1)).optional().describe(
|
|
822
890
|
"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."
|
|
823
891
|
),
|
|
824
|
-
urgencyHint:
|
|
892
|
+
urgencyHint: z12.enum(["whenever", "soon", "now"]).optional().describe(
|
|
825
893
|
"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."
|
|
826
894
|
),
|
|
827
895
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
828
896
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
829
897
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
830
898
|
* urgencyHint/blocking win when both are sent. */
|
|
831
|
-
waiting:
|
|
899
|
+
waiting: z12.enum(["none", "soft", "hard"]).optional().describe(
|
|
832
900
|
"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."
|
|
833
901
|
),
|
|
834
902
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -836,98 +904,98 @@ var NotifyRequestFields = z9.object({
|
|
|
836
904
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
837
905
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
838
906
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
839
|
-
confirm:
|
|
907
|
+
confirm: z12.boolean().optional().describe(
|
|
840
908
|
"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'."
|
|
841
909
|
),
|
|
842
910
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
843
911
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
844
912
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
845
913
|
* waiting:'hard'. */
|
|
846
|
-
channel:
|
|
914
|
+
channel: z12.enum(["call", "message"]).optional().describe(
|
|
847
915
|
"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."
|
|
848
916
|
),
|
|
849
|
-
confirmStyle:
|
|
917
|
+
confirmStyle: z12.enum(["yesno", "approve"]).default("yesno").describe(
|
|
850
918
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
851
919
|
),
|
|
852
|
-
blocking:
|
|
920
|
+
blocking: z12.boolean().default(false).describe(
|
|
853
921
|
"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."
|
|
854
922
|
)
|
|
855
923
|
});
|
|
856
924
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
857
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
925
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
858
926
|
if (r.ask !== void 0) {
|
|
859
927
|
for (const f of ["context", "select", "points"]) {
|
|
860
928
|
if (r[f] !== void 0)
|
|
861
|
-
ctx.addIssue({ code:
|
|
929
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: [f], message: `the simplified \`ask\` form takes no ${f} \u2014 the broker derives the answer shape from your prose. Drop ${f} and say it in \`ask\` instead ("should I\u2026" for approve/deny, "which of these\u2026" for a pick), passing \`options\` when you're offering concrete alternatives.` });
|
|
862
930
|
}
|
|
863
931
|
return;
|
|
864
932
|
}
|
|
865
933
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
866
|
-
ctx.addIssue({ code:
|
|
934
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
867
935
|
if (!r.context)
|
|
868
|
-
ctx.addIssue({ code:
|
|
936
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
869
937
|
if (!r.select)
|
|
870
|
-
ctx.addIssue({ code:
|
|
938
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
871
939
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
872
940
|
if (needsOptions && !r.options?.length)
|
|
873
|
-
ctx.addIssue({ code:
|
|
941
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
874
942
|
if (!needsOptions && r.options?.length)
|
|
875
|
-
ctx.addIssue({ code:
|
|
943
|
+
ctx.addIssue({ code: z12.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
876
944
|
});
|
|
877
|
-
var NotifyStatusSchema =
|
|
878
|
-
var AgentStateSchema =
|
|
879
|
-
var TurnSchema =
|
|
880
|
-
prompt:
|
|
881
|
-
reply:
|
|
945
|
+
var NotifyStatusSchema = z12.enum(["pending", "answered", "ignored"]);
|
|
946
|
+
var AgentStateSchema = z12.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
947
|
+
var TurnSchema = z12.object({
|
|
948
|
+
prompt: z12.string(),
|
|
949
|
+
reply: z12.string()
|
|
882
950
|
});
|
|
883
|
-
var UserAnswerSchema =
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
951
|
+
var UserAnswerSchema = z12.discriminatedUnion("kind", [
|
|
952
|
+
z12.object({ kind: z12.literal("option"), optionId: z12.string(), label: z12.string().optional() }),
|
|
953
|
+
z12.object({ kind: z12.literal("text"), text: z12.string() }),
|
|
954
|
+
z12.object({ kind: z12.literal("ignored") }),
|
|
955
|
+
z12.object({ kind: z12.literal("multi"), optionIds: z12.array(z12.string()), labels: z12.array(z12.string()).optional() }),
|
|
956
|
+
z12.object({ kind: z12.literal("ranked"), optionIds: z12.array(z12.string()), labels: z12.array(z12.string()).optional() }),
|
|
957
|
+
z12.object({ kind: z12.literal("clarify"), chunks: z12.array(z12.string()).min(1) }),
|
|
958
|
+
z12.object({ kind: z12.literal("confirm"), approved: z12.boolean() }),
|
|
959
|
+
z12.object({ kind: z12.literal("turns"), turns: z12.array(TurnSchema).min(1) }),
|
|
892
960
|
/** An auto-answer derived from the user's PAST decisions (docs/brain/broker/precedent-design.md §2):
|
|
893
961
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
894
962
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
895
963
|
* card the user can reply to — the broker never overrides the user. */
|
|
896
|
-
|
|
964
|
+
z12.object({ kind: z12.literal("precedent"), answer: z12.string(), derivation: z12.string(), sources: z12.array(z12.string()).min(1) })
|
|
897
965
|
]);
|
|
898
|
-
var IntentSchema =
|
|
966
|
+
var IntentSchema = z12.object({
|
|
899
967
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
900
968
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
901
969
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
902
970
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
903
|
-
kind:
|
|
904
|
-
detail:
|
|
971
|
+
kind: z12.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
972
|
+
detail: z12.string(),
|
|
905
973
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
906
974
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
907
975
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
908
976
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
909
|
-
dueInSeconds:
|
|
977
|
+
dueInSeconds: z12.number().int().positive().optional(),
|
|
910
978
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
911
979
|
* already read the utterance, so `signals.kind` stops defaulting to
|
|
912
980
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
913
981
|
* about what cannot answer "is the bot looping less this week?". */
|
|
914
|
-
fault:
|
|
982
|
+
fault: z12.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
915
983
|
});
|
|
916
|
-
var RideAlongSchema =
|
|
984
|
+
var RideAlongSchema = z12.object({
|
|
917
985
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
918
|
-
noteId:
|
|
986
|
+
noteId: z12.string(),
|
|
919
987
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
920
|
-
text:
|
|
988
|
+
text: z12.string(),
|
|
921
989
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
922
|
-
parentId:
|
|
990
|
+
parentId: z12.string().nullable()
|
|
923
991
|
});
|
|
924
|
-
var AwaitItemSchema =
|
|
925
|
-
|
|
926
|
-
type:
|
|
927
|
-
parentId:
|
|
928
|
-
notificationId:
|
|
929
|
-
workId:
|
|
930
|
-
decisionId:
|
|
992
|
+
var AwaitItemSchema = z12.discriminatedUnion("type", [
|
|
993
|
+
z12.object({
|
|
994
|
+
type: z12.literal("reply"),
|
|
995
|
+
parentId: z12.string(),
|
|
996
|
+
notificationId: z12.string(),
|
|
997
|
+
workId: z12.string().uuid().optional(),
|
|
998
|
+
decisionId: z12.string().uuid().optional(),
|
|
931
999
|
answer: UserAnswerSchema,
|
|
932
1000
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
933
1001
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -937,107 +1005,107 @@ var AwaitItemSchema = z9.discriminatedUnion("type", [
|
|
|
937
1005
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
938
1006
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
939
1007
|
* a sentence with a hole in it is worse than no sentence. */
|
|
940
|
-
note:
|
|
1008
|
+
note: z12.string().optional(),
|
|
941
1009
|
/** The call record rendered for THIS agent (`docs/brain/voice/record-design.md`): the words the
|
|
942
1010
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
943
1011
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
944
1012
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
945
|
-
transcript:
|
|
1013
|
+
transcript: z12.string().optional(),
|
|
946
1014
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
947
1015
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
948
|
-
covered:
|
|
1016
|
+
covered: z12.array(z12.string()).optional(),
|
|
949
1017
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
950
1018
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
951
1019
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
952
1020
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
953
1021
|
* dropped. Absent/empty = nothing owed. */
|
|
954
|
-
also:
|
|
1022
|
+
also: z12.array(RideAlongSchema).optional()
|
|
955
1023
|
}),
|
|
956
|
-
|
|
957
|
-
type:
|
|
958
|
-
parentId:
|
|
959
|
-
notificationId:
|
|
960
|
-
remindAt:
|
|
1024
|
+
z12.object({
|
|
1025
|
+
type: z12.literal("remind"),
|
|
1026
|
+
parentId: z12.string(),
|
|
1027
|
+
notificationId: z12.string(),
|
|
1028
|
+
remindAt: z12.string().datetime({ offset: true }),
|
|
961
1029
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
962
|
-
remindInSeconds:
|
|
1030
|
+
remindInSeconds: z12.number()
|
|
963
1031
|
}),
|
|
964
1032
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
965
1033
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
966
1034
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
967
1035
|
* re-orient via contact({})). */
|
|
968
|
-
|
|
969
|
-
type:
|
|
970
|
-
parentId:
|
|
971
|
-
notificationId:
|
|
1036
|
+
z12.object({
|
|
1037
|
+
type: z12.literal("superseded"),
|
|
1038
|
+
parentId: z12.string(),
|
|
1039
|
+
notificationId: z12.string()
|
|
972
1040
|
}),
|
|
973
1041
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
974
1042
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
975
1043
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
976
1044
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
977
1045
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
978
|
-
|
|
979
|
-
type:
|
|
980
|
-
notificationId:
|
|
981
|
-
inFlight:
|
|
982
|
-
turn:
|
|
983
|
-
idx:
|
|
984
|
-
prompt:
|
|
985
|
-
reply:
|
|
986
|
-
acts:
|
|
1046
|
+
z12.object({
|
|
1047
|
+
type: z12.literal("partial"),
|
|
1048
|
+
notificationId: z12.string(),
|
|
1049
|
+
inFlight: z12.literal(true),
|
|
1050
|
+
turn: z12.object({
|
|
1051
|
+
idx: z12.number(),
|
|
1052
|
+
prompt: z12.string(),
|
|
1053
|
+
reply: z12.string(),
|
|
1054
|
+
acts: z12.array(IntentSchema).nullable().optional()
|
|
987
1055
|
})
|
|
988
1056
|
}),
|
|
989
|
-
|
|
990
|
-
type:
|
|
991
|
-
also:
|
|
1057
|
+
z12.object({
|
|
1058
|
+
type: z12.literal("idle"),
|
|
1059
|
+
also: z12.array(RideAlongSchema).optional(),
|
|
992
1060
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
993
1061
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
994
1062
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
995
1063
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
996
1064
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
997
|
-
inFlight:
|
|
1065
|
+
inFlight: z12.boolean().optional()
|
|
998
1066
|
})
|
|
999
1067
|
]);
|
|
1000
|
-
var VoiceKeySchema =
|
|
1001
|
-
var AgendaTurnSchema =
|
|
1068
|
+
var VoiceKeySchema = z12.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
1069
|
+
var AgendaTurnSchema = z12.object({
|
|
1002
1070
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
1003
1071
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
1004
1072
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
1005
1073
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
1006
1074
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
1007
1075
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
1008
|
-
id:
|
|
1076
|
+
id: z12.string().optional(),
|
|
1009
1077
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
1010
1078
|
* the planner declares duplicates instead of asking them twice. */
|
|
1011
|
-
coveredIds:
|
|
1079
|
+
coveredIds: z12.array(z12.string()).optional(),
|
|
1012
1080
|
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
1013
1081
|
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
1014
|
-
info:
|
|
1015
|
-
question:
|
|
1082
|
+
info: z12.array(z12.string().min(1)).default([]),
|
|
1083
|
+
question: z12.string().min(1).nullable(),
|
|
1016
1084
|
/** True on the one turn carrying the agent's own declared question. */
|
|
1017
|
-
asks:
|
|
1085
|
+
asks: z12.boolean().optional(),
|
|
1018
1086
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
1019
1087
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
1020
1088
|
* which route nothing. */
|
|
1021
|
-
claimId:
|
|
1089
|
+
claimId: z12.string().optional(),
|
|
1022
1090
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
1023
|
-
voice:
|
|
1091
|
+
voice: z12.string().optional(),
|
|
1024
1092
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
1025
1093
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
1026
1094
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
1027
1095
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
1028
|
-
agent:
|
|
1096
|
+
agent: z12.string().optional(),
|
|
1029
1097
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
1030
1098
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
1031
1099
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
1032
1100
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
1033
1101
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
1034
|
-
agentId:
|
|
1102
|
+
agentId: z12.string().optional(),
|
|
1035
1103
|
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
1036
1104
|
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
1037
1105
|
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
1038
|
-
onTable:
|
|
1106
|
+
onTable: z12.boolean().optional(),
|
|
1039
1107
|
select: SelectShapeSchema.optional(),
|
|
1040
|
-
options:
|
|
1108
|
+
options: z12.array(OptionSchema.omit({ id: true })).optional(),
|
|
1041
1109
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
1042
1110
|
paces itself now — a short beat between the sentences of a turn, the longer one at its end
|
|
1043
1111
|
(owner, 2026-09-30: "remove the bot deciding pace") — and it does that where the words are
|
|
@@ -1046,33 +1114,33 @@ var AgendaTurnSchema = z9.object({
|
|
|
1046
1114
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
1047
1115
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
1048
1116
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
1049
|
-
blocking:
|
|
1117
|
+
blocking: z12.boolean().optional(),
|
|
1050
1118
|
/** SPOKEN ONLY IF THEY SAY NOTHING (owner, 2026-10-01, call 812de935: "you're gonna re-ask, but it
|
|
1051
1119
|
* shouldn't be the same words … more like, hey, are you still there, or are you able to answer, or
|
|
1052
1120
|
* would you need more information"). The walk holds this turn out of its queue; at the queue's end it
|
|
1053
1121
|
* listens for the last word, and only if that listen is silent is this turn said and asked. If they
|
|
1054
1122
|
* speak, it is dropped and their words are taken like any reply. */
|
|
1055
|
-
ifSilent:
|
|
1123
|
+
ifSilent: z12.boolean().optional()
|
|
1056
1124
|
});
|
|
1057
1125
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
1058
|
-
var InboxItemSchema =
|
|
1059
|
-
id:
|
|
1060
|
-
tokenId:
|
|
1126
|
+
var InboxItemSchema = z12.object({
|
|
1127
|
+
id: z12.string(),
|
|
1128
|
+
tokenId: z12.string().optional(),
|
|
1061
1129
|
status: NotifyStatusSchema,
|
|
1062
1130
|
context: ContextSchema,
|
|
1063
|
-
options:
|
|
1131
|
+
options: z12.array(OptionSchema).optional(),
|
|
1064
1132
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
1065
|
-
points:
|
|
1133
|
+
points: z12.array(z12.string()).optional(),
|
|
1066
1134
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
1067
1135
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
1068
1136
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
1069
1137
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
1070
|
-
asks:
|
|
1138
|
+
asks: z12.boolean().optional(),
|
|
1071
1139
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
1072
1140
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
1073
1141
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
1074
1142
|
* which must never CLAIM stalled. */
|
|
1075
|
-
lastSeenAt:
|
|
1143
|
+
lastSeenAt: z12.string().optional(),
|
|
1076
1144
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
1077
1145
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
1078
1146
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -1082,7 +1150,7 @@ var InboxItemSchema = z9.object({
|
|
|
1082
1150
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
1083
1151
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
1084
1152
|
* `createdAt`. */
|
|
1085
|
-
agentStateAt:
|
|
1153
|
+
agentStateAt: z12.string().datetime().optional(),
|
|
1086
1154
|
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (docs/clients/app/walk/design.md §11, owner
|
|
1087
1155
|
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
1088
1156
|
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
@@ -1096,61 +1164,61 @@ var InboxItemSchema = z9.object({
|
|
|
1096
1164
|
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
1097
1165
|
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
1098
1166
|
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
1099
|
-
questions:
|
|
1100
|
-
id:
|
|
1101
|
-
entryId:
|
|
1102
|
-
title:
|
|
1103
|
-
state:
|
|
1104
|
-
answer:
|
|
1167
|
+
questions: z12.array(z12.object({
|
|
1168
|
+
id: z12.string(),
|
|
1169
|
+
entryId: z12.string(),
|
|
1170
|
+
title: z12.string(),
|
|
1171
|
+
state: z12.enum(["open", "answered"]),
|
|
1172
|
+
answer: z12.string().nullable(),
|
|
1105
1173
|
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
1106
1174
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
1107
1175
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
1108
1176
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
1109
|
-
agent:
|
|
1177
|
+
agent: z12.object({ tokenId: z12.string(), name: z12.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
1110
1178
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
1111
1179
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
1112
1180
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
1113
1181
|
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
1114
|
-
options:
|
|
1182
|
+
options: z12.array(OptionSchema).optional(),
|
|
1115
1183
|
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
1116
1184
|
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
1117
1185
|
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
1118
1186
|
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
1119
1187
|
* preview layout for any `options`, and must see exactly what it did. */
|
|
1120
|
-
choices:
|
|
1188
|
+
choices: z12.array(OptionSchema).optional(),
|
|
1121
1189
|
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
1122
|
-
select:
|
|
1190
|
+
select: z12.enum(["one", "many", "rank"]).optional()
|
|
1123
1191
|
})).optional(),
|
|
1124
|
-
visuals:
|
|
1192
|
+
visuals: z12.array(VisualSchema).optional(),
|
|
1125
1193
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
1126
1194
|
* agent's suggestion, or a default silly name). */
|
|
1127
|
-
name:
|
|
1195
|
+
name: z12.string(),
|
|
1128
1196
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
1129
1197
|
voice: VoiceKeySchema.optional(),
|
|
1130
|
-
repo:
|
|
1131
|
-
branch:
|
|
1132
|
-
createdAt:
|
|
1133
|
-
snoozedUntil:
|
|
1198
|
+
repo: z12.string().optional(),
|
|
1199
|
+
branch: z12.string().optional(),
|
|
1200
|
+
createdAt: z12.string().datetime(),
|
|
1201
|
+
snoozedUntil: z12.string().datetime().optional(),
|
|
1134
1202
|
agentState: AgentStateSchema.default("idle"),
|
|
1135
1203
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
1136
1204
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
1137
1205
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
1138
|
-
turn:
|
|
1206
|
+
turn: z12.enum(["you", "agent"]).default("you"),
|
|
1139
1207
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
1140
1208
|
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
1141
1209
|
* error badge + Retry. */
|
|
1142
|
-
error:
|
|
1210
|
+
error: z12.string().optional(),
|
|
1143
1211
|
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
1144
1212
|
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
1145
1213
|
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
1146
1214
|
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
1147
|
-
cold:
|
|
1148
|
-
clarifies:
|
|
1215
|
+
cold: z12.string().datetime().optional(),
|
|
1216
|
+
clarifies: z12.string().optional(),
|
|
1149
1217
|
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
1150
1218
|
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
1151
1219
|
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
1152
1220
|
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
1153
|
-
onCall:
|
|
1221
|
+
onCall: z12.literal(true).optional(),
|
|
1154
1222
|
/** THE RING, ON THE ITEM (docs/clients/app/walk/design.md §12 §17, #2251): the last ring on this card was
|
|
1155
1223
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
1156
1224
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -1160,31 +1228,31 @@ var InboxItemSchema = z9.object({
|
|
|
1160
1228
|
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
1161
1229
|
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
1162
1230
|
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
1163
|
-
ring:
|
|
1164
|
-
declinedAt:
|
|
1165
|
-
anchorAt:
|
|
1166
|
-
nextRingAt:
|
|
1167
|
-
step:
|
|
1231
|
+
ring: z12.object({
|
|
1232
|
+
declinedAt: z12.string().datetime(),
|
|
1233
|
+
anchorAt: z12.string().datetime(),
|
|
1234
|
+
nextRingAt: z12.string().datetime().nullable(),
|
|
1235
|
+
step: z12.number().int()
|
|
1168
1236
|
}).optional(),
|
|
1169
1237
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
1170
1238
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
1171
1239
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
1172
1240
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
1173
1241
|
* feedback ABOUT. */
|
|
1174
|
-
why:
|
|
1242
|
+
why: z12.object({
|
|
1175
1243
|
asked: NotifyLevelSchema,
|
|
1176
1244
|
got: NotifyLevelSchema,
|
|
1177
|
-
because:
|
|
1178
|
-
line:
|
|
1245
|
+
because: z12.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
1246
|
+
line: z12.string()
|
|
1179
1247
|
}).optional(),
|
|
1180
|
-
select:
|
|
1181
|
-
confirmStyle:
|
|
1248
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
1249
|
+
confirmStyle: z12.enum(["yesno", "approve"]).default("yesno").describe(
|
|
1182
1250
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
1183
1251
|
),
|
|
1184
1252
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
1185
1253
|
* urgency (see the main README's "premier use case" + docs/delivery/notify/states.md). Drives the
|
|
1186
1254
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
1187
|
-
blocking:
|
|
1255
|
+
blocking: z12.boolean().default(false),
|
|
1188
1256
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
1189
1257
|
answer: UserAnswerSchema.optional(),
|
|
1190
1258
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -1192,17 +1260,17 @@ var InboxItemSchema = z9.object({
|
|
|
1192
1260
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
1193
1261
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
1194
1262
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
1195
|
-
communication:
|
|
1196
|
-
deliveryId:
|
|
1197
|
-
kind:
|
|
1198
|
-
entryId:
|
|
1199
|
-
goalIds:
|
|
1200
|
-
decisionNeedId:
|
|
1201
|
-
sealed:
|
|
1202
|
-
goalState:
|
|
1263
|
+
communication: z12.object({
|
|
1264
|
+
deliveryId: z12.string(),
|
|
1265
|
+
kind: z12.enum(["notification", "call"]),
|
|
1266
|
+
entryId: z12.string(),
|
|
1267
|
+
goalIds: z12.array(z12.string()),
|
|
1268
|
+
decisionNeedId: z12.string().optional(),
|
|
1269
|
+
sealed: z12.boolean(),
|
|
1270
|
+
goalState: z12.string().optional(),
|
|
1203
1271
|
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
1204
1272
|
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
1205
|
-
goalTitle:
|
|
1273
|
+
goalTitle: z12.string().optional()
|
|
1206
1274
|
}).optional(),
|
|
1207
1275
|
/** WHAT THIS CARD IS, IN TWELVE CHARACTERS (#3019) — the hash of every other field on it, stamped
|
|
1208
1276
|
* by the one read that serves the open list (`apps/api/src/inbox/project.ts` `inboxFor`). It is
|
|
@@ -1214,27 +1282,27 @@ var InboxItemSchema = z9.object({
|
|
|
1214
1282
|
* own moves (its Goal's state and name, how cold the work behind it has gone, whether a ring is
|
|
1215
1283
|
* live). Optional, so a fixture, the demo and the archive lens need not spell one, and a card
|
|
1216
1284
|
* with no rev is simply always re-sent. */
|
|
1217
|
-
rev:
|
|
1285
|
+
rev: z12.string().optional()
|
|
1218
1286
|
});
|
|
1219
1287
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
1220
|
-
var PushTokenSchema =
|
|
1221
|
-
voipToken:
|
|
1222
|
-
alertToken:
|
|
1223
|
-
fcmToken:
|
|
1224
|
-
platform:
|
|
1288
|
+
var PushTokenSchema = z12.object({
|
|
1289
|
+
voipToken: z12.string().min(1).optional(),
|
|
1290
|
+
alertToken: z12.string().min(1).optional(),
|
|
1291
|
+
fcmToken: z12.string().min(1).optional(),
|
|
1292
|
+
platform: z12.enum(["ios", "android"])
|
|
1225
1293
|
}).superRefine((v, ctx) => {
|
|
1226
1294
|
if (v.platform !== "ios") return;
|
|
1227
1295
|
for (const field of ["voipToken", "alertToken"]) {
|
|
1228
1296
|
const token = v[field];
|
|
1229
1297
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
1230
1298
|
ctx.addIssue({
|
|
1231
|
-
code:
|
|
1299
|
+
code: z12.ZodIssueCode.custom,
|
|
1232
1300
|
path: [field],
|
|
1233
1301
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
1234
1302
|
});
|
|
1235
1303
|
}
|
|
1236
1304
|
});
|
|
1237
|
-
var MissedCallSchema =
|
|
1305
|
+
var MissedCallSchema = z12.enum([
|
|
1238
1306
|
"retry_10m",
|
|
1239
1307
|
"retry_30m",
|
|
1240
1308
|
"retry_60m",
|
|
@@ -1244,33 +1312,33 @@ var MissedCallSchema = z9.enum([
|
|
|
1244
1312
|
"inbox",
|
|
1245
1313
|
"dismiss"
|
|
1246
1314
|
]);
|
|
1247
|
-
var
|
|
1248
|
-
var QUIET = ` Nothing rings from ${
|
|
1249
|
-
var BrokerTuningSchema =
|
|
1315
|
+
var clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
1316
|
+
var QUIET = ` Nothing rings from ${clock2(NIGHT.from)} to ${clock2(NIGHT.to)} your time; the count waits for morning.`;
|
|
1317
|
+
var BrokerTuningSchema = z12.object({
|
|
1250
1318
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
1251
|
-
ackVerbosity:
|
|
1319
|
+
ackVerbosity: z12.enum(["normal", "none"]).optional(),
|
|
1252
1320
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
1253
1321
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
1254
|
-
clarifyEagerness:
|
|
1322
|
+
clarifyEagerness: z12.enum(["low", "normal", "high"]).optional(),
|
|
1255
1323
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
1256
|
-
phrasebook:
|
|
1324
|
+
phrasebook: z12.array(z12.object({ say: z12.string().min(1).max(60), mean: z12.string().min(1).max(120) })).max(24).optional(),
|
|
1257
1325
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
1258
1326
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
1259
1327
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
1260
1328
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
1261
1329
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
1262
|
-
language:
|
|
1330
|
+
language: z12.enum(["en", "es"]).optional()
|
|
1263
1331
|
});
|
|
1264
|
-
var UserSettingsSchema =
|
|
1265
|
-
permissions:
|
|
1266
|
-
call:
|
|
1267
|
-
banner:
|
|
1268
|
-
push:
|
|
1332
|
+
var UserSettingsSchema = z12.object({
|
|
1333
|
+
permissions: z12.object({
|
|
1334
|
+
call: z12.boolean(),
|
|
1335
|
+
banner: z12.boolean(),
|
|
1336
|
+
push: z12.boolean()
|
|
1269
1337
|
}),
|
|
1270
1338
|
/** LockedIn / Default / DateNight on screen; the stored words are unchanged on purpose —
|
|
1271
1339
|
* they are an enum on a live column across every account, and the rename is a rename of
|
|
1272
1340
|
* what people read (owner, 2026-09-30). */
|
|
1273
|
-
sessionMode:
|
|
1341
|
+
sessionMode: z12.enum(["default", "all_calls", "silent"]),
|
|
1274
1342
|
/** `silentPush` lived here until #2813 and is now GONE, field and column both. It was kept as an
|
|
1275
1343
|
* optional long after DateNight stopped reading it, on the theory that a phone on an older
|
|
1276
1344
|
* bundle PATCHing the whole settings object would be REFUSED for sending a key we had stopped
|
|
@@ -1279,7 +1347,7 @@ var UserSettingsSchema = z9.object({
|
|
|
1279
1347
|
* an old bundle's `silentPush` is accepted and ignored. Worth remembering before keeping the
|
|
1280
1348
|
* next dead field for the same reason. */
|
|
1281
1349
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
1282
|
-
improveConsent:
|
|
1350
|
+
improveConsent: z12.boolean(),
|
|
1283
1351
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
1284
1352
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
1285
1353
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -1287,17 +1355,17 @@ var UserSettingsSchema = z9.object({
|
|
|
1287
1355
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
1288
1356
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
1289
1357
|
* write, 'hosted' on read (see store.ts). */
|
|
1290
|
-
voiceMode:
|
|
1358
|
+
voiceMode: z12.enum(["hosted", "on_device"]).optional(),
|
|
1291
1359
|
/** Talk — after you answer, the next step is read aloud (docs/clients/app/walk/design.md §6). ALWAYS ON until
|
|
1292
1360
|
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
1293
1361
|
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
1294
1362
|
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
1295
1363
|
* read (see store.ts). */
|
|
1296
|
-
talk:
|
|
1364
|
+
talk: z12.boolean().optional(),
|
|
1297
1365
|
/** CALL DIAGNOSTICS (owner, 2026-10-01): the call report carries each listen and the bot's own
|
|
1298
1366
|
* load timings. SERVER-SET, no UI — on for every account that existed on 2026-10-01, off for
|
|
1299
1367
|
* newer ones (migration 20261001132859). Read-only here: the settings PATCH never writes it. */
|
|
1300
|
-
callDiagnostics:
|
|
1368
|
+
callDiagnostics: z12.boolean().optional(),
|
|
1301
1369
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
1302
1370
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
1303
1371
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -1305,7 +1373,7 @@ var UserSettingsSchema = z9.object({
|
|
|
1305
1373
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
1306
1374
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
1307
1375
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
1308
|
-
voiceTuning:
|
|
1376
|
+
voiceTuning: z12.record(z12.string(), z12.union([z12.number(), z12.string()])).optional(),
|
|
1309
1377
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
1310
1378
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
1311
1379
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -1314,53 +1382,53 @@ var UserSettingsSchema = z9.object({
|
|
|
1314
1382
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
1315
1383
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
1316
1384
|
* call — honest about what we know. */
|
|
1317
|
-
timezone:
|
|
1385
|
+
timezone: z12.string().min(1).max(64).optional(),
|
|
1318
1386
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
1319
1387
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
1320
1388
|
broker: BrokerTuningSchema.optional()
|
|
1321
1389
|
});
|
|
1322
|
-
var HistoryWorkSchema =
|
|
1323
|
-
id:
|
|
1324
|
-
title:
|
|
1325
|
-
state:
|
|
1390
|
+
var HistoryWorkSchema = z12.object({
|
|
1391
|
+
id: z12.string(),
|
|
1392
|
+
title: z12.string(),
|
|
1393
|
+
state: z12.enum(["done", "cancelled"]),
|
|
1326
1394
|
/** Who held it (`agent:<tokenId>` or `human:<userId>`). */
|
|
1327
|
-
assignee:
|
|
1395
|
+
assignee: z12.string()
|
|
1328
1396
|
});
|
|
1329
|
-
var HistoryEntrySchema =
|
|
1330
|
-
|
|
1331
|
-
|
|
1397
|
+
var HistoryEntrySchema = z12.union([
|
|
1398
|
+
z12.object({ at: z12.string(), card: InboxItemSchema }),
|
|
1399
|
+
z12.object({ at: z12.string(), work: HistoryWorkSchema })
|
|
1332
1400
|
]);
|
|
1333
|
-
var HistoryPageSchema =
|
|
1334
|
-
entries:
|
|
1335
|
-
next:
|
|
1401
|
+
var HistoryPageSchema = z12.object({
|
|
1402
|
+
entries: z12.array(HistoryEntrySchema),
|
|
1403
|
+
next: z12.string().nullable()
|
|
1336
1404
|
});
|
|
1337
1405
|
var ACTIVITY_LINES = 2;
|
|
1338
1406
|
var ACTIVITY_LINE_MAX = 80;
|
|
1339
|
-
var AgentActivitySchema =
|
|
1407
|
+
var AgentActivitySchema = z12.object({
|
|
1340
1408
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
1341
|
-
lines:
|
|
1409
|
+
lines: z12.array(z12.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
1342
1410
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
1343
1411
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
1344
|
-
at:
|
|
1412
|
+
at: z12.string().datetime()
|
|
1345
1413
|
});
|
|
1346
|
-
var ConnectionSummarySchema =
|
|
1414
|
+
var ConnectionSummarySchema = z12.object({
|
|
1347
1415
|
/** The connection = the agent's token id (used to address a request). */
|
|
1348
|
-
id:
|
|
1416
|
+
id: z12.string(),
|
|
1349
1417
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
1350
1418
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
1351
1419
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
1352
1420
|
* docs/server/tokens/devices-vs-agents-design.md. */
|
|
1353
|
-
kind:
|
|
1421
|
+
kind: z12.enum(["device", "agent"]).optional(),
|
|
1354
1422
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
1355
1423
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
1356
1424
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
1357
|
-
mintedByDevice:
|
|
1358
|
-
device:
|
|
1425
|
+
mintedByDevice: z12.string().nullable().optional(),
|
|
1426
|
+
device: z12.string().nullable(),
|
|
1359
1427
|
/** The agent's display name (the single pairing name). */
|
|
1360
|
-
name:
|
|
1428
|
+
name: z12.string(),
|
|
1361
1429
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
1362
1430
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
1363
|
-
provider:
|
|
1431
|
+
provider: z12.string().nullable(),
|
|
1364
1432
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
1365
1433
|
voice: VoiceKeySchema.nullable(),
|
|
1366
1434
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -1371,34 +1439,34 @@ var ConnectionSummarySchema = z9.object({
|
|
|
1371
1439
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
1372
1440
|
* set once must not overrule a rule they set about one agent. */
|
|
1373
1441
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
1374
|
-
createdAt:
|
|
1442
|
+
createdAt: z12.string().datetime(),
|
|
1375
1443
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
1376
1444
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
1377
|
-
lastContactAt:
|
|
1445
|
+
lastContactAt: z12.string().datetime().nullable(),
|
|
1378
1446
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
1379
1447
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
1380
|
-
lastSeenAt:
|
|
1448
|
+
lastSeenAt: z12.string().datetime().nullable().optional(),
|
|
1381
1449
|
/** WORKING, NOT JUST CONNECTED (owner, 2026-09-30): the last time the agent itself acted on one of
|
|
1382
1450
|
* its Goals — wrote on one or recorded an operation (`tokens.last_worked_at`). Within
|
|
1383
1451
|
* `WORKING_MS` it is working; otherwise it is connected but idle. Null = not seen working yet. */
|
|
1384
|
-
lastWorkedAt:
|
|
1452
|
+
lastWorkedAt: z12.string().datetime().nullable().optional(),
|
|
1385
1453
|
/** The oldest of its Goals that is `ready` for it — work handed to it that nobody has started.
|
|
1386
1454
|
* With no work of its own for `WORKING_MS`, an agent sitting on this is not taking its work. */
|
|
1387
|
-
oldestReadyAt:
|
|
1455
|
+
oldestReadyAt: z12.string().datetime().nullable().optional(),
|
|
1388
1456
|
/** What a live desktop can run (docs/clients/desktop/companion.md §2.2), advertised on its heartbeat:
|
|
1389
1457
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
1390
1458
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
1391
|
-
runtime:
|
|
1459
|
+
runtime: z12.object({
|
|
1392
1460
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
1393
1461
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
1394
|
-
version:
|
|
1395
|
-
harnesses:
|
|
1396
|
-
workspaces:
|
|
1462
|
+
version: z12.string().optional(),
|
|
1463
|
+
harnesses: z12.array(z12.object({ name: z12.string(), label: z12.string(), status: z12.string() })).optional(),
|
|
1464
|
+
workspaces: z12.array(z12.string()).optional(),
|
|
1397
1465
|
/** THE GIT REPOS IN THOSE FOLDERS (2026-10-01, Goal 26982211): each granted folder that is a
|
|
1398
1466
|
* repo, and each repo directly inside one, with its `origin` remote. A session started for
|
|
1399
1467
|
* work on `mauurda/paigy` opens in that repo rather than the folder above it, where the repo's
|
|
1400
1468
|
* own AGENTS.md is never read (`workspaceForRepo`). Absent on hosts that predate it. */
|
|
1401
|
-
repos:
|
|
1469
|
+
repos: z12.array(z12.object({ path: z12.string(), remote: z12.string() })).optional()
|
|
1402
1470
|
}).optional(),
|
|
1403
1471
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
1404
1472
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -1407,160 +1475,166 @@ var ConnectionSummarySchema = z9.object({
|
|
|
1407
1475
|
activity: AgentActivitySchema.optional(),
|
|
1408
1476
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
1409
1477
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
1410
|
-
managed:
|
|
1478
|
+
managed: z12.boolean()
|
|
1411
1479
|
});
|
|
1412
|
-
var LedgerItemSchema =
|
|
1413
|
-
var AgentLedgerSchema =
|
|
1480
|
+
var LedgerItemSchema = z12.object({ id: z12.string(), parentId: z12.string(), title: z12.string(), createdAt: z12.string() });
|
|
1481
|
+
var AgentLedgerSchema = z12.object({
|
|
1414
1482
|
/** Null when the agent has not named itself yet — never a placeholder (owner, 2026-10-01). */
|
|
1415
|
-
agent:
|
|
1483
|
+
agent: z12.object({ id: z12.string(), name: z12.string().nullable(), revokedAt: z12.string().nullable() }),
|
|
1416
1484
|
/** Its own questions you have not answered. */
|
|
1417
|
-
asks:
|
|
1485
|
+
asks: z12.array(LedgerItemSchema),
|
|
1418
1486
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
1419
|
-
answered:
|
|
1487
|
+
answered: z12.array(LedgerItemSchema),
|
|
1420
1488
|
/** Requests you sent it that it never took. */
|
|
1421
|
-
requests:
|
|
1422
|
-
goals:
|
|
1423
|
-
callbacks:
|
|
1489
|
+
requests: z12.array(LedgerItemSchema),
|
|
1490
|
+
goals: z12.array(z12.object({ id: z12.string(), outcome: z12.string(), state: z12.string() })),
|
|
1491
|
+
callbacks: z12.array(z12.object({ id: z12.string(), parentId: z12.string(), trigger: z12.string(), note: z12.string(), dueAt: z12.string().nullable() }))
|
|
1424
1492
|
});
|
|
1425
|
-
var ReassignResultSchema =
|
|
1426
|
-
moved:
|
|
1427
|
-
parentId:
|
|
1493
|
+
var ReassignResultSchema = z12.object({
|
|
1494
|
+
moved: z12.object({ asks: z12.number(), answered: z12.number(), requests: z12.number(), goals: z12.number(), callbacks: z12.number() }),
|
|
1495
|
+
parentId: z12.string().nullable()
|
|
1428
1496
|
});
|
|
1429
|
-
var LessonStateSchema =
|
|
1430
|
-
var LessonViewSchema =
|
|
1431
|
-
id:
|
|
1432
|
-
text:
|
|
1497
|
+
var LessonStateSchema = z12.enum(["active", "proposed", "retired"]);
|
|
1498
|
+
var LessonViewSchema = z12.object({
|
|
1499
|
+
id: z12.string(),
|
|
1500
|
+
text: z12.string(),
|
|
1433
1501
|
state: LessonStateSchema,
|
|
1434
1502
|
/** The Goal it is scoped to; null = the whole account. */
|
|
1435
|
-
scopeGoalId:
|
|
1436
|
-
goalTitle:
|
|
1437
|
-
version:
|
|
1438
|
-
pinned:
|
|
1503
|
+
scopeGoalId: z12.string().nullable(),
|
|
1504
|
+
goalTitle: z12.string().nullable(),
|
|
1505
|
+
version: z12.number(),
|
|
1506
|
+
pinned: z12.boolean(),
|
|
1439
1507
|
/** When the person last wrote its text themselves. */
|
|
1440
|
-
editedAt:
|
|
1441
|
-
createdAt:
|
|
1442
|
-
updatedAt:
|
|
1508
|
+
editedAt: z12.string().nullable(),
|
|
1509
|
+
createdAt: z12.string(),
|
|
1510
|
+
updatedAt: z12.string(),
|
|
1443
1511
|
/** The Entries it came from, oldest first; `words` is null when an Entry has none to show (sealed). */
|
|
1444
|
-
sources:
|
|
1512
|
+
sources: z12.array(z12.object({ entryId: z12.string(), words: z12.string().nullable(), at: z12.string() }))
|
|
1445
1513
|
});
|
|
1446
|
-
var QueueQuestionSchema =
|
|
1514
|
+
var QueueQuestionSchema = z12.object({
|
|
1447
1515
|
/** The decision need's id — what an answer is accepted against. */
|
|
1448
|
-
id:
|
|
1516
|
+
id: z12.string(),
|
|
1449
1517
|
/** The words that were asked, from the request Entry that asked them. */
|
|
1450
|
-
question:
|
|
1518
|
+
question: z12.string(),
|
|
1451
1519
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
1452
1520
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
1453
1521
|
* can answer. */
|
|
1454
|
-
deliveryId:
|
|
1522
|
+
deliveryId: z12.string().nullable().default(null),
|
|
1455
1523
|
/** The Entry the ruling is about. */
|
|
1456
|
-
aboutId:
|
|
1524
|
+
aboutId: z12.string().nullable().default(null),
|
|
1457
1525
|
/** Empty for a free-text question. */
|
|
1458
|
-
options:
|
|
1459
|
-
select:
|
|
1460
|
-
askedAt:
|
|
1526
|
+
options: z12.array(OptionSchema).default([]),
|
|
1527
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
1528
|
+
askedAt: z12.string(),
|
|
1461
1529
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
1462
|
-
answeredAt:
|
|
1530
|
+
answeredAt: z12.string().nullable().default(null),
|
|
1463
1531
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
1464
1532
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
1465
1533
|
* for a settled question whose reply carried nothing readable. */
|
|
1466
|
-
answer:
|
|
1534
|
+
answer: z12.string().nullable().default(null),
|
|
1467
1535
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
1468
1536
|
* an `InboxItem`'s `communication.goalIds[0]` (docs/clients/app/walk/design.md §12 item 3).
|
|
1469
1537
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
1470
1538
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
1471
1539
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
1472
1540
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
1473
|
-
goalId:
|
|
1541
|
+
goalId: z12.string(),
|
|
1474
1542
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
1475
1543
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
1476
1544
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
1477
1545
|
* gate at all. */
|
|
1478
|
-
blocked:
|
|
1546
|
+
blocked: z12.boolean().default(false)
|
|
1479
1547
|
});
|
|
1480
|
-
var QueueReplySchema =
|
|
1548
|
+
var QueueReplySchema = z12.object({
|
|
1481
1549
|
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
1482
1550
|
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
1483
1551
|
* walk can name it in its zoom. */
|
|
1484
|
-
id:
|
|
1552
|
+
id: z12.string(),
|
|
1485
1553
|
/** The Goal the note is on. */
|
|
1486
|
-
goalId:
|
|
1554
|
+
goalId: z12.string(),
|
|
1487
1555
|
/** What the note said. */
|
|
1488
|
-
note:
|
|
1556
|
+
note: z12.string(),
|
|
1489
1557
|
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
1490
|
-
deliveryId:
|
|
1491
|
-
requestEntryId:
|
|
1492
|
-
askedAt:
|
|
1558
|
+
deliveryId: z12.string(),
|
|
1559
|
+
requestEntryId: z12.string(),
|
|
1560
|
+
askedAt: z12.string(),
|
|
1493
1561
|
/** When the person last replied — the window's start. */
|
|
1494
|
-
repliedAt:
|
|
1562
|
+
repliedAt: z12.string(),
|
|
1495
1563
|
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
1496
|
-
reply:
|
|
1564
|
+
reply: z12.string().nullable()
|
|
1497
1565
|
});
|
|
1498
|
-
var QueueItemSchema =
|
|
1499
|
-
id:
|
|
1566
|
+
var QueueItemSchema = z12.object({
|
|
1567
|
+
id: z12.string(),
|
|
1500
1568
|
/** One-line headline — the first sentence of the outcome. */
|
|
1501
|
-
title:
|
|
1569
|
+
title: z12.string(),
|
|
1502
1570
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
1503
|
-
intent:
|
|
1571
|
+
intent: z12.string(),
|
|
1504
1572
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
1505
|
-
state:
|
|
1573
|
+
state: z12.string(),
|
|
1506
1574
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
1507
|
-
assignee:
|
|
1575
|
+
assignee: z12.string().nullable().default(null),
|
|
1508
1576
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
1509
|
-
progress:
|
|
1577
|
+
progress: z12.string().nullable().default(null),
|
|
1510
1578
|
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
1511
1579
|
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
1512
|
-
progressLine:
|
|
1513
|
-
reviewPending:
|
|
1514
|
-
dueAt:
|
|
1580
|
+
progressLine: z12.string().nullable().optional(),
|
|
1581
|
+
reviewPending: z12.boolean().default(false),
|
|
1582
|
+
dueAt: z12.string().nullable().default(null),
|
|
1515
1583
|
/** WHEN ITS OWNER SAID DONE WHILE CHILDREN WERE OPEN (#2704): its own work is finished and it closes
|
|
1516
1584
|
* with its last open child. Null otherwise; optional, so hand-built queues need not spell it. */
|
|
1517
|
-
finishedAt:
|
|
1585
|
+
finishedAt: z12.string().nullable().optional(),
|
|
1518
1586
|
/** The Goal this one was opened under; null at the root. */
|
|
1519
|
-
parentGoalId:
|
|
1587
|
+
parentGoalId: z12.string().nullable().default(null),
|
|
1520
1588
|
/** Goals opened under this one — only those the same list holds. */
|
|
1521
|
-
childGoalIds:
|
|
1589
|
+
childGoalIds: z12.array(z12.string()).default([]),
|
|
1522
1590
|
/** Goals this one waits on (start or finish gates). */
|
|
1523
|
-
dependencyGoalIds:
|
|
1591
|
+
dependencyGoalIds: z12.array(z12.string()).default([]),
|
|
1524
1592
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
1525
|
-
blocked:
|
|
1593
|
+
blocked: z12.boolean().default(false),
|
|
1526
1594
|
/** Its questions: every OPEN one, and at most ten settled, newest settled first
|
|
1527
1595
|
* (20260929133308) — the page decides which of them to show. NOT the whole set: `asked` and
|
|
1528
1596
|
* `answered` are, and a settled one's words are a line (280 characters), its body read when the
|
|
1529
1597
|
* question is opened. */
|
|
1530
|
-
questions:
|
|
1598
|
+
questions: z12.array(QueueQuestionSchema).default([]),
|
|
1531
1599
|
/** HOW MANY QUESTIONS THIS WORK HAS ASKED, and how many are answered — the Goal's own totals,
|
|
1532
1600
|
* bounded at 100 server-side. A tally counted off `questions` is a wrong number that looks
|
|
1533
1601
|
* right once the cap bites (`walk/trees.ts` `tallyOf`). Optional, and defaulted from the array
|
|
1534
1602
|
* by the projection, so hand-built queues (fixtures, the demo) need not spell them. */
|
|
1535
|
-
asked:
|
|
1536
|
-
answered:
|
|
1603
|
+
asked: z12.number().optional(),
|
|
1604
|
+
answered: z12.number().optional(),
|
|
1537
1605
|
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
1538
1606
|
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
1539
1607
|
* not spell an empty list. */
|
|
1540
|
-
replies:
|
|
1608
|
+
replies: z12.array(QueueReplySchema).optional(),
|
|
1541
1609
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
1542
|
-
repo:
|
|
1543
|
-
createdAt:
|
|
1544
|
-
updatedAt:
|
|
1610
|
+
repo: z12.string().nullable().optional(),
|
|
1611
|
+
createdAt: z12.string(),
|
|
1612
|
+
updatedAt: z12.string().nullable().default(null),
|
|
1545
1613
|
/** When its owner last SAID something about it (the newest `progress` Entry: a contact update kept
|
|
1546
1614
|
* as progress). `updatedAt` moves for reasons nobody chose — a
|
|
1547
1615
|
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
1548
|
-
lastProgressAt:
|
|
1616
|
+
lastProgressAt: z12.string().nullable().optional(),
|
|
1549
1617
|
/** THE GOAL'S NEWEST WORD, FROM EITHER SIDE (owner, 2026-09-27): the newest Entry on it, of any
|
|
1550
1618
|
* kind — what the person added ("Add to this"), their reply, the agent's ask or its progress
|
|
1551
1619
|
* note. A progress note is an Entry, so this is already the newer of the two: the person's note
|
|
1552
1620
|
* shows the moment it is written, and the agent's reply or next note replaces it by being newer.
|
|
1553
1621
|
* `said` is bounded to 280 characters server-side (a line, not the conversation). Null when the
|
|
1554
1622
|
* Goal carries no readable Entry; optional, so hand-built queues need not spell it. */
|
|
1555
|
-
latest:
|
|
1556
|
-
from:
|
|
1557
|
-
said:
|
|
1558
|
-
at:
|
|
1559
|
-
entryId:
|
|
1623
|
+
latest: z12.object({
|
|
1624
|
+
from: z12.enum(["person", "agent"]),
|
|
1625
|
+
said: z12.string(),
|
|
1626
|
+
at: z12.string(),
|
|
1627
|
+
entryId: z12.string(),
|
|
1560
1628
|
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
1561
1629
|
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
1562
1630
|
* none was. The Work row reads it before `said`. */
|
|
1563
|
-
summary:
|
|
1631
|
+
summary: z12.string().optional(),
|
|
1632
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
1633
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
1634
|
+
titles: z12.array(z12.string()).optional(),
|
|
1635
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
1636
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
1637
|
+
line: z12.string().optional()
|
|
1564
1638
|
}).nullable().optional(),
|
|
1565
1639
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
1566
1640
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -1573,7 +1647,7 @@ var QueueItemSchema = z9.object({
|
|
|
1573
1647
|
* minute after the person speaks erases their instant from it, and the durable traces the client
|
|
1574
1648
|
* can see (`replies`, `questions[].answeredAt`) miss a spontaneous note entirely — a `request`
|
|
1575
1649
|
* Entry with no `about_id` is in neither. */
|
|
1576
|
-
lastPersonAt:
|
|
1650
|
+
lastPersonAt: z12.string().nullable().optional(),
|
|
1577
1651
|
/** WHAT THIS ROW IS, IN TWELVE CHARACTERS (#2928) — the hash of every other field on it, stamped
|
|
1578
1652
|
* by the one projection that builds the row (`apps/api/src/goal/queue.ts`). It is how the
|
|
1579
1653
|
* incremental read knows a row has not moved: the phone echoes back the revs it holds
|
|
@@ -1584,40 +1658,40 @@ var QueueItemSchema = z9.object({
|
|
|
1584
1658
|
* the row shows that no `updated_at` moves for (`active` lapsing, a dependency's state, a
|
|
1585
1659
|
* sibling appearing in `childGoalIds`) cannot go unnoticed. Optional because a hand-built
|
|
1586
1660
|
* queue (a fixture, the demo) spells none, and a row with no rev is simply always re-sent. */
|
|
1587
|
-
rev:
|
|
1661
|
+
rev: z12.string().optional()
|
|
1588
1662
|
});
|
|
1589
|
-
var QueueDeltaSchema =
|
|
1590
|
-
ids:
|
|
1591
|
-
items:
|
|
1663
|
+
var QueueDeltaSchema = z12.object({
|
|
1664
|
+
ids: z12.array(z12.string()),
|
|
1665
|
+
items: z12.array(QueueItemSchema)
|
|
1592
1666
|
});
|
|
1593
1667
|
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
1594
|
-
var NoteSourceSchema =
|
|
1595
|
-
var NoteStatusSchema =
|
|
1596
|
-
var NoteRepeatSchema =
|
|
1597
|
-
var DecisionSchema =
|
|
1598
|
-
id:
|
|
1668
|
+
var NoteSourceSchema = z12.enum(["app", "call"]);
|
|
1669
|
+
var NoteStatusSchema = z12.enum(["open", "assigned", "in_progress", "done"]);
|
|
1670
|
+
var NoteRepeatSchema = z12.enum(["once", "until_done"]);
|
|
1671
|
+
var DecisionSchema = z12.object({
|
|
1672
|
+
id: z12.string(),
|
|
1599
1673
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
1600
1674
|
* extensibility seam — any conversation can accrue decisions). */
|
|
1601
|
-
noteId:
|
|
1675
|
+
noteId: z12.string().nullable(),
|
|
1602
1676
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
1603
|
-
question:
|
|
1677
|
+
question: z12.string(),
|
|
1604
1678
|
/** The user's ruling; null while the question is open. */
|
|
1605
|
-
answer:
|
|
1606
|
-
decidedAt:
|
|
1607
|
-
createdAt:
|
|
1679
|
+
answer: z12.string().nullable(),
|
|
1680
|
+
decidedAt: z12.string().nullable(),
|
|
1681
|
+
createdAt: z12.string()
|
|
1608
1682
|
});
|
|
1609
|
-
var NoteSchema =
|
|
1610
|
-
id:
|
|
1683
|
+
var NoteSchema = z12.object({
|
|
1684
|
+
id: z12.string(),
|
|
1611
1685
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
1612
|
-
title:
|
|
1686
|
+
title: z12.string(),
|
|
1613
1687
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
1614
|
-
intent:
|
|
1688
|
+
intent: z12.string(),
|
|
1615
1689
|
source: NoteSourceSchema,
|
|
1616
1690
|
status: NoteStatusSchema,
|
|
1617
1691
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
1618
|
-
assignee:
|
|
1692
|
+
assignee: z12.string().nullable(),
|
|
1619
1693
|
/** The request thread minted at assignment; null until assigned. */
|
|
1620
|
-
parentId:
|
|
1694
|
+
parentId: z12.string().nullable(),
|
|
1621
1695
|
/** REMINDERS (docs/model/notes/reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
1622
1696
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
1623
1697
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -1626,152 +1700,152 @@ var NoteSchema = z9.object({
|
|
|
1626
1700
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
1627
1701
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
1628
1702
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
1629
|
-
dueAt:
|
|
1703
|
+
dueAt: z12.string().nullable().default(null),
|
|
1630
1704
|
repeat: NoteRepeatSchema.default("once"),
|
|
1631
1705
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
1632
|
-
rides:
|
|
1633
|
-
lastRideAt:
|
|
1634
|
-
createdAt:
|
|
1706
|
+
rides: z12.number().int().default(0),
|
|
1707
|
+
lastRideAt: z12.string().nullable().default(null),
|
|
1708
|
+
createdAt: z12.string()
|
|
1635
1709
|
});
|
|
1636
|
-
var TriageItemSchema =
|
|
1637
|
-
noteId:
|
|
1710
|
+
var TriageItemSchema = z12.object({
|
|
1711
|
+
noteId: z12.string(),
|
|
1638
1712
|
/** The note's headline at run time. */
|
|
1639
|
-
title:
|
|
1713
|
+
title: z12.string(),
|
|
1640
1714
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
1641
1715
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
1642
1716
|
* Never a model's reasoning transcript, never an id. */
|
|
1643
|
-
why:
|
|
1717
|
+
why: z12.string()
|
|
1644
1718
|
});
|
|
1645
|
-
var TriageAssignmentSchema =
|
|
1719
|
+
var TriageAssignmentSchema = z12.object({
|
|
1646
1720
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
1647
|
-
agent:
|
|
1721
|
+
agent: z12.string(),
|
|
1648
1722
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
1649
1723
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
1650
|
-
agentName:
|
|
1651
|
-
notes:
|
|
1724
|
+
agentName: z12.string(),
|
|
1725
|
+
notes: z12.array(TriageItemSchema)
|
|
1652
1726
|
});
|
|
1653
|
-
var TriageStatusSchema =
|
|
1654
|
-
var SubmitTriageSchema =
|
|
1727
|
+
var TriageStatusSchema = z12.enum(["open", "superseded", "dismissed"]);
|
|
1728
|
+
var SubmitTriageSchema = z12.object({
|
|
1655
1729
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
1656
1730
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
1657
1731
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
1658
1732
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
1659
|
-
provider:
|
|
1733
|
+
provider: z12.string().min(1).max(60),
|
|
1660
1734
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
1661
|
-
model:
|
|
1735
|
+
model: z12.string().max(200).nullable().optional(),
|
|
1662
1736
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
1663
1737
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
1664
|
-
reviewed:
|
|
1665
|
-
close:
|
|
1666
|
-
stale:
|
|
1667
|
-
assign:
|
|
1738
|
+
reviewed: z12.number().int().min(0).max(1e4).default(0),
|
|
1739
|
+
close: z12.array(TriageItemSchema).max(200).default([]),
|
|
1740
|
+
stale: z12.array(TriageItemSchema).max(200).default([]),
|
|
1741
|
+
assign: z12.array(TriageAssignmentSchema).max(50).default([])
|
|
1668
1742
|
});
|
|
1669
1743
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
1670
|
-
id:
|
|
1671
|
-
runAt:
|
|
1744
|
+
id: z12.string(),
|
|
1745
|
+
runAt: z12.string(),
|
|
1672
1746
|
status: TriageStatusSchema,
|
|
1673
|
-
model:
|
|
1747
|
+
model: z12.string().nullable().default(null)
|
|
1674
1748
|
});
|
|
1675
|
-
var AcceptTriageSchema =
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
group:
|
|
1680
|
-
agent:
|
|
1681
|
-
noteIds:
|
|
1749
|
+
var AcceptTriageSchema = z12.discriminatedUnion("group", [
|
|
1750
|
+
z12.object({ group: z12.literal("close"), noteIds: z12.array(z12.string()).max(200).optional() }),
|
|
1751
|
+
z12.object({ group: z12.literal("stale"), noteIds: z12.array(z12.string()).max(200).optional() }),
|
|
1752
|
+
z12.object({
|
|
1753
|
+
group: z12.literal("assign"),
|
|
1754
|
+
agent: z12.string().min(1),
|
|
1755
|
+
noteIds: z12.array(z12.string()).max(200).optional()
|
|
1682
1756
|
})
|
|
1683
1757
|
]);
|
|
1684
|
-
var AcceptTriageResultSchema =
|
|
1685
|
-
accepted:
|
|
1686
|
-
failed:
|
|
1758
|
+
var AcceptTriageResultSchema = z12.object({
|
|
1759
|
+
accepted: z12.array(z12.string()),
|
|
1760
|
+
failed: z12.array(z12.object({ noteId: z12.string(), reason: z12.string() }))
|
|
1687
1761
|
});
|
|
1688
|
-
var DeliveryModeSchema =
|
|
1689
|
-
var RegisterDeliverySchema =
|
|
1690
|
-
var OAuthStartSchema =
|
|
1691
|
-
provider:
|
|
1692
|
-
returnTo:
|
|
1762
|
+
var DeliveryModeSchema = z12.enum(["poll", "self_hosted"]);
|
|
1763
|
+
var RegisterDeliverySchema = z12.object({ mode: DeliveryModeSchema });
|
|
1764
|
+
var OAuthStartSchema = z12.object({
|
|
1765
|
+
provider: z12.enum(["cma"]),
|
|
1766
|
+
returnTo: z12.string().min(1)
|
|
1693
1767
|
});
|
|
1694
|
-
var DeliveryConfigSchema =
|
|
1695
|
-
tokenId:
|
|
1768
|
+
var DeliveryConfigSchema = z12.object({
|
|
1769
|
+
tokenId: z12.string(),
|
|
1696
1770
|
mode: DeliveryModeSchema,
|
|
1697
1771
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
1698
1772
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
1699
1773
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
1700
|
-
realtime:
|
|
1774
|
+
realtime: z12.object({ url: z12.string(), anonKey: z12.string() }).nullable()
|
|
1701
1775
|
});
|
|
1702
|
-
var HostDecisionSchema =
|
|
1776
|
+
var HostDecisionSchema = z12.object({
|
|
1703
1777
|
/** The agent's token id: the row's `recipient`. */
|
|
1704
|
-
agent:
|
|
1705
|
-
decision:
|
|
1778
|
+
agent: z12.string().uuid(),
|
|
1779
|
+
decision: z12.enum(["stood_back", "took_over"]),
|
|
1706
1780
|
/** The work it was about: the Goal waiting on that agent next (`claimable` on its `contact({})` read). */
|
|
1707
|
-
goalId:
|
|
1781
|
+
goalId: z12.string().uuid().nullable().optional(),
|
|
1708
1782
|
/** When the server last heard from the agent, as the host read it: the presence it stood back for. */
|
|
1709
|
-
seenAt:
|
|
1783
|
+
seenAt: z12.string().datetime().nullable().optional(),
|
|
1710
1784
|
/** When that work last moved (`claimable.since` on an agent's `contact({})` read), the fact the bound is judged on. */
|
|
1711
|
-
since:
|
|
1785
|
+
since: z12.string().datetime().nullable().optional(),
|
|
1712
1786
|
/** What the host said, in its log's own words: why it stood back, or what the take-over did. */
|
|
1713
|
-
said:
|
|
1787
|
+
said: z12.string().max(300).optional()
|
|
1714
1788
|
});
|
|
1715
|
-
var WakeNudgeSchema =
|
|
1716
|
-
kind:
|
|
1717
|
-
notificationId:
|
|
1718
|
-
parentId:
|
|
1789
|
+
var WakeNudgeSchema = z12.object({
|
|
1790
|
+
kind: z12.enum(["reply", "request", "callback"]),
|
|
1791
|
+
notificationId: z12.string().optional(),
|
|
1792
|
+
parentId: z12.string()
|
|
1719
1793
|
});
|
|
1720
|
-
var PairingStatusSchema =
|
|
1721
|
-
var DeviceCodeSchema =
|
|
1722
|
-
device_code:
|
|
1723
|
-
user_code:
|
|
1724
|
-
verification_uri:
|
|
1725
|
-
verification_uri_complete:
|
|
1726
|
-
interval:
|
|
1727
|
-
expires_in:
|
|
1794
|
+
var PairingStatusSchema = z12.enum(["pending", "approved", "denied", "expired"]);
|
|
1795
|
+
var DeviceCodeSchema = z12.object({
|
|
1796
|
+
device_code: z12.string(),
|
|
1797
|
+
user_code: z12.string(),
|
|
1798
|
+
verification_uri: z12.string().url(),
|
|
1799
|
+
verification_uri_complete: z12.string().url(),
|
|
1800
|
+
interval: z12.number(),
|
|
1801
|
+
expires_in: z12.number()
|
|
1728
1802
|
});
|
|
1729
|
-
var DeviceInfoSchema =
|
|
1730
|
-
code:
|
|
1803
|
+
var DeviceInfoSchema = z12.object({
|
|
1804
|
+
code: z12.string(),
|
|
1731
1805
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
1732
1806
|
* pre-filling the name field the human can edit. */
|
|
1733
|
-
name:
|
|
1807
|
+
name: z12.string(),
|
|
1734
1808
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
1735
1809
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
1736
1810
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
1737
1811
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
1738
|
-
agent:
|
|
1739
|
-
device:
|
|
1812
|
+
agent: z12.string().optional(),
|
|
1813
|
+
device: z12.string().nullable(),
|
|
1740
1814
|
status: PairingStatusSchema
|
|
1741
1815
|
});
|
|
1742
|
-
var DeviceTokenSchema =
|
|
1743
|
-
access_token:
|
|
1816
|
+
var DeviceTokenSchema = z12.object({
|
|
1817
|
+
access_token: z12.string(),
|
|
1744
1818
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
1745
1819
|
* a default silly name). */
|
|
1746
|
-
name:
|
|
1747
|
-
device:
|
|
1820
|
+
name: z12.string(),
|
|
1821
|
+
device: z12.string().nullable(),
|
|
1748
1822
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
1749
1823
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
1750
|
-
voice:
|
|
1824
|
+
voice: z12.string().nullable().optional(),
|
|
1751
1825
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
1752
1826
|
* that survives a rename. Cached by the host's identity beat. */
|
|
1753
|
-
token_id:
|
|
1827
|
+
token_id: z12.string().nullable().optional(),
|
|
1754
1828
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
1755
1829
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
1756
1830
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
1757
1831
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
1758
|
-
workspace:
|
|
1832
|
+
workspace: z12.string().nullable().optional(),
|
|
1759
1833
|
/** Local host recovery must preserve the launch's runtime and Paigy identity. */
|
|
1760
|
-
harness:
|
|
1761
|
-
session_id:
|
|
1834
|
+
harness: z12.enum(["claude", "codex", "agy"]).optional(),
|
|
1835
|
+
session_id: z12.string().uuid().optional(),
|
|
1762
1836
|
/** A conversation the host must not resume: its context is full, so every turn fails
|
|
1763
1837
|
* ("Prompt is too long"). Written when a run hits it (`run.ts` `onFull`); the host skips a slot
|
|
1764
1838
|
* whose resumable session is this one, so its Goals reach the dead-agent handoff instead of a
|
|
1765
1839
|
* copy that types the person's words into a turn that cannot run (Calls, 2026-10-06). */
|
|
1766
|
-
full_session:
|
|
1767
|
-
uik_pub:
|
|
1840
|
+
full_session: z12.string().optional(),
|
|
1841
|
+
uik_pub: z12.string().nullable().optional()
|
|
1768
1842
|
});
|
|
1769
|
-
var SupportRequestSchema =
|
|
1770
|
-
email:
|
|
1771
|
-
message:
|
|
1772
|
-
name:
|
|
1843
|
+
var SupportRequestSchema = z12.object({
|
|
1844
|
+
email: z12.string().email().max(320),
|
|
1845
|
+
message: z12.string().trim().min(1).max(5e3),
|
|
1846
|
+
name: z12.string().trim().max(120).optional()
|
|
1773
1847
|
});
|
|
1774
|
-
var NotificationFeedbackKindSchema =
|
|
1848
|
+
var NotificationFeedbackKindSchema = z12.enum([
|
|
1775
1849
|
"break_down",
|
|
1776
1850
|
// "This should be more than one ask — break it down."
|
|
1777
1851
|
"regenerate_options",
|
|
@@ -1786,119 +1860,121 @@ var NotificationFeedbackKindSchema = z9.enum([
|
|
|
1786
1860
|
// anything else — the note carries it.
|
|
1787
1861
|
]);
|
|
1788
1862
|
var SlimOptionSchema = OptionSchema.omit({ html: true });
|
|
1789
|
-
var QuestionRowSchema =
|
|
1863
|
+
var QuestionRowSchema = z12.object({
|
|
1790
1864
|
/** The card's id (`deliveryId:needId`, or `deliveryId:entryId` for an update), as the inbox mints it. */
|
|
1791
|
-
id:
|
|
1792
|
-
deliveryId:
|
|
1793
|
-
entryId:
|
|
1865
|
+
id: z12.string(),
|
|
1866
|
+
deliveryId: z12.string(),
|
|
1867
|
+
entryId: z12.string(),
|
|
1794
1868
|
/** The decision it waits on; null for an update, which asks nothing. */
|
|
1795
|
-
needId:
|
|
1796
|
-
goalIds:
|
|
1869
|
+
needId: z12.string().nullable(),
|
|
1870
|
+
goalIds: z12.array(z12.string()),
|
|
1797
1871
|
/** The name of the work it is about, when the read could word it. */
|
|
1798
|
-
goalTitle:
|
|
1799
|
-
tokenId:
|
|
1800
|
-
name:
|
|
1801
|
-
title:
|
|
1802
|
-
body:
|
|
1803
|
-
select:
|
|
1804
|
-
options:
|
|
1805
|
-
hasPreview:
|
|
1806
|
-
blocking:
|
|
1807
|
-
askedAt:
|
|
1872
|
+
goalTitle: z12.string().optional(),
|
|
1873
|
+
tokenId: z12.string().optional(),
|
|
1874
|
+
name: z12.string(),
|
|
1875
|
+
title: z12.string(),
|
|
1876
|
+
body: z12.string(),
|
|
1877
|
+
select: z12.enum(["one", "many", "rank", "confirm", "text"]),
|
|
1878
|
+
options: z12.array(SlimOptionSchema),
|
|
1879
|
+
hasPreview: z12.boolean(),
|
|
1880
|
+
blocking: z12.boolean(),
|
|
1881
|
+
askedAt: z12.string().datetime(),
|
|
1808
1882
|
ring: InboxItemSchema.shape.ring,
|
|
1809
|
-
onCall:
|
|
1810
|
-
sealed:
|
|
1883
|
+
onCall: z12.literal(true).optional(),
|
|
1884
|
+
sealed: z12.boolean()
|
|
1811
1885
|
});
|
|
1812
|
-
var WorkStateSchema =
|
|
1813
|
-
var WorkRowSchema =
|
|
1814
|
-
id:
|
|
1815
|
-
parentId:
|
|
1816
|
-
title:
|
|
1886
|
+
var WorkStateSchema = z12.enum(["ready", "active", "waiting", "done", "cancelled"]);
|
|
1887
|
+
var WorkRowSchema = z12.object({
|
|
1888
|
+
id: z12.string(),
|
|
1889
|
+
parentId: z12.string().nullable(),
|
|
1890
|
+
title: z12.string(),
|
|
1817
1891
|
/** Straight off the Goal. */
|
|
1818
1892
|
state: WorkStateSchema,
|
|
1819
|
-
owner:
|
|
1820
|
-
revision:
|
|
1893
|
+
owner: z12.string().nullable(),
|
|
1894
|
+
revision: z12.number().int(),
|
|
1821
1895
|
/** Open questions on it, counted to 100. */
|
|
1822
|
-
waiting:
|
|
1896
|
+
waiting: z12.number().int(),
|
|
1823
1897
|
/** Held by a gate on work that is not done. */
|
|
1824
|
-
blocked:
|
|
1825
|
-
lastProgressAt:
|
|
1898
|
+
blocked: z12.boolean(),
|
|
1899
|
+
lastProgressAt: z12.string().datetime().nullable(),
|
|
1826
1900
|
/** The line written for its newest progress note, else that note's first words. */
|
|
1827
|
-
line:
|
|
1901
|
+
line: z12.string().nullable(),
|
|
1828
1902
|
/** Work directly under it, counted to 100; the list carries up to 12 of them. */
|
|
1829
|
-
children:
|
|
1830
|
-
createdAt:
|
|
1831
|
-
updatedAt:
|
|
1903
|
+
children: z12.number().int(),
|
|
1904
|
+
createdAt: z12.string().datetime(),
|
|
1905
|
+
updatedAt: z12.string().datetime(),
|
|
1832
1906
|
/** When anything at or under it last moved — the order the list is in. */
|
|
1833
|
-
activeAt:
|
|
1907
|
+
activeAt: z12.string().datetime(),
|
|
1834
1908
|
/** A sealed outcome has no title here; the work's page opens it. */
|
|
1835
|
-
sealed:
|
|
1909
|
+
sealed: z12.boolean()
|
|
1836
1910
|
});
|
|
1837
1911
|
var ComputerRowSchema = ConnectionSummarySchema.omit({ activity: true });
|
|
1838
1912
|
var AgentRowSchema = ComputerRowSchema.extend({
|
|
1839
1913
|
/** Open questions it is asking the person, over every open card; null when that read failed. */
|
|
1840
|
-
asking:
|
|
1841
|
-
oldestAskAt:
|
|
1914
|
+
asking: z12.number().int().nullable(),
|
|
1915
|
+
oldestAskAt: z12.string().datetime().nullable(),
|
|
1842
1916
|
/** Up to three of the live Goals it holds, oldest first (the order it picks them up), and how
|
|
1843
1917
|
* many in all among the account's 200 most recently active agent-held live Goals
|
|
1844
1918
|
* (`agent_holds`); null when that read failed. */
|
|
1845
|
-
holds:
|
|
1846
|
-
held:
|
|
1919
|
+
holds: z12.array(z12.object({ id: z12.string(), title: z12.string() })).nullable(),
|
|
1920
|
+
held: z12.number().int().nullable(),
|
|
1847
1921
|
/** The earliest instant any Goal it holds went quiet, by the one rule (`coldSince`); null
|
|
1848
1922
|
* while none has, or when that read failed. */
|
|
1849
|
-
cold:
|
|
1923
|
+
cold: z12.string().datetime().nullable(),
|
|
1850
1924
|
/** The newest line of its working log, and when the harness saw it. */
|
|
1851
|
-
line:
|
|
1852
|
-
lineAt:
|
|
1925
|
+
line: z12.string().nullable(),
|
|
1926
|
+
lineAt: z12.string().datetime().nullable()
|
|
1853
1927
|
});
|
|
1854
|
-
var SnapshotSchema =
|
|
1928
|
+
var SnapshotSchema = z12.object({
|
|
1855
1929
|
/** The API's clock, taken before the first read: what a later delta will start from. */
|
|
1856
|
-
at:
|
|
1857
|
-
questions:
|
|
1930
|
+
at: z12.string().datetime(),
|
|
1931
|
+
questions: z12.object({
|
|
1858
1932
|
/** The newest 30 open cards, questions before updates. */
|
|
1859
|
-
items:
|
|
1933
|
+
items: z12.array(QuestionRowSchema),
|
|
1860
1934
|
/** Every open question, and apart from them every update, and what was put off. */
|
|
1861
|
-
total:
|
|
1862
|
-
updates:
|
|
1863
|
-
putOff:
|
|
1935
|
+
total: z12.number().int(),
|
|
1936
|
+
updates: z12.number().int(),
|
|
1937
|
+
putOff: z12.number().int()
|
|
1864
1938
|
}).nullable(),
|
|
1865
|
-
agents:
|
|
1939
|
+
agents: z12.object({
|
|
1866
1940
|
/** Up to 60, most recently seen first. */
|
|
1867
|
-
items:
|
|
1868
|
-
more:
|
|
1941
|
+
items: z12.array(AgentRowSchema),
|
|
1942
|
+
more: z12.boolean()
|
|
1869
1943
|
}).nullable(),
|
|
1870
|
-
work:
|
|
1944
|
+
work: z12.object({
|
|
1871
1945
|
/** The 60 most recently active roots, each followed by up to 12 children; 240 rows at most. */
|
|
1872
|
-
items:
|
|
1946
|
+
items: z12.array(WorkRowSchema),
|
|
1873
1947
|
/** How much work is behind each of the Work tab's four filters, each counted to 100, read with
|
|
1874
1948
|
* the rows. `work_list` (20260928023533) owns the predicates: Live is `ready`, `active` or
|
|
1875
1949
|
* `waiting`; Waiting on you is live work with an open question or an unmet gate; Not started
|
|
1876
1950
|
* is `ready`; Done is `done` or `cancelled`. */
|
|
1877
|
-
counts:
|
|
1951
|
+
counts: z12.object({ live: z12.number().int(), waiting: z12.number().int(), notStarted: z12.number().int(), done: z12.number().int() })
|
|
1878
1952
|
}).nullable(),
|
|
1879
|
-
you:
|
|
1953
|
+
you: z12.object({
|
|
1880
1954
|
settings: UserSettingsSchema,
|
|
1881
|
-
callable:
|
|
1955
|
+
callable: z12.boolean(),
|
|
1882
1956
|
/** Up to 20 paired computers; null when the roster read failed. */
|
|
1883
|
-
computers:
|
|
1957
|
+
computers: z12.array(ComputerRowSchema).nullable()
|
|
1884
1958
|
}).nullable()
|
|
1885
1959
|
});
|
|
1886
|
-
var CallRecapSchema =
|
|
1887
|
-
call:
|
|
1888
|
-
status:
|
|
1889
|
-
startedAt:
|
|
1890
|
-
durationMs:
|
|
1891
|
-
|
|
1960
|
+
var CallRecapSchema = z12.object({
|
|
1961
|
+
call: z12.object({
|
|
1962
|
+
status: z12.string(),
|
|
1963
|
+
startedAt: z12.string(),
|
|
1964
|
+
durationMs: z12.number().nullable(),
|
|
1965
|
+
/** Bot-clock pickup offset; absent before the timestamp release, null before its report lands. */
|
|
1966
|
+
pickupMs: z12.number().nullable().optional(),
|
|
1967
|
+
agents: z12.array(z12.object({ id: z12.string(), name: z12.string().nullable() }))
|
|
1892
1968
|
}),
|
|
1893
|
-
topics:
|
|
1894
|
-
goalId:
|
|
1895
|
-
title:
|
|
1896
|
-
owner:
|
|
1897
|
-
state:
|
|
1969
|
+
topics: z12.array(z12.object({
|
|
1970
|
+
goalId: z12.string().uuid(),
|
|
1971
|
+
title: z12.string(),
|
|
1972
|
+
owner: z12.string(),
|
|
1973
|
+
state: z12.string(),
|
|
1898
1974
|
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
1899
1975
|
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
1900
|
-
presented:
|
|
1901
|
-
questions:
|
|
1976
|
+
presented: z12.boolean(),
|
|
1977
|
+
questions: z12.array(z12.object({ id: z12.string().uuid(), state: z12.string(), title: z12.string() })),
|
|
1902
1978
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
1903
1979
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
1904
1980
|
* gave, so the row scans like a chosen option and their own words stay under it. Absent when
|
|
@@ -1908,31 +1984,33 @@ var CallRecapSchema = z9.object({
|
|
|
1908
1984
|
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
1909
1985
|
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
1910
1986
|
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
1911
|
-
lines:
|
|
1912
|
-
entryId:
|
|
1913
|
-
words:
|
|
1914
|
-
summary:
|
|
1915
|
-
about:
|
|
1916
|
-
reply:
|
|
1917
|
-
replyKind:
|
|
1918
|
-
at:
|
|
1987
|
+
lines: z12.array(z12.object({
|
|
1988
|
+
entryId: z12.string().uuid(),
|
|
1989
|
+
words: z12.string(),
|
|
1990
|
+
summary: z12.string().optional(),
|
|
1991
|
+
about: z12.string().nullable().optional(),
|
|
1992
|
+
reply: z12.string().nullable().optional(),
|
|
1993
|
+
replyKind: z12.string().nullable().optional(),
|
|
1994
|
+
at: z12.array(z12.number()).optional()
|
|
1919
1995
|
}))
|
|
1920
1996
|
})),
|
|
1921
|
-
unfiled:
|
|
1997
|
+
unfiled: z12.array(z12.object({ lineId: z12.string().uuid(), words: z12.string(), atMs: z12.number() })),
|
|
1922
1998
|
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
1923
1999
|
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
1924
2000
|
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
1925
2001
|
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
1926
2002
|
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
1927
2003
|
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
1928
|
-
said:
|
|
1929
|
-
lineId:
|
|
1930
|
-
speaker:
|
|
1931
|
-
agent:
|
|
1932
|
-
words:
|
|
1933
|
-
atMs:
|
|
2004
|
+
said: z12.array(z12.object({
|
|
2005
|
+
lineId: z12.string().uuid(),
|
|
2006
|
+
speaker: z12.enum(["user", "agent"]),
|
|
2007
|
+
agent: z12.object({ id: z12.string().nullable(), name: z12.string().nullable() }).nullable(),
|
|
2008
|
+
words: z12.string(),
|
|
2009
|
+
atMs: z12.number()
|
|
1934
2010
|
})),
|
|
1935
|
-
more:
|
|
2011
|
+
more: z12.object({ lines: z12.number(), entries: z12.number(), topics: z12.number() }),
|
|
2012
|
+
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
2013
|
+
next: z12.number().int().nonnegative().nullable().optional()
|
|
1936
2014
|
});
|
|
1937
2015
|
|
|
1938
2016
|
// src/listening.ts
|
|
@@ -1996,7 +2074,7 @@ function decideListen(f) {
|
|
|
1996
2074
|
return {
|
|
1997
2075
|
status: "start",
|
|
1998
2076
|
command: `PAIGY_AGENT=${word(f.slot)} PAIGY_SESSION_ID=${word(f.session)} ${f.env.CODEX_THREAD_ID ? `CODEX_THREAD_ID=${word(f.env.CODEX_THREAD_ID)} ` : ""}${f.env.PAIGY_ON_WAKE ? `PAIGY_ON_WAKE=${word(f.env.PAIGY_ON_WAKE)} ` : ""}${word(f.execPath)} ${word(f.listenJs)} --brief`,
|
|
1999
|
-
how: "Run this command NOW as a watched background task (your harness's background-task/monitor tool), and leave it running for this session. Each line it prints is a Delivery waiting for you: read the Goal it names with
|
|
2077
|
+
how: "Run this command NOW as a watched background task (your harness's background-task/monitor tool), and leave it running for this session. Each line it prints is a Delivery waiting for you: read the Goal it names with get_goals, and answer with contact. Start it once."
|
|
2000
2078
|
};
|
|
2001
2079
|
}
|
|
2002
2080
|
function listenMark(slot, home = homedir()) {
|