@paigy/mcp 0.35.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-U6SLIORK.js → chunk-PRC6GKRN.js} +1937 -691
- package/dist/chunk-UG2FARVV.js +54 -0
- package/dist/chunk-VWVZLPZD.js +143 -0
- package/dist/{chunk-YOGAG4YY.js → chunk-WNE6STMA.js} +633 -489
- package/dist/chunk-XIPO3U6I.js +309 -0
- package/dist/enable.js +3 -2
- package/dist/index.js +115 -354
- package/dist/listen.js +31 -34
- package/dist/onboard.js +62 -45
- package/dist/statusline.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-KNMI3CJ3.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);
|
|
2295
2975
|
return {
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
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)));
|
|
2993
|
+
return {
|
|
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;
|
|
2316
3398
|
}
|
|
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
3399
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
if (
|
|
2324
|
-
|
|
2325
|
-
if (UNSPEAKABLE.test(req.context.title))
|
|
2326
|
-
problems.push("title contains a code fence \u2014 one plain-prose line");
|
|
2327
|
-
if (req.context.description.length > CHUNKS_MAX)
|
|
2328
|
-
problems.push(`${req.context.description.length} description chunks \u2014 cap at ${CHUNKS_MAX}; merge or drop the rest`);
|
|
2329
|
-
for (const [i, chunk] of req.context.description.entries()) {
|
|
2330
|
-
if (chunk.length > CHUNK_MAX)
|
|
2331
|
-
problems.push(`description[${i}] is ${chunk.length} chars \u2014 split it into standalone points of \u2264${CHUNK_MAX}`);
|
|
3400
|
+
if (seenItem && !forceResolution) {
|
|
3401
|
+
const seenSchema = get$ref(seenItem, refs);
|
|
3402
|
+
if (seenSchema !== void 0) {
|
|
3403
|
+
return seenSchema;
|
|
2332
3404
|
}
|
|
2333
|
-
if (spoken && UNSPEAKABLE.test(req.context.description.join(" ")))
|
|
2334
|
-
problems.push("urgency is 'call'/'banner' but the description has a code fence \u2014 rewrite in spoken register (it will be read aloud)");
|
|
2335
3405
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
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
|
|
3494
|
+
}
|
|
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.");
|
|
3503
|
+
}
|
|
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);
|
|
2341
3539
|
}
|
|
2342
|
-
var
|
|
2343
|
-
|
|
2344
|
-
|
|
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;
|
|
3577
|
+
}
|
|
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"])
|
|
3862
|
+
});
|
|
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"
|
|
2621
3869
|
});
|
|
2622
|
-
var TurnSchema =
|
|
2623
|
-
prompt:
|
|
2624
|
-
reply:
|
|
3870
|
+
var TurnSchema = z2.object({
|
|
3871
|
+
prompt: z2.string(),
|
|
3872
|
+
reply: z2.string()
|
|
2625
3873
|
});
|
|
2626
|
-
var UserAnswerSchema =
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
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:
|
|
3894
|
+
kind: z2.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
3895
|
+
detail: z2.string(),
|
|
2648
3896
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2649
3897
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2650
3898
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2651
3899
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2652
|
-
dueInSeconds:
|
|
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,32 +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(),
|
|
3084
4353
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
3085
4354
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
3086
4355
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
3087
4356
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
3088
4357
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
3089
|
-
language:
|
|
4358
|
+
language: z2.enum(["en", "es"]).optional()
|
|
3090
4359
|
});
|
|
3091
|
-
var UserSettingsSchema =
|
|
3092
|
-
permissions:
|
|
3093
|
-
call:
|
|
3094
|
-
banner:
|
|
3095
|
-
push:
|
|
4360
|
+
var UserSettingsSchema = z2.object({
|
|
4361
|
+
permissions: z2.object({
|
|
4362
|
+
call: z2.boolean(),
|
|
4363
|
+
banner: z2.boolean(),
|
|
4364
|
+
push: z2.boolean()
|
|
3096
4365
|
}),
|
|
3097
|
-
sessionMode:
|
|
3098
|
-
silentPush:
|
|
3099
|
-
autoCallback:
|
|
4366
|
+
sessionMode: z2.enum(["default", "all_calls", "silent"]),
|
|
4367
|
+
silentPush: z2.boolean(),
|
|
4368
|
+
autoCallback: z2.boolean(),
|
|
3100
4369
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
3101
|
-
improveConsent:
|
|
4370
|
+
improveConsent: z2.boolean(),
|
|
3102
4371
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
3103
4372
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
3104
4373
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -3106,7 +4375,7 @@ var UserSettingsSchema = z.object({
|
|
|
3106
4375
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
3107
4376
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
3108
4377
|
* write, 'hosted' on read (see store.ts). */
|
|
3109
|
-
voiceMode:
|
|
4378
|
+
voiceMode: z2.enum(["hosted", "on_device"]).optional(),
|
|
3110
4379
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
3111
4380
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
3112
4381
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -3114,10 +4383,10 @@ var UserSettingsSchema = z.object({
|
|
|
3114
4383
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
3115
4384
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
3116
4385
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
3117
|
-
voiceTuning:
|
|
4386
|
+
voiceTuning: z2.record(z2.string(), z2.union([z2.number(), z2.string()])).optional(),
|
|
3118
4387
|
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
3119
4388
|
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
3120
|
-
pstnCalls:
|
|
4389
|
+
pstnCalls: z2.boolean().optional(),
|
|
3121
4390
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
3122
4391
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
3123
4392
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -3126,50 +4395,50 @@ var UserSettingsSchema = z.object({
|
|
|
3126
4395
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
3127
4396
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
3128
4397
|
* call — honest about what we know. */
|
|
3129
|
-
timezone:
|
|
4398
|
+
timezone: z2.string().min(1).max(64).optional(),
|
|
3130
4399
|
/** Account E2EE state (text lane): 'off' (default) = today's plaintext; 'on' =
|
|
3131
4400
|
* content is sealed end-to-end between the local agent and the phone. Like
|
|
3132
4401
|
* voiceMode, OPTIONAL and NOT defaulted so a stale client PATCHing the full
|
|
3133
4402
|
* settings object without it can't silently flip the account's E2EE state.
|
|
3134
4403
|
* Absent = leave unchanged on write, 'off' on read (see store.ts). The demo
|
|
3135
4404
|
* account is plaintext by construction and refuses any non-'off' value. */
|
|
3136
|
-
e2eeMode:
|
|
4405
|
+
e2eeMode: z2.enum(["off", "on"]).optional(),
|
|
3137
4406
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
3138
4407
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
3139
4408
|
broker: BrokerTuningSchema.optional()
|
|
3140
4409
|
});
|
|
3141
|
-
var HistoryItemSchema =
|
|
3142
|
-
id:
|
|
3143
|
-
parentId:
|
|
4410
|
+
var HistoryItemSchema = z2.object({
|
|
4411
|
+
id: z2.string(),
|
|
4412
|
+
parentId: z2.string(),
|
|
3144
4413
|
/** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
|
|
3145
|
-
initiator:
|
|
3146
|
-
title:
|
|
4414
|
+
initiator: z2.enum(["user", "agent"]),
|
|
4415
|
+
title: z2.string(),
|
|
3147
4416
|
/** The agent on the other end (its name). */
|
|
3148
|
-
name:
|
|
3149
|
-
createdAt:
|
|
4417
|
+
name: z2.string(),
|
|
4418
|
+
createdAt: z2.string(),
|
|
3150
4419
|
/** When the agent fetched your request (user→agent only). */
|
|
3151
|
-
agentAckedAt:
|
|
4420
|
+
agentAckedAt: z2.string().nullable(),
|
|
3152
4421
|
/** When you answered the agent's notification (agent→user only). */
|
|
3153
|
-
humanAckedAt:
|
|
4422
|
+
humanAckedAt: z2.string().nullable()
|
|
3154
4423
|
});
|
|
3155
4424
|
var ACTIVITY_LINES = 2;
|
|
3156
4425
|
var ACTIVITY_LINE_MAX = 80;
|
|
3157
|
-
var AgentActivitySchema =
|
|
4426
|
+
var AgentActivitySchema = z2.object({
|
|
3158
4427
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
3159
|
-
lines:
|
|
4428
|
+
lines: z2.array(z2.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
3160
4429
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
3161
4430
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
3162
|
-
at:
|
|
4431
|
+
at: z2.string().datetime()
|
|
3163
4432
|
});
|
|
3164
|
-
var ConnectionSummarySchema =
|
|
4433
|
+
var ConnectionSummarySchema = z2.object({
|
|
3165
4434
|
/** The connection = the agent's token id (used to address a request). */
|
|
3166
|
-
id:
|
|
3167
|
-
device:
|
|
4435
|
+
id: z2.string(),
|
|
4436
|
+
device: z2.string().nullable(),
|
|
3168
4437
|
/** The agent's display name (the single pairing name). */
|
|
3169
|
-
name:
|
|
4438
|
+
name: z2.string(),
|
|
3170
4439
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
3171
4440
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
3172
|
-
provider:
|
|
4441
|
+
provider: z2.string().nullable(),
|
|
3173
4442
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
3174
4443
|
voice: VoiceKeySchema.nullable(),
|
|
3175
4444
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -3180,19 +4449,19 @@ var ConnectionSummarySchema = z.object({
|
|
|
3180
4449
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
3181
4450
|
* set once must not overrule a rule they set about one agent. */
|
|
3182
4451
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
3183
|
-
createdAt:
|
|
4452
|
+
createdAt: z2.string().datetime(),
|
|
3184
4453
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
3185
4454
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
3186
|
-
lastContactAt:
|
|
4455
|
+
lastContactAt: z2.string().datetime().nullable(),
|
|
3187
4456
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
3188
4457
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
3189
|
-
lastSeenAt:
|
|
4458
|
+
lastSeenAt: z2.string().datetime().nullable().optional(),
|
|
3190
4459
|
/** What a live desktop can run (companion.md §2.2), advertised on its heartbeat:
|
|
3191
4460
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
3192
4461
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
3193
|
-
runtime:
|
|
3194
|
-
harnesses:
|
|
3195
|
-
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()
|
|
3196
4465
|
}).optional(),
|
|
3197
4466
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
3198
4467
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -3201,98 +4470,103 @@ var ConnectionSummarySchema = z.object({
|
|
|
3201
4470
|
activity: AgentActivitySchema.optional(),
|
|
3202
4471
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
3203
4472
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
3204
|
-
managed:
|
|
4473
|
+
managed: z2.boolean()
|
|
3205
4474
|
});
|
|
3206
|
-
var MoveRingSchema =
|
|
3207
|
-
var MoveSchema =
|
|
3208
|
-
id:
|
|
4475
|
+
var MoveRingSchema = z2.enum(["home", "travels", "retired", "quarantined"]);
|
|
4476
|
+
var MoveSchema = z2.object({
|
|
4477
|
+
id: z2.string(),
|
|
3209
4478
|
/** The reusable question, as distill normalized it. */
|
|
3210
|
-
question:
|
|
4479
|
+
question: z2.string(),
|
|
3211
4480
|
/** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
|
|
3212
|
-
answer:
|
|
4481
|
+
answer: z2.string(),
|
|
3213
4482
|
/** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
|
|
3214
4483
|
* told so, and the ruling only derives essentially the same question in the same scope. */
|
|
3215
|
-
rationale:
|
|
4484
|
+
rationale: z2.string().nullable(),
|
|
3216
4485
|
/** Where the ruling lives: a repo/workspace, or 'global'. */
|
|
3217
|
-
scope:
|
|
4486
|
+
scope: z2.string(),
|
|
3218
4487
|
ring: MoveRingSchema,
|
|
3219
4488
|
/** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
|
|
3220
|
-
pinned:
|
|
4489
|
+
pinned: z2.boolean(),
|
|
3221
4490
|
/** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
|
|
3222
4491
|
* break is visible instead of a pin silently disappearing. */
|
|
3223
|
-
pinBroken:
|
|
4492
|
+
pinBroken: z2.boolean(),
|
|
3224
4493
|
/** When the ruling was distilled. */
|
|
3225
|
-
learnedAt:
|
|
4494
|
+
learnedAt: z2.string(),
|
|
3226
4495
|
/** Last time it answered an ask. Null = never fired. */
|
|
3227
|
-
lastUsedAt:
|
|
4496
|
+
lastUsedAt: z2.string().nullable(),
|
|
3228
4497
|
/** How many asks it has answered. Instrumentation — deliberately NOT an input to the
|
|
3229
4498
|
* evidence curve: firing says the question keeps arising, not that the ruling is right. */
|
|
3230
|
-
usedCount:
|
|
4499
|
+
usedCount: z2.number(),
|
|
3231
4500
|
/** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
|
|
3232
|
-
confirms:
|
|
4501
|
+
confirms: z2.number(),
|
|
3233
4502
|
/** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
|
|
3234
4503
|
* Linear and priced above the entire confirmation budget, so any full counter wins. */
|
|
3235
|
-
counters:
|
|
4504
|
+
counters: z2.number(),
|
|
3236
4505
|
/** The agent that asked the question this move came from, when known. Null for a move
|
|
3237
4506
|
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
3238
|
-
learnedFrom:
|
|
4507
|
+
learnedFrom: z2.object({ id: z2.string(), name: z2.string() }).nullable()
|
|
3239
4508
|
});
|
|
3240
|
-
var CreateRequestSchema =
|
|
4509
|
+
var CreateRequestSchema = z2.object({
|
|
3241
4510
|
/** The connection (token id) to send to, from GET /api/tokens. */
|
|
3242
|
-
tokenId:
|
|
4511
|
+
tokenId: z2.string(),
|
|
3243
4512
|
/** The user's message to the agent. */
|
|
3244
|
-
text:
|
|
4513
|
+
text: z2.string().min(1),
|
|
3245
4514
|
/** Land the request on an existing conversation thread (History → "Continue")
|
|
3246
4515
|
* instead of minting a fresh one. Must belong to the requesting user. */
|
|
3247
|
-
parentId:
|
|
4516
|
+
parentId: z2.string().optional(),
|
|
3248
4517
|
/** Point the agent at a past conversation (possibly with a different agent) as
|
|
3249
4518
|
* starting context (History → "New session from this"). A reference, not a copy —
|
|
3250
4519
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
3251
|
-
contextParentId:
|
|
4520
|
+
contextParentId: z2.string().optional()
|
|
3252
4521
|
});
|
|
3253
|
-
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(),
|
|
3254
4525
|
/** Land the note on an existing thread; omitted mints a fresh one. */
|
|
3255
|
-
parentId:
|
|
4526
|
+
parentId: z2.string().uuid().optional(),
|
|
3256
4527
|
/** One-line headline of the working context handed off. */
|
|
3257
|
-
title:
|
|
4528
|
+
title: z2.string().min(1),
|
|
3258
4529
|
/** The brief — standalone notes the successor reads (what was done, what's left, links). */
|
|
3259
|
-
notes:
|
|
4530
|
+
notes: z2.array(z2.string().min(1)).min(1),
|
|
3260
4531
|
/** A sibling connection to dispatch directly to (token id or agent nickname). Same-account
|
|
3261
4532
|
* only; omit to leave the thread for the user to hand off in the app. */
|
|
3262
|
-
target:
|
|
4533
|
+
target: z2.string().optional(),
|
|
3263
4534
|
/** Write the note as a RECAP (kind:'recap', #617): a summary turn that supersedes the
|
|
3264
4535
|
* thread's earlier turns for rehydration — get_thread returns the latest recap + only
|
|
3265
4536
|
* the turns after it. Handoff-to-a-successor and handoff-to-yourself-later are the
|
|
3266
4537
|
* same primitive; a recap is one whose audience includes you. */
|
|
3267
|
-
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"]
|
|
3268
4542
|
});
|
|
3269
|
-
var NoteSourceSchema =
|
|
3270
|
-
var NoteStatusSchema =
|
|
3271
|
-
var NoteRepeatSchema =
|
|
3272
|
-
var DecisionSchema =
|
|
3273
|
-
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(),
|
|
3274
4548
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
3275
4549
|
* extensibility seam — any conversation can accrue decisions). */
|
|
3276
|
-
noteId:
|
|
4550
|
+
noteId: z2.string().nullable(),
|
|
3277
4551
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
3278
|
-
question:
|
|
4552
|
+
question: z2.string(),
|
|
3279
4553
|
/** The user's ruling; null while the question is open. */
|
|
3280
|
-
answer:
|
|
3281
|
-
decidedAt:
|
|
3282
|
-
createdAt:
|
|
4554
|
+
answer: z2.string().nullable(),
|
|
4555
|
+
decidedAt: z2.string().nullable(),
|
|
4556
|
+
createdAt: z2.string()
|
|
3283
4557
|
});
|
|
3284
|
-
var NoteSchema =
|
|
3285
|
-
id:
|
|
4558
|
+
var NoteSchema = z2.object({
|
|
4559
|
+
id: z2.string(),
|
|
3286
4560
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
3287
|
-
title:
|
|
4561
|
+
title: z2.string(),
|
|
3288
4562
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
3289
|
-
intent:
|
|
4563
|
+
intent: z2.string(),
|
|
3290
4564
|
source: NoteSourceSchema,
|
|
3291
4565
|
status: NoteStatusSchema,
|
|
3292
4566
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
3293
|
-
assignee:
|
|
4567
|
+
assignee: z2.string().nullable(),
|
|
3294
4568
|
/** The request thread minted at assignment; null until assigned. */
|
|
3295
|
-
parentId:
|
|
4569
|
+
parentId: z2.string().nullable(),
|
|
3296
4570
|
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3297
4571
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
3298
4572
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -3301,200 +4575,200 @@ var NoteSchema = z.object({
|
|
|
3301
4575
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3302
4576
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
3303
4577
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
3304
|
-
dueAt:
|
|
4578
|
+
dueAt: z2.string().nullable().default(null),
|
|
3305
4579
|
repeat: NoteRepeatSchema.default("once"),
|
|
3306
4580
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
3307
|
-
rides:
|
|
3308
|
-
lastRideAt:
|
|
3309
|
-
createdAt:
|
|
4581
|
+
rides: z2.number().int().default(0),
|
|
4582
|
+
lastRideAt: z2.string().nullable().default(null),
|
|
4583
|
+
createdAt: z2.string()
|
|
3310
4584
|
});
|
|
3311
|
-
var CreateNoteSchema =
|
|
4585
|
+
var CreateNoteSchema = z2.object({
|
|
3312
4586
|
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
3313
|
-
text:
|
|
4587
|
+
text: z2.string().min(1).max(4e3),
|
|
3314
4588
|
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
3315
4589
|
* their next call instead of being handed to an agent. Everything else about the
|
|
3316
4590
|
* note is identical; this is the one parameter that separates the two. */
|
|
3317
|
-
forMe:
|
|
4591
|
+
forMe: z2.boolean().optional(),
|
|
3318
4592
|
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
3319
|
-
dueAt:
|
|
4593
|
+
dueAt: z2.string().datetime().optional(),
|
|
3320
4594
|
repeat: NoteRepeatSchema.optional()
|
|
3321
4595
|
});
|
|
3322
|
-
var RecordDecisionSchema =
|
|
4596
|
+
var RecordDecisionSchema = z2.object({
|
|
3323
4597
|
/** An open decision (from /clarify) to answer. */
|
|
3324
|
-
decisionId:
|
|
4598
|
+
decisionId: z2.string().optional(),
|
|
3325
4599
|
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
3326
|
-
question:
|
|
4600
|
+
question: z2.string().min(1).max(500).optional(),
|
|
3327
4601
|
/** The ruling. */
|
|
3328
|
-
answer:
|
|
4602
|
+
answer: z2.string().min(1).max(2e3)
|
|
3329
4603
|
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
3330
|
-
var AssignNoteSchema =
|
|
4604
|
+
var AssignNoteSchema = z2.object({
|
|
3331
4605
|
/** An EXISTING agent: token id or nickname. Omit when spawning fresh. */
|
|
3332
|
-
target:
|
|
4606
|
+
target: z2.string().min(1).optional(),
|
|
3333
4607
|
/** Spawn a NEW session for this note (companion.md §2.2): the assignee doesn't
|
|
3334
4608
|
* exist yet — mint it on a live desktop that advertises the harness+workspace,
|
|
3335
4609
|
* named after the note. The brief arrives as its opening request. */
|
|
3336
|
-
spawn:
|
|
3337
|
-
hostTokenId:
|
|
3338
|
-
harness:
|
|
3339
|
-
workspace:
|
|
4610
|
+
spawn: z2.object({
|
|
4611
|
+
hostTokenId: z2.string().uuid(),
|
|
4612
|
+
harness: z2.string().min(1),
|
|
4613
|
+
workspace: z2.string().min(1)
|
|
3340
4614
|
}).optional()
|
|
3341
4615
|
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
3342
|
-
var TriageVerdictSchema =
|
|
3343
|
-
var TriageItemSchema =
|
|
3344
|
-
noteId:
|
|
4616
|
+
var TriageVerdictSchema = z2.enum(["keep", "stale", "close", "assign"]);
|
|
4617
|
+
var TriageItemSchema = z2.object({
|
|
4618
|
+
noteId: z2.string(),
|
|
3345
4619
|
/** The note's headline at run time. */
|
|
3346
|
-
title:
|
|
4620
|
+
title: z2.string(),
|
|
3347
4621
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
3348
4622
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
3349
4623
|
* Never a model's reasoning transcript, never an id. */
|
|
3350
|
-
why:
|
|
4624
|
+
why: z2.string()
|
|
3351
4625
|
});
|
|
3352
|
-
var TriageAssignmentSchema =
|
|
4626
|
+
var TriageAssignmentSchema = z2.object({
|
|
3353
4627
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
3354
|
-
agent:
|
|
4628
|
+
agent: z2.string(),
|
|
3355
4629
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
3356
4630
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
3357
|
-
agentName:
|
|
3358
|
-
notes:
|
|
4631
|
+
agentName: z2.string(),
|
|
4632
|
+
notes: z2.array(TriageItemSchema)
|
|
3359
4633
|
});
|
|
3360
|
-
var TriageStatusSchema =
|
|
3361
|
-
var SubmitTriageSchema =
|
|
4634
|
+
var TriageStatusSchema = z2.enum(["open", "superseded", "dismissed"]);
|
|
4635
|
+
var SubmitTriageSchema = z2.object({
|
|
3362
4636
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
3363
4637
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
3364
4638
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
3365
4639
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
3366
|
-
provider:
|
|
4640
|
+
provider: z2.string().min(1).max(60),
|
|
3367
4641
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
3368
|
-
model:
|
|
4642
|
+
model: z2.string().max(200).nullable().optional(),
|
|
3369
4643
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
3370
4644
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
3371
|
-
reviewed:
|
|
3372
|
-
close:
|
|
3373
|
-
stale:
|
|
3374
|
-
assign:
|
|
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([])
|
|
3375
4649
|
});
|
|
3376
4650
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
3377
|
-
id:
|
|
3378
|
-
runAt:
|
|
4651
|
+
id: z2.string(),
|
|
4652
|
+
runAt: z2.string(),
|
|
3379
4653
|
status: TriageStatusSchema,
|
|
3380
|
-
model:
|
|
4654
|
+
model: z2.string().nullable().default(null)
|
|
3381
4655
|
});
|
|
3382
|
-
var AcceptTriageSchema =
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
group:
|
|
3387
|
-
agent:
|
|
3388
|
-
noteIds:
|
|
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()
|
|
3389
4663
|
})
|
|
3390
4664
|
]);
|
|
3391
|
-
var AcceptTriageResultSchema =
|
|
3392
|
-
accepted:
|
|
3393
|
-
failed:
|
|
4665
|
+
var AcceptTriageResultSchema = z2.object({
|
|
4666
|
+
accepted: z2.array(z2.string()),
|
|
4667
|
+
failed: z2.array(z2.object({ noteId: z2.string(), reason: z2.string() }))
|
|
3394
4668
|
});
|
|
3395
|
-
var DeliveryModeSchema =
|
|
3396
|
-
var RegisterDeliverySchema =
|
|
3397
|
-
var OAuthStartSchema =
|
|
3398
|
-
provider:
|
|
3399
|
-
returnTo:
|
|
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)
|
|
3400
4674
|
});
|
|
3401
|
-
var DeliveryConfigSchema =
|
|
3402
|
-
tokenId:
|
|
4675
|
+
var DeliveryConfigSchema = z2.object({
|
|
4676
|
+
tokenId: z2.string(),
|
|
3403
4677
|
mode: DeliveryModeSchema,
|
|
3404
4678
|
/** null when the server has no SUPABASE_ANON_KEY set — the listener then falls
|
|
3405
4679
|
* back to its own PAIGY_SUPABASE_URL / PAIGY_SUPABASE_ANON_KEY env. */
|
|
3406
|
-
realtime:
|
|
4680
|
+
realtime: z2.object({ url: z2.string(), anonKey: z2.string() }).nullable()
|
|
3407
4681
|
});
|
|
3408
|
-
var StatusSchema =
|
|
3409
|
-
name:
|
|
3410
|
-
sessionMode:
|
|
4682
|
+
var StatusSchema = z2.object({
|
|
4683
|
+
name: z2.string(),
|
|
4684
|
+
sessionMode: z2.enum(["default", "all_calls", "silent"]),
|
|
3411
4685
|
/** A phone is registered for push/ring (any push token on the account). */
|
|
3412
|
-
phone:
|
|
4686
|
+
phone: z2.boolean()
|
|
3413
4687
|
});
|
|
3414
|
-
var EnvelopeRecipientSchema =
|
|
3415
|
-
keyId:
|
|
3416
|
-
epk:
|
|
3417
|
-
wnonce:
|
|
3418
|
-
wrap:
|
|
4688
|
+
var EnvelopeRecipientSchema = z2.object({
|
|
4689
|
+
keyId: z2.string(),
|
|
4690
|
+
epk: z2.string(),
|
|
4691
|
+
wnonce: z2.string(),
|
|
4692
|
+
wrap: z2.string()
|
|
3419
4693
|
});
|
|
3420
|
-
var EnvelopeHeaderSchema =
|
|
3421
|
-
field:
|
|
3422
|
-
kind:
|
|
3423
|
-
senderRole:
|
|
3424
|
-
recipientKeyIds:
|
|
3425
|
-
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()
|
|
3426
4700
|
});
|
|
3427
|
-
var EnvelopeSchema =
|
|
3428
|
-
v:
|
|
3429
|
-
alg:
|
|
3430
|
-
msgId:
|
|
4701
|
+
var EnvelopeSchema = z2.object({
|
|
4702
|
+
v: z2.literal(1),
|
|
4703
|
+
alg: z2.literal("x25519-xsalsa20poly1305"),
|
|
4704
|
+
msgId: z2.string(),
|
|
3431
4705
|
hdr: EnvelopeHeaderSchema,
|
|
3432
|
-
recipients:
|
|
3433
|
-
nonce:
|
|
3434
|
-
ct:
|
|
4706
|
+
recipients: z2.array(EnvelopeRecipientSchema).min(1),
|
|
4707
|
+
nonce: z2.string(),
|
|
4708
|
+
ct: z2.string()
|
|
3435
4709
|
});
|
|
3436
|
-
var SealedAnswerSchema =
|
|
3437
|
-
ignored:
|
|
4710
|
+
var SealedAnswerSchema = z2.object({
|
|
4711
|
+
ignored: z2.boolean(),
|
|
3438
4712
|
envelope: EnvelopeSchema
|
|
3439
4713
|
});
|
|
3440
|
-
var DeviceCredentialSchema =
|
|
3441
|
-
deviceId:
|
|
3442
|
-
kind:
|
|
3443
|
-
x25519Pub:
|
|
3444
|
-
ed25519Pub:
|
|
3445
|
-
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()
|
|
3446
4720
|
});
|
|
3447
|
-
var DeviceRosterSchema =
|
|
3448
|
-
uikPub:
|
|
3449
|
-
devices:
|
|
4721
|
+
var DeviceRosterSchema = z2.object({
|
|
4722
|
+
uikPub: z2.string(),
|
|
4723
|
+
devices: z2.array(DeviceCredentialSchema)
|
|
3450
4724
|
});
|
|
3451
|
-
var WakeNudgeSchema =
|
|
3452
|
-
kind:
|
|
3453
|
-
notificationId:
|
|
3454
|
-
parentId:
|
|
4725
|
+
var WakeNudgeSchema = z2.object({
|
|
4726
|
+
kind: z2.enum(["reply", "request", "callback"]),
|
|
4727
|
+
notificationId: z2.string().optional(),
|
|
4728
|
+
parentId: z2.string()
|
|
3455
4729
|
});
|
|
3456
|
-
var PairingStatusSchema =
|
|
3457
|
-
var PairingRevealSchema =
|
|
3458
|
-
x25519:
|
|
3459
|
-
ed25519:
|
|
3460
|
-
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()
|
|
3461
4735
|
});
|
|
3462
|
-
var DeviceCodeRequestSchema =
|
|
4736
|
+
var DeviceCodeRequestSchema = z2.object({
|
|
3463
4737
|
/** The agent's suggested name for the pairing — the human sees it pre-filled at
|
|
3464
4738
|
* approval and can override. Optional; blank → a default silly name server-side. */
|
|
3465
|
-
suggestedName:
|
|
4739
|
+
suggestedName: z2.string().optional(),
|
|
3466
4740
|
/** Legacy alias for suggestedName (older MCPs sent `agent`). Accepted for
|
|
3467
4741
|
* back-compat; `suggestedName` wins when both are present. ponytail: drop once no
|
|
3468
4742
|
* pre-`name` MCP is in the wild. */
|
|
3469
|
-
agent:
|
|
3470
|
-
device:
|
|
3471
|
-
proto:
|
|
4743
|
+
agent: z2.union([z2.string(), z2.object({ name: z2.string().optional() })]).optional(),
|
|
4744
|
+
device: z2.string().optional(),
|
|
4745
|
+
proto: z2.string().optional(),
|
|
3472
4746
|
// compiled-in tag, e.g. "paigy-pair-v2|sas=24"
|
|
3473
|
-
commitment:
|
|
4747
|
+
commitment: z2.string().optional()
|
|
3474
4748
|
// agent's Ca
|
|
3475
4749
|
});
|
|
3476
|
-
var DeviceCodeSchema =
|
|
3477
|
-
device_code:
|
|
3478
|
-
user_code:
|
|
3479
|
-
verification_uri:
|
|
3480
|
-
verification_uri_complete:
|
|
3481
|
-
interval:
|
|
3482
|
-
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()
|
|
3483
4757
|
});
|
|
3484
|
-
var DeviceInfoSchema =
|
|
3485
|
-
code:
|
|
4758
|
+
var DeviceInfoSchema = z2.object({
|
|
4759
|
+
code: z2.string(),
|
|
3486
4760
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
3487
4761
|
* pre-filling the name field the human can edit. */
|
|
3488
|
-
name:
|
|
4762
|
+
name: z2.string(),
|
|
3489
4763
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
3490
4764
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
3491
4765
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
3492
4766
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
3493
|
-
agent:
|
|
3494
|
-
device:
|
|
4767
|
+
agent: z2.string().optional(),
|
|
4768
|
+
device: z2.string().nullable(),
|
|
3495
4769
|
status: PairingStatusSchema,
|
|
3496
|
-
proto:
|
|
3497
|
-
agent_commitment:
|
|
4770
|
+
proto: z2.string().nullable().optional(),
|
|
4771
|
+
agent_commitment: z2.string().nullable().optional(),
|
|
3498
4772
|
// Ca
|
|
3499
4773
|
agent_reveal: PairingRevealSchema.nullable().optional(),
|
|
3500
4774
|
// present once the agent reveals
|
|
@@ -3504,48 +4778,48 @@ var DeviceInfoSchema = z.object({
|
|
|
3504
4778
|
// withheld until e2ee=true) can compute the SAS WITHOUT a token. Public keys — same safety
|
|
3505
4779
|
// class as agent_reveal above. Present only once the phone reveals; null otherwise.
|
|
3506
4780
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
3507
|
-
uik_pub:
|
|
4781
|
+
uik_pub: z2.string().nullable().optional()
|
|
3508
4782
|
});
|
|
3509
|
-
var DeviceTokenRequestSchema =
|
|
3510
|
-
device_code:
|
|
4783
|
+
var DeviceTokenRequestSchema = z2.object({
|
|
4784
|
+
device_code: z2.string(),
|
|
3511
4785
|
reveal: PairingRevealSchema.optional()
|
|
3512
4786
|
// agent's reveal {x25519, ed25519, nonce}
|
|
3513
4787
|
});
|
|
3514
|
-
var DeviceTokenSchema =
|
|
3515
|
-
access_token:
|
|
4788
|
+
var DeviceTokenSchema = z2.object({
|
|
4789
|
+
access_token: z2.string(),
|
|
3516
4790
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
3517
4791
|
* a default silly name). */
|
|
3518
|
-
name:
|
|
3519
|
-
device:
|
|
4792
|
+
name: z2.string(),
|
|
4793
|
+
device: z2.string().nullable(),
|
|
3520
4794
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
3521
4795
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
3522
|
-
voice:
|
|
4796
|
+
voice: z2.string().nullable().optional(),
|
|
3523
4797
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
3524
4798
|
* that survives a rename. Cached by the host's identity beat. */
|
|
3525
|
-
token_id:
|
|
4799
|
+
token_id: z2.string().nullable().optional(),
|
|
3526
4800
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
3527
4801
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
3528
4802
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
3529
4803
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
3530
|
-
workspace:
|
|
4804
|
+
workspace: z2.string().nullable().optional(),
|
|
3531
4805
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
3532
4806
|
// present once the phone reveals
|
|
3533
|
-
uik_pub:
|
|
4807
|
+
uik_pub: z2.string().nullable().optional()
|
|
3534
4808
|
});
|
|
3535
|
-
var DeviceCommitRequestSchema =
|
|
3536
|
-
code:
|
|
3537
|
-
commitment:
|
|
4809
|
+
var DeviceCommitRequestSchema = z2.object({
|
|
4810
|
+
code: z2.string(),
|
|
4811
|
+
commitment: z2.string()
|
|
3538
4812
|
// Cb
|
|
3539
4813
|
});
|
|
3540
|
-
var DevicePeerCommitSchema =
|
|
3541
|
-
commitment:
|
|
4814
|
+
var DevicePeerCommitSchema = z2.object({
|
|
4815
|
+
commitment: z2.string().nullable()
|
|
3542
4816
|
});
|
|
3543
|
-
var SupportRequestSchema =
|
|
3544
|
-
email:
|
|
3545
|
-
message:
|
|
3546
|
-
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()
|
|
3547
4821
|
});
|
|
3548
|
-
var NotificationFeedbackKindSchema =
|
|
4822
|
+
var NotificationFeedbackKindSchema = z2.enum([
|
|
3549
4823
|
"break_down",
|
|
3550
4824
|
// "This should be more than one ask — break it down."
|
|
3551
4825
|
"regenerate_options",
|
|
@@ -3559,76 +4833,30 @@ var NotificationFeedbackKindSchema = z.enum([
|
|
|
3559
4833
|
"other"
|
|
3560
4834
|
// anything else — the note carries it.
|
|
3561
4835
|
]);
|
|
3562
|
-
var NotificationFeedbackSchema =
|
|
3563
|
-
notificationId:
|
|
4836
|
+
var NotificationFeedbackSchema = z2.object({
|
|
4837
|
+
notificationId: z2.string(),
|
|
3564
4838
|
kind: NotificationFeedbackKindSchema,
|
|
3565
4839
|
/** Optional free-text elaboration for a preset; required (non-empty) for 'other'. */
|
|
3566
|
-
note:
|
|
4840
|
+
note: z2.string().trim().max(2e3).optional()
|
|
3567
4841
|
}).superRefine((r, ctx) => {
|
|
3568
4842
|
if (r.kind === "other" && !r.note)
|
|
3569
|
-
ctx.addIssue({ code:
|
|
4843
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, path: ["note"], message: "note is required for 'other' feedback" });
|
|
3570
4844
|
});
|
|
3571
|
-
var FeedbackResolutionSchema =
|
|
3572
|
-
var FeedbackOutcomeSchema =
|
|
4845
|
+
var FeedbackResolutionSchema = z2.enum(["broker_fixed", "sent_to_agent"]);
|
|
4846
|
+
var FeedbackOutcomeSchema = z2.object({
|
|
3573
4847
|
resolution: FeedbackResolutionSchema,
|
|
3574
4848
|
transform: TransformSchema,
|
|
3575
|
-
message:
|
|
3576
|
-
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
|
|
3577
4859
|
});
|
|
3578
|
-
var WORD_NUMS = {
|
|
3579
|
-
a: 1,
|
|
3580
|
-
an: 1,
|
|
3581
|
-
one: 1,
|
|
3582
|
-
two: 2,
|
|
3583
|
-
three: 3,
|
|
3584
|
-
four: 4,
|
|
3585
|
-
five: 5,
|
|
3586
|
-
six: 6,
|
|
3587
|
-
seven: 7,
|
|
3588
|
-
eight: 8,
|
|
3589
|
-
nine: 9,
|
|
3590
|
-
ten: 10,
|
|
3591
|
-
fifteen: 15,
|
|
3592
|
-
twenty: 20,
|
|
3593
|
-
thirty: 30,
|
|
3594
|
-
forty: 40,
|
|
3595
|
-
"forty-five": 45,
|
|
3596
|
-
fifty: 50,
|
|
3597
|
-
sixty: 60,
|
|
3598
|
-
"half an": 0.5,
|
|
3599
|
-
"a couple of": 2,
|
|
3600
|
-
"a few": 3
|
|
3601
|
-
};
|
|
3602
|
-
function untilHour(hour, now) {
|
|
3603
|
-
const due = new Date(now);
|
|
3604
|
-
due.setHours(hour, 0, 0, 0);
|
|
3605
|
-
if (due.getTime() <= now.getTime()) due.setDate(due.getDate() + 1);
|
|
3606
|
-
return Math.round((due.getTime() - now.getTime()) / 1e3);
|
|
3607
|
-
}
|
|
3608
|
-
function parseDue(detail, now = /* @__PURE__ */ new Date()) {
|
|
3609
|
-
const t = detail.toLowerCase().trim();
|
|
3610
|
-
const rel = /(?:^|\b)in\s+(half an|a couple of|a few|an?|[\w-]+|\d+)\s*(minutes?|mins?|hours?|hrs?)\b/.exec(t);
|
|
3611
|
-
if (rel) {
|
|
3612
|
-
const n = /^\d+$/.test(rel[1]) ? Number(rel[1]) : WORD_NUMS[rel[1]];
|
|
3613
|
-
if (n !== void 0) {
|
|
3614
|
-
const unit = rel[2].startsWith("h") ? 3600 : 60;
|
|
3615
|
-
return Math.round(n * unit);
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
if (/after lunch|lunchtime/.test(t)) return untilHour(13, now);
|
|
3619
|
-
if (/tomorrow/.test(t)) {
|
|
3620
|
-
const due = new Date(now);
|
|
3621
|
-
due.setDate(due.getDate() + 1);
|
|
3622
|
-
due.setHours(9, 0, 0, 0);
|
|
3623
|
-
return Math.round((due.getTime() - now.getTime()) / 1e3);
|
|
3624
|
-
}
|
|
3625
|
-
if (/tonight|this evening/.test(t)) {
|
|
3626
|
-
return now.getHours() < 20 ? untilHour(20, now) : 3600;
|
|
3627
|
-
}
|
|
3628
|
-
if (/end of (the )?day|eod/.test(t)) return untilHour(17, now);
|
|
3629
|
-
if (/later|in a (bit|while)/.test(t)) return 1800;
|
|
3630
|
-
return null;
|
|
3631
|
-
}
|
|
3632
4860
|
var import_tweetnacl = __toESM(require_nacl_fast(), 1);
|
|
3633
4861
|
var import_tweetnacl2 = __toESM(require_nacl_fast(), 1);
|
|
3634
4862
|
var import_tweetnacl3 = __toESM(require_nacl_fast(), 1);
|
|
@@ -3897,10 +5125,32 @@ function verifyPairingCommit(commitment, proto, role, x25519Pub, ed25519Pub, non
|
|
|
3897
5125
|
return false;
|
|
3898
5126
|
}
|
|
3899
5127
|
}
|
|
3900
|
-
function sessionSlot(
|
|
3901
|
-
const id =
|
|
5128
|
+
function sessionSlot(sessionId2) {
|
|
5129
|
+
const id = sessionId2 ?? sessionId();
|
|
3902
5130
|
return `session:${id.slice(0, 8)}`;
|
|
3903
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
|
+
}
|
|
3904
5154
|
var AGENT_NAME = process.env.PAIGY_AGENT || sessionSlot();
|
|
3905
5155
|
var PROTO = "paigy-pair-v2|sas=24";
|
|
3906
5156
|
var SAS_BITS = 24;
|
|
@@ -3972,13 +5222,15 @@ async function revokeToken(token) {
|
|
|
3972
5222
|
});
|
|
3973
5223
|
return res.ok;
|
|
3974
5224
|
}
|
|
3975
|
-
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
5225
|
+
async function requestCode(suggestedName = AGENT_NAME, e2ee, device) {
|
|
3976
5226
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
3977
5227
|
method: "POST",
|
|
3978
5228
|
headers: { "content-type": "application/json" },
|
|
3979
|
-
body: JSON.stringify(
|
|
3980
|
-
|
|
3981
|
-
|
|
5229
|
+
body: JSON.stringify({
|
|
5230
|
+
suggestedName,
|
|
5231
|
+
...device ? { device } : {},
|
|
5232
|
+
...e2ee ? { proto: e2ee.proto, commitment: e2ee.commitment } : {}
|
|
5233
|
+
})
|
|
3982
5234
|
});
|
|
3983
5235
|
if (!res.ok) throw new Error(`/api/device/code failed: ${res.status} ${await res.text()}`);
|
|
3984
5236
|
return await res.json();
|
|
@@ -4162,6 +5414,16 @@ async function sealForE2ee(req, token, deps = {}) {
|
|
|
4162
5414
|
const { context: _c, options: _o, visuals: _v, repo: _r, branch: _b, ...meta } = req;
|
|
4163
5415
|
return { ...meta, envelope };
|
|
4164
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
|
+
}
|
|
4165
5427
|
async function submitNotification(req, opts = {}) {
|
|
4166
5428
|
const token = authToken(opts.token) ?? "";
|
|
4167
5429
|
const body = await sealForE2ee(req, token);
|
|
@@ -4173,18 +5435,11 @@ async function submitNotification(req, opts = {}) {
|
|
|
4173
5435
|
if (!res.ok) throw new Error(`notify failed: ${res.status} ${await res.text()}`);
|
|
4174
5436
|
return await res.json();
|
|
4175
5437
|
}
|
|
4176
|
-
|
|
4177
|
-
if (!reply.sealed) return reply.answer;
|
|
4178
|
-
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
4179
|
-
if (!keyFile?.keypair) {
|
|
4180
|
-
throw new Error("received a sealed answer but this agent has no device key to open it \u2014 re-pair.");
|
|
4181
|
-
}
|
|
4182
|
-
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
4183
|
-
}
|
|
5438
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4184
5439
|
var AWAIT_WINDOW_MS = 45e3;
|
|
4185
5440
|
var _partialSeen = /* @__PURE__ */ new Map();
|
|
4186
5441
|
async function pollPartials(notificationId) {
|
|
4187
|
-
const token =
|
|
5442
|
+
const token = authToken();
|
|
4188
5443
|
const after = _partialSeen.get(notificationId) ?? 0;
|
|
4189
5444
|
try {
|
|
4190
5445
|
const res = await reach(
|
|
@@ -4201,13 +5456,26 @@ async function pollPartials(notificationId) {
|
|
|
4201
5456
|
return null;
|
|
4202
5457
|
}
|
|
4203
5458
|
}
|
|
4204
|
-
function
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
5459
|
+
async function awaitReply(notificationId, opts) {
|
|
5460
|
+
const intervalMs = opts.intervalMs ?? 5e3;
|
|
5461
|
+
const windowMs = opts.windowMs ?? AWAIT_WINDOW_MS;
|
|
5462
|
+
const doSleep = opts.sleep ?? sleep2;
|
|
5463
|
+
const now = opts.now ?? Date.now;
|
|
5464
|
+
const read = opts.read;
|
|
5465
|
+
const start = now();
|
|
5466
|
+
while (true) {
|
|
5467
|
+
if (opts.signal?.aborted) return { type: "idle" };
|
|
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
|
+
}
|
|
5476
|
+
if (now() - start >= windowMs) return { type: "idle" };
|
|
5477
|
+
await doSleep(intervalMs);
|
|
5478
|
+
}
|
|
4211
5479
|
}
|
|
4212
5480
|
async function getThread(parentId) {
|
|
4213
5481
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
@@ -4223,26 +5491,16 @@ async function searchThreads(q) {
|
|
|
4223
5491
|
if (!res.ok) throw new Error(`search_threads failed: ${res.status} ${await res.text()}`);
|
|
4224
5492
|
return await res.json();
|
|
4225
5493
|
}
|
|
4226
|
-
async function
|
|
5494
|
+
async function sweepLeased(opts = {}) {
|
|
4227
5495
|
const token = authToken(opts.token);
|
|
4228
5496
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
|
|
4229
5497
|
headers: {
|
|
4230
5498
|
authorization: `Bearer ${token}`,
|
|
4231
|
-
|
|
5499
|
+
"x-paigy-await-ack": "1"
|
|
4232
5500
|
}
|
|
4233
5501
|
}));
|
|
4234
5502
|
if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
|
|
4235
|
-
|
|
4236
|
-
if (opts.replyAck) return pending;
|
|
4237
|
-
return {
|
|
4238
|
-
...pending,
|
|
4239
|
-
replies: pending.replies.map((raw) => {
|
|
4240
|
-
const r = { ...raw, intents: landIntents(raw.intents) };
|
|
4241
|
-
if (!r.sealed) return r;
|
|
4242
|
-
const { sealed: _s, ...rest } = r;
|
|
4243
|
-
return { ...rest, answer: decryptAnswer(r) };
|
|
4244
|
-
})
|
|
4245
|
-
};
|
|
5503
|
+
return await res.json();
|
|
4246
5504
|
}
|
|
4247
5505
|
async function answerCallerQuestion(notificationId, answer) {
|
|
4248
5506
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/voice/session-answer`, {
|
|
@@ -4308,7 +5566,7 @@ async function registerDelivery(mode) {
|
|
|
4308
5566
|
return await res.json();
|
|
4309
5567
|
}
|
|
4310
5568
|
async function scheduleCallback(req) {
|
|
4311
|
-
const token =
|
|
5569
|
+
const token = authToken();
|
|
4312
5570
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/callback`, {
|
|
4313
5571
|
method: "POST",
|
|
4314
5572
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
@@ -4318,7 +5576,7 @@ async function scheduleCallback(req) {
|
|
|
4318
5576
|
return await res.json();
|
|
4319
5577
|
}
|
|
4320
5578
|
async function handoff(req) {
|
|
4321
|
-
const token =
|
|
5579
|
+
const token = authToken();
|
|
4322
5580
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/handoff`, {
|
|
4323
5581
|
method: "POST",
|
|
4324
5582
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
@@ -4327,7 +5585,7 @@ async function handoff(req) {
|
|
|
4327
5585
|
if (!res.ok) throw new Error(`handoff failed: ${res.status} ${await res.text()}`);
|
|
4328
5586
|
return await res.json();
|
|
4329
5587
|
}
|
|
4330
|
-
var
|
|
5588
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4331
5589
|
var ReplyLeaseExpiredError = class extends Error {
|
|
4332
5590
|
constructor() {
|
|
4333
5591
|
super(
|
|
@@ -4344,11 +5602,7 @@ var ReplyAckError = class extends Error {
|
|
|
4344
5602
|
}
|
|
4345
5603
|
retryable;
|
|
4346
5604
|
};
|
|
4347
|
-
function
|
|
4348
|
-
if (res.status === 401) throw new UnpairedError();
|
|
4349
|
-
return res;
|
|
4350
|
-
}
|
|
4351
|
-
function decryptAnswer2(reply, deps = {}) {
|
|
5605
|
+
function decryptAnswer(reply, deps = {}) {
|
|
4352
5606
|
if (!reply.sealed) return reply.answer;
|
|
4353
5607
|
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
4354
5608
|
if (!keyFile?.keypair) {
|
|
@@ -4358,22 +5612,11 @@ function decryptAnswer2(reply, deps = {}) {
|
|
|
4358
5612
|
}
|
|
4359
5613
|
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
4360
5614
|
}
|
|
4361
|
-
function landIntents2(intents) {
|
|
4362
|
-
if (!intents?.length) return intents;
|
|
4363
|
-
return intents.map((intent) => {
|
|
4364
|
-
if (intent.kind !== "defer") return intent;
|
|
4365
|
-
const due = parseDue(intent.detail);
|
|
4366
|
-
return due !== null ? { ...intent, dueInSeconds: due } : intent;
|
|
4367
|
-
});
|
|
4368
|
-
}
|
|
4369
5615
|
function decodeReply(item, deps = {}) {
|
|
4370
5616
|
const { delivery: _delivery, ...withoutDelivery } = item;
|
|
4371
|
-
const enriched = {
|
|
4372
|
-
...withoutDelivery,
|
|
4373
|
-
intents: landIntents2(withoutDelivery.intents)
|
|
4374
|
-
};
|
|
5617
|
+
const enriched = { ...withoutDelivery };
|
|
4375
5618
|
if (!enriched.sealed) return enriched;
|
|
4376
|
-
const answer =
|
|
5619
|
+
const answer = decryptAnswer(enriched, deps);
|
|
4377
5620
|
const { sealed: _sealed, ...plaintext } = enriched;
|
|
4378
5621
|
return { ...plaintext, answer };
|
|
4379
5622
|
}
|
|
@@ -4399,11 +5642,11 @@ async function responseDetail(res) {
|
|
|
4399
5642
|
async function acknowledge(token, leaseId, opts = {}) {
|
|
4400
5643
|
const attempts = Math.max(1, Math.min(opts.attempts ?? 3, 5));
|
|
4401
5644
|
const backoffMs = Math.max(0, opts.backoffMs ?? 100);
|
|
4402
|
-
const doSleep = opts.doSleep ??
|
|
5645
|
+
const doSleep = opts.doSleep ?? sleep3;
|
|
4403
5646
|
let lastError;
|
|
4404
5647
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
4405
5648
|
try {
|
|
4406
|
-
const res =
|
|
5649
|
+
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/await/ack`, {
|
|
4407
5650
|
method: "POST",
|
|
4408
5651
|
headers: {
|
|
4409
5652
|
authorization: `Bearer ${token}`,
|
|
@@ -4434,6 +5677,15 @@ async function acknowledge(token, leaseId, opts = {}) {
|
|
|
4434
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.`
|
|
4435
5678
|
);
|
|
4436
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
|
+
}
|
|
4437
5689
|
async function receiveReply(item, token, opts = {}) {
|
|
4438
5690
|
const decoded = decodeReply(item, opts);
|
|
4439
5691
|
const delivery = deliveryOf(item);
|
|
@@ -4452,7 +5704,7 @@ async function receiveReply(item, token, opts = {}) {
|
|
|
4452
5704
|
}
|
|
4453
5705
|
async function pollAnswer(notificationId, opts = {}) {
|
|
4454
5706
|
const token = authToken() ?? "";
|
|
4455
|
-
const res =
|
|
5707
|
+
const res = ensureAuthed(await reach(
|
|
4456
5708
|
`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`,
|
|
4457
5709
|
{
|
|
4458
5710
|
headers: {
|
|
@@ -4466,24 +5718,11 @@ async function pollAnswer(notificationId, opts = {}) {
|
|
|
4466
5718
|
if (wire.type !== "reply") return wire;
|
|
4467
5719
|
return receiveReply(wire, token, opts);
|
|
4468
5720
|
}
|
|
4469
|
-
async function
|
|
4470
|
-
|
|
4471
|
-
const windowMs = opts.windowMs ?? AWAIT_WINDOW_MS;
|
|
4472
|
-
const doSleep = opts.sleep ?? sleep2;
|
|
4473
|
-
const now = opts.now ?? Date.now;
|
|
4474
|
-
const start = now();
|
|
4475
|
-
while (true) {
|
|
4476
|
-
if (opts.signal?.aborted) return { type: "idle" };
|
|
4477
|
-
const item = await pollAnswer(notificationId);
|
|
4478
|
-
if (item.type !== "idle") return item;
|
|
4479
|
-
const partial = await pollPartials(notificationId);
|
|
4480
|
-
if (partial) return partial;
|
|
4481
|
-
if (now() - start >= windowMs) return { type: "idle" };
|
|
4482
|
-
await doSleep(intervalMs);
|
|
4483
|
-
}
|
|
5721
|
+
async function awaitReply2(notificationId, opts = {}) {
|
|
5722
|
+
return awaitReply(notificationId, { ...opts, read: pollAnswer });
|
|
4484
5723
|
}
|
|
4485
|
-
async function
|
|
4486
|
-
const pending = await
|
|
5724
|
+
async function checkReplies(opts = {}) {
|
|
5725
|
+
const pending = await sweepLeased(opts);
|
|
4487
5726
|
const token = authToken(opts.token) ?? "";
|
|
4488
5727
|
const replies = await Promise.all(pending.replies.map(async (reply) => {
|
|
4489
5728
|
const received = await receiveReply(
|
|
@@ -4496,17 +5735,14 @@ async function checkReplies2(opts = {}) {
|
|
|
4496
5735
|
}));
|
|
4497
5736
|
return {
|
|
4498
5737
|
...pending,
|
|
4499
|
-
replies
|
|
5738
|
+
replies,
|
|
5739
|
+
work: pending.work ?? []
|
|
4500
5740
|
};
|
|
4501
5741
|
}
|
|
4502
|
-
function
|
|
4503
|
-
if (res.status === 401) throw new UnpairedError();
|
|
4504
|
-
return res;
|
|
4505
|
-
}
|
|
4506
|
-
async function setTaskState(notificationId, state, opts = {}) {
|
|
5742
|
+
async function patchState(path, state, opts, operation) {
|
|
4507
5743
|
const token = authToken(opts.token) ?? "";
|
|
4508
|
-
const res =
|
|
4509
|
-
`${BACKEND_URL}
|
|
5744
|
+
const res = ensureAuthed(await reach(
|
|
5745
|
+
`${BACKEND_URL}${path}`,
|
|
4510
5746
|
{
|
|
4511
5747
|
method: "PATCH",
|
|
4512
5748
|
headers: {
|
|
@@ -4518,22 +5754,27 @@ async function setTaskState(notificationId, state, opts = {}) {
|
|
|
4518
5754
|
}
|
|
4519
5755
|
));
|
|
4520
5756
|
if (!res.ok) {
|
|
4521
|
-
throw new Error(
|
|
5757
|
+
throw new Error(`${operation} failed: ${res.status} ${await res.text()}`);
|
|
4522
5758
|
}
|
|
4523
5759
|
return await res.json();
|
|
4524
5760
|
}
|
|
4525
|
-
function
|
|
4526
|
-
|
|
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");
|
|
4527
5768
|
}
|
|
4528
5769
|
|
|
4529
5770
|
export {
|
|
4530
5771
|
BACKEND_URL,
|
|
5772
|
+
isNetworkError,
|
|
4531
5773
|
reach,
|
|
4532
|
-
normalizeSpeech,
|
|
4533
|
-
lintNotify,
|
|
4534
5774
|
NotifyRequestSchema,
|
|
4535
|
-
|
|
5775
|
+
SetWorkStateSchema,
|
|
4536
5776
|
ScheduleCallbackSchema,
|
|
5777
|
+
contactParentId,
|
|
4537
5778
|
AGENT_NAME,
|
|
4538
5779
|
TOKEN_PATH,
|
|
4539
5780
|
saveToken,
|
|
@@ -4544,7 +5785,6 @@ export {
|
|
|
4544
5785
|
deleteToken,
|
|
4545
5786
|
revokeToken,
|
|
4546
5787
|
requestCode,
|
|
4547
|
-
pollToken,
|
|
4548
5788
|
saveKeyFile,
|
|
4549
5789
|
readKeyFile,
|
|
4550
5790
|
deleteKeyFile,
|
|
@@ -4553,10 +5793,14 @@ export {
|
|
|
4553
5793
|
fetchCredential,
|
|
4554
5794
|
pairStep,
|
|
4555
5795
|
UnpairedError,
|
|
5796
|
+
createGoal,
|
|
4556
5797
|
submitNotification,
|
|
4557
5798
|
getThread,
|
|
4558
5799
|
searchThreads,
|
|
5800
|
+
sweepLeased,
|
|
4559
5801
|
answerCallerQuestion,
|
|
5802
|
+
overrideToken,
|
|
5803
|
+
authToken,
|
|
4560
5804
|
hatch,
|
|
4561
5805
|
whoAmI,
|
|
4562
5806
|
setIdentity,
|
|
@@ -4564,8 +5808,10 @@ export {
|
|
|
4564
5808
|
registerDelivery,
|
|
4565
5809
|
scheduleCallback,
|
|
4566
5810
|
handoff,
|
|
4567
|
-
|
|
4568
|
-
|
|
5811
|
+
decodeReply,
|
|
5812
|
+
ackReply,
|
|
5813
|
+
awaitReply2,
|
|
5814
|
+
checkReplies,
|
|
4569
5815
|
setTaskState,
|
|
4570
|
-
|
|
5816
|
+
setWorkState
|
|
4571
5817
|
};
|