@paigy/mcp 0.34.0 → 0.36.0
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/dist/{chunk-X42EUXML.js → chunk-PRC6GKRN.js} +2113 -647
- package/dist/chunk-UG2FARVV.js +54 -0
- package/dist/chunk-VWVZLPZD.js +143 -0
- package/dist/{chunk-45CIZLCJ.js → chunk-WNE6STMA.js} +668 -463
- package/dist/chunk-XIPO3U6I.js +309 -0
- package/dist/enable.js +3 -2
- package/dist/index.js +110 -349
- package/dist/listen.js +31 -34
- package/dist/onboard.js +59 -42
- package/dist/statusline.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-G2WR7FCJ.js +0 -177
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// ../../packages/sdk/dist/index.js
|
|
2
2
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
|
+
import { z as z2 } from "zod";
|
|
4
5
|
import { z } from "zod";
|
|
6
|
+
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
7
|
+
import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
8
|
+
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
|
|
9
|
+
import { createHash } from "crypto";
|
|
5
10
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
6
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
7
11
|
import { homedir } from "os";
|
|
8
12
|
import { join } from "path";
|
|
9
13
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
@@ -336,17 +340,17 @@ var require_nacl_fast = __commonJS({
|
|
|
336
340
|
}
|
|
337
341
|
var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]);
|
|
338
342
|
function crypto_stream_salsa20_xor(c, cpos, m, mpos, b, n, k) {
|
|
339
|
-
var
|
|
343
|
+
var z3 = new Uint8Array(16), x = new Uint8Array(64);
|
|
340
344
|
var u, i;
|
|
341
|
-
for (i = 0; i < 16; i++)
|
|
342
|
-
for (i = 0; i < 8; i++)
|
|
345
|
+
for (i = 0; i < 16; i++) z3[i] = 0;
|
|
346
|
+
for (i = 0; i < 8; i++) z3[i] = n[i];
|
|
343
347
|
while (b >= 64) {
|
|
344
|
-
crypto_core_salsa20(x,
|
|
348
|
+
crypto_core_salsa20(x, z3, k, sigma);
|
|
345
349
|
for (i = 0; i < 64; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
|
346
350
|
u = 1;
|
|
347
351
|
for (i = 8; i < 16; i++) {
|
|
348
|
-
u = u + (
|
|
349
|
-
|
|
352
|
+
u = u + (z3[i] & 255) | 0;
|
|
353
|
+
z3[i] = u & 255;
|
|
350
354
|
u >>>= 8;
|
|
351
355
|
}
|
|
352
356
|
b -= 64;
|
|
@@ -354,30 +358,30 @@ var require_nacl_fast = __commonJS({
|
|
|
354
358
|
mpos += 64;
|
|
355
359
|
}
|
|
356
360
|
if (b > 0) {
|
|
357
|
-
crypto_core_salsa20(x,
|
|
361
|
+
crypto_core_salsa20(x, z3, k, sigma);
|
|
358
362
|
for (i = 0; i < b; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
|
359
363
|
}
|
|
360
364
|
return 0;
|
|
361
365
|
}
|
|
362
366
|
function crypto_stream_salsa20(c, cpos, b, n, k) {
|
|
363
|
-
var
|
|
367
|
+
var z3 = new Uint8Array(16), x = new Uint8Array(64);
|
|
364
368
|
var u, i;
|
|
365
|
-
for (i = 0; i < 16; i++)
|
|
366
|
-
for (i = 0; i < 8; i++)
|
|
369
|
+
for (i = 0; i < 16; i++) z3[i] = 0;
|
|
370
|
+
for (i = 0; i < 8; i++) z3[i] = n[i];
|
|
367
371
|
while (b >= 64) {
|
|
368
|
-
crypto_core_salsa20(x,
|
|
372
|
+
crypto_core_salsa20(x, z3, k, sigma);
|
|
369
373
|
for (i = 0; i < 64; i++) c[cpos + i] = x[i];
|
|
370
374
|
u = 1;
|
|
371
375
|
for (i = 8; i < 16; i++) {
|
|
372
|
-
u = u + (
|
|
373
|
-
|
|
376
|
+
u = u + (z3[i] & 255) | 0;
|
|
377
|
+
z3[i] = u & 255;
|
|
374
378
|
u >>>= 8;
|
|
375
379
|
}
|
|
376
380
|
b -= 64;
|
|
377
381
|
cpos += 64;
|
|
378
382
|
}
|
|
379
383
|
if (b > 0) {
|
|
380
|
-
crypto_core_salsa20(x,
|
|
384
|
+
crypto_core_salsa20(x, z3, k, sigma);
|
|
381
385
|
for (i = 0; i < b; i++) c[cpos + i] = x[i];
|
|
382
386
|
}
|
|
383
387
|
return 0;
|
|
@@ -1257,12 +1261,12 @@ var require_nacl_fast = __commonJS({
|
|
|
1257
1261
|
for (a = 0; a < 16; a++) o[a] = c[a];
|
|
1258
1262
|
}
|
|
1259
1263
|
function crypto_scalarmult(q, n, p) {
|
|
1260
|
-
var
|
|
1264
|
+
var z3 = new Uint8Array(32);
|
|
1261
1265
|
var x = new Float64Array(80), r, i;
|
|
1262
1266
|
var a = gf(), b = gf(), c = gf(), d = gf(), e = gf(), f = gf();
|
|
1263
|
-
for (i = 0; i < 31; i++)
|
|
1264
|
-
|
|
1265
|
-
|
|
1267
|
+
for (i = 0; i < 31; i++) z3[i] = n[i];
|
|
1268
|
+
z3[31] = n[31] & 127 | 64;
|
|
1269
|
+
z3[0] &= 248;
|
|
1266
1270
|
unpack25519(x, p);
|
|
1267
1271
|
for (i = 0; i < 16; i++) {
|
|
1268
1272
|
b[i] = x[i];
|
|
@@ -1270,7 +1274,7 @@ var require_nacl_fast = __commonJS({
|
|
|
1270
1274
|
}
|
|
1271
1275
|
a[0] = d[0] = 1;
|
|
1272
1276
|
for (i = 254; i >= 0; --i) {
|
|
1273
|
-
r =
|
|
1277
|
+
r = z3[i >>> 3] >>> (i & 7) & 1;
|
|
1274
1278
|
sel25519(a, b, r);
|
|
1275
1279
|
sel25519(c, d, r);
|
|
1276
1280
|
A(e, a, c);
|
|
@@ -2271,6 +2275,12 @@ async function proxy() {
|
|
|
2271
2275
|
}
|
|
2272
2276
|
var INSTANCE_ID = randomUUID();
|
|
2273
2277
|
var SESSION_ID = process.env.PAIGY_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID ?? INSTANCE_ID;
|
|
2278
|
+
function sessionId() {
|
|
2279
|
+
return SESSION_ID;
|
|
2280
|
+
}
|
|
2281
|
+
function isNetworkError(e) {
|
|
2282
|
+
return e instanceof Error && e.message.startsWith(NETWORK_MSG);
|
|
2283
|
+
}
|
|
2274
2284
|
async function reach(url, init) {
|
|
2275
2285
|
try {
|
|
2276
2286
|
const headers = {
|
|
@@ -2283,73 +2293,1300 @@ async function reach(url, init) {
|
|
|
2283
2293
|
throw new Error(`${NETWORK_MSG} (${e?.message ?? String(e)})`);
|
|
2284
2294
|
}
|
|
2285
2295
|
}
|
|
2286
|
-
var
|
|
2287
|
-
var
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2296
|
+
var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
2297
|
+
var defaultOptions = {
|
|
2298
|
+
name: void 0,
|
|
2299
|
+
$refStrategy: "root",
|
|
2300
|
+
basePath: ["#"],
|
|
2301
|
+
effectStrategy: "input",
|
|
2302
|
+
pipeStrategy: "all",
|
|
2303
|
+
dateStrategy: "format:date-time",
|
|
2304
|
+
mapStrategy: "entries",
|
|
2305
|
+
removeAdditionalStrategy: "passthrough",
|
|
2306
|
+
allowedAdditionalProperties: true,
|
|
2307
|
+
rejectedAdditionalProperties: false,
|
|
2308
|
+
definitionPath: "definitions",
|
|
2309
|
+
target: "jsonSchema7",
|
|
2310
|
+
strictUnions: false,
|
|
2311
|
+
definitions: {},
|
|
2312
|
+
errorMessages: false,
|
|
2313
|
+
markdownDescription: false,
|
|
2314
|
+
patternStrategy: "escape",
|
|
2315
|
+
applyRegexFlags: false,
|
|
2316
|
+
emailStrategy: "format:email",
|
|
2317
|
+
base64Strategy: "contentEncoding:base64",
|
|
2318
|
+
nameStrategy: "ref",
|
|
2319
|
+
openAiAnyTypeName: "OpenAiAnyType"
|
|
2320
|
+
};
|
|
2321
|
+
var getDefaultOptions = (options) => typeof options === "string" ? {
|
|
2322
|
+
...defaultOptions,
|
|
2323
|
+
name: options
|
|
2324
|
+
} : {
|
|
2325
|
+
...defaultOptions,
|
|
2326
|
+
...options
|
|
2327
|
+
};
|
|
2328
|
+
var getRefs = (options) => {
|
|
2329
|
+
const _options = getDefaultOptions(options);
|
|
2330
|
+
const currentPath = _options.name !== void 0 ? [..._options.basePath, _options.definitionPath, _options.name] : _options.basePath;
|
|
2331
|
+
return {
|
|
2332
|
+
..._options,
|
|
2333
|
+
flags: { hasReferencedOpenAiAnyType: false },
|
|
2334
|
+
currentPath,
|
|
2335
|
+
propertyPath: void 0,
|
|
2336
|
+
seen: new Map(Object.entries(_options.definitions).map(([name, def]) => [
|
|
2337
|
+
def._def,
|
|
2338
|
+
{
|
|
2339
|
+
def: def._def,
|
|
2340
|
+
path: [..._options.basePath, _options.definitionPath, name],
|
|
2341
|
+
// Resolution of references will be forced even though seen, so it's ok that the schema is undefined here for now.
|
|
2342
|
+
jsonSchema: void 0
|
|
2343
|
+
}
|
|
2344
|
+
]))
|
|
2345
|
+
};
|
|
2346
|
+
};
|
|
2347
|
+
function addErrorMessage(res, key, errorMessage, refs) {
|
|
2348
|
+
if (!refs?.errorMessages)
|
|
2349
|
+
return;
|
|
2350
|
+
if (errorMessage) {
|
|
2351
|
+
res.errorMessage = {
|
|
2352
|
+
...res.errorMessage,
|
|
2353
|
+
[key]: errorMessage
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
|
|
2358
|
+
res[key] = value;
|
|
2359
|
+
addErrorMessage(res, key, errorMessage, refs);
|
|
2360
|
+
}
|
|
2361
|
+
var getRelativePath = (pathA, pathB) => {
|
|
2362
|
+
let i = 0;
|
|
2363
|
+
for (; i < pathA.length && i < pathB.length; i++) {
|
|
2364
|
+
if (pathA[i] !== pathB[i])
|
|
2365
|
+
break;
|
|
2366
|
+
}
|
|
2367
|
+
return [(pathA.length - i).toString(), ...pathB.slice(i)].join("/");
|
|
2368
|
+
};
|
|
2369
|
+
function parseAnyDef(refs) {
|
|
2370
|
+
if (refs.target !== "openAi") {
|
|
2371
|
+
return {};
|
|
2372
|
+
}
|
|
2373
|
+
const anyDefinitionPath = [
|
|
2374
|
+
...refs.basePath,
|
|
2375
|
+
refs.definitionPath,
|
|
2376
|
+
refs.openAiAnyTypeName
|
|
2377
|
+
];
|
|
2378
|
+
refs.flags.hasReferencedOpenAiAnyType = true;
|
|
2379
|
+
return {
|
|
2380
|
+
$ref: refs.$refStrategy === "relative" ? getRelativePath(anyDefinitionPath, refs.currentPath) : anyDefinitionPath.join("/")
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
function parseArrayDef(def, refs) {
|
|
2384
|
+
const res = {
|
|
2385
|
+
type: "array"
|
|
2386
|
+
};
|
|
2387
|
+
if (def.type?._def && def.type?._def?.typeName !== ZodFirstPartyTypeKind.ZodAny) {
|
|
2388
|
+
res.items = parseDef(def.type._def, {
|
|
2389
|
+
...refs,
|
|
2390
|
+
currentPath: [...refs.currentPath, "items"]
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
if (def.minLength) {
|
|
2394
|
+
setResponseValueAndErrors(res, "minItems", def.minLength.value, def.minLength.message, refs);
|
|
2395
|
+
}
|
|
2396
|
+
if (def.maxLength) {
|
|
2397
|
+
setResponseValueAndErrors(res, "maxItems", def.maxLength.value, def.maxLength.message, refs);
|
|
2398
|
+
}
|
|
2399
|
+
if (def.exactLength) {
|
|
2400
|
+
setResponseValueAndErrors(res, "minItems", def.exactLength.value, def.exactLength.message, refs);
|
|
2401
|
+
setResponseValueAndErrors(res, "maxItems", def.exactLength.value, def.exactLength.message, refs);
|
|
2402
|
+
}
|
|
2403
|
+
return res;
|
|
2404
|
+
}
|
|
2405
|
+
function parseBigintDef(def, refs) {
|
|
2406
|
+
const res = {
|
|
2407
|
+
type: "integer",
|
|
2408
|
+
format: "int64"
|
|
2409
|
+
};
|
|
2410
|
+
if (!def.checks)
|
|
2411
|
+
return res;
|
|
2412
|
+
for (const check of def.checks) {
|
|
2413
|
+
switch (check.kind) {
|
|
2414
|
+
case "min":
|
|
2415
|
+
if (refs.target === "jsonSchema7") {
|
|
2416
|
+
if (check.inclusive) {
|
|
2417
|
+
setResponseValueAndErrors(res, "minimum", check.value, check.message, refs);
|
|
2418
|
+
} else {
|
|
2419
|
+
setResponseValueAndErrors(res, "exclusiveMinimum", check.value, check.message, refs);
|
|
2420
|
+
}
|
|
2421
|
+
} else {
|
|
2422
|
+
if (!check.inclusive) {
|
|
2423
|
+
res.exclusiveMinimum = true;
|
|
2424
|
+
}
|
|
2425
|
+
setResponseValueAndErrors(res, "minimum", check.value, check.message, refs);
|
|
2426
|
+
}
|
|
2427
|
+
break;
|
|
2428
|
+
case "max":
|
|
2429
|
+
if (refs.target === "jsonSchema7") {
|
|
2430
|
+
if (check.inclusive) {
|
|
2431
|
+
setResponseValueAndErrors(res, "maximum", check.value, check.message, refs);
|
|
2432
|
+
} else {
|
|
2433
|
+
setResponseValueAndErrors(res, "exclusiveMaximum", check.value, check.message, refs);
|
|
2434
|
+
}
|
|
2435
|
+
} else {
|
|
2436
|
+
if (!check.inclusive) {
|
|
2437
|
+
res.exclusiveMaximum = true;
|
|
2438
|
+
}
|
|
2439
|
+
setResponseValueAndErrors(res, "maximum", check.value, check.message, refs);
|
|
2440
|
+
}
|
|
2441
|
+
break;
|
|
2442
|
+
case "multipleOf":
|
|
2443
|
+
setResponseValueAndErrors(res, "multipleOf", check.value, check.message, refs);
|
|
2444
|
+
break;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return res;
|
|
2448
|
+
}
|
|
2449
|
+
function parseBooleanDef() {
|
|
2450
|
+
return {
|
|
2451
|
+
type: "boolean"
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
function parseBrandedDef(_def, refs) {
|
|
2455
|
+
return parseDef(_def.type._def, refs);
|
|
2456
|
+
}
|
|
2457
|
+
var parseCatchDef = (def, refs) => {
|
|
2458
|
+
return parseDef(def.innerType._def, refs);
|
|
2459
|
+
};
|
|
2460
|
+
function parseDateDef(def, refs, overrideDateStrategy) {
|
|
2461
|
+
const strategy = overrideDateStrategy ?? refs.dateStrategy;
|
|
2462
|
+
if (Array.isArray(strategy)) {
|
|
2463
|
+
return {
|
|
2464
|
+
anyOf: strategy.map((item, i) => parseDateDef(def, refs, item))
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
switch (strategy) {
|
|
2468
|
+
case "string":
|
|
2469
|
+
case "format:date-time":
|
|
2470
|
+
return {
|
|
2471
|
+
type: "string",
|
|
2472
|
+
format: "date-time"
|
|
2473
|
+
};
|
|
2474
|
+
case "format:date":
|
|
2475
|
+
return {
|
|
2476
|
+
type: "string",
|
|
2477
|
+
format: "date"
|
|
2478
|
+
};
|
|
2479
|
+
case "integer":
|
|
2480
|
+
return integerDateParser(def, refs);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
var integerDateParser = (def, refs) => {
|
|
2484
|
+
const res = {
|
|
2485
|
+
type: "integer",
|
|
2486
|
+
format: "unix-time"
|
|
2487
|
+
};
|
|
2488
|
+
if (refs.target === "openApi3") {
|
|
2489
|
+
return res;
|
|
2490
|
+
}
|
|
2491
|
+
for (const check of def.checks) {
|
|
2492
|
+
switch (check.kind) {
|
|
2493
|
+
case "min":
|
|
2494
|
+
setResponseValueAndErrors(
|
|
2495
|
+
res,
|
|
2496
|
+
"minimum",
|
|
2497
|
+
check.value,
|
|
2498
|
+
// This is in milliseconds
|
|
2499
|
+
check.message,
|
|
2500
|
+
refs
|
|
2501
|
+
);
|
|
2502
|
+
break;
|
|
2503
|
+
case "max":
|
|
2504
|
+
setResponseValueAndErrors(
|
|
2505
|
+
res,
|
|
2506
|
+
"maximum",
|
|
2507
|
+
check.value,
|
|
2508
|
+
// This is in milliseconds
|
|
2509
|
+
check.message,
|
|
2510
|
+
refs
|
|
2511
|
+
);
|
|
2512
|
+
break;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
return res;
|
|
2516
|
+
};
|
|
2517
|
+
function parseDefaultDef(_def, refs) {
|
|
2518
|
+
return {
|
|
2519
|
+
...parseDef(_def.innerType._def, refs),
|
|
2520
|
+
default: _def.defaultValue()
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
function parseEffectsDef(_def, refs) {
|
|
2524
|
+
return refs.effectStrategy === "input" ? parseDef(_def.schema._def, refs) : parseAnyDef(refs);
|
|
2525
|
+
}
|
|
2526
|
+
function parseEnumDef(def) {
|
|
2527
|
+
return {
|
|
2528
|
+
type: "string",
|
|
2529
|
+
enum: Array.from(def.values)
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
var isJsonSchema7AllOfType = (type) => {
|
|
2533
|
+
if ("type" in type && type.type === "string")
|
|
2534
|
+
return false;
|
|
2535
|
+
return "allOf" in type;
|
|
2536
|
+
};
|
|
2537
|
+
function parseIntersectionDef(def, refs) {
|
|
2538
|
+
const allOf = [
|
|
2539
|
+
parseDef(def.left._def, {
|
|
2540
|
+
...refs,
|
|
2541
|
+
currentPath: [...refs.currentPath, "allOf", "0"]
|
|
2542
|
+
}),
|
|
2543
|
+
parseDef(def.right._def, {
|
|
2544
|
+
...refs,
|
|
2545
|
+
currentPath: [...refs.currentPath, "allOf", "1"]
|
|
2546
|
+
})
|
|
2547
|
+
].filter((x) => !!x);
|
|
2548
|
+
let unevaluatedProperties = refs.target === "jsonSchema2019-09" ? { unevaluatedProperties: false } : void 0;
|
|
2549
|
+
const mergedAllOf = [];
|
|
2550
|
+
allOf.forEach((schema) => {
|
|
2551
|
+
if (isJsonSchema7AllOfType(schema)) {
|
|
2552
|
+
mergedAllOf.push(...schema.allOf);
|
|
2553
|
+
if (schema.unevaluatedProperties === void 0) {
|
|
2554
|
+
unevaluatedProperties = void 0;
|
|
2555
|
+
}
|
|
2556
|
+
} else {
|
|
2557
|
+
let nestedSchema = schema;
|
|
2558
|
+
if ("additionalProperties" in schema && schema.additionalProperties === false) {
|
|
2559
|
+
const { additionalProperties, ...rest } = schema;
|
|
2560
|
+
nestedSchema = rest;
|
|
2561
|
+
} else {
|
|
2562
|
+
unevaluatedProperties = void 0;
|
|
2563
|
+
}
|
|
2564
|
+
mergedAllOf.push(nestedSchema);
|
|
2565
|
+
}
|
|
2566
|
+
});
|
|
2567
|
+
return mergedAllOf.length ? {
|
|
2568
|
+
allOf: mergedAllOf,
|
|
2569
|
+
...unevaluatedProperties
|
|
2570
|
+
} : void 0;
|
|
2571
|
+
}
|
|
2572
|
+
function parseLiteralDef(def, refs) {
|
|
2573
|
+
const parsedType = typeof def.value;
|
|
2574
|
+
if (parsedType !== "bigint" && parsedType !== "number" && parsedType !== "boolean" && parsedType !== "string") {
|
|
2575
|
+
return {
|
|
2576
|
+
type: Array.isArray(def.value) ? "array" : "object"
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
if (refs.target === "openApi3") {
|
|
2580
|
+
return {
|
|
2581
|
+
type: parsedType === "bigint" ? "integer" : parsedType,
|
|
2582
|
+
enum: [def.value]
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
type: parsedType === "bigint" ? "integer" : parsedType,
|
|
2587
|
+
const: def.value
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
var emojiRegex = void 0;
|
|
2591
|
+
var zodPatterns = {
|
|
2592
|
+
/**
|
|
2593
|
+
* `c` was changed to `[cC]` to replicate /i flag
|
|
2594
|
+
*/
|
|
2595
|
+
cuid: /^[cC][^\s-]{8,}$/,
|
|
2596
|
+
cuid2: /^[0-9a-z]+$/,
|
|
2597
|
+
ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/,
|
|
2598
|
+
/**
|
|
2599
|
+
* `a-z` was added to replicate /i flag
|
|
2600
|
+
*/
|
|
2601
|
+
email: /^(?!\.)(?!.*\.\.)([a-zA-Z0-9_'+\-\.]*)[a-zA-Z0-9_+-]@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/,
|
|
2602
|
+
/**
|
|
2603
|
+
* Constructed a valid Unicode RegExp
|
|
2604
|
+
*
|
|
2605
|
+
* Lazily instantiate since this type of regex isn't supported
|
|
2606
|
+
* in all envs (e.g. React Native).
|
|
2607
|
+
*
|
|
2608
|
+
* See:
|
|
2609
|
+
* https://github.com/colinhacks/zod/issues/2433
|
|
2610
|
+
* Fix in Zod:
|
|
2611
|
+
* https://github.com/colinhacks/zod/commit/9340fd51e48576a75adc919bff65dbc4a5d4c99b
|
|
2612
|
+
*/
|
|
2613
|
+
emoji: () => {
|
|
2614
|
+
if (emojiRegex === void 0) {
|
|
2615
|
+
emojiRegex = RegExp("^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$", "u");
|
|
2616
|
+
}
|
|
2617
|
+
return emojiRegex;
|
|
2618
|
+
},
|
|
2619
|
+
/**
|
|
2620
|
+
* Unused
|
|
2621
|
+
*/
|
|
2622
|
+
uuid: /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/,
|
|
2623
|
+
/**
|
|
2624
|
+
* Unused
|
|
2625
|
+
*/
|
|
2626
|
+
ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,
|
|
2627
|
+
ipv4Cidr: /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/,
|
|
2628
|
+
/**
|
|
2629
|
+
* Unused
|
|
2630
|
+
*/
|
|
2631
|
+
ipv6: /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/,
|
|
2632
|
+
ipv6Cidr: /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,
|
|
2633
|
+
base64: /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,
|
|
2634
|
+
base64url: /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/,
|
|
2635
|
+
nanoid: /^[a-zA-Z0-9_-]{21}$/,
|
|
2636
|
+
jwt: /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/
|
|
2637
|
+
};
|
|
2638
|
+
function parseStringDef(def, refs) {
|
|
2639
|
+
const res = {
|
|
2640
|
+
type: "string"
|
|
2641
|
+
};
|
|
2642
|
+
if (def.checks) {
|
|
2643
|
+
for (const check of def.checks) {
|
|
2644
|
+
switch (check.kind) {
|
|
2645
|
+
case "min":
|
|
2646
|
+
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength, check.value) : check.value, check.message, refs);
|
|
2647
|
+
break;
|
|
2648
|
+
case "max":
|
|
2649
|
+
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength, check.value) : check.value, check.message, refs);
|
|
2650
|
+
break;
|
|
2651
|
+
case "email":
|
|
2652
|
+
switch (refs.emailStrategy) {
|
|
2653
|
+
case "format:email":
|
|
2654
|
+
addFormat(res, "email", check.message, refs);
|
|
2655
|
+
break;
|
|
2656
|
+
case "format:idn-email":
|
|
2657
|
+
addFormat(res, "idn-email", check.message, refs);
|
|
2658
|
+
break;
|
|
2659
|
+
case "pattern:zod":
|
|
2660
|
+
addPattern(res, zodPatterns.email, check.message, refs);
|
|
2661
|
+
break;
|
|
2662
|
+
}
|
|
2663
|
+
break;
|
|
2664
|
+
case "url":
|
|
2665
|
+
addFormat(res, "uri", check.message, refs);
|
|
2666
|
+
break;
|
|
2667
|
+
case "uuid":
|
|
2668
|
+
addFormat(res, "uuid", check.message, refs);
|
|
2669
|
+
break;
|
|
2670
|
+
case "regex":
|
|
2671
|
+
addPattern(res, check.regex, check.message, refs);
|
|
2672
|
+
break;
|
|
2673
|
+
case "cuid":
|
|
2674
|
+
addPattern(res, zodPatterns.cuid, check.message, refs);
|
|
2675
|
+
break;
|
|
2676
|
+
case "cuid2":
|
|
2677
|
+
addPattern(res, zodPatterns.cuid2, check.message, refs);
|
|
2678
|
+
break;
|
|
2679
|
+
case "startsWith":
|
|
2680
|
+
addPattern(res, RegExp(`^${escapeLiteralCheckValue(check.value, refs)}`), check.message, refs);
|
|
2681
|
+
break;
|
|
2682
|
+
case "endsWith":
|
|
2683
|
+
addPattern(res, RegExp(`${escapeLiteralCheckValue(check.value, refs)}$`), check.message, refs);
|
|
2684
|
+
break;
|
|
2685
|
+
case "datetime":
|
|
2686
|
+
addFormat(res, "date-time", check.message, refs);
|
|
2687
|
+
break;
|
|
2688
|
+
case "date":
|
|
2689
|
+
addFormat(res, "date", check.message, refs);
|
|
2690
|
+
break;
|
|
2691
|
+
case "time":
|
|
2692
|
+
addFormat(res, "time", check.message, refs);
|
|
2693
|
+
break;
|
|
2694
|
+
case "duration":
|
|
2695
|
+
addFormat(res, "duration", check.message, refs);
|
|
2696
|
+
break;
|
|
2697
|
+
case "length":
|
|
2698
|
+
setResponseValueAndErrors(res, "minLength", typeof res.minLength === "number" ? Math.max(res.minLength, check.value) : check.value, check.message, refs);
|
|
2699
|
+
setResponseValueAndErrors(res, "maxLength", typeof res.maxLength === "number" ? Math.min(res.maxLength, check.value) : check.value, check.message, refs);
|
|
2700
|
+
break;
|
|
2701
|
+
case "includes": {
|
|
2702
|
+
addPattern(res, RegExp(escapeLiteralCheckValue(check.value, refs)), check.message, refs);
|
|
2703
|
+
break;
|
|
2704
|
+
}
|
|
2705
|
+
case "ip": {
|
|
2706
|
+
if (check.version !== "v6") {
|
|
2707
|
+
addFormat(res, "ipv4", check.message, refs);
|
|
2708
|
+
}
|
|
2709
|
+
if (check.version !== "v4") {
|
|
2710
|
+
addFormat(res, "ipv6", check.message, refs);
|
|
2711
|
+
}
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2714
|
+
case "base64url":
|
|
2715
|
+
addPattern(res, zodPatterns.base64url, check.message, refs);
|
|
2716
|
+
break;
|
|
2717
|
+
case "jwt":
|
|
2718
|
+
addPattern(res, zodPatterns.jwt, check.message, refs);
|
|
2719
|
+
break;
|
|
2720
|
+
case "cidr": {
|
|
2721
|
+
if (check.version !== "v6") {
|
|
2722
|
+
addPattern(res, zodPatterns.ipv4Cidr, check.message, refs);
|
|
2723
|
+
}
|
|
2724
|
+
if (check.version !== "v4") {
|
|
2725
|
+
addPattern(res, zodPatterns.ipv6Cidr, check.message, refs);
|
|
2726
|
+
}
|
|
2727
|
+
break;
|
|
2728
|
+
}
|
|
2729
|
+
case "emoji":
|
|
2730
|
+
addPattern(res, zodPatterns.emoji(), check.message, refs);
|
|
2731
|
+
break;
|
|
2732
|
+
case "ulid": {
|
|
2733
|
+
addPattern(res, zodPatterns.ulid, check.message, refs);
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
2736
|
+
case "base64": {
|
|
2737
|
+
switch (refs.base64Strategy) {
|
|
2738
|
+
case "format:binary": {
|
|
2739
|
+
addFormat(res, "binary", check.message, refs);
|
|
2740
|
+
break;
|
|
2741
|
+
}
|
|
2742
|
+
case "contentEncoding:base64": {
|
|
2743
|
+
setResponseValueAndErrors(res, "contentEncoding", "base64", check.message, refs);
|
|
2744
|
+
break;
|
|
2745
|
+
}
|
|
2746
|
+
case "pattern:zod": {
|
|
2747
|
+
addPattern(res, zodPatterns.base64, check.message, refs);
|
|
2748
|
+
break;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
break;
|
|
2752
|
+
}
|
|
2753
|
+
case "nanoid": {
|
|
2754
|
+
addPattern(res, zodPatterns.nanoid, check.message, refs);
|
|
2755
|
+
}
|
|
2756
|
+
case "toLowerCase":
|
|
2757
|
+
case "toUpperCase":
|
|
2758
|
+
case "trim":
|
|
2759
|
+
break;
|
|
2760
|
+
default:
|
|
2761
|
+
/* @__PURE__ */ ((_) => {
|
|
2762
|
+
})(check);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
return res;
|
|
2767
|
+
}
|
|
2768
|
+
function escapeLiteralCheckValue(literal, refs) {
|
|
2769
|
+
return refs.patternStrategy === "escape" ? escapeNonAlphaNumeric(literal) : literal;
|
|
2770
|
+
}
|
|
2771
|
+
var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
|
|
2772
|
+
function escapeNonAlphaNumeric(source) {
|
|
2773
|
+
let result = "";
|
|
2774
|
+
for (let i = 0; i < source.length; i++) {
|
|
2775
|
+
if (!ALPHA_NUMERIC.has(source[i])) {
|
|
2776
|
+
result += "\\";
|
|
2777
|
+
}
|
|
2778
|
+
result += source[i];
|
|
2779
|
+
}
|
|
2780
|
+
return result;
|
|
2781
|
+
}
|
|
2782
|
+
function addFormat(schema, value, message, refs) {
|
|
2783
|
+
if (schema.format || schema.anyOf?.some((x) => x.format)) {
|
|
2784
|
+
if (!schema.anyOf) {
|
|
2785
|
+
schema.anyOf = [];
|
|
2786
|
+
}
|
|
2787
|
+
if (schema.format) {
|
|
2788
|
+
schema.anyOf.push({
|
|
2789
|
+
format: schema.format,
|
|
2790
|
+
...schema.errorMessage && refs.errorMessages && {
|
|
2791
|
+
errorMessage: { format: schema.errorMessage.format }
|
|
2792
|
+
}
|
|
2793
|
+
});
|
|
2794
|
+
delete schema.format;
|
|
2795
|
+
if (schema.errorMessage) {
|
|
2796
|
+
delete schema.errorMessage.format;
|
|
2797
|
+
if (Object.keys(schema.errorMessage).length === 0) {
|
|
2798
|
+
delete schema.errorMessage;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
schema.anyOf.push({
|
|
2803
|
+
format: value,
|
|
2804
|
+
...message && refs.errorMessages && { errorMessage: { format: message } }
|
|
2805
|
+
});
|
|
2806
|
+
} else {
|
|
2807
|
+
setResponseValueAndErrors(schema, "format", value, message, refs);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
function addPattern(schema, regex, message, refs) {
|
|
2811
|
+
if (schema.pattern || schema.allOf?.some((x) => x.pattern)) {
|
|
2812
|
+
if (!schema.allOf) {
|
|
2813
|
+
schema.allOf = [];
|
|
2814
|
+
}
|
|
2815
|
+
if (schema.pattern) {
|
|
2816
|
+
schema.allOf.push({
|
|
2817
|
+
pattern: schema.pattern,
|
|
2818
|
+
...schema.errorMessage && refs.errorMessages && {
|
|
2819
|
+
errorMessage: { pattern: schema.errorMessage.pattern }
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
delete schema.pattern;
|
|
2823
|
+
if (schema.errorMessage) {
|
|
2824
|
+
delete schema.errorMessage.pattern;
|
|
2825
|
+
if (Object.keys(schema.errorMessage).length === 0) {
|
|
2826
|
+
delete schema.errorMessage;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
schema.allOf.push({
|
|
2831
|
+
pattern: stringifyRegExpWithFlags(regex, refs),
|
|
2832
|
+
...message && refs.errorMessages && { errorMessage: { pattern: message } }
|
|
2833
|
+
});
|
|
2834
|
+
} else {
|
|
2835
|
+
setResponseValueAndErrors(schema, "pattern", stringifyRegExpWithFlags(regex, refs), message, refs);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function stringifyRegExpWithFlags(regex, refs) {
|
|
2839
|
+
if (!refs.applyRegexFlags || !regex.flags) {
|
|
2840
|
+
return regex.source;
|
|
2841
|
+
}
|
|
2842
|
+
const flags = {
|
|
2843
|
+
i: regex.flags.includes("i"),
|
|
2844
|
+
m: regex.flags.includes("m"),
|
|
2845
|
+
s: regex.flags.includes("s")
|
|
2846
|
+
// `.` matches newlines
|
|
2847
|
+
};
|
|
2848
|
+
const source = flags.i ? regex.source.toLowerCase() : regex.source;
|
|
2849
|
+
let pattern = "";
|
|
2850
|
+
let isEscaped = false;
|
|
2851
|
+
let inCharGroup = false;
|
|
2852
|
+
let inCharRange = false;
|
|
2853
|
+
for (let i = 0; i < source.length; i++) {
|
|
2854
|
+
if (isEscaped) {
|
|
2855
|
+
pattern += source[i];
|
|
2856
|
+
isEscaped = false;
|
|
2857
|
+
continue;
|
|
2858
|
+
}
|
|
2859
|
+
if (flags.i) {
|
|
2860
|
+
if (inCharGroup) {
|
|
2861
|
+
if (source[i].match(/[a-z]/)) {
|
|
2862
|
+
if (inCharRange) {
|
|
2863
|
+
pattern += source[i];
|
|
2864
|
+
pattern += `${source[i - 2]}-${source[i]}`.toUpperCase();
|
|
2865
|
+
inCharRange = false;
|
|
2866
|
+
} else if (source[i + 1] === "-" && source[i + 2]?.match(/[a-z]/)) {
|
|
2867
|
+
pattern += source[i];
|
|
2868
|
+
inCharRange = true;
|
|
2869
|
+
} else {
|
|
2870
|
+
pattern += `${source[i]}${source[i].toUpperCase()}`;
|
|
2871
|
+
}
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
} else if (source[i].match(/[a-z]/)) {
|
|
2875
|
+
pattern += `[${source[i]}${source[i].toUpperCase()}]`;
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
if (flags.m) {
|
|
2880
|
+
if (source[i] === "^") {
|
|
2881
|
+
pattern += `(^|(?<=[\r
|
|
2882
|
+
]))`;
|
|
2883
|
+
continue;
|
|
2884
|
+
} else if (source[i] === "$") {
|
|
2885
|
+
pattern += `($|(?=[\r
|
|
2886
|
+
]))`;
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
if (flags.s && source[i] === ".") {
|
|
2891
|
+
pattern += inCharGroup ? `${source[i]}\r
|
|
2892
|
+
` : `[${source[i]}\r
|
|
2893
|
+
]`;
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
pattern += source[i];
|
|
2897
|
+
if (source[i] === "\\") {
|
|
2898
|
+
isEscaped = true;
|
|
2899
|
+
} else if (inCharGroup && source[i] === "]") {
|
|
2900
|
+
inCharGroup = false;
|
|
2901
|
+
} else if (!inCharGroup && source[i] === "[") {
|
|
2902
|
+
inCharGroup = true;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
try {
|
|
2906
|
+
new RegExp(pattern);
|
|
2907
|
+
} catch {
|
|
2908
|
+
console.warn(`Could not convert regex pattern at ${refs.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`);
|
|
2909
|
+
return regex.source;
|
|
2910
|
+
}
|
|
2911
|
+
return pattern;
|
|
2912
|
+
}
|
|
2913
|
+
function parseRecordDef(def, refs) {
|
|
2914
|
+
if (refs.target === "openAi") {
|
|
2915
|
+
console.warn("Warning: OpenAI may not support records in schemas! Try an array of key-value pairs instead.");
|
|
2916
|
+
}
|
|
2917
|
+
if (refs.target === "openApi3" && def.keyType?._def.typeName === ZodFirstPartyTypeKind2.ZodEnum) {
|
|
2918
|
+
return {
|
|
2919
|
+
type: "object",
|
|
2920
|
+
required: def.keyType._def.values,
|
|
2921
|
+
properties: def.keyType._def.values.reduce((acc, key) => ({
|
|
2922
|
+
...acc,
|
|
2923
|
+
[key]: parseDef(def.valueType._def, {
|
|
2924
|
+
...refs,
|
|
2925
|
+
currentPath: [...refs.currentPath, "properties", key]
|
|
2926
|
+
}) ?? parseAnyDef(refs)
|
|
2927
|
+
}), {}),
|
|
2928
|
+
additionalProperties: refs.rejectedAdditionalProperties
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
const schema = {
|
|
2932
|
+
type: "object",
|
|
2933
|
+
additionalProperties: parseDef(def.valueType._def, {
|
|
2934
|
+
...refs,
|
|
2935
|
+
currentPath: [...refs.currentPath, "additionalProperties"]
|
|
2936
|
+
}) ?? refs.allowedAdditionalProperties
|
|
2937
|
+
};
|
|
2938
|
+
if (refs.target === "openApi3") {
|
|
2939
|
+
return schema;
|
|
2940
|
+
}
|
|
2941
|
+
if (def.keyType?._def.typeName === ZodFirstPartyTypeKind2.ZodString && def.keyType._def.checks?.length) {
|
|
2942
|
+
const { type, ...keyType } = parseStringDef(def.keyType._def, refs);
|
|
2943
|
+
return {
|
|
2944
|
+
...schema,
|
|
2945
|
+
propertyNames: keyType
|
|
2946
|
+
};
|
|
2947
|
+
} else if (def.keyType?._def.typeName === ZodFirstPartyTypeKind2.ZodEnum) {
|
|
2948
|
+
return {
|
|
2949
|
+
...schema,
|
|
2950
|
+
propertyNames: {
|
|
2951
|
+
enum: def.keyType._def.values
|
|
2952
|
+
}
|
|
2953
|
+
};
|
|
2954
|
+
} else if (def.keyType?._def.typeName === ZodFirstPartyTypeKind2.ZodBranded && def.keyType._def.type._def.typeName === ZodFirstPartyTypeKind2.ZodString && def.keyType._def.type._def.checks?.length) {
|
|
2955
|
+
const { type, ...keyType } = parseBrandedDef(def.keyType._def, refs);
|
|
2956
|
+
return {
|
|
2957
|
+
...schema,
|
|
2958
|
+
propertyNames: keyType
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
return schema;
|
|
2962
|
+
}
|
|
2963
|
+
function parseMapDef(def, refs) {
|
|
2964
|
+
if (refs.mapStrategy === "record") {
|
|
2965
|
+
return parseRecordDef(def, refs);
|
|
2966
|
+
}
|
|
2967
|
+
const keys = parseDef(def.keyType._def, {
|
|
2968
|
+
...refs,
|
|
2969
|
+
currentPath: [...refs.currentPath, "items", "items", "0"]
|
|
2970
|
+
}) || parseAnyDef(refs);
|
|
2971
|
+
const values = parseDef(def.valueType._def, {
|
|
2972
|
+
...refs,
|
|
2973
|
+
currentPath: [...refs.currentPath, "items", "items", "1"]
|
|
2974
|
+
}) || parseAnyDef(refs);
|
|
2975
|
+
return {
|
|
2976
|
+
type: "array",
|
|
2977
|
+
maxItems: 125,
|
|
2978
|
+
items: {
|
|
2979
|
+
type: "array",
|
|
2980
|
+
items: [keys, values],
|
|
2981
|
+
minItems: 2,
|
|
2982
|
+
maxItems: 2
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
function parseNativeEnumDef(def) {
|
|
2987
|
+
const object = def.values;
|
|
2988
|
+
const actualKeys = Object.keys(def.values).filter((key) => {
|
|
2989
|
+
return typeof object[object[key]] !== "number";
|
|
2990
|
+
});
|
|
2991
|
+
const actualValues = actualKeys.map((key) => object[key]);
|
|
2992
|
+
const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
|
|
2295
2993
|
return {
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2994
|
+
type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
|
|
2995
|
+
enum: actualValues
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
function parseNeverDef(refs) {
|
|
2999
|
+
return refs.target === "openAi" ? void 0 : {
|
|
3000
|
+
not: parseAnyDef({
|
|
3001
|
+
...refs,
|
|
3002
|
+
currentPath: [...refs.currentPath, "not"]
|
|
3003
|
+
})
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function parseNullDef(refs) {
|
|
3007
|
+
return refs.target === "openApi3" ? {
|
|
3008
|
+
enum: ["null"],
|
|
3009
|
+
nullable: true
|
|
3010
|
+
} : {
|
|
3011
|
+
type: "null"
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
var primitiveMappings = {
|
|
3015
|
+
ZodString: "string",
|
|
3016
|
+
ZodNumber: "number",
|
|
3017
|
+
ZodBigInt: "integer",
|
|
3018
|
+
ZodBoolean: "boolean",
|
|
3019
|
+
ZodNull: "null"
|
|
3020
|
+
};
|
|
3021
|
+
function parseUnionDef(def, refs) {
|
|
3022
|
+
if (refs.target === "openApi3")
|
|
3023
|
+
return asAnyOf(def, refs);
|
|
3024
|
+
const options = def.options instanceof Map ? Array.from(def.options.values()) : def.options;
|
|
3025
|
+
if (options.every((x) => x._def.typeName in primitiveMappings && (!x._def.checks || !x._def.checks.length))) {
|
|
3026
|
+
const types = options.reduce((types2, x) => {
|
|
3027
|
+
const type = primitiveMappings[x._def.typeName];
|
|
3028
|
+
return type && !types2.includes(type) ? [...types2, type] : types2;
|
|
3029
|
+
}, []);
|
|
3030
|
+
return {
|
|
3031
|
+
type: types.length > 1 ? types : types[0]
|
|
3032
|
+
};
|
|
3033
|
+
} else if (options.every((x) => x._def.typeName === "ZodLiteral" && !x.description)) {
|
|
3034
|
+
const types = options.reduce((acc, x) => {
|
|
3035
|
+
const type = typeof x._def.value;
|
|
3036
|
+
switch (type) {
|
|
3037
|
+
case "string":
|
|
3038
|
+
case "number":
|
|
3039
|
+
case "boolean":
|
|
3040
|
+
return [...acc, type];
|
|
3041
|
+
case "bigint":
|
|
3042
|
+
return [...acc, "integer"];
|
|
3043
|
+
case "object":
|
|
3044
|
+
if (x._def.value === null)
|
|
3045
|
+
return [...acc, "null"];
|
|
3046
|
+
case "symbol":
|
|
3047
|
+
case "undefined":
|
|
3048
|
+
case "function":
|
|
3049
|
+
default:
|
|
3050
|
+
return acc;
|
|
3051
|
+
}
|
|
3052
|
+
}, []);
|
|
3053
|
+
if (types.length === options.length) {
|
|
3054
|
+
const uniqueTypes = types.filter((x, i, a) => a.indexOf(x) === i);
|
|
3055
|
+
return {
|
|
3056
|
+
type: uniqueTypes.length > 1 ? uniqueTypes : uniqueTypes[0],
|
|
3057
|
+
enum: options.reduce((acc, x) => {
|
|
3058
|
+
return acc.includes(x._def.value) ? acc : [...acc, x._def.value];
|
|
3059
|
+
}, [])
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
} else if (options.every((x) => x._def.typeName === "ZodEnum")) {
|
|
3063
|
+
return {
|
|
3064
|
+
type: "string",
|
|
3065
|
+
enum: options.reduce((acc, x) => [
|
|
3066
|
+
...acc,
|
|
3067
|
+
...x._def.values.filter((x2) => !acc.includes(x2))
|
|
3068
|
+
], [])
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
return asAnyOf(def, refs);
|
|
3072
|
+
}
|
|
3073
|
+
var asAnyOf = (def, refs) => {
|
|
3074
|
+
const anyOf = (def.options instanceof Map ? Array.from(def.options.values()) : def.options).map((x, i) => parseDef(x._def, {
|
|
3075
|
+
...refs,
|
|
3076
|
+
currentPath: [...refs.currentPath, "anyOf", `${i}`]
|
|
3077
|
+
})).filter((x) => !!x && (!refs.strictUnions || typeof x === "object" && Object.keys(x).length > 0));
|
|
3078
|
+
return anyOf.length ? { anyOf } : void 0;
|
|
3079
|
+
};
|
|
3080
|
+
function parseNullableDef(def, refs) {
|
|
3081
|
+
if (["ZodString", "ZodNumber", "ZodBigInt", "ZodBoolean", "ZodNull"].includes(def.innerType._def.typeName) && (!def.innerType._def.checks || !def.innerType._def.checks.length)) {
|
|
3082
|
+
if (refs.target === "openApi3") {
|
|
3083
|
+
return {
|
|
3084
|
+
type: primitiveMappings[def.innerType._def.typeName],
|
|
3085
|
+
nullable: true
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
return {
|
|
3089
|
+
type: [
|
|
3090
|
+
primitiveMappings[def.innerType._def.typeName],
|
|
3091
|
+
"null"
|
|
3092
|
+
]
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
if (refs.target === "openApi3") {
|
|
3096
|
+
const base2 = parseDef(def.innerType._def, {
|
|
3097
|
+
...refs,
|
|
3098
|
+
currentPath: [...refs.currentPath]
|
|
3099
|
+
});
|
|
3100
|
+
if (base2 && "$ref" in base2)
|
|
3101
|
+
return { allOf: [base2], nullable: true };
|
|
3102
|
+
return base2 && { ...base2, nullable: true };
|
|
3103
|
+
}
|
|
3104
|
+
const base = parseDef(def.innerType._def, {
|
|
3105
|
+
...refs,
|
|
3106
|
+
currentPath: [...refs.currentPath, "anyOf", "0"]
|
|
3107
|
+
});
|
|
3108
|
+
return base && { anyOf: [base, { type: "null" }] };
|
|
3109
|
+
}
|
|
3110
|
+
function parseNumberDef(def, refs) {
|
|
3111
|
+
const res = {
|
|
3112
|
+
type: "number"
|
|
3113
|
+
};
|
|
3114
|
+
if (!def.checks)
|
|
3115
|
+
return res;
|
|
3116
|
+
for (const check of def.checks) {
|
|
3117
|
+
switch (check.kind) {
|
|
3118
|
+
case "int":
|
|
3119
|
+
res.type = "integer";
|
|
3120
|
+
addErrorMessage(res, "type", check.message, refs);
|
|
3121
|
+
break;
|
|
3122
|
+
case "min":
|
|
3123
|
+
if (refs.target === "jsonSchema7") {
|
|
3124
|
+
if (check.inclusive) {
|
|
3125
|
+
setResponseValueAndErrors(res, "minimum", check.value, check.message, refs);
|
|
3126
|
+
} else {
|
|
3127
|
+
setResponseValueAndErrors(res, "exclusiveMinimum", check.value, check.message, refs);
|
|
3128
|
+
}
|
|
3129
|
+
} else {
|
|
3130
|
+
if (!check.inclusive) {
|
|
3131
|
+
res.exclusiveMinimum = true;
|
|
3132
|
+
}
|
|
3133
|
+
setResponseValueAndErrors(res, "minimum", check.value, check.message, refs);
|
|
3134
|
+
}
|
|
3135
|
+
break;
|
|
3136
|
+
case "max":
|
|
3137
|
+
if (refs.target === "jsonSchema7") {
|
|
3138
|
+
if (check.inclusive) {
|
|
3139
|
+
setResponseValueAndErrors(res, "maximum", check.value, check.message, refs);
|
|
3140
|
+
} else {
|
|
3141
|
+
setResponseValueAndErrors(res, "exclusiveMaximum", check.value, check.message, refs);
|
|
3142
|
+
}
|
|
3143
|
+
} else {
|
|
3144
|
+
if (!check.inclusive) {
|
|
3145
|
+
res.exclusiveMaximum = true;
|
|
3146
|
+
}
|
|
3147
|
+
setResponseValueAndErrors(res, "maximum", check.value, check.message, refs);
|
|
3148
|
+
}
|
|
3149
|
+
break;
|
|
3150
|
+
case "multipleOf":
|
|
3151
|
+
setResponseValueAndErrors(res, "multipleOf", check.value, check.message, refs);
|
|
3152
|
+
break;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
return res;
|
|
3156
|
+
}
|
|
3157
|
+
function parseObjectDef(def, refs) {
|
|
3158
|
+
const forceOptionalIntoNullable = refs.target === "openAi";
|
|
3159
|
+
const result = {
|
|
3160
|
+
type: "object",
|
|
3161
|
+
properties: {}
|
|
3162
|
+
};
|
|
3163
|
+
const required = [];
|
|
3164
|
+
const shape = def.shape();
|
|
3165
|
+
for (const propName in shape) {
|
|
3166
|
+
let propDef = shape[propName];
|
|
3167
|
+
if (propDef === void 0 || propDef._def === void 0) {
|
|
3168
|
+
continue;
|
|
3169
|
+
}
|
|
3170
|
+
let propOptional = safeIsOptional(propDef);
|
|
3171
|
+
if (propOptional && forceOptionalIntoNullable) {
|
|
3172
|
+
if (propDef._def.typeName === "ZodOptional") {
|
|
3173
|
+
propDef = propDef._def.innerType;
|
|
3174
|
+
}
|
|
3175
|
+
if (!propDef.isNullable()) {
|
|
3176
|
+
propDef = propDef.nullable();
|
|
2303
3177
|
}
|
|
2304
|
-
|
|
3178
|
+
propOptional = false;
|
|
3179
|
+
}
|
|
3180
|
+
const parsedDef = parseDef(propDef._def, {
|
|
3181
|
+
...refs,
|
|
3182
|
+
currentPath: [...refs.currentPath, "properties", propName],
|
|
3183
|
+
propertyPath: [...refs.currentPath, "properties", propName]
|
|
3184
|
+
});
|
|
3185
|
+
if (parsedDef === void 0) {
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
result.properties[propName] = parsedDef;
|
|
3189
|
+
if (!propOptional) {
|
|
3190
|
+
required.push(propName);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
if (required.length) {
|
|
3194
|
+
result.required = required;
|
|
3195
|
+
}
|
|
3196
|
+
const additionalProperties = decideAdditionalProperties(def, refs);
|
|
3197
|
+
if (additionalProperties !== void 0) {
|
|
3198
|
+
result.additionalProperties = additionalProperties;
|
|
3199
|
+
}
|
|
3200
|
+
return result;
|
|
3201
|
+
}
|
|
3202
|
+
function decideAdditionalProperties(def, refs) {
|
|
3203
|
+
if (def.catchall._def.typeName !== "ZodNever") {
|
|
3204
|
+
return parseDef(def.catchall._def, {
|
|
3205
|
+
...refs,
|
|
3206
|
+
currentPath: [...refs.currentPath, "additionalProperties"]
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
switch (def.unknownKeys) {
|
|
3210
|
+
case "passthrough":
|
|
3211
|
+
return refs.allowedAdditionalProperties;
|
|
3212
|
+
case "strict":
|
|
3213
|
+
return refs.rejectedAdditionalProperties;
|
|
3214
|
+
case "strip":
|
|
3215
|
+
return refs.removeAdditionalStrategy === "strict" ? refs.allowedAdditionalProperties : refs.rejectedAdditionalProperties;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
function safeIsOptional(schema) {
|
|
3219
|
+
try {
|
|
3220
|
+
return schema.isOptional();
|
|
3221
|
+
} catch {
|
|
3222
|
+
return true;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
var parseOptionalDef = (def, refs) => {
|
|
3226
|
+
if (refs.currentPath.toString() === refs.propertyPath?.toString()) {
|
|
3227
|
+
return parseDef(def.innerType._def, refs);
|
|
3228
|
+
}
|
|
3229
|
+
const innerSchema = parseDef(def.innerType._def, {
|
|
3230
|
+
...refs,
|
|
3231
|
+
currentPath: [...refs.currentPath, "anyOf", "1"]
|
|
3232
|
+
});
|
|
3233
|
+
return innerSchema ? {
|
|
3234
|
+
anyOf: [
|
|
3235
|
+
{
|
|
3236
|
+
not: parseAnyDef(refs)
|
|
3237
|
+
},
|
|
3238
|
+
innerSchema
|
|
3239
|
+
]
|
|
3240
|
+
} : parseAnyDef(refs);
|
|
3241
|
+
};
|
|
3242
|
+
var parsePipelineDef = (def, refs) => {
|
|
3243
|
+
if (refs.pipeStrategy === "input") {
|
|
3244
|
+
return parseDef(def.in._def, refs);
|
|
3245
|
+
} else if (refs.pipeStrategy === "output") {
|
|
3246
|
+
return parseDef(def.out._def, refs);
|
|
3247
|
+
}
|
|
3248
|
+
const a = parseDef(def.in._def, {
|
|
3249
|
+
...refs,
|
|
3250
|
+
currentPath: [...refs.currentPath, "allOf", "0"]
|
|
3251
|
+
});
|
|
3252
|
+
const b = parseDef(def.out._def, {
|
|
3253
|
+
...refs,
|
|
3254
|
+
currentPath: [...refs.currentPath, "allOf", a ? "1" : "0"]
|
|
3255
|
+
});
|
|
3256
|
+
return {
|
|
3257
|
+
allOf: [a, b].filter((x) => x !== void 0)
|
|
3258
|
+
};
|
|
3259
|
+
};
|
|
3260
|
+
function parsePromiseDef(def, refs) {
|
|
3261
|
+
return parseDef(def.type._def, refs);
|
|
3262
|
+
}
|
|
3263
|
+
function parseSetDef(def, refs) {
|
|
3264
|
+
const items = parseDef(def.valueType._def, {
|
|
3265
|
+
...refs,
|
|
3266
|
+
currentPath: [...refs.currentPath, "items"]
|
|
3267
|
+
});
|
|
3268
|
+
const schema = {
|
|
3269
|
+
type: "array",
|
|
3270
|
+
uniqueItems: true,
|
|
3271
|
+
items
|
|
3272
|
+
};
|
|
3273
|
+
if (def.minSize) {
|
|
3274
|
+
setResponseValueAndErrors(schema, "minItems", def.minSize.value, def.minSize.message, refs);
|
|
3275
|
+
}
|
|
3276
|
+
if (def.maxSize) {
|
|
3277
|
+
setResponseValueAndErrors(schema, "maxItems", def.maxSize.value, def.maxSize.message, refs);
|
|
3278
|
+
}
|
|
3279
|
+
return schema;
|
|
3280
|
+
}
|
|
3281
|
+
function parseTupleDef(def, refs) {
|
|
3282
|
+
if (def.rest) {
|
|
3283
|
+
return {
|
|
3284
|
+
type: "array",
|
|
3285
|
+
minItems: def.items.length,
|
|
3286
|
+
items: def.items.map((x, i) => parseDef(x._def, {
|
|
3287
|
+
...refs,
|
|
3288
|
+
currentPath: [...refs.currentPath, "items", `${i}`]
|
|
3289
|
+
})).reduce((acc, x) => x === void 0 ? acc : [...acc, x], []),
|
|
3290
|
+
additionalItems: parseDef(def.rest._def, {
|
|
3291
|
+
...refs,
|
|
3292
|
+
currentPath: [...refs.currentPath, "additionalItems"]
|
|
3293
|
+
})
|
|
3294
|
+
};
|
|
3295
|
+
} else {
|
|
3296
|
+
return {
|
|
3297
|
+
type: "array",
|
|
3298
|
+
minItems: def.items.length,
|
|
3299
|
+
maxItems: def.items.length,
|
|
3300
|
+
items: def.items.map((x, i) => parseDef(x._def, {
|
|
3301
|
+
...refs,
|
|
3302
|
+
currentPath: [...refs.currentPath, "items", `${i}`]
|
|
3303
|
+
})).reduce((acc, x) => x === void 0 ? acc : [...acc, x], [])
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
function parseUndefinedDef(refs) {
|
|
3308
|
+
return {
|
|
3309
|
+
not: parseAnyDef(refs)
|
|
2305
3310
|
};
|
|
2306
3311
|
}
|
|
2307
|
-
function
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
3312
|
+
function parseUnknownDef(refs) {
|
|
3313
|
+
return parseAnyDef(refs);
|
|
3314
|
+
}
|
|
3315
|
+
var parseReadonlyDef = (def, refs) => {
|
|
3316
|
+
return parseDef(def.innerType._def, refs);
|
|
3317
|
+
};
|
|
3318
|
+
var selectParser = (def, typeName, refs) => {
|
|
3319
|
+
switch (typeName) {
|
|
3320
|
+
case ZodFirstPartyTypeKind3.ZodString:
|
|
3321
|
+
return parseStringDef(def, refs);
|
|
3322
|
+
case ZodFirstPartyTypeKind3.ZodNumber:
|
|
3323
|
+
return parseNumberDef(def, refs);
|
|
3324
|
+
case ZodFirstPartyTypeKind3.ZodObject:
|
|
3325
|
+
return parseObjectDef(def, refs);
|
|
3326
|
+
case ZodFirstPartyTypeKind3.ZodBigInt:
|
|
3327
|
+
return parseBigintDef(def, refs);
|
|
3328
|
+
case ZodFirstPartyTypeKind3.ZodBoolean:
|
|
3329
|
+
return parseBooleanDef();
|
|
3330
|
+
case ZodFirstPartyTypeKind3.ZodDate:
|
|
3331
|
+
return parseDateDef(def, refs);
|
|
3332
|
+
case ZodFirstPartyTypeKind3.ZodUndefined:
|
|
3333
|
+
return parseUndefinedDef(refs);
|
|
3334
|
+
case ZodFirstPartyTypeKind3.ZodNull:
|
|
3335
|
+
return parseNullDef(refs);
|
|
3336
|
+
case ZodFirstPartyTypeKind3.ZodArray:
|
|
3337
|
+
return parseArrayDef(def, refs);
|
|
3338
|
+
case ZodFirstPartyTypeKind3.ZodUnion:
|
|
3339
|
+
case ZodFirstPartyTypeKind3.ZodDiscriminatedUnion:
|
|
3340
|
+
return parseUnionDef(def, refs);
|
|
3341
|
+
case ZodFirstPartyTypeKind3.ZodIntersection:
|
|
3342
|
+
return parseIntersectionDef(def, refs);
|
|
3343
|
+
case ZodFirstPartyTypeKind3.ZodTuple:
|
|
3344
|
+
return parseTupleDef(def, refs);
|
|
3345
|
+
case ZodFirstPartyTypeKind3.ZodRecord:
|
|
3346
|
+
return parseRecordDef(def, refs);
|
|
3347
|
+
case ZodFirstPartyTypeKind3.ZodLiteral:
|
|
3348
|
+
return parseLiteralDef(def, refs);
|
|
3349
|
+
case ZodFirstPartyTypeKind3.ZodEnum:
|
|
3350
|
+
return parseEnumDef(def);
|
|
3351
|
+
case ZodFirstPartyTypeKind3.ZodNativeEnum:
|
|
3352
|
+
return parseNativeEnumDef(def);
|
|
3353
|
+
case ZodFirstPartyTypeKind3.ZodNullable:
|
|
3354
|
+
return parseNullableDef(def, refs);
|
|
3355
|
+
case ZodFirstPartyTypeKind3.ZodOptional:
|
|
3356
|
+
return parseOptionalDef(def, refs);
|
|
3357
|
+
case ZodFirstPartyTypeKind3.ZodMap:
|
|
3358
|
+
return parseMapDef(def, refs);
|
|
3359
|
+
case ZodFirstPartyTypeKind3.ZodSet:
|
|
3360
|
+
return parseSetDef(def, refs);
|
|
3361
|
+
case ZodFirstPartyTypeKind3.ZodLazy:
|
|
3362
|
+
return () => def.getter()._def;
|
|
3363
|
+
case ZodFirstPartyTypeKind3.ZodPromise:
|
|
3364
|
+
return parsePromiseDef(def, refs);
|
|
3365
|
+
case ZodFirstPartyTypeKind3.ZodNaN:
|
|
3366
|
+
case ZodFirstPartyTypeKind3.ZodNever:
|
|
3367
|
+
return parseNeverDef(refs);
|
|
3368
|
+
case ZodFirstPartyTypeKind3.ZodEffects:
|
|
3369
|
+
return parseEffectsDef(def, refs);
|
|
3370
|
+
case ZodFirstPartyTypeKind3.ZodAny:
|
|
3371
|
+
return parseAnyDef(refs);
|
|
3372
|
+
case ZodFirstPartyTypeKind3.ZodUnknown:
|
|
3373
|
+
return parseUnknownDef(refs);
|
|
3374
|
+
case ZodFirstPartyTypeKind3.ZodDefault:
|
|
3375
|
+
return parseDefaultDef(def, refs);
|
|
3376
|
+
case ZodFirstPartyTypeKind3.ZodBranded:
|
|
3377
|
+
return parseBrandedDef(def, refs);
|
|
3378
|
+
case ZodFirstPartyTypeKind3.ZodReadonly:
|
|
3379
|
+
return parseReadonlyDef(def, refs);
|
|
3380
|
+
case ZodFirstPartyTypeKind3.ZodCatch:
|
|
3381
|
+
return parseCatchDef(def, refs);
|
|
3382
|
+
case ZodFirstPartyTypeKind3.ZodPipeline:
|
|
3383
|
+
return parsePipelineDef(def, refs);
|
|
3384
|
+
case ZodFirstPartyTypeKind3.ZodFunction:
|
|
3385
|
+
case ZodFirstPartyTypeKind3.ZodVoid:
|
|
3386
|
+
case ZodFirstPartyTypeKind3.ZodSymbol:
|
|
3387
|
+
return void 0;
|
|
3388
|
+
default:
|
|
3389
|
+
return /* @__PURE__ */ ((_) => void 0)(typeName);
|
|
3390
|
+
}
|
|
3391
|
+
};
|
|
3392
|
+
function parseDef(def, refs, forceResolution = false) {
|
|
3393
|
+
const seenItem = refs.seen.get(def);
|
|
3394
|
+
if (refs.override) {
|
|
3395
|
+
const overrideResult = refs.override?.(def, refs, seenItem, forceResolution);
|
|
3396
|
+
if (overrideResult !== ignoreOverride) {
|
|
3397
|
+
return overrideResult;
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
if (seenItem && !forceResolution) {
|
|
3401
|
+
const seenSchema = get$ref(seenItem, refs);
|
|
3402
|
+
if (seenSchema !== void 0) {
|
|
3403
|
+
return seenSchema;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
const newItem = { def, path: refs.currentPath, jsonSchema: void 0 };
|
|
3407
|
+
refs.seen.set(def, newItem);
|
|
3408
|
+
const jsonSchemaOrGetter = selectParser(def, def.typeName, refs);
|
|
3409
|
+
const jsonSchema = typeof jsonSchemaOrGetter === "function" ? parseDef(jsonSchemaOrGetter(), refs) : jsonSchemaOrGetter;
|
|
3410
|
+
if (jsonSchema) {
|
|
3411
|
+
addMeta(def, refs, jsonSchema);
|
|
3412
|
+
}
|
|
3413
|
+
if (refs.postProcess) {
|
|
3414
|
+
const postProcessResult = refs.postProcess(jsonSchema, def, refs);
|
|
3415
|
+
newItem.jsonSchema = jsonSchema;
|
|
3416
|
+
return postProcessResult;
|
|
3417
|
+
}
|
|
3418
|
+
newItem.jsonSchema = jsonSchema;
|
|
3419
|
+
return jsonSchema;
|
|
3420
|
+
}
|
|
3421
|
+
var get$ref = (item, refs) => {
|
|
3422
|
+
switch (refs.$refStrategy) {
|
|
3423
|
+
case "root":
|
|
3424
|
+
return { $ref: item.path.join("/") };
|
|
3425
|
+
case "relative":
|
|
3426
|
+
return { $ref: getRelativePath(refs.currentPath, item.path) };
|
|
3427
|
+
case "none":
|
|
3428
|
+
case "seen": {
|
|
3429
|
+
if (item.path.length < refs.currentPath.length && item.path.every((value, index) => refs.currentPath[index] === value)) {
|
|
3430
|
+
console.warn(`Recursive reference detected at ${refs.currentPath.join("/")}! Defaulting to any`);
|
|
3431
|
+
return parseAnyDef(refs);
|
|
3432
|
+
}
|
|
3433
|
+
return refs.$refStrategy === "seen" ? parseAnyDef(refs) : void 0;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
var addMeta = (def, refs, jsonSchema) => {
|
|
3438
|
+
if (def.description) {
|
|
3439
|
+
jsonSchema.description = def.description;
|
|
3440
|
+
if (refs.markdownDescription) {
|
|
3441
|
+
jsonSchema.markdownDescription = def.description;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return jsonSchema;
|
|
3445
|
+
};
|
|
3446
|
+
var zodToJsonSchema = (schema, options) => {
|
|
3447
|
+
const refs = getRefs(options);
|
|
3448
|
+
let definitions = typeof options === "object" && options.definitions ? Object.entries(options.definitions).reduce((acc, [name2, schema2]) => ({
|
|
3449
|
+
...acc,
|
|
3450
|
+
[name2]: parseDef(schema2._def, {
|
|
3451
|
+
...refs,
|
|
3452
|
+
currentPath: [...refs.basePath, refs.definitionPath, name2]
|
|
3453
|
+
}, true) ?? parseAnyDef(refs)
|
|
3454
|
+
}), {}) : void 0;
|
|
3455
|
+
const name = typeof options === "string" ? options : options?.nameStrategy === "title" ? void 0 : options?.name;
|
|
3456
|
+
const main = parseDef(schema._def, name === void 0 ? refs : {
|
|
3457
|
+
...refs,
|
|
3458
|
+
currentPath: [...refs.basePath, refs.definitionPath, name]
|
|
3459
|
+
}, false) ?? parseAnyDef(refs);
|
|
3460
|
+
const title = typeof options === "object" && options.name !== void 0 && options.nameStrategy === "title" ? options.name : void 0;
|
|
3461
|
+
if (title !== void 0) {
|
|
3462
|
+
main.title = title;
|
|
3463
|
+
}
|
|
3464
|
+
if (refs.flags.hasReferencedOpenAiAnyType) {
|
|
3465
|
+
if (!definitions) {
|
|
3466
|
+
definitions = {};
|
|
3467
|
+
}
|
|
3468
|
+
if (!definitions[refs.openAiAnyTypeName]) {
|
|
3469
|
+
definitions[refs.openAiAnyTypeName] = {
|
|
3470
|
+
// Skipping "object" as no properties can be defined and additionalProperties must be "false"
|
|
3471
|
+
type: ["string", "number", "integer", "boolean", "array", "null"],
|
|
3472
|
+
items: {
|
|
3473
|
+
$ref: refs.$refStrategy === "relative" ? "1" : [
|
|
3474
|
+
...refs.basePath,
|
|
3475
|
+
refs.definitionPath,
|
|
3476
|
+
refs.openAiAnyTypeName
|
|
3477
|
+
].join("/")
|
|
3478
|
+
}
|
|
3479
|
+
};
|
|
2316
3480
|
}
|
|
2317
|
-
if ((req.needs?.length ?? 0) > NEEDS_MAX)
|
|
2318
|
-
problems.push(`${req.needs?.length} needs \u2014 cap at ${NEEDS_MAX}; a call can't cover more in one conversation, split the rest into a second ask`);
|
|
2319
|
-
return problems;
|
|
2320
3481
|
}
|
|
2321
|
-
const
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
3482
|
+
const combined = name === void 0 ? definitions ? {
|
|
3483
|
+
...main,
|
|
3484
|
+
[refs.definitionPath]: definitions
|
|
3485
|
+
} : main : {
|
|
3486
|
+
$ref: [
|
|
3487
|
+
...refs.$refStrategy === "relative" ? [] : refs.basePath,
|
|
3488
|
+
refs.definitionPath,
|
|
3489
|
+
name
|
|
3490
|
+
].join("/"),
|
|
3491
|
+
[refs.definitionPath]: {
|
|
3492
|
+
...definitions,
|
|
3493
|
+
[name]: main
|
|
2332
3494
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
3495
|
+
};
|
|
3496
|
+
if (refs.target === "jsonSchema7") {
|
|
3497
|
+
combined.$schema = "http://json-schema.org/draft-07/schema#";
|
|
3498
|
+
} else if (refs.target === "jsonSchema2019-09" || refs.target === "openAi") {
|
|
3499
|
+
combined.$schema = "https://json-schema.org/draft/2019-09/schema#";
|
|
3500
|
+
}
|
|
3501
|
+
if (refs.target === "openAi" && ("anyOf" in combined || "oneOf" in combined || "allOf" in combined || "type" in combined && Array.isArray(combined.type))) {
|
|
3502
|
+
console.warn("Warning: OpenAI may not support schemas with unions as roots! Try wrapping it in an object property.");
|
|
2335
3503
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
3504
|
+
return combined;
|
|
3505
|
+
};
|
|
3506
|
+
var OPTIONS_MIN = 2;
|
|
3507
|
+
var OPTIONS_MAX = 6;
|
|
3508
|
+
var MISSED_CALL_PLAN = {
|
|
3509
|
+
retry_10m: { kind: "every", minutes: 10 },
|
|
3510
|
+
retry_30m: { kind: "every", minutes: 30 },
|
|
3511
|
+
retry_60m: { kind: "every", minutes: 60 },
|
|
3512
|
+
backoff_gentle: { kind: "at", minutes: [30, 120, 360] },
|
|
3513
|
+
backoff_standard: { kind: "at", minutes: [10, 30, 120] },
|
|
3514
|
+
backoff_aggressive: { kind: "at", minutes: [5, 15, 45] },
|
|
3515
|
+
inbox: { kind: "once" },
|
|
3516
|
+
dismiss: { kind: "grace", minutes: 2 }
|
|
3517
|
+
};
|
|
3518
|
+
function draft2020(node) {
|
|
3519
|
+
if (Array.isArray(node)) return node.map(draft2020);
|
|
3520
|
+
if (node && typeof node === "object") {
|
|
3521
|
+
const o = node;
|
|
3522
|
+
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
3523
|
+
if (typeof o[excl] === "boolean") {
|
|
3524
|
+
if (o[excl] === true && typeof o[lim] === "number") {
|
|
3525
|
+
o[excl] = o[lim];
|
|
3526
|
+
delete o[lim];
|
|
3527
|
+
} else delete o[excl];
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
for (const k of Object.keys(o)) o[k] = draft2020(o[k]);
|
|
3531
|
+
return o;
|
|
2339
3532
|
}
|
|
2340
|
-
return
|
|
3533
|
+
return node;
|
|
3534
|
+
}
|
|
3535
|
+
function mcpInputSchema(s) {
|
|
3536
|
+
const schema = zodToJsonSchema(s, { target: "jsonSchema2019-09", $refStrategy: "none" });
|
|
3537
|
+
delete schema.$schema;
|
|
3538
|
+
return draft2020(schema);
|
|
3539
|
+
}
|
|
3540
|
+
var CreateGoalSchema = z.object({
|
|
3541
|
+
outcome: z.string().trim().min(1).max(1e4),
|
|
3542
|
+
ownerParticipant: z.string().trim().min(1).optional(),
|
|
3543
|
+
idempotencyKey: z.string().trim().min(1).max(200)
|
|
3544
|
+
});
|
|
3545
|
+
var fmtMin = (m) => m >= 60 ? `${m / 60} hr` : `${m} min`;
|
|
3546
|
+
var STANDARD_MEANS = (() => {
|
|
3547
|
+
const plan = MISSED_CALL_PLAN.backoff_standard;
|
|
3548
|
+
const mins = plan.kind === "at" ? plan.minutes : [];
|
|
3549
|
+
const parts = mins.map(fmtMin);
|
|
3550
|
+
const list = parts.length > 1 ? `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}` : parts[0] ?? "";
|
|
3551
|
+
return `Rings again ${list} after the missed call, then leaves it in your inbox`;
|
|
3552
|
+
})();
|
|
3553
|
+
function contactSchemaFrom(fields) {
|
|
3554
|
+
const surface = z.object({
|
|
3555
|
+
ask: fields.ask.describe(
|
|
3556
|
+
`What to tell the user, or what you need to find out from them. Plain prose \u2014 as long as it needs to be (up to 10k characters); Paigy splits it into topics and reads back a few sentences at a time, so do NOT compress a briefing into one line. May be spoken aloud on a call, so write natural speech and name things (not IDs). Contact at exactly two moments: BLOCKED on a decision only they can make, or DONE (one short report \u2014 what shipped, how you verified it, what you flagged). DONE IS SAID ONCE: "all set", "nothing open on my end", "that thread is complete" are the same report in new words, and each one reaches them separately (live 2026-08-12: three of them in three minutes). After the first, you are finished speaking; if they acknowledge it, stop rather than confirming the acknowledgement. Progress is never a contact: set_work_state carries it, and working narration stays in your own terminal \u2014 the user sees you're working without being interrupted by it.`
|
|
3557
|
+
),
|
|
3558
|
+
waiting: fields.waiting.describe(
|
|
3559
|
+
"What happens to your work while you wait. 'none': you're just informing them. 'soft': you'd like an answer but can keep working. 'hard': you are STOPPED until they answer \u2014 reaches them urgently and escalates to a real phone call if unanswered."
|
|
3560
|
+
),
|
|
3561
|
+
options: fields.options.describe(
|
|
3562
|
+
`The choices the user picks from, when you have them \u2014 ${OPTIONS_MIN} to ${OPTIONS_MAX}, drawn from your own sentence.`
|
|
3563
|
+
),
|
|
3564
|
+
channel: fields.channel.describe(
|
|
3565
|
+
"Relay how the user explicitly said to reach them ('call me' \u2192 'call', 'just message/text me' \u2192 'message'), or use 'call' when promoting the same quiet ask after it becomes a substantial blocker. Omit otherwise; Paigy picks."
|
|
3566
|
+
),
|
|
3567
|
+
parentId: fields.parentId.describe(
|
|
3568
|
+
"To continue an earlier conversation, pass the parentId a previous contact or reply returned. Omit to start a new one."
|
|
3569
|
+
),
|
|
3570
|
+
workId: fields.workId.describe(
|
|
3571
|
+
"The durable Work this contact advances. Pass the workId from check_replies or a prior reply when asking for a decision that blocks that work."
|
|
3572
|
+
)
|
|
3573
|
+
});
|
|
3574
|
+
const out = mcpInputSchema(surface);
|
|
3575
|
+
out.required = ["ask"];
|
|
3576
|
+
return out;
|
|
2341
3577
|
}
|
|
2342
|
-
var
|
|
2343
|
-
|
|
2344
|
-
|
|
3578
|
+
var CONTACT_DESCRIPTION = `Reach the user through Paigy \u2014 tell them something, or ask and get their answer. State what you need in \`ask\`, say what happens to your work while you wait in \`waiting\`, and Paigy handles the rest (channel, phrasing, answer format). If the user explicitly asks you to CALL them, send waiting:'hard' and say so in the ask. Returns { notificationId, parentId, workId?, decisionId? } \u2014 pass notificationId to the reply path named by the returned \`message\`, and parentId to a later contact to continue the conversation. When it rang, the reply also carries { ifMissed: { mode, means } }: what the user's own policy does with a call they don't take ("${STANDARD_MEANS}"), so a no-answer tells you how long to wait before coming back. THREADING REPLACES: a threaded follow-up SUPERSEDES your earlier pending items on that thread \u2014 right for updates to one ask, WRONG for a checklist (send independent to-dos un-threaded). A threaded re-send with IDENTICAL content escalates the pending ask in place. If a reply comes back as {kind:'clarify', chunks:[...]}, the user wants more detail \u2014 contact again on the SAME parentId with an expanded ask. BLOCKED ON A DECISION for existing work? Pass that work's \`workId\`; the reply returns the same workId plus a decisionId, so the answer resumes the right outcome. ONE ASK, ONE ROW: never restate a still-pending ask's question inside a NEW contact (e.g. weaving it into a briefing) \u2014 the whole answer settles on the new row and the original can never receive it. Keep waiting on the original (a live call reads every pending ask out separately, each answer routes to its own row), and use \`needs\` for a genuinely multi-part NEW ask. ANSWERABLE, NOT JUST ASKED: when the reply comes back carrying \`plan.units[].needs\`, that unit asked for something it gave the user no way to answer \u2014 'options' means it posed a choice with nothing to choose from, 'visuals' means it asked about something to look at with nothing to look at. Send it again on the SAME parentId with ${OPTIONS_MIN}-${OPTIONS_MAX} options (or the image), drawn from your own sentence. Paigy will not add them for you: a shape it guessed wrong cannot be undone, and you are the one who knows what the real alternatives are. \`units\` reports WHAT BECAME OF YOUR PROSE \u2014 { kept, raw, why }: how many topics Paigy compressed for delivery, how many kept your exact words, and the reason when it kept them (e.g. 'no_output' = compression produced nothing usable, so the user got your raw sentence). It needs no action and is not an error \u2014 read it only when the delivered wording matters to you; a high \`raw\` count means the user is hearing you verbatim.`;
|
|
3579
|
+
var ContextSchema = z2.object({
|
|
3580
|
+
title: z2.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
3581
|
+
description: z2.array(z2.string().min(1)).describe(
|
|
2345
3582
|
"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."
|
|
2346
3583
|
)
|
|
2347
3584
|
});
|
|
2348
|
-
var ParticipantSchema =
|
|
2349
|
-
kind:
|
|
2350
|
-
id:
|
|
3585
|
+
var ParticipantSchema = z2.object({
|
|
3586
|
+
kind: z2.enum(["human", "agent"]),
|
|
3587
|
+
id: z2.string()
|
|
2351
3588
|
});
|
|
2352
|
-
var TransformSchema =
|
|
3589
|
+
var TransformSchema = z2.enum([
|
|
2353
3590
|
"structure",
|
|
2354
3591
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
2355
3592
|
"request_more",
|
|
@@ -2365,25 +3602,25 @@ var TransformSchema = z.enum([
|
|
|
2365
3602
|
"summarize"
|
|
2366
3603
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
2367
3604
|
]);
|
|
2368
|
-
var OptionSchema =
|
|
2369
|
-
id:
|
|
2370
|
-
label:
|
|
3605
|
+
var OptionSchema = z2.object({
|
|
3606
|
+
id: z2.string(),
|
|
3607
|
+
label: z2.string(),
|
|
2371
3608
|
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
2372
3609
|
// the constraints below are what an agent reads when deciding to use these.
|
|
2373
|
-
html:
|
|
3610
|
+
html: z2.string().max(16384).describe(
|
|
2374
3611
|
"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. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
2375
3612
|
).optional(),
|
|
2376
|
-
image:
|
|
3613
|
+
image: z2.string().url().describe(
|
|
2377
3614
|
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
2378
3615
|
).optional()
|
|
2379
3616
|
});
|
|
2380
|
-
var VisualSchema =
|
|
2381
|
-
url:
|
|
2382
|
-
label:
|
|
3617
|
+
var VisualSchema = z2.object({
|
|
3618
|
+
url: z2.string().url(),
|
|
3619
|
+
label: z2.string().optional()
|
|
2383
3620
|
});
|
|
2384
|
-
var NotifyLevelSchema =
|
|
2385
|
-
var SelectShapeSchema =
|
|
2386
|
-
var ReceiptEventSchema =
|
|
3621
|
+
var NotifyLevelSchema = z2.enum(["inbox", "push", "banner", "call"]);
|
|
3622
|
+
var SelectShapeSchema = z2.enum(["one", "many", "rank", "confirm", "text"]);
|
|
3623
|
+
var ReceiptEventSchema = z2.enum([
|
|
2387
3624
|
"delivered",
|
|
2388
3625
|
// the bundle reached the recipient at some level
|
|
2389
3626
|
"seen",
|
|
@@ -2413,41 +3650,44 @@ var ReceiptEventSchema = z.enum([
|
|
|
2413
3650
|
// be rewound by a writer that forgot to advance it.
|
|
2414
3651
|
"restarted"
|
|
2415
3652
|
]);
|
|
2416
|
-
var AttentionSchema =
|
|
3653
|
+
var AttentionSchema = z2.object({
|
|
2417
3654
|
urgency: NotifyLevelSchema,
|
|
2418
3655
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
2419
3656
|
select: SelectShapeSchema.nullable(),
|
|
2420
3657
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
2421
|
-
points:
|
|
3658
|
+
points: z2.array(z2.string()).nullable(),
|
|
2422
3659
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
2423
|
-
blocking:
|
|
3660
|
+
blocking: z2.boolean(),
|
|
2424
3661
|
/** Reserved (MODEL.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
2425
3662
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
2426
|
-
deadline:
|
|
3663
|
+
deadline: z2.string().datetime().nullable().optional()
|
|
2427
3664
|
});
|
|
2428
|
-
var
|
|
3665
|
+
var NotifyRequestFields = z2.object({
|
|
2429
3666
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
2430
3667
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
2431
3668
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
2432
3669
|
context: ContextSchema.optional(),
|
|
2433
|
-
options:
|
|
3670
|
+
options: z2.array(OptionSchema.omit({ id: true })).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
2434
3671
|
"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)."
|
|
2435
3672
|
),
|
|
2436
|
-
points:
|
|
3673
|
+
points: z2.array(z2.string().min(1)).optional().describe(
|
|
2437
3674
|
"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."
|
|
2438
3675
|
),
|
|
2439
|
-
visuals:
|
|
3676
|
+
visuals: z2.array(VisualSchema).optional().describe(
|
|
2440
3677
|
"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."
|
|
2441
3678
|
),
|
|
2442
3679
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
2443
|
-
repo:
|
|
3680
|
+
repo: z2.string().optional(),
|
|
2444
3681
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
2445
|
-
branch:
|
|
3682
|
+
branch: z2.string().optional(),
|
|
2446
3683
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
2447
3684
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
2448
3685
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
2449
3686
|
* still accepts the old name from older clients. */
|
|
2450
|
-
parentId:
|
|
3687
|
+
parentId: z2.string().uuid().optional(),
|
|
3688
|
+
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
3689
|
+
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
3690
|
+
workId: z2.string().uuid().optional(),
|
|
2451
3691
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
2452
3692
|
"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."
|
|
2453
3693
|
),
|
|
@@ -2455,7 +3695,7 @@ var NotifyRequestSchema = z.object({
|
|
|
2455
3695
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
2456
3696
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
2457
3697
|
* says HOW. */
|
|
2458
|
-
clarifies:
|
|
3698
|
+
clarifies: z2.string().optional(),
|
|
2459
3699
|
/** E2EE (text lane): when the pairing is E2EE, the sealed replacements for the
|
|
2460
3700
|
* plaintext content fields, keyed by field name. FINALIZED wire shape (was
|
|
2461
3701
|
* provisional in the storage PR): a per-field map `{ context?, options?,
|
|
@@ -2468,10 +3708,10 @@ var NotifyRequestSchema = z.object({
|
|
|
2468
3708
|
* notifications.envelope and relays it blindly; it never decrypts. Absent =
|
|
2469
3709
|
* today's plaintext path (context/options/visuals carry the cleartext).
|
|
2470
3710
|
* z.lazy because EnvelopeSchema is declared further down (E2EE section). */
|
|
2471
|
-
envelope:
|
|
2472
|
-
context:
|
|
2473
|
-
options:
|
|
2474
|
-
visuals:
|
|
3711
|
+
envelope: z2.object({
|
|
3712
|
+
context: z2.lazy(() => EnvelopeSchema).optional(),
|
|
3713
|
+
options: z2.lazy(() => EnvelopeSchema).optional(),
|
|
3714
|
+
visuals: z2.lazy(() => EnvelopeSchema).optional()
|
|
2475
3715
|
}).optional(),
|
|
2476
3716
|
select: SelectShapeSchema.optional().describe(
|
|
2477
3717
|
"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."
|
|
@@ -2486,20 +3726,20 @@ var NotifyRequestSchema = z.object({
|
|
|
2486
3726
|
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
2487
3727
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
2488
3728
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
2489
|
-
ask:
|
|
3729
|
+
ask: z2.string().min(1).max(1e4).optional().describe(
|
|
2490
3730
|
'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.'
|
|
2491
3731
|
),
|
|
2492
|
-
needs:
|
|
3732
|
+
needs: z2.array(z2.string().min(1)).optional().describe(
|
|
2493
3733
|
"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."
|
|
2494
3734
|
),
|
|
2495
|
-
urgencyHint:
|
|
3735
|
+
urgencyHint: z2.enum(["whenever", "soon", "now"]).optional().describe(
|
|
2496
3736
|
"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."
|
|
2497
3737
|
),
|
|
2498
3738
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
2499
3739
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
2500
3740
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
2501
3741
|
* urgencyHint/blocking win when both are sent. */
|
|
2502
|
-
waiting:
|
|
3742
|
+
waiting: z2.enum(["none", "soft", "hard"]).optional().describe(
|
|
2503
3743
|
"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."
|
|
2504
3744
|
),
|
|
2505
3745
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -2507,57 +3747,58 @@ var NotifyRequestSchema = z.object({
|
|
|
2507
3747
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
2508
3748
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
2509
3749
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
2510
|
-
confirm:
|
|
3750
|
+
confirm: z2.boolean().optional().describe(
|
|
2511
3751
|
"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'."
|
|
2512
3752
|
),
|
|
2513
3753
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
2514
3754
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
2515
3755
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
2516
3756
|
* waiting:'hard'. */
|
|
2517
|
-
channel:
|
|
3757
|
+
channel: z2.enum(["call", "message"]).optional().describe(
|
|
2518
3758
|
"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."
|
|
2519
3759
|
),
|
|
2520
|
-
confirmStyle:
|
|
3760
|
+
confirmStyle: z2.enum(["yesno", "approve"]).default("yesno").describe(
|
|
2521
3761
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
2522
3762
|
),
|
|
2523
|
-
blocking:
|
|
3763
|
+
blocking: z2.boolean().default(false).describe(
|
|
2524
3764
|
"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."
|
|
2525
3765
|
)
|
|
2526
|
-
})
|
|
3766
|
+
});
|
|
3767
|
+
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
2527
3768
|
const sealed = !!r.envelope;
|
|
2528
3769
|
if (sealed) {
|
|
2529
3770
|
if (!r.envelope?.context)
|
|
2530
|
-
ctx.addIssue({ code:
|
|
3771
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["envelope", "context"], message: "sealed request must include envelope.context" });
|
|
2531
3772
|
for (const f of ["context", "options", "visuals"]) {
|
|
2532
3773
|
if (r[f] !== void 0)
|
|
2533
|
-
ctx.addIssue({ code:
|
|
3774
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: [f], message: `E2EE request must not carry plaintext ${f} \u2014 it's sealed in envelope.${f}` });
|
|
2534
3775
|
}
|
|
2535
3776
|
if (r.points !== void 0)
|
|
2536
|
-
ctx.addIssue({ code:
|
|
3777
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["points"], message: "E2EE request must not carry plaintext points" });
|
|
2537
3778
|
if (r.ask !== void 0)
|
|
2538
|
-
ctx.addIssue({ code:
|
|
3779
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["ask"], message: "E2EE request must not carry a plaintext ask \u2014 derive the shape agent-side and seal it" });
|
|
2539
3780
|
if (r.needs !== void 0)
|
|
2540
|
-
ctx.addIssue({ code:
|
|
3781
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["needs"], message: "E2EE request must not carry plaintext needs" });
|
|
2541
3782
|
return;
|
|
2542
3783
|
}
|
|
2543
3784
|
if (r.ask !== void 0) {
|
|
2544
3785
|
for (const f of ["context", "select", "points"]) {
|
|
2545
3786
|
if (r[f] !== void 0)
|
|
2546
|
-
ctx.addIssue({ code:
|
|
3787
|
+
ctx.addIssue({ code: z2.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.` });
|
|
2547
3788
|
}
|
|
2548
3789
|
return;
|
|
2549
3790
|
}
|
|
2550
3791
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
2551
|
-
ctx.addIssue({ code:
|
|
3792
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
2552
3793
|
if (!r.context)
|
|
2553
|
-
ctx.addIssue({ code:
|
|
3794
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
2554
3795
|
if (!r.select)
|
|
2555
|
-
ctx.addIssue({ code:
|
|
3796
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
2556
3797
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
2557
3798
|
if (needsOptions && !r.options?.length)
|
|
2558
|
-
ctx.addIssue({ code:
|
|
3799
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
2559
3800
|
if (!needsOptions && r.options?.length)
|
|
2560
|
-
ctx.addIssue({ code:
|
|
3801
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
2561
3802
|
});
|
|
2562
3803
|
function normalizeWaiting(req) {
|
|
2563
3804
|
if (!req.waiting) return req;
|
|
@@ -2614,61 +3855,70 @@ function deriveAsk(req) {
|
|
|
2614
3855
|
...needs?.length ? { points: needs } : {}
|
|
2615
3856
|
};
|
|
2616
3857
|
}
|
|
2617
|
-
var NotifyStatusSchema =
|
|
2618
|
-
var AgentStateSchema =
|
|
2619
|
-
var SetTaskStateSchema =
|
|
2620
|
-
state:
|
|
3858
|
+
var NotifyStatusSchema = z2.enum(["pending", "answered", "ignored"]);
|
|
3859
|
+
var AgentStateSchema = z2.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
3860
|
+
var SetTaskStateSchema = z2.object({
|
|
3861
|
+
state: z2.enum(["in_progress", "completed", "needs_input"])
|
|
2621
3862
|
});
|
|
2622
|
-
var
|
|
2623
|
-
|
|
2624
|
-
|
|
3863
|
+
var SetWorkStateSchema = z2.object({
|
|
3864
|
+
workId: z2.string().uuid().optional(),
|
|
3865
|
+
notificationId: z2.string().optional(),
|
|
3866
|
+
state: SetTaskStateSchema.shape.state
|
|
3867
|
+
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
3868
|
+
message: "exactly one of workId or notificationId is required"
|
|
2625
3869
|
});
|
|
2626
|
-
var
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
3870
|
+
var TurnSchema = z2.object({
|
|
3871
|
+
prompt: z2.string(),
|
|
3872
|
+
reply: z2.string()
|
|
3873
|
+
});
|
|
3874
|
+
var UserAnswerSchema = z2.discriminatedUnion("kind", [
|
|
3875
|
+
z2.object({ kind: z2.literal("option"), optionId: z2.string(), label: z2.string().optional() }),
|
|
3876
|
+
z2.object({ kind: z2.literal("text"), text: z2.string() }),
|
|
3877
|
+
z2.object({ kind: z2.literal("ignored") }),
|
|
3878
|
+
z2.object({ kind: z2.literal("multi"), optionIds: z2.array(z2.string()), labels: z2.array(z2.string()).optional() }),
|
|
3879
|
+
z2.object({ kind: z2.literal("ranked"), optionIds: z2.array(z2.string()), labels: z2.array(z2.string()).optional() }),
|
|
3880
|
+
z2.object({ kind: z2.literal("clarify"), chunks: z2.array(z2.string()).min(1) }),
|
|
3881
|
+
z2.object({ kind: z2.literal("confirm"), approved: z2.boolean() }),
|
|
3882
|
+
z2.object({ kind: z2.literal("turns"), turns: z2.array(TurnSchema).min(1) }),
|
|
2635
3883
|
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
2636
3884
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
2637
3885
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
2638
3886
|
* card the user can reply to — the broker never overrides the user. */
|
|
2639
|
-
|
|
3887
|
+
z2.object({ kind: z2.literal("precedent"), answer: z2.string(), derivation: z2.string(), sources: z2.array(z2.string()).min(1) })
|
|
2640
3888
|
]);
|
|
2641
|
-
var IntentSchema =
|
|
3889
|
+
var IntentSchema = z2.object({
|
|
2642
3890
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
2643
3891
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
2644
3892
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
2645
3893
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2646
|
-
kind:
|
|
2647
|
-
detail:
|
|
2648
|
-
/**
|
|
2649
|
-
*
|
|
2650
|
-
* the
|
|
2651
|
-
*
|
|
2652
|
-
dueInSeconds:
|
|
3894
|
+
kind: z2.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
3895
|
+
detail: z2.string(),
|
|
3896
|
+
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
3897
|
+
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
3898
|
+
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
3899
|
+
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
3900
|
+
dueInSeconds: z2.number().int().positive().optional(),
|
|
2653
3901
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2654
3902
|
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
2655
3903
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
2656
3904
|
* about what cannot answer "is the bot looping less this week?". */
|
|
2657
|
-
fault:
|
|
3905
|
+
fault: z2.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
2658
3906
|
});
|
|
2659
|
-
var RideAlongSchema =
|
|
3907
|
+
var RideAlongSchema = z2.object({
|
|
2660
3908
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
2661
|
-
noteId:
|
|
3909
|
+
noteId: z2.string(),
|
|
2662
3910
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
2663
|
-
text:
|
|
3911
|
+
text: z2.string(),
|
|
2664
3912
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
2665
|
-
parentId:
|
|
3913
|
+
parentId: z2.string().nullable()
|
|
2666
3914
|
});
|
|
2667
|
-
var AwaitItemSchema =
|
|
2668
|
-
|
|
2669
|
-
type:
|
|
2670
|
-
parentId:
|
|
2671
|
-
notificationId:
|
|
3915
|
+
var AwaitItemSchema = z2.discriminatedUnion("type", [
|
|
3916
|
+
z2.object({
|
|
3917
|
+
type: z2.literal("reply"),
|
|
3918
|
+
parentId: z2.string(),
|
|
3919
|
+
notificationId: z2.string(),
|
|
3920
|
+
workId: z2.string().uuid().optional(),
|
|
3921
|
+
decisionId: z2.string().uuid().optional(),
|
|
2672
3922
|
answer: UserAnswerSchema,
|
|
2673
3923
|
/** E2EE: present when the answer is sealed. The server relays the opaque answer
|
|
2674
3924
|
* envelope + the plaintext `ignored` status hint; the receiving agent OPENS it
|
|
@@ -2676,126 +3926,146 @@ var AwaitItemSchema = z.discriminatedUnion("type", [
|
|
|
2676
3926
|
* against `ignored` as tampering. Absent = today's plaintext answer (in `answer`).
|
|
2677
3927
|
* On a sealed reply the plaintext `answer` is a placeholder (kind reflects only
|
|
2678
3928
|
* the `ignored` bit) — never the real content, which stays sealed. */
|
|
2679
|
-
sealed:
|
|
2680
|
-
/**
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
2683
|
-
|
|
2684
|
-
|
|
3929
|
+
sealed: z2.lazy(() => SealedAnswerSchema).optional(),
|
|
3930
|
+
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
3931
|
+
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
3932
|
+
* the notification they belong to, and the `contact` call that reaches the person
|
|
3933
|
+
* again, spelled with its real arguments. It restates nothing — deleting the brain's
|
|
3934
|
+
* acts deleted the paraphrase, and this is not it coming back: an agent that reads
|
|
3935
|
+
* "call me back after you merge" in their own words decides for itself what to do,
|
|
3936
|
+
* and now knows exactly which call to make. Absent when either half is missing —
|
|
3937
|
+
* a sentence with a hole in it is worse than no sentence. */
|
|
3938
|
+
note: z2.string().optional(),
|
|
3939
|
+
/** The call record rendered for THIS agent (`voice/record-design.md`): the words the
|
|
3940
|
+
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
3941
|
+
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
3942
|
+
* 2026-09-04): the agent reads the sentence and decides. */
|
|
3943
|
+
transcript: z2.string().optional(),
|
|
2685
3944
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
2686
3945
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
2687
|
-
covered:
|
|
3946
|
+
covered: z2.array(z2.string()).optional(),
|
|
2688
3947
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
2689
3948
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
2690
3949
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
2691
3950
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
2692
3951
|
* dropped. Absent/empty = nothing owed. */
|
|
2693
|
-
also:
|
|
3952
|
+
also: z2.array(RideAlongSchema).optional()
|
|
2694
3953
|
}),
|
|
2695
|
-
|
|
2696
|
-
type:
|
|
2697
|
-
parentId:
|
|
2698
|
-
notificationId:
|
|
2699
|
-
remindAt:
|
|
3954
|
+
z2.object({
|
|
3955
|
+
type: z2.literal("remind"),
|
|
3956
|
+
parentId: z2.string(),
|
|
3957
|
+
notificationId: z2.string(),
|
|
3958
|
+
remindAt: z2.string().datetime({ offset: true }),
|
|
2700
3959
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
2701
|
-
remindInSeconds:
|
|
3960
|
+
remindInSeconds: z2.number()
|
|
2702
3961
|
}),
|
|
2703
3962
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
2704
3963
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
2705
3964
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
2706
3965
|
* re-orient via get_thread / check_replies). */
|
|
2707
|
-
|
|
2708
|
-
type:
|
|
2709
|
-
parentId:
|
|
2710
|
-
notificationId:
|
|
3966
|
+
z2.object({
|
|
3967
|
+
type: z2.literal("superseded"),
|
|
3968
|
+
parentId: z2.string(),
|
|
3969
|
+
notificationId: z2.string()
|
|
2711
3970
|
}),
|
|
2712
3971
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
2713
3972
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
2714
3973
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
2715
3974
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
2716
3975
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
2717
|
-
|
|
2718
|
-
type:
|
|
2719
|
-
notificationId:
|
|
2720
|
-
inFlight:
|
|
2721
|
-
turn:
|
|
2722
|
-
idx:
|
|
2723
|
-
prompt:
|
|
2724
|
-
reply:
|
|
2725
|
-
acts:
|
|
3976
|
+
z2.object({
|
|
3977
|
+
type: z2.literal("partial"),
|
|
3978
|
+
notificationId: z2.string(),
|
|
3979
|
+
inFlight: z2.literal(true),
|
|
3980
|
+
turn: z2.object({
|
|
3981
|
+
idx: z2.number(),
|
|
3982
|
+
prompt: z2.string(),
|
|
3983
|
+
reply: z2.string(),
|
|
3984
|
+
acts: z2.array(IntentSchema).nullable().optional()
|
|
2726
3985
|
})
|
|
2727
3986
|
}),
|
|
2728
|
-
|
|
3987
|
+
z2.object({ type: z2.literal("idle"), also: z2.array(RideAlongSchema).optional() })
|
|
2729
3988
|
]);
|
|
2730
|
-
var CallbackTriggerSchema =
|
|
2731
|
-
var ScheduleCallbackSchema =
|
|
2732
|
-
parentId:
|
|
3989
|
+
var CallbackTriggerSchema = z2.enum(["on_done", "on_blocked", "scheduled"]);
|
|
3990
|
+
var ScheduleCallbackSchema = z2.object({
|
|
3991
|
+
parentId: z2.string().describe("The thread to call back on (from a prior contact / reply / request)."),
|
|
2733
3992
|
trigger: CallbackTriggerSchema,
|
|
2734
|
-
dueInSeconds:
|
|
2735
|
-
note:
|
|
3993
|
+
dueInSeconds: z2.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
|
|
3994
|
+
note: z2.string().optional().describe("What to tell the user when you follow up.")
|
|
2736
3995
|
});
|
|
2737
|
-
var PendingRepliesSchema =
|
|
2738
|
-
replies:
|
|
2739
|
-
|
|
2740
|
-
parentId:
|
|
2741
|
-
notificationId:
|
|
3996
|
+
var PendingRepliesSchema = z2.object({
|
|
3997
|
+
replies: z2.array(
|
|
3998
|
+
z2.object({
|
|
3999
|
+
parentId: z2.string(),
|
|
4000
|
+
notificationId: z2.string(),
|
|
4001
|
+
workId: z2.string().uuid().optional(),
|
|
4002
|
+
decisionId: z2.string().uuid().optional(),
|
|
2742
4003
|
answer: UserAnswerSchema,
|
|
2743
4004
|
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the
|
|
2744
4005
|
* pairing is E2EE — the agent opens it and re-derives the real answer. Absent =
|
|
2745
4006
|
* plaintext answer (in `answer`). See AwaitItemSchema's reply variant. */
|
|
2746
|
-
sealed:
|
|
2747
|
-
/**
|
|
2748
|
-
*
|
|
2749
|
-
|
|
2750
|
-
intents: z.array(IntentSchema).optional(),
|
|
2751
|
-
transcript: z.string().optional(),
|
|
4007
|
+
sealed: z2.lazy(() => SealedAnswerSchema).optional(),
|
|
4008
|
+
/** The call record rendered for THIS agent: the raw words the shaped answer was
|
|
4009
|
+
* mapped from, filtered to its own claims. See AwaitItemSchema's reply variant. */
|
|
4010
|
+
transcript: z2.string().optional(),
|
|
2752
4011
|
/** Coverage report (#396): which declared `points` this answer addressed. */
|
|
2753
|
-
covered:
|
|
4012
|
+
covered: z2.array(z2.string()).optional()
|
|
2754
4013
|
})
|
|
2755
4014
|
),
|
|
2756
|
-
pending:
|
|
2757
|
-
|
|
4015
|
+
pending: z2.array(
|
|
4016
|
+
z2.object({ parentId: z2.string(), notificationId: z2.string(), createdAt: z2.string() })
|
|
2758
4017
|
),
|
|
2759
4018
|
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
2760
4019
|
* sees for this session's identity. From inside a session there was no way to find out —
|
|
2761
4020
|
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
2762
4021
|
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
2763
4022
|
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
2764
|
-
you:
|
|
4023
|
+
you: z2.object({ name: z2.string(), device: z2.string().nullable(), tokenId: z2.string() }).optional(),
|
|
2765
4024
|
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
2766
4025
|
* contact on the same parentId. Keeps reappearing until you call
|
|
2767
|
-
*
|
|
2768
|
-
requests:
|
|
2769
|
-
|
|
2770
|
-
parentId:
|
|
2771
|
-
notificationId:
|
|
2772
|
-
|
|
2773
|
-
|
|
4026
|
+
* set_work_state on its workId (or notificationId once while bootstrapping). */
|
|
4027
|
+
requests: z2.array(
|
|
4028
|
+
z2.object({
|
|
4029
|
+
parentId: z2.string(),
|
|
4030
|
+
notificationId: z2.string(),
|
|
4031
|
+
workId: z2.string().uuid().optional(),
|
|
4032
|
+
text: z2.string(),
|
|
4033
|
+
createdAt: z2.string(),
|
|
2774
4034
|
/** The user seeded this request with a past conversation — call get_thread on it
|
|
2775
4035
|
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
2776
|
-
contextParentId:
|
|
4036
|
+
contextParentId: z2.string().optional(),
|
|
2777
4037
|
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
2778
4038
|
* the account — the name here — which has not been seen since it landed, so nobody
|
|
2779
4039
|
* came for it. Handed to you because you are the session that is here. Take it like
|
|
2780
|
-
* any request (
|
|
4040
|
+
* any request (set_work_state claims it, reply with contact on its parentId), and say
|
|
2781
4041
|
* whose it was, because the user chose that agent on purpose. */
|
|
2782
|
-
stranded:
|
|
4042
|
+
stranded: z2.string().optional()
|
|
2783
4043
|
})
|
|
2784
4044
|
),
|
|
4045
|
+
/** Durable outcomes currently owned by this agent. This is the Work-native queue; flat
|
|
4046
|
+
* notification lists remain during migration so old clients keep their existing view. */
|
|
4047
|
+
work: z2.array(z2.object({
|
|
4048
|
+
workId: z2.string().uuid(),
|
|
4049
|
+
parentId: z2.string().optional(),
|
|
4050
|
+
objective: z2.string().nullable(),
|
|
4051
|
+
state: z2.enum(["active", "blocked", "waiting_external"]),
|
|
4052
|
+
blockedOn: z2.array(z2.string().uuid()),
|
|
4053
|
+
updatedAt: z2.string()
|
|
4054
|
+
})).optional(),
|
|
2785
4055
|
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
2786
4056
|
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
2787
4057
|
* fulfill one by calling contact on its parentId. */
|
|
2788
4058
|
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
2789
4059
|
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
2790
|
-
also:
|
|
2791
|
-
owedCallbacks:
|
|
2792
|
-
|
|
4060
|
+
also: z2.array(RideAlongSchema).optional(),
|
|
4061
|
+
owedCallbacks: z2.array(
|
|
4062
|
+
z2.object({ parentId: z2.string(), trigger: CallbackTriggerSchema, note: z2.string() })
|
|
2793
4063
|
),
|
|
2794
4064
|
/** Work (either direction) you reported in_progress a while ago and never reported
|
|
2795
4065
|
* completed — likely left half-done by this session or a prior one that crashed or
|
|
2796
|
-
* went idle. Report a real state (
|
|
2797
|
-
stalled:
|
|
2798
|
-
|
|
4066
|
+
* went idle. Report a real state (set_work_state) or continue the work. */
|
|
4067
|
+
stalled: z2.array(
|
|
4068
|
+
z2.object({ parentId: z2.string(), notificationId: z2.string(), title: z2.string().nullable(), startedAt: z2.string() })
|
|
2799
4069
|
),
|
|
2800
4070
|
/** The queue rail (#614, pending/design.md): the same replies + requests, grouped by
|
|
2801
4071
|
* thread and ordered oldest-thread-first, so you work ONE thread at a time — fold all of
|
|
@@ -2804,123 +4074,125 @@ var PendingRepliesSchema = z.object({
|
|
|
2804
4074
|
* ride the next turn. `items` are that thread's replies/requests in arrival order; the
|
|
2805
4075
|
* full payload for each is in the flat `replies`/`requests` arrays (matched by
|
|
2806
4076
|
* notificationId). Derived, never stored — a crashed agent recomputes it exactly. */
|
|
2807
|
-
threads:
|
|
2808
|
-
|
|
2809
|
-
parentId:
|
|
2810
|
-
busy:
|
|
2811
|
-
items:
|
|
2812
|
-
|
|
2813
|
-
kind:
|
|
2814
|
-
notificationId:
|
|
2815
|
-
at:
|
|
4077
|
+
threads: z2.array(
|
|
4078
|
+
z2.object({
|
|
4079
|
+
parentId: z2.string(),
|
|
4080
|
+
busy: z2.boolean(),
|
|
4081
|
+
items: z2.array(
|
|
4082
|
+
z2.object({
|
|
4083
|
+
kind: z2.enum(["reply", "request"]),
|
|
4084
|
+
notificationId: z2.string(),
|
|
4085
|
+
at: z2.string()
|
|
2816
4086
|
})
|
|
2817
4087
|
)
|
|
2818
4088
|
})
|
|
2819
4089
|
)
|
|
2820
4090
|
});
|
|
2821
|
-
var NotifyResponseSchema =
|
|
2822
|
-
notificationId:
|
|
4091
|
+
var NotifyResponseSchema = z2.object({
|
|
4092
|
+
notificationId: z2.string(),
|
|
4093
|
+
workId: z2.string().uuid().optional(),
|
|
4094
|
+
decisionId: z2.string().uuid().optional(),
|
|
2823
4095
|
status: NotifyStatusSchema,
|
|
2824
|
-
createdAt:
|
|
4096
|
+
createdAt: z2.string().datetime(),
|
|
2825
4097
|
answer: UserAnswerSchema.optional(),
|
|
2826
|
-
answeredAt:
|
|
4098
|
+
answeredAt: z2.string().datetime().optional(),
|
|
2827
4099
|
/** Ride-alongs for THIS agent — pending work it should pick up when it's done with
|
|
2828
4100
|
* what it came for. Present on any reply, because an unwakeable agent's only
|
|
2829
4101
|
* reliable moment is one it initiated. Absent/empty = nothing owed. */
|
|
2830
|
-
also:
|
|
4102
|
+
also: z2.array(RideAlongSchema).optional()
|
|
2831
4103
|
});
|
|
2832
|
-
var NotifyPlanUnitSchema =
|
|
2833
|
-
notificationId:
|
|
4104
|
+
var NotifyPlanUnitSchema = z2.object({
|
|
4105
|
+
notificationId: z2.string(),
|
|
2834
4106
|
/** The unit's own heading, so the agent can tell which of its paragraphs this became. */
|
|
2835
|
-
title:
|
|
4107
|
+
title: z2.string(),
|
|
2836
4108
|
/** How loudly this unit was arbitrated to arrive — per unit, which is the point of units. */
|
|
2837
4109
|
level: NotifyLevelSchema,
|
|
2838
4110
|
/** Answered from something the user already decided: nobody is interrupted, and a trail card
|
|
2839
4111
|
* says so. The agent should not wait on this one. */
|
|
2840
|
-
settled:
|
|
4112
|
+
settled: z2.literal(true).optional(),
|
|
2841
4113
|
/** What this unit would need to be answerable and does not carry (#894). A PROPOSAL to the
|
|
2842
4114
|
* agent — nothing here changed the ask, and ignoring it costs nothing. */
|
|
2843
|
-
needs:
|
|
4115
|
+
needs: z2.array(z2.enum(["options", "visuals"])).optional(),
|
|
2844
4116
|
/** The SHAPE the broker would give this unit, for the agent to ratify (#886/#894). The
|
|
2845
4117
|
* split layer reads prose and can see that a paragraph is a yes/no or a pick-one — but a
|
|
2846
4118
|
* broker that DECIDES that destroys the only fact separating a statement from a real ask
|
|
2847
4119
|
* (#731), so it is offered, never applied: the unit is stored `text` until the agent
|
|
2848
4120
|
* confirms the shape (POST /notify/:id/confirm). Ignoring it costs nothing. */
|
|
2849
|
-
proposal:
|
|
4121
|
+
proposal: z2.object({
|
|
2850
4122
|
select: SelectShapeSchema,
|
|
2851
|
-
options:
|
|
4123
|
+
options: z2.array(z2.object({ label: z2.string().min(1) })).optional()
|
|
2852
4124
|
}).optional(),
|
|
2853
4125
|
/** What the broker READ this unit as wanting from the human (#952 layer 2): a `decision`
|
|
2854
4126
|
* between alternatives, an `approval` the agent is blocked on, or `knowledge` it just
|
|
2855
4127
|
* needs to know. Reported so the agent can correct a misread the same way it ratifies a
|
|
2856
4128
|
* shape — the read RAISES (a decision always asks) and never silences a question the
|
|
2857
4129
|
* agent declared (#731, #923). */
|
|
2858
|
-
wants:
|
|
4130
|
+
wants: z2.enum(["decision", "approval", "knowledge"]).optional()
|
|
2859
4131
|
});
|
|
2860
|
-
var NotifyPlanSchema =
|
|
2861
|
-
units:
|
|
4132
|
+
var NotifyPlanSchema = z2.object({
|
|
4133
|
+
units: z2.array(NotifyPlanUnitSchema),
|
|
2862
4134
|
/** Which unit is this arrival's ONE interruption (units-design.md D23). Absent means nobody
|
|
2863
4135
|
* was interrupted — every unit was either settled or quiet enough to sit in the inbox. */
|
|
2864
|
-
speaks:
|
|
4136
|
+
speaks: z2.string().optional()
|
|
2865
4137
|
});
|
|
2866
|
-
var UserResponseSchema =
|
|
2867
|
-
requestId:
|
|
4138
|
+
var UserResponseSchema = z2.object({
|
|
4139
|
+
requestId: z2.string(),
|
|
2868
4140
|
answer: UserAnswerSchema,
|
|
2869
|
-
answeredAt:
|
|
2870
|
-
|
|
2871
|
-
transcript: z.string().optional(),
|
|
4141
|
+
answeredAt: z2.string().datetime(),
|
|
4142
|
+
transcript: z2.string().optional(),
|
|
2872
4143
|
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the item was
|
|
2873
4144
|
* E2EE-sealed. Present → the server persists it opaquely and branches status on `ignored`;
|
|
2874
4145
|
* the plaintext `answer` is a placeholder (`{ kind: "ignored" }`) the server ignores for a
|
|
2875
4146
|
* sealed row. Absent = today's plaintext answer, unchanged. */
|
|
2876
|
-
sealed:
|
|
4147
|
+
sealed: z2.lazy(() => SealedAnswerSchema).optional(),
|
|
2877
4148
|
/** Coverage report (#396): which of the ask's declared `points` were addressed. */
|
|
2878
|
-
covered:
|
|
4149
|
+
covered: z2.array(z2.string()).optional()
|
|
2879
4150
|
});
|
|
2880
|
-
var VoiceKeySchema =
|
|
2881
|
-
var AgendaTurnSchema =
|
|
4151
|
+
var VoiceKeySchema = z2.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
4152
|
+
var AgendaTurnSchema = z2.object({
|
|
2882
4153
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
2883
4154
|
* the planner declares duplicates instead of asking them twice. */
|
|
2884
|
-
coveredIds:
|
|
4155
|
+
coveredIds: z2.array(z2.string()).optional(),
|
|
2885
4156
|
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
2886
4157
|
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
2887
|
-
info:
|
|
2888
|
-
question:
|
|
4158
|
+
info: z2.array(z2.string().min(1)).max(3).default([]),
|
|
4159
|
+
question: z2.string().min(1).nullable(),
|
|
2889
4160
|
/** True on the one turn carrying the agent's own declared question. */
|
|
2890
|
-
asks:
|
|
4161
|
+
asks: z2.boolean().optional(),
|
|
2891
4162
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
2892
4163
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
2893
4164
|
* which route nothing. */
|
|
2894
|
-
claimId:
|
|
4165
|
+
claimId: z2.string().optional(),
|
|
2895
4166
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
2896
|
-
voice:
|
|
4167
|
+
voice: z2.string().optional(),
|
|
2897
4168
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
2898
4169
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
2899
4170
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
2900
4171
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
2901
|
-
agent:
|
|
4172
|
+
agent: z2.string().optional(),
|
|
4173
|
+
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
4174
|
+
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
4175
|
+
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
4176
|
+
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
4177
|
+
* speaks for several agents — the turn is the only place that knows which. */
|
|
4178
|
+
agentId: z2.string().optional(),
|
|
2902
4179
|
select: SelectShapeSchema.optional(),
|
|
2903
|
-
options:
|
|
2904
|
-
/** The seat was satisfied by the call's own ACCOUNT (replan-design.md, user_info): the
|
|
2905
|
-
* caller already answered this claim in an earlier utterance, quoted here VERBATIM —
|
|
2906
|
-
* the bot speaks the turn's short confirmation, posts these words as the claim's
|
|
2907
|
-
* answer, and never re-asks. Grounded at parse time: an invented settle is #796. */
|
|
2908
|
-
settle: z.string().optional(),
|
|
4180
|
+
options: z2.array(OptionSchema.omit({ id: true })).optional(),
|
|
2909
4181
|
/** Pacing (#826, owner 2026-08-03: "how fast we move through them ... are parameters"):
|
|
2910
4182
|
* seconds the floor stays open after this turn speaks. Absent = the bot's defaults
|
|
2911
4183
|
* (the beat for context, the answer window for asks). Clamped bot-side. */
|
|
2912
|
-
pace:
|
|
4184
|
+
pace: z2.number().positive().optional(),
|
|
2913
4185
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
2914
4186
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
2915
4187
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
2916
|
-
blocking:
|
|
4188
|
+
blocking: z2.boolean().optional()
|
|
2917
4189
|
});
|
|
2918
4190
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
2919
|
-
var InboxItemSchema =
|
|
2920
|
-
id:
|
|
4191
|
+
var InboxItemSchema = z2.object({
|
|
4192
|
+
id: z2.string(),
|
|
2921
4193
|
/** The conversation thread + connection this item lives on. Present on the replied
|
|
2922
4194
|
* detail — they power History's "Continue" / "New session from this" (#57/#251). */
|
|
2923
|
-
parentId:
|
|
4195
|
+
parentId: z2.string().optional(),
|
|
2924
4196
|
/** THE ARRIVAL this row is one unit of (`notifications.ask_id` → `asks`). A claim is one
|
|
2925
4197
|
* arrival and its units are N rows of it, so this — not `parentId` — is what makes a
|
|
2926
4198
|
* multi-part notification one thing on screen. The thread is the whole CONVERSATION: it
|
|
@@ -2928,13 +4200,13 @@ var InboxItemSchema = z.object({
|
|
|
2928
4200
|
* unrelated updates as a single "12-part request". Absent on rows written before the
|
|
2929
4201
|
* `asks` table, and on anything that never went through `notify` — both fall back to the
|
|
2930
4202
|
* thread, which is what the client did for all rows until now. */
|
|
2931
|
-
askId:
|
|
4203
|
+
askId: z2.string().optional(),
|
|
2932
4204
|
/** WHERE this unit sat in the message it was cut from (`notifications.seq`). The batch
|
|
2933
4205
|
* shares one `created_at` to the microsecond, so without it the author's order is
|
|
2934
4206
|
* unrecoverable client-side — a four-paragraph briefing rendered opening-paragraph-last
|
|
2935
4207
|
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
2936
4208
|
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
2937
|
-
seq:
|
|
4209
|
+
seq: z2.number().int().optional(),
|
|
2938
4210
|
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
2939
4211
|
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
2940
4212
|
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
@@ -2943,13 +4215,13 @@ var InboxItemSchema = z.object({
|
|
|
2943
4215
|
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
2944
4216
|
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
2945
4217
|
* the one part of its own arrival a single row cannot answer. */
|
|
2946
|
-
units:
|
|
2947
|
-
tokenId:
|
|
4218
|
+
units: z2.number().int().positive().optional(),
|
|
4219
|
+
tokenId: z2.string().optional(),
|
|
2948
4220
|
status: NotifyStatusSchema,
|
|
2949
4221
|
context: ContextSchema,
|
|
2950
|
-
options:
|
|
4222
|
+
options: z2.array(OptionSchema).optional(),
|
|
2951
4223
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
2952
|
-
points:
|
|
4224
|
+
points: z2.array(z2.string()).optional(),
|
|
2953
4225
|
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
2954
4226
|
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
2955
4227
|
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
@@ -2958,12 +4230,12 @@ var InboxItemSchema = z.object({
|
|
|
2958
4230
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
2959
4231
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
2960
4232
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
2961
|
-
asks:
|
|
4233
|
+
asks: z2.boolean().optional(),
|
|
2962
4234
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
2963
4235
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
2964
4236
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
2965
4237
|
* which must never CLAIM stalled. */
|
|
2966
|
-
lastSeenAt:
|
|
4238
|
+
lastSeenAt: z2.string().optional(),
|
|
2967
4239
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
2968
4240
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
2969
4241
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -2973,56 +4245,53 @@ var InboxItemSchema = z.object({
|
|
|
2973
4245
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
2974
4246
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
2975
4247
|
* `createdAt`. */
|
|
2976
|
-
agentStateAt:
|
|
2977
|
-
agenda:
|
|
2978
|
-
|
|
2979
|
-
* ("call back after lunch") — shown so they can see the commitment was captured. */
|
|
2980
|
-
intents: z.array(IntentSchema).optional(),
|
|
2981
|
-
visuals: z.array(VisualSchema).optional(),
|
|
4248
|
+
agentStateAt: z2.string().datetime().optional(),
|
|
4249
|
+
agenda: z2.array(AgendaTurnSchema).optional(),
|
|
4250
|
+
visuals: z2.array(VisualSchema).optional(),
|
|
2982
4251
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
2983
4252
|
* agent's suggestion, or a default silly name). */
|
|
2984
|
-
name:
|
|
4253
|
+
name: z2.string(),
|
|
2985
4254
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
2986
4255
|
voice: VoiceKeySchema.optional(),
|
|
2987
|
-
repo:
|
|
2988
|
-
branch:
|
|
2989
|
-
createdAt:
|
|
2990
|
-
snoozedUntil:
|
|
4256
|
+
repo: z2.string().optional(),
|
|
4257
|
+
branch: z2.string().optional(),
|
|
4258
|
+
createdAt: z2.string().datetime(),
|
|
4259
|
+
snoozedUntil: z2.string().datetime().optional(),
|
|
2991
4260
|
agentState: AgentStateSchema.default("idle"),
|
|
2992
4261
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
2993
4262
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
2994
4263
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
2995
|
-
turn:
|
|
4264
|
+
turn: z2.enum(["you", "agent"]).default("you"),
|
|
2996
4265
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
2997
4266
|
* the agent (provider-agnostic; set server-side). Absent = no hard error, though the
|
|
2998
4267
|
* client may still flag a stall by age. Drives the inbox error badge + Retry. */
|
|
2999
|
-
error:
|
|
3000
|
-
clarifies:
|
|
4268
|
+
error: z2.string().optional(),
|
|
4269
|
+
clarifies: z2.string().optional(),
|
|
3001
4270
|
/** The ring ladder ran out while this was still pending — we tried to reach you and
|
|
3002
4271
|
* STOPPED trying (`arbitration/arbitrate.ts` `nextRing` → `stop`). Distinct from an
|
|
3003
4272
|
* agent with nothing to say, which the roster drew identically until now: "nothing to
|
|
3004
4273
|
* say" and "gave up saying it" are opposite situations wearing the same face
|
|
3005
4274
|
* (navigation-design.md, gap 1). False for anything that never rang. */
|
|
3006
|
-
gaveUp:
|
|
4275
|
+
gaveUp: z2.boolean().default(false),
|
|
3007
4276
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
3008
4277
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
3009
4278
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
3010
4279
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
3011
4280
|
* feedback ABOUT. */
|
|
3012
|
-
why:
|
|
4281
|
+
why: z2.object({
|
|
3013
4282
|
asked: NotifyLevelSchema,
|
|
3014
4283
|
got: NotifyLevelSchema,
|
|
3015
|
-
because:
|
|
3016
|
-
line:
|
|
4284
|
+
because: z2.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
4285
|
+
line: z2.string()
|
|
3017
4286
|
}).optional(),
|
|
3018
|
-
select:
|
|
3019
|
-
confirmStyle:
|
|
4287
|
+
select: z2.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
4288
|
+
confirmStyle: z2.enum(["yesno", "approve"]).default("yesno").describe(
|
|
3020
4289
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
3021
4290
|
),
|
|
3022
4291
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
3023
4292
|
* urgency (see the main README's "premier use case" + notify/states.md). Drives the
|
|
3024
4293
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
3025
|
-
blocking:
|
|
4294
|
+
blocking: z2.boolean().default(false),
|
|
3026
4295
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
3027
4296
|
answer: UserAnswerSchema.optional(),
|
|
3028
4297
|
/** E2EE (text lane): the sealed content this item carries when the pairing is E2EE, in
|
|
@@ -3031,39 +4300,39 @@ var InboxItemSchema = z.object({
|
|
|
3031
4300
|
* NotifyRequest.envelope. The native app decrypts it locally via openNotification;
|
|
3032
4301
|
* web / an un-enrolled device can't and renders a locked placeholder. Absent = today's
|
|
3033
4302
|
* plaintext item (context carries the cleartext), so plaintext items are unchanged. */
|
|
3034
|
-
envelope:
|
|
3035
|
-
context:
|
|
3036
|
-
options:
|
|
3037
|
-
visuals:
|
|
4303
|
+
envelope: z2.object({
|
|
4304
|
+
context: z2.lazy(() => EnvelopeSchema).optional(),
|
|
4305
|
+
options: z2.lazy(() => EnvelopeSchema).optional(),
|
|
4306
|
+
visuals: z2.lazy(() => EnvelopeSchema).optional()
|
|
3038
4307
|
}).optional(),
|
|
3039
4308
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
3040
4309
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
3041
4310
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
3042
|
-
agentX25519:
|
|
4311
|
+
agentX25519: z2.string().optional()
|
|
3043
4312
|
});
|
|
3044
|
-
var SnoozeRequestSchema =
|
|
3045
|
-
requestId:
|
|
3046
|
-
until:
|
|
4313
|
+
var SnoozeRequestSchema = z2.object({
|
|
4314
|
+
requestId: z2.string(),
|
|
4315
|
+
until: z2.string().datetime()
|
|
3047
4316
|
});
|
|
3048
4317
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
3049
|
-
var PushTokenSchema =
|
|
3050
|
-
voipToken:
|
|
3051
|
-
alertToken:
|
|
3052
|
-
fcmToken:
|
|
3053
|
-
platform:
|
|
4318
|
+
var PushTokenSchema = z2.object({
|
|
4319
|
+
voipToken: z2.string().min(1).optional(),
|
|
4320
|
+
alertToken: z2.string().min(1).optional(),
|
|
4321
|
+
fcmToken: z2.string().min(1).optional(),
|
|
4322
|
+
platform: z2.enum(["ios", "android"])
|
|
3054
4323
|
}).superRefine((v, ctx) => {
|
|
3055
4324
|
if (v.platform !== "ios") return;
|
|
3056
4325
|
for (const field of ["voipToken", "alertToken"]) {
|
|
3057
4326
|
const token = v[field];
|
|
3058
4327
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
3059
4328
|
ctx.addIssue({
|
|
3060
|
-
code:
|
|
4329
|
+
code: z2.ZodIssueCode.custom,
|
|
3061
4330
|
path: [field],
|
|
3062
4331
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
3063
4332
|
});
|
|
3064
4333
|
}
|
|
3065
4334
|
});
|
|
3066
|
-
var MissedCallSchema =
|
|
4335
|
+
var MissedCallSchema = z2.enum([
|
|
3067
4336
|
"retry_10m",
|
|
3068
4337
|
"retry_30m",
|
|
3069
4338
|
"retry_60m",
|
|
@@ -3073,26 +4342,32 @@ var MissedCallSchema = z.enum([
|
|
|
3073
4342
|
"inbox",
|
|
3074
4343
|
"dismiss"
|
|
3075
4344
|
]);
|
|
3076
|
-
var BrokerTuningSchema =
|
|
4345
|
+
var BrokerTuningSchema = z2.object({
|
|
3077
4346
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
3078
|
-
ackVerbosity:
|
|
4347
|
+
ackVerbosity: z2.enum(["normal", "none"]).optional(),
|
|
3079
4348
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
3080
4349
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
3081
|
-
clarifyEagerness:
|
|
4350
|
+
clarifyEagerness: z2.enum(["low", "normal", "high"]).optional(),
|
|
3082
4351
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
3083
|
-
phrasebook:
|
|
4352
|
+
phrasebook: z2.array(z2.object({ say: z2.string().min(1).max(60), mean: z2.string().min(1).max(120) })).max(24).optional(),
|
|
4353
|
+
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
4354
|
+
* which is every account today — means the agent's own words decide, per ask: a call
|
|
4355
|
+
* about an English ask opens in English. This is the only thing that overrides that,
|
|
4356
|
+
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
4357
|
+
* Set per user (no UI yet), like `voiceTuning`. */
|
|
4358
|
+
language: z2.enum(["en", "es"]).optional()
|
|
3084
4359
|
});
|
|
3085
|
-
var UserSettingsSchema =
|
|
3086
|
-
permissions:
|
|
3087
|
-
call:
|
|
3088
|
-
banner:
|
|
3089
|
-
push:
|
|
4360
|
+
var UserSettingsSchema = z2.object({
|
|
4361
|
+
permissions: z2.object({
|
|
4362
|
+
call: z2.boolean(),
|
|
4363
|
+
banner: z2.boolean(),
|
|
4364
|
+
push: z2.boolean()
|
|
3090
4365
|
}),
|
|
3091
|
-
sessionMode:
|
|
3092
|
-
silentPush:
|
|
3093
|
-
autoCallback:
|
|
4366
|
+
sessionMode: z2.enum(["default", "all_calls", "silent"]),
|
|
4367
|
+
silentPush: z2.boolean(),
|
|
4368
|
+
autoCallback: z2.boolean(),
|
|
3094
4369
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
3095
|
-
improveConsent:
|
|
4370
|
+
improveConsent: z2.boolean(),
|
|
3096
4371
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
3097
4372
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
3098
4373
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -3100,7 +4375,7 @@ var UserSettingsSchema = z.object({
|
|
|
3100
4375
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
3101
4376
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
3102
4377
|
* write, 'hosted' on read (see store.ts). */
|
|
3103
|
-
voiceMode:
|
|
4378
|
+
voiceMode: z2.enum(["hosted", "on_device"]).optional(),
|
|
3104
4379
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
3105
4380
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
3106
4381
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -3108,10 +4383,10 @@ var UserSettingsSchema = z.object({
|
|
|
3108
4383
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
3109
4384
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
3110
4385
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
3111
|
-
voiceTuning:
|
|
4386
|
+
voiceTuning: z2.record(z2.string(), z2.union([z2.number(), z2.string()])).optional(),
|
|
3112
4387
|
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
3113
4388
|
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
3114
|
-
pstnCalls:
|
|
4389
|
+
pstnCalls: z2.boolean().optional(),
|
|
3115
4390
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
3116
4391
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
3117
4392
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -3120,50 +4395,50 @@ var UserSettingsSchema = z.object({
|
|
|
3120
4395
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
3121
4396
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
3122
4397
|
* call — honest about what we know. */
|
|
3123
|
-
timezone:
|
|
4398
|
+
timezone: z2.string().min(1).max(64).optional(),
|
|
3124
4399
|
/** Account E2EE state (text lane): 'off' (default) = today's plaintext; 'on' =
|
|
3125
4400
|
* content is sealed end-to-end between the local agent and the phone. Like
|
|
3126
4401
|
* voiceMode, OPTIONAL and NOT defaulted so a stale client PATCHing the full
|
|
3127
4402
|
* settings object without it can't silently flip the account's E2EE state.
|
|
3128
4403
|
* Absent = leave unchanged on write, 'off' on read (see store.ts). The demo
|
|
3129
4404
|
* account is plaintext by construction and refuses any non-'off' value. */
|
|
3130
|
-
e2eeMode:
|
|
4405
|
+
e2eeMode: z2.enum(["off", "on"]).optional(),
|
|
3131
4406
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
3132
4407
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
3133
4408
|
broker: BrokerTuningSchema.optional()
|
|
3134
4409
|
});
|
|
3135
|
-
var HistoryItemSchema =
|
|
3136
|
-
id:
|
|
3137
|
-
parentId:
|
|
4410
|
+
var HistoryItemSchema = z2.object({
|
|
4411
|
+
id: z2.string(),
|
|
4412
|
+
parentId: z2.string(),
|
|
3138
4413
|
/** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
|
|
3139
|
-
initiator:
|
|
3140
|
-
title:
|
|
4414
|
+
initiator: z2.enum(["user", "agent"]),
|
|
4415
|
+
title: z2.string(),
|
|
3141
4416
|
/** The agent on the other end (its name). */
|
|
3142
|
-
name:
|
|
3143
|
-
createdAt:
|
|
4417
|
+
name: z2.string(),
|
|
4418
|
+
createdAt: z2.string(),
|
|
3144
4419
|
/** When the agent fetched your request (user→agent only). */
|
|
3145
|
-
agentAckedAt:
|
|
4420
|
+
agentAckedAt: z2.string().nullable(),
|
|
3146
4421
|
/** When you answered the agent's notification (agent→user only). */
|
|
3147
|
-
humanAckedAt:
|
|
4422
|
+
humanAckedAt: z2.string().nullable()
|
|
3148
4423
|
});
|
|
3149
4424
|
var ACTIVITY_LINES = 2;
|
|
3150
4425
|
var ACTIVITY_LINE_MAX = 80;
|
|
3151
|
-
var AgentActivitySchema =
|
|
4426
|
+
var AgentActivitySchema = z2.object({
|
|
3152
4427
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
3153
|
-
lines:
|
|
4428
|
+
lines: z2.array(z2.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
3154
4429
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
3155
4430
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
3156
|
-
at:
|
|
4431
|
+
at: z2.string().datetime()
|
|
3157
4432
|
});
|
|
3158
|
-
var ConnectionSummarySchema =
|
|
4433
|
+
var ConnectionSummarySchema = z2.object({
|
|
3159
4434
|
/** The connection = the agent's token id (used to address a request). */
|
|
3160
|
-
id:
|
|
3161
|
-
device:
|
|
4435
|
+
id: z2.string(),
|
|
4436
|
+
device: z2.string().nullable(),
|
|
3162
4437
|
/** The agent's display name (the single pairing name). */
|
|
3163
|
-
name:
|
|
4438
|
+
name: z2.string(),
|
|
3164
4439
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
3165
4440
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
3166
|
-
provider:
|
|
4441
|
+
provider: z2.string().nullable(),
|
|
3167
4442
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
3168
4443
|
voice: VoiceKeySchema.nullable(),
|
|
3169
4444
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -3174,19 +4449,19 @@ var ConnectionSummarySchema = z.object({
|
|
|
3174
4449
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
3175
4450
|
* set once must not overrule a rule they set about one agent. */
|
|
3176
4451
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
3177
|
-
createdAt:
|
|
4452
|
+
createdAt: z2.string().datetime(),
|
|
3178
4453
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
3179
4454
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
3180
|
-
lastContactAt:
|
|
4455
|
+
lastContactAt: z2.string().datetime().nullable(),
|
|
3181
4456
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
3182
4457
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
3183
|
-
lastSeenAt:
|
|
4458
|
+
lastSeenAt: z2.string().datetime().nullable().optional(),
|
|
3184
4459
|
/** What a live desktop can run (companion.md §2.2), advertised on its heartbeat:
|
|
3185
4460
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
3186
4461
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
3187
|
-
runtime:
|
|
3188
|
-
harnesses:
|
|
3189
|
-
workspaces:
|
|
4462
|
+
runtime: z2.object({
|
|
4463
|
+
harnesses: z2.array(z2.object({ name: z2.string(), label: z2.string(), status: z2.string() })).optional(),
|
|
4464
|
+
workspaces: z2.array(z2.string()).optional()
|
|
3190
4465
|
}).optional(),
|
|
3191
4466
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
3192
4467
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -3195,245 +4470,305 @@ var ConnectionSummarySchema = z.object({
|
|
|
3195
4470
|
activity: AgentActivitySchema.optional(),
|
|
3196
4471
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
3197
4472
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
3198
|
-
managed:
|
|
4473
|
+
managed: z2.boolean()
|
|
3199
4474
|
});
|
|
3200
|
-
var MoveRingSchema =
|
|
3201
|
-
var MoveSchema =
|
|
3202
|
-
id:
|
|
4475
|
+
var MoveRingSchema = z2.enum(["home", "travels", "retired", "quarantined"]);
|
|
4476
|
+
var MoveSchema = z2.object({
|
|
4477
|
+
id: z2.string(),
|
|
3203
4478
|
/** The reusable question, as distill normalized it. */
|
|
3204
|
-
question:
|
|
4479
|
+
question: z2.string(),
|
|
3205
4480
|
/** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
|
|
3206
|
-
answer:
|
|
4481
|
+
answer: z2.string(),
|
|
3207
4482
|
/** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
|
|
3208
4483
|
* told so, and the ruling only derives essentially the same question in the same scope. */
|
|
3209
|
-
rationale:
|
|
4484
|
+
rationale: z2.string().nullable(),
|
|
3210
4485
|
/** Where the ruling lives: a repo/workspace, or 'global'. */
|
|
3211
|
-
scope:
|
|
4486
|
+
scope: z2.string(),
|
|
3212
4487
|
ring: MoveRingSchema,
|
|
3213
4488
|
/** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
|
|
3214
|
-
pinned:
|
|
4489
|
+
pinned: z2.boolean(),
|
|
3215
4490
|
/** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
|
|
3216
4491
|
* break is visible instead of a pin silently disappearing. */
|
|
3217
|
-
pinBroken:
|
|
4492
|
+
pinBroken: z2.boolean(),
|
|
3218
4493
|
/** When the ruling was distilled. */
|
|
3219
|
-
learnedAt:
|
|
4494
|
+
learnedAt: z2.string(),
|
|
3220
4495
|
/** Last time it answered an ask. Null = never fired. */
|
|
3221
|
-
lastUsedAt:
|
|
4496
|
+
lastUsedAt: z2.string().nullable(),
|
|
3222
4497
|
/** How many asks it has answered. Instrumentation — deliberately NOT an input to the
|
|
3223
4498
|
* evidence curve: firing says the question keeps arising, not that the ruling is right. */
|
|
3224
|
-
usedCount:
|
|
4499
|
+
usedCount: z2.number(),
|
|
3225
4500
|
/** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
|
|
3226
|
-
confirms:
|
|
4501
|
+
confirms: z2.number(),
|
|
3227
4502
|
/** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
|
|
3228
4503
|
* Linear and priced above the entire confirmation budget, so any full counter wins. */
|
|
3229
|
-
counters:
|
|
4504
|
+
counters: z2.number(),
|
|
3230
4505
|
/** The agent that asked the question this move came from, when known. Null for a move
|
|
3231
4506
|
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
3232
|
-
learnedFrom:
|
|
4507
|
+
learnedFrom: z2.object({ id: z2.string(), name: z2.string() }).nullable()
|
|
3233
4508
|
});
|
|
3234
|
-
var CreateRequestSchema =
|
|
4509
|
+
var CreateRequestSchema = z2.object({
|
|
3235
4510
|
/** The connection (token id) to send to, from GET /api/tokens. */
|
|
3236
|
-
tokenId:
|
|
4511
|
+
tokenId: z2.string(),
|
|
3237
4512
|
/** The user's message to the agent. */
|
|
3238
|
-
text:
|
|
4513
|
+
text: z2.string().min(1),
|
|
3239
4514
|
/** Land the request on an existing conversation thread (History → "Continue")
|
|
3240
4515
|
* instead of minting a fresh one. Must belong to the requesting user. */
|
|
3241
|
-
parentId:
|
|
4516
|
+
parentId: z2.string().optional(),
|
|
3242
4517
|
/** Point the agent at a past conversation (possibly with a different agent) as
|
|
3243
4518
|
* starting context (History → "New session from this"). A reference, not a copy —
|
|
3244
4519
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
3245
|
-
contextParentId:
|
|
4520
|
+
contextParentId: z2.string().optional()
|
|
3246
4521
|
});
|
|
3247
|
-
var HandoffSchema =
|
|
4522
|
+
var HandoffSchema = z2.object({
|
|
4523
|
+
/** Move this existing outcome to `target` without reminting it. Requires `target`. */
|
|
4524
|
+
workId: z2.string().uuid().optional(),
|
|
3248
4525
|
/** Land the note on an existing thread; omitted mints a fresh one. */
|
|
3249
|
-
parentId:
|
|
4526
|
+
parentId: z2.string().uuid().optional(),
|
|
3250
4527
|
/** One-line headline of the working context handed off. */
|
|
3251
|
-
title:
|
|
4528
|
+
title: z2.string().min(1),
|
|
3252
4529
|
/** The brief — standalone notes the successor reads (what was done, what's left, links). */
|
|
3253
|
-
notes:
|
|
4530
|
+
notes: z2.array(z2.string().min(1)).min(1),
|
|
3254
4531
|
/** A sibling connection to dispatch directly to (token id or agent nickname). Same-account
|
|
3255
4532
|
* only; omit to leave the thread for the user to hand off in the app. */
|
|
3256
|
-
target:
|
|
4533
|
+
target: z2.string().optional(),
|
|
3257
4534
|
/** Write the note as a RECAP (kind:'recap', #617): a summary turn that supersedes the
|
|
3258
4535
|
* thread's earlier turns for rehydration — get_thread returns the latest recap + only
|
|
3259
4536
|
* the turns after it. Handoff-to-a-successor and handoff-to-yourself-later are the
|
|
3260
4537
|
* same primitive; a recap is one whose audience includes you. */
|
|
3261
|
-
recap:
|
|
4538
|
+
recap: z2.boolean().optional()
|
|
4539
|
+
}).refine((value) => !value.workId || Boolean(value.target), {
|
|
4540
|
+
message: "target is required when handing off Work",
|
|
4541
|
+
path: ["target"]
|
|
3262
4542
|
});
|
|
3263
|
-
var NoteSourceSchema =
|
|
3264
|
-
var NoteStatusSchema =
|
|
3265
|
-
var NoteRepeatSchema =
|
|
3266
|
-
var DecisionSchema =
|
|
3267
|
-
id:
|
|
4543
|
+
var NoteSourceSchema = z2.enum(["app", "call"]);
|
|
4544
|
+
var NoteStatusSchema = z2.enum(["open", "assigned", "in_progress", "done"]);
|
|
4545
|
+
var NoteRepeatSchema = z2.enum(["once", "until_done"]);
|
|
4546
|
+
var DecisionSchema = z2.object({
|
|
4547
|
+
id: z2.string(),
|
|
3268
4548
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
3269
4549
|
* extensibility seam — any conversation can accrue decisions). */
|
|
3270
|
-
noteId:
|
|
4550
|
+
noteId: z2.string().nullable(),
|
|
3271
4551
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
3272
|
-
question:
|
|
4552
|
+
question: z2.string(),
|
|
3273
4553
|
/** The user's ruling; null while the question is open. */
|
|
3274
|
-
answer:
|
|
3275
|
-
decidedAt:
|
|
3276
|
-
createdAt:
|
|
4554
|
+
answer: z2.string().nullable(),
|
|
4555
|
+
decidedAt: z2.string().nullable(),
|
|
4556
|
+
createdAt: z2.string()
|
|
3277
4557
|
});
|
|
3278
|
-
var NoteSchema =
|
|
3279
|
-
id:
|
|
4558
|
+
var NoteSchema = z2.object({
|
|
4559
|
+
id: z2.string(),
|
|
3280
4560
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
3281
|
-
title:
|
|
4561
|
+
title: z2.string(),
|
|
3282
4562
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
3283
|
-
intent:
|
|
4563
|
+
intent: z2.string(),
|
|
3284
4564
|
source: NoteSourceSchema,
|
|
3285
4565
|
status: NoteStatusSchema,
|
|
3286
4566
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
3287
|
-
assignee:
|
|
4567
|
+
assignee: z2.string().nullable(),
|
|
3288
4568
|
/** The request thread minted at assignment; null until assigned. */
|
|
3289
|
-
parentId:
|
|
4569
|
+
parentId: z2.string().nullable(),
|
|
3290
4570
|
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3291
|
-
* call — never a deadline
|
|
4571
|
+
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
4572
|
+
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
4573
|
+
* a ring at that time); after that it rides like any other. Null = "the very next
|
|
3292
4574
|
* call", the right reading of "remind me to…" with no time attached. */
|
|
3293
4575
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3294
4576
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
3295
4577
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
3296
|
-
dueAt:
|
|
4578
|
+
dueAt: z2.string().nullable().default(null),
|
|
3297
4579
|
repeat: NoteRepeatSchema.default("once"),
|
|
3298
4580
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
3299
|
-
rides:
|
|
3300
|
-
lastRideAt:
|
|
3301
|
-
createdAt:
|
|
4581
|
+
rides: z2.number().int().default(0),
|
|
4582
|
+
lastRideAt: z2.string().nullable().default(null),
|
|
4583
|
+
createdAt: z2.string()
|
|
3302
4584
|
});
|
|
3303
|
-
var CreateNoteSchema =
|
|
4585
|
+
var CreateNoteSchema = z2.object({
|
|
3304
4586
|
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
3305
|
-
text:
|
|
4587
|
+
text: z2.string().min(1).max(4e3),
|
|
3306
4588
|
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
3307
4589
|
* their next call instead of being handed to an agent. Everything else about the
|
|
3308
4590
|
* note is identical; this is the one parameter that separates the two. */
|
|
3309
|
-
forMe:
|
|
4591
|
+
forMe: z2.boolean().optional(),
|
|
3310
4592
|
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
3311
|
-
dueAt:
|
|
4593
|
+
dueAt: z2.string().datetime().optional(),
|
|
3312
4594
|
repeat: NoteRepeatSchema.optional()
|
|
3313
4595
|
});
|
|
3314
|
-
var RecordDecisionSchema =
|
|
4596
|
+
var RecordDecisionSchema = z2.object({
|
|
3315
4597
|
/** An open decision (from /clarify) to answer. */
|
|
3316
|
-
decisionId:
|
|
4598
|
+
decisionId: z2.string().optional(),
|
|
3317
4599
|
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
3318
|
-
question:
|
|
4600
|
+
question: z2.string().min(1).max(500).optional(),
|
|
3319
4601
|
/** The ruling. */
|
|
3320
|
-
answer:
|
|
4602
|
+
answer: z2.string().min(1).max(2e3)
|
|
3321
4603
|
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
3322
|
-
var AssignNoteSchema =
|
|
4604
|
+
var AssignNoteSchema = z2.object({
|
|
3323
4605
|
/** An EXISTING agent: token id or nickname. Omit when spawning fresh. */
|
|
3324
|
-
target:
|
|
4606
|
+
target: z2.string().min(1).optional(),
|
|
3325
4607
|
/** Spawn a NEW session for this note (companion.md §2.2): the assignee doesn't
|
|
3326
4608
|
* exist yet — mint it on a live desktop that advertises the harness+workspace,
|
|
3327
4609
|
* named after the note. The brief arrives as its opening request. */
|
|
3328
|
-
spawn:
|
|
3329
|
-
hostTokenId:
|
|
3330
|
-
harness:
|
|
3331
|
-
workspace:
|
|
4610
|
+
spawn: z2.object({
|
|
4611
|
+
hostTokenId: z2.string().uuid(),
|
|
4612
|
+
harness: z2.string().min(1),
|
|
4613
|
+
workspace: z2.string().min(1)
|
|
3332
4614
|
}).optional()
|
|
3333
4615
|
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
3334
|
-
var
|
|
3335
|
-
var
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
4616
|
+
var TriageVerdictSchema = z2.enum(["keep", "stale", "close", "assign"]);
|
|
4617
|
+
var TriageItemSchema = z2.object({
|
|
4618
|
+
noteId: z2.string(),
|
|
4619
|
+
/** The note's headline at run time. */
|
|
4620
|
+
title: z2.string(),
|
|
4621
|
+
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
4622
|
+
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
4623
|
+
* Never a model's reasoning transcript, never an id. */
|
|
4624
|
+
why: z2.string()
|
|
4625
|
+
});
|
|
4626
|
+
var TriageAssignmentSchema = z2.object({
|
|
4627
|
+
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
4628
|
+
agent: z2.string(),
|
|
4629
|
+
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
4630
|
+
* same reason as `title`: the card must render from the proposal alone. */
|
|
4631
|
+
agentName: z2.string(),
|
|
4632
|
+
notes: z2.array(TriageItemSchema)
|
|
4633
|
+
});
|
|
4634
|
+
var TriageStatusSchema = z2.enum(["open", "superseded", "dismissed"]);
|
|
4635
|
+
var SubmitTriageSchema = z2.object({
|
|
4636
|
+
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
4637
|
+
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
4638
|
+
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
4639
|
+
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
4640
|
+
provider: z2.string().min(1).max(60),
|
|
4641
|
+
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
4642
|
+
model: z2.string().max(200).nullable().optional(),
|
|
4643
|
+
/** How many open notes the run actually looked at — the denominator on the phone
|
|
4644
|
+
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
4645
|
+
reviewed: z2.number().int().min(0).max(1e4).default(0),
|
|
4646
|
+
close: z2.array(TriageItemSchema).max(200).default([]),
|
|
4647
|
+
stale: z2.array(TriageItemSchema).max(200).default([]),
|
|
4648
|
+
assign: z2.array(TriageAssignmentSchema).max(50).default([])
|
|
3339
4649
|
});
|
|
3340
|
-
var
|
|
3341
|
-
|
|
4650
|
+
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
4651
|
+
id: z2.string(),
|
|
4652
|
+
runAt: z2.string(),
|
|
4653
|
+
status: TriageStatusSchema,
|
|
4654
|
+
model: z2.string().nullable().default(null)
|
|
4655
|
+
});
|
|
4656
|
+
var AcceptTriageSchema = z2.discriminatedUnion("group", [
|
|
4657
|
+
z2.object({ group: z2.literal("close"), noteIds: z2.array(z2.string()).max(200).optional() }),
|
|
4658
|
+
z2.object({ group: z2.literal("stale"), noteIds: z2.array(z2.string()).max(200).optional() }),
|
|
4659
|
+
z2.object({
|
|
4660
|
+
group: z2.literal("assign"),
|
|
4661
|
+
agent: z2.string().min(1),
|
|
4662
|
+
noteIds: z2.array(z2.string()).max(200).optional()
|
|
4663
|
+
})
|
|
4664
|
+
]);
|
|
4665
|
+
var AcceptTriageResultSchema = z2.object({
|
|
4666
|
+
accepted: z2.array(z2.string()),
|
|
4667
|
+
failed: z2.array(z2.object({ noteId: z2.string(), reason: z2.string() }))
|
|
4668
|
+
});
|
|
4669
|
+
var DeliveryModeSchema = z2.enum(["poll", "self_hosted"]);
|
|
4670
|
+
var RegisterDeliverySchema = z2.object({ mode: DeliveryModeSchema });
|
|
4671
|
+
var OAuthStartSchema = z2.object({
|
|
4672
|
+
provider: z2.enum(["cma"]),
|
|
4673
|
+
returnTo: z2.string().min(1)
|
|
4674
|
+
});
|
|
4675
|
+
var DeliveryConfigSchema = z2.object({
|
|
4676
|
+
tokenId: z2.string(),
|
|
3342
4677
|
mode: DeliveryModeSchema,
|
|
3343
4678
|
/** null when the server has no SUPABASE_ANON_KEY set — the listener then falls
|
|
3344
4679
|
* back to its own PAIGY_SUPABASE_URL / PAIGY_SUPABASE_ANON_KEY env. */
|
|
3345
|
-
realtime:
|
|
4680
|
+
realtime: z2.object({ url: z2.string(), anonKey: z2.string() }).nullable()
|
|
3346
4681
|
});
|
|
3347
|
-
var StatusSchema =
|
|
3348
|
-
name:
|
|
3349
|
-
sessionMode:
|
|
4682
|
+
var StatusSchema = z2.object({
|
|
4683
|
+
name: z2.string(),
|
|
4684
|
+
sessionMode: z2.enum(["default", "all_calls", "silent"]),
|
|
3350
4685
|
/** A phone is registered for push/ring (any push token on the account). */
|
|
3351
|
-
phone:
|
|
4686
|
+
phone: z2.boolean()
|
|
3352
4687
|
});
|
|
3353
|
-
var EnvelopeRecipientSchema =
|
|
3354
|
-
keyId:
|
|
3355
|
-
epk:
|
|
3356
|
-
wnonce:
|
|
3357
|
-
wrap:
|
|
4688
|
+
var EnvelopeRecipientSchema = z2.object({
|
|
4689
|
+
keyId: z2.string(),
|
|
4690
|
+
epk: z2.string(),
|
|
4691
|
+
wnonce: z2.string(),
|
|
4692
|
+
wrap: z2.string()
|
|
3358
4693
|
});
|
|
3359
|
-
var EnvelopeHeaderSchema =
|
|
3360
|
-
field:
|
|
3361
|
-
kind:
|
|
3362
|
-
senderRole:
|
|
3363
|
-
recipientKeyIds:
|
|
3364
|
-
seq:
|
|
4694
|
+
var EnvelopeHeaderSchema = z2.object({
|
|
4695
|
+
field: z2.enum(["context", "options", "visuals", "answer"]),
|
|
4696
|
+
kind: z2.string(),
|
|
4697
|
+
senderRole: z2.enum(["agent", "user"]),
|
|
4698
|
+
recipientKeyIds: z2.array(z2.string()),
|
|
4699
|
+
seq: z2.number().int().nonnegative()
|
|
3365
4700
|
});
|
|
3366
|
-
var EnvelopeSchema =
|
|
3367
|
-
v:
|
|
3368
|
-
alg:
|
|
3369
|
-
msgId:
|
|
4701
|
+
var EnvelopeSchema = z2.object({
|
|
4702
|
+
v: z2.literal(1),
|
|
4703
|
+
alg: z2.literal("x25519-xsalsa20poly1305"),
|
|
4704
|
+
msgId: z2.string(),
|
|
3370
4705
|
hdr: EnvelopeHeaderSchema,
|
|
3371
|
-
recipients:
|
|
3372
|
-
nonce:
|
|
3373
|
-
ct:
|
|
4706
|
+
recipients: z2.array(EnvelopeRecipientSchema).min(1),
|
|
4707
|
+
nonce: z2.string(),
|
|
4708
|
+
ct: z2.string()
|
|
3374
4709
|
});
|
|
3375
|
-
var SealedAnswerSchema =
|
|
3376
|
-
ignored:
|
|
4710
|
+
var SealedAnswerSchema = z2.object({
|
|
4711
|
+
ignored: z2.boolean(),
|
|
3377
4712
|
envelope: EnvelopeSchema
|
|
3378
4713
|
});
|
|
3379
|
-
var DeviceCredentialSchema =
|
|
3380
|
-
deviceId:
|
|
3381
|
-
kind:
|
|
3382
|
-
x25519Pub:
|
|
3383
|
-
ed25519Pub:
|
|
3384
|
-
sig:
|
|
4714
|
+
var DeviceCredentialSchema = z2.object({
|
|
4715
|
+
deviceId: z2.string(),
|
|
4716
|
+
kind: z2.enum(["phone", "web", "agent"]),
|
|
4717
|
+
x25519Pub: z2.string(),
|
|
4718
|
+
ed25519Pub: z2.string(),
|
|
4719
|
+
sig: z2.string()
|
|
3385
4720
|
});
|
|
3386
|
-
var DeviceRosterSchema =
|
|
3387
|
-
uikPub:
|
|
3388
|
-
devices:
|
|
4721
|
+
var DeviceRosterSchema = z2.object({
|
|
4722
|
+
uikPub: z2.string(),
|
|
4723
|
+
devices: z2.array(DeviceCredentialSchema)
|
|
3389
4724
|
});
|
|
3390
|
-
var WakeNudgeSchema =
|
|
3391
|
-
kind:
|
|
3392
|
-
notificationId:
|
|
3393
|
-
parentId:
|
|
4725
|
+
var WakeNudgeSchema = z2.object({
|
|
4726
|
+
kind: z2.enum(["reply", "request", "callback"]),
|
|
4727
|
+
notificationId: z2.string().optional(),
|
|
4728
|
+
parentId: z2.string()
|
|
3394
4729
|
});
|
|
3395
|
-
var PairingStatusSchema =
|
|
3396
|
-
var PairingRevealSchema =
|
|
3397
|
-
x25519:
|
|
3398
|
-
ed25519:
|
|
3399
|
-
nonce:
|
|
4730
|
+
var PairingStatusSchema = z2.enum(["pending", "approved", "denied", "expired"]);
|
|
4731
|
+
var PairingRevealSchema = z2.object({
|
|
4732
|
+
x25519: z2.string(),
|
|
4733
|
+
ed25519: z2.string(),
|
|
4734
|
+
nonce: z2.string()
|
|
3400
4735
|
});
|
|
3401
|
-
var DeviceCodeRequestSchema =
|
|
4736
|
+
var DeviceCodeRequestSchema = z2.object({
|
|
3402
4737
|
/** The agent's suggested name for the pairing — the human sees it pre-filled at
|
|
3403
4738
|
* approval and can override. Optional; blank → a default silly name server-side. */
|
|
3404
|
-
suggestedName:
|
|
4739
|
+
suggestedName: z2.string().optional(),
|
|
3405
4740
|
/** Legacy alias for suggestedName (older MCPs sent `agent`). Accepted for
|
|
3406
4741
|
* back-compat; `suggestedName` wins when both are present. ponytail: drop once no
|
|
3407
4742
|
* pre-`name` MCP is in the wild. */
|
|
3408
|
-
agent:
|
|
3409
|
-
device:
|
|
3410
|
-
proto:
|
|
4743
|
+
agent: z2.union([z2.string(), z2.object({ name: z2.string().optional() })]).optional(),
|
|
4744
|
+
device: z2.string().optional(),
|
|
4745
|
+
proto: z2.string().optional(),
|
|
3411
4746
|
// compiled-in tag, e.g. "paigy-pair-v2|sas=24"
|
|
3412
|
-
commitment:
|
|
4747
|
+
commitment: z2.string().optional()
|
|
3413
4748
|
// agent's Ca
|
|
3414
4749
|
});
|
|
3415
|
-
var DeviceCodeSchema =
|
|
3416
|
-
device_code:
|
|
3417
|
-
user_code:
|
|
3418
|
-
verification_uri:
|
|
3419
|
-
verification_uri_complete:
|
|
3420
|
-
interval:
|
|
3421
|
-
expires_in:
|
|
4750
|
+
var DeviceCodeSchema = z2.object({
|
|
4751
|
+
device_code: z2.string(),
|
|
4752
|
+
user_code: z2.string(),
|
|
4753
|
+
verification_uri: z2.string().url(),
|
|
4754
|
+
verification_uri_complete: z2.string().url(),
|
|
4755
|
+
interval: z2.number(),
|
|
4756
|
+
expires_in: z2.number()
|
|
3422
4757
|
});
|
|
3423
|
-
var DeviceInfoSchema =
|
|
3424
|
-
code:
|
|
4758
|
+
var DeviceInfoSchema = z2.object({
|
|
4759
|
+
code: z2.string(),
|
|
3425
4760
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3426
4761
|
* pre-filling the name field the human can edit. */
|
|
3427
|
-
name:
|
|
4762
|
+
name: z2.string(),
|
|
3428
4763
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3429
4764
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3430
4765
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3431
4766
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3432
|
-
agent:
|
|
3433
|
-
device:
|
|
4767
|
+
agent: z2.string().optional(),
|
|
4768
|
+
device: z2.string().nullable(),
|
|
3434
4769
|
status: PairingStatusSchema,
|
|
3435
|
-
proto:
|
|
3436
|
-
agent_commitment:
|
|
4770
|
+
proto: z2.string().nullable().optional(),
|
|
4771
|
+
agent_commitment: z2.string().nullable().optional(),
|
|
3437
4772
|
// Ca
|
|
3438
4773
|
agent_reveal: PairingRevealSchema.nullable().optional(),
|
|
3439
4774
|
// present once the agent reveals
|
|
@@ -3443,48 +4778,48 @@ var DeviceInfoSchema = z.object({
|
|
|
3443
4778
|
// withheld until e2ee=true) can compute the SAS WITHOUT a token. Public keys — same safety
|
|
3444
4779
|
// class as agent_reveal above. Present only once the phone reveals; null otherwise.
|
|
3445
4780
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
3446
|
-
uik_pub:
|
|
4781
|
+
uik_pub: z2.string().nullable().optional()
|
|
3447
4782
|
});
|
|
3448
|
-
var DeviceTokenRequestSchema =
|
|
3449
|
-
device_code:
|
|
4783
|
+
var DeviceTokenRequestSchema = z2.object({
|
|
4784
|
+
device_code: z2.string(),
|
|
3450
4785
|
reveal: PairingRevealSchema.optional()
|
|
3451
4786
|
// agent's reveal {x25519, ed25519, nonce}
|
|
3452
4787
|
});
|
|
3453
|
-
var DeviceTokenSchema =
|
|
3454
|
-
access_token:
|
|
4788
|
+
var DeviceTokenSchema = z2.object({
|
|
4789
|
+
access_token: z2.string(),
|
|
3455
4790
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3456
4791
|
* a default silly name). */
|
|
3457
|
-
name:
|
|
3458
|
-
device:
|
|
4792
|
+
name: z2.string(),
|
|
4793
|
+
device: z2.string().nullable(),
|
|
3459
4794
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3460
4795
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3461
|
-
voice:
|
|
4796
|
+
voice: z2.string().nullable().optional(),
|
|
3462
4797
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3463
4798
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3464
|
-
token_id:
|
|
4799
|
+
token_id: z2.string().nullable().optional(),
|
|
3465
4800
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3466
4801
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3467
4802
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3468
4803
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3469
|
-
workspace:
|
|
4804
|
+
workspace: z2.string().nullable().optional(),
|
|
3470
4805
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
3471
4806
|
// present once the phone reveals
|
|
3472
|
-
uik_pub:
|
|
4807
|
+
uik_pub: z2.string().nullable().optional()
|
|
3473
4808
|
});
|
|
3474
|
-
var DeviceCommitRequestSchema =
|
|
3475
|
-
code:
|
|
3476
|
-
commitment:
|
|
4809
|
+
var DeviceCommitRequestSchema = z2.object({
|
|
4810
|
+
code: z2.string(),
|
|
4811
|
+
commitment: z2.string()
|
|
3477
4812
|
// Cb
|
|
3478
4813
|
});
|
|
3479
|
-
var DevicePeerCommitSchema =
|
|
3480
|
-
commitment:
|
|
4814
|
+
var DevicePeerCommitSchema = z2.object({
|
|
4815
|
+
commitment: z2.string().nullable()
|
|
3481
4816
|
});
|
|
3482
|
-
var SupportRequestSchema =
|
|
3483
|
-
email:
|
|
3484
|
-
message:
|
|
3485
|
-
name:
|
|
4817
|
+
var SupportRequestSchema = z2.object({
|
|
4818
|
+
email: z2.string().email().max(320),
|
|
4819
|
+
message: z2.string().trim().min(1).max(5e3),
|
|
4820
|
+
name: z2.string().trim().max(120).optional()
|
|
3486
4821
|
});
|
|
3487
|
-
var NotificationFeedbackKindSchema =
|
|
4822
|
+
var NotificationFeedbackKindSchema = z2.enum([
|
|
3488
4823
|
"break_down",
|
|
3489
4824
|
// "This should be more than one ask — break it down."
|
|
3490
4825
|
"regenerate_options",
|
|
@@ -3498,76 +4833,30 @@ var NotificationFeedbackKindSchema = z.enum([
|
|
|
3498
4833
|
"other"
|
|
3499
4834
|
// anything else — the note carries it.
|
|
3500
4835
|
]);
|
|
3501
|
-
var NotificationFeedbackSchema =
|
|
3502
|
-
notificationId:
|
|
4836
|
+
var NotificationFeedbackSchema = z2.object({
|
|
4837
|
+
notificationId: z2.string(),
|
|
3503
4838
|
kind: NotificationFeedbackKindSchema,
|
|
3504
4839
|
/** Optional free-text elaboration for a preset; required (non-empty) for 'other'. */
|
|
3505
|
-
note:
|
|
4840
|
+
note: z2.string().trim().max(2e3).optional()
|
|
3506
4841
|
}).superRefine((r, ctx) => {
|
|
3507
4842
|
if (r.kind === "other" && !r.note)
|
|
3508
|
-
ctx.addIssue({ code:
|
|
4843
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["note"], message: "note is required for 'other' feedback" });
|
|
3509
4844
|
});
|
|
3510
|
-
var FeedbackResolutionSchema =
|
|
3511
|
-
var FeedbackOutcomeSchema =
|
|
4845
|
+
var FeedbackResolutionSchema = z2.enum(["broker_fixed", "sent_to_agent"]);
|
|
4846
|
+
var FeedbackOutcomeSchema = z2.object({
|
|
3512
4847
|
resolution: FeedbackResolutionSchema,
|
|
3513
4848
|
transform: TransformSchema,
|
|
3514
|
-
message:
|
|
3515
|
-
childIds:
|
|
4849
|
+
message: z2.string(),
|
|
4850
|
+
childIds: z2.array(z2.string()).optional()
|
|
4851
|
+
});
|
|
4852
|
+
var CONTACT_SCHEMA = contactSchemaFrom({
|
|
4853
|
+
ask: NotifyRequestFields.shape.ask,
|
|
4854
|
+
waiting: NotifyRequestFields.shape.waiting,
|
|
4855
|
+
options: NotifyRequestFields.shape.options,
|
|
4856
|
+
channel: NotifyRequestFields.shape.channel,
|
|
4857
|
+
parentId: NotifyRequestFields.shape.parentId,
|
|
4858
|
+
workId: NotifyRequestFields.shape.workId
|
|
3516
4859
|
});
|
|
3517
|
-
var WORD_NUMS = {
|
|
3518
|
-
a: 1,
|
|
3519
|
-
an: 1,
|
|
3520
|
-
one: 1,
|
|
3521
|
-
two: 2,
|
|
3522
|
-
three: 3,
|
|
3523
|
-
four: 4,
|
|
3524
|
-
five: 5,
|
|
3525
|
-
six: 6,
|
|
3526
|
-
seven: 7,
|
|
3527
|
-
eight: 8,
|
|
3528
|
-
nine: 9,
|
|
3529
|
-
ten: 10,
|
|
3530
|
-
fifteen: 15,
|
|
3531
|
-
twenty: 20,
|
|
3532
|
-
thirty: 30,
|
|
3533
|
-
forty: 40,
|
|
3534
|
-
"forty-five": 45,
|
|
3535
|
-
fifty: 50,
|
|
3536
|
-
sixty: 60,
|
|
3537
|
-
"half an": 0.5,
|
|
3538
|
-
"a couple of": 2,
|
|
3539
|
-
"a few": 3
|
|
3540
|
-
};
|
|
3541
|
-
function untilHour(hour, now) {
|
|
3542
|
-
const due = new Date(now);
|
|
3543
|
-
due.setHours(hour, 0, 0, 0);
|
|
3544
|
-
if (due.getTime() <= now.getTime()) due.setDate(due.getDate() + 1);
|
|
3545
|
-
return Math.round((due.getTime() - now.getTime()) / 1e3);
|
|
3546
|
-
}
|
|
3547
|
-
function parseDue(detail, now = /* @__PURE__ */ new Date()) {
|
|
3548
|
-
const t = detail.toLowerCase().trim();
|
|
3549
|
-
const rel = /(?:^|\b)in\s+(half an|a couple of|a few|an?|[\w-]+|\d+)\s*(minutes?|mins?|hours?|hrs?)\b/.exec(t);
|
|
3550
|
-
if (rel) {
|
|
3551
|
-
const n = /^\d+$/.test(rel[1]) ? Number(rel[1]) : WORD_NUMS[rel[1]];
|
|
3552
|
-
if (n !== void 0) {
|
|
3553
|
-
const unit = rel[2].startsWith("h") ? 3600 : 60;
|
|
3554
|
-
return Math.round(n * unit);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
if (/after lunch|lunchtime/.test(t)) return untilHour(13, now);
|
|
3558
|
-
if (/tomorrow/.test(t)) {
|
|
3559
|
-
const due = new Date(now);
|
|
3560
|
-
due.setDate(due.getDate() + 1);
|
|
3561
|
-
due.setHours(9, 0, 0, 0);
|
|
3562
|
-
return Math.round((due.getTime() - now.getTime()) / 1e3);
|
|
3563
|
-
}
|
|
3564
|
-
if (/tonight|this evening/.test(t)) {
|
|
3565
|
-
return now.getHours() < 20 ? untilHour(20, now) : 3600;
|
|
3566
|
-
}
|
|
3567
|
-
if (/end of (the )?day|eod/.test(t)) return untilHour(17, now);
|
|
3568
|
-
if (/later|in a (bit|while)/.test(t)) return 1800;
|
|
3569
|
-
return null;
|
|
3570
|
-
}
|
|
3571
4860
|
var import_tweetnacl = __toESM(require_nacl_fast(), 1);
|
|
3572
4861
|
var import_tweetnacl2 = __toESM(require_nacl_fast(), 1);
|
|
3573
4862
|
var import_tweetnacl3 = __toESM(require_nacl_fast(), 1);
|
|
@@ -3836,10 +5125,32 @@ function verifyPairingCommit(commitment, proto, role, x25519Pub, ed25519Pub, non
|
|
|
3836
5125
|
return false;
|
|
3837
5126
|
}
|
|
3838
5127
|
}
|
|
3839
|
-
function sessionSlot(
|
|
3840
|
-
const id =
|
|
5128
|
+
function sessionSlot(sessionId2) {
|
|
5129
|
+
const id = sessionId2 ?? sessionId();
|
|
3841
5130
|
return `session:${id.slice(0, 8)}`;
|
|
3842
5131
|
}
|
|
5132
|
+
function subagentId(env = process.env) {
|
|
5133
|
+
if (!env.CLAUDE_CODE_CHILD_SESSION) return null;
|
|
5134
|
+
const pid = env.CLAUDE_PID ?? env.CLAUDE_CODE_MESSAGING_SOCKET?.match(/(\d+)\.sock$/)?.[1];
|
|
5135
|
+
return pid ?? null;
|
|
5136
|
+
}
|
|
5137
|
+
function subagentThread(env = process.env, sessionId2) {
|
|
5138
|
+
const base = sessionId2 ?? sessionId();
|
|
5139
|
+
const sub = subagentId(env);
|
|
5140
|
+
return sub ? `${base}:sub:${sub}` : base;
|
|
5141
|
+
}
|
|
5142
|
+
function uuidFromKey(key) {
|
|
5143
|
+
const h = createHash("sha1").update(`paigy-thread:${key}`).digest();
|
|
5144
|
+
h[6] = h[6] & 15 | 80;
|
|
5145
|
+
h[8] = h[8] & 63 | 128;
|
|
5146
|
+
const hex = h.subarray(0, 16).toString("hex");
|
|
5147
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
5148
|
+
}
|
|
5149
|
+
function contactParentId(explicit, env = process.env) {
|
|
5150
|
+
if (explicit) return explicit;
|
|
5151
|
+
if (!subagentId(env)) return void 0;
|
|
5152
|
+
return uuidFromKey(subagentThread(env));
|
|
5153
|
+
}
|
|
3843
5154
|
var AGENT_NAME = process.env.PAIGY_AGENT || sessionSlot();
|
|
3844
5155
|
var PROTO = "paigy-pair-v2|sas=24";
|
|
3845
5156
|
var SAS_BITS = 24;
|
|
@@ -3911,13 +5222,15 @@ async function revokeToken(token) {
|
|
|
3911
5222
|
});
|
|
3912
5223
|
return res.ok;
|
|
3913
5224
|
}
|
|
3914
|
-
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
5225
|
+
async function requestCode(suggestedName = AGENT_NAME, e2ee, device) {
|
|
3915
5226
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
3916
5227
|
method: "POST",
|
|
3917
5228
|
headers: { "content-type": "application/json" },
|
|
3918
|
-
body: JSON.stringify(
|
|
3919
|
-
|
|
3920
|
-
|
|
5229
|
+
body: JSON.stringify({
|
|
5230
|
+
suggestedName,
|
|
5231
|
+
...device ? { device } : {},
|
|
5232
|
+
...e2ee ? { proto: e2ee.proto, commitment: e2ee.commitment } : {}
|
|
5233
|
+
})
|
|
3921
5234
|
});
|
|
3922
5235
|
if (!res.ok) throw new Error(`/api/device/code failed: ${res.status} ${await res.text()}`);
|
|
3923
5236
|
return await res.json();
|
|
@@ -4101,6 +5414,16 @@ async function sealForE2ee(req, token, deps = {}) {
|
|
|
4101
5414
|
const { context: _c, options: _o, visuals: _v, repo: _r, branch: _b, ...meta } = req;
|
|
4102
5415
|
return { ...meta, envelope };
|
|
4103
5416
|
}
|
|
5417
|
+
async function createGoal(input, opts = {}) {
|
|
5418
|
+
const token = authToken(opts.token) ?? "";
|
|
5419
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/goals`, {
|
|
5420
|
+
method: "POST",
|
|
5421
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
5422
|
+
body: JSON.stringify(input)
|
|
5423
|
+
}));
|
|
5424
|
+
if (!res.ok) throw new Error(`create_goal failed: ${res.status} ${await res.text()}`);
|
|
5425
|
+
return await res.json();
|
|
5426
|
+
}
|
|
4104
5427
|
async function submitNotification(req, opts = {}) {
|
|
4105
5428
|
const token = authToken(opts.token) ?? "";
|
|
4106
5429
|
const body = await sealForE2ee(req, token);
|
|
@@ -4113,27 +5436,10 @@ async function submitNotification(req, opts = {}) {
|
|
|
4113
5436
|
return await res.json();
|
|
4114
5437
|
}
|
|
4115
5438
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4116
|
-
function decryptAnswer(reply, deps = {}) {
|
|
4117
|
-
if (!reply.sealed) return reply.answer;
|
|
4118
|
-
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
4119
|
-
if (!keyFile?.keypair) {
|
|
4120
|
-
throw new Error("received a sealed answer but this agent has no device key to open it \u2014 re-pair.");
|
|
4121
|
-
}
|
|
4122
|
-
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
4123
|
-
}
|
|
4124
|
-
async function pollAnswer(notificationId) {
|
|
4125
|
-
const token = readToken();
|
|
4126
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`, {
|
|
4127
|
-
headers: { authorization: `Bearer ${token}` }
|
|
4128
|
-
}));
|
|
4129
|
-
if (!res.ok) throw new Error(`await failed: ${res.status} ${await res.text()}`);
|
|
4130
|
-
const item = await res.json();
|
|
4131
|
-
return item.type === "idle" ? item : decryptItem(item);
|
|
4132
|
-
}
|
|
4133
5439
|
var AWAIT_WINDOW_MS = 45e3;
|
|
4134
5440
|
var _partialSeen = /* @__PURE__ */ new Map();
|
|
4135
5441
|
async function pollPartials(notificationId) {
|
|
4136
|
-
const token =
|
|
5442
|
+
const token = authToken();
|
|
4137
5443
|
const after = _partialSeen.get(notificationId) ?? 0;
|
|
4138
5444
|
try {
|
|
4139
5445
|
const res = await reach(
|
|
@@ -4150,38 +5456,27 @@ async function pollPartials(notificationId) {
|
|
|
4150
5456
|
return null;
|
|
4151
5457
|
}
|
|
4152
5458
|
}
|
|
4153
|
-
async function awaitReply(notificationId, opts
|
|
5459
|
+
async function awaitReply(notificationId, opts) {
|
|
4154
5460
|
const intervalMs = opts.intervalMs ?? 5e3;
|
|
4155
5461
|
const windowMs = opts.windowMs ?? AWAIT_WINDOW_MS;
|
|
4156
5462
|
const doSleep = opts.sleep ?? sleep2;
|
|
4157
5463
|
const now = opts.now ?? Date.now;
|
|
5464
|
+
const read = opts.read;
|
|
4158
5465
|
const start = now();
|
|
4159
5466
|
while (true) {
|
|
4160
5467
|
if (opts.signal?.aborted) return { type: "idle" };
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
5468
|
+
try {
|
|
5469
|
+
const item = await read(notificationId);
|
|
5470
|
+
if (item.type !== "idle") return item;
|
|
5471
|
+
const partial = await pollPartials(notificationId);
|
|
5472
|
+
if (partial) return partial;
|
|
5473
|
+
} catch (e) {
|
|
5474
|
+
if (!isNetworkError(e)) throw e;
|
|
5475
|
+
}
|
|
4165
5476
|
if (now() - start >= windowMs) return { type: "idle" };
|
|
4166
5477
|
await doSleep(intervalMs);
|
|
4167
5478
|
}
|
|
4168
5479
|
}
|
|
4169
|
-
function decryptItem(item) {
|
|
4170
|
-
if (item.type !== "reply") return item;
|
|
4171
|
-
const enriched = { ...item, intents: landIntents(item.intents) };
|
|
4172
|
-
if (!enriched.sealed) return enriched;
|
|
4173
|
-
const answer = decryptAnswer(enriched);
|
|
4174
|
-
const { sealed: _s, ...rest } = enriched;
|
|
4175
|
-
return { ...rest, answer };
|
|
4176
|
-
}
|
|
4177
|
-
function landIntents(intents) {
|
|
4178
|
-
if (!intents?.length) return intents;
|
|
4179
|
-
return intents.map((i) => {
|
|
4180
|
-
if (i.kind !== "defer") return i;
|
|
4181
|
-
const due = parseDue(i.detail);
|
|
4182
|
-
return due !== null ? { ...i, dueInSeconds: due } : i;
|
|
4183
|
-
});
|
|
4184
|
-
}
|
|
4185
5480
|
async function getThread(parentId) {
|
|
4186
5481
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
4187
5482
|
headers: { authorization: `Bearer ${authToken()}` }
|
|
@@ -4196,22 +5491,16 @@ async function searchThreads(q) {
|
|
|
4196
5491
|
if (!res.ok) throw new Error(`search_threads failed: ${res.status} ${await res.text()}`);
|
|
4197
5492
|
return await res.json();
|
|
4198
5493
|
}
|
|
4199
|
-
async function
|
|
5494
|
+
async function sweepLeased(opts = {}) {
|
|
4200
5495
|
const token = authToken(opts.token);
|
|
4201
5496
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
|
|
4202
|
-
headers: {
|
|
5497
|
+
headers: {
|
|
5498
|
+
authorization: `Bearer ${token}`,
|
|
5499
|
+
"x-paigy-await-ack": "1"
|
|
5500
|
+
}
|
|
4203
5501
|
}));
|
|
4204
5502
|
if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
|
|
4205
|
-
|
|
4206
|
-
return {
|
|
4207
|
-
...pending,
|
|
4208
|
-
replies: pending.replies.map((raw) => {
|
|
4209
|
-
const r = { ...raw, intents: landIntents(raw.intents) };
|
|
4210
|
-
if (!r.sealed) return r;
|
|
4211
|
-
const { sealed: _s, ...rest } = r;
|
|
4212
|
-
return { ...rest, answer: decryptAnswer(r) };
|
|
4213
|
-
})
|
|
4214
|
-
};
|
|
5503
|
+
return await res.json();
|
|
4215
5504
|
}
|
|
4216
5505
|
async function answerCallerQuestion(notificationId, answer) {
|
|
4217
5506
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/voice/session-answer`, {
|
|
@@ -4267,15 +5556,6 @@ async function heartbeat(runtime, opts = {}) {
|
|
|
4267
5556
|
}));
|
|
4268
5557
|
if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
|
|
4269
5558
|
}
|
|
4270
|
-
async function setTaskState(notificationId, state, opts = {}) {
|
|
4271
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notify/${notificationId}/state`, {
|
|
4272
|
-
method: "PATCH",
|
|
4273
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
4274
|
-
body: JSON.stringify({ state })
|
|
4275
|
-
}));
|
|
4276
|
-
if (!res.ok) throw new Error(`set_task_state failed: ${res.status} ${await res.text()}`);
|
|
4277
|
-
return await res.json();
|
|
4278
|
-
}
|
|
4279
5559
|
async function registerDelivery(mode) {
|
|
4280
5560
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
|
|
4281
5561
|
method: "POST",
|
|
@@ -4286,7 +5566,7 @@ async function registerDelivery(mode) {
|
|
|
4286
5566
|
return await res.json();
|
|
4287
5567
|
}
|
|
4288
5568
|
async function scheduleCallback(req) {
|
|
4289
|
-
const token =
|
|
5569
|
+
const token = authToken();
|
|
4290
5570
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/callback`, {
|
|
4291
5571
|
method: "POST",
|
|
4292
5572
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
@@ -4296,7 +5576,7 @@ async function scheduleCallback(req) {
|
|
|
4296
5576
|
return await res.json();
|
|
4297
5577
|
}
|
|
4298
5578
|
async function handoff(req) {
|
|
4299
|
-
const token =
|
|
5579
|
+
const token = authToken();
|
|
4300
5580
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/handoff`, {
|
|
4301
5581
|
method: "POST",
|
|
4302
5582
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
@@ -4305,15 +5585,196 @@ async function handoff(req) {
|
|
|
4305
5585
|
if (!res.ok) throw new Error(`handoff failed: ${res.status} ${await res.text()}`);
|
|
4306
5586
|
return await res.json();
|
|
4307
5587
|
}
|
|
5588
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5589
|
+
var ReplyLeaseExpiredError = class extends Error {
|
|
5590
|
+
constructor() {
|
|
5591
|
+
super(
|
|
5592
|
+
"The Paigy reply lease expired before acknowledgement. The answer remains durable; call await_reply again to reclaim it before acting."
|
|
5593
|
+
);
|
|
5594
|
+
this.name = "ReplyLeaseExpiredError";
|
|
5595
|
+
}
|
|
5596
|
+
};
|
|
5597
|
+
var ReplyAckError = class extends Error {
|
|
5598
|
+
constructor(message, retryable) {
|
|
5599
|
+
super(message);
|
|
5600
|
+
this.retryable = retryable;
|
|
5601
|
+
this.name = "ReplyAckError";
|
|
5602
|
+
}
|
|
5603
|
+
retryable;
|
|
5604
|
+
};
|
|
5605
|
+
function decryptAnswer(reply, deps = {}) {
|
|
5606
|
+
if (!reply.sealed) return reply.answer;
|
|
5607
|
+
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
5608
|
+
if (!keyFile?.keypair) {
|
|
5609
|
+
throw new Error(
|
|
5610
|
+
"received a sealed answer but this agent has no device key to open it \u2014 re-pair."
|
|
5611
|
+
);
|
|
5612
|
+
}
|
|
5613
|
+
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
5614
|
+
}
|
|
5615
|
+
function decodeReply(item, deps = {}) {
|
|
5616
|
+
const { delivery: _delivery, ...withoutDelivery } = item;
|
|
5617
|
+
const enriched = { ...withoutDelivery };
|
|
5618
|
+
if (!enriched.sealed) return enriched;
|
|
5619
|
+
const answer = decryptAnswer(enriched, deps);
|
|
5620
|
+
const { sealed: _sealed, ...plaintext } = enriched;
|
|
5621
|
+
return { ...plaintext, answer };
|
|
5622
|
+
}
|
|
5623
|
+
function deliveryOf(item) {
|
|
5624
|
+
if (item.type !== "reply" || !("delivery" in item) || item.delivery === void 0) {
|
|
5625
|
+
return null;
|
|
5626
|
+
}
|
|
5627
|
+
const delivery = item.delivery;
|
|
5628
|
+
if (!delivery || typeof delivery.leaseId !== "string" || !delivery.leaseId || typeof delivery.expiresAt !== "string" || !delivery.expiresAt) {
|
|
5629
|
+
throw new Error(
|
|
5630
|
+
"Paigy returned invalid reply-delivery metadata; the answer was not acknowledged and remains recoverable."
|
|
5631
|
+
);
|
|
5632
|
+
}
|
|
5633
|
+
return delivery;
|
|
5634
|
+
}
|
|
5635
|
+
async function responseDetail(res) {
|
|
5636
|
+
try {
|
|
5637
|
+
return (await res.text()).slice(0, 300);
|
|
5638
|
+
} catch {
|
|
5639
|
+
return "";
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
async function acknowledge(token, leaseId, opts = {}) {
|
|
5643
|
+
const attempts = Math.max(1, Math.min(opts.attempts ?? 3, 5));
|
|
5644
|
+
const backoffMs = Math.max(0, opts.backoffMs ?? 100);
|
|
5645
|
+
const doSleep = opts.doSleep ?? sleep3;
|
|
5646
|
+
let lastError;
|
|
5647
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
5648
|
+
try {
|
|
5649
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/await/ack`, {
|
|
5650
|
+
method: "POST",
|
|
5651
|
+
headers: {
|
|
5652
|
+
authorization: `Bearer ${token}`,
|
|
5653
|
+
"content-type": "application/json"
|
|
5654
|
+
},
|
|
5655
|
+
body: JSON.stringify({ leaseId })
|
|
5656
|
+
}));
|
|
5657
|
+
if (res.ok) return;
|
|
5658
|
+
const detail2 = await responseDetail(res);
|
|
5659
|
+
if (res.status === 409) throw new ReplyLeaseExpiredError();
|
|
5660
|
+
const retryable = res.status === 408 || res.status === 425 || res.status === 429 || res.status >= 500;
|
|
5661
|
+
throw new ReplyAckError(
|
|
5662
|
+
`Paigy reply acknowledgement failed: ${res.status}${detail2 ? ` ${detail2}` : ""}`,
|
|
5663
|
+
retryable
|
|
5664
|
+
);
|
|
5665
|
+
} catch (error) {
|
|
5666
|
+
if (error instanceof UnpairedError || error instanceof ReplyLeaseExpiredError || error instanceof ReplyAckError && !error.retryable) {
|
|
5667
|
+
throw error;
|
|
5668
|
+
}
|
|
5669
|
+
lastError = error;
|
|
5670
|
+
if (attempt + 1 < attempts) {
|
|
5671
|
+
await doSleep(backoffMs * 2 ** attempt);
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
5676
|
+
throw new Error(
|
|
5677
|
+
`Could not acknowledge the Paigy reply after ${attempts} attempts (${detail}). The answer was not returned to the agent and remains recoverable after the lease expires.`
|
|
5678
|
+
);
|
|
5679
|
+
}
|
|
5680
|
+
async function ackReply(item, token, opts = {}) {
|
|
5681
|
+
const delivery = deliveryOf(item);
|
|
5682
|
+
if (!delivery) throw new Error("Paigy returned a reply without a delivery lease; nothing to acknowledge.");
|
|
5683
|
+
await acknowledge(token, delivery.leaseId, {
|
|
5684
|
+
attempts: opts.ackAttempts,
|
|
5685
|
+
backoffMs: opts.ackBackoffMs,
|
|
5686
|
+
doSleep: opts.ackSleep
|
|
5687
|
+
});
|
|
5688
|
+
}
|
|
5689
|
+
async function receiveReply(item, token, opts = {}) {
|
|
5690
|
+
const decoded = decodeReply(item, opts);
|
|
5691
|
+
const delivery = deliveryOf(item);
|
|
5692
|
+
if (!delivery) {
|
|
5693
|
+
if (opts.requireDelivery) {
|
|
5694
|
+
throw new Error("Paigy returned a catch-up reply without a delivery lease; the answer was not exposed.");
|
|
5695
|
+
}
|
|
5696
|
+
return decoded;
|
|
5697
|
+
}
|
|
5698
|
+
await acknowledge(token, delivery.leaseId, {
|
|
5699
|
+
attempts: opts.ackAttempts,
|
|
5700
|
+
backoffMs: opts.ackBackoffMs,
|
|
5701
|
+
doSleep: opts.ackSleep
|
|
5702
|
+
});
|
|
5703
|
+
return decoded;
|
|
5704
|
+
}
|
|
5705
|
+
async function pollAnswer(notificationId, opts = {}) {
|
|
5706
|
+
const token = authToken() ?? "";
|
|
5707
|
+
const res = ensureAuthed(await reach(
|
|
5708
|
+
`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`,
|
|
5709
|
+
{
|
|
5710
|
+
headers: {
|
|
5711
|
+
authorization: `Bearer ${token}`,
|
|
5712
|
+
"x-paigy-await-ack": "1"
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
));
|
|
5716
|
+
if (!res.ok) throw new Error(`await failed: ${res.status} ${await res.text()}`);
|
|
5717
|
+
const wire = await res.json();
|
|
5718
|
+
if (wire.type !== "reply") return wire;
|
|
5719
|
+
return receiveReply(wire, token, opts);
|
|
5720
|
+
}
|
|
5721
|
+
async function awaitReply2(notificationId, opts = {}) {
|
|
5722
|
+
return awaitReply(notificationId, { ...opts, read: pollAnswer });
|
|
5723
|
+
}
|
|
5724
|
+
async function checkReplies(opts = {}) {
|
|
5725
|
+
const pending = await sweepLeased(opts);
|
|
5726
|
+
const token = authToken(opts.token) ?? "";
|
|
5727
|
+
const replies = await Promise.all(pending.replies.map(async (reply) => {
|
|
5728
|
+
const received = await receiveReply(
|
|
5729
|
+
{ type: "reply", ...reply },
|
|
5730
|
+
token,
|
|
5731
|
+
{ requireDelivery: true }
|
|
5732
|
+
);
|
|
5733
|
+
const { type: _type, ...withoutType } = received;
|
|
5734
|
+
return withoutType;
|
|
5735
|
+
}));
|
|
5736
|
+
return {
|
|
5737
|
+
...pending,
|
|
5738
|
+
replies,
|
|
5739
|
+
work: pending.work ?? []
|
|
5740
|
+
};
|
|
5741
|
+
}
|
|
5742
|
+
async function patchState(path, state, opts, operation) {
|
|
5743
|
+
const token = authToken(opts.token) ?? "";
|
|
5744
|
+
const res = ensureAuthed(await reach(
|
|
5745
|
+
`${BACKEND_URL}${path}`,
|
|
5746
|
+
{
|
|
5747
|
+
method: "PATCH",
|
|
5748
|
+
headers: {
|
|
5749
|
+
"content-type": "application/json",
|
|
5750
|
+
authorization: `Bearer ${token}`,
|
|
5751
|
+
"x-paigy-await-ack": "1"
|
|
5752
|
+
},
|
|
5753
|
+
body: JSON.stringify({ state })
|
|
5754
|
+
}
|
|
5755
|
+
));
|
|
5756
|
+
if (!res.ok) {
|
|
5757
|
+
throw new Error(`${operation} failed: ${res.status} ${await res.text()}`);
|
|
5758
|
+
}
|
|
5759
|
+
return await res.json();
|
|
5760
|
+
}
|
|
5761
|
+
function setTaskState(notificationId, state, opts = {}) {
|
|
5762
|
+
return patchState(`/api/notify/${encodeURIComponent(notificationId)}/state`, state, opts, "set_task_state");
|
|
5763
|
+
}
|
|
5764
|
+
function setWorkState(input, opts = {}) {
|
|
5765
|
+
const parsed = SetWorkStateSchema.parse(input);
|
|
5766
|
+
const path = parsed.workId ? `/api/work/${encodeURIComponent(parsed.workId)}/state` : `/api/notify/${encodeURIComponent(parsed.notificationId)}/state`;
|
|
5767
|
+
return patchState(path, parsed.state, opts, "set_work_state");
|
|
5768
|
+
}
|
|
4308
5769
|
|
|
4309
5770
|
export {
|
|
4310
5771
|
BACKEND_URL,
|
|
5772
|
+
isNetworkError,
|
|
4311
5773
|
reach,
|
|
4312
|
-
normalizeSpeech,
|
|
4313
|
-
lintNotify,
|
|
4314
5774
|
NotifyRequestSchema,
|
|
4315
|
-
|
|
5775
|
+
SetWorkStateSchema,
|
|
4316
5776
|
ScheduleCallbackSchema,
|
|
5777
|
+
contactParentId,
|
|
4317
5778
|
AGENT_NAME,
|
|
4318
5779
|
TOKEN_PATH,
|
|
4319
5780
|
saveToken,
|
|
@@ -4324,7 +5785,6 @@ export {
|
|
|
4324
5785
|
deleteToken,
|
|
4325
5786
|
revokeToken,
|
|
4326
5787
|
requestCode,
|
|
4327
|
-
pollToken,
|
|
4328
5788
|
saveKeyFile,
|
|
4329
5789
|
readKeyFile,
|
|
4330
5790
|
deleteKeyFile,
|
|
@@ -4333,19 +5793,25 @@ export {
|
|
|
4333
5793
|
fetchCredential,
|
|
4334
5794
|
pairStep,
|
|
4335
5795
|
UnpairedError,
|
|
5796
|
+
createGoal,
|
|
4336
5797
|
submitNotification,
|
|
4337
|
-
awaitReply,
|
|
4338
5798
|
getThread,
|
|
4339
5799
|
searchThreads,
|
|
4340
|
-
|
|
5800
|
+
sweepLeased,
|
|
4341
5801
|
answerCallerQuestion,
|
|
4342
5802
|
overrideToken,
|
|
5803
|
+
authToken,
|
|
4343
5804
|
hatch,
|
|
4344
5805
|
whoAmI,
|
|
4345
5806
|
setIdentity,
|
|
4346
5807
|
heartbeat,
|
|
4347
|
-
setTaskState,
|
|
4348
5808
|
registerDelivery,
|
|
4349
5809
|
scheduleCallback,
|
|
4350
|
-
handoff
|
|
5810
|
+
handoff,
|
|
5811
|
+
decodeReply,
|
|
5812
|
+
ackReply,
|
|
5813
|
+
awaitReply2,
|
|
5814
|
+
checkReplies,
|
|
5815
|
+
setTaskState,
|
|
5816
|
+
setWorkState
|
|
4351
5817
|
};
|