@paigy/mcp 0.38.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/dist/{chunk-KE63GZEQ.js → chunk-3QUIB42M.js} +602 -580
- package/dist/{chunk-PPACGOTU.js → chunk-4UZAPBKK.js} +752 -872
- package/dist/{chunk-4UEL7Y5U.js → chunk-CXABA27N.js} +1 -1
- package/dist/chunk-TLASZUJD.js +224 -0
- package/dist/enable.js +3 -3
- package/dist/index.js +11 -152
- package/dist/listen.js +25 -5
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/statusline.js +4 -2
- package/package.json +2 -2
- package/dist/chunk-7IGGSB3H.js +0 -315
- package/dist/chunk-UG2FARVV.js +0 -54
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
// ../../packages/sdk/dist/index.js
|
|
2
2
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
3
3
|
import { createHash, randomUUID } from "crypto";
|
|
4
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5
|
+
import { setTimeout as sleep3 } from "timers/promises";
|
|
6
|
+
import { z as z3 } from "zod";
|
|
4
7
|
import { z as z2 } from "zod";
|
|
5
|
-
import { z } from "zod";
|
|
6
8
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
7
9
|
import { ZodFirstPartyTypeKind } from "zod/v3";
|
|
8
10
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2 } from "zod/v3";
|
|
9
|
-
import {
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
10
13
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
11
14
|
import { homedir } from "os";
|
|
12
15
|
import { join } from "path";
|
|
16
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
13
17
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
14
18
|
var __create = Object.create;
|
|
15
19
|
var __defProp = Object.defineProperty;
|
|
@@ -340,17 +344,17 @@ var require_nacl_fast = __commonJS({
|
|
|
340
344
|
}
|
|
341
345
|
var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]);
|
|
342
346
|
function crypto_stream_salsa20_xor(c, cpos, m, mpos, b, n, k) {
|
|
343
|
-
var
|
|
347
|
+
var z4 = new Uint8Array(16), x = new Uint8Array(64);
|
|
344
348
|
var u, i;
|
|
345
|
-
for (i = 0; i < 16; i++)
|
|
346
|
-
for (i = 0; i < 8; i++)
|
|
349
|
+
for (i = 0; i < 16; i++) z4[i] = 0;
|
|
350
|
+
for (i = 0; i < 8; i++) z4[i] = n[i];
|
|
347
351
|
while (b >= 64) {
|
|
348
|
-
crypto_core_salsa20(x,
|
|
352
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
349
353
|
for (i = 0; i < 64; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
|
350
354
|
u = 1;
|
|
351
355
|
for (i = 8; i < 16; i++) {
|
|
352
|
-
u = u + (
|
|
353
|
-
|
|
356
|
+
u = u + (z4[i] & 255) | 0;
|
|
357
|
+
z4[i] = u & 255;
|
|
354
358
|
u >>>= 8;
|
|
355
359
|
}
|
|
356
360
|
b -= 64;
|
|
@@ -358,30 +362,30 @@ var require_nacl_fast = __commonJS({
|
|
|
358
362
|
mpos += 64;
|
|
359
363
|
}
|
|
360
364
|
if (b > 0) {
|
|
361
|
-
crypto_core_salsa20(x,
|
|
365
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
362
366
|
for (i = 0; i < b; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
|
363
367
|
}
|
|
364
368
|
return 0;
|
|
365
369
|
}
|
|
366
370
|
function crypto_stream_salsa20(c, cpos, b, n, k) {
|
|
367
|
-
var
|
|
371
|
+
var z4 = new Uint8Array(16), x = new Uint8Array(64);
|
|
368
372
|
var u, i;
|
|
369
|
-
for (i = 0; i < 16; i++)
|
|
370
|
-
for (i = 0; i < 8; i++)
|
|
373
|
+
for (i = 0; i < 16; i++) z4[i] = 0;
|
|
374
|
+
for (i = 0; i < 8; i++) z4[i] = n[i];
|
|
371
375
|
while (b >= 64) {
|
|
372
|
-
crypto_core_salsa20(x,
|
|
376
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
373
377
|
for (i = 0; i < 64; i++) c[cpos + i] = x[i];
|
|
374
378
|
u = 1;
|
|
375
379
|
for (i = 8; i < 16; i++) {
|
|
376
|
-
u = u + (
|
|
377
|
-
|
|
380
|
+
u = u + (z4[i] & 255) | 0;
|
|
381
|
+
z4[i] = u & 255;
|
|
378
382
|
u >>>= 8;
|
|
379
383
|
}
|
|
380
384
|
b -= 64;
|
|
381
385
|
cpos += 64;
|
|
382
386
|
}
|
|
383
387
|
if (b > 0) {
|
|
384
|
-
crypto_core_salsa20(x,
|
|
388
|
+
crypto_core_salsa20(x, z4, k, sigma);
|
|
385
389
|
for (i = 0; i < b; i++) c[cpos + i] = x[i];
|
|
386
390
|
}
|
|
387
391
|
return 0;
|
|
@@ -1261,12 +1265,12 @@ var require_nacl_fast = __commonJS({
|
|
|
1261
1265
|
for (a = 0; a < 16; a++) o[a] = c[a];
|
|
1262
1266
|
}
|
|
1263
1267
|
function crypto_scalarmult(q, n, p) {
|
|
1264
|
-
var
|
|
1268
|
+
var z4 = new Uint8Array(32);
|
|
1265
1269
|
var x = new Float64Array(80), r, i;
|
|
1266
1270
|
var a = gf(), b = gf(), c = gf(), d = gf(), e = gf(), f = gf();
|
|
1267
|
-
for (i = 0; i < 31; i++)
|
|
1268
|
-
|
|
1269
|
-
|
|
1271
|
+
for (i = 0; i < 31; i++) z4[i] = n[i];
|
|
1272
|
+
z4[31] = n[31] & 127 | 64;
|
|
1273
|
+
z4[0] &= 248;
|
|
1270
1274
|
unpack25519(x, p);
|
|
1271
1275
|
for (i = 0; i < 16; i++) {
|
|
1272
1276
|
b[i] = x[i];
|
|
@@ -1274,7 +1278,7 @@ var require_nacl_fast = __commonJS({
|
|
|
1274
1278
|
}
|
|
1275
1279
|
a[0] = d[0] = 1;
|
|
1276
1280
|
for (i = 254; i >= 0; --i) {
|
|
1277
|
-
r =
|
|
1281
|
+
r = z4[i >>> 3] >>> (i & 7) & 1;
|
|
1278
1282
|
sel25519(a, b, r);
|
|
1279
1283
|
sel25519(c, d, r);
|
|
1280
1284
|
A(e, a, c);
|
|
@@ -3516,20 +3520,17 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
3516
3520
|
};
|
|
3517
3521
|
var OPTIONS_MIN = 2;
|
|
3518
3522
|
var OPTIONS_MAX = 6;
|
|
3519
|
-
var MISSED_CALL_PLAN = {
|
|
3520
|
-
retry_10m: { kind: "every", minutes: 10 },
|
|
3521
|
-
retry_30m: { kind: "every", minutes: 30 },
|
|
3522
|
-
retry_60m: { kind: "every", minutes: 60 },
|
|
3523
|
-
backoff_gentle: { kind: "at", minutes: [30, 120, 360] },
|
|
3524
|
-
backoff_standard: { kind: "at", minutes: [10, 30, 120] },
|
|
3525
|
-
backoff_aggressive: { kind: "at", minutes: [5, 15, 45] },
|
|
3526
|
-
inbox: { kind: "once" },
|
|
3527
|
-
dismiss: { kind: "grace", minutes: 2 }
|
|
3528
|
-
};
|
|
3529
3523
|
function draft2020(node) {
|
|
3530
3524
|
if (Array.isArray(node)) return node.map(draft2020);
|
|
3531
3525
|
if (node && typeof node === "object") {
|
|
3532
3526
|
const o = node;
|
|
3527
|
+
if (Array.isArray(o.items)) {
|
|
3528
|
+
o.prefixItems = o.items;
|
|
3529
|
+
if ("additionalItems" in o) {
|
|
3530
|
+
o.items = o.additionalItems;
|
|
3531
|
+
delete o.additionalItems;
|
|
3532
|
+
} else delete o.items;
|
|
3533
|
+
}
|
|
3533
3534
|
for (const [excl, lim] of [["exclusiveMinimum", "minimum"], ["exclusiveMaximum", "maximum"]]) {
|
|
3534
3535
|
if (typeof o[excl] === "boolean") {
|
|
3535
3536
|
if (o[excl] === true && typeof o[lim] === "number") {
|
|
@@ -3548,66 +3549,79 @@ function mcpInputSchema(s) {
|
|
|
3548
3549
|
delete schema.$schema;
|
|
3549
3550
|
return draft2020(schema);
|
|
3550
3551
|
}
|
|
3551
|
-
var
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3552
|
+
var StartContactSchema = z.object({
|
|
3553
|
+
goalIds: z.tuple([z.string().uuid()]),
|
|
3554
|
+
ask: z.string().trim().min(1).max(1e4),
|
|
3555
|
+
waiting: z.enum(["none", "hard"]).default("none"),
|
|
3556
|
+
channel: z.enum(["notification", "call"]).default("notification"),
|
|
3557
|
+
options: z.array(z.object({ label: z.string().trim().min(1).max(1e3), image: z.string().url().optional(), html: z.string().max(16384).optional() }).strict()).min(2).max(6).optional(),
|
|
3558
|
+
threadId: z.string().uuid().optional().describe("Continue an existing Thread: the threadId a prior Delivery returned. Omit to start a new Thread.")
|
|
3559
|
+
}).strict();
|
|
3560
|
+
var ContactSchema = z.union([StartContactSchema, z.object({ deliveryId: z.string().uuid() }).strict()]);
|
|
3561
|
+
var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
3562
|
+
var CONTACT_DESCRIPTION = "Contact the user about exactly one existing Goal: pass goalIds:[goalId], ask, channel:'notification'|'call', and waiting:'none'|'hard'. Options supply choices. Notification returns immediately; collect durable answers with claim_goal/get_goal. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Continuation sends nothing and rereads the same durable evidence, including previously read answers. Entries retain authorship and provenance; accepted decisions are separate from quoted speech. Call state open does not mean ringing. Unsupported: soft waiting, multiple Goals/questions, outcome admission, and re-presentation of an existing request. Create a Goal explicitly first; never resend a pending ask to continue waiting.";
|
|
3563
|
+
var CreateGoalSchema = z2.object({
|
|
3564
|
+
outcome: z2.string().trim().min(1).max(1e4),
|
|
3565
|
+
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
3566
|
+
idempotencyKey: z2.string().trim().min(1).max(200)
|
|
3555
3567
|
});
|
|
3556
|
-
var
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3568
|
+
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
3569
|
+
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
3570
|
+
}).strict();
|
|
3571
|
+
var CREATE_GOAL_DESCRIPTION = "Create a durable root Goal for an outcome. Admission only: the owner must claim it before doing work, then update it as it advances. Returns an admission receipt with goalId, current state, revision, ownerParticipant, and the next step; no Goal content or execution lease.";
|
|
3572
|
+
var UpdateGoalSchema = z2.object({
|
|
3573
|
+
revision: z2.number().int().positive(),
|
|
3574
|
+
changes: z2.object({
|
|
3575
|
+
outcome: z2.string().trim().min(1).max(1e4).optional(),
|
|
3576
|
+
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
3577
|
+
parentGoalId: z2.string().uuid().nullable().optional(),
|
|
3578
|
+
dependencies: z2.array(z2.object({ goalId: z2.string().uuid(), gate: z2.enum(["start", "finish"]) }).strict()).optional(),
|
|
3579
|
+
children: z2.array(z2.object({ outcome: z2.string().trim().min(1).max(1e4), ownerParticipant: z2.string().trim().min(1), gate: z2.enum(["start", "finish"]).optional() }).strict()).optional(),
|
|
3580
|
+
state: z2.enum(["active", "done", "cancelled"]).optional(),
|
|
3581
|
+
progress: z2.string().trim().min(1).max(1e4).optional(),
|
|
3582
|
+
reviewed: z2.literal(true).optional()
|
|
3583
|
+
}).strict().refine((v) => Object.keys(v).length > 0),
|
|
3584
|
+
reason: z2.string().trim().min(1).max(2e3),
|
|
3585
|
+
operationId: z2.string().uuid().optional()
|
|
3586
|
+
}).strict();
|
|
3587
|
+
var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z2.string().uuid() }).strict();
|
|
3588
|
+
var ClaimGoalSchema = z2.object({ goalId: z2.string().uuid().optional() }).strict();
|
|
3589
|
+
var GetGoalSchema = z2.object({ goalId: z2.string().uuid() }).strict();
|
|
3590
|
+
var GET_GOAL_DESCRIPTION = "Read the current authorized Goal brief: state, owner, blockers, open decisions, progress, and the next operation. Foreign or sibling-owned Goals are not disclosed.";
|
|
3591
|
+
var UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, and review acknowledgement are explicit; stale revisions are rejected. Returns the new revision and a prose summary.";
|
|
3592
|
+
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns a Goal-scoped brief, current revision, blockers, and the next valid operation. Claiming creates or renews the execution lease.";
|
|
3593
|
+
var CHECK_REPLIES_DESCRIPTION = "Your open Deliveries: every Notification or Call currently addressed to you \u2014 a request the user started toward you, an answer relayed to something you asked, a handoff \u2014 each with its durable Entries, accepted decisions and open decision needs, in the same shape a contact read returns. A pure read with no arguments: nothing is consumed, acknowledged or claimed by reading it, so call it on startup, after a long wait, or whenever you want to know what is outstanding. To act on one, claim its Goal (claim_goal) or reread it with contact({deliveryId}). Your runnable and review-pending Goals come from claim_goal, not from here.";
|
|
3594
|
+
var CheckRepliesSchema = z2.object({}).strict();
|
|
3595
|
+
var GetThreadSchema = z2.object({
|
|
3596
|
+
parentId: z2.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
3597
|
+
}).strict();
|
|
3598
|
+
var GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
|
|
3599
|
+
var SearchThreadsSchema = z2.object({
|
|
3600
|
+
q: z2.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
|
|
3601
|
+
}).strict();
|
|
3602
|
+
var SEARCH_THREADS_DESCRIPTION = `Search your PAST conversations before asking \u2014 "have we discussed this before?". Full-text over your own threads (the asks you sent + the user's answers); returns ranked threads with highlighted snippets, NOT rows: { hits: [{ parentId, at, agentLabel, matches: [{ notificationId, role, snippet }] }] }. The loop this exists for: search first \u2192 get_thread the best hit to rehydrate it \u2192 THEN continue or contact, so you answer with receipts ("last week you said ship it") instead of re-asking. Read-only, safe to call anytime; scoped to your own account's threads.`;
|
|
3603
|
+
var AGENT_TOOLS = [
|
|
3604
|
+
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
3605
|
+
{ name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
|
|
3606
|
+
{ name: "get_thread", description: GET_THREAD_DESCRIPTION, inputSchema: mcpInputSchema(GetThreadSchema) },
|
|
3607
|
+
{ name: "search_threads", description: SEARCH_THREADS_DESCRIPTION, inputSchema: mcpInputSchema(SearchThreadsSchema) },
|
|
3608
|
+
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
3609
|
+
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
3610
|
+
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
3611
|
+
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
3612
|
+
];
|
|
3613
|
+
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
3614
|
+
var ContextSchema = z3.object({
|
|
3615
|
+
title: z3.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
3616
|
+
description: z3.array(z3.string().min(1)).describe(
|
|
3603
3617
|
"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."
|
|
3604
3618
|
)
|
|
3605
3619
|
});
|
|
3606
|
-
var ParticipantSchema =
|
|
3607
|
-
kind:
|
|
3608
|
-
id:
|
|
3620
|
+
var ParticipantSchema = z3.object({
|
|
3621
|
+
kind: z3.enum(["human", "agent"]),
|
|
3622
|
+
id: z3.string()
|
|
3609
3623
|
});
|
|
3610
|
-
var TransformSchema =
|
|
3624
|
+
var TransformSchema = z3.enum([
|
|
3611
3625
|
"structure",
|
|
3612
3626
|
// shape intent into an answer contract; pick channel/urgency — broker `ask`, `select` shapes, `points`
|
|
3613
3627
|
"request_more",
|
|
@@ -3623,25 +3637,25 @@ var TransformSchema = z2.enum([
|
|
|
3623
3637
|
"summarize"
|
|
3624
3638
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
3625
3639
|
]);
|
|
3626
|
-
var OptionSchema =
|
|
3627
|
-
id:
|
|
3628
|
-
label:
|
|
3640
|
+
var OptionSchema = z3.object({
|
|
3641
|
+
id: z3.string(),
|
|
3642
|
+
label: z3.string(),
|
|
3629
3643
|
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
3630
3644
|
// the constraints below are what an agent reads when deciding to use these.
|
|
3631
|
-
html:
|
|
3645
|
+
html: z3.string().max(16384).describe(
|
|
3632
3646
|
"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."
|
|
3633
3647
|
).optional(),
|
|
3634
|
-
image:
|
|
3648
|
+
image: z3.string().url().describe(
|
|
3635
3649
|
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
3636
3650
|
).optional()
|
|
3637
3651
|
});
|
|
3638
|
-
var VisualSchema =
|
|
3639
|
-
url:
|
|
3640
|
-
label:
|
|
3652
|
+
var VisualSchema = z3.object({
|
|
3653
|
+
url: z3.string().url(),
|
|
3654
|
+
label: z3.string().optional()
|
|
3641
3655
|
});
|
|
3642
|
-
var NotifyLevelSchema =
|
|
3643
|
-
var SelectShapeSchema =
|
|
3644
|
-
var ReceiptEventSchema =
|
|
3656
|
+
var NotifyLevelSchema = z3.enum(["inbox", "push", "banner", "call"]);
|
|
3657
|
+
var SelectShapeSchema = z3.enum(["one", "many", "rank", "confirm", "text"]);
|
|
3658
|
+
var ReceiptEventSchema = z3.enum([
|
|
3645
3659
|
"delivered",
|
|
3646
3660
|
// the bundle reached the recipient at some level
|
|
3647
3661
|
"seen",
|
|
@@ -3671,44 +3685,47 @@ var ReceiptEventSchema = z2.enum([
|
|
|
3671
3685
|
// be rewound by a writer that forgot to advance it.
|
|
3672
3686
|
"restarted"
|
|
3673
3687
|
]);
|
|
3674
|
-
var AttentionSchema =
|
|
3688
|
+
var AttentionSchema = z3.object({
|
|
3675
3689
|
urgency: NotifyLevelSchema,
|
|
3676
3690
|
/** The required answer shape, or null for a plain notify that asks nothing back. */
|
|
3677
3691
|
select: SelectShapeSchema.nullable(),
|
|
3678
3692
|
/** Coverage contract (#396) — points the answer must address; null = none declared. */
|
|
3679
|
-
points:
|
|
3693
|
+
points: z3.array(z3.string()).nullable(),
|
|
3680
3694
|
/** Whether the ask blocks the sender — what lets arbitration escalate it on silence. */
|
|
3681
|
-
blocking:
|
|
3695
|
+
blocking: z3.boolean(),
|
|
3682
3696
|
/** Reserved (MODEL.md lists it): a response deadline. No row column yet — a later Phase 2
|
|
3683
3697
|
* slice wires it; optional so today's rows/callers project cleanly. */
|
|
3684
|
-
deadline:
|
|
3698
|
+
deadline: z3.string().datetime().nullable().optional()
|
|
3685
3699
|
});
|
|
3686
|
-
var NotifyRequestFields =
|
|
3700
|
+
var NotifyRequestFields = z3.object({
|
|
3687
3701
|
/** Plaintext message content. Present on the plaintext path (today's shape);
|
|
3688
3702
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
3689
3703
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
3690
3704
|
context: ContextSchema.optional(),
|
|
3691
|
-
options:
|
|
3705
|
+
options: z3.array(OptionSchema.omit({ id: true })).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
3692
3706
|
"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)."
|
|
3693
3707
|
),
|
|
3694
|
-
points:
|
|
3708
|
+
points: z3.array(z3.string().min(1)).optional().describe(
|
|
3695
3709
|
"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."
|
|
3696
3710
|
),
|
|
3697
|
-
visuals:
|
|
3711
|
+
visuals: z3.array(VisualSchema).optional().describe(
|
|
3698
3712
|
"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."
|
|
3699
3713
|
),
|
|
3700
3714
|
/** Git repo the agent is working in ("owner/name"). Local MCP fills this from the checkout — omit unless overriding. */
|
|
3701
|
-
repo:
|
|
3715
|
+
repo: z3.string().optional(),
|
|
3702
3716
|
/** Git branch the agent is on. Local MCP fills this from the checkout — omit unless overriding. */
|
|
3703
|
-
branch:
|
|
3717
|
+
branch: z3.string().optional(),
|
|
3704
3718
|
/** Continue an existing conversation — the id of any notification in it (its root
|
|
3705
3719
|
* is the conversation's identity). Omitted = start a new conversation. Renamed
|
|
3706
3720
|
* from `parentId` (2026-08-03): one linkage system, the parent; the API edge
|
|
3707
3721
|
* still accepts the old name from older clients. */
|
|
3708
|
-
parentId:
|
|
3722
|
+
parentId: z3.string().uuid().optional(),
|
|
3709
3723
|
/** The durable outcome this contact advances. Optional during the notification-to-Work
|
|
3710
3724
|
* migration; when present, a blocking ask creates a DecisionNeed for this Work. */
|
|
3711
|
-
workId:
|
|
3725
|
+
workId: z3.string().uuid().optional(),
|
|
3726
|
+
/** Target Goal scope. During staged migration this is accepted by the shared contract but
|
|
3727
|
+
* target delivery activation remains model-gated; workId and goalId are mutually exclusive. */
|
|
3728
|
+
goalId: z3.string().uuid().optional(),
|
|
3712
3729
|
urgency: NotifyLevelSchema.default("inbox").describe(
|
|
3713
3730
|
"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."
|
|
3714
3731
|
),
|
|
@@ -3716,7 +3733,7 @@ var NotifyRequestFields = z2.object({
|
|
|
3716
3733
|
* visible and marks it needs_input. Renamed from the old `parentId` (2026-08-03)
|
|
3717
3734
|
* when `parentId` became the conversation handle: `parentId` says WHERE, this
|
|
3718
3735
|
* says HOW. */
|
|
3719
|
-
clarifies:
|
|
3736
|
+
clarifies: z3.string().optional(),
|
|
3720
3737
|
/** E2EE (text lane): when the pairing is E2EE, the sealed replacements for the
|
|
3721
3738
|
* plaintext content fields, keyed by field name. FINALIZED wire shape (was
|
|
3722
3739
|
* provisional in the storage PR): a per-field map `{ context?, options?,
|
|
@@ -3729,10 +3746,10 @@ var NotifyRequestFields = z2.object({
|
|
|
3729
3746
|
* notifications.envelope and relays it blindly; it never decrypts. Absent =
|
|
3730
3747
|
* today's plaintext path (context/options/visuals carry the cleartext).
|
|
3731
3748
|
* z.lazy because EnvelopeSchema is declared further down (E2EE section). */
|
|
3732
|
-
envelope:
|
|
3733
|
-
context:
|
|
3734
|
-
options:
|
|
3735
|
-
visuals:
|
|
3749
|
+
envelope: z3.object({
|
|
3750
|
+
context: z3.lazy(() => EnvelopeSchema).optional(),
|
|
3751
|
+
options: z3.lazy(() => EnvelopeSchema).optional(),
|
|
3752
|
+
visuals: z3.lazy(() => EnvelopeSchema).optional()
|
|
3736
3753
|
}).optional(),
|
|
3737
3754
|
select: SelectShapeSchema.optional().describe(
|
|
3738
3755
|
"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."
|
|
@@ -3747,20 +3764,20 @@ var NotifyRequestFields = z2.object({
|
|
|
3747
3764
|
// (broker/agenda-design.md) — not by a wire cap the agent has to pre-summarize under.
|
|
3748
3765
|
// Owner, 2026-07-28: "our actual limitation on how long something is to the user should
|
|
3749
3766
|
// come from the broker splitting and summarizing." The cap that remains is a size guard.
|
|
3750
|
-
ask:
|
|
3767
|
+
ask: z3.string().min(1).max(1e4).optional().describe(
|
|
3751
3768
|
'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.'
|
|
3752
3769
|
),
|
|
3753
|
-
needs:
|
|
3770
|
+
needs: z3.array(z3.string().min(1)).optional().describe(
|
|
3754
3771
|
"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."
|
|
3755
3772
|
),
|
|
3756
|
-
urgencyHint:
|
|
3773
|
+
urgencyHint: z3.enum(["whenever", "soon", "now"]).optional().describe(
|
|
3757
3774
|
"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."
|
|
3758
3775
|
),
|
|
3759
3776
|
/** #575: the ONE self-report that replaces urgencyHint + blocking — what happens
|
|
3760
3777
|
* to the agent's work while it waits. Normalized server-side into those two
|
|
3761
3778
|
* fields (normalizeWaiting) so everything downstream is untouched; explicit
|
|
3762
3779
|
* urgencyHint/blocking win when both are sent. */
|
|
3763
|
-
waiting:
|
|
3780
|
+
waiting: z3.enum(["none", "soft", "hard"]).optional().describe(
|
|
3764
3781
|
"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."
|
|
3765
3782
|
),
|
|
3766
3783
|
/** Δ9b (#895): HOLD this claim so the sender can correct the plan before anyone is
|
|
@@ -3768,58 +3785,59 @@ var NotifyRequestFields = z2.object({
|
|
|
3768
3785
|
* holding by default would charge every quiet claim that minute before any agent could
|
|
3769
3786
|
* correct anything. Ignored for `waiting: 'hard'`: a blocking ask rings on what we have,
|
|
3770
3787
|
* and the enrichment can still land mid-call (#781 re-plans the unspoken tail). */
|
|
3771
|
-
confirm:
|
|
3788
|
+
confirm: z3.boolean().optional().describe(
|
|
3772
3789
|
"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'."
|
|
3773
3790
|
),
|
|
3774
3791
|
/** #575: a RELAY of the user's explicitly stated preference, never the agent's
|
|
3775
3792
|
* choice. Outranks waiting in both directions: 'call' rings even for a
|
|
3776
3793
|
* waiting:'none' "call me when it's done"; 'message' never rings even for
|
|
3777
3794
|
* waiting:'hard'. */
|
|
3778
|
-
channel:
|
|
3795
|
+
channel: z3.enum(["call", "message"]).optional().describe(
|
|
3779
3796
|
"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."
|
|
3780
3797
|
),
|
|
3781
|
-
confirmStyle:
|
|
3798
|
+
confirmStyle: z3.enum(["yesno", "approve"]).default("yesno").describe(
|
|
3782
3799
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
3783
3800
|
),
|
|
3784
|
-
blocking:
|
|
3801
|
+
blocking: z3.boolean().default(false).describe(
|
|
3785
3802
|
"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."
|
|
3786
3803
|
)
|
|
3787
3804
|
});
|
|
3788
3805
|
var NotifyRequestSchema = NotifyRequestFields.superRefine((r, ctx) => {
|
|
3806
|
+
if (r.workId && r.goalId) ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["goalId"], message: "pass goalId or workId, not both" });
|
|
3789
3807
|
const sealed = !!r.envelope;
|
|
3790
3808
|
if (sealed) {
|
|
3791
3809
|
if (!r.envelope?.context)
|
|
3792
|
-
ctx.addIssue({ code:
|
|
3810
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["envelope", "context"], message: "sealed request must include envelope.context" });
|
|
3793
3811
|
for (const f of ["context", "options", "visuals"]) {
|
|
3794
3812
|
if (r[f] !== void 0)
|
|
3795
|
-
ctx.addIssue({ code:
|
|
3813
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: [f], message: `E2EE request must not carry plaintext ${f} \u2014 it's sealed in envelope.${f}` });
|
|
3796
3814
|
}
|
|
3797
3815
|
if (r.points !== void 0)
|
|
3798
|
-
ctx.addIssue({ code:
|
|
3816
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["points"], message: "E2EE request must not carry plaintext points" });
|
|
3799
3817
|
if (r.ask !== void 0)
|
|
3800
|
-
ctx.addIssue({ code:
|
|
3818
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["ask"], message: "E2EE request must not carry a plaintext ask \u2014 derive the shape agent-side and seal it" });
|
|
3801
3819
|
if (r.needs !== void 0)
|
|
3802
|
-
ctx.addIssue({ code:
|
|
3820
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["needs"], message: "E2EE request must not carry plaintext needs" });
|
|
3803
3821
|
return;
|
|
3804
3822
|
}
|
|
3805
3823
|
if (r.ask !== void 0) {
|
|
3806
3824
|
for (const f of ["context", "select", "points"]) {
|
|
3807
3825
|
if (r[f] !== void 0)
|
|
3808
|
-
ctx.addIssue({ code:
|
|
3826
|
+
ctx.addIssue({ code: z3.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.` });
|
|
3809
3827
|
}
|
|
3810
3828
|
return;
|
|
3811
3829
|
}
|
|
3812
3830
|
if (r.needs !== void 0 || r.urgencyHint !== void 0)
|
|
3813
|
-
ctx.addIssue({ code:
|
|
3831
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["needs"], message: "needs/urgencyHint belong to the simplified `ask` form \u2014 with a shaped request use points/urgency" });
|
|
3814
3832
|
if (!r.context)
|
|
3815
|
-
ctx.addIssue({ code:
|
|
3833
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["context"], message: "context is required (plaintext path)" });
|
|
3816
3834
|
if (!r.select)
|
|
3817
|
-
ctx.addIssue({ code:
|
|
3835
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["select"], message: "select is required on the shaped form" });
|
|
3818
3836
|
const needsOptions = r.select === "one" || r.select === "many" || r.select === "rank";
|
|
3819
3837
|
if (needsOptions && !r.options?.length)
|
|
3820
|
-
ctx.addIssue({ code:
|
|
3838
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' requires options` });
|
|
3821
3839
|
if (!needsOptions && r.options?.length)
|
|
3822
|
-
ctx.addIssue({ code:
|
|
3840
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["options"], message: `select:'${r.select}' takes no options` });
|
|
3823
3841
|
});
|
|
3824
3842
|
function normalizeWaiting(req) {
|
|
3825
3843
|
if (!req.waiting) return req;
|
|
@@ -3876,70 +3894,70 @@ function deriveAsk(req) {
|
|
|
3876
3894
|
...needs?.length ? { points: needs } : {}
|
|
3877
3895
|
};
|
|
3878
3896
|
}
|
|
3879
|
-
var NotifyStatusSchema =
|
|
3880
|
-
var AgentStateSchema =
|
|
3881
|
-
var SetTaskStateSchema =
|
|
3882
|
-
state:
|
|
3897
|
+
var NotifyStatusSchema = z3.enum(["pending", "answered", "ignored"]);
|
|
3898
|
+
var AgentStateSchema = z3.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
3899
|
+
var SetTaskStateSchema = z3.object({
|
|
3900
|
+
state: z3.enum(["in_progress", "completed", "needs_input"])
|
|
3883
3901
|
});
|
|
3884
|
-
var SetWorkStateSchema =
|
|
3885
|
-
workId:
|
|
3886
|
-
notificationId:
|
|
3902
|
+
var SetWorkStateSchema = z3.object({
|
|
3903
|
+
workId: z3.string().uuid().optional(),
|
|
3904
|
+
notificationId: z3.string().optional(),
|
|
3887
3905
|
state: SetTaskStateSchema.shape.state
|
|
3888
3906
|
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
3889
3907
|
message: "exactly one of workId or notificationId is required"
|
|
3890
3908
|
});
|
|
3891
|
-
var TurnSchema =
|
|
3892
|
-
prompt:
|
|
3893
|
-
reply:
|
|
3909
|
+
var TurnSchema = z3.object({
|
|
3910
|
+
prompt: z3.string(),
|
|
3911
|
+
reply: z3.string()
|
|
3894
3912
|
});
|
|
3895
|
-
var UserAnswerSchema =
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3913
|
+
var UserAnswerSchema = z3.discriminatedUnion("kind", [
|
|
3914
|
+
z3.object({ kind: z3.literal("option"), optionId: z3.string(), label: z3.string().optional() }),
|
|
3915
|
+
z3.object({ kind: z3.literal("text"), text: z3.string() }),
|
|
3916
|
+
z3.object({ kind: z3.literal("ignored") }),
|
|
3917
|
+
z3.object({ kind: z3.literal("multi"), optionIds: z3.array(z3.string()), labels: z3.array(z3.string()).optional() }),
|
|
3918
|
+
z3.object({ kind: z3.literal("ranked"), optionIds: z3.array(z3.string()), labels: z3.array(z3.string()).optional() }),
|
|
3919
|
+
z3.object({ kind: z3.literal("clarify"), chunks: z3.array(z3.string()).min(1) }),
|
|
3920
|
+
z3.object({ kind: z3.literal("confirm"), approved: z3.boolean() }),
|
|
3921
|
+
z3.object({ kind: z3.literal("turns"), turns: z3.array(TurnSchema).min(1) }),
|
|
3904
3922
|
/** An auto-answer derived from the user's PAST decisions (broker/precedent-design.md §2):
|
|
3905
3923
|
* delivered through the same settle/await path as a human answer, carrying the judge's
|
|
3906
3924
|
* derivation and the precedent ids it grew from. Always paired with a visible trail
|
|
3907
3925
|
* card the user can reply to — the broker never overrides the user. */
|
|
3908
|
-
|
|
3926
|
+
z3.object({ kind: z3.literal("precedent"), answer: z3.string(), derivation: z3.string(), sources: z3.array(z3.string()).min(1) })
|
|
3909
3927
|
]);
|
|
3910
|
-
var IntentSchema =
|
|
3928
|
+
var IntentSchema = z3.object({
|
|
3911
3929
|
// The full vocabulary the bot's mapper emits (mapper.INTENT_KINDS) — the schema lagged
|
|
3912
3930
|
// it by two ("detail", "feedback"), and because the settle handler parsed the array
|
|
3913
3931
|
// all-or-nothing, ONE feedback act silently dropped EVERY intent on the call,
|
|
3914
3932
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
3915
|
-
kind:
|
|
3916
|
-
detail:
|
|
3933
|
+
kind: z3.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
3934
|
+
detail: z3.string(),
|
|
3917
3935
|
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
3918
3936
|
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
3919
3937
|
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
3920
3938
|
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
3921
|
-
dueInSeconds:
|
|
3939
|
+
dueInSeconds: z3.number().int().positive().optional(),
|
|
3922
3940
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
3923
3941
|
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
3924
3942
|
* 'other' on every row. A table that records that something was wrong and nothing
|
|
3925
3943
|
* about what cannot answer "is the bot looping less this week?". */
|
|
3926
|
-
fault:
|
|
3944
|
+
fault: z3.enum(["loop", "unanswered", "overridden", "misheard", "slow", "other"]).optional()
|
|
3927
3945
|
});
|
|
3928
|
-
var RideAlongSchema =
|
|
3946
|
+
var RideAlongSchema = z3.object({
|
|
3929
3947
|
/** The note this came from — assign/clarify/close it through /api/notes/:id. */
|
|
3930
|
-
noteId:
|
|
3948
|
+
noteId: z3.string(),
|
|
3931
3949
|
/** What to do, in the owner's own words (the note's headline). Never model-rewritten. */
|
|
3932
|
-
text:
|
|
3950
|
+
text: z3.string(),
|
|
3933
3951
|
/** The thread to report back on, when the note was dispatched over the request rail. */
|
|
3934
|
-
parentId:
|
|
3952
|
+
parentId: z3.string().nullable()
|
|
3935
3953
|
});
|
|
3936
|
-
var AwaitItemSchema =
|
|
3937
|
-
|
|
3938
|
-
type:
|
|
3939
|
-
parentId:
|
|
3940
|
-
notificationId:
|
|
3941
|
-
workId:
|
|
3942
|
-
decisionId:
|
|
3954
|
+
var AwaitItemSchema = z3.discriminatedUnion("type", [
|
|
3955
|
+
z3.object({
|
|
3956
|
+
type: z3.literal("reply"),
|
|
3957
|
+
parentId: z3.string(),
|
|
3958
|
+
notificationId: z3.string(),
|
|
3959
|
+
workId: z3.string().uuid().optional(),
|
|
3960
|
+
decisionId: z3.string().uuid().optional(),
|
|
3943
3961
|
answer: UserAnswerSchema,
|
|
3944
3962
|
/** E2EE: present when the answer is sealed. The server relays the opaque answer
|
|
3945
3963
|
* envelope + the plaintext `ignored` status hint; the receiving agent OPENS it
|
|
@@ -3947,7 +3965,7 @@ var AwaitItemSchema = z2.discriminatedUnion("type", [
|
|
|
3947
3965
|
* against `ignored` as tampering. Absent = today's plaintext answer (in `answer`).
|
|
3948
3966
|
* On a sealed reply the plaintext `answer` is a placeholder (kind reflects only
|
|
3949
3967
|
* the `ignored` bit) — never the real content, which stays sealed. */
|
|
3950
|
-
sealed:
|
|
3968
|
+
sealed: z3.lazy(() => SealedAnswerSchema).optional(),
|
|
3951
3969
|
/** WHAT THE AGENT CANNOT KNOW FROM THE FIELDS BESIDE IT (owner, 2026-09-04, issue
|
|
3952
3970
|
* #1537). One line, built from the record: the ask and the caller's reply VERBATIM,
|
|
3953
3971
|
* the notification they belong to, and the `contact` call that reaches the person
|
|
@@ -3956,138 +3974,147 @@ var AwaitItemSchema = z2.discriminatedUnion("type", [
|
|
|
3956
3974
|
* "call me back after you merge" in their own words decides for itself what to do,
|
|
3957
3975
|
* and now knows exactly which call to make. Absent when either half is missing —
|
|
3958
3976
|
* a sentence with a hole in it is worse than no sentence. */
|
|
3959
|
-
note:
|
|
3977
|
+
note: z3.string().optional(),
|
|
3960
3978
|
/** The call record rendered for THIS agent (`voice/record-design.md`): the words the
|
|
3961
3979
|
* shaped answer was mapped from, filtered to its own claims. There is no second list
|
|
3962
3980
|
* of labels beside it — the acts went 2026-09-04 and `intents` went with them (owner,
|
|
3963
3981
|
* 2026-09-04): the agent reads the sentence and decides. */
|
|
3964
|
-
transcript:
|
|
3982
|
+
transcript: z3.string().optional(),
|
|
3965
3983
|
/** Coverage report (#396), when the ask declared `points`: which of them this
|
|
3966
3984
|
* answer addressed. Missing points = re-ask or proceed knowingly partial. */
|
|
3967
|
-
covered:
|
|
3985
|
+
covered: z3.array(z3.string()).optional(),
|
|
3968
3986
|
/** Ride-alongs (RideAlongSchema) — pending work for you, attached to the moment you
|
|
3969
3987
|
* became free. Only `reply` and `idle` carry it: those are the two outcomes that
|
|
3970
3988
|
* END a wait. `remind`, `superseded` and `turn` are mid-flight, and handing an
|
|
3971
3989
|
* agent a side-quest while it is still holding the line is how the main thing gets
|
|
3972
3990
|
* dropped. Absent/empty = nothing owed. */
|
|
3973
|
-
also:
|
|
3991
|
+
also: z3.array(RideAlongSchema).optional()
|
|
3974
3992
|
}),
|
|
3975
|
-
|
|
3976
|
-
type:
|
|
3977
|
-
parentId:
|
|
3978
|
-
notificationId:
|
|
3979
|
-
remindAt:
|
|
3993
|
+
z3.object({
|
|
3994
|
+
type: z3.literal("remind"),
|
|
3995
|
+
parentId: z3.string(),
|
|
3996
|
+
notificationId: z3.string(),
|
|
3997
|
+
remindAt: z3.string().datetime({ offset: true }),
|
|
3980
3998
|
/** Seconds until remindAt, server-computed — pass straight to ScheduleWakeup. */
|
|
3981
|
-
remindInSeconds:
|
|
3999
|
+
remindInSeconds: z3.number()
|
|
3982
4000
|
}),
|
|
3983
4001
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
3984
4002
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
3985
4003
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
3986
4004
|
* re-orient via get_thread / check_replies). */
|
|
3987
|
-
|
|
3988
|
-
type:
|
|
3989
|
-
parentId:
|
|
3990
|
-
notificationId:
|
|
4005
|
+
z3.object({
|
|
4006
|
+
type: z3.literal("superseded"),
|
|
4007
|
+
parentId: z3.string(),
|
|
4008
|
+
notificationId: z3.string()
|
|
3991
4009
|
}),
|
|
3992
4010
|
/** A LIVE call's turn, streamed as it lands (#783). PROVISIONAL: the user can still
|
|
3993
4011
|
* revise any of these until the final reply arrives — partial = intelligence,
|
|
3994
4012
|
* settled = authorization. Use it to PREPARE (fetch, draft, warm), never to act
|
|
3995
4013
|
* irreversibly. If `acts` carries a question aimed at you and you know the answer,
|
|
3996
4014
|
* contact on the same thread right away — the caller hears it on the same call. */
|
|
3997
|
-
|
|
3998
|
-
type:
|
|
3999
|
-
notificationId:
|
|
4000
|
-
inFlight:
|
|
4001
|
-
turn:
|
|
4002
|
-
idx:
|
|
4003
|
-
prompt:
|
|
4004
|
-
reply:
|
|
4005
|
-
acts:
|
|
4015
|
+
z3.object({
|
|
4016
|
+
type: z3.literal("partial"),
|
|
4017
|
+
notificationId: z3.string(),
|
|
4018
|
+
inFlight: z3.literal(true),
|
|
4019
|
+
turn: z3.object({
|
|
4020
|
+
idx: z3.number(),
|
|
4021
|
+
prompt: z3.string(),
|
|
4022
|
+
reply: z3.string(),
|
|
4023
|
+
acts: z3.array(IntentSchema).nullable().optional()
|
|
4006
4024
|
})
|
|
4007
4025
|
}),
|
|
4008
|
-
|
|
4026
|
+
z3.object({
|
|
4027
|
+
type: z3.literal("idle"),
|
|
4028
|
+
also: z3.array(RideAlongSchema).optional(),
|
|
4029
|
+
/** Is a call live for this agent's user right now? The SDK polls the partial stream
|
|
4030
|
+
* (#783) between idle ticks ONLY while this is not `false` — a partial can only exist
|
|
4031
|
+
* during a live call, and polling for one on a banner/message was a wasted HTTP call +
|
|
4032
|
+
* 3 queries on every idle tick of every waiting agent (~80% of all traffic at scale).
|
|
4033
|
+
* Absent = an older API → the SDK keeps polling, exactly as before. */
|
|
4034
|
+
inFlight: z3.boolean().optional()
|
|
4035
|
+
})
|
|
4009
4036
|
]);
|
|
4010
|
-
var CallbackTriggerSchema =
|
|
4011
|
-
var ScheduleCallbackSchema =
|
|
4012
|
-
parentId:
|
|
4013
|
-
goalId:
|
|
4037
|
+
var CallbackTriggerSchema = z3.enum(["on_done", "on_blocked", "scheduled"]);
|
|
4038
|
+
var ScheduleCallbackSchema = z3.object({
|
|
4039
|
+
parentId: z3.string().describe("The thread to call back on (from a prior contact / reply / request)."),
|
|
4040
|
+
goalId: z3.string().uuid().optional().describe("The Goal this callback advances; preferred for Goal-owned work."),
|
|
4014
4041
|
trigger: CallbackTriggerSchema,
|
|
4015
|
-
dueInSeconds:
|
|
4016
|
-
note:
|
|
4042
|
+
dueInSeconds: z3.number().int().positive().optional().describe("For 'scheduled' only: how many seconds from now to fire."),
|
|
4043
|
+
note: z3.string().optional().describe("What to tell the user when you follow up.")
|
|
4017
4044
|
});
|
|
4018
|
-
var PendingRepliesSchema =
|
|
4019
|
-
replies:
|
|
4020
|
-
|
|
4021
|
-
parentId:
|
|
4022
|
-
notificationId:
|
|
4023
|
-
workId:
|
|
4024
|
-
decisionId:
|
|
4045
|
+
var PendingRepliesSchema = z3.object({
|
|
4046
|
+
replies: z3.array(
|
|
4047
|
+
z3.object({
|
|
4048
|
+
parentId: z3.string(),
|
|
4049
|
+
notificationId: z3.string(),
|
|
4050
|
+
workId: z3.string().uuid().optional(),
|
|
4051
|
+
decisionId: z3.string().uuid().optional(),
|
|
4025
4052
|
answer: UserAnswerSchema,
|
|
4026
4053
|
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the
|
|
4027
4054
|
* pairing is E2EE — the agent opens it and re-derives the real answer. Absent =
|
|
4028
4055
|
* plaintext answer (in `answer`). See AwaitItemSchema's reply variant. */
|
|
4029
|
-
sealed:
|
|
4056
|
+
sealed: z3.lazy(() => SealedAnswerSchema).optional(),
|
|
4030
4057
|
/** The call record rendered for THIS agent: the raw words the shaped answer was
|
|
4031
4058
|
* mapped from, filtered to its own claims. See AwaitItemSchema's reply variant. */
|
|
4032
|
-
transcript:
|
|
4059
|
+
transcript: z3.string().optional(),
|
|
4033
4060
|
/** Coverage report (#396): which declared `points` this answer addressed. */
|
|
4034
|
-
covered:
|
|
4061
|
+
covered: z3.array(z3.string()).optional()
|
|
4035
4062
|
})
|
|
4036
4063
|
),
|
|
4037
|
-
pending:
|
|
4038
|
-
|
|
4064
|
+
pending: z3.array(
|
|
4065
|
+
z3.object({ parentId: z3.string(), notificationId: z3.string(), createdAt: z3.string() })
|
|
4039
4066
|
),
|
|
4040
4067
|
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
4041
4068
|
* sees for this session's identity. From inside a session there was no way to find out —
|
|
4042
4069
|
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
4043
4070
|
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
4044
4071
|
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
4045
|
-
you:
|
|
4072
|
+
you: z3.object({ name: z3.string(), device: z3.string().nullable(), tokenId: z3.string() }).optional(),
|
|
4046
4073
|
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
4047
4074
|
* contact on the same parentId. Keeps reappearing until you call
|
|
4048
4075
|
* set_work_state on its workId (or notificationId once while bootstrapping). */
|
|
4049
|
-
requests:
|
|
4050
|
-
|
|
4051
|
-
parentId:
|
|
4052
|
-
notificationId:
|
|
4053
|
-
workId:
|
|
4054
|
-
text:
|
|
4055
|
-
createdAt:
|
|
4076
|
+
requests: z3.array(
|
|
4077
|
+
z3.object({
|
|
4078
|
+
parentId: z3.string(),
|
|
4079
|
+
notificationId: z3.string(),
|
|
4080
|
+
workId: z3.string().uuid().optional(),
|
|
4081
|
+
text: z3.string(),
|
|
4082
|
+
createdAt: z3.string(),
|
|
4056
4083
|
/** The user seeded this request with a past conversation — call get_thread on it
|
|
4057
4084
|
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
4058
|
-
contextParentId:
|
|
4085
|
+
contextParentId: z3.string().optional(),
|
|
4059
4086
|
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
4060
4087
|
* the account — the name here — which has not been seen since it landed, so nobody
|
|
4061
4088
|
* came for it. Handed to you because you are the session that is here. Take it like
|
|
4062
4089
|
* any request (set_work_state claims it, reply with contact on its parentId), and say
|
|
4063
4090
|
* whose it was, because the user chose that agent on purpose. */
|
|
4064
|
-
stranded:
|
|
4091
|
+
stranded: z3.string().optional()
|
|
4065
4092
|
})
|
|
4066
4093
|
),
|
|
4067
4094
|
/** Durable outcomes currently owned by this agent. This is the Work-native queue; flat
|
|
4068
4095
|
* notification lists remain during migration so old clients keep their existing view. */
|
|
4069
|
-
work:
|
|
4070
|
-
workId:
|
|
4071
|
-
parentId:
|
|
4072
|
-
objective:
|
|
4073
|
-
state:
|
|
4074
|
-
blockedOn:
|
|
4075
|
-
updatedAt:
|
|
4096
|
+
work: z3.array(z3.object({
|
|
4097
|
+
workId: z3.string().uuid(),
|
|
4098
|
+
parentId: z3.string().optional(),
|
|
4099
|
+
objective: z3.string().nullable(),
|
|
4100
|
+
state: z3.enum(["active", "blocked", "waiting_external"]),
|
|
4101
|
+
blockedOn: z3.array(z3.string().uuid()),
|
|
4102
|
+
updatedAt: z3.string()
|
|
4076
4103
|
})).optional(),
|
|
4077
4104
|
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
4078
4105
|
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
4079
4106
|
* fulfill one by calling contact on its parentId. */
|
|
4080
4107
|
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
4081
4108
|
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
4082
|
-
also:
|
|
4083
|
-
owedCallbacks:
|
|
4084
|
-
|
|
4109
|
+
also: z3.array(RideAlongSchema).optional(),
|
|
4110
|
+
owedCallbacks: z3.array(
|
|
4111
|
+
z3.object({ parentId: z3.string(), trigger: CallbackTriggerSchema, note: z3.string() })
|
|
4085
4112
|
),
|
|
4086
4113
|
/** Work (either direction) you reported in_progress a while ago and never reported
|
|
4087
4114
|
* completed — likely left half-done by this session or a prior one that crashed or
|
|
4088
4115
|
* went idle. Report a real state (set_work_state) or continue the work. */
|
|
4089
|
-
stalled:
|
|
4090
|
-
|
|
4116
|
+
stalled: z3.array(
|
|
4117
|
+
z3.object({ parentId: z3.string(), notificationId: z3.string(), title: z3.string().nullable(), startedAt: z3.string() })
|
|
4091
4118
|
),
|
|
4092
4119
|
/** The queue rail (#614, pending/design.md): the same replies + requests, grouped by
|
|
4093
4120
|
* thread and ordered oldest-thread-first, so you work ONE thread at a time — fold all of
|
|
@@ -4096,125 +4123,132 @@ var PendingRepliesSchema = z2.object({
|
|
|
4096
4123
|
* ride the next turn. `items` are that thread's replies/requests in arrival order; the
|
|
4097
4124
|
* full payload for each is in the flat `replies`/`requests` arrays (matched by
|
|
4098
4125
|
* notificationId). Derived, never stored — a crashed agent recomputes it exactly. */
|
|
4099
|
-
threads:
|
|
4100
|
-
|
|
4101
|
-
parentId:
|
|
4102
|
-
busy:
|
|
4103
|
-
items:
|
|
4104
|
-
|
|
4105
|
-
kind:
|
|
4106
|
-
notificationId:
|
|
4107
|
-
at:
|
|
4126
|
+
threads: z3.array(
|
|
4127
|
+
z3.object({
|
|
4128
|
+
parentId: z3.string(),
|
|
4129
|
+
busy: z3.boolean(),
|
|
4130
|
+
items: z3.array(
|
|
4131
|
+
z3.object({
|
|
4132
|
+
kind: z3.enum(["reply", "request"]),
|
|
4133
|
+
notificationId: z3.string(),
|
|
4134
|
+
at: z3.string()
|
|
4108
4135
|
})
|
|
4109
4136
|
)
|
|
4110
4137
|
})
|
|
4111
4138
|
)
|
|
4112
4139
|
});
|
|
4113
|
-
var NotifyResponseSchema =
|
|
4114
|
-
notificationId:
|
|
4115
|
-
workId:
|
|
4116
|
-
decisionId:
|
|
4140
|
+
var NotifyResponseSchema = z3.object({
|
|
4141
|
+
notificationId: z3.string(),
|
|
4142
|
+
workId: z3.string().uuid().optional(),
|
|
4143
|
+
decisionId: z3.string().uuid().optional(),
|
|
4117
4144
|
status: NotifyStatusSchema,
|
|
4118
|
-
createdAt:
|
|
4145
|
+
createdAt: z3.string().datetime(),
|
|
4119
4146
|
answer: UserAnswerSchema.optional(),
|
|
4120
|
-
answeredAt:
|
|
4147
|
+
answeredAt: z3.string().datetime().optional(),
|
|
4121
4148
|
/** Ride-alongs for THIS agent — pending work it should pick up when it's done with
|
|
4122
4149
|
* what it came for. Present on any reply, because an unwakeable agent's only
|
|
4123
4150
|
* reliable moment is one it initiated. Absent/empty = nothing owed. */
|
|
4124
|
-
also:
|
|
4151
|
+
also: z3.array(RideAlongSchema).optional()
|
|
4125
4152
|
});
|
|
4126
|
-
var NotifyPlanUnitSchema =
|
|
4127
|
-
notificationId:
|
|
4153
|
+
var NotifyPlanUnitSchema = z3.object({
|
|
4154
|
+
notificationId: z3.string(),
|
|
4128
4155
|
/** The unit's own heading, so the agent can tell which of its paragraphs this became. */
|
|
4129
|
-
title:
|
|
4156
|
+
title: z3.string(),
|
|
4130
4157
|
/** How loudly this unit was arbitrated to arrive — per unit, which is the point of units. */
|
|
4131
4158
|
level: NotifyLevelSchema,
|
|
4132
4159
|
/** Answered from something the user already decided: nobody is interrupted, and a trail card
|
|
4133
4160
|
* says so. The agent should not wait on this one. */
|
|
4134
|
-
settled:
|
|
4161
|
+
settled: z3.literal(true).optional(),
|
|
4135
4162
|
/** What this unit would need to be answerable and does not carry (#894). A PROPOSAL to the
|
|
4136
4163
|
* agent — nothing here changed the ask, and ignoring it costs nothing. */
|
|
4137
|
-
needs:
|
|
4164
|
+
needs: z3.array(z3.enum(["options", "visuals"])).optional(),
|
|
4138
4165
|
/** The SHAPE the broker would give this unit, for the agent to ratify (#886/#894). The
|
|
4139
4166
|
* split layer reads prose and can see that a paragraph is a yes/no or a pick-one — but a
|
|
4140
4167
|
* broker that DECIDES that destroys the only fact separating a statement from a real ask
|
|
4141
4168
|
* (#731), so it is offered, never applied: the unit is stored `text` until the agent
|
|
4142
4169
|
* confirms the shape (POST /notify/:id/confirm). Ignoring it costs nothing. */
|
|
4143
|
-
proposal:
|
|
4170
|
+
proposal: z3.object({
|
|
4144
4171
|
select: SelectShapeSchema,
|
|
4145
|
-
options:
|
|
4172
|
+
options: z3.array(z3.object({ label: z3.string().min(1) })).optional()
|
|
4146
4173
|
}).optional(),
|
|
4147
4174
|
/** What the broker READ this unit as wanting from the human (#952 layer 2): a `decision`
|
|
4148
4175
|
* between alternatives, an `approval` the agent is blocked on, or `knowledge` it just
|
|
4149
4176
|
* needs to know. Reported so the agent can correct a misread the same way it ratifies a
|
|
4150
4177
|
* shape — the read RAISES (a decision always asks) and never silences a question the
|
|
4151
4178
|
* agent declared (#731, #923). */
|
|
4152
|
-
wants:
|
|
4179
|
+
wants: z3.enum(["decision", "approval", "knowledge"]).optional()
|
|
4153
4180
|
});
|
|
4154
|
-
var NotifyPlanSchema =
|
|
4155
|
-
units:
|
|
4181
|
+
var NotifyPlanSchema = z3.object({
|
|
4182
|
+
units: z3.array(NotifyPlanUnitSchema),
|
|
4156
4183
|
/** Which unit is this arrival's ONE interruption (units-design.md D23). Absent means nobody
|
|
4157
4184
|
* was interrupted — every unit was either settled or quiet enough to sit in the inbox. */
|
|
4158
|
-
speaks:
|
|
4185
|
+
speaks: z3.string().optional()
|
|
4159
4186
|
});
|
|
4160
|
-
var UserResponseSchema =
|
|
4161
|
-
requestId:
|
|
4187
|
+
var UserResponseSchema = z3.object({
|
|
4188
|
+
requestId: z3.string(),
|
|
4162
4189
|
answer: UserAnswerSchema,
|
|
4163
|
-
answeredAt:
|
|
4164
|
-
transcript:
|
|
4190
|
+
answeredAt: z3.string().datetime(),
|
|
4191
|
+
transcript: z3.string().optional(),
|
|
4165
4192
|
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the item was
|
|
4166
4193
|
* E2EE-sealed. Present → the server persists it opaquely and branches status on `ignored`;
|
|
4167
4194
|
* the plaintext `answer` is a placeholder (`{ kind: "ignored" }`) the server ignores for a
|
|
4168
4195
|
* sealed row. Absent = today's plaintext answer, unchanged. */
|
|
4169
|
-
sealed:
|
|
4196
|
+
sealed: z3.lazy(() => SealedAnswerSchema).optional(),
|
|
4170
4197
|
/** Coverage report (#396): which of the ask's declared `points` were addressed. */
|
|
4171
|
-
covered:
|
|
4198
|
+
covered: z3.array(z3.string()).optional()
|
|
4172
4199
|
});
|
|
4173
|
-
var VoiceKeySchema =
|
|
4174
|
-
var AgendaTurnSchema =
|
|
4200
|
+
var VoiceKeySchema = z3.enum(["rachel", "george", "jessica", "brian", "lily"]);
|
|
4201
|
+
var AgendaTurnSchema = z3.object({
|
|
4202
|
+
/** THE TURN'S IDENTITY (the first-sentence stream, 2026-09-09): the brain call that wrote
|
|
4203
|
+
* it and its place in that reply — `<brainCallId>:<index>`, with `:p` on the first
|
|
4204
|
+
* sentence a re-plan publishes ahead of the rest. A turn is spoken once, by this id: the
|
|
4205
|
+
* completion of a streamed re-plan carries the published sentence again, and the walk
|
|
4206
|
+
* drops what it already said by identity, never by the API's guess of what was polled.
|
|
4207
|
+
* Absent on plans nothing streams (a ring plan, a floor). */
|
|
4208
|
+
id: z3.string().optional(),
|
|
4175
4209
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
4176
4210
|
* the planner declares duplicates instead of asking them twice. */
|
|
4177
|
-
coveredIds:
|
|
4211
|
+
coveredIds: z3.array(z3.string()).optional(),
|
|
4178
4212
|
/** At most three short spoken sentences. Capped because a turn is a breath: a 1031-char
|
|
4179
4213
|
* line went out on 2026-07-28 and the caller could not answer it at all. */
|
|
4180
|
-
info:
|
|
4181
|
-
question:
|
|
4214
|
+
info: z3.array(z3.string().min(1)).max(3).default([]),
|
|
4215
|
+
question: z3.string().min(1).nullable(),
|
|
4182
4216
|
/** True on the one turn carrying the agent's own declared question. */
|
|
4183
|
-
asks:
|
|
4217
|
+
asks: z3.boolean().optional(),
|
|
4184
4218
|
/** The claim this turn belongs to (#781) — the RETURN identity: answers route by it.
|
|
4185
4219
|
* Absent on a single-claim plan (the session's own claim) and on shared context turns,
|
|
4186
4220
|
* which route nothing. */
|
|
4187
|
-
claimId:
|
|
4221
|
+
claimId: z3.string().optional(),
|
|
4188
4222
|
/** The claim's voice key (#462) — the OUTBOUND identity, audible who-is-asking. */
|
|
4189
|
-
voice:
|
|
4223
|
+
voice: z3.string().optional(),
|
|
4190
4224
|
/** The claim's AGENT NAME (#838) — the spoken identity. A voice alone doesn't say
|
|
4191
4225
|
* whose request this is: an item that folded in from another agent arrived as a bare
|
|
4192
4226
|
* non-sequitur ("First real production sign-in is yours to make whenever you want.")
|
|
4193
4227
|
* and the owner answered "What?". The bot names the agent before its first turn. */
|
|
4194
|
-
agent:
|
|
4228
|
+
agent: z3.string().optional(),
|
|
4195
4229
|
/** The claim's agent by ID — the pairing's connection id (`notifications.token_id`), the
|
|
4196
4230
|
* same id a face is minted from. A name is not an identity: two pairings may be called
|
|
4197
4231
|
* "Claude", and a name cannot be joined on. The record's entries carry it (`agent_id`)
|
|
4198
4232
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
4199
4233
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
4200
|
-
agentId:
|
|
4234
|
+
agentId: z3.string().optional(),
|
|
4201
4235
|
select: SelectShapeSchema.optional(),
|
|
4202
|
-
options:
|
|
4236
|
+
options: z3.array(OptionSchema.omit({ id: true })).optional(),
|
|
4203
4237
|
/** Pacing (#826, owner 2026-08-03: "how fast we move through them ... are parameters"):
|
|
4204
4238
|
* seconds the floor stays open after this turn speaks. Absent = the bot's defaults
|
|
4205
4239
|
* (the beat for context, the answer window for asks). Clamped bot-side. */
|
|
4206
|
-
pace:
|
|
4240
|
+
pace: z3.number().positive().optional(),
|
|
4207
4241
|
/** Whether the walk WAITS for an answer before moving on. Absent = derived as today
|
|
4208
4242
|
* (a question blocks, context flows). blocking:false on a question = ask and move
|
|
4209
4243
|
* on, the claim stays pending; blocking:true on context = hold for a reply. */
|
|
4210
|
-
blocking:
|
|
4244
|
+
blocking: z3.boolean().optional()
|
|
4211
4245
|
});
|
|
4212
4246
|
var CLAIM_STALE_MS = 30 * 6e4;
|
|
4213
|
-
var InboxItemSchema =
|
|
4214
|
-
id:
|
|
4247
|
+
var InboxItemSchema = z3.object({
|
|
4248
|
+
id: z3.string(),
|
|
4215
4249
|
/** The conversation thread + connection this item lives on. Present on the replied
|
|
4216
4250
|
* detail — they power History's "Continue" / "New session from this" (#57/#251). */
|
|
4217
|
-
parentId:
|
|
4251
|
+
parentId: z3.string().optional(),
|
|
4218
4252
|
/** THE ARRIVAL this row is one unit of (`notifications.ask_id` → `asks`). A claim is one
|
|
4219
4253
|
* arrival and its units are N rows of it, so this — not `parentId` — is what makes a
|
|
4220
4254
|
* multi-part notification one thing on screen. The thread is the whole CONVERSATION: it
|
|
@@ -4222,13 +4256,13 @@ var InboxItemSchema = z2.object({
|
|
|
4222
4256
|
* unrelated updates as a single "12-part request". Absent on rows written before the
|
|
4223
4257
|
* `asks` table, and on anything that never went through `notify` — both fall back to the
|
|
4224
4258
|
* thread, which is what the client did for all rows until now. */
|
|
4225
|
-
askId:
|
|
4259
|
+
askId: z3.string().optional(),
|
|
4226
4260
|
/** WHERE this unit sat in the message it was cut from (`notifications.seq`). The batch
|
|
4227
4261
|
* shares one `created_at` to the microsecond, so without it the author's order is
|
|
4228
4262
|
* unrecoverable client-side — a four-paragraph briefing rendered opening-paragraph-last
|
|
4229
4263
|
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
4230
4264
|
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
4231
|
-
seq:
|
|
4265
|
+
seq: z3.number().int().optional(),
|
|
4232
4266
|
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
4233
4267
|
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
4234
4268
|
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
@@ -4237,13 +4271,13 @@ var InboxItemSchema = z2.object({
|
|
|
4237
4271
|
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
4238
4272
|
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
4239
4273
|
* the one part of its own arrival a single row cannot answer. */
|
|
4240
|
-
units:
|
|
4241
|
-
tokenId:
|
|
4274
|
+
units: z3.number().int().positive().optional(),
|
|
4275
|
+
tokenId: z3.string().optional(),
|
|
4242
4276
|
status: NotifyStatusSchema,
|
|
4243
4277
|
context: ContextSchema,
|
|
4244
|
-
options:
|
|
4278
|
+
options: z3.array(OptionSchema).optional(),
|
|
4245
4279
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
4246
|
-
points:
|
|
4280
|
+
points: z3.array(z3.string()).optional(),
|
|
4247
4281
|
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
4248
4282
|
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
4249
4283
|
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
@@ -4252,12 +4286,12 @@ var InboxItemSchema = z2.object({
|
|
|
4252
4286
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
4253
4287
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
4254
4288
|
* and neither of those is "got it" (owner, 2026-08-10). */
|
|
4255
|
-
asks:
|
|
4289
|
+
asks: z3.boolean().optional(),
|
|
4256
4290
|
/** When a live process last pulsed for this row's agent — the liveness input for
|
|
4257
4291
|
* "working requires a pulse" (#928): the list said "Working…" from agent_state alone
|
|
4258
4292
|
* while the party called the same dead claim stalled. Absent = no token/no data,
|
|
4259
4293
|
* which must never CLAIM stalled. */
|
|
4260
|
-
lastSeenAt:
|
|
4294
|
+
lastSeenAt: z3.string().optional(),
|
|
4261
4295
|
/** WHEN THE AGENT LAST SAID ANYTHING ABOUT THIS CLAIM — the newest `agent_state` row in
|
|
4262
4296
|
* the `notification_events` ledger (trigger-written since 20260621010000, so every row a
|
|
4263
4297
|
* user can see has one). The age input for `CLAIM_STALE_MS`, and it has to be this rather
|
|
@@ -4267,53 +4301,53 @@ var InboxItemSchema = z2.object({
|
|
|
4267
4301
|
* work. Reading the row's birth as the claim's age brands that "No update in 8h" the
|
|
4268
4302
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
4269
4303
|
* `createdAt`. */
|
|
4270
|
-
agentStateAt:
|
|
4271
|
-
agenda:
|
|
4272
|
-
visuals:
|
|
4304
|
+
agentStateAt: z3.string().datetime().optional(),
|
|
4305
|
+
agenda: z3.array(AgendaTurnSchema).optional(),
|
|
4306
|
+
visuals: z3.array(VisualSchema).optional(),
|
|
4273
4307
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
4274
4308
|
* agent's suggestion, or a default silly name). */
|
|
4275
|
-
name:
|
|
4309
|
+
name: z3.string(),
|
|
4276
4310
|
/** The pairing's assigned voice (#462); absent = the default voice. */
|
|
4277
4311
|
voice: VoiceKeySchema.optional(),
|
|
4278
|
-
repo:
|
|
4279
|
-
branch:
|
|
4280
|
-
createdAt:
|
|
4281
|
-
snoozedUntil:
|
|
4312
|
+
repo: z3.string().optional(),
|
|
4313
|
+
branch: z3.string().optional(),
|
|
4314
|
+
createdAt: z3.string().datetime(),
|
|
4315
|
+
snoozedUntil: z3.string().datetime().optional(),
|
|
4282
4316
|
agentState: AgentStateSchema.default("idle"),
|
|
4283
4317
|
/** Whose action the item is waiting on: "you" = an agent asked you (the default,
|
|
4284
4318
|
* every agent→user notification); "agent" = you sent a request and it's awaiting the
|
|
4285
4319
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
4286
|
-
turn:
|
|
4320
|
+
turn: z3.enum(["you", "agent"]).default("you"),
|
|
4287
4321
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
4288
4322
|
* the agent (provider-agnostic; set server-side). Absent = no hard error, though the
|
|
4289
4323
|
* client may still flag a stall by age. Drives the inbox error badge + Retry. */
|
|
4290
|
-
error:
|
|
4291
|
-
clarifies:
|
|
4324
|
+
error: z3.string().optional(),
|
|
4325
|
+
clarifies: z3.string().optional(),
|
|
4292
4326
|
/** The ring ladder ran out while this was still pending — we tried to reach you and
|
|
4293
4327
|
* STOPPED trying (`arbitration/arbitrate.ts` `nextRing` → `stop`). Distinct from an
|
|
4294
4328
|
* agent with nothing to say, which the roster drew identically until now: "nothing to
|
|
4295
4329
|
* say" and "gave up saying it" are opposite situations wearing the same face
|
|
4296
4330
|
* (navigation-design.md, gap 1). False for anything that never rang. */
|
|
4297
|
-
gaveUp:
|
|
4331
|
+
gaveUp: z3.boolean().default(false),
|
|
4298
4332
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
4299
4333
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
4300
4334
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
4301
4335
|
* 2026-08-07) — its real job is to give "this didn't need a call" something to be
|
|
4302
4336
|
* feedback ABOUT. */
|
|
4303
|
-
why:
|
|
4337
|
+
why: z3.object({
|
|
4304
4338
|
asked: NotifyLevelSchema,
|
|
4305
4339
|
got: NotifyLevelSchema,
|
|
4306
|
-
because:
|
|
4307
|
-
line:
|
|
4340
|
+
because: z3.enum(["unresponsive", "dismissed", "not_permitted", "silent", "coalesced", "agent_capped", "unplanned", "learned_raise"]).optional(),
|
|
4341
|
+
line: z3.string()
|
|
4308
4342
|
}).optional(),
|
|
4309
|
-
select:
|
|
4310
|
-
confirmStyle:
|
|
4343
|
+
select: z3.enum(["one", "many", "rank", "confirm", "text"]).default("one"),
|
|
4344
|
+
confirmStyle: z3.enum(["yesno", "approve"]).default("yesno").describe(
|
|
4311
4345
|
"Labels for a select:'confirm' paige \u2014 'yesno' (Yes/No) or 'approve' (Approve/Deny). Ignored unless select is 'confirm'."
|
|
4312
4346
|
),
|
|
4313
4347
|
/** Real downstream work is stuck behind this one — set by the agent, independent of
|
|
4314
4348
|
* urgency (see the main README's "premier use case" + notify/states.md). Drives the
|
|
4315
4349
|
* inbox's blocking badge and the extra confirm step before dismissing it. */
|
|
4316
|
-
blocking:
|
|
4350
|
+
blocking: z3.boolean().default(false),
|
|
4317
4351
|
/** The user's locked-in answer; present only for replied items (GET /api/replied/:id). */
|
|
4318
4352
|
answer: UserAnswerSchema.optional(),
|
|
4319
4353
|
/** E2EE (text lane): the sealed content this item carries when the pairing is E2EE, in
|
|
@@ -4322,39 +4356,39 @@ var InboxItemSchema = z2.object({
|
|
|
4322
4356
|
* NotifyRequest.envelope. The native app decrypts it locally via openNotification;
|
|
4323
4357
|
* web / an un-enrolled device can't and renders a locked placeholder. Absent = today's
|
|
4324
4358
|
* plaintext item (context carries the cleartext), so plaintext items are unchanged. */
|
|
4325
|
-
envelope:
|
|
4326
|
-
context:
|
|
4327
|
-
options:
|
|
4328
|
-
visuals:
|
|
4359
|
+
envelope: z3.object({
|
|
4360
|
+
context: z3.lazy(() => EnvelopeSchema).optional(),
|
|
4361
|
+
options: z3.lazy(() => EnvelopeSchema).optional(),
|
|
4362
|
+
visuals: z3.lazy(() => EnvelopeSchema).optional()
|
|
4329
4363
|
}).optional(),
|
|
4330
4364
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
4331
4365
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
4332
4366
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
4333
|
-
agentX25519:
|
|
4367
|
+
agentX25519: z3.string().optional()
|
|
4334
4368
|
});
|
|
4335
|
-
var SnoozeRequestSchema =
|
|
4336
|
-
requestId:
|
|
4337
|
-
until:
|
|
4369
|
+
var SnoozeRequestSchema = z3.object({
|
|
4370
|
+
requestId: z3.string(),
|
|
4371
|
+
until: z3.string().datetime()
|
|
4338
4372
|
});
|
|
4339
4373
|
var APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
4340
|
-
var PushTokenSchema =
|
|
4341
|
-
voipToken:
|
|
4342
|
-
alertToken:
|
|
4343
|
-
fcmToken:
|
|
4344
|
-
platform:
|
|
4374
|
+
var PushTokenSchema = z3.object({
|
|
4375
|
+
voipToken: z3.string().min(1).optional(),
|
|
4376
|
+
alertToken: z3.string().min(1).optional(),
|
|
4377
|
+
fcmToken: z3.string().min(1).optional(),
|
|
4378
|
+
platform: z3.enum(["ios", "android"])
|
|
4345
4379
|
}).superRefine((v, ctx) => {
|
|
4346
4380
|
if (v.platform !== "ios") return;
|
|
4347
4381
|
for (const field of ["voipToken", "alertToken"]) {
|
|
4348
4382
|
const token = v[field];
|
|
4349
4383
|
if (token === void 0 || APNS_TOKEN_RE.test(token)) continue;
|
|
4350
4384
|
ctx.addIssue({
|
|
4351
|
-
code:
|
|
4385
|
+
code: z3.ZodIssueCode.custom,
|
|
4352
4386
|
path: [field],
|
|
4353
4387
|
message: `not an APNs device token (want 64 hex chars, got ${token.length})`
|
|
4354
4388
|
});
|
|
4355
4389
|
}
|
|
4356
4390
|
});
|
|
4357
|
-
var MissedCallSchema =
|
|
4391
|
+
var MissedCallSchema = z3.enum([
|
|
4358
4392
|
"retry_10m",
|
|
4359
4393
|
"retry_30m",
|
|
4360
4394
|
"retry_60m",
|
|
@@ -4364,32 +4398,32 @@ var MissedCallSchema = z2.enum([
|
|
|
4364
4398
|
"inbox",
|
|
4365
4399
|
"dismiss"
|
|
4366
4400
|
]);
|
|
4367
|
-
var BrokerTuningSchema =
|
|
4401
|
+
var BrokerTuningSchema = z3.object({
|
|
4368
4402
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
4369
|
-
ackVerbosity:
|
|
4403
|
+
ackVerbosity: z3.enum(["normal", "none"]).optional(),
|
|
4370
4404
|
/** How readily the mapper asks its one clarification: 'low' = only when truly
|
|
4371
4405
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
4372
|
-
clarifyEagerness:
|
|
4406
|
+
clarifyEagerness: z3.enum(["low", "normal", "high"]).optional(),
|
|
4373
4407
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
4374
|
-
phrasebook:
|
|
4408
|
+
phrasebook: z3.array(z3.object({ say: z3.string().min(1).max(60), mean: z3.string().min(1).max(120) })).max(24).optional(),
|
|
4375
4409
|
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
4376
4410
|
* which is every account today — means the agent's own words decide, per ask: a call
|
|
4377
4411
|
* about an English ask opens in English. This is the only thing that overrides that,
|
|
4378
4412
|
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
4379
4413
|
* Set per user (no UI yet), like `voiceTuning`. */
|
|
4380
|
-
language:
|
|
4414
|
+
language: z3.enum(["en", "es"]).optional()
|
|
4381
4415
|
});
|
|
4382
|
-
var UserSettingsSchema =
|
|
4383
|
-
permissions:
|
|
4384
|
-
call:
|
|
4385
|
-
banner:
|
|
4386
|
-
push:
|
|
4416
|
+
var UserSettingsSchema = z3.object({
|
|
4417
|
+
permissions: z3.object({
|
|
4418
|
+
call: z3.boolean(),
|
|
4419
|
+
banner: z3.boolean(),
|
|
4420
|
+
push: z3.boolean()
|
|
4387
4421
|
}),
|
|
4388
|
-
sessionMode:
|
|
4389
|
-
silentPush:
|
|
4390
|
-
autoCallback:
|
|
4422
|
+
sessionMode: z3.enum(["default", "all_calls", "silent"]),
|
|
4423
|
+
silentPush: z3.boolean(),
|
|
4424
|
+
autoCallback: z3.boolean(),
|
|
4391
4425
|
/** Opt-in (default false) to using your content to improve Paigy and train models. */
|
|
4392
|
-
improveConsent:
|
|
4426
|
+
improveConsent: z3.boolean(),
|
|
4393
4427
|
missedCall: MissedCallSchema.default("backoff_standard"),
|
|
4394
4428
|
/** Where voice audio is processed. 'hosted' (default) = Paigy's voice services
|
|
4395
4429
|
* (ElevenLabs TTS, faster-whisper STT, the call bot); 'on_device' = the phone
|
|
@@ -4397,7 +4431,7 @@ var UserSettingsSchema = z2.object({
|
|
|
4397
4431
|
* Optional, NOT defaulted: a stale client PATCHing the full settings object
|
|
4398
4432
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
4399
4433
|
* write, 'hosted' on read (see store.ts). */
|
|
4400
|
-
voiceMode:
|
|
4434
|
+
voiceMode: z3.enum(["hosted", "on_device"]).optional(),
|
|
4401
4435
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
4402
4436
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
4403
4437
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -4405,10 +4439,10 @@ var UserSettingsSchema = z2.object({
|
|
|
4405
4439
|
* payload['tuning'] (e.g. { silence_s: 3.5 } — a longer pause window for a
|
|
4406
4440
|
* slower speaker). No API-side semantics; the bot resolves each key with its
|
|
4407
4441
|
* own defaults. Set per user (no UI yet); absent = bot defaults. */
|
|
4408
|
-
voiceTuning:
|
|
4442
|
+
voiceTuning: z3.record(z3.string(), z3.union([z3.number(), z3.string()])).optional(),
|
|
4409
4443
|
/** Opt-in to real-phone (PSTN) calls when the app can't ring. Optional, not
|
|
4410
4444
|
* defaulted — an older client PATCHing the full object must not clobber it. */
|
|
4411
|
-
pstnCalls:
|
|
4445
|
+
pstnCalls: z3.boolean().optional(),
|
|
4412
4446
|
/** The user's IANA timezone (e.g. "America/Bogota"), recorded by the app — it is the
|
|
4413
4447
|
* only party that knows it. REMINDERS are why it exists: "remind me at ten" becomes
|
|
4414
4448
|
* an absolute `due_at` only if we know whose ten. Optional and never defaulted, for
|
|
@@ -4417,59 +4451,59 @@ var UserSettingsSchema = z2.object({
|
|
|
4417
4451
|
* that failure reads as the reminder rail being unreliable rather than as a missing
|
|
4418
4452
|
* setting. Absent = a spoken time can't be landed, so the reminder rides the next
|
|
4419
4453
|
* call — honest about what we know. */
|
|
4420
|
-
timezone:
|
|
4454
|
+
timezone: z3.string().min(1).max(64).optional(),
|
|
4421
4455
|
/** Account E2EE state (text lane): 'off' (default) = today's plaintext; 'on' =
|
|
4422
4456
|
* content is sealed end-to-end between the local agent and the phone. Like
|
|
4423
4457
|
* voiceMode, OPTIONAL and NOT defaulted so a stale client PATCHing the full
|
|
4424
4458
|
* settings object without it can't silently flip the account's E2EE state.
|
|
4425
4459
|
* Absent = leave unchanged on write, 'off' on read (see store.ts). The demo
|
|
4426
4460
|
* account is plaintext by construction and refuses any non-'off' value. */
|
|
4427
|
-
e2eeMode:
|
|
4461
|
+
e2eeMode: z3.enum(["off", "on"]).optional(),
|
|
4428
4462
|
/** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
|
|
4429
4463
|
* clobber guard as voiceMode: absent = leave unchanged on write. */
|
|
4430
4464
|
broker: BrokerTuningSchema.optional()
|
|
4431
4465
|
});
|
|
4432
|
-
var HistoryItemSchema =
|
|
4433
|
-
id:
|
|
4434
|
-
parentId:
|
|
4466
|
+
var HistoryItemSchema = z3.object({
|
|
4467
|
+
id: z3.string(),
|
|
4468
|
+
parentId: z3.string(),
|
|
4435
4469
|
/** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
|
|
4436
|
-
initiator:
|
|
4437
|
-
title:
|
|
4470
|
+
initiator: z3.enum(["user", "agent"]),
|
|
4471
|
+
title: z3.string(),
|
|
4438
4472
|
/** The agent on the other end (its name). */
|
|
4439
|
-
name:
|
|
4440
|
-
createdAt:
|
|
4473
|
+
name: z3.string(),
|
|
4474
|
+
createdAt: z3.string(),
|
|
4441
4475
|
/** When the agent fetched your request (user→agent only). */
|
|
4442
|
-
agentAckedAt:
|
|
4476
|
+
agentAckedAt: z3.string().nullable(),
|
|
4443
4477
|
/** When you answered the agent's notification (agent→user only). */
|
|
4444
|
-
humanAckedAt:
|
|
4478
|
+
humanAckedAt: z3.string().nullable()
|
|
4445
4479
|
});
|
|
4446
4480
|
var ACTIVITY_LINES = 2;
|
|
4447
4481
|
var ACTIVITY_LINE_MAX = 80;
|
|
4448
|
-
var AgentActivitySchema =
|
|
4482
|
+
var AgentActivitySchema = z3.object({
|
|
4449
4483
|
/** Oldest first, so the newest line is last — the one that replaces in place. */
|
|
4450
|
-
lines:
|
|
4484
|
+
lines: z3.array(z3.string().max(ACTIVITY_LINE_MAX)).max(ACTIVITY_LINES),
|
|
4451
4485
|
/** When the harness observed this tail. Its own timestamp, not the heartbeat's: a beat
|
|
4452
4486
|
* that carries an UNCHANGED tail must not make a stalled agent look like it just moved. */
|
|
4453
|
-
at:
|
|
4487
|
+
at: z3.string().datetime()
|
|
4454
4488
|
});
|
|
4455
|
-
var ConnectionSummarySchema =
|
|
4489
|
+
var ConnectionSummarySchema = z3.object({
|
|
4456
4490
|
/** The connection = the agent's token id (used to address a request). */
|
|
4457
|
-
id:
|
|
4491
|
+
id: z3.string(),
|
|
4458
4492
|
/** The credential kind: "device" = a paired machine (mint-only — it hosts and mints, it
|
|
4459
4493
|
* never talks); "agent" = an identity that sends. The roster and devices surfaces split
|
|
4460
4494
|
* on this. Optional/absent reads as "agent" (a row predating the kind column). See
|
|
4461
4495
|
* apps/api/src/tokens/devices-vs-agents-design.md. */
|
|
4462
|
-
kind:
|
|
4496
|
+
kind: z3.enum(["device", "agent"]).optional(),
|
|
4463
4497
|
/** For an agent, the token id of the DEVICE that minted it — so agents group under their
|
|
4464
4498
|
* machine, and revoking a device cascades to them. Null on devices, and on unlinked
|
|
4465
4499
|
* agents (phone-launched, provider-managed, or minted before the link existed). */
|
|
4466
|
-
mintedByDevice:
|
|
4467
|
-
device:
|
|
4500
|
+
mintedByDevice: z3.string().nullable().optional(),
|
|
4501
|
+
device: z3.string().nullable(),
|
|
4468
4502
|
/** The agent's display name (the single pairing name). */
|
|
4469
|
-
name:
|
|
4503
|
+
name: z3.string(),
|
|
4470
4504
|
/** For a managed connection, the provider key (e.g. "cma") that agentOrigin maps to a
|
|
4471
4505
|
* label; null for a local connection. Sourced from the token's provider, not the name. */
|
|
4472
|
-
provider:
|
|
4506
|
+
provider: z3.string().nullable(),
|
|
4473
4507
|
/** The pairing's assigned voice (#462); null = the default voice. */
|
|
4474
4508
|
voice: VoiceKeySchema.nullable(),
|
|
4475
4509
|
/** The LOUDEST this agent may ever reach you — a ceiling on `NOTIFY_LADDER`, set by the
|
|
@@ -4480,19 +4514,19 @@ var ConnectionSummarySchema = z2.object({
|
|
|
4480
4514
|
* every surface at once and outranks even `sessionMode: all_calls` — a mode the user
|
|
4481
4515
|
* set once must not overrule a rule they set about one agent. */
|
|
4482
4516
|
reach: NotifyLevelSchema.nullable().optional(),
|
|
4483
|
-
createdAt:
|
|
4517
|
+
createdAt: z3.string().datetime(),
|
|
4484
4518
|
/** Most recent notification on this connection, either direction. Null = no contact yet.
|
|
4485
4519
|
* Drives the agents-page recency grouping (Today / This week / …). */
|
|
4486
|
-
lastContactAt:
|
|
4520
|
+
lastContactAt: z3.string().datetime().nullable(),
|
|
4487
4521
|
/** Last presence heartbeat from a running agent process (POST /api/presence) — the
|
|
4488
4522
|
* desktop app while open. Null = never seen; stale = offline. */
|
|
4489
|
-
lastSeenAt:
|
|
4523
|
+
lastSeenAt: z3.string().datetime().nullable().optional(),
|
|
4490
4524
|
/** What a live desktop can run (companion.md §2.2), advertised on its heartbeat:
|
|
4491
4525
|
* harness availabilities + granted workspaces — the option set the phone's
|
|
4492
4526
|
* "new session" sheet offers. Absent for ordinary MCP agents. */
|
|
4493
|
-
runtime:
|
|
4494
|
-
harnesses:
|
|
4495
|
-
workspaces:
|
|
4527
|
+
runtime: z3.object({
|
|
4528
|
+
harnesses: z3.array(z3.object({ name: z3.string(), label: z3.string(), status: z3.string() })).optional(),
|
|
4529
|
+
workspaces: z3.array(z3.string()).optional()
|
|
4496
4530
|
}).optional(),
|
|
4497
4531
|
/** The tail of this agent's working log, when a harness is driving it — the agent page's
|
|
4498
4532
|
* live strip. Absent for anything the desktop harness isn't running (a hatched identity
|
|
@@ -4501,103 +4535,119 @@ var ConnectionSummarySchema = z2.object({
|
|
|
4501
4535
|
activity: AgentActivitySchema.optional(),
|
|
4502
4536
|
/** True = a provider-managed agent running in the provider's cloud (e.g. Anthropic CMA);
|
|
4503
4537
|
* false = a local MCP connection running on the user's computer (Claude Code/Codex/…). */
|
|
4504
|
-
managed:
|
|
4538
|
+
managed: z3.boolean()
|
|
4539
|
+
});
|
|
4540
|
+
var LedgerItemSchema = z3.object({ id: z3.string(), parentId: z3.string(), title: z3.string(), createdAt: z3.string() });
|
|
4541
|
+
var AgentLedgerSchema = z3.object({
|
|
4542
|
+
agent: z3.object({ id: z3.string(), name: z3.string(), revokedAt: z3.string().nullable() }),
|
|
4543
|
+
/** Its own questions you have not answered. */
|
|
4544
|
+
asks: z3.array(LedgerItemSchema),
|
|
4545
|
+
/** Its questions you answered that nobody acted on — still owed to somebody. */
|
|
4546
|
+
answered: z3.array(LedgerItemSchema),
|
|
4547
|
+
/** Requests you sent it that it never took. */
|
|
4548
|
+
requests: z3.array(LedgerItemSchema),
|
|
4549
|
+
goals: z3.array(z3.object({ id: z3.string(), outcome: z3.string(), state: z3.string() })),
|
|
4550
|
+
callbacks: z3.array(z3.object({ id: z3.string(), parentId: z3.string(), trigger: z3.string(), note: z3.string(), dueAt: z3.string().nullable() }))
|
|
4505
4551
|
});
|
|
4506
|
-
var
|
|
4507
|
-
|
|
4508
|
-
|
|
4552
|
+
var ReassignResultSchema = z3.object({
|
|
4553
|
+
moved: z3.object({ asks: z3.number(), answered: z3.number(), requests: z3.number(), goals: z3.number(), callbacks: z3.number() }),
|
|
4554
|
+
parentId: z3.string().nullable()
|
|
4555
|
+
});
|
|
4556
|
+
var MoveRingSchema = z3.enum(["home", "travels", "retired", "quarantined"]);
|
|
4557
|
+
var MoveSchema = z3.object({
|
|
4558
|
+
id: z3.string(),
|
|
4509
4559
|
/** The reusable question, as distill normalized it. */
|
|
4510
|
-
question:
|
|
4560
|
+
question: z3.string(),
|
|
4511
4561
|
/** The operative ruling. Editable by the user (PATCH) — which resets the ledger. */
|
|
4512
|
-
answer:
|
|
4562
|
+
answer: z3.string(),
|
|
4513
4563
|
/** The user's stated reason, when they gave one. Null = inherently narrow: the judge is
|
|
4514
4564
|
* told so, and the ruling only derives essentially the same question in the same scope. */
|
|
4515
|
-
rationale:
|
|
4565
|
+
rationale: z3.string().nullable(),
|
|
4516
4566
|
/** Where the ruling lives: a repo/workspace, or 'global'. */
|
|
4517
|
-
scope:
|
|
4567
|
+
scope: z3.string(),
|
|
4518
4568
|
ring: MoveRingSchema,
|
|
4519
4569
|
/** True = the user pinned it with `always` (travel granted by hand, not by evidence). */
|
|
4520
|
-
pinned:
|
|
4570
|
+
pinned: z3.boolean(),
|
|
4521
4571
|
/** True = a pin the user placed was BROKEN by later counter-evidence. Surfaced so the
|
|
4522
4572
|
* break is visible instead of a pin silently disappearing. */
|
|
4523
|
-
pinBroken:
|
|
4573
|
+
pinBroken: z3.boolean(),
|
|
4524
4574
|
/** When the ruling was distilled. */
|
|
4525
|
-
learnedAt:
|
|
4575
|
+
learnedAt: z3.string(),
|
|
4526
4576
|
/** Last time it answered an ask. Null = never fired. */
|
|
4527
|
-
lastUsedAt:
|
|
4577
|
+
lastUsedAt: z3.string().nullable(),
|
|
4528
4578
|
/** How many asks it has answered. Instrumentation — deliberately NOT an input to the
|
|
4529
4579
|
* evidence curve: firing says the question keeps arising, not that the ruling is right. */
|
|
4530
|
-
usedCount:
|
|
4580
|
+
usedCount: z3.number(),
|
|
4531
4581
|
/** Ledger: outcomes that said it held up. Saturating — the tenth is worth almost nothing. */
|
|
4532
|
-
confirms:
|
|
4582
|
+
confirms: z3.number(),
|
|
4533
4583
|
/** Ledger: contradictions, in signal units (a full override = 1, weaker signals less).
|
|
4534
4584
|
* Linear and priced above the entire confirmation budget, so any full counter wins. */
|
|
4535
|
-
counters:
|
|
4585
|
+
counters: z3.number(),
|
|
4536
4586
|
/** The agent that asked the question this move came from, when known. Null for a move
|
|
4537
4587
|
* distilled from a clarify ruling (those carry no agent) or one whose source rows are gone. */
|
|
4538
|
-
learnedFrom:
|
|
4588
|
+
learnedFrom: z3.object({ id: z3.string(), name: z3.string() }).nullable()
|
|
4539
4589
|
});
|
|
4540
|
-
var CreateRequestSchema =
|
|
4590
|
+
var CreateRequestSchema = z3.object({
|
|
4541
4591
|
/** The connection (token id) to send to, from GET /api/tokens. */
|
|
4542
|
-
tokenId:
|
|
4592
|
+
tokenId: z3.string(),
|
|
4543
4593
|
/** The user's message to the agent. */
|
|
4544
|
-
text:
|
|
4594
|
+
text: z3.string().min(1),
|
|
4545
4595
|
/** Land the request on an existing conversation thread (History → "Continue")
|
|
4546
4596
|
* instead of minting a fresh one. Must belong to the requesting user. */
|
|
4547
|
-
parentId:
|
|
4597
|
+
parentId: z3.string().optional(),
|
|
4548
4598
|
/** Point the agent at a past conversation (possibly with a different agent) as
|
|
4549
4599
|
* starting context (History → "New session from this"). A reference, not a copy —
|
|
4550
4600
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
4551
|
-
contextParentId:
|
|
4601
|
+
contextParentId: z3.string().optional()
|
|
4552
4602
|
});
|
|
4553
|
-
var HandoffSchema =
|
|
4603
|
+
var HandoffSchema = z3.object({
|
|
4554
4604
|
/** Move this existing outcome to `target` without reminting it. Requires `target`. */
|
|
4555
|
-
workId:
|
|
4605
|
+
workId: z3.string().uuid().optional(),
|
|
4556
4606
|
/** Land the note on an existing thread; omitted mints a fresh one. */
|
|
4557
|
-
parentId:
|
|
4607
|
+
parentId: z3.string().uuid().optional(),
|
|
4558
4608
|
/** One-line headline of the working context handed off. */
|
|
4559
|
-
title:
|
|
4609
|
+
title: z3.string().min(1),
|
|
4560
4610
|
/** The brief — standalone notes the successor reads (what was done, what's left, links). */
|
|
4561
|
-
notes:
|
|
4611
|
+
notes: z3.array(z3.string().min(1)).min(1),
|
|
4562
4612
|
/** A sibling connection to dispatch directly to (token id or agent nickname). Same-account
|
|
4563
4613
|
* only; omit to leave the thread for the user to hand off in the app. */
|
|
4564
|
-
target:
|
|
4614
|
+
target: z3.string().optional(),
|
|
4565
4615
|
/** Write the note as a RECAP (kind:'recap', #617): a summary turn that supersedes the
|
|
4566
4616
|
* thread's earlier turns for rehydration — get_thread returns the latest recap + only
|
|
4567
4617
|
* the turns after it. Handoff-to-a-successor and handoff-to-yourself-later are the
|
|
4568
4618
|
* same primitive; a recap is one whose audience includes you. */
|
|
4569
|
-
recap:
|
|
4619
|
+
recap: z3.boolean().optional()
|
|
4570
4620
|
}).refine((value) => !value.workId || Boolean(value.target), {
|
|
4571
4621
|
message: "target is required when handing off Work",
|
|
4572
4622
|
path: ["target"]
|
|
4573
4623
|
});
|
|
4574
|
-
var NoteSourceSchema =
|
|
4575
|
-
var NoteStatusSchema =
|
|
4576
|
-
var NoteRepeatSchema =
|
|
4577
|
-
var DecisionSchema =
|
|
4578
|
-
id:
|
|
4624
|
+
var NoteSourceSchema = z3.enum(["app", "call"]);
|
|
4625
|
+
var NoteStatusSchema = z3.enum(["open", "assigned", "in_progress", "done"]);
|
|
4626
|
+
var NoteRepeatSchema = z3.enum(["once", "until_done"]);
|
|
4627
|
+
var DecisionSchema = z3.object({
|
|
4628
|
+
id: z3.string(),
|
|
4579
4629
|
/** The note this decision refines; null = recorded on a bare thread (the
|
|
4580
4630
|
* extensibility seam — any conversation can accrue decisions). */
|
|
4581
|
-
noteId:
|
|
4631
|
+
noteId: z3.string().nullable(),
|
|
4582
4632
|
/** What was ambiguous — the broker's (or the user's own) question. */
|
|
4583
|
-
question:
|
|
4633
|
+
question: z3.string(),
|
|
4584
4634
|
/** The user's ruling; null while the question is open. */
|
|
4585
|
-
answer:
|
|
4586
|
-
decidedAt:
|
|
4587
|
-
createdAt:
|
|
4635
|
+
answer: z3.string().nullable(),
|
|
4636
|
+
decidedAt: z3.string().nullable(),
|
|
4637
|
+
createdAt: z3.string()
|
|
4588
4638
|
});
|
|
4589
|
-
var NoteSchema =
|
|
4590
|
-
id:
|
|
4639
|
+
var NoteSchema = z3.object({
|
|
4640
|
+
id: z3.string(),
|
|
4591
4641
|
/** One-line headline (broker-titled; deterministic floor). */
|
|
4592
|
-
title:
|
|
4642
|
+
title: z3.string(),
|
|
4593
4643
|
/** The original intent, verbatim — assignees always see the user's own words. */
|
|
4594
|
-
intent:
|
|
4644
|
+
intent: z3.string(),
|
|
4595
4645
|
source: NoteSourceSchema,
|
|
4596
4646
|
status: NoteStatusSchema,
|
|
4597
4647
|
/** Who it was assigned to (a participant ref, 'agent:<tokenId>'); null = unassigned. */
|
|
4598
|
-
assignee:
|
|
4648
|
+
assignee: z3.string().nullable(),
|
|
4599
4649
|
/** The request thread minted at assignment; null until assigned. */
|
|
4600
|
-
parentId:
|
|
4650
|
+
parentId: z3.string().nullable(),
|
|
4601
4651
|
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
4602
4652
|
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
4603
4653
|
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
@@ -4606,200 +4656,213 @@ var NoteSchema = z2.object({
|
|
|
4606
4656
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
4607
4657
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
4608
4658
|
// one ride, never ridden. Parsing must not fail across a rolling deploy.
|
|
4609
|
-
dueAt:
|
|
4659
|
+
dueAt: z3.string().nullable().default(null),
|
|
4610
4660
|
repeat: NoteRepeatSchema.default("once"),
|
|
4611
4661
|
/** How many calls have already carried it — the fatigue cap counts rides, not days. */
|
|
4612
|
-
rides:
|
|
4613
|
-
lastRideAt:
|
|
4614
|
-
createdAt:
|
|
4662
|
+
rides: z3.number().int().default(0),
|
|
4663
|
+
lastRideAt: z3.string().nullable().default(null),
|
|
4664
|
+
createdAt: z3.string()
|
|
4615
4665
|
});
|
|
4616
|
-
var CreateNoteSchema =
|
|
4666
|
+
var CreateNoteSchema = z3.object({
|
|
4617
4667
|
/** The intent, in the user's own words. Stored verbatim; the broker only titles it. */
|
|
4618
|
-
text:
|
|
4668
|
+
text: z3.string().min(1).max(4e3),
|
|
4619
4669
|
/** Capture it as a REMINDER — a note assigned to the user themselves, which rides
|
|
4620
4670
|
* their next call instead of being handed to an agent. Everything else about the
|
|
4621
4671
|
* note is identical; this is the one parameter that separates the two. */
|
|
4622
|
-
forMe:
|
|
4672
|
+
forMe: z3.boolean().optional(),
|
|
4623
4673
|
/** The not-before, when the user already said one. Absent = the very next call. */
|
|
4624
|
-
dueAt:
|
|
4674
|
+
dueAt: z3.string().datetime().optional(),
|
|
4625
4675
|
repeat: NoteRepeatSchema.optional()
|
|
4626
4676
|
});
|
|
4627
|
-
var RecordDecisionSchema =
|
|
4677
|
+
var RecordDecisionSchema = z3.object({
|
|
4628
4678
|
/** An open decision (from /clarify) to answer. */
|
|
4629
|
-
decisionId:
|
|
4679
|
+
decisionId: z3.string().optional(),
|
|
4630
4680
|
/** For an ad-hoc decision: what was ambiguous. Required without `decisionId`. */
|
|
4631
|
-
question:
|
|
4681
|
+
question: z3.string().min(1).max(500).optional(),
|
|
4632
4682
|
/** The ruling. */
|
|
4633
|
-
answer:
|
|
4683
|
+
answer: z3.string().min(1).max(2e3)
|
|
4634
4684
|
}).refine((d) => d.decisionId || d.question, { message: "decisionId or question required" });
|
|
4635
|
-
var AssignNoteSchema =
|
|
4685
|
+
var AssignNoteSchema = z3.object({
|
|
4636
4686
|
/** An EXISTING agent: token id or nickname. Omit when spawning fresh. */
|
|
4637
|
-
target:
|
|
4687
|
+
target: z3.string().min(1).optional(),
|
|
4638
4688
|
/** Spawn a NEW session for this note (companion.md §2.2): the assignee doesn't
|
|
4639
4689
|
* exist yet — mint it on a live desktop that advertises the harness+workspace,
|
|
4640
4690
|
* named after the note. The brief arrives as its opening request. */
|
|
4641
|
-
spawn:
|
|
4642
|
-
hostTokenId:
|
|
4643
|
-
harness:
|
|
4644
|
-
workspace:
|
|
4691
|
+
spawn: z3.object({
|
|
4692
|
+
hostTokenId: z3.string().uuid(),
|
|
4693
|
+
harness: z3.string().min(1),
|
|
4694
|
+
workspace: z3.string().min(1)
|
|
4645
4695
|
}).optional()
|
|
4646
4696
|
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
4647
|
-
var TriageVerdictSchema =
|
|
4648
|
-
var TriageItemSchema =
|
|
4649
|
-
noteId:
|
|
4697
|
+
var TriageVerdictSchema = z3.enum(["keep", "stale", "close", "assign"]);
|
|
4698
|
+
var TriageItemSchema = z3.object({
|
|
4699
|
+
noteId: z3.string(),
|
|
4650
4700
|
/** The note's headline at run time. */
|
|
4651
|
-
title:
|
|
4701
|
+
title: z3.string(),
|
|
4652
4702
|
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
4653
4703
|
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
4654
4704
|
* Never a model's reasoning transcript, never an id. */
|
|
4655
|
-
why:
|
|
4705
|
+
why: z3.string()
|
|
4656
4706
|
});
|
|
4657
|
-
var TriageAssignmentSchema =
|
|
4707
|
+
var TriageAssignmentSchema = z3.object({
|
|
4658
4708
|
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
4659
|
-
agent:
|
|
4709
|
+
agent: z3.string(),
|
|
4660
4710
|
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
4661
4711
|
* same reason as `title`: the card must render from the proposal alone. */
|
|
4662
|
-
agentName:
|
|
4663
|
-
notes:
|
|
4712
|
+
agentName: z3.string(),
|
|
4713
|
+
notes: z3.array(TriageItemSchema)
|
|
4664
4714
|
});
|
|
4665
|
-
var TriageStatusSchema =
|
|
4666
|
-
var SubmitTriageSchema =
|
|
4715
|
+
var TriageStatusSchema = z3.enum(["open", "superseded", "dismissed"]);
|
|
4716
|
+
var SubmitTriageSchema = z3.object({
|
|
4667
4717
|
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
4668
4718
|
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
4669
4719
|
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
4670
4720
|
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
4671
|
-
provider:
|
|
4721
|
+
provider: z3.string().min(1).max(60),
|
|
4672
4722
|
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
4673
|
-
model:
|
|
4723
|
+
model: z3.string().max(200).nullable().optional(),
|
|
4674
4724
|
/** How many open notes the run actually looked at — the denominator on the phone
|
|
4675
4725
|
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
4676
|
-
reviewed:
|
|
4677
|
-
close:
|
|
4678
|
-
stale:
|
|
4679
|
-
assign:
|
|
4726
|
+
reviewed: z3.number().int().min(0).max(1e4).default(0),
|
|
4727
|
+
close: z3.array(TriageItemSchema).max(200).default([]),
|
|
4728
|
+
stale: z3.array(TriageItemSchema).max(200).default([]),
|
|
4729
|
+
assign: z3.array(TriageAssignmentSchema).max(50).default([])
|
|
4680
4730
|
});
|
|
4681
4731
|
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
4682
|
-
id:
|
|
4683
|
-
runAt:
|
|
4732
|
+
id: z3.string(),
|
|
4733
|
+
runAt: z3.string(),
|
|
4684
4734
|
status: TriageStatusSchema,
|
|
4685
|
-
model:
|
|
4735
|
+
model: z3.string().nullable().default(null)
|
|
4686
4736
|
});
|
|
4687
|
-
var AcceptTriageSchema =
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
group:
|
|
4692
|
-
agent:
|
|
4693
|
-
noteIds:
|
|
4737
|
+
var AcceptTriageSchema = z3.discriminatedUnion("group", [
|
|
4738
|
+
z3.object({ group: z3.literal("close"), noteIds: z3.array(z3.string()).max(200).optional() }),
|
|
4739
|
+
z3.object({ group: z3.literal("stale"), noteIds: z3.array(z3.string()).max(200).optional() }),
|
|
4740
|
+
z3.object({
|
|
4741
|
+
group: z3.literal("assign"),
|
|
4742
|
+
agent: z3.string().min(1),
|
|
4743
|
+
noteIds: z3.array(z3.string()).max(200).optional()
|
|
4694
4744
|
})
|
|
4695
4745
|
]);
|
|
4696
|
-
var AcceptTriageResultSchema =
|
|
4697
|
-
accepted:
|
|
4698
|
-
failed:
|
|
4746
|
+
var AcceptTriageResultSchema = z3.object({
|
|
4747
|
+
accepted: z3.array(z3.string()),
|
|
4748
|
+
failed: z3.array(z3.object({ noteId: z3.string(), reason: z3.string() }))
|
|
4699
4749
|
});
|
|
4700
|
-
var DeliveryModeSchema =
|
|
4701
|
-
var RegisterDeliverySchema =
|
|
4702
|
-
var OAuthStartSchema =
|
|
4703
|
-
provider:
|
|
4704
|
-
returnTo:
|
|
4750
|
+
var DeliveryModeSchema = z3.enum(["poll", "self_hosted"]);
|
|
4751
|
+
var RegisterDeliverySchema = z3.object({ mode: DeliveryModeSchema });
|
|
4752
|
+
var OAuthStartSchema = z3.object({
|
|
4753
|
+
provider: z3.enum(["cma"]),
|
|
4754
|
+
returnTo: z3.string().min(1)
|
|
4705
4755
|
});
|
|
4706
|
-
var DeliveryConfigSchema =
|
|
4707
|
-
tokenId:
|
|
4756
|
+
var DeliveryConfigSchema = z3.object({
|
|
4757
|
+
tokenId: z3.string(),
|
|
4708
4758
|
mode: DeliveryModeSchema,
|
|
4709
4759
|
/** null when the server has no SUPABASE_ANON_KEY set — the listener then falls
|
|
4710
4760
|
* back to its own PAIGY_SUPABASE_URL / PAIGY_SUPABASE_ANON_KEY env. */
|
|
4711
|
-
realtime:
|
|
4761
|
+
realtime: z3.object({ url: z3.string(), anonKey: z3.string() }).nullable()
|
|
4712
4762
|
});
|
|
4713
|
-
var StatusSchema =
|
|
4714
|
-
name:
|
|
4715
|
-
sessionMode:
|
|
4763
|
+
var StatusSchema = z3.object({
|
|
4764
|
+
name: z3.string(),
|
|
4765
|
+
sessionMode: z3.enum(["default", "all_calls", "silent"]),
|
|
4716
4766
|
/** A phone is registered for push/ring (any push token on the account). */
|
|
4717
|
-
phone:
|
|
4767
|
+
phone: z3.boolean(),
|
|
4768
|
+
/** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
|
|
4769
|
+
* it never picked up. THE SAME NUMBER the harness's wake gate reads
|
|
4770
|
+
* (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
|
|
4771
|
+
* zero while the sweep sees one is two ideas of "waiting".
|
|
4772
|
+
*
|
|
4773
|
+
* Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
|
|
4774
|
+
* that nobody spawned, so a terminal session only learns of work by asking. The harness
|
|
4775
|
+
* used to paper over that by spawning a SECOND process on the identity; now it stands
|
|
4776
|
+
* back, correctly, and the person sitting at the terminal is the one who can act. A
|
|
4777
|
+
* coffee-beans request sat unread for three days.
|
|
4778
|
+
*
|
|
4779
|
+
* Optional: an older API sends no field, and the statusline then renders exactly as before. */
|
|
4780
|
+
waiting: z3.number().int().nonnegative().optional()
|
|
4718
4781
|
});
|
|
4719
|
-
var EnvelopeRecipientSchema =
|
|
4720
|
-
keyId:
|
|
4721
|
-
epk:
|
|
4722
|
-
wnonce:
|
|
4723
|
-
wrap:
|
|
4782
|
+
var EnvelopeRecipientSchema = z3.object({
|
|
4783
|
+
keyId: z3.string(),
|
|
4784
|
+
epk: z3.string(),
|
|
4785
|
+
wnonce: z3.string(),
|
|
4786
|
+
wrap: z3.string()
|
|
4724
4787
|
});
|
|
4725
|
-
var EnvelopeHeaderSchema =
|
|
4726
|
-
field:
|
|
4727
|
-
kind:
|
|
4728
|
-
senderRole:
|
|
4729
|
-
recipientKeyIds:
|
|
4730
|
-
seq:
|
|
4788
|
+
var EnvelopeHeaderSchema = z3.object({
|
|
4789
|
+
field: z3.enum(["context", "options", "visuals", "answer"]),
|
|
4790
|
+
kind: z3.string(),
|
|
4791
|
+
senderRole: z3.enum(["agent", "user"]),
|
|
4792
|
+
recipientKeyIds: z3.array(z3.string()),
|
|
4793
|
+
seq: z3.number().int().nonnegative()
|
|
4731
4794
|
});
|
|
4732
|
-
var EnvelopeSchema =
|
|
4733
|
-
v:
|
|
4734
|
-
alg:
|
|
4735
|
-
msgId:
|
|
4795
|
+
var EnvelopeSchema = z3.object({
|
|
4796
|
+
v: z3.literal(1),
|
|
4797
|
+
alg: z3.literal("x25519-xsalsa20poly1305"),
|
|
4798
|
+
msgId: z3.string(),
|
|
4736
4799
|
hdr: EnvelopeHeaderSchema,
|
|
4737
|
-
recipients:
|
|
4738
|
-
nonce:
|
|
4739
|
-
ct:
|
|
4800
|
+
recipients: z3.array(EnvelopeRecipientSchema).min(1),
|
|
4801
|
+
nonce: z3.string(),
|
|
4802
|
+
ct: z3.string()
|
|
4740
4803
|
});
|
|
4741
|
-
var SealedAnswerSchema =
|
|
4742
|
-
ignored:
|
|
4804
|
+
var SealedAnswerSchema = z3.object({
|
|
4805
|
+
ignored: z3.boolean(),
|
|
4743
4806
|
envelope: EnvelopeSchema
|
|
4744
4807
|
});
|
|
4745
|
-
var DeviceCredentialSchema =
|
|
4746
|
-
deviceId:
|
|
4747
|
-
kind:
|
|
4748
|
-
x25519Pub:
|
|
4749
|
-
ed25519Pub:
|
|
4750
|
-
sig:
|
|
4808
|
+
var DeviceCredentialSchema = z3.object({
|
|
4809
|
+
deviceId: z3.string(),
|
|
4810
|
+
kind: z3.enum(["phone", "web", "agent"]),
|
|
4811
|
+
x25519Pub: z3.string(),
|
|
4812
|
+
ed25519Pub: z3.string(),
|
|
4813
|
+
sig: z3.string()
|
|
4751
4814
|
});
|
|
4752
|
-
var DeviceRosterSchema =
|
|
4753
|
-
uikPub:
|
|
4754
|
-
devices:
|
|
4815
|
+
var DeviceRosterSchema = z3.object({
|
|
4816
|
+
uikPub: z3.string(),
|
|
4817
|
+
devices: z3.array(DeviceCredentialSchema)
|
|
4755
4818
|
});
|
|
4756
|
-
var WakeNudgeSchema =
|
|
4757
|
-
kind:
|
|
4758
|
-
notificationId:
|
|
4759
|
-
parentId:
|
|
4819
|
+
var WakeNudgeSchema = z3.object({
|
|
4820
|
+
kind: z3.enum(["reply", "request", "callback"]),
|
|
4821
|
+
notificationId: z3.string().optional(),
|
|
4822
|
+
parentId: z3.string()
|
|
4760
4823
|
});
|
|
4761
|
-
var PairingStatusSchema =
|
|
4762
|
-
var PairingRevealSchema =
|
|
4763
|
-
x25519:
|
|
4764
|
-
ed25519:
|
|
4765
|
-
nonce:
|
|
4824
|
+
var PairingStatusSchema = z3.enum(["pending", "approved", "denied", "expired"]);
|
|
4825
|
+
var PairingRevealSchema = z3.object({
|
|
4826
|
+
x25519: z3.string(),
|
|
4827
|
+
ed25519: z3.string(),
|
|
4828
|
+
nonce: z3.string()
|
|
4766
4829
|
});
|
|
4767
|
-
var DeviceCodeRequestSchema =
|
|
4830
|
+
var DeviceCodeRequestSchema = z3.object({
|
|
4768
4831
|
/** The agent's suggested name for the pairing — the human sees it pre-filled at
|
|
4769
4832
|
* approval and can override. Optional; blank → a default silly name server-side. */
|
|
4770
|
-
suggestedName:
|
|
4833
|
+
suggestedName: z3.string().optional(),
|
|
4771
4834
|
/** Legacy alias for suggestedName (older MCPs sent `agent`). Accepted for
|
|
4772
4835
|
* back-compat; `suggestedName` wins when both are present. ponytail: drop once no
|
|
4773
4836
|
* pre-`name` MCP is in the wild. */
|
|
4774
|
-
agent:
|
|
4775
|
-
device:
|
|
4776
|
-
proto:
|
|
4837
|
+
agent: z3.union([z3.string(), z3.object({ name: z3.string().optional() })]).optional(),
|
|
4838
|
+
device: z3.string().optional(),
|
|
4839
|
+
proto: z3.string().optional(),
|
|
4777
4840
|
// compiled-in tag, e.g. "paigy-pair-v2|sas=24"
|
|
4778
|
-
commitment:
|
|
4841
|
+
commitment: z3.string().optional()
|
|
4779
4842
|
// agent's Ca
|
|
4780
4843
|
});
|
|
4781
|
-
var DeviceCodeSchema =
|
|
4782
|
-
device_code:
|
|
4783
|
-
user_code:
|
|
4784
|
-
verification_uri:
|
|
4785
|
-
verification_uri_complete:
|
|
4786
|
-
interval:
|
|
4787
|
-
expires_in:
|
|
4844
|
+
var DeviceCodeSchema = z3.object({
|
|
4845
|
+
device_code: z3.string(),
|
|
4846
|
+
user_code: z3.string(),
|
|
4847
|
+
verification_uri: z3.string().url(),
|
|
4848
|
+
verification_uri_complete: z3.string().url(),
|
|
4849
|
+
interval: z3.number(),
|
|
4850
|
+
expires_in: z3.number()
|
|
4788
4851
|
});
|
|
4789
|
-
var DeviceInfoSchema =
|
|
4790
|
-
code:
|
|
4852
|
+
var DeviceInfoSchema = z3.object({
|
|
4853
|
+
code: z3.string(),
|
|
4791
4854
|
/** The agent's suggested name (from /device/code) — shown on the approval screen,
|
|
4792
4855
|
* pre-filling the name field the human can edit. */
|
|
4793
|
-
name:
|
|
4856
|
+
name: z3.string(),
|
|
4794
4857
|
/** @deprecated Legacy alias of `name` for the pre-#531 embedded bundle in App Store
|
|
4795
4858
|
* build 35, whose DeviceFlow renders `info.agent.slice(0, 2)` — without this a FRESH
|
|
4796
4859
|
* install crashes on the pairing screen on first launch, before the OTA lands
|
|
4797
4860
|
* (seen live: PAIGY-5T, 2026-07-21). Remove once a newer binary is the floor. */
|
|
4798
|
-
agent:
|
|
4799
|
-
device:
|
|
4861
|
+
agent: z3.string().optional(),
|
|
4862
|
+
device: z3.string().nullable(),
|
|
4800
4863
|
status: PairingStatusSchema,
|
|
4801
|
-
proto:
|
|
4802
|
-
agent_commitment:
|
|
4864
|
+
proto: z3.string().nullable().optional(),
|
|
4865
|
+
agent_commitment: z3.string().nullable().optional(),
|
|
4803
4866
|
// Ca
|
|
4804
4867
|
agent_reveal: PairingRevealSchema.nullable().optional(),
|
|
4805
4868
|
// present once the agent reveals
|
|
@@ -4809,48 +4872,48 @@ var DeviceInfoSchema = z2.object({
|
|
|
4809
4872
|
// withheld until e2ee=true) can compute the SAS WITHOUT a token. Public keys — same safety
|
|
4810
4873
|
// class as agent_reveal above. Present only once the phone reveals; null otherwise.
|
|
4811
4874
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
4812
|
-
uik_pub:
|
|
4875
|
+
uik_pub: z3.string().nullable().optional()
|
|
4813
4876
|
});
|
|
4814
|
-
var DeviceTokenRequestSchema =
|
|
4815
|
-
device_code:
|
|
4877
|
+
var DeviceTokenRequestSchema = z3.object({
|
|
4878
|
+
device_code: z3.string(),
|
|
4816
4879
|
reveal: PairingRevealSchema.optional()
|
|
4817
4880
|
// agent's reveal {x25519, ed25519, nonce}
|
|
4818
4881
|
});
|
|
4819
|
-
var DeviceTokenSchema =
|
|
4820
|
-
access_token:
|
|
4882
|
+
var DeviceTokenSchema = z3.object({
|
|
4883
|
+
access_token: z3.string(),
|
|
4821
4884
|
/** The pairing's single name (user-typed at approval, the agent's suggestion, or
|
|
4822
4885
|
* a default silly name). */
|
|
4823
|
-
name:
|
|
4824
|
-
device:
|
|
4886
|
+
name: z3.string(),
|
|
4887
|
+
device: z3.string().nullable(),
|
|
4825
4888
|
/** The pairing's assigned voice, cached so the desktop can seed the SAME face the phone
|
|
4826
4889
|
* draws — voice is the third ingredient of a hatchling's build (party/traits.ts). */
|
|
4827
|
-
voice:
|
|
4890
|
+
voice: z3.string().nullable().optional(),
|
|
4828
4891
|
/** The token's server-side id — the face's COLOUR anchor, and the only seed ingredient
|
|
4829
4892
|
* that survives a rename. Cached by the host's identity beat. */
|
|
4830
|
-
token_id:
|
|
4893
|
+
token_id: z3.string().nullable().optional(),
|
|
4831
4894
|
/** WHERE this identity works — the folder a wake should land it in. Written by the host
|
|
4832
4895
|
* at spawn and by `paigy-harness handoff` from a live terminal. Without it every wake
|
|
4833
4896
|
* landed in the FIRST granted workspace and the agent rediscovered its own repo from
|
|
4834
4897
|
* the thread each time (host.ts, live catch 2026-08-06 — prompt-papered until now). */
|
|
4835
|
-
workspace:
|
|
4898
|
+
workspace: z3.string().nullable().optional(),
|
|
4836
4899
|
phone_reveal: PairingRevealSchema.nullable().optional(),
|
|
4837
4900
|
// present once the phone reveals
|
|
4838
|
-
uik_pub:
|
|
4901
|
+
uik_pub: z3.string().nullable().optional()
|
|
4839
4902
|
});
|
|
4840
|
-
var DeviceCommitRequestSchema =
|
|
4841
|
-
code:
|
|
4842
|
-
commitment:
|
|
4903
|
+
var DeviceCommitRequestSchema = z3.object({
|
|
4904
|
+
code: z3.string(),
|
|
4905
|
+
commitment: z3.string()
|
|
4843
4906
|
// Cb
|
|
4844
4907
|
});
|
|
4845
|
-
var DevicePeerCommitSchema =
|
|
4846
|
-
commitment:
|
|
4908
|
+
var DevicePeerCommitSchema = z3.object({
|
|
4909
|
+
commitment: z3.string().nullable()
|
|
4847
4910
|
});
|
|
4848
|
-
var SupportRequestSchema =
|
|
4849
|
-
email:
|
|
4850
|
-
message:
|
|
4851
|
-
name:
|
|
4911
|
+
var SupportRequestSchema = z3.object({
|
|
4912
|
+
email: z3.string().email().max(320),
|
|
4913
|
+
message: z3.string().trim().min(1).max(5e3),
|
|
4914
|
+
name: z3.string().trim().max(120).optional()
|
|
4852
4915
|
});
|
|
4853
|
-
var NotificationFeedbackKindSchema =
|
|
4916
|
+
var NotificationFeedbackKindSchema = z3.enum([
|
|
4854
4917
|
"break_down",
|
|
4855
4918
|
// "This should be more than one ask — break it down."
|
|
4856
4919
|
"regenerate_options",
|
|
@@ -4864,29 +4927,21 @@ var NotificationFeedbackKindSchema = z2.enum([
|
|
|
4864
4927
|
"other"
|
|
4865
4928
|
// anything else — the note carries it.
|
|
4866
4929
|
]);
|
|
4867
|
-
var NotificationFeedbackSchema =
|
|
4868
|
-
notificationId:
|
|
4930
|
+
var NotificationFeedbackSchema = z3.object({
|
|
4931
|
+
notificationId: z3.string(),
|
|
4869
4932
|
kind: NotificationFeedbackKindSchema,
|
|
4870
4933
|
/** Optional free-text elaboration for a preset; required (non-empty) for 'other'. */
|
|
4871
|
-
note:
|
|
4934
|
+
note: z3.string().trim().max(2e3).optional()
|
|
4872
4935
|
}).superRefine((r, ctx) => {
|
|
4873
4936
|
if (r.kind === "other" && !r.note)
|
|
4874
|
-
ctx.addIssue({ code:
|
|
4937
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["note"], message: "note is required for 'other' feedback" });
|
|
4875
4938
|
});
|
|
4876
|
-
var FeedbackResolutionSchema =
|
|
4877
|
-
var FeedbackOutcomeSchema =
|
|
4939
|
+
var FeedbackResolutionSchema = z3.enum(["broker_fixed", "sent_to_agent"]);
|
|
4940
|
+
var FeedbackOutcomeSchema = z3.object({
|
|
4878
4941
|
resolution: FeedbackResolutionSchema,
|
|
4879
4942
|
transform: TransformSchema,
|
|
4880
|
-
message:
|
|
4881
|
-
childIds:
|
|
4882
|
-
});
|
|
4883
|
-
var CONTACT_SCHEMA = contactSchemaFrom({
|
|
4884
|
-
ask: NotifyRequestFields.shape.ask,
|
|
4885
|
-
waiting: NotifyRequestFields.shape.waiting,
|
|
4886
|
-
options: NotifyRequestFields.shape.options,
|
|
4887
|
-
channel: NotifyRequestFields.shape.channel,
|
|
4888
|
-
parentId: NotifyRequestFields.shape.parentId,
|
|
4889
|
-
workId: NotifyRequestFields.shape.workId
|
|
4943
|
+
message: z3.string(),
|
|
4944
|
+
childIds: z3.array(z3.string()).optional()
|
|
4890
4945
|
});
|
|
4891
4946
|
var import_tweetnacl = __toESM(require_nacl_fast(), 1);
|
|
4892
4947
|
var import_tweetnacl2 = __toESM(require_nacl_fast(), 1);
|
|
@@ -5043,15 +5098,6 @@ function verifyDeviceCredential(cred, uikPublicB64) {
|
|
|
5043
5098
|
}
|
|
5044
5099
|
var ENVELOPE_VERSION = 1;
|
|
5045
5100
|
var ENVELOPE_ALG = "x25519-xsalsa20poly1305";
|
|
5046
|
-
function padme(L) {
|
|
5047
|
-
if (L < 2) return L;
|
|
5048
|
-
const E = Math.floor(Math.log2(L));
|
|
5049
|
-
const S = Math.floor(Math.log2(E)) + 1;
|
|
5050
|
-
const lastBits = E - S;
|
|
5051
|
-
if (lastBits <= 0) return L;
|
|
5052
|
-
const mask = (1 << lastBits) - 1;
|
|
5053
|
-
return L + mask & ~mask;
|
|
5054
|
-
}
|
|
5055
5101
|
function canonicalHeader(msgId, hdr) {
|
|
5056
5102
|
const parts = [
|
|
5057
5103
|
u16be(ENVELOPE_VERSION),
|
|
@@ -5066,42 +5112,6 @@ function canonicalHeader(msgId, hdr) {
|
|
|
5066
5112
|
];
|
|
5067
5113
|
return concat(parts);
|
|
5068
5114
|
}
|
|
5069
|
-
function seal(input) {
|
|
5070
|
-
ensureCsprng();
|
|
5071
|
-
if (input.recipients.length === 0) throw new Error("seal: no recipients");
|
|
5072
|
-
const kind = input.kind ?? "";
|
|
5073
|
-
const recs = input.recipients.map((pub) => ({ pub, id: keyId(pub) })).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
5074
|
-
const hdr = {
|
|
5075
|
-
field: input.field,
|
|
5076
|
-
kind,
|
|
5077
|
-
senderRole: input.senderRole,
|
|
5078
|
-
recipientKeyIds: recs.map((r) => r.id),
|
|
5079
|
-
seq: input.seq
|
|
5080
|
-
};
|
|
5081
|
-
const msgIdBytes = input.msgId ? fromB64(input.msgId) : randomBytes(16);
|
|
5082
|
-
const cek = randomBytes(32);
|
|
5083
|
-
const canon = canonicalHeader(msgIdBytes, hdr);
|
|
5084
|
-
const plaintext = concat([u32be(canon.length), canon, u32be(input.body.length), input.body]);
|
|
5085
|
-
const padded = new Uint8Array(padme(plaintext.length));
|
|
5086
|
-
padded.set(plaintext);
|
|
5087
|
-
const nonce = randomBytes(24);
|
|
5088
|
-
const ct = import_tweetnacl3.default.secretbox(padded, nonce, cek);
|
|
5089
|
-
const recipients = recs.map((r) => {
|
|
5090
|
-
const eph = import_tweetnacl3.default.box.keyPair();
|
|
5091
|
-
const wnonce = randomBytes(24);
|
|
5092
|
-
const wrap = import_tweetnacl3.default.box(cek, wnonce, fromB64(r.pub), eph.secretKey);
|
|
5093
|
-
return { keyId: r.id, epk: toB64(eph.publicKey), wnonce: toB64(wnonce), wrap: toB64(wrap) };
|
|
5094
|
-
});
|
|
5095
|
-
return {
|
|
5096
|
-
v: ENVELOPE_VERSION,
|
|
5097
|
-
alg: ENVELOPE_ALG,
|
|
5098
|
-
msgId: toB64(msgIdBytes),
|
|
5099
|
-
hdr,
|
|
5100
|
-
recipients,
|
|
5101
|
-
nonce: toB64(nonce),
|
|
5102
|
-
ct: toB64(ct)
|
|
5103
|
-
};
|
|
5104
|
-
}
|
|
5105
5115
|
function open(envelope, myKeyId, mySecretKeyB64) {
|
|
5106
5116
|
if (envelope.v !== ENVELOPE_VERSION) throw new Error(`unsupported envelope version ${String(envelope.v)}`);
|
|
5107
5117
|
if (envelope.alg !== ENVELOPE_ALG) throw new Error(`unsupported alg ${String(envelope.alg)}`);
|
|
@@ -5160,28 +5170,6 @@ function sessionSlot(sessionId2) {
|
|
|
5160
5170
|
const id = sessionId2 ?? sessionId();
|
|
5161
5171
|
return `session:${id.slice(0, 8)}`;
|
|
5162
5172
|
}
|
|
5163
|
-
function subagentId(env = process.env) {
|
|
5164
|
-
if (!env.CLAUDE_CODE_CHILD_SESSION) return null;
|
|
5165
|
-
const pid = env.CLAUDE_PID ?? env.CLAUDE_CODE_MESSAGING_SOCKET?.match(/(\d+)\.sock$/)?.[1];
|
|
5166
|
-
return pid ?? null;
|
|
5167
|
-
}
|
|
5168
|
-
function subagentThread(env = process.env, sessionId2) {
|
|
5169
|
-
const base = sessionId2 ?? sessionId();
|
|
5170
|
-
const sub = subagentId(env);
|
|
5171
|
-
return sub ? `${base}:sub:${sub}` : base;
|
|
5172
|
-
}
|
|
5173
|
-
function uuidFromKey(key) {
|
|
5174
|
-
const h = createHash2("sha1").update(`paigy-thread:${key}`).digest();
|
|
5175
|
-
h[6] = h[6] & 15 | 80;
|
|
5176
|
-
h[8] = h[8] & 63 | 128;
|
|
5177
|
-
const hex = h.subarray(0, 16).toString("hex");
|
|
5178
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
5179
|
-
}
|
|
5180
|
-
function contactParentId(explicit, env = process.env) {
|
|
5181
|
-
if (explicit) return explicit;
|
|
5182
|
-
if (!subagentId(env)) return void 0;
|
|
5183
|
-
return uuidFromKey(subagentThread(env));
|
|
5184
|
-
}
|
|
5185
5173
|
var AGENT_NAME = process.env.PAIGY_AGENT || sessionSlot();
|
|
5186
5174
|
var PROTO = "paigy-pair-v2|sas=24";
|
|
5187
5175
|
var SAS_BITS = 24;
|
|
@@ -5372,30 +5360,6 @@ async function pairStep(deviceCode, keyFile) {
|
|
|
5372
5360
|
return { kind: "e2ee_aborted", warning: e.message };
|
|
5373
5361
|
}
|
|
5374
5362
|
}
|
|
5375
|
-
async function fetchRoster(token) {
|
|
5376
|
-
const res = await reach(`${BACKEND_URL}/api/roster`, { headers: { authorization: `Bearer ${token}` } });
|
|
5377
|
-
if (!res.ok) return null;
|
|
5378
|
-
return await res.json().catch(() => null);
|
|
5379
|
-
}
|
|
5380
|
-
function verifiedRecipients(roster, pinnedUikPub) {
|
|
5381
|
-
return roster.devices.filter((cred) => verifyDeviceCredential(cred, pinnedUikPub)).map((cred) => cred.x25519Pub);
|
|
5382
|
-
}
|
|
5383
|
-
function sealFields(content, recipients) {
|
|
5384
|
-
if (recipients.length === 0) throw new Error("sealFields: no verified recipients");
|
|
5385
|
-
const seq = Math.floor(Date.now() / 1e3);
|
|
5386
|
-
const out = {};
|
|
5387
|
-
for (const field of ["context", "options", "visuals"]) {
|
|
5388
|
-
if (content[field] === void 0) continue;
|
|
5389
|
-
out[field] = seal({
|
|
5390
|
-
body: utf8ToBytes(JSON.stringify(content[field])),
|
|
5391
|
-
field,
|
|
5392
|
-
senderRole: "agent",
|
|
5393
|
-
seq,
|
|
5394
|
-
recipients
|
|
5395
|
-
});
|
|
5396
|
-
}
|
|
5397
|
-
return out;
|
|
5398
|
-
}
|
|
5399
5363
|
function openSealedAnswer(sealed, keypair) {
|
|
5400
5364
|
const myKeyId = keyId(keypair.x25519.publicKey);
|
|
5401
5365
|
const { header, body } = open(sealed.envelope, myKeyId, keypair.x25519.secretKey);
|
|
@@ -5421,117 +5385,68 @@ function ensureAuthed(res) {
|
|
|
5421
5385
|
if (res.status === 401) throw new UnpairedError();
|
|
5422
5386
|
return res;
|
|
5423
5387
|
}
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
throw new Error("No roster device verified against the pinned identity key \u2014 refusing to send plaintext (a server-injected device is dropped).");
|
|
5388
|
+
var ApiError = class extends Error {
|
|
5389
|
+
status;
|
|
5390
|
+
body;
|
|
5391
|
+
constructor(what, status, text) {
|
|
5392
|
+
super(`${what} failed: ${status} ${text}`);
|
|
5393
|
+
this.name = "ApiError";
|
|
5394
|
+
this.status = status;
|
|
5395
|
+
let body = void 0;
|
|
5396
|
+
try {
|
|
5397
|
+
body = JSON.parse(text);
|
|
5398
|
+
} catch {
|
|
5399
|
+
}
|
|
5400
|
+
this.body = body;
|
|
5438
5401
|
}
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
...req.branch !== void 0 ? { branch: req.branch } : {}
|
|
5443
|
-
};
|
|
5444
|
-
const envelope = sealFields({ context: sealedContext, options: req.options, visuals: req.visuals }, recipients);
|
|
5445
|
-
const { context: _c, options: _o, visuals: _v, repo: _r, branch: _b, ...meta } = req;
|
|
5446
|
-
return { ...meta, envelope };
|
|
5402
|
+
};
|
|
5403
|
+
async function fail(what, res) {
|
|
5404
|
+
throw new ApiError(what, res.status, await res.text());
|
|
5447
5405
|
}
|
|
5448
5406
|
async function createGoal(input, opts = {}) {
|
|
5449
5407
|
const token = authToken(opts.token) ?? "";
|
|
5450
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/goals`, {
|
|
5408
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals`, {
|
|
5451
5409
|
method: "POST",
|
|
5452
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}
|
|
5410
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" },
|
|
5453
5411
|
body: JSON.stringify(input)
|
|
5454
5412
|
}));
|
|
5455
|
-
if (!res.ok)
|
|
5413
|
+
if (!res.ok) await fail("create_goal", res);
|
|
5456
5414
|
return await res.json();
|
|
5457
5415
|
}
|
|
5458
5416
|
async function claimGoal(goalId, opts = {}) {
|
|
5459
5417
|
const token = authToken(opts.token) ?? "";
|
|
5460
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/goals
|
|
5461
|
-
if (!res.ok)
|
|
5418
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/claim`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify(goalId ? { goalId } : {}) }));
|
|
5419
|
+
if (!res.ok) await fail("claim_goal", res);
|
|
5462
5420
|
return await res.json();
|
|
5463
5421
|
}
|
|
5464
|
-
async function
|
|
5422
|
+
async function getGoal(goalId, opts = {}) {
|
|
5465
5423
|
const token = authToken(opts.token) ?? "";
|
|
5466
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, {
|
|
5467
|
-
|
|
5424
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, {
|
|
5425
|
+
method: "GET",
|
|
5426
|
+
headers: { authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }
|
|
5427
|
+
}));
|
|
5428
|
+
if (!res.ok) await fail("get_goal", res);
|
|
5468
5429
|
return await res.json();
|
|
5469
5430
|
}
|
|
5470
|
-
async function
|
|
5431
|
+
async function updateGoal(goalId, input, opts = {}) {
|
|
5471
5432
|
const token = authToken(opts.token) ?? "";
|
|
5472
|
-
const
|
|
5473
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/
|
|
5474
|
-
|
|
5475
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
5476
|
-
body: JSON.stringify(body)
|
|
5477
|
-
}));
|
|
5478
|
-
if (!res.ok) throw new Error(`notify failed: ${res.status} ${await res.text()}`);
|
|
5433
|
+
const operationId = input.operationId ?? randomUUID2();
|
|
5434
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", headers: { "content-type": "application/json", authorization: `Bearer ${token}`, "x-paigy-model": "goal-entry-v1" }, body: JSON.stringify({ ...input, operationId }) }));
|
|
5435
|
+
if (!res.ok) await fail("update_goal", res);
|
|
5479
5436
|
return await res.json();
|
|
5480
5437
|
}
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
async function pollPartials(notificationId) {
|
|
5485
|
-
const token = authToken();
|
|
5486
|
-
const after = _partialSeen.get(notificationId) ?? 0;
|
|
5487
|
-
try {
|
|
5488
|
-
const res = await reach(
|
|
5489
|
-
`${BACKEND_URL}/api/await/partials?notificationId=${encodeURIComponent(notificationId)}&afterIdx=${after}`,
|
|
5490
|
-
{ headers: { authorization: `Bearer ${token}` } }
|
|
5491
|
-
);
|
|
5492
|
-
if (!res.ok) return null;
|
|
5493
|
-
const body = await res.json();
|
|
5494
|
-
const next = body.turns?.[0];
|
|
5495
|
-
if (!next) return null;
|
|
5496
|
-
_partialSeen.set(notificationId, next.idx);
|
|
5497
|
-
return { type: "partial", notificationId, inFlight: true, turn: next };
|
|
5498
|
-
} catch {
|
|
5499
|
-
return null;
|
|
5500
|
-
}
|
|
5501
|
-
}
|
|
5502
|
-
async function awaitReply(notificationId, opts) {
|
|
5503
|
-
const intervalMs = opts.intervalMs ?? 5e3;
|
|
5504
|
-
const windowMs = opts.windowMs ?? AWAIT_WINDOW_MS;
|
|
5505
|
-
const doSleep = opts.sleep ?? sleep2;
|
|
5506
|
-
const now = opts.now ?? Date.now;
|
|
5507
|
-
const read = opts.read;
|
|
5508
|
-
const start = now();
|
|
5509
|
-
while (true) {
|
|
5510
|
-
if (opts.signal?.aborted) return { type: "idle" };
|
|
5511
|
-
try {
|
|
5512
|
-
const item = await read(notificationId);
|
|
5513
|
-
if (item.type !== "idle") return item;
|
|
5514
|
-
const partial = await pollPartials(notificationId);
|
|
5515
|
-
if (partial) return partial;
|
|
5516
|
-
} catch (e) {
|
|
5517
|
-
if (!isNetworkError(e)) throw e;
|
|
5518
|
-
}
|
|
5519
|
-
if (now() - start >= windowMs) return { type: "idle" };
|
|
5520
|
-
await doSleep(intervalMs);
|
|
5521
|
-
}
|
|
5522
|
-
}
|
|
5523
|
-
async function getThread(parentId) {
|
|
5524
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
5525
|
-
headers: { authorization: `Bearer ${authToken()}` }
|
|
5438
|
+
async function getThread(parentId, opts = {}) {
|
|
5439
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
5440
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
5526
5441
|
}));
|
|
5527
|
-
if (!res.ok)
|
|
5442
|
+
if (!res.ok) await fail("get_thread", res);
|
|
5528
5443
|
return await res.json();
|
|
5529
5444
|
}
|
|
5530
|
-
async function searchThreads(q) {
|
|
5531
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/search?q=${encodeURIComponent(q)}`, {
|
|
5532
|
-
headers: { authorization: `Bearer ${authToken()}
|
|
5445
|
+
async function searchThreads(q, opts = {}) {
|
|
5446
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/search?q=${encodeURIComponent(q)}`, {
|
|
5447
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
5533
5448
|
}));
|
|
5534
|
-
if (!res.ok)
|
|
5449
|
+
if (!res.ok) await fail("search_threads", res);
|
|
5535
5450
|
return await res.json();
|
|
5536
5451
|
}
|
|
5537
5452
|
async function sweepLeased(opts = {}) {
|
|
@@ -5545,15 +5460,6 @@ async function sweepLeased(opts = {}) {
|
|
|
5545
5460
|
if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
|
|
5546
5461
|
return await res.json();
|
|
5547
5462
|
}
|
|
5548
|
-
async function answerCallerQuestion(notificationId, answer) {
|
|
5549
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/voice/session-answer`, {
|
|
5550
|
-
method: "POST",
|
|
5551
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${authToken()}` },
|
|
5552
|
-
body: JSON.stringify({ notificationId, answer })
|
|
5553
|
-
}));
|
|
5554
|
-
if (!res.ok) throw new Error(`answer_caller_question failed: ${res.status} ${await res.text()}`);
|
|
5555
|
-
return await res.json();
|
|
5556
|
-
}
|
|
5557
5463
|
var tokenOverride = null;
|
|
5558
5464
|
function overrideToken(secret) {
|
|
5559
5465
|
tokenOverride = secret;
|
|
@@ -5608,31 +5514,101 @@ async function registerDelivery(mode) {
|
|
|
5608
5514
|
if (!res.ok) throw new Error(`register_delivery failed: ${res.status} ${await res.text()}`);
|
|
5609
5515
|
return await res.json();
|
|
5610
5516
|
}
|
|
5611
|
-
async function
|
|
5612
|
-
const
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5517
|
+
async function contact(input, opts = {}) {
|
|
5518
|
+
const parsed = ContactSchema.parse(input);
|
|
5519
|
+
opts.signal?.throwIfAborted();
|
|
5520
|
+
const send = opts.reach ?? reach;
|
|
5521
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
5522
|
+
let deliveryId;
|
|
5523
|
+
if ("deliveryId" in parsed) deliveryId = parsed.deliveryId;
|
|
5524
|
+
else {
|
|
5525
|
+
if (!opts.reach && readKeyFile()?.e2ee) throw new Error("Goal contact does not support E2EE yet; refusing to send plaintext.");
|
|
5526
|
+
const shaped = deriveAsk(NotifyRequestSchema.parse({ ask: parsed.ask, waiting: parsed.waiting, options: parsed.options }));
|
|
5527
|
+
const res = ensureAuthed(await send(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
|
|
5528
|
+
method: "POST",
|
|
5529
|
+
headers,
|
|
5530
|
+
signal: opts.signal,
|
|
5531
|
+
body: JSON.stringify({ operationId: opts.operationId ?? randomUUID3(), threadId: parsed.threadId ?? null, content: parsed.ask, select: shaped.select, options: shaped.options ?? [], blocking: parsed.waiting === "hard", channel: parsed.channel === "call" ? "call" : "message" })
|
|
5532
|
+
}));
|
|
5533
|
+
if (!res.ok) await fail("goal contact", res);
|
|
5534
|
+
const receipt = await res.json();
|
|
5535
|
+
deliveryId = receipt.deliveryId ?? receipt.callId ?? receipt.notificationId ?? "";
|
|
5536
|
+
if (!deliveryId) throw new Error("Goal contact returned no Delivery identity");
|
|
5537
|
+
}
|
|
5538
|
+
const read = async (signal2) => {
|
|
5539
|
+
const res = ensureAuthed(await send(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
5540
|
+
if (!res.ok) await fail("read_delivery", res);
|
|
5541
|
+
const delivery = await res.json();
|
|
5542
|
+
return delivery.kind === "notification" ? { ...delivery, message: `${delivery.message}
|
|
5543
|
+
Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answers. Do not poll this Notification.` } : delivery;
|
|
5544
|
+
};
|
|
5545
|
+
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
5546
|
+
if (opts.waits === false) return read(opts.signal);
|
|
5547
|
+
const window = AbortSignal.timeout(45e3);
|
|
5548
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
5549
|
+
let latest;
|
|
5550
|
+
try {
|
|
5551
|
+
while (true) {
|
|
5552
|
+
signal.throwIfAborted();
|
|
5553
|
+
latest = await read(signal);
|
|
5554
|
+
if (settled(latest)) return latest;
|
|
5555
|
+
await sleep3(5e3, void 0, { signal });
|
|
5556
|
+
}
|
|
5557
|
+
} catch (error) {
|
|
5558
|
+
opts.signal?.throwIfAborted();
|
|
5559
|
+
if (window.aborted && latest) return latest;
|
|
5560
|
+
throw error;
|
|
5561
|
+
}
|
|
5620
5562
|
}
|
|
5621
|
-
async function
|
|
5622
|
-
const
|
|
5623
|
-
|
|
5624
|
-
method: "POST",
|
|
5625
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
5626
|
-
body: JSON.stringify(req)
|
|
5563
|
+
async function checkReplies(opts = {}) {
|
|
5564
|
+
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/deliveries`, {
|
|
5565
|
+
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
5627
5566
|
}));
|
|
5628
|
-
if (!res.ok)
|
|
5567
|
+
if (!res.ok) await fail("check_replies", res);
|
|
5629
5568
|
return await res.json();
|
|
5630
5569
|
}
|
|
5631
|
-
|
|
5570
|
+
async function runTool(name, args, opts) {
|
|
5571
|
+
const { waits, signal, ...client } = opts;
|
|
5572
|
+
const input = args ?? {};
|
|
5573
|
+
switch (name) {
|
|
5574
|
+
case "contact":
|
|
5575
|
+
return contact(input, { ...client, signal, waits });
|
|
5576
|
+
case "check_replies":
|
|
5577
|
+
CheckRepliesSchema.parse(input);
|
|
5578
|
+
return checkReplies(client);
|
|
5579
|
+
case "get_thread": {
|
|
5580
|
+
const { parentId } = GetThreadSchema.parse(input);
|
|
5581
|
+
return getThread(parentId, client);
|
|
5582
|
+
}
|
|
5583
|
+
case "search_threads": {
|
|
5584
|
+
const { q } = SearchThreadsSchema.parse(input);
|
|
5585
|
+
return searchThreads(q, client);
|
|
5586
|
+
}
|
|
5587
|
+
case "create_goal": {
|
|
5588
|
+
const goal = CreateGoalToolSchema.parse(input);
|
|
5589
|
+
return createGoal({ ...goal, idempotencyKey: goal.idempotencyKey ?? randomUUID4() }, client);
|
|
5590
|
+
}
|
|
5591
|
+
case "claim_goal": {
|
|
5592
|
+
const { goalId } = ClaimGoalSchema.parse(input);
|
|
5593
|
+
return claimGoal(goalId, client);
|
|
5594
|
+
}
|
|
5595
|
+
case "get_goal": {
|
|
5596
|
+
const { goalId } = GetGoalSchema.parse(input);
|
|
5597
|
+
return getGoal(goalId, client);
|
|
5598
|
+
}
|
|
5599
|
+
case "update_goal": {
|
|
5600
|
+
const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
|
|
5601
|
+
return updateGoal(goalId, body, client);
|
|
5602
|
+
}
|
|
5603
|
+
default:
|
|
5604
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
var sleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5632
5608
|
var ReplyLeaseExpiredError = class extends Error {
|
|
5633
5609
|
constructor() {
|
|
5634
5610
|
super(
|
|
5635
|
-
"The Paigy reply lease expired before acknowledgement. The answer remains durable;
|
|
5611
|
+
"The Paigy reply lease expired before acknowledgement. The answer remains durable; wait on it again (contact with only { wait: notificationId }) to reclaim it before acting."
|
|
5636
5612
|
);
|
|
5637
5613
|
this.name = "ReplyLeaseExpiredError";
|
|
5638
5614
|
}
|
|
@@ -5685,7 +5661,7 @@ async function responseDetail(res) {
|
|
|
5685
5661
|
async function acknowledge(token, leaseId, opts = {}) {
|
|
5686
5662
|
const attempts = Math.max(1, Math.min(opts.attempts ?? 3, 5));
|
|
5687
5663
|
const backoffMs = Math.max(0, opts.backoffMs ?? 100);
|
|
5688
|
-
const doSleep = opts.doSleep ??
|
|
5664
|
+
const doSleep = opts.doSleep ?? sleep4;
|
|
5689
5665
|
let lastError;
|
|
5690
5666
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
5691
5667
|
try {
|
|
@@ -5729,96 +5705,12 @@ async function ackReply(item, token, opts = {}) {
|
|
|
5729
5705
|
doSleep: opts.ackSleep
|
|
5730
5706
|
});
|
|
5731
5707
|
}
|
|
5732
|
-
async function receiveReply(item, token, opts = {}) {
|
|
5733
|
-
const decoded = decodeReply(item, opts);
|
|
5734
|
-
const delivery = deliveryOf(item);
|
|
5735
|
-
if (!delivery) {
|
|
5736
|
-
if (opts.requireDelivery) {
|
|
5737
|
-
throw new Error("Paigy returned a catch-up reply without a delivery lease; the answer was not exposed.");
|
|
5738
|
-
}
|
|
5739
|
-
return decoded;
|
|
5740
|
-
}
|
|
5741
|
-
await acknowledge(token, delivery.leaseId, {
|
|
5742
|
-
attempts: opts.ackAttempts,
|
|
5743
|
-
backoffMs: opts.ackBackoffMs,
|
|
5744
|
-
doSleep: opts.ackSleep
|
|
5745
|
-
});
|
|
5746
|
-
return decoded;
|
|
5747
|
-
}
|
|
5748
|
-
async function pollAnswer(notificationId, opts = {}) {
|
|
5749
|
-
const token = authToken() ?? "";
|
|
5750
|
-
const res = ensureAuthed(await reach(
|
|
5751
|
-
`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`,
|
|
5752
|
-
{
|
|
5753
|
-
headers: {
|
|
5754
|
-
authorization: `Bearer ${token}`,
|
|
5755
|
-
"x-paigy-await-ack": "1"
|
|
5756
|
-
}
|
|
5757
|
-
}
|
|
5758
|
-
));
|
|
5759
|
-
if (!res.ok) throw new Error(`await failed: ${res.status} ${await res.text()}`);
|
|
5760
|
-
const wire = await res.json();
|
|
5761
|
-
if (wire.type !== "reply") return wire;
|
|
5762
|
-
return receiveReply(wire, token, opts);
|
|
5763
|
-
}
|
|
5764
|
-
async function awaitReply2(notificationId, opts = {}) {
|
|
5765
|
-
return awaitReply(notificationId, { ...opts, read: pollAnswer });
|
|
5766
|
-
}
|
|
5767
|
-
async function checkReplies(opts = {}) {
|
|
5768
|
-
const pending = await sweepLeased(opts);
|
|
5769
|
-
const token = authToken(opts.token) ?? "";
|
|
5770
|
-
const replies = await Promise.all(pending.replies.map(async (reply) => {
|
|
5771
|
-
const received = await receiveReply(
|
|
5772
|
-
{ type: "reply", ...reply },
|
|
5773
|
-
token,
|
|
5774
|
-
{ requireDelivery: true }
|
|
5775
|
-
);
|
|
5776
|
-
const { type: _type, ...withoutType } = received;
|
|
5777
|
-
return withoutType;
|
|
5778
|
-
}));
|
|
5779
|
-
return {
|
|
5780
|
-
...pending,
|
|
5781
|
-
replies,
|
|
5782
|
-
work: pending.work ?? []
|
|
5783
|
-
};
|
|
5784
|
-
}
|
|
5785
|
-
async function patchState(path, state, opts, operation) {
|
|
5786
|
-
const token = authToken(opts.token) ?? "";
|
|
5787
|
-
const res = ensureAuthed(await reach(
|
|
5788
|
-
`${BACKEND_URL}${path}`,
|
|
5789
|
-
{
|
|
5790
|
-
method: "PATCH",
|
|
5791
|
-
headers: {
|
|
5792
|
-
"content-type": "application/json",
|
|
5793
|
-
authorization: `Bearer ${token}`,
|
|
5794
|
-
"x-paigy-await-ack": "1"
|
|
5795
|
-
},
|
|
5796
|
-
body: JSON.stringify({ state })
|
|
5797
|
-
}
|
|
5798
|
-
));
|
|
5799
|
-
if (!res.ok) {
|
|
5800
|
-
throw new Error(`${operation} failed: ${res.status} ${await res.text()}`);
|
|
5801
|
-
}
|
|
5802
|
-
return await res.json();
|
|
5803
|
-
}
|
|
5804
|
-
function setTaskState(notificationId, state, opts = {}) {
|
|
5805
|
-
return patchState(`/api/notify/${encodeURIComponent(notificationId)}/state`, state, opts, "set_task_state");
|
|
5806
|
-
}
|
|
5807
|
-
function setWorkState(input, opts = {}) {
|
|
5808
|
-
const parsed = SetWorkStateSchema.parse(input);
|
|
5809
|
-
const path = parsed.workId ? `/api/work/${encodeURIComponent(parsed.workId)}/state` : `/api/notify/${encodeURIComponent(parsed.notificationId)}/state`;
|
|
5810
|
-
return patchState(path, parsed.state, opts, "set_work_state");
|
|
5811
|
-
}
|
|
5812
5708
|
|
|
5813
5709
|
export {
|
|
5814
5710
|
BACKEND_URL,
|
|
5815
5711
|
isNetworkError,
|
|
5816
5712
|
reach,
|
|
5817
|
-
NotifyRequestSchema,
|
|
5818
|
-
SetWorkStateSchema,
|
|
5819
|
-
ScheduleCallbackSchema,
|
|
5820
5713
|
sessionSlot,
|
|
5821
|
-
contactParentId,
|
|
5822
5714
|
AGENT_NAME,
|
|
5823
5715
|
TOKEN_PATH,
|
|
5824
5716
|
saveToken,
|
|
@@ -5837,14 +5729,7 @@ export {
|
|
|
5837
5729
|
fetchCredential,
|
|
5838
5730
|
pairStep,
|
|
5839
5731
|
UnpairedError,
|
|
5840
|
-
createGoal,
|
|
5841
|
-
claimGoal,
|
|
5842
|
-
updateGoal,
|
|
5843
|
-
submitNotification,
|
|
5844
|
-
getThread,
|
|
5845
|
-
searchThreads,
|
|
5846
5732
|
sweepLeased,
|
|
5847
|
-
answerCallerQuestion,
|
|
5848
5733
|
overrideToken,
|
|
5849
5734
|
authToken,
|
|
5850
5735
|
hatch,
|
|
@@ -5852,12 +5737,7 @@ export {
|
|
|
5852
5737
|
setIdentity,
|
|
5853
5738
|
heartbeat,
|
|
5854
5739
|
registerDelivery,
|
|
5855
|
-
|
|
5856
|
-
handoff,
|
|
5740
|
+
runTool,
|
|
5857
5741
|
decodeReply,
|
|
5858
|
-
ackReply
|
|
5859
|
-
awaitReply2,
|
|
5860
|
-
checkReplies,
|
|
5861
|
-
setTaskState,
|
|
5862
|
-
setWorkState
|
|
5742
|
+
ackReply
|
|
5863
5743
|
};
|