@paigy/mcp 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-G2WR7FCJ.js → chunk-KNMI3CJ3.js} +1 -1
- package/dist/{chunk-X42EUXML.js → chunk-U6SLIORK.js} +275 -55
- package/dist/{chunk-45CIZLCJ.js → chunk-YOGAG4YY.js} +67 -6
- package/dist/enable.js +2 -2
- package/dist/index.js +8 -8
- package/dist/listen.js +4 -4
- package/dist/onboard.js +5 -5
- package/dist/statusline.js +1 -1
- package/package.json +1 -1
|
@@ -2645,10 +2645,10 @@ var IntentSchema = z.object({
|
|
|
2645
2645
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
2646
2646
|
kind: z.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
2647
2647
|
detail: z.string(),
|
|
2648
|
-
/**
|
|
2649
|
-
*
|
|
2650
|
-
* the
|
|
2651
|
-
*
|
|
2648
|
+
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
2649
|
+
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
2650
|
+
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
2651
|
+
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
2652
2652
|
dueInSeconds: z.number().int().positive().optional(),
|
|
2653
2653
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
2654
2654
|
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
@@ -3080,7 +3080,13 @@ var BrokerTuningSchema = z.object({
|
|
|
3080
3080
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
3081
3081
|
clarifyEagerness: z.enum(["low", "normal", "high"]).optional(),
|
|
3082
3082
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
3083
|
-
phrasebook: z.array(z.object({ say: z.string().min(1).max(60), mean: z.string().min(1).max(120) })).max(24).optional()
|
|
3083
|
+
phrasebook: z.array(z.object({ say: z.string().min(1).max(60), mean: z.string().min(1).max(120) })).max(24).optional(),
|
|
3084
|
+
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
3085
|
+
* which is every account today — means the agent's own words decide, per ask: a call
|
|
3086
|
+
* about an English ask opens in English. This is the only thing that overrides that,
|
|
3087
|
+
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
3088
|
+
* Set per user (no UI yet), like `voiceTuning`. */
|
|
3089
|
+
language: z.enum(["en", "es"]).optional()
|
|
3084
3090
|
});
|
|
3085
3091
|
var UserSettingsSchema = z.object({
|
|
3086
3092
|
permissions: z.object({
|
|
@@ -3288,7 +3294,9 @@ var NoteSchema = z.object({
|
|
|
3288
3294
|
/** The request thread minted at assignment; null until assigned. */
|
|
3289
3295
|
parentId: z.string().nullable(),
|
|
3290
3296
|
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
3291
|
-
* call — never a deadline
|
|
3297
|
+
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
3298
|
+
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
3299
|
+
* a ring at that time); after that it rides like any other. Null = "the very next
|
|
3292
3300
|
* call", the right reading of "remind me to…" with no time attached. */
|
|
3293
3301
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
3294
3302
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
@@ -3331,6 +3339,59 @@ var AssignNoteSchema = z.object({
|
|
|
3331
3339
|
workspace: z.string().min(1)
|
|
3332
3340
|
}).optional()
|
|
3333
3341
|
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
3342
|
+
var TriageVerdictSchema = z.enum(["keep", "stale", "close", "assign"]);
|
|
3343
|
+
var TriageItemSchema = z.object({
|
|
3344
|
+
noteId: z.string(),
|
|
3345
|
+
/** The note's headline at run time. */
|
|
3346
|
+
title: z.string(),
|
|
3347
|
+
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
3348
|
+
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
3349
|
+
* Never a model's reasoning transcript, never an id. */
|
|
3350
|
+
why: z.string()
|
|
3351
|
+
});
|
|
3352
|
+
var TriageAssignmentSchema = z.object({
|
|
3353
|
+
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
3354
|
+
agent: z.string(),
|
|
3355
|
+
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
3356
|
+
* same reason as `title`: the card must render from the proposal alone. */
|
|
3357
|
+
agentName: z.string(),
|
|
3358
|
+
notes: z.array(TriageItemSchema)
|
|
3359
|
+
});
|
|
3360
|
+
var TriageStatusSchema = z.enum(["open", "superseded", "dismissed"]);
|
|
3361
|
+
var SubmitTriageSchema = z.object({
|
|
3362
|
+
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
3363
|
+
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
3364
|
+
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
3365
|
+
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
3366
|
+
provider: z.string().min(1).max(60),
|
|
3367
|
+
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
3368
|
+
model: z.string().max(200).nullable().optional(),
|
|
3369
|
+
/** How many open notes the run actually looked at — the denominator on the phone
|
|
3370
|
+
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
3371
|
+
reviewed: z.number().int().min(0).max(1e4).default(0),
|
|
3372
|
+
close: z.array(TriageItemSchema).max(200).default([]),
|
|
3373
|
+
stale: z.array(TriageItemSchema).max(200).default([]),
|
|
3374
|
+
assign: z.array(TriageAssignmentSchema).max(50).default([])
|
|
3375
|
+
});
|
|
3376
|
+
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
3377
|
+
id: z.string(),
|
|
3378
|
+
runAt: z.string(),
|
|
3379
|
+
status: TriageStatusSchema,
|
|
3380
|
+
model: z.string().nullable().default(null)
|
|
3381
|
+
});
|
|
3382
|
+
var AcceptTriageSchema = z.discriminatedUnion("group", [
|
|
3383
|
+
z.object({ group: z.literal("close"), noteIds: z.array(z.string()).max(200).optional() }),
|
|
3384
|
+
z.object({ group: z.literal("stale"), noteIds: z.array(z.string()).max(200).optional() }),
|
|
3385
|
+
z.object({
|
|
3386
|
+
group: z.literal("assign"),
|
|
3387
|
+
agent: z.string().min(1),
|
|
3388
|
+
noteIds: z.array(z.string()).max(200).optional()
|
|
3389
|
+
})
|
|
3390
|
+
]);
|
|
3391
|
+
var AcceptTriageResultSchema = z.object({
|
|
3392
|
+
accepted: z.array(z.string()),
|
|
3393
|
+
failed: z.array(z.object({ noteId: z.string(), reason: z.string() }))
|
|
3394
|
+
});
|
|
3334
3395
|
var DeliveryModeSchema = z.enum(["poll", "self_hosted"]);
|
|
3335
3396
|
var RegisterDeliverySchema = z.object({ mode: DeliveryModeSchema });
|
|
3336
3397
|
var OAuthStartSchema = z.object({
|
|
@@ -4112,7 +4173,6 @@ async function submitNotification(req, opts = {}) {
|
|
|
4112
4173
|
if (!res.ok) throw new Error(`notify failed: ${res.status} ${await res.text()}`);
|
|
4113
4174
|
return await res.json();
|
|
4114
4175
|
}
|
|
4115
|
-
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4116
4176
|
function decryptAnswer(reply, deps = {}) {
|
|
4117
4177
|
if (!reply.sealed) return reply.answer;
|
|
4118
4178
|
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
@@ -4121,15 +4181,6 @@ function decryptAnswer(reply, deps = {}) {
|
|
|
4121
4181
|
}
|
|
4122
4182
|
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
4123
4183
|
}
|
|
4124
|
-
async function pollAnswer(notificationId) {
|
|
4125
|
-
const token = readToken();
|
|
4126
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`, {
|
|
4127
|
-
headers: { authorization: `Bearer ${token}` }
|
|
4128
|
-
}));
|
|
4129
|
-
if (!res.ok) throw new Error(`await failed: ${res.status} ${await res.text()}`);
|
|
4130
|
-
const item = await res.json();
|
|
4131
|
-
return item.type === "idle" ? item : decryptItem(item);
|
|
4132
|
-
}
|
|
4133
4184
|
var AWAIT_WINDOW_MS = 45e3;
|
|
4134
4185
|
var _partialSeen = /* @__PURE__ */ new Map();
|
|
4135
4186
|
async function pollPartials(notificationId) {
|
|
@@ -4150,30 +4201,6 @@ async function pollPartials(notificationId) {
|
|
|
4150
4201
|
return null;
|
|
4151
4202
|
}
|
|
4152
4203
|
}
|
|
4153
|
-
async function awaitReply(notificationId, opts = {}) {
|
|
4154
|
-
const intervalMs = opts.intervalMs ?? 5e3;
|
|
4155
|
-
const windowMs = opts.windowMs ?? AWAIT_WINDOW_MS;
|
|
4156
|
-
const doSleep = opts.sleep ?? sleep2;
|
|
4157
|
-
const now = opts.now ?? Date.now;
|
|
4158
|
-
const start = now();
|
|
4159
|
-
while (true) {
|
|
4160
|
-
if (opts.signal?.aborted) return { type: "idle" };
|
|
4161
|
-
const item = await pollAnswer(notificationId);
|
|
4162
|
-
if (item.type !== "idle") return item;
|
|
4163
|
-
const partial = await pollPartials(notificationId);
|
|
4164
|
-
if (partial) return partial;
|
|
4165
|
-
if (now() - start >= windowMs) return { type: "idle" };
|
|
4166
|
-
await doSleep(intervalMs);
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
function decryptItem(item) {
|
|
4170
|
-
if (item.type !== "reply") return item;
|
|
4171
|
-
const enriched = { ...item, intents: landIntents(item.intents) };
|
|
4172
|
-
if (!enriched.sealed) return enriched;
|
|
4173
|
-
const answer = decryptAnswer(enriched);
|
|
4174
|
-
const { sealed: _s, ...rest } = enriched;
|
|
4175
|
-
return { ...rest, answer };
|
|
4176
|
-
}
|
|
4177
4204
|
function landIntents(intents) {
|
|
4178
4205
|
if (!intents?.length) return intents;
|
|
4179
4206
|
return intents.map((i) => {
|
|
@@ -4199,10 +4226,14 @@ async function searchThreads(q) {
|
|
|
4199
4226
|
async function checkReplies(opts = {}) {
|
|
4200
4227
|
const token = authToken(opts.token);
|
|
4201
4228
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
|
|
4202
|
-
headers: {
|
|
4229
|
+
headers: {
|
|
4230
|
+
authorization: `Bearer ${token}`,
|
|
4231
|
+
...opts.replyAck ? { "x-paigy-await-ack": "1" } : {}
|
|
4232
|
+
}
|
|
4203
4233
|
}));
|
|
4204
4234
|
if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
|
|
4205
4235
|
const pending = await res.json();
|
|
4236
|
+
if (opts.replyAck) return pending;
|
|
4206
4237
|
return {
|
|
4207
4238
|
...pending,
|
|
4208
4239
|
replies: pending.replies.map((raw) => {
|
|
@@ -4267,15 +4298,6 @@ async function heartbeat(runtime, opts = {}) {
|
|
|
4267
4298
|
}));
|
|
4268
4299
|
if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
|
|
4269
4300
|
}
|
|
4270
|
-
async function setTaskState(notificationId, state, opts = {}) {
|
|
4271
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notify/${notificationId}/state`, {
|
|
4272
|
-
method: "PATCH",
|
|
4273
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
4274
|
-
body: JSON.stringify({ state })
|
|
4275
|
-
}));
|
|
4276
|
-
if (!res.ok) throw new Error(`set_task_state failed: ${res.status} ${await res.text()}`);
|
|
4277
|
-
return await res.json();
|
|
4278
|
-
}
|
|
4279
4301
|
async function registerDelivery(mode) {
|
|
4280
4302
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
|
|
4281
4303
|
method: "POST",
|
|
@@ -4305,6 +4327,204 @@ async function handoff(req) {
|
|
|
4305
4327
|
if (!res.ok) throw new Error(`handoff failed: ${res.status} ${await res.text()}`);
|
|
4306
4328
|
return await res.json();
|
|
4307
4329
|
}
|
|
4330
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4331
|
+
var ReplyLeaseExpiredError = class extends Error {
|
|
4332
|
+
constructor() {
|
|
4333
|
+
super(
|
|
4334
|
+
"The Paigy reply lease expired before acknowledgement. The answer remains durable; call await_reply again to reclaim it before acting."
|
|
4335
|
+
);
|
|
4336
|
+
this.name = "ReplyLeaseExpiredError";
|
|
4337
|
+
}
|
|
4338
|
+
};
|
|
4339
|
+
var ReplyAckError = class extends Error {
|
|
4340
|
+
constructor(message, retryable) {
|
|
4341
|
+
super(message);
|
|
4342
|
+
this.retryable = retryable;
|
|
4343
|
+
this.name = "ReplyAckError";
|
|
4344
|
+
}
|
|
4345
|
+
retryable;
|
|
4346
|
+
};
|
|
4347
|
+
function ensureAuthed2(res) {
|
|
4348
|
+
if (res.status === 401) throw new UnpairedError();
|
|
4349
|
+
return res;
|
|
4350
|
+
}
|
|
4351
|
+
function decryptAnswer2(reply, deps = {}) {
|
|
4352
|
+
if (!reply.sealed) return reply.answer;
|
|
4353
|
+
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
4354
|
+
if (!keyFile?.keypair) {
|
|
4355
|
+
throw new Error(
|
|
4356
|
+
"received a sealed answer but this agent has no device key to open it \u2014 re-pair."
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
4359
|
+
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
4360
|
+
}
|
|
4361
|
+
function landIntents2(intents) {
|
|
4362
|
+
if (!intents?.length) return intents;
|
|
4363
|
+
return intents.map((intent) => {
|
|
4364
|
+
if (intent.kind !== "defer") return intent;
|
|
4365
|
+
const due = parseDue(intent.detail);
|
|
4366
|
+
return due !== null ? { ...intent, dueInSeconds: due } : intent;
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
function decodeReply(item, deps = {}) {
|
|
4370
|
+
const { delivery: _delivery, ...withoutDelivery } = item;
|
|
4371
|
+
const enriched = {
|
|
4372
|
+
...withoutDelivery,
|
|
4373
|
+
intents: landIntents2(withoutDelivery.intents)
|
|
4374
|
+
};
|
|
4375
|
+
if (!enriched.sealed) return enriched;
|
|
4376
|
+
const answer = decryptAnswer2(enriched, deps);
|
|
4377
|
+
const { sealed: _sealed, ...plaintext } = enriched;
|
|
4378
|
+
return { ...plaintext, answer };
|
|
4379
|
+
}
|
|
4380
|
+
function deliveryOf(item) {
|
|
4381
|
+
if (item.type !== "reply" || !("delivery" in item) || item.delivery === void 0) {
|
|
4382
|
+
return null;
|
|
4383
|
+
}
|
|
4384
|
+
const delivery = item.delivery;
|
|
4385
|
+
if (!delivery || typeof delivery.leaseId !== "string" || !delivery.leaseId || typeof delivery.expiresAt !== "string" || !delivery.expiresAt) {
|
|
4386
|
+
throw new Error(
|
|
4387
|
+
"Paigy returned invalid reply-delivery metadata; the answer was not acknowledged and remains recoverable."
|
|
4388
|
+
);
|
|
4389
|
+
}
|
|
4390
|
+
return delivery;
|
|
4391
|
+
}
|
|
4392
|
+
async function responseDetail(res) {
|
|
4393
|
+
try {
|
|
4394
|
+
return (await res.text()).slice(0, 300);
|
|
4395
|
+
} catch {
|
|
4396
|
+
return "";
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
async function acknowledge(token, leaseId, opts = {}) {
|
|
4400
|
+
const attempts = Math.max(1, Math.min(opts.attempts ?? 3, 5));
|
|
4401
|
+
const backoffMs = Math.max(0, opts.backoffMs ?? 100);
|
|
4402
|
+
const doSleep = opts.doSleep ?? sleep2;
|
|
4403
|
+
let lastError;
|
|
4404
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
4405
|
+
try {
|
|
4406
|
+
const res = ensureAuthed2(await reach(`${BACKEND_URL}/api/await/ack`, {
|
|
4407
|
+
method: "POST",
|
|
4408
|
+
headers: {
|
|
4409
|
+
authorization: `Bearer ${token}`,
|
|
4410
|
+
"content-type": "application/json"
|
|
4411
|
+
},
|
|
4412
|
+
body: JSON.stringify({ leaseId })
|
|
4413
|
+
}));
|
|
4414
|
+
if (res.ok) return;
|
|
4415
|
+
const detail2 = await responseDetail(res);
|
|
4416
|
+
if (res.status === 409) throw new ReplyLeaseExpiredError();
|
|
4417
|
+
const retryable = res.status === 408 || res.status === 425 || res.status === 429 || res.status >= 500;
|
|
4418
|
+
throw new ReplyAckError(
|
|
4419
|
+
`Paigy reply acknowledgement failed: ${res.status}${detail2 ? ` ${detail2}` : ""}`,
|
|
4420
|
+
retryable
|
|
4421
|
+
);
|
|
4422
|
+
} catch (error) {
|
|
4423
|
+
if (error instanceof UnpairedError || error instanceof ReplyLeaseExpiredError || error instanceof ReplyAckError && !error.retryable) {
|
|
4424
|
+
throw error;
|
|
4425
|
+
}
|
|
4426
|
+
lastError = error;
|
|
4427
|
+
if (attempt + 1 < attempts) {
|
|
4428
|
+
await doSleep(backoffMs * 2 ** attempt);
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
4433
|
+
throw new Error(
|
|
4434
|
+
`Could not acknowledge the Paigy reply after ${attempts} attempts (${detail}). The answer was not returned to the agent and remains recoverable after the lease expires.`
|
|
4435
|
+
);
|
|
4436
|
+
}
|
|
4437
|
+
async function receiveReply(item, token, opts = {}) {
|
|
4438
|
+
const decoded = decodeReply(item, opts);
|
|
4439
|
+
const delivery = deliveryOf(item);
|
|
4440
|
+
if (!delivery) {
|
|
4441
|
+
if (opts.requireDelivery) {
|
|
4442
|
+
throw new Error("Paigy returned a catch-up reply without a delivery lease; the answer was not exposed.");
|
|
4443
|
+
}
|
|
4444
|
+
return decoded;
|
|
4445
|
+
}
|
|
4446
|
+
await acknowledge(token, delivery.leaseId, {
|
|
4447
|
+
attempts: opts.ackAttempts,
|
|
4448
|
+
backoffMs: opts.ackBackoffMs,
|
|
4449
|
+
doSleep: opts.ackSleep
|
|
4450
|
+
});
|
|
4451
|
+
return decoded;
|
|
4452
|
+
}
|
|
4453
|
+
async function pollAnswer(notificationId, opts = {}) {
|
|
4454
|
+
const token = authToken() ?? "";
|
|
4455
|
+
const res = ensureAuthed2(await reach(
|
|
4456
|
+
`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`,
|
|
4457
|
+
{
|
|
4458
|
+
headers: {
|
|
4459
|
+
authorization: `Bearer ${token}`,
|
|
4460
|
+
"x-paigy-await-ack": "1"
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
));
|
|
4464
|
+
if (!res.ok) throw new Error(`await failed: ${res.status} ${await res.text()}`);
|
|
4465
|
+
const wire = await res.json();
|
|
4466
|
+
if (wire.type !== "reply") return wire;
|
|
4467
|
+
return receiveReply(wire, token, opts);
|
|
4468
|
+
}
|
|
4469
|
+
async function awaitReply(notificationId, opts = {}) {
|
|
4470
|
+
const intervalMs = opts.intervalMs ?? 5e3;
|
|
4471
|
+
const windowMs = opts.windowMs ?? AWAIT_WINDOW_MS;
|
|
4472
|
+
const doSleep = opts.sleep ?? sleep2;
|
|
4473
|
+
const now = opts.now ?? Date.now;
|
|
4474
|
+
const start = now();
|
|
4475
|
+
while (true) {
|
|
4476
|
+
if (opts.signal?.aborted) return { type: "idle" };
|
|
4477
|
+
const item = await pollAnswer(notificationId);
|
|
4478
|
+
if (item.type !== "idle") return item;
|
|
4479
|
+
const partial = await pollPartials(notificationId);
|
|
4480
|
+
if (partial) return partial;
|
|
4481
|
+
if (now() - start >= windowMs) return { type: "idle" };
|
|
4482
|
+
await doSleep(intervalMs);
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
async function checkReplies2(opts = {}) {
|
|
4486
|
+
const pending = await checkReplies({ ...opts, replyAck: true });
|
|
4487
|
+
const token = authToken(opts.token) ?? "";
|
|
4488
|
+
const replies = await Promise.all(pending.replies.map(async (reply) => {
|
|
4489
|
+
const received = await receiveReply(
|
|
4490
|
+
{ type: "reply", ...reply },
|
|
4491
|
+
token,
|
|
4492
|
+
{ requireDelivery: true }
|
|
4493
|
+
);
|
|
4494
|
+
const { type: _type, ...withoutType } = received;
|
|
4495
|
+
return withoutType;
|
|
4496
|
+
}));
|
|
4497
|
+
return {
|
|
4498
|
+
...pending,
|
|
4499
|
+
replies
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
function ensureAuthed3(res) {
|
|
4503
|
+
if (res.status === 401) throw new UnpairedError();
|
|
4504
|
+
return res;
|
|
4505
|
+
}
|
|
4506
|
+
async function setTaskState(notificationId, state, opts = {}) {
|
|
4507
|
+
const token = authToken(opts.token) ?? "";
|
|
4508
|
+
const res = ensureAuthed3(await reach(
|
|
4509
|
+
`${BACKEND_URL}/api/notify/${encodeURIComponent(notificationId)}/state`,
|
|
4510
|
+
{
|
|
4511
|
+
method: "PATCH",
|
|
4512
|
+
headers: {
|
|
4513
|
+
"content-type": "application/json",
|
|
4514
|
+
authorization: `Bearer ${token}`,
|
|
4515
|
+
"x-paigy-await-ack": "1"
|
|
4516
|
+
},
|
|
4517
|
+
body: JSON.stringify({ state })
|
|
4518
|
+
}
|
|
4519
|
+
));
|
|
4520
|
+
if (!res.ok) {
|
|
4521
|
+
throw new Error(`set_task_state failed: ${res.status} ${await res.text()}`);
|
|
4522
|
+
}
|
|
4523
|
+
return await res.json();
|
|
4524
|
+
}
|
|
4525
|
+
function overrideToken2(secret) {
|
|
4526
|
+
overrideToken(secret);
|
|
4527
|
+
}
|
|
4308
4528
|
|
|
4309
4529
|
export {
|
|
4310
4530
|
BACKEND_URL,
|
|
@@ -4334,18 +4554,18 @@ export {
|
|
|
4334
4554
|
pairStep,
|
|
4335
4555
|
UnpairedError,
|
|
4336
4556
|
submitNotification,
|
|
4337
|
-
awaitReply,
|
|
4338
4557
|
getThread,
|
|
4339
4558
|
searchThreads,
|
|
4340
|
-
checkReplies,
|
|
4341
4559
|
answerCallerQuestion,
|
|
4342
|
-
overrideToken,
|
|
4343
4560
|
hatch,
|
|
4344
4561
|
whoAmI,
|
|
4345
4562
|
setIdentity,
|
|
4346
4563
|
heartbeat,
|
|
4347
|
-
setTaskState,
|
|
4348
4564
|
registerDelivery,
|
|
4349
4565
|
scheduleCallback,
|
|
4350
|
-
handoff
|
|
4566
|
+
handoff,
|
|
4567
|
+
awaitReply,
|
|
4568
|
+
checkReplies2,
|
|
4569
|
+
setTaskState,
|
|
4570
|
+
overrideToken2
|
|
4351
4571
|
};
|
|
@@ -251,10 +251,10 @@ var IntentSchema = z.object({
|
|
|
251
251
|
// questions included. Found auditing five calls' stored feedback, 2026-08-01.
|
|
252
252
|
kind: z.enum(["defer", "delegate", "channel", "question", "detail", "feedback", "command", "control"]),
|
|
253
253
|
detail: z.string(),
|
|
254
|
-
/**
|
|
255
|
-
*
|
|
256
|
-
* the
|
|
257
|
-
*
|
|
254
|
+
/** Defer only: seconds until the callback the caller asked for, when something upstream
|
|
255
|
+
* already read the time. Nothing sets it today (#397 documented an MCP parser that was
|
|
256
|
+
* never written) — the API reads the defer's `detail` itself with `notes/when.ts`
|
|
257
|
+
* (`parseDelay`, #1292), and a value here simply wins over that reading. */
|
|
258
258
|
dueInSeconds: z.number().int().positive().optional(),
|
|
259
259
|
/** Feedback only (#812): WHICH failure the complaint names — typed by the mapper that
|
|
260
260
|
* already read the utterance, so `feedback_from_call.kind` stops defaulting to
|
|
@@ -696,7 +696,13 @@ var BrokerTuningSchema = z.object({
|
|
|
696
696
|
* uninterpretable, 'high' = whenever not fully certain. */
|
|
697
697
|
clarifyEagerness: z.enum(["low", "normal", "high"]).optional(),
|
|
698
698
|
/** The user's own shorthand: when they say `say`, they mean `mean`. */
|
|
699
|
-
phrasebook: z.array(z.object({ say: z.string().min(1).max(60), mean: z.string().min(1).max(120) })).max(24).optional()
|
|
699
|
+
phrasebook: z.array(z.object({ say: z.string().min(1).max(60), mean: z.string().min(1).max(120) })).max(24).optional(),
|
|
700
|
+
/** The language calls are PLANNED in, when the account has chosen one (#1272). Absent —
|
|
701
|
+
* which is every account today — means the agent's own words decide, per ask: a call
|
|
702
|
+
* about an English ask opens in English. This is the only thing that overrides that,
|
|
703
|
+
* and a live caller who switches language mid-call still outranks it (broker/lang.ts).
|
|
704
|
+
* Set per user (no UI yet), like `voiceTuning`. */
|
|
705
|
+
language: z.enum(["en", "es"]).optional()
|
|
700
706
|
});
|
|
701
707
|
var UserSettingsSchema = z.object({
|
|
702
708
|
permissions: z.object({
|
|
@@ -904,7 +910,9 @@ var NoteSchema = z.object({
|
|
|
904
910
|
/** The request thread minted at assignment; null until assigned. */
|
|
905
911
|
parentId: z.string().nullable(),
|
|
906
912
|
/** REMINDERS (reminders-design.md): the NOT-BEFORE this becomes eligible to ride a
|
|
907
|
-
* call — never a deadline
|
|
913
|
+
* call — never a deadline. It only ever comes from the user's own words, so when it
|
|
914
|
+
* passes Paigy rings ONCE (#1293, owner 2026-08-26: a time said out loud is consent to
|
|
915
|
+
* a ring at that time); after that it rides like any other. Null = "the very next
|
|
908
916
|
* call", the right reading of "remind me to…" with no time attached. */
|
|
909
917
|
// Defaulted, not required: a Note from an API deploy older than the reminders
|
|
910
918
|
// migration has none of these, and the defaults ARE what it means — no not-before,
|
|
@@ -947,6 +955,59 @@ var AssignNoteSchema = z.object({
|
|
|
947
955
|
workspace: z.string().min(1)
|
|
948
956
|
}).optional()
|
|
949
957
|
}).refine((a) => !!a.target !== !!a.spawn, { message: "exactly one of target or spawn" });
|
|
958
|
+
var TriageVerdictSchema = z.enum(["keep", "stale", "close", "assign"]);
|
|
959
|
+
var TriageItemSchema = z.object({
|
|
960
|
+
noteId: z.string(),
|
|
961
|
+
/** The note's headline at run time. */
|
|
962
|
+
title: z.string(),
|
|
963
|
+
/** WHY, in one short human line, evidence first — this is read on a phone underneath
|
|
964
|
+
* the note's title: "no movement in 34 days", "worked 3 notes in this repo this week".
|
|
965
|
+
* Never a model's reasoning transcript, never an id. */
|
|
966
|
+
why: z.string()
|
|
967
|
+
});
|
|
968
|
+
var TriageAssignmentSchema = z.object({
|
|
969
|
+
/** The agent's token id — what `dispatchNote` resolves and what a request is addressed to. */
|
|
970
|
+
agent: z.string(),
|
|
971
|
+
/** Its display name at run time (the name on the hatchling's card). Denormalized for the
|
|
972
|
+
* same reason as `title`: the card must render from the proposal alone. */
|
|
973
|
+
agentName: z.string(),
|
|
974
|
+
notes: z.array(TriageItemSchema)
|
|
975
|
+
});
|
|
976
|
+
var TriageStatusSchema = z.enum(["open", "superseded", "dismissed"]);
|
|
977
|
+
var SubmitTriageSchema = z.object({
|
|
978
|
+
/** Which runtime judged: "ollama" (inference never left the machine) or a harness the
|
|
979
|
+
* user already runs under their own credentials ("claude" / "codex" / "agy"). Recorded
|
|
980
|
+
* so the phone can say where the content went — an unattributed privacy claim is worth
|
|
981
|
+
* nothing, and #1106's promise is precisely "Paigy's servers never see this". */
|
|
982
|
+
provider: z.string().min(1).max(60),
|
|
983
|
+
/** The concrete model when the provider names one (an ollama tag); null otherwise. */
|
|
984
|
+
model: z.string().max(200).nullable().optional(),
|
|
985
|
+
/** How many open notes the run actually looked at — the denominator on the phone
|
|
986
|
+
* ("6 of 50"), and the honest answer to "did it read the whole queue?". */
|
|
987
|
+
reviewed: z.number().int().min(0).max(1e4).default(0),
|
|
988
|
+
close: z.array(TriageItemSchema).max(200).default([]),
|
|
989
|
+
stale: z.array(TriageItemSchema).max(200).default([]),
|
|
990
|
+
assign: z.array(TriageAssignmentSchema).max(50).default([])
|
|
991
|
+
});
|
|
992
|
+
var TriageProposalSchema = SubmitTriageSchema.extend({
|
|
993
|
+
id: z.string(),
|
|
994
|
+
runAt: z.string(),
|
|
995
|
+
status: TriageStatusSchema,
|
|
996
|
+
model: z.string().nullable().default(null)
|
|
997
|
+
});
|
|
998
|
+
var AcceptTriageSchema = z.discriminatedUnion("group", [
|
|
999
|
+
z.object({ group: z.literal("close"), noteIds: z.array(z.string()).max(200).optional() }),
|
|
1000
|
+
z.object({ group: z.literal("stale"), noteIds: z.array(z.string()).max(200).optional() }),
|
|
1001
|
+
z.object({
|
|
1002
|
+
group: z.literal("assign"),
|
|
1003
|
+
agent: z.string().min(1),
|
|
1004
|
+
noteIds: z.array(z.string()).max(200).optional()
|
|
1005
|
+
})
|
|
1006
|
+
]);
|
|
1007
|
+
var AcceptTriageResultSchema = z.object({
|
|
1008
|
+
accepted: z.array(z.string()),
|
|
1009
|
+
failed: z.array(z.object({ noteId: z.string(), reason: z.string() }))
|
|
1010
|
+
});
|
|
950
1011
|
var DeliveryModeSchema = z.enum(["poll", "self_hosted"]);
|
|
951
1012
|
var WAKE_EVENT = "wake";
|
|
952
1013
|
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
package/dist/enable.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
HandoffSchema,
|
|
4
4
|
MISSED_CALL_PLAN
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YOGAG4YY.js";
|
|
6
6
|
import {
|
|
7
7
|
ENABLE_COMMAND,
|
|
8
8
|
PAIGY_TOOL_IDS,
|
|
9
9
|
autoConfigureClients,
|
|
10
10
|
claudeInstallHint,
|
|
11
11
|
paigyToolsAllowlisted
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KNMI3CJ3.js";
|
|
13
13
|
import {
|
|
14
14
|
clearSurface,
|
|
15
15
|
writeSurface
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
UnpairedError,
|
|
23
23
|
answerCallerQuestion,
|
|
24
24
|
awaitReply,
|
|
25
|
-
|
|
25
|
+
checkReplies2,
|
|
26
26
|
deleteKeyFile,
|
|
27
27
|
deleteToken,
|
|
28
28
|
fetchCredential,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
lintNotify,
|
|
35
35
|
listSlots,
|
|
36
36
|
normalizeSpeech,
|
|
37
|
-
|
|
37
|
+
overrideToken2,
|
|
38
38
|
pairStep,
|
|
39
39
|
readKeyFile,
|
|
40
40
|
readToken,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
startE2ee,
|
|
52
52
|
submitNotification,
|
|
53
53
|
whoAmI
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-U6SLIORK.js";
|
|
55
55
|
|
|
56
56
|
// src/index.ts
|
|
57
57
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -404,7 +404,7 @@ function prependNote(result, note) {
|
|
|
404
404
|
}
|
|
405
405
|
async function hatchUnderDevice(name, voice) {
|
|
406
406
|
if (!listSlots().includes("Desktop")) return null;
|
|
407
|
-
|
|
407
|
+
overrideToken2(readToken("Desktop"));
|
|
408
408
|
try {
|
|
409
409
|
const minted = await hatch(name ?? suggestedAgentName() ?? "Agent", voice ?? null);
|
|
410
410
|
const dt = { ok: true, access_token: minted.token, name: minted.name, device: null };
|
|
@@ -413,7 +413,7 @@ async function hatchUnderDevice(name, voice) {
|
|
|
413
413
|
} catch {
|
|
414
414
|
return null;
|
|
415
415
|
} finally {
|
|
416
|
-
|
|
416
|
+
overrideToken2(null);
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
async function runPair(device_code, name, voice, note) {
|
|
@@ -658,7 +658,7 @@ async function handleTool(request, signal) {
|
|
|
658
658
|
return { content: [{ type: "text", text: JSON.stringify(item) }] };
|
|
659
659
|
}
|
|
660
660
|
case "check_replies": {
|
|
661
|
-
const result = await
|
|
661
|
+
const result = await checkReplies2();
|
|
662
662
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
663
663
|
}
|
|
664
664
|
case "get_thread": {
|
package/dist/listen.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
WAKE_EVENT,
|
|
4
4
|
wakeChannel
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YOGAG4YY.js";
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
checkReplies2,
|
|
8
8
|
registerDelivery
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-U6SLIORK.js";
|
|
10
10
|
|
|
11
11
|
// src/listen.ts
|
|
12
12
|
import { createClient } from "@supabase/supabase-js";
|
|
@@ -170,7 +170,7 @@ function launchEnv(work, reason) {
|
|
|
170
170
|
}
|
|
171
171
|
async function sweep(reason) {
|
|
172
172
|
try {
|
|
173
|
-
const work = await
|
|
173
|
+
const work = await checkReplies2();
|
|
174
174
|
emit({ type: "sweep", reason, ...work });
|
|
175
175
|
const cmd = process.env.PAIGY_ON_WAKE;
|
|
176
176
|
const inbound = work.replies.length > 0 || work.requests.length > 0;
|
package/dist/onboard.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
autoConfigureClients,
|
|
4
4
|
claudeInstallHint,
|
|
5
5
|
openBrowser
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KNMI3CJ3.js";
|
|
7
7
|
import {
|
|
8
8
|
AGENT_NAME,
|
|
9
9
|
TOKEN_PATH,
|
|
10
10
|
hatch,
|
|
11
11
|
listSlots,
|
|
12
|
-
|
|
12
|
+
overrideToken2,
|
|
13
13
|
pollToken,
|
|
14
14
|
readToken,
|
|
15
15
|
requestCode,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setIdentity,
|
|
18
18
|
sleep,
|
|
19
19
|
whoAmI
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-U6SLIORK.js";
|
|
21
21
|
|
|
22
22
|
// src/onboard.ts
|
|
23
23
|
async function main() {
|
|
@@ -50,7 +50,7 @@ Paigy MCP onboarding (agent: "${AGENT_NAME}")
|
|
|
50
50
|
console.log("The saved token is no longer valid \u2014 re-pairing.\n");
|
|
51
51
|
}
|
|
52
52
|
if (listSlots().includes("Desktop")) {
|
|
53
|
-
|
|
53
|
+
overrideToken2(readToken("Desktop"));
|
|
54
54
|
try {
|
|
55
55
|
const minted = await hatch(process.env.PAIGY_NAME ?? "Agent", null);
|
|
56
56
|
saveToken({ ok: true, access_token: minted.token, name: minted.name, device: null });
|
|
@@ -67,7 +67,7 @@ Paigy MCP onboarding (agent: "${AGENT_NAME}")
|
|
|
67
67
|
} catch {
|
|
68
68
|
console.log("(This machine's saved credential didn't work \u2014 falling back to a code.)\n");
|
|
69
69
|
} finally {
|
|
70
|
-
|
|
70
|
+
overrideToken2(null);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
const code = await requestCode();
|
package/dist/statusline.js
CHANGED