@paigy/mcp 0.40.11 → 0.40.15
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 +35 -9
- package/dist/{chunk-FQVK7SO4.js → chunk-37FS33UP.js} +56 -5
- package/dist/{chunk-H5GGWCPS.js → chunk-PHTQITTC.js} +835 -498
- package/dist/{chunk-I5QINUN6.js → chunk-SE2JCKDI.js} +593 -454
- package/dist/{chunk-XIHVHYDU.js → chunk-X6G7XXCP.js} +1 -1
- package/dist/dist-DKP3DJQJ.js +110 -0
- package/dist/enable.js +9 -4
- package/dist/index.js +29 -5
- package/dist/listen.js +48 -16
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/stalled.js +35 -0
- package/dist/statusline.js +1 -1
- package/package.json +3 -2
|
@@ -7,16 +7,18 @@ import { homedir } from "os";
|
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9
9
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
10
|
+
import { z as z4 } from "zod";
|
|
11
|
+
import { z } from "zod";
|
|
10
12
|
import { z as z3 } from "zod";
|
|
11
|
-
import { z as z2 } from "zod";
|
|
12
13
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
13
14
|
import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
14
15
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
|
|
15
|
-
import { z } from "zod";
|
|
16
|
+
import { z as z2 } from "zod";
|
|
16
17
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
17
18
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
18
19
|
import { homedir as homedir2 } from "os";
|
|
19
20
|
import { join as join2 } from "path";
|
|
21
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
20
22
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
21
23
|
import { WebSocket } from "undici";
|
|
22
24
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
@@ -77,6 +79,9 @@ function isNetworkError(e) {
|
|
|
77
79
|
async function reach(url, init) {
|
|
78
80
|
return send(url, init, sessionId());
|
|
79
81
|
}
|
|
82
|
+
function reachAs(session) {
|
|
83
|
+
return (url, init) => send(url, init, session);
|
|
84
|
+
}
|
|
80
85
|
async function send(url, init, session) {
|
|
81
86
|
try {
|
|
82
87
|
const headers = {
|
|
@@ -1301,6 +1306,190 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
1301
1306
|
};
|
|
1302
1307
|
var OPTIONS_MIN = 2;
|
|
1303
1308
|
var OPTIONS_MAX = 6;
|
|
1309
|
+
var OptionSchema = z.object({
|
|
1310
|
+
id: z.string(),
|
|
1311
|
+
label: z.string(),
|
|
1312
|
+
hint: z.string().max(500).describe("Optional short projection of consequence or action if this option is chosen (e.g. 'Reruns test suite', 'Merges to main').").optional(),
|
|
1313
|
+
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
1314
|
+
// the constraints below are what an agent reads when deciding to use these.
|
|
1315
|
+
html: z.string().max(16384).describe(
|
|
1316
|
+
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
1317
|
+
).optional(),
|
|
1318
|
+
image: z.string().url().describe(
|
|
1319
|
+
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
1320
|
+
).optional()
|
|
1321
|
+
});
|
|
1322
|
+
var OptionInputSchema = OptionSchema.omit({ id: true }).extend({
|
|
1323
|
+
label: z.string().trim().min(1).max(1e3)
|
|
1324
|
+
}).strict();
|
|
1325
|
+
function tzOffsetMinutes(tz, atMs) {
|
|
1326
|
+
try {
|
|
1327
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
1328
|
+
timeZone: tz,
|
|
1329
|
+
hour12: false,
|
|
1330
|
+
year: "numeric",
|
|
1331
|
+
month: "2-digit",
|
|
1332
|
+
day: "2-digit",
|
|
1333
|
+
hour: "2-digit",
|
|
1334
|
+
minute: "2-digit",
|
|
1335
|
+
second: "2-digit"
|
|
1336
|
+
});
|
|
1337
|
+
const p = {};
|
|
1338
|
+
for (const { type, value } of dtf.formatToParts(new Date(atMs))) p[type] = value;
|
|
1339
|
+
const asUtc = Date.UTC(
|
|
1340
|
+
Number(p["year"]),
|
|
1341
|
+
Number(p["month"]) - 1,
|
|
1342
|
+
Number(p["day"]),
|
|
1343
|
+
Number(p["hour"]) === 24 ? 0 : Number(p["hour"]),
|
|
1344
|
+
Number(p["minute"]),
|
|
1345
|
+
Number(p["second"])
|
|
1346
|
+
);
|
|
1347
|
+
return Math.round((asUtc - atMs) / 6e4);
|
|
1348
|
+
} catch {
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
function instantAt(tz, local) {
|
|
1353
|
+
const naive = Date.UTC(local.y, local.mo - 1, local.d, local.h, local.mi, 0);
|
|
1354
|
+
const first = tzOffsetMinutes(tz, naive);
|
|
1355
|
+
if (first === null) return null;
|
|
1356
|
+
const guess = naive - first * 6e4;
|
|
1357
|
+
const second = tzOffsetMinutes(tz, guess);
|
|
1358
|
+
return second === null ? guess : naive - second * 6e4;
|
|
1359
|
+
}
|
|
1360
|
+
function localDate(tz, atMs) {
|
|
1361
|
+
const off = tzOffsetMinutes(tz, atMs);
|
|
1362
|
+
if (off === null) return null;
|
|
1363
|
+
const shifted = new Date(atMs + off * 6e4);
|
|
1364
|
+
return {
|
|
1365
|
+
y: shifted.getUTCFullYear(),
|
|
1366
|
+
mo: shifted.getUTCMonth() + 1,
|
|
1367
|
+
d: shifted.getUTCDate(),
|
|
1368
|
+
h: shifted.getUTCHours(),
|
|
1369
|
+
mi: shifted.getUTCMinutes()
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
var PARTS = {
|
|
1373
|
+
earliest: { morning: 8, afternoon: 13, evening: 18, night: 20, noon: 12, midday: 12, lunch: 13 },
|
|
1374
|
+
ring: { morning: 9, afternoon: 13, evening: 18, night: 20, noon: 12, midday: 12, lunch: 13 }
|
|
1375
|
+
};
|
|
1376
|
+
var END_OF_DAY = 17;
|
|
1377
|
+
var VAGUE = /\b(later|in a bit|in a while)\b/i;
|
|
1378
|
+
var TODAY_ONLY = /\b(tonight|this evening)\b/i;
|
|
1379
|
+
var RELATIVE = /\bin\s+(a\s+couple\s+of|a\s+few|a|an|one|two|three|four|five|six|seven|eight|nine|ten|fifteen|twenty|thirty|forty|forty[- ]five|half\s+an|\d{1,3})\s*(minute|minutes|min|mins|hour|hours|hr|hrs|day|days|week|weeks)\b/i;
|
|
1380
|
+
var WORD_NUM = {
|
|
1381
|
+
a: 1,
|
|
1382
|
+
an: 1,
|
|
1383
|
+
one: 1,
|
|
1384
|
+
two: 2,
|
|
1385
|
+
three: 3,
|
|
1386
|
+
four: 4,
|
|
1387
|
+
five: 5,
|
|
1388
|
+
six: 6,
|
|
1389
|
+
seven: 7,
|
|
1390
|
+
eight: 8,
|
|
1391
|
+
nine: 9,
|
|
1392
|
+
ten: 10,
|
|
1393
|
+
fifteen: 15,
|
|
1394
|
+
twenty: 20,
|
|
1395
|
+
thirty: 30,
|
|
1396
|
+
forty: 40,
|
|
1397
|
+
"forty-five": 45,
|
|
1398
|
+
"forty five": 45,
|
|
1399
|
+
// Vague-but-real quantities the SDK's parser read and this one did not (#1367). "A few"
|
|
1400
|
+
// is deliberately small and "a couple" is exactly two — a caller who says either has
|
|
1401
|
+
// named a rough size, and rounding it away to null loses the request entirely.
|
|
1402
|
+
"a couple of": 2,
|
|
1403
|
+
"a few": 3
|
|
1404
|
+
};
|
|
1405
|
+
var CLOCK = /\b(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(a\.?m\.?|p\.?m\.?|o'?clock)?\b/i;
|
|
1406
|
+
var SPOKEN_HOUR = {
|
|
1407
|
+
one: 1,
|
|
1408
|
+
two: 2,
|
|
1409
|
+
three: 3,
|
|
1410
|
+
four: 4,
|
|
1411
|
+
five: 5,
|
|
1412
|
+
six: 6,
|
|
1413
|
+
seven: 7,
|
|
1414
|
+
eight: 8,
|
|
1415
|
+
nine: 9,
|
|
1416
|
+
ten: 10,
|
|
1417
|
+
eleven: 11,
|
|
1418
|
+
twelve: 12
|
|
1419
|
+
};
|
|
1420
|
+
function parseWhen(said2, tz, nowMs, read = "earliest") {
|
|
1421
|
+
const text = said2.trim();
|
|
1422
|
+
if (!text) return null;
|
|
1423
|
+
const rel = RELATIVE.exec(text);
|
|
1424
|
+
if (rel) {
|
|
1425
|
+
const rawN = rel[1].toLowerCase();
|
|
1426
|
+
const half = /half/.test(rawN);
|
|
1427
|
+
const n = half ? 0.5 : WORD_NUM[rawN] ?? Number(rawN);
|
|
1428
|
+
if (Number.isFinite(n) && n > 0) {
|
|
1429
|
+
const unit = rel[2].toLowerCase();
|
|
1430
|
+
const ms = unit.startsWith("min") ? n * 6e4 : unit.startsWith("h") ? n * 36e5 : unit.startsWith("d") ? n * 864e5 : n * 7 * 864e5;
|
|
1431
|
+
return new Date(nowMs + ms).toISOString();
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
const today = localDate(tz, nowMs);
|
|
1435
|
+
if (!today) return null;
|
|
1436
|
+
let dayShift = /\btomorrow\b/i.test(text) ? 1 : 0;
|
|
1437
|
+
if (/\bnext week\b/i.test(text)) dayShift = 7;
|
|
1438
|
+
let hour = null;
|
|
1439
|
+
let minute = 0;
|
|
1440
|
+
const spoken = new RegExp(`\\bat\\s+(${Object.keys(SPOKEN_HOUR).join("|")})\\b`, "i").exec(text);
|
|
1441
|
+
const clock2 = CLOCK.exec(text);
|
|
1442
|
+
if (spoken) {
|
|
1443
|
+
hour = SPOKEN_HOUR[spoken[1].toLowerCase()];
|
|
1444
|
+
} else if (clock2 && (clock2[3] || /\bat\s/i.test(clock2[0]))) {
|
|
1445
|
+
hour = Number(clock2[1]);
|
|
1446
|
+
minute = clock2[2] ? Number(clock2[2]) : 0;
|
|
1447
|
+
if (hour > 23 || minute > 59) return null;
|
|
1448
|
+
}
|
|
1449
|
+
if (hour !== null) {
|
|
1450
|
+
const mer = clock2?.[3]?.toLowerCase().replace(/\./g, "") ?? "";
|
|
1451
|
+
const pmWord = /\b(p\.?m\.?|afternoon|evening|tonight|at night)\b/i.test(text);
|
|
1452
|
+
const amWord = /\b(a\.?m\.?|morning)\b/i.test(text);
|
|
1453
|
+
if (mer.startsWith("p") || pmWord && !amWord) {
|
|
1454
|
+
if (hour < 12) hour += 12;
|
|
1455
|
+
} else if (mer.startsWith("a") || amWord) {
|
|
1456
|
+
if (hour === 12) hour = 0;
|
|
1457
|
+
} else if (hour < 12 && dayShift === 0 && hour <= today.h) hour += 12;
|
|
1458
|
+
} else {
|
|
1459
|
+
const parts = PARTS[read];
|
|
1460
|
+
const part = Object.keys(parts).find((k) => new RegExp(`\\b${k}\\b`, "i").test(text));
|
|
1461
|
+
if (/\btonight\b/i.test(text)) hour = parts["night"];
|
|
1462
|
+
else if (part) hour = parts[part];
|
|
1463
|
+
else if (dayShift > 0) hour = 9;
|
|
1464
|
+
else if (/\bend of (the )?day\b|\beod\b/i.test(text)) hour = END_OF_DAY;
|
|
1465
|
+
else if (read === "ring" && VAGUE.test(text)) return new Date(nowMs + 30 * 6e4).toISOString();
|
|
1466
|
+
else return null;
|
|
1467
|
+
}
|
|
1468
|
+
const at2 = instantAt(tz, {
|
|
1469
|
+
y: today.y,
|
|
1470
|
+
mo: today.mo,
|
|
1471
|
+
d: today.d + dayShift,
|
|
1472
|
+
h: hour,
|
|
1473
|
+
mi: minute
|
|
1474
|
+
});
|
|
1475
|
+
if (at2 === null) return null;
|
|
1476
|
+
if (at2 <= nowMs && dayShift === 0 && TODAY_ONLY.test(text)) {
|
|
1477
|
+
return new Date(nowMs + 36e5).toISOString();
|
|
1478
|
+
}
|
|
1479
|
+
const rolled = at2 <= nowMs && dayShift === 0 ? instantAt(tz, {
|
|
1480
|
+
y: today.y,
|
|
1481
|
+
mo: today.mo,
|
|
1482
|
+
d: today.d + 1,
|
|
1483
|
+
h: hour,
|
|
1484
|
+
mi: minute
|
|
1485
|
+
}) : at2;
|
|
1486
|
+
return new Date(rolled ?? at2).toISOString();
|
|
1487
|
+
}
|
|
1488
|
+
function parseDelay(said2, tz, nowMs, read = "earliest") {
|
|
1489
|
+
const at2 = parseWhen(said2, tz, nowMs, read);
|
|
1490
|
+
if (!at2) return null;
|
|
1491
|
+
return Math.max(0, Math.round((Date.parse(at2) - nowMs) / 1e3));
|
|
1492
|
+
}
|
|
1304
1493
|
var NIGHT = { from: 23, to: 7 };
|
|
1305
1494
|
function draft2020(node) {
|
|
1306
1495
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
@@ -1331,39 +1520,30 @@ function mcpInputSchema(s) {
|
|
|
1331
1520
|
delete schema.$schema;
|
|
1332
1521
|
return draft2020(schema);
|
|
1333
1522
|
}
|
|
1334
|
-
var AskInputSchema =
|
|
1335
|
-
id:
|
|
1336
|
-
parentId:
|
|
1337
|
-
repo:
|
|
1338
|
-
ask:
|
|
1339
|
-
"
|
|
1523
|
+
var AskInputSchema = z2.object({
|
|
1524
|
+
id: z2.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
|
|
1525
|
+
parentId: z2.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
|
|
1526
|
+
repo: z2.string().optional().describe("Optional repository context."),
|
|
1527
|
+
ask: z2.string().trim().min(1).max(1e4).describe(
|
|
1528
|
+
"ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 parts of one piece of work under one Goal, separate things as separate Goals \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; a call contact JOINS a call already happening, so several arrive as one call."
|
|
1340
1529
|
),
|
|
1341
|
-
options:
|
|
1342
|
-
label: z.string().trim().min(1).max(1e3),
|
|
1343
|
-
hint: z.string().trim().max(500).describe("Optional short projection of consequence or action if this option is chosen.").optional(),
|
|
1344
|
-
image: z.string().url().optional(),
|
|
1345
|
-
html: z.string().max(16384).describe("Optional sandboxed HTML/CSS preview. No JavaScript or network; inline CSS and data: URIs only. It renders edge-to-edge in a responsive card 200pt tall (about 320pt wide on a phone, with the next option peeking beside it), so fit the HTML to that viewport.").optional()
|
|
1346
|
-
}).strict()).min(2).max(6).optional()
|
|
1530
|
+
options: z2.array(OptionInputSchema).min(2).max(6).optional()
|
|
1347
1531
|
}).strict();
|
|
1348
|
-
var StartContactSchema =
|
|
1349
|
-
asks:
|
|
1350
|
-
waiting:
|
|
1351
|
-
channel:
|
|
1532
|
+
var StartContactSchema = z2.object({
|
|
1533
|
+
asks: z2.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; only an ask naming no Goal is placed in the tree by Paigy."),
|
|
1534
|
+
waiting: z2.enum(["none", "hard"]).default("none"),
|
|
1535
|
+
channel: z2.enum(["notification", "call"]).default("notification")
|
|
1352
1536
|
}).strict();
|
|
1353
|
-
var ContactSchema =
|
|
1537
|
+
var ContactSchema = z2.union([StartContactSchema, z2.object({ deliveryId: z2.string().uuid() }).strict()]);
|
|
1354
1538
|
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
1355
|
-
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}.
|
|
1356
|
-
var CreateGoalSchema =
|
|
1357
|
-
outcome:
|
|
1539
|
+
var CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals.";
|
|
1540
|
+
var CreateGoalSchema = z3.object({
|
|
1541
|
+
outcome: z3.string().trim().min(1).max(1e4),
|
|
1358
1542
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
1359
1543
|
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
1360
|
-
title:
|
|
1361
|
-
ownerParticipant:
|
|
1362
|
-
idempotencyKey:
|
|
1363
|
-
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
1364
|
-
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
1365
|
-
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
1366
|
-
* the writer refuses a thread belonging to another account. */
|
|
1544
|
+
title: z3.string().trim().min(1).max(80).optional(),
|
|
1545
|
+
ownerParticipant: z3.string().trim().min(1).optional(),
|
|
1546
|
+
idempotencyKey: z3.string().trim().min(1).max(200),
|
|
1367
1547
|
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
1368
1548
|
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
1369
1549
|
* worked"). It could not have been: this door took no parent, so the only route was
|
|
@@ -1371,55 +1551,45 @@ var CreateGoalSchema = z2.object({
|
|
|
1371
1551
|
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
1372
1552
|
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
1373
1553
|
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
1374
|
-
parentGoalId:
|
|
1554
|
+
parentGoalId: z3.string().uuid().optional(),
|
|
1375
1555
|
/** The repository or project identifier this Goal belongs to (#2280) — e.g. "owner/repo" or
|
|
1376
1556
|
* repo name. Delegated work inherits this from its parent Goal when omitted. */
|
|
1377
|
-
repo:
|
|
1557
|
+
repo: z3.string().trim().min(1).max(200).optional()
|
|
1378
1558
|
});
|
|
1379
1559
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
1380
1560
|
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
1381
1561
|
}).strict();
|
|
1382
1562
|
var CREATE_GOAL_DESCRIPTION = 'Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. Pass repo to anchor the work to a specific repository ("owner/repo" or repo name); delegated children inherit it. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.';
|
|
1383
|
-
var UpdateGoalSchema =
|
|
1384
|
-
revision:
|
|
1385
|
-
changes:
|
|
1386
|
-
outcome:
|
|
1563
|
+
var UpdateGoalSchema = z3.object({
|
|
1564
|
+
revision: z3.number().int().positive(),
|
|
1565
|
+
changes: z3.object({
|
|
1566
|
+
outcome: z3.string().trim().min(1).max(1e4).optional(),
|
|
1387
1567
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
1388
1568
|
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
1389
|
-
title:
|
|
1390
|
-
ownerParticipant:
|
|
1391
|
-
parentGoalId:
|
|
1392
|
-
dependencies:
|
|
1393
|
-
children:
|
|
1394
|
-
state:
|
|
1395
|
-
progress:
|
|
1396
|
-
reviewed:
|
|
1397
|
-
dueAt:
|
|
1569
|
+
title: z3.string().trim().min(1).max(80).nullable().optional(),
|
|
1570
|
+
ownerParticipant: z3.string().trim().min(1).optional(),
|
|
1571
|
+
parentGoalId: z3.string().uuid().nullable().optional(),
|
|
1572
|
+
dependencies: z3.array(z3.object({ goalId: z3.string().uuid(), gate: z3.enum(["start", "finish"]) }).strict()).optional(),
|
|
1573
|
+
children: z3.array(z3.object({ outcome: z3.string().trim().min(1).max(1e4), ownerParticipant: z3.string().trim().min(1), gate: z3.enum(["start", "finish"]).optional() }).strict()).optional(),
|
|
1574
|
+
state: z3.enum(["active", "done", "cancelled"]).optional(),
|
|
1575
|
+
progress: z3.string().trim().min(1).max(1e4).optional(),
|
|
1576
|
+
reviewed: z3.literal(true).optional(),
|
|
1577
|
+
dueAt: z3.string().datetime({ offset: true }).nullable().optional()
|
|
1398
1578
|
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
1399
|
-
reason:
|
|
1400
|
-
operationId:
|
|
1579
|
+
reason: z3.string().trim().min(1).max(2e3),
|
|
1580
|
+
operationId: z3.string().uuid().optional()
|
|
1401
1581
|
}).strict();
|
|
1402
|
-
var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId:
|
|
1403
|
-
var ClaimGoalSchema =
|
|
1404
|
-
var GetGoalSchema =
|
|
1582
|
+
var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z3.string().uuid() }).strict();
|
|
1583
|
+
var ClaimGoalSchema = z3.object({ goalId: z3.string().uuid().optional() }).strict();
|
|
1584
|
+
var GetGoalSchema = z3.object({ goalId: z3.string().uuid() }).strict();
|
|
1405
1585
|
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
|
|
1406
1586
|
var UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the Goal as get_goal reads it, at its new revision.";
|
|
1407
|
-
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
1587
|
+
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
1408
1588
|
var CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 one row each: its Goals, how many decisions are still open, and the newest words in brief. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
|
|
1409
|
-
var CheckRepliesSchema =
|
|
1410
|
-
var GetThreadSchema = z2.object({
|
|
1411
|
-
parentId: z2.string().describe("The Thread to read \u2014 the parentId of a search_threads hit.")
|
|
1412
|
-
}).strict();
|
|
1413
|
-
var GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume and get_goal for the conversation on a Goal; use this only to rehydrate a Thread that a search hit named.";
|
|
1414
|
-
var SearchThreadsSchema = z2.object({
|
|
1415
|
-
q: z2.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
|
|
1416
|
-
}).strict();
|
|
1417
|
-
var SEARCH_THREADS_DESCRIPTION = `Search your PAST conversations before asking \u2014 "have we discussed this before?". Full-text over your own threads (the asks you sent + the user's answers); returns ranked threads with highlighted snippets, NOT rows: { hits: [{ parentId, at, agentLabel, matches: [{ notificationId, role, snippet }] }] }. The loop this exists for: search first \u2192 get_thread the best hit to rehydrate it \u2192 THEN continue or contact, so you answer with receipts ("last week you said ship it") instead of re-asking. Read-only, safe to call anytime; scoped to your own account's threads.`;
|
|
1589
|
+
var CheckRepliesSchema = z3.object({}).strict();
|
|
1418
1590
|
var AGENT_TOOLS = [
|
|
1419
1591
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
1420
1592
|
{ name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
|
|
1421
|
-
{ name: "get_thread", description: GET_THREAD_DESCRIPTION, inputSchema: mcpInputSchema(GetThreadSchema) },
|
|
1422
|
-
{ name: "search_threads", description: SEARCH_THREADS_DESCRIPTION, inputSchema: mcpInputSchema(SearchThreadsSchema) },
|
|
1423
1593
|
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
1424
1594
|
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
1425
1595
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
@@ -1442,17 +1612,17 @@ function entryWords(entry) {
|
|
|
1442
1612
|
return entry.sources.map((source) => source.text).join("\n");
|
|
1443
1613
|
}
|
|
1444
1614
|
var LIVE_MS = 3 * 6e4;
|
|
1445
|
-
var ContextSchema =
|
|
1446
|
-
title:
|
|
1447
|
-
description:
|
|
1615
|
+
var ContextSchema = z4.object({
|
|
1616
|
+
title: z4.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
1617
|
+
description: z4.array(z4.string().min(1)).describe(
|
|
1448
1618
|
"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."
|
|
1449
1619
|
)
|
|
1450
1620
|
});
|
|
1451
|
-
var ParticipantSchema =
|
|
1452
|
-
kind:
|
|
1453
|
-
id:
|
|
1621
|
+
var ParticipantSchema = z4.object({
|
|
1622
|
+
kind: z4.enum(["human", "agent"]),
|
|
1623
|
+
id: z4.string()
|
|
1454
1624
|
});
|
|
1455
|
-
var TransformSchema =
|
|
1625
|
+
var TransformSchema = z4.enum([
|
|
1456
1626
|
"structure",
|
|
1457
1627
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
1458
1628
|
"request_more",
|
|
@@ -1468,26 +1638,13 @@ var TransformSchema = z3.enum([
|
|
|
1468
1638
|
"summarize"
|
|
1469
1639
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
1470
1640
|
]);
|
|
1471
|
-
var
|
|
1472
|
-
|
|
1473
|
-
label:
|
|
1474
|
-
hint: z3.string().max(500).describe("Optional short projection of consequence or action if this option is chosen (e.g. 'Reruns test suite', 'Merges to main').").optional(),
|
|
1475
|
-
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
1476
|
-
// the constraints below are what an agent reads when deciding to use these.
|
|
1477
|
-
html: z3.string().max(16384).describe(
|
|
1478
|
-
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
1479
|
-
).optional(),
|
|
1480
|
-
image: z3.string().url().describe(
|
|
1481
|
-
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
1482
|
-
).optional()
|
|
1641
|
+
var VisualSchema = z4.object({
|
|
1642
|
+
url: z4.string().url(),
|
|
1643
|
+
label: z4.string().optional()
|
|
1483
1644
|
});
|
|
1484
|
-
var
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
});
|
|
1488
|
-
var NotifyLevelSchema = z3.enum(["inbox", "push", "banner", "call"]);
|
|
1489
|
-
var SelectShapeSchema = z3.enum(["one", "many", "rank", "confirm", "text"]);
|
|
1490
|
-
var ReceiptEventSchema = z3.enum([
|
|
1645
|
+
var NotifyLevelSchema = z4.enum(["inbox", "push", "banner", "call"]);
|
|
1646
|
+
var SelectShapeSchema = z4.enum(["one", "many", "rank", "confirm", "text"]);
|
|
1647
|
+
var ReceiptEventSchema = z4.enum([
|
|
1491
1648
|
"delivered",
|
|
1492
1649
|
// the bundle reached the recipient at some level
|
|
1493
1650
|
"seen",
|
|
@@ -1517,47 +1674,47 @@ var ReceiptEventSchema = z3.enum([
|
|
|
1517
1674
|
// be rewound by a writer that forgot to advance it.
|
|
1518
1675
|
"restarted"
|
|
1519
1676
|
]);
|
|
1520
|
-
var AttentionSchema =
|
|
1677
|
+
var AttentionSchema = z4.object({
|
|
1521
1678
|
urgency: NotifyLevelSchema,
|
|
1522
1679
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
1523
1680
|
select: SelectShapeSchema.nullable(),
|
|
1524
1681
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
1525
|
-
points:
|
|
1682
|
+
points: z4.array(z4.string()).nullable(),
|
|
1526
1683
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
1527
|
-
blocking:
|
|
1684
|
+
blocking: z4.boolean(),
|
|
1528
1685
|
/** Reserved (MODEL.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
1529
1686
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
1530
|
-
deadline:
|
|
1687
|
+
deadline: z4.string().datetime().nullable().optional()
|
|
1531
1688
|
});
|
|
1532
|
-
var NotifyRequestFields =
|
|
1689
|
+
var NotifyRequestFields = z4.object({
|
|
1533
1690
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
1534
1691
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
1535
1692
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
1536
1693
|
context: ContextSchema.optional(),
|
|
1537
|
-
options:
|
|
1694
|
+
options: z4.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
1538
1695
|
"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)."
|
|
1539
1696
|
),
|
|
1540
|
-
points:
|
|
1697
|
+
points: z4.array(z4.string().min(1)).optional().describe(
|
|
1541
1698
|
"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."
|
|
1542
1699
|
),
|
|
1543
|
-
visuals:
|
|
1700
|
+
visuals: z4.array(VisualSchema).optional().describe(
|
|
1544
1701
|
"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."
|
|
1545
1702
|
),
|
|
1546
1703
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
1547
|
-
repo:
|
|
1704
|
+
repo: z4.string().optional(),
|
|
1548
1705
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
1549
|
-
branch:
|
|
1706
|
+
branch: z4.string().optional(),
|
|
1550
1707
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
1551
1708
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
1552
1709
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
1553
1710
|
* still accepts the old name from older clients. */
|
|
1554
|
-
parentId:
|
|
1711
|
+
parentId: z4.string().uuid().optional(),
|
|
1555
1712
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
1556
1713
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
1557
|
-
workId:
|
|
1714
|
+
workId: z4.string().uuid().optional(),
|
|
1558
1715
|
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
1559
1716
|
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
1560
|
-
goalId:
|
|
1717
|
+
goalId: z4.string().uuid().optional(),
|
|
1561
1718
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
1562
1719
|
"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."
|
|
1563
1720
|
),
|
|
@@ -1565,7 +1722,7 @@ var NotifyRequestFields = z3.object({
|
|
|
1565
1722
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
1566
1723
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
1567
1724
|
* says HOW. */
|
|
1568
|
-
clarifies:
|
|
1725
|
+
clarifies: z4.string().optional(),
|
|
1569
1726
|
select: SelectShapeSchema.optional().describe(
|
|
1570
1727
|
"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."
|
|
1571
1728
|
),
|
|
@@ -1579,20 +1736,20 @@ var NotifyRequestFields = z3.object({
|
|
|
1579
1736
|
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
1580
1737
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
1581
1738
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
1582
|
-
ask:
|
|
1739
|
+
ask: z4.string().min(1).max(1e4).optional().describe(
|
|
1583
1740
|
'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.'
|
|
1584
1741
|
),
|
|
1585
|
-
needs:
|
|
1742
|
+
needs: z4.array(z4.string().min(1)).optional().describe(
|
|
1586
1743
|
"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."
|
|
1587
1744
|
),
|
|
1588
|
-
urgencyHint:
|
|
1745
|
+
urgencyHint: z4.enum(["whenever", "soon", "now"]).optional().describe(
|
|
1589
1746
|
"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."
|
|
1590
1747
|
),
|
|
1591
1748
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
1592
1749
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
1593
1750
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
1594
1751
|
* urgencyHint/blocking win when both are sent. */
|
|
1595
|
-
waiting:
|
|
1752
|
+
waiting: z4.enum(["none", "soft", "hard"]).optional().describe(
|
|
1596
1753
|
"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."
|
|
1597
1754
|
),
|
|
1598
1755
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -1600,98 +1757,98 @@ var NotifyRequestFields = z3.object({
|
|
|
1600
1757
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
1601
1758
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
1602
1759
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
1603
|
-
confirm:
|
|
1760
|
+
confirm: z4.boolean().optional().describe(
|
|
1604
1761
|
"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'."
|
|
1605
1762
|
),
|
|
1606
1763
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
1607
1764
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
1608
1765
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
1609
1766
|
* waiting:'hard'. */
|
|
1610
|
-
channel:
|
|
1767
|
+
channel: z4.enum(["call", "message"]).optional().describe(
|
|
1611
1768
|
"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."
|
|
1612
1769
|
),
|
|
1613
|
-
confirmStyle:
|
|
1770
|
+
confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
|
|
1614
1771
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
1615
1772
|
),
|
|
1616
|
-
blocking:
|
|
1773
|
+
blocking: z4.boolean().default(false).describe(
|
|
1617
1774
|
"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."
|
|
1618
1775
|
)
|
|
1619
1776
|
});
|
|
1620
1777
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
1621
|
-
if (r.workId && r.goalId) ctx.addIssue({ code:
|
|
1778
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
1622
1779
|
if (r.ask !== void 0) {
|
|
1623
1780
|
for (const f of ["context", "select", "points"]) {
|
|
1624
1781
|
if (r[f] !== void 0)
|
|
1625
|
-
ctx.addIssue({ code:
|
|
1782
|
+
ctx.addIssue({ code: z4.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.` });
|
|
1626
1783
|
}
|
|
1627
1784
|
return;
|
|
1628
1785
|
}
|
|
1629
1786
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
1630
|
-
ctx.addIssue({ code:
|
|
1787
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
1631
1788
|
if (!r.context)
|
|
1632
|
-
ctx.addIssue({ code:
|
|
1789
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
1633
1790
|
if (!r.select)
|
|
1634
|
-
ctx.addIssue({ code:
|
|
1791
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
1635
1792
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
1636
1793
|
if (needsOptions && !r.options?.length)
|
|
1637
|
-
ctx.addIssue({ code:
|
|
1794
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
1638
1795
|
if (!needsOptions && r.options?.length)
|
|
1639
|
-
ctx.addIssue({ code:
|
|
1796
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
1640
1797
|
});
|
|
1641
|
-
var NotifyStatusSchema =
|
|
1642
|
-
var AgentStateSchema =
|
|
1643
|
-
var TurnSchema =
|
|
1644
|
-
prompt:
|
|
1645
|
-
reply:
|
|
1798
|
+
var NotifyStatusSchema = z4.enum(["pending", "answered", "ignored"]);
|
|
1799
|
+
var AgentStateSchema = z4.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
1800
|
+
var TurnSchema = z4.object({
|
|
1801
|
+
prompt: z4.string(),
|
|
1802
|
+
reply: z4.string()
|
|
1646
1803
|
});
|
|
1647
|
-
var UserAnswerSchema =
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1804
|
+
var UserAnswerSchema = z4.discriminatedUnion("kind", [
|
|
1805
|
+
z4.object({ kind: z4.literal("option"), optionId: z4.string(), label: z4.string().optional() }),
|
|
1806
|
+
z4.object({ kind: z4.literal("text"), text: z4.string() }),
|
|
1807
|
+
z4.object({ kind: z4.literal("ignored") }),
|
|
1808
|
+
z4.object({ kind: z4.literal("multi"), optionIds: z4.array(z4.string()), labels: z4.array(z4.string()).optional() }),
|
|
1809
|
+
z4.object({ kind: z4.literal("ranked"), optionIds: z4.array(z4.string()), labels: z4.array(z4.string()).optional() }),
|
|
1810
|
+
z4.object({ kind: z4.literal("clarify"), chunks: z4.array(z4.string()).min(1) }),
|
|
1811
|
+
z4.object({ kind: z4.literal("confirm"), approved: z4.boolean() }),
|
|
1812
|
+
z4.object({ kind: z4.literal("turns"), turns: z4.array(TurnSchema).min(1) }),
|
|
1656
1813
|
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
1657
1814
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
1658
1815
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
1659
1816
|
* card the user can reply to — the broker never overrides the user. */
|
|
1660
|
-
|
|
1817
|
+
z4.object({ kind: z4.literal("precedent"), answer: z4.string(), derivation: z4.string(), sources: z4.array(z4.string()).min(1) })
|
|
1661
1818
|
]);
|
|
1662
|
-
var IntentSchema =
|
|
1819
|
+
var IntentSchema = z4.object({
|
|
1663
1820
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
1664
1821
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
1665
1822
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
1666
1823
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
1667
|
-
kind:
|
|
1668
|
-
detail:
|
|
1824
|
+
kind: z4.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
1825
|
+
detail: z4.string(),
|
|
1669
1826
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
1670
1827
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
1671
1828
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
1672
1829
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
1673
|
-
dueInSeconds:
|
|
1830
|
+
dueInSeconds: z4.number().int().positive().optional(),
|
|
1674
1831
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
1675
1832
|
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
1676
1833
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
1677
1834
|
* about what cannot answer "is the bot looping less this week?". */
|
|
1678
|
-
fault:
|
|
1835
|
+
fault: z4.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
1679
1836
|
});
|
|
1680
|
-
var RideAlongSchema =
|
|
1837
|
+
var RideAlongSchema = z4.object({
|
|
1681
1838
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
1682
|
-
noteId:
|
|
1839
|
+
noteId: z4.string(),
|
|
1683
1840
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
1684
|
-
text:
|
|
1841
|
+
text: z4.string(),
|
|
1685
1842
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
1686
|
-
parentId:
|
|
1843
|
+
parentId: z4.string().nullable()
|
|
1687
1844
|
});
|
|
1688
|
-
var AwaitItemSchema =
|
|
1689
|
-
|
|
1690
|
-
type:
|
|
1691
|
-
parentId:
|
|
1692
|
-
notificationId:
|
|
1693
|
-
workId:
|
|
1694
|
-
decisionId:
|
|
1845
|
+
var AwaitItemSchema = z4.discriminatedUnion("type", [
|
|
1846
|
+
z4.object({
|
|
1847
|
+
type: z4.literal("reply"),
|
|
1848
|
+
parentId: z4.string(),
|
|
1849
|
+
notificationId: z4.string(),
|
|
1850
|
+
workId: z4.string().uuid().optional(),
|
|
1851
|
+
decisionId: z4.string().uuid().optional(),
|
|
1695
1852
|
answer: UserAnswerSchema,
|
|
1696
1853
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
1697
1854
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
@@ -1701,161 +1858,131 @@ var AwaitItemSchema = z3.discriminatedUnion("type", [
|
|
|
1701
1858
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
1702
1859
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
1703
1860
|
* a sentence with a hole in it is worse than no sentence. */
|
|
1704
|
-
note:
|
|
1861
|
+
note: z4.string().optional(),
|
|
1705
1862
|
/** The call record rendered for THIS agent (`voice/record-design.md`): the words the
|
|
1706
1863
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
1707
1864
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
1708
1865
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
1709
|
-
transcript:
|
|
1866
|
+
transcript: z4.string().optional(),
|
|
1710
1867
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
1711
1868
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
1712
|
-
covered:
|
|
1869
|
+
covered: z4.array(z4.string()).optional(),
|
|
1713
1870
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
1714
1871
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
1715
1872
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
1716
1873
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
1717
1874
|
* dropped. Absent/empty = nothing owed. */
|
|
1718
|
-
also:
|
|
1875
|
+
also: z4.array(RideAlongSchema).optional()
|
|
1719
1876
|
}),
|
|
1720
|
-
|
|
1721
|
-
type:
|
|
1722
|
-
parentId:
|
|
1723
|
-
notificationId:
|
|
1724
|
-
remindAt:
|
|
1877
|
+
z4.object({
|
|
1878
|
+
type: z4.literal("remind"),
|
|
1879
|
+
parentId: z4.string(),
|
|
1880
|
+
notificationId: z4.string(),
|
|
1881
|
+
remindAt: z4.string().datetime({ offset: true }),
|
|
1725
1882
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
1726
|
-
remindInSeconds:
|
|
1883
|
+
remindInSeconds: z4.number()
|
|
1727
1884
|
}),
|
|
1728
1885
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
1729
1886
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
1730
1887
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
1731
|
-
* re-orient via
|
|
1732
|
-
|
|
1733
|
-
type:
|
|
1734
|
-
parentId:
|
|
1735
|
-
notificationId:
|
|
1888
|
+
* re-orient via check_replies). */
|
|
1889
|
+
z4.object({
|
|
1890
|
+
type: z4.literal("superseded"),
|
|
1891
|
+
parentId: z4.string(),
|
|
1892
|
+
notificationId: z4.string()
|
|
1736
1893
|
}),
|
|
1737
1894
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
1738
1895
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
1739
1896
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
1740
1897
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
1741
1898
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
1742
|
-
|
|
1743
|
-
type:
|
|
1744
|
-
notificationId:
|
|
1745
|
-
inFlight:
|
|
1746
|
-
turn:
|
|
1747
|
-
idx:
|
|
1748
|
-
prompt:
|
|
1749
|
-
reply:
|
|
1750
|
-
acts:
|
|
1899
|
+
z4.object({
|
|
1900
|
+
type: z4.literal("partial"),
|
|
1901
|
+
notificationId: z4.string(),
|
|
1902
|
+
inFlight: z4.literal(true),
|
|
1903
|
+
turn: z4.object({
|
|
1904
|
+
idx: z4.number(),
|
|
1905
|
+
prompt: z4.string(),
|
|
1906
|
+
reply: z4.string(),
|
|
1907
|
+
acts: z4.array(IntentSchema).nullable().optional()
|
|
1751
1908
|
})
|
|
1752
1909
|
}),
|
|
1753
|
-
|
|
1754
|
-
type:
|
|
1755
|
-
also:
|
|
1910
|
+
z4.object({
|
|
1911
|
+
type: z4.literal("idle"),
|
|
1912
|
+
also: z4.array(RideAlongSchema).optional(),
|
|
1756
1913
|
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
1757
1914
|
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
1758
1915
|
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
1759
1916
|
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
1760
1917
|
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
1761
|
-
inFlight:
|
|
1918
|
+
inFlight: z4.boolean().optional()
|
|
1762
1919
|
})
|
|
1763
1920
|
]);
|
|
1764
|
-
var VoiceKeySchema =
|
|
1765
|
-
var AgendaTurnSchema =
|
|
1921
|
+
var VoiceKeySchema = z4.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
1922
|
+
var AgendaTurnSchema = z4.object({
|
|
1766
1923
|
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
1767
1924
|
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
1768
1925
|
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
1769
1926
|
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
1770
1927
|
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
1771
1928
|
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
1772
|
-
id:
|
|
1929
|
+
id: z4.string().optional(),
|
|
1773
1930
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
1774
1931
|
* the planner declares duplicates instead of asking them twice. */
|
|
1775
|
-
coveredIds:
|
|
1932
|
+
coveredIds: z4.array(z4.string()).optional(),
|
|
1776
1933
|
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
1777
1934
|
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
1778
|
-
info:
|
|
1779
|
-
question:
|
|
1935
|
+
info: z4.array(z4.string().min(1)).max(3).default([]),
|
|
1936
|
+
question: z4.string().min(1).nullable(),
|
|
1780
1937
|
/** True on the one turn carrying the agent's own declared question. */
|
|
1781
|
-
asks:
|
|
1938
|
+
asks: z4.boolean().optional(),
|
|
1782
1939
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
1783
1940
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
1784
1941
|
* which route nothing. */
|
|
1785
|
-
claimId:
|
|
1942
|
+
claimId: z4.string().optional(),
|
|
1786
1943
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
1787
|
-
voice:
|
|
1944
|
+
voice: z4.string().optional(),
|
|
1788
1945
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
1789
1946
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
1790
1947
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
1791
1948
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
1792
|
-
agent:
|
|
1949
|
+
agent: z4.string().optional(),
|
|
1793
1950
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
1794
1951
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
1795
1952
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
1796
1953
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
1797
1954
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
1798
|
-
agentId:
|
|
1955
|
+
agentId: z4.string().optional(),
|
|
1799
1956
|
select: SelectShapeSchema.optional(),
|
|
1800
|
-
options:
|
|
1957
|
+
options: z4.array(OptionSchema.omit({ id: true })).optional(),
|
|
1801
1958
|
/** Pacing (#826, owner 2026-08-03: "how fast we move through them ... are parameters"):
|
|
1802
1959
|
* seconds the floor stays open after this turn speaks. Absent = the bot's defaults
|
|
1803
1960
|
* (the beat for context, the answer window for asks). Clamped bot-side. */
|
|
1804
|
-
pace:
|
|
1961
|
+
pace: z4.number().positive().optional(),
|
|
1805
1962
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
1806
1963
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
1807
1964
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
1808
|
-
blocking:
|
|
1965
|
+
blocking: z4.boolean().optional()
|
|
1809
1966
|
});
|
|
1810
1967
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
1811
|
-
var InboxItemSchema =
|
|
1812
|
-
id:
|
|
1813
|
-
|
|
1814
|
-
* detail — they power History's "Continue" / "New session from this" (#57/#251). */
|
|
1815
|
-
parentId: z3.string().optional(),
|
|
1816
|
-
/** THE ARRIVAL this row is one unit of (`notifications.ask_id` → `asks`). A claim is one
|
|
1817
|
-
* arrival and its units are N rows of it, so this — not `parentId` — is what makes a
|
|
1818
|
-
* multi-part notification one thing on screen. The thread is the whole CONVERSATION: it
|
|
1819
|
-
* accumulates every message an agent ever sent, so grouping by it renders a day of
|
|
1820
|
-
* unrelated updates as a single "12-part request". Absent on rows written before the
|
|
1821
|
-
* `asks` table, and on anything that never went through `notify` — both fall back to the
|
|
1822
|
-
* thread, which is what the client did for all rows until now. */
|
|
1823
|
-
askId: z3.string().optional(),
|
|
1824
|
-
/** WHERE this unit sat in the message it was cut from (`notifications.seq`). The batch
|
|
1825
|
-
* shares one `created_at` to the microsecond, so without it the author's order is
|
|
1826
|
-
* unrecoverable client-side — a four-paragraph briefing rendered opening-paragraph-last
|
|
1827
|
-
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
1828
|
-
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
1829
|
-
seq: z3.number().int().optional(),
|
|
1830
|
-
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
1831
|
-
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
1832
|
-
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
1833
|
-
* screen read "1 of 3", then "1 of 2", then no chip at all, each answer having removed the
|
|
1834
|
-
* only evidence of itself. How big an arrival is, is a fact about the arrival, so the
|
|
1835
|
-
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
1836
|
-
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
1837
|
-
* the one part of its own arrival a single row cannot answer. */
|
|
1838
|
-
units: z3.number().int().positive().optional(),
|
|
1839
|
-
tokenId: z3.string().optional(),
|
|
1968
|
+
var InboxItemSchema = z4.object({
|
|
1969
|
+
id: z4.string(),
|
|
1970
|
+
tokenId: z4.string().optional(),
|
|
1840
1971
|
status: NotifyStatusSchema,
|
|
1841
1972
|
context: ContextSchema,
|
|
1842
|
-
options:
|
|
1973
|
+
options: z4.array(OptionSchema).optional(),
|
|
1843
1974
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
1844
|
-
points:
|
|
1845
|
-
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
1846
|
-
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
1847
|
-
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
1848
|
-
* a status update stays a statement instead of being shaped into a yes/no. */
|
|
1975
|
+
points: z4.array(z4.string()).optional(),
|
|
1849
1976
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
1850
1977
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
1851
1978
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
1852
1979
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
1853
|
-
asks:
|
|
1980
|
+
asks: z4.boolean().optional(),
|
|
1854
1981
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
1855
1982
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
1856
1983
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
1857
1984
|
* which must never CLAIM stalled. */
|
|
1858
|
-
lastSeenAt:
|
|
1985
|
+
lastSeenAt: z4.string().optional(),
|
|
1859
1986
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
1860
1987
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
1861
1988
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -1865,53 +1992,91 @@ var InboxItemSchema = z3.object({
|
|
|
1865
1992
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
1866
1993
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
1867
1994
|
* `createdAt`. */
|
|
1868
|
-
agentStateAt:
|
|
1869
|
-
agenda
|
|
1870
|
-
|
|
1995
|
+
agentStateAt: z4.string().datetime().optional(),
|
|
1996
|
+
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (walk/design.md §11, owner
|
|
1997
|
+
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
1998
|
+
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
1999
|
+
* Call's cards, and every card of that Call carries the same list: the call screen reads it
|
|
2000
|
+
* once, off the one read it already makes (`GET /api/inbox/:callId`).
|
|
2001
|
+
*
|
|
2002
|
+
* It counts DECISIONS, not agenda turns: turns include context-only lines and are re-planned
|
|
2003
|
+
* every cycle, so a spine drawn from them would change length under the caller mid-call.
|
|
2004
|
+
*
|
|
2005
|
+
* `entryId` is the request Entry — the id the bot calls a CLAIM, and the one it names on the
|
|
2006
|
+
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
2007
|
+
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
2008
|
+
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
2009
|
+
questions: z4.array(z4.object({
|
|
2010
|
+
id: z4.string(),
|
|
2011
|
+
entryId: z4.string(),
|
|
2012
|
+
title: z4.string(),
|
|
2013
|
+
state: z4.enum(["open", "answered"]),
|
|
2014
|
+
answer: z4.string().nullable(),
|
|
2015
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
2016
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
2017
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
2018
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
2019
|
+
agent: z4.object({ tokenId: z4.string(), name: z4.string(), voice: VoiceKeySchema.optional() }).optional()
|
|
2020
|
+
})).optional(),
|
|
2021
|
+
visuals: z4.array(VisualSchema).optional(),
|
|
1871
2022
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
1872
2023
|
* agent's suggestion, or a default silly name). */
|
|
1873
|
-
name:
|
|
2024
|
+
name: z4.string(),
|
|
1874
2025
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
1875
2026
|
voice: VoiceKeySchema.optional(),
|
|
1876
|
-
repo:
|
|
1877
|
-
branch:
|
|
1878
|
-
createdAt:
|
|
1879
|
-
snoozedUntil:
|
|
2027
|
+
repo: z4.string().optional(),
|
|
2028
|
+
branch: z4.string().optional(),
|
|
2029
|
+
createdAt: z4.string().datetime(),
|
|
2030
|
+
snoozedUntil: z4.string().datetime().optional(),
|
|
1880
2031
|
agentState: AgentStateSchema.default("idle"),
|
|
1881
2032
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
1882
2033
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
1883
2034
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
1884
|
-
turn:
|
|
2035
|
+
turn: z4.enum(["you", "agent"]).default("you"),
|
|
1885
2036
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
1886
|
-
* the agent (provider-agnostic; set server-side). Absent = no hard error
|
|
1887
|
-
*
|
|
1888
|
-
error:
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2037
|
+
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
2038
|
+
* error badge + Retry. */
|
|
2039
|
+
error: z4.string().optional(),
|
|
2040
|
+
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
2041
|
+
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
2042
|
+
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
2043
|
+
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
2044
|
+
cold: z4.string().datetime().optional(),
|
|
2045
|
+
clarifies: z4.string().optional(),
|
|
2046
|
+
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
2047
|
+
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
2048
|
+
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
2049
|
+
* The ladder is ACCOUNT-WIDE (#2259): `anchorAt` and `step` are the account's position;
|
|
2050
|
+
* `nextRingAt` is this card's armed instant (`deliveries.next_ring_at`), null once the cron
|
|
2051
|
+
* has disarmed it — the curve's end, `inbox`/`dismiss`, or a setting that said no more rings.
|
|
2052
|
+
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
2053
|
+
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
2054
|
+
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
2055
|
+
ring: z4.object({
|
|
2056
|
+
declinedAt: z4.string().datetime(),
|
|
2057
|
+
anchorAt: z4.string().datetime(),
|
|
2058
|
+
nextRingAt: z4.string().datetime().nullable(),
|
|
2059
|
+
step: z4.number().int()
|
|
2060
|
+
}).optional(),
|
|
1896
2061
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
1897
2062
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
1898
2063
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
1899
2064
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
1900
2065
|
* feedback ABOUT. */
|
|
1901
|
-
why:
|
|
2066
|
+
why: z4.object({
|
|
1902
2067
|
asked: NotifyLevelSchema,
|
|
1903
2068
|
got: NotifyLevelSchema,
|
|
1904
|
-
because:
|
|
1905
|
-
line:
|
|
2069
|
+
because: z4.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
2070
|
+
line: z4.string()
|
|
1906
2071
|
}).optional(),
|
|
1907
|
-
select:
|
|
1908
|
-
confirmStyle:
|
|
2072
|
+
select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
2073
|
+
confirmStyle: z4.enum(["yesno", "approve"]).default("yesno").describe(
|
|
1909
2074
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
1910
2075
|
),
|
|
1911
2076
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
1912
2077
|
* urgency (see the main README's "premier use case" + notify/states.md). Drives the
|
|
1913
2078
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
1914
|
-
blocking:
|
|
2079
|
+
blocking: z4.boolean().default(false),
|
|
1915
2080
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
1916
2081
|
answer: UserAnswerSchema.optional(),
|
|
1917
2082
|
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
@@ -1919,35 +2084,38 @@ var InboxItemSchema = z3.object({
|
|
|
1919
2084
|
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
1920
2085
|
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
1921
2086
|
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
1922
|
-
communication:
|
|
1923
|
-
deliveryId:
|
|
1924
|
-
kind:
|
|
1925
|
-
entryId:
|
|
1926
|
-
goalIds:
|
|
1927
|
-
decisionNeedId:
|
|
1928
|
-
sealed:
|
|
1929
|
-
goalState:
|
|
2087
|
+
communication: z4.object({
|
|
2088
|
+
deliveryId: z4.string(),
|
|
2089
|
+
kind: z4.enum(["notification", "call"]),
|
|
2090
|
+
entryId: z4.string(),
|
|
2091
|
+
goalIds: z4.array(z4.string()),
|
|
2092
|
+
decisionNeedId: z4.string().optional(),
|
|
2093
|
+
sealed: z4.boolean(),
|
|
2094
|
+
goalState: z4.string().optional(),
|
|
2095
|
+
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
2096
|
+
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
2097
|
+
goalTitle: z4.string().optional()
|
|
1930
2098
|
}).optional()
|
|
1931
2099
|
});
|
|
1932
2100
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
1933
|
-
var PushTokenSchema =
|
|
1934
|
-
voipToken:
|
|
1935
|
-
alertToken:
|
|
1936
|
-
fcmToken:
|
|
1937
|
-
platform:
|
|
2101
|
+
var PushTokenSchema = z4.object({
|
|
2102
|
+
voipToken: z4.string().min(1).optional(),
|
|
2103
|
+
alertToken: z4.string().min(1).optional(),
|
|
2104
|
+
fcmToken: z4.string().min(1).optional(),
|
|
2105
|
+
platform: z4.enum(["ios", "android"])
|
|
1938
2106
|
}).superRefine((v, ctx) => {
|
|
1939
2107
|
if (v.platform !== "ios") return;
|
|
1940
2108
|
for (const field of ["voipToken", "alertToken"]) {
|
|
1941
2109
|
const token = v[field];
|
|
1942
2110
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
1943
2111
|
ctx.addIssue({
|
|
1944
|
-
code:
|
|
2112
|
+
code: z4.ZodIssueCode.custom,
|
|
1945
2113
|
path: [field],
|
|
1946
2114
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
1947
2115
|
});
|
|
1948
2116
|
}
|
|
1949
2117
|
});
|
|
1950
|
-
var MissedCallSchema =
|
|
2118
|
+
var MissedCallSchema = z4.enum([
|
|
1951
2119
|
"retry_10m",
|
|
1952
2120
|
"retry_30m",
|
|
1953
2121
|
"retry_60m",
|
|
@@ -1959,32 +2127,32 @@ var MissedCallSchema = z3.enum([
|
|
|
1959
2127
|
]);
|
|
1960
2128
|
var clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
1961
2129
|
var QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
|
|
1962
|
-
var BrokerTuningSchema =
|
|
2130
|
+
var BrokerTuningSchema = z4.object({
|
|
1963
2131
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
1964
|
-
ackVerbosity:
|
|
2132
|
+
ackVerbosity: z4.enum(["normal", "none"]).optional(),
|
|
1965
2133
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
1966
2134
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
1967
|
-
clarifyEagerness:
|
|
2135
|
+
clarifyEagerness: z4.enum(["low", "normal", "high"]).optional(),
|
|
1968
2136
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
1969
|
-
phrasebook:
|
|
2137
|
+
phrasebook: z4.array(z4.object({ say: z4.string().min(1).max(60), mean: z4.string().min(1).max(120) })).max(24).optional(),
|
|
1970
2138
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
1971
2139
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
1972
2140
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
1973
2141
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
1974
2142
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
1975
|
-
language:
|
|
2143
|
+
language: z4.enum(["en", "es"]).optional()
|
|
1976
2144
|
});
|
|
1977
|
-
var UserSettingsSchema =
|
|
1978
|
-
permissions:
|
|
1979
|
-
call:
|
|
1980
|
-
banner:
|
|
1981
|
-
push:
|
|
2145
|
+
var UserSettingsSchema = z4.object({
|
|
2146
|
+
permissions: z4.object({
|
|
2147
|
+
call: z4.boolean(),
|
|
2148
|
+
banner: z4.boolean(),
|
|
2149
|
+
push: z4.boolean()
|
|
1982
2150
|
}),
|
|
1983
|
-
sessionMode:
|
|
1984
|
-
silentPush:
|
|
1985
|
-
autoCallback:
|
|
2151
|
+
sessionMode: z4.enum(["default", "all_calls", "silent"]),
|
|
2152
|
+
silentPush: z4.boolean(),
|
|
2153
|
+
autoCallback: z4.boolean(),
|
|
1986
2154
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
1987
|
-
improveConsent:
|
|
2155
|
+
improveConsent: z4.boolean(),
|
|
1988
2156
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
1989
2157
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
1990
2158
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -1992,7 +2160,13 @@ var UserSettingsSchema = z3.object({
|
|
|
1992
2160
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
1993
2161
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
1994
2162
|
* write, 'hosted' on read (see store.ts). */
|
|
1995
|
-
voiceMode:
|
|
2163
|
+
voiceMode: z4.enum(["hosted", "on_device"]).optional(),
|
|
2164
|
+
/** Talk — after you answer, the next step is read aloud (walk/design.md §6). ALWAYS ON until
|
|
2165
|
+
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
2166
|
+
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
2167
|
+
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
2168
|
+
* read (see store.ts). */
|
|
2169
|
+
talk: z4.boolean().optional(),
|
|
1996
2170
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
1997
2171
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
1998
2172
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -2000,10 +2174,10 @@ var UserSettingsSchema = z3.object({
|
|
|
2000
2174
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
2001
2175
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
2002
2176
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
2003
|
-
voiceTuning:
|
|
2177
|
+
voiceTuning: z4.record(z4.string(), z4.union([z4.number(), z4.string()])).optional(),
|
|
2004
2178
|
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
2005
2179
|
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
2006
|
-
pstnCalls:
|
|
2180
|
+
pstnCalls: z4.boolean().optional(),
|
|
2007
2181
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
2008
2182
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
2009
2183
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -2012,52 +2186,51 @@ var UserSettingsSchema = z3.object({
|
|
|
2012
2186
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
2013
2187
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
2014
2188
|
* call — honest about what we know. */
|
|
2015
|
-
timezone:
|
|
2189
|
+
timezone: z4.string().min(1).max(64).optional(),
|
|
2016
2190
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
2017
2191
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
2018
2192
|
broker: BrokerTuningSchema.optional()
|
|
2019
2193
|
});
|
|
2020
|
-
var HistoryItemSchema =
|
|
2021
|
-
id:
|
|
2022
|
-
parentId: z3.string(),
|
|
2194
|
+
var HistoryItemSchema = z4.object({
|
|
2195
|
+
id: z4.string(),
|
|
2023
2196
|
/** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
|
|
2024
|
-
initiator:
|
|
2025
|
-
title:
|
|
2197
|
+
initiator: z4.enum(["user", "agent"]),
|
|
2198
|
+
title: z4.string(),
|
|
2026
2199
|
/** The agent on the other end (its name). */
|
|
2027
|
-
name:
|
|
2028
|
-
createdAt:
|
|
2200
|
+
name: z4.string(),
|
|
2201
|
+
createdAt: z4.string(),
|
|
2029
2202
|
/** When the agent fetched your request (user→agent only). */
|
|
2030
|
-
agentAckedAt:
|
|
2203
|
+
agentAckedAt: z4.string().nullable(),
|
|
2031
2204
|
/** When you answered the agent's notification (agent→user only). */
|
|
2032
|
-
humanAckedAt:
|
|
2205
|
+
humanAckedAt: z4.string().nullable()
|
|
2033
2206
|
});
|
|
2034
2207
|
var ACTIVITY_LINES = 2;
|
|
2035
2208
|
var ACTIVITY_LINE_MAX = 80;
|
|
2036
|
-
var AgentActivitySchema =
|
|
2209
|
+
var AgentActivitySchema = z4.object({
|
|
2037
2210
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
2038
|
-
lines:
|
|
2211
|
+
lines: z4.array(z4.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
2039
2212
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
2040
2213
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
2041
|
-
at:
|
|
2214
|
+
at: z4.string().datetime()
|
|
2042
2215
|
});
|
|
2043
|
-
var ConnectionSummarySchema =
|
|
2216
|
+
var ConnectionSummarySchema = z4.object({
|
|
2044
2217
|
/** The connection = the agent's token id (used to address a request). */
|
|
2045
|
-
id:
|
|
2218
|
+
id: z4.string(),
|
|
2046
2219
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
2047
2220
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
2048
2221
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
2049
2222
|
* apps/api/src/tokens/devices-vs-agents-design.md. */
|
|
2050
|
-
kind:
|
|
2223
|
+
kind: z4.enum(["device", "agent"]).optional(),
|
|
2051
2224
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
2052
2225
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
2053
2226
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
2054
|
-
mintedByDevice:
|
|
2055
|
-
device:
|
|
2227
|
+
mintedByDevice: z4.string().nullable().optional(),
|
|
2228
|
+
device: z4.string().nullable(),
|
|
2056
2229
|
/** The agent's display name (the single pairing name). */
|
|
2057
|
-
name:
|
|
2230
|
+
name: z4.string(),
|
|
2058
2231
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
2059
2232
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
2060
|
-
provider:
|
|
2233
|
+
provider: z4.string().nullable(),
|
|
2061
2234
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
2062
2235
|
voice: VoiceKeySchema.nullable(),
|
|
2063
2236
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -2068,22 +2241,22 @@ var ConnectionSummarySchema = z3.object({
|
|
|
2068
2241
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
2069
2242
|
* set once must not overrule a rule they set about one agent. */
|
|
2070
2243
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
2071
|
-
createdAt:
|
|
2244
|
+
createdAt: z4.string().datetime(),
|
|
2072
2245
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
2073
2246
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
2074
|
-
lastContactAt:
|
|
2247
|
+
lastContactAt: z4.string().datetime().nullable(),
|
|
2075
2248
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
2076
2249
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
2077
|
-
lastSeenAt:
|
|
2250
|
+
lastSeenAt: z4.string().datetime().nullable().optional(),
|
|
2078
2251
|
/** What a live desktop can run (companion.md §2.2), advertised on its heartbeat:
|
|
2079
2252
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
2080
2253
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
2081
|
-
runtime:
|
|
2254
|
+
runtime: z4.object({
|
|
2082
2255
|
/** The @paigy/harness this host is running — a machine the self-update has not reached
|
|
2083
2256
|
* shows its age here (`apps/desktop/src/update.ts`). */
|
|
2084
|
-
version:
|
|
2085
|
-
harnesses:
|
|
2086
|
-
workspaces:
|
|
2257
|
+
version: z4.string().optional(),
|
|
2258
|
+
harnesses: z4.array(z4.object({ name: z4.string(), label: z4.string(), status: z4.string() })).optional(),
|
|
2259
|
+
workspaces: z4.array(z4.string()).optional()
|
|
2087
2260
|
}).optional(),
|
|
2088
2261
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
2089
2262
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -2092,148 +2265,178 @@ var ConnectionSummarySchema = z3.object({
|
|
|
2092
2265
|
activity: AgentActivitySchema.optional(),
|
|
2093
2266
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
2094
2267
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
2095
|
-
managed:
|
|
2268
|
+
managed: z4.boolean()
|
|
2096
2269
|
});
|
|
2097
|
-
var LedgerItemSchema =
|
|
2098
|
-
var AgentLedgerSchema =
|
|
2099
|
-
agent:
|
|
2270
|
+
var LedgerItemSchema = z4.object({ id: z4.string(), parentId: z4.string(), title: z4.string(), createdAt: z4.string() });
|
|
2271
|
+
var AgentLedgerSchema = z4.object({
|
|
2272
|
+
agent: z4.object({ id: z4.string(), name: z4.string(), revokedAt: z4.string().nullable() }),
|
|
2100
2273
|
/** Its own questions you have not answered. */
|
|
2101
|
-
asks:
|
|
2274
|
+
asks: z4.array(LedgerItemSchema),
|
|
2102
2275
|
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
2103
|
-
answered:
|
|
2276
|
+
answered: z4.array(LedgerItemSchema),
|
|
2104
2277
|
/** Requests you sent it that it never took. */
|
|
2105
|
-
requests:
|
|
2106
|
-
goals:
|
|
2107
|
-
callbacks:
|
|
2278
|
+
requests: z4.array(LedgerItemSchema),
|
|
2279
|
+
goals: z4.array(z4.object({ id: z4.string(), outcome: z4.string(), state: z4.string() })),
|
|
2280
|
+
callbacks: z4.array(z4.object({ id: z4.string(), parentId: z4.string(), trigger: z4.string(), note: z4.string(), dueAt: z4.string().nullable() }))
|
|
2108
2281
|
});
|
|
2109
|
-
var ReassignResultSchema =
|
|
2110
|
-
moved:
|
|
2111
|
-
parentId:
|
|
2282
|
+
var ReassignResultSchema = z4.object({
|
|
2283
|
+
moved: z4.object({ asks: z4.number(), answered: z4.number(), requests: z4.number(), goals: z4.number(), callbacks: z4.number() }),
|
|
2284
|
+
parentId: z4.string().nullable()
|
|
2112
2285
|
});
|
|
2113
|
-
var MoveRingSchema =
|
|
2114
|
-
var MoveSchema =
|
|
2115
|
-
id:
|
|
2286
|
+
var MoveRingSchema = z4.enum(["home", "travels", "retired", "quarantined"]);
|
|
2287
|
+
var MoveSchema = z4.object({
|
|
2288
|
+
id: z4.string(),
|
|
2116
2289
|
/** The reusable question, as distill normalized it. */
|
|
2117
|
-
question:
|
|
2290
|
+
question: z4.string(),
|
|
2118
2291
|
/** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
|
|
2119
|
-
answer:
|
|
2292
|
+
answer: z4.string(),
|
|
2120
2293
|
/** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
|
|
2121
2294
|
* told so, and the ruling only derives essentially the same question in the same scope. */
|
|
2122
|
-
rationale:
|
|
2295
|
+
rationale: z4.string().nullable(),
|
|
2123
2296
|
/** Where the ruling lives: a repo/workspace, or 'global'. */
|
|
2124
|
-
scope:
|
|
2297
|
+
scope: z4.string(),
|
|
2125
2298
|
ring: MoveRingSchema,
|
|
2126
2299
|
/** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
|
|
2127
|
-
pinned:
|
|
2300
|
+
pinned: z4.boolean(),
|
|
2128
2301
|
/** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
|
|
2129
2302
|
* break is visible instead of a pin silently disappearing. */
|
|
2130
|
-
pinBroken:
|
|
2303
|
+
pinBroken: z4.boolean(),
|
|
2131
2304
|
/** When the ruling was distilled. */
|
|
2132
|
-
learnedAt:
|
|
2305
|
+
learnedAt: z4.string(),
|
|
2133
2306
|
/** Last time it answered an ask. Null = never fired. */
|
|
2134
|
-
lastUsedAt:
|
|
2307
|
+
lastUsedAt: z4.string().nullable(),
|
|
2135
2308
|
/** How many asks it has answered. Instrumentation — deliberately NOT an input to the
|
|
2136
2309
|
* evidence curve: firing says the question keeps arising, not that the ruling is right. */
|
|
2137
|
-
usedCount:
|
|
2310
|
+
usedCount: z4.number(),
|
|
2138
2311
|
/** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
|
|
2139
|
-
confirms:
|
|
2312
|
+
confirms: z4.number(),
|
|
2140
2313
|
/** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
|
|
2141
2314
|
* Linear and priced above the entire confirmation budget, so any full counter wins. */
|
|
2142
|
-
counters:
|
|
2315
|
+
counters: z4.number(),
|
|
2143
2316
|
/** The agent that asked the question this move came from, when known. Null for a move
|
|
2144
2317
|
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
2145
|
-
learnedFrom:
|
|
2318
|
+
learnedFrom: z4.object({ id: z4.string(), name: z4.string() }).nullable()
|
|
2146
2319
|
});
|
|
2147
|
-
var QueueQuestionSchema =
|
|
2320
|
+
var QueueQuestionSchema = z4.object({
|
|
2148
2321
|
/** The decision need's id — what an answer is accepted against. */
|
|
2149
|
-
id:
|
|
2322
|
+
id: z4.string(),
|
|
2150
2323
|
/** The words that were asked, from the request Entry that asked them. */
|
|
2151
|
-
question:
|
|
2324
|
+
question: z4.string(),
|
|
2152
2325
|
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
2153
2326
|
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
2154
2327
|
* can answer. */
|
|
2155
|
-
deliveryId:
|
|
2328
|
+
deliveryId: z4.string().nullable().default(null),
|
|
2156
2329
|
/** The Entry the ruling is about. */
|
|
2157
|
-
aboutId:
|
|
2330
|
+
aboutId: z4.string().nullable().default(null),
|
|
2158
2331
|
/** Empty for a free-text question. */
|
|
2159
|
-
options:
|
|
2160
|
-
select:
|
|
2161
|
-
askedAt:
|
|
2332
|
+
options: z4.array(OptionSchema).default([]),
|
|
2333
|
+
select: z4.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
2334
|
+
askedAt: z4.string(),
|
|
2162
2335
|
/** Null while the question is open — which is how the page tells the two apart. */
|
|
2163
|
-
answeredAt:
|
|
2336
|
+
answeredAt: z4.string().nullable().default(null),
|
|
2164
2337
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
2165
2338
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
2166
2339
|
* for a settled question whose reply carried nothing readable. */
|
|
2167
|
-
answer:
|
|
2340
|
+
answer: z4.string().nullable().default(null),
|
|
2168
2341
|
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
2169
2342
|
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
2170
2343
|
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
2171
2344
|
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
2172
2345
|
* this is the field they agree through rather than each re-deriving it from the row it
|
|
2173
2346
|
* arrived under. Required because the API projects it on every need it sends. */
|
|
2174
|
-
goalId:
|
|
2347
|
+
goalId: z4.string(),
|
|
2175
2348
|
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
2176
2349
|
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
2177
2350
|
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
2178
2351
|
* gate at all. */
|
|
2179
|
-
blocked:
|
|
2352
|
+
blocked: z4.boolean().default(false)
|
|
2353
|
+
});
|
|
2354
|
+
var QueueReplySchema = z4.object({
|
|
2355
|
+
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
2356
|
+
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
2357
|
+
* walk can name it in its zoom. */
|
|
2358
|
+
id: z4.string(),
|
|
2359
|
+
/** The Goal the note is on. */
|
|
2360
|
+
goalId: z4.string(),
|
|
2361
|
+
/** What the note said. */
|
|
2362
|
+
note: z4.string(),
|
|
2363
|
+
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
2364
|
+
deliveryId: z4.string(),
|
|
2365
|
+
requestEntryId: z4.string(),
|
|
2366
|
+
askedAt: z4.string(),
|
|
2367
|
+
/** When the person last replied — the window's start. */
|
|
2368
|
+
repliedAt: z4.string(),
|
|
2369
|
+
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
2370
|
+
reply: z4.string().nullable()
|
|
2180
2371
|
});
|
|
2181
|
-
var QueueItemSchema =
|
|
2182
|
-
id:
|
|
2372
|
+
var QueueItemSchema = z4.object({
|
|
2373
|
+
id: z4.string(),
|
|
2183
2374
|
/** One-line headline — the first sentence of the outcome. */
|
|
2184
|
-
title:
|
|
2375
|
+
title: z4.string(),
|
|
2185
2376
|
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
2186
|
-
intent:
|
|
2377
|
+
intent: z4.string(),
|
|
2187
2378
|
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
2188
|
-
state:
|
|
2379
|
+
state: z4.string(),
|
|
2189
2380
|
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
2190
|
-
assignee:
|
|
2381
|
+
assignee: z4.string().nullable().default(null),
|
|
2191
2382
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
2192
|
-
progress:
|
|
2193
|
-
|
|
2194
|
-
|
|
2383
|
+
progress: z4.string().nullable().default(null),
|
|
2384
|
+
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
2385
|
+
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
2386
|
+
progressLine: z4.string().nullable().optional(),
|
|
2387
|
+
reviewPending: z4.boolean().default(false),
|
|
2388
|
+
dueAt: z4.string().nullable().default(null),
|
|
2195
2389
|
/** The Goal this one was opened under; null at the root. */
|
|
2196
|
-
parentGoalId:
|
|
2390
|
+
parentGoalId: z4.string().nullable().default(null),
|
|
2197
2391
|
/** Goals opened under this one — only those the same list holds. */
|
|
2198
|
-
childGoalIds:
|
|
2392
|
+
childGoalIds: z4.array(z4.string()).default([]),
|
|
2199
2393
|
/** Goals this one waits on (start or finish gates). */
|
|
2200
|
-
dependencyGoalIds:
|
|
2394
|
+
dependencyGoalIds: z4.array(z4.string()).default([]),
|
|
2201
2395
|
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
2202
|
-
blocked:
|
|
2396
|
+
blocked: z4.boolean().default(false),
|
|
2203
2397
|
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
2204
|
-
questions:
|
|
2398
|
+
questions: z4.array(QueueQuestionSchema).default([]),
|
|
2399
|
+
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
2400
|
+
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
2401
|
+
* not spell an empty list. */
|
|
2402
|
+
replies: z4.array(QueueReplySchema).optional(),
|
|
2205
2403
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
2206
|
-
repo:
|
|
2207
|
-
createdAt:
|
|
2208
|
-
updatedAt:
|
|
2404
|
+
repo: z4.string().nullable().optional(),
|
|
2405
|
+
createdAt: z4.string(),
|
|
2406
|
+
updatedAt: z4.string().nullable().default(null),
|
|
2407
|
+
/** When its owner last SAID something about it (`goals.last_progress_at`, written by every
|
|
2408
|
+
* `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
|
|
2409
|
+
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
2410
|
+
lastProgressAt: z4.string().nullable().optional()
|
|
2209
2411
|
});
|
|
2210
|
-
var
|
|
2211
|
-
var
|
|
2212
|
-
var
|
|
2213
|
-
var
|
|
2214
|
-
|
|
2412
|
+
var COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
2413
|
+
var NoteSourceSchema = z4.enum(["app", "call"]);
|
|
2414
|
+
var NoteStatusSchema = z4.enum(["open", "assigned", "in_progress", "done"]);
|
|
2415
|
+
var NoteRepeatSchema = z4.enum(["once", "until_done"]);
|
|
2416
|
+
var DecisionSchema = z4.object({
|
|
2417
|
+
id: z4.string(),
|
|
2215
2418
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
2216
2419
|
* extensibility seam — any conversation can accrue decisions). */
|
|
2217
|
-
noteId:
|
|
2420
|
+
noteId: z4.string().nullable(),
|
|
2218
2421
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
2219
|
-
question:
|
|
2422
|
+
question: z4.string(),
|
|
2220
2423
|
/** The user's ruling; null while the question is open. */
|
|
2221
|
-
answer:
|
|
2222
|
-
decidedAt:
|
|
2223
|
-
createdAt:
|
|
2424
|
+
answer: z4.string().nullable(),
|
|
2425
|
+
decidedAt: z4.string().nullable(),
|
|
2426
|
+
createdAt: z4.string()
|
|
2224
2427
|
});
|
|
2225
|
-
var NoteSchema =
|
|
2226
|
-
id:
|
|
2428
|
+
var NoteSchema = z4.object({
|
|
2429
|
+
id: z4.string(),
|
|
2227
2430
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
2228
|
-
title:
|
|
2431
|
+
title: z4.string(),
|
|
2229
2432
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
2230
|
-
intent:
|
|
2433
|
+
intent: z4.string(),
|
|
2231
2434
|
source: NoteSourceSchema,
|
|
2232
2435
|
status: NoteStatusSchema,
|
|
2233
2436
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
2234
|
-
assignee:
|
|
2437
|
+
assignee: z4.string().nullable(),
|
|
2235
2438
|
/** The request thread minted at assignment; null until assigned. */
|
|
2236
|
-
parentId:
|
|
2439
|
+
parentId: z4.string().nullable(),
|
|
2237
2440
|
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
2238
2441
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
2239
2442
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -2242,133 +2445,133 @@ var NoteSchema = z3.object({
|
|
|
2242
2445
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
2243
2446
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
2244
2447
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
2245
|
-
dueAt:
|
|
2448
|
+
dueAt: z4.string().nullable().default(null),
|
|
2246
2449
|
repeat: NoteRepeatSchema.default("once"),
|
|
2247
2450
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
2248
|
-
rides:
|
|
2249
|
-
lastRideAt:
|
|
2250
|
-
createdAt:
|
|
2451
|
+
rides: z4.number().int().default(0),
|
|
2452
|
+
lastRideAt: z4.string().nullable().default(null),
|
|
2453
|
+
createdAt: z4.string()
|
|
2251
2454
|
});
|
|
2252
|
-
var TriageItemSchema =
|
|
2253
|
-
noteId:
|
|
2455
|
+
var TriageItemSchema = z4.object({
|
|
2456
|
+
noteId: z4.string(),
|
|
2254
2457
|
/** The note's headline at run time. */
|
|
2255
|
-
title:
|
|
2458
|
+
title: z4.string(),
|
|
2256
2459
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
2257
2460
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
2258
2461
|
* Never a model's reasoning transcript, never an id. */
|
|
2259
|
-
why:
|
|
2462
|
+
why: z4.string()
|
|
2260
2463
|
});
|
|
2261
|
-
var TriageAssignmentSchema =
|
|
2464
|
+
var TriageAssignmentSchema = z4.object({
|
|
2262
2465
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
2263
|
-
agent:
|
|
2466
|
+
agent: z4.string(),
|
|
2264
2467
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
2265
2468
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
2266
|
-
agentName:
|
|
2267
|
-
notes:
|
|
2469
|
+
agentName: z4.string(),
|
|
2470
|
+
notes: z4.array(TriageItemSchema)
|
|
2268
2471
|
});
|
|
2269
|
-
var TriageStatusSchema =
|
|
2270
|
-
var SubmitTriageSchema =
|
|
2472
|
+
var TriageStatusSchema = z4.enum(["open", "superseded", "dismissed"]);
|
|
2473
|
+
var SubmitTriageSchema = z4.object({
|
|
2271
2474
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
2272
2475
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
2273
2476
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
2274
2477
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
2275
|
-
provider:
|
|
2478
|
+
provider: z4.string().min(1).max(60),
|
|
2276
2479
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
2277
|
-
model:
|
|
2480
|
+
model: z4.string().max(200).nullable().optional(),
|
|
2278
2481
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
2279
2482
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
2280
|
-
reviewed:
|
|
2281
|
-
close:
|
|
2282
|
-
stale:
|
|
2283
|
-
assign:
|
|
2483
|
+
reviewed: z4.number().int().min(0).max(1e4).default(0),
|
|
2484
|
+
close: z4.array(TriageItemSchema).max(200).default([]),
|
|
2485
|
+
stale: z4.array(TriageItemSchema).max(200).default([]),
|
|
2486
|
+
assign: z4.array(TriageAssignmentSchema).max(50).default([])
|
|
2284
2487
|
});
|
|
2285
2488
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
2286
|
-
id:
|
|
2287
|
-
runAt:
|
|
2489
|
+
id: z4.string(),
|
|
2490
|
+
runAt: z4.string(),
|
|
2288
2491
|
status: TriageStatusSchema,
|
|
2289
|
-
model:
|
|
2492
|
+
model: z4.string().nullable().default(null)
|
|
2290
2493
|
});
|
|
2291
|
-
var AcceptTriageSchema =
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
group:
|
|
2296
|
-
agent:
|
|
2297
|
-
noteIds:
|
|
2494
|
+
var AcceptTriageSchema = z4.discriminatedUnion("group", [
|
|
2495
|
+
z4.object({ group: z4.literal("close"), noteIds: z4.array(z4.string()).max(200).optional() }),
|
|
2496
|
+
z4.object({ group: z4.literal("stale"), noteIds: z4.array(z4.string()).max(200).optional() }),
|
|
2497
|
+
z4.object({
|
|
2498
|
+
group: z4.literal("assign"),
|
|
2499
|
+
agent: z4.string().min(1),
|
|
2500
|
+
noteIds: z4.array(z4.string()).max(200).optional()
|
|
2298
2501
|
})
|
|
2299
2502
|
]);
|
|
2300
|
-
var AcceptTriageResultSchema =
|
|
2301
|
-
accepted:
|
|
2302
|
-
failed:
|
|
2503
|
+
var AcceptTriageResultSchema = z4.object({
|
|
2504
|
+
accepted: z4.array(z4.string()),
|
|
2505
|
+
failed: z4.array(z4.object({ noteId: z4.string(), reason: z4.string() }))
|
|
2303
2506
|
});
|
|
2304
|
-
var DeliveryModeSchema =
|
|
2507
|
+
var DeliveryModeSchema = z4.enum(["poll", "self_hosted"]);
|
|
2305
2508
|
var WAKE_EVENT = "wake";
|
|
2306
2509
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
2307
|
-
var RegisterDeliverySchema =
|
|
2308
|
-
var OAuthStartSchema =
|
|
2309
|
-
provider:
|
|
2310
|
-
returnTo:
|
|
2510
|
+
var RegisterDeliverySchema = z4.object({ mode: DeliveryModeSchema });
|
|
2511
|
+
var OAuthStartSchema = z4.object({
|
|
2512
|
+
provider: z4.enum(["cma"]),
|
|
2513
|
+
returnTo: z4.string().min(1)
|
|
2311
2514
|
});
|
|
2312
|
-
var DeliveryConfigSchema =
|
|
2313
|
-
tokenId:
|
|
2515
|
+
var DeliveryConfigSchema = z4.object({
|
|
2516
|
+
tokenId: z4.string(),
|
|
2314
2517
|
mode: DeliveryModeSchema,
|
|
2315
2518
|
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
2316
2519
|
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
2317
2520
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
2318
|
-
realtime:
|
|
2521
|
+
realtime: z4.object({ url: z4.string(), anonKey: z4.string() }).nullable()
|
|
2319
2522
|
});
|
|
2320
|
-
var WakeNudgeSchema =
|
|
2321
|
-
kind:
|
|
2322
|
-
notificationId:
|
|
2323
|
-
parentId:
|
|
2523
|
+
var WakeNudgeSchema = z4.object({
|
|
2524
|
+
kind: z4.enum(["reply", "request", "callback"]),
|
|
2525
|
+
notificationId: z4.string().optional(),
|
|
2526
|
+
parentId: z4.string()
|
|
2324
2527
|
});
|
|
2325
|
-
var PairingStatusSchema =
|
|
2326
|
-
var DeviceCodeSchema =
|
|
2327
|
-
device_code:
|
|
2328
|
-
user_code:
|
|
2329
|
-
verification_uri:
|
|
2330
|
-
verification_uri_complete:
|
|
2331
|
-
interval:
|
|
2332
|
-
expires_in:
|
|
2528
|
+
var PairingStatusSchema = z4.enum(["pending", "approved", "denied", "expired"]);
|
|
2529
|
+
var DeviceCodeSchema = z4.object({
|
|
2530
|
+
device_code: z4.string(),
|
|
2531
|
+
user_code: z4.string(),
|
|
2532
|
+
verification_uri: z4.string().url(),
|
|
2533
|
+
verification_uri_complete: z4.string().url(),
|
|
2534
|
+
interval: z4.number(),
|
|
2535
|
+
expires_in: z4.number()
|
|
2333
2536
|
});
|
|
2334
|
-
var DeviceInfoSchema =
|
|
2335
|
-
code:
|
|
2537
|
+
var DeviceInfoSchema = z4.object({
|
|
2538
|
+
code: z4.string(),
|
|
2336
2539
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
2337
2540
|
* pre-filling the name field the human can edit. */
|
|
2338
|
-
name:
|
|
2541
|
+
name: z4.string(),
|
|
2339
2542
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
2340
2543
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
2341
2544
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
2342
2545
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
2343
|
-
agent:
|
|
2344
|
-
device:
|
|
2546
|
+
agent: z4.string().optional(),
|
|
2547
|
+
device: z4.string().nullable(),
|
|
2345
2548
|
status: PairingStatusSchema
|
|
2346
2549
|
});
|
|
2347
|
-
var DeviceTokenSchema =
|
|
2348
|
-
access_token:
|
|
2550
|
+
var DeviceTokenSchema = z4.object({
|
|
2551
|
+
access_token: z4.string(),
|
|
2349
2552
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
2350
2553
|
* a default silly name). */
|
|
2351
|
-
name:
|
|
2352
|
-
device:
|
|
2554
|
+
name: z4.string(),
|
|
2555
|
+
device: z4.string().nullable(),
|
|
2353
2556
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
2354
2557
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
2355
|
-
voice:
|
|
2558
|
+
voice: z4.string().nullable().optional(),
|
|
2356
2559
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
2357
2560
|
* that survives a rename. Cached by the host's identity beat. */
|
|
2358
|
-
token_id:
|
|
2561
|
+
token_id: z4.string().nullable().optional(),
|
|
2359
2562
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
2360
2563
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
2361
2564
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
2362
2565
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
2363
|
-
workspace:
|
|
2364
|
-
uik_pub:
|
|
2566
|
+
workspace: z4.string().nullable().optional(),
|
|
2567
|
+
uik_pub: z4.string().nullable().optional()
|
|
2365
2568
|
});
|
|
2366
|
-
var SupportRequestSchema =
|
|
2367
|
-
email:
|
|
2368
|
-
message:
|
|
2369
|
-
name:
|
|
2569
|
+
var SupportRequestSchema = z4.object({
|
|
2570
|
+
email: z4.string().email().max(320),
|
|
2571
|
+
message: z4.string().trim().min(1).max(5e3),
|
|
2572
|
+
name: z4.string().trim().max(120).optional()
|
|
2370
2573
|
});
|
|
2371
|
-
var NotificationFeedbackKindSchema =
|
|
2574
|
+
var NotificationFeedbackKindSchema = z4.enum([
|
|
2372
2575
|
"break_down",
|
|
2373
2576
|
// "This should be more than one ask — break it down."
|
|
2374
2577
|
"regenerate_options",
|
|
@@ -2389,6 +2592,7 @@ function sessionSlot(sessionId2) {
|
|
|
2389
2592
|
function agentName() {
|
|
2390
2593
|
return process.env.PAIGY_AGENT || sessionSlot();
|
|
2391
2594
|
}
|
|
2595
|
+
var PROTO = "paigy-pair-v2|sas=24";
|
|
2392
2596
|
var TOKEN_PATH = join2(homedir2(), ".paigy", "token.json");
|
|
2393
2597
|
var KEY_PATH = join2(homedir2(), ".paigy", "key.json");
|
|
2394
2598
|
function readTokenFile() {
|
|
@@ -2429,6 +2633,20 @@ function saveToken(token, agent2 = agentName()) {
|
|
|
2429
2633
|
writeFileSync(TOKEN_PATH, JSON.stringify(slots, null, 2) + "\n", { mode: 384 });
|
|
2430
2634
|
});
|
|
2431
2635
|
}
|
|
2636
|
+
function updateSlot(agent2, patch) {
|
|
2637
|
+
return withTokenLock(() => {
|
|
2638
|
+
const slots = readTokenFile();
|
|
2639
|
+
const existing = slots[agent2];
|
|
2640
|
+
if (!existing) return false;
|
|
2641
|
+
slots[agent2] = { ...existing, ...patch };
|
|
2642
|
+
writeFileSync(TOKEN_PATH, JSON.stringify(slots, null, 2) + "\n", { mode: 384 });
|
|
2643
|
+
return true;
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
function slotIdentity(agent2) {
|
|
2647
|
+
const t = readTokenFile()[agent2];
|
|
2648
|
+
return { name: t?.name ?? null, voice: t?.voice ?? null, tokenId: t?.token_id ?? null, workspace: t?.workspace ?? null };
|
|
2649
|
+
}
|
|
2432
2650
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2433
2651
|
function readToken(agent2 = agentName()) {
|
|
2434
2652
|
if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
|
|
@@ -2510,6 +2728,11 @@ var ApiError = class extends Error {
|
|
|
2510
2728
|
this.body = body;
|
|
2511
2729
|
}
|
|
2512
2730
|
};
|
|
2731
|
+
var tokenOverride = null;
|
|
2732
|
+
function overrideToken(secret) {
|
|
2733
|
+
tokenOverride = secret;
|
|
2734
|
+
}
|
|
2735
|
+
var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
2513
2736
|
async function fail(what, res) {
|
|
2514
2737
|
throw new ApiError(what, res.status, await res.text());
|
|
2515
2738
|
}
|
|
@@ -2546,25 +2769,6 @@ async function updateGoal(goalId, input, opts = {}) {
|
|
|
2546
2769
|
return await res.json();
|
|
2547
2770
|
}
|
|
2548
2771
|
var AWAIT_WINDOW_MS = 45e3;
|
|
2549
|
-
async function getThread(parentId, opts = {}) {
|
|
2550
|
-
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
2551
|
-
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
2552
|
-
}));
|
|
2553
|
-
if (!res.ok) await fail("get_thread", res);
|
|
2554
|
-
return await res.json();
|
|
2555
|
-
}
|
|
2556
|
-
async function searchThreads(q, opts = {}) {
|
|
2557
|
-
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/search?q=${encodeURIComponent(q)}`, {
|
|
2558
|
-
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
2559
|
-
}));
|
|
2560
|
-
if (!res.ok) await fail("search_threads", res);
|
|
2561
|
-
return await res.json();
|
|
2562
|
-
}
|
|
2563
|
-
var tokenOverride = null;
|
|
2564
|
-
function overrideToken(secret) {
|
|
2565
|
-
tokenOverride = secret;
|
|
2566
|
-
}
|
|
2567
|
-
var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
2568
2772
|
async function hatch(name, voice = null) {
|
|
2569
2773
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/hatch`, {
|
|
2570
2774
|
method: "POST",
|
|
@@ -2593,6 +2797,14 @@ async function setIdentity(patch, opts = {}) {
|
|
|
2593
2797
|
if (!res.ok) throw new Error(`set_identity failed: ${res.status} ${await res.text()}`);
|
|
2594
2798
|
return await res.json();
|
|
2595
2799
|
}
|
|
2800
|
+
async function claimSessions(opts = {}) {
|
|
2801
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/sessions/claim`, {
|
|
2802
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
2803
|
+
// the HOST's identity
|
|
2804
|
+
}));
|
|
2805
|
+
if (!res.ok) throw new Error(`claim_sessions failed: ${res.status}`);
|
|
2806
|
+
return (await res.json()).sessions;
|
|
2807
|
+
}
|
|
2596
2808
|
async function heartbeat(runtime, opts = {}) {
|
|
2597
2809
|
const body = {
|
|
2598
2810
|
...runtime !== void 0 ? { runtime } : {},
|
|
@@ -2614,6 +2826,79 @@ async function registerDelivery(mode, opts = {}) {
|
|
|
2614
2826
|
if (!res.ok) throw new Error(`register_delivery failed: ${res.status} ${await res.text()}`);
|
|
2615
2827
|
return await res.json();
|
|
2616
2828
|
}
|
|
2829
|
+
async function listNotes(opts = {}) {
|
|
2830
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notes`, {
|
|
2831
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
2832
|
+
}));
|
|
2833
|
+
if (!res.ok) throw new Error(`list_notes failed: ${res.status} ${await res.text()}`);
|
|
2834
|
+
return await res.json();
|
|
2835
|
+
}
|
|
2836
|
+
async function listConnections(opts = {}) {
|
|
2837
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/tokens`, {
|
|
2838
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}`, "x-paigy-model": "goal-entry-v1" }
|
|
2839
|
+
}));
|
|
2840
|
+
if (!res.ok) throw new Error(`list_connections failed: ${res.status} ${await res.text()}`);
|
|
2841
|
+
return await res.json();
|
|
2842
|
+
}
|
|
2843
|
+
async function submitTriage(run, opts = {}) {
|
|
2844
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage`, {
|
|
2845
|
+
method: "POST",
|
|
2846
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
2847
|
+
body: JSON.stringify(run)
|
|
2848
|
+
}));
|
|
2849
|
+
if (!res.ok) throw new Error(`submit_triage failed: ${res.status} ${await res.text()}`);
|
|
2850
|
+
return await res.json();
|
|
2851
|
+
}
|
|
2852
|
+
async function getTriage(opts = {}) {
|
|
2853
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage`, {
|
|
2854
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
2855
|
+
}));
|
|
2856
|
+
if (!res.ok) throw new Error(`get_triage failed: ${res.status} ${await res.text()}`);
|
|
2857
|
+
return await res.json();
|
|
2858
|
+
}
|
|
2859
|
+
async function acceptTriage(proposalId, body, opts = {}) {
|
|
2860
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage/${encodeURIComponent(proposalId)}/accept`, {
|
|
2861
|
+
method: "POST",
|
|
2862
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
2863
|
+
body: JSON.stringify(body)
|
|
2864
|
+
}));
|
|
2865
|
+
if (!res.ok) throw new Error(`accept_triage failed: ${res.status} ${await res.text()}`);
|
|
2866
|
+
return await res.json();
|
|
2867
|
+
}
|
|
2868
|
+
async function dismissTriage(proposalId, opts = {}) {
|
|
2869
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/triage/${encodeURIComponent(proposalId)}/dismiss`, {
|
|
2870
|
+
method: "POST",
|
|
2871
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
2872
|
+
}));
|
|
2873
|
+
if (!res.ok) throw new Error(`dismiss_triage failed: ${res.status} ${await res.text()}`);
|
|
2874
|
+
return await res.json();
|
|
2875
|
+
}
|
|
2876
|
+
function repoFromRemote(url) {
|
|
2877
|
+
const said = (url ?? "").trim();
|
|
2878
|
+
if (!said) return null;
|
|
2879
|
+
const path = said.replace(/^[a-z+]+:\/\/[^/]+\//i, "").replace(/^[^@]+@[^:]+:/, "").replace(/\.git$/i, "").replace(/\/+$/, "");
|
|
2880
|
+
const parts = path.split("/").filter(Boolean);
|
|
2881
|
+
if (parts.length < 2) return null;
|
|
2882
|
+
const [owner, name] = parts.slice(-2);
|
|
2883
|
+
if (!owner || !name) return null;
|
|
2884
|
+
return `${owner}/${name}`.toLowerCase();
|
|
2885
|
+
}
|
|
2886
|
+
var asked;
|
|
2887
|
+
function currentRepo(cwd = process.cwd()) {
|
|
2888
|
+
if (asked !== void 0) return asked;
|
|
2889
|
+
try {
|
|
2890
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
2891
|
+
cwd,
|
|
2892
|
+
encoding: "utf8",
|
|
2893
|
+
timeout: 2e3,
|
|
2894
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2895
|
+
});
|
|
2896
|
+
asked = repoFromRemote(url);
|
|
2897
|
+
} catch {
|
|
2898
|
+
asked = null;
|
|
2899
|
+
}
|
|
2900
|
+
return asked;
|
|
2901
|
+
}
|
|
2617
2902
|
async function contact(input, opts = {}) {
|
|
2618
2903
|
const parsed = ContactSchema.parse(input);
|
|
2619
2904
|
opts.signal?.throwIfAborted();
|
|
@@ -2627,7 +2912,11 @@ async function contact(input, opts = {}) {
|
|
|
2627
2912
|
const reqAsks = parsed.asks.map((a) => ({
|
|
2628
2913
|
id: a.id,
|
|
2629
2914
|
parentId: a.parentId,
|
|
2630
|
-
|
|
2915
|
+
// Derived, not asked for (#2315): the agent's own remote, in one spelling, so a project's
|
|
2916
|
+
// work does not grow three trees for "paigy", "mauurda/paigy" and "Paigy". An explicit one
|
|
2917
|
+
// still wins — normalised the same way — because a caller that names one knows something
|
|
2918
|
+
// this cannot see.
|
|
2919
|
+
repo: repoFromRemote(a.repo) ?? a.repo ?? currentRepo() ?? void 0,
|
|
2631
2920
|
ask: a.ask,
|
|
2632
2921
|
options: a.options ?? []
|
|
2633
2922
|
}));
|
|
@@ -2645,6 +2934,9 @@ async function contact(input, opts = {}) {
|
|
|
2645
2934
|
if (!res.ok) await fail("batch contact", res);
|
|
2646
2935
|
const receipt = await res.json();
|
|
2647
2936
|
deliveries = receipt.deliveries ?? [];
|
|
2937
|
+
if (deliveries.length === 0 && receipt.recorded?.length) {
|
|
2938
|
+
return { recorded: receipt.recorded, message: receipt.message ?? "Recorded as progress on its Goal; nothing was sent to the person." };
|
|
2939
|
+
}
|
|
2648
2940
|
if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
|
|
2649
2941
|
deliveryId = deliveries[0]?.deliveryId ?? "";
|
|
2650
2942
|
if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
|
|
@@ -2736,6 +3028,14 @@ function goalView(g) {
|
|
|
2736
3028
|
owner: g.ownerParticipant,
|
|
2737
3029
|
parentGoalId: g.parentGoalId,
|
|
2738
3030
|
childGoalIds: g.childGoalIds,
|
|
3031
|
+
// EVERYTHING BELOW, read through this Goal (umbrella reads): one line per Goal, indented by
|
|
3032
|
+
// depth, then each question with its answer — so an agent that asked through an umbrella
|
|
3033
|
+
// reads every answer in one call, whoever owns the child it landed on.
|
|
3034
|
+
subtree: g.subtree?.length ? g.subtree.map((c) => compact({
|
|
3035
|
+
goal: `${" ".repeat(c.depth - 1)}${c.title} (${c.state})`,
|
|
3036
|
+
goalId: c.goalId,
|
|
3037
|
+
questions: c.questions.length ? c.questions.map((q) => q.answer ? `${q.question} \u2192 ${q.answer}` : `${q.question} \u2192 (open)`) : void 0
|
|
3038
|
+
})) : void 0,
|
|
2739
3039
|
blockedBy: g.blockedDependencyGoalIds,
|
|
2740
3040
|
dueAt: g.dueAt,
|
|
2741
3041
|
leaseExpiresAt: g.leaseExpiresAt,
|
|
@@ -2755,18 +3055,10 @@ function deliveryView(d) {
|
|
|
2755
3055
|
next: d.kind === "notification" ? "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification." : d.message
|
|
2756
3056
|
});
|
|
2757
3057
|
}
|
|
2758
|
-
function threadView(t) {
|
|
2759
|
-
return {
|
|
2760
|
-
parentId: t.parentId,
|
|
2761
|
-
turns: (t.turns ?? []).map((turn) => {
|
|
2762
|
-
const lines2 = turn.role === "user" ? [turn.text ?? ""] : [turn.title ?? "", ...turn.description ?? []];
|
|
2763
|
-
const said = lines2.map((l) => l.trim()).filter((l, i, all) => l && all.indexOf(l) === i).join("\n");
|
|
2764
|
-
return compact({ from: turn.role === "user" ? "person" : "agent", at: turn.at ? at(turn.at) : void 0, said });
|
|
2765
|
-
})
|
|
2766
|
-
};
|
|
2767
|
-
}
|
|
2768
3058
|
function repliesView(r) {
|
|
2769
3059
|
const deliveries = r.deliveries ?? [];
|
|
3060
|
+
const stalled = r.stalled ?? [];
|
|
3061
|
+
const others = r.stalledOthers ?? [];
|
|
2770
3062
|
return {
|
|
2771
3063
|
deliveries: deliveries.map((d) => compact({
|
|
2772
3064
|
deliveryId: d.deliveryId,
|
|
@@ -2775,26 +3067,29 @@ function repliesView(r) {
|
|
|
2775
3067
|
openDecisions: d.decisionNeeds.filter((n) => n.state === "open").length || void 0,
|
|
2776
3068
|
latest: "about" in d ? d.about : void 0
|
|
2777
3069
|
})),
|
|
2778
|
-
|
|
3070
|
+
// YOUR STALLED WORK (#2257): Goals you own with no progress for three days. Named here so an
|
|
3071
|
+
// agent that reads its replies also sees what it left, and says what to do about each.
|
|
3072
|
+
...stalled.length ? { stalled } : {},
|
|
3073
|
+
// OTHER AGENTS' STALLED WORK (owner, 2026-09-23): any agent may take it over and close it.
|
|
3074
|
+
...others.length ? { stalledOthers: others } : {},
|
|
3075
|
+
next: [
|
|
3076
|
+
deliveries.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting.",
|
|
3077
|
+
...stalled.length ? [`${stalled.length} of your Goals have had no progress for 3 days: update_goal each with progress, finish it, or cancel it.`] : [],
|
|
3078
|
+
...others.length ? [`${others.length} of other agents' Goals have gone quiet for 3 days: claim_goal({goalId}) takes one over, then finish or cancel it with update_goal.`] : []
|
|
3079
|
+
].join(" ")
|
|
2779
3080
|
};
|
|
2780
3081
|
}
|
|
2781
3082
|
async function runTool(name, args, opts) {
|
|
2782
3083
|
const { waits, signal, ...client } = opts;
|
|
2783
3084
|
const input = args ?? {};
|
|
2784
3085
|
switch (name) {
|
|
2785
|
-
case "contact":
|
|
2786
|
-
|
|
3086
|
+
case "contact": {
|
|
3087
|
+
const sent = await contact(input, { ...client, signal, waits });
|
|
3088
|
+
return "recorded" in sent ? sent : deliveryView(sent);
|
|
3089
|
+
}
|
|
2787
3090
|
case "check_replies":
|
|
2788
3091
|
CheckRepliesSchema.parse(input);
|
|
2789
3092
|
return repliesView(await checkReplies(client));
|
|
2790
|
-
case "get_thread": {
|
|
2791
|
-
const { parentId } = GetThreadSchema.parse(input);
|
|
2792
|
-
return threadView(await getThread(parentId, client));
|
|
2793
|
-
}
|
|
2794
|
-
case "search_threads": {
|
|
2795
|
-
const { q } = SearchThreadsSchema.parse(input);
|
|
2796
|
-
return searchThreads(q, client);
|
|
2797
|
-
}
|
|
2798
3093
|
case "create_goal": {
|
|
2799
3094
|
const goal = CreateGoalToolSchema.parse(input);
|
|
2800
3095
|
return createGoal({ ...goal, idempotencyKey: goal.idempotencyKey ?? randomUUID4() }, client);
|
|
@@ -2892,7 +3187,8 @@ function subscribeRealtime(args) {
|
|
|
2892
3187
|
};
|
|
2893
3188
|
}
|
|
2894
3189
|
async function subscribeWake(onNudge, opts = {}) {
|
|
2895
|
-
const
|
|
3190
|
+
const { release = true, ...client } = opts;
|
|
3191
|
+
const cfg = await registerDelivery("self_hosted", client);
|
|
2896
3192
|
const { url, anonKey } = cfg.realtime;
|
|
2897
3193
|
const channel = wakeChannel(cfg.tokenId);
|
|
2898
3194
|
const ch = subscribeRealtime({
|
|
@@ -2907,18 +3203,42 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
2907
3203
|
channel,
|
|
2908
3204
|
close: async () => {
|
|
2909
3205
|
ch.close();
|
|
2910
|
-
await registerDelivery("poll",
|
|
3206
|
+
if (release) await registerDelivery("poll", client).catch(() => {
|
|
2911
3207
|
});
|
|
2912
3208
|
}
|
|
2913
3209
|
};
|
|
2914
3210
|
}
|
|
3211
|
+
function parseDue(detail, now = /* @__PURE__ */ new Date()) {
|
|
3212
|
+
const tz = machineZone();
|
|
3213
|
+
if (!tz) return null;
|
|
3214
|
+
return parseDelay(detail, tz, now.getTime(), "ring");
|
|
3215
|
+
}
|
|
3216
|
+
function machineZone() {
|
|
3217
|
+
try {
|
|
3218
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
|
|
3219
|
+
} catch {
|
|
3220
|
+
return null;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
2915
3223
|
|
|
2916
3224
|
export {
|
|
3225
|
+
BACKEND_URL,
|
|
3226
|
+
NETWORK_MSG,
|
|
3227
|
+
sessionId,
|
|
2917
3228
|
isNetworkError,
|
|
3229
|
+
reach,
|
|
3230
|
+
reachAs,
|
|
3231
|
+
AGENT_TOOLS,
|
|
3232
|
+
AGENT_TOOL_NAMES,
|
|
3233
|
+
NotifyRequestSchema,
|
|
2918
3234
|
sessionSlot,
|
|
2919
3235
|
agentName,
|
|
3236
|
+
PROTO,
|
|
2920
3237
|
TOKEN_PATH,
|
|
3238
|
+
KEY_PATH,
|
|
2921
3239
|
saveToken,
|
|
3240
|
+
updateSlot,
|
|
3241
|
+
slotIdentity,
|
|
2922
3242
|
sleep,
|
|
2923
3243
|
readToken,
|
|
2924
3244
|
listSlots,
|
|
@@ -2928,14 +3248,31 @@ export {
|
|
|
2928
3248
|
requestCode,
|
|
2929
3249
|
pollToken,
|
|
2930
3250
|
UnpairedError,
|
|
2931
|
-
|
|
3251
|
+
ApiError,
|
|
2932
3252
|
overrideToken,
|
|
3253
|
+
authToken,
|
|
3254
|
+
createGoal,
|
|
3255
|
+
claimGoal,
|
|
3256
|
+
getGoal,
|
|
3257
|
+
updateGoal,
|
|
3258
|
+
AWAIT_WINDOW_MS,
|
|
2933
3259
|
hatch,
|
|
2934
3260
|
whoAmI,
|
|
2935
3261
|
setIdentity,
|
|
3262
|
+
claimSessions,
|
|
2936
3263
|
heartbeat,
|
|
3264
|
+
registerDelivery,
|
|
3265
|
+
listNotes,
|
|
3266
|
+
listConnections,
|
|
3267
|
+
submitTriage,
|
|
3268
|
+
getTriage,
|
|
3269
|
+
acceptTriage,
|
|
3270
|
+
dismissTriage,
|
|
3271
|
+
repoFromRemote,
|
|
3272
|
+
currentRepo,
|
|
2937
3273
|
contact,
|
|
2938
3274
|
checkReplies,
|
|
2939
3275
|
runTool,
|
|
2940
|
-
subscribeWake
|
|
3276
|
+
subscribeWake,
|
|
3277
|
+
parseDue
|
|
2941
3278
|
};
|