@paigy/mcp 0.40.1 → 0.40.3
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 +25 -22
- package/dist/{chunk-TLASZUJD.js → chunk-4JDG67EF.js} +2 -2
- package/dist/{chunk-4UZAPBKK.js → chunk-6QQHTUKJ.js} +195 -325
- package/dist/{chunk-CXABA27N.js → chunk-TNQOFCZC.js} +1 -1
- package/dist/{chunk-3QUIB42M.js → chunk-V3VVCML7.js} +113 -142
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +34 -102
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/statusline.js +1 -1
- package/package.json +13 -13
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
3
3
|
import { createHash, randomUUID } from "crypto";
|
|
4
4
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5
|
-
import { setTimeout as
|
|
5
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
6
6
|
import { z as z3 } from "zod";
|
|
7
7
|
import { z as z2 } from "zod";
|
|
8
8
|
import { ZodFirstPartyTypeKind as ZodFirstPartyTypeKind3 } from "zod/v3";
|
|
@@ -14,6 +14,7 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, statS
|
|
|
14
14
|
import { homedir } from "os";
|
|
15
15
|
import { join } from "path";
|
|
16
16
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
17
|
+
import { WebSocket } from "undici";
|
|
17
18
|
var require2 = __sdkCreateRequire(import.meta.url);
|
|
18
19
|
var __create = Object.create;
|
|
19
20
|
var __defProp = Object.defineProperty;
|
|
@@ -3551,7 +3552,22 @@ function mcpInputSchema(s) {
|
|
|
3551
3552
|
}
|
|
3552
3553
|
var StartContactSchema = z.object({
|
|
3553
3554
|
goalIds: z.tuple([z.string().uuid()]),
|
|
3554
|
-
|
|
3555
|
+
// THE ASK IS THE QUESTION, not a bulletin with a question at the end (2026-09-16).
|
|
3556
|
+
//
|
|
3557
|
+
// A DecisionNeed is settled only by an answer in the shape THIS ask declares. Bundle news,
|
|
3558
|
+
// findings and a decision into one, and the person answers whichever part engaged them —
|
|
3559
|
+
// which settles nothing, leaves the need open, and gets them asked again. On call 619c5a92
|
|
3560
|
+
// one ask carried a briefing, a side question ("the ring should show the agent's name now —
|
|
3561
|
+
// what did you actually see?") and a one-of-four about how far an erase reaches. The owner
|
|
3562
|
+
// answered the side question. The need stayed open; the card asked again. Of the 45 call
|
|
3563
|
+
// asks carrying a decision, 27 are over 600 characters.
|
|
3564
|
+
//
|
|
3565
|
+
// Sending them separately costs nothing, because a CALL contact joins the call already
|
|
3566
|
+
// happening (`goal/store.ts`, JOIN BEFORE MINTING) — several contacts on one Goal arrive as
|
|
3567
|
+
// one call, planned as its turns, and each keeps its own settleable need.
|
|
3568
|
+
ask: z.string().trim().min(1).max(1e4).describe(
|
|
3569
|
+
"The question, and only what is needed to answer it. News, progress and findings are their own contact \u2014 a call contact JOINS a call already happening, so several arrive as one call. Do not bundle: a DecisionNeed is settled only by an answer in the shape this ask declares, so someone who answers the part that interested them settles nothing and is asked again."
|
|
3570
|
+
),
|
|
3555
3571
|
waiting: z.enum(["none", "hard"]).default("none"),
|
|
3556
3572
|
channel: z.enum(["notification", "call"]).default("notification"),
|
|
3557
3573
|
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(),
|
|
@@ -3563,12 +3579,25 @@ var CONTACT_DESCRIPTION = "Contact the user about exactly one existing Goal: pas
|
|
|
3563
3579
|
var CreateGoalSchema = z2.object({
|
|
3564
3580
|
outcome: z2.string().trim().min(1).max(1e4),
|
|
3565
3581
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
3566
|
-
idempotencyKey: z2.string().trim().min(1).max(200)
|
|
3582
|
+
idempotencyKey: z2.string().trim().min(1).max(200),
|
|
3583
|
+
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
3584
|
+
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
3585
|
+
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
3586
|
+
* the writer refuses a thread belonging to another account. */
|
|
3587
|
+
contextThreadId: z2.string().uuid().optional(),
|
|
3588
|
+
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
3589
|
+
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
3590
|
+
* worked"). It could not have been: this door took no parent, so the only route was
|
|
3591
|
+
* create -> claim -> `update_goal`, three calls with a lease in the middle, and every agent
|
|
3592
|
+
* took the short one. The hierarchy has been modelled since Goals existed and had been used
|
|
3593
|
+
* ZERO times in 2,031 of them. Absent, the server judges it against the caller's open Goals
|
|
3594
|
+
* (`apps/api/src/goal/intake.ts`). The writer refuses a Goal belonging to another account. */
|
|
3595
|
+
parentGoalId: z2.string().uuid().optional()
|
|
3567
3596
|
});
|
|
3568
3597
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
3569
3598
|
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
3599
|
}).strict();
|
|
3571
|
-
var CREATE_GOAL_DESCRIPTION = "Create a durable
|
|
3600
|
+
var CREATE_GOAL_DESCRIPTION = "Create a durable Goal for an outcome. Without parentGoalId it is placed against your open Goals: if one already IS this work, that Goal comes back (existing: true) and nothing new is created \u2014 continue it; if the work belongs under one, it is created there (parentGoalId in the receipt); otherwise it is a root. Pass parentGoalId yourself to put it under a specific Goal. 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
3601
|
var UpdateGoalSchema = z2.object({
|
|
3573
3602
|
revision: z2.number().int().positive(),
|
|
3574
3603
|
changes: z2.object({
|
|
@@ -3896,16 +3925,6 @@ function deriveAsk(req) {
|
|
|
3896
3925
|
}
|
|
3897
3926
|
var NotifyStatusSchema = z3.enum(["pending", "answered", "ignored"]);
|
|
3898
3927
|
var AgentStateSchema = z3.enum(["idle", "in_progress", "completed", "needs_input"]);
|
|
3899
|
-
var SetTaskStateSchema = z3.object({
|
|
3900
|
-
state: z3.enum(["in_progress", "completed", "needs_input"])
|
|
3901
|
-
});
|
|
3902
|
-
var SetWorkStateSchema = z3.object({
|
|
3903
|
-
workId: z3.string().uuid().optional(),
|
|
3904
|
-
notificationId: z3.string().optional(),
|
|
3905
|
-
state: SetTaskStateSchema.shape.state
|
|
3906
|
-
}).refine((value) => Number(Boolean(value.workId)) + Number(Boolean(value.notificationId)) === 1, {
|
|
3907
|
-
message: "exactly one of workId or notificationId is required"
|
|
3908
|
-
});
|
|
3909
3928
|
var TurnSchema = z3.object({
|
|
3910
3929
|
prompt: z3.string(),
|
|
3911
3930
|
reply: z3.string()
|
|
@@ -4035,108 +4054,6 @@ var AwaitItemSchema = z3.discriminatedUnion("type", [
|
|
|
4035
4054
|
})
|
|
4036
4055
|
]);
|
|
4037
4056
|
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."),
|
|
4041
|
-
trigger: CallbackTriggerSchema,
|
|
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.")
|
|
4044
|
-
});
|
|
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(),
|
|
4052
|
-
answer: UserAnswerSchema,
|
|
4053
|
-
/** E2EE: the sealed answer (opaque envelope + plaintext `ignored` hint) when the
|
|
4054
|
-
* pairing is E2EE — the agent opens it and re-derives the real answer. Absent =
|
|
4055
|
-
* plaintext answer (in `answer`). See AwaitItemSchema's reply variant. */
|
|
4056
|
-
sealed: z3.lazy(() => SealedAnswerSchema).optional(),
|
|
4057
|
-
/** The call record rendered for THIS agent: the raw words the shaped answer was
|
|
4058
|
-
* mapped from, filtered to its own claims. See AwaitItemSchema's reply variant. */
|
|
4059
|
-
transcript: z3.string().optional(),
|
|
4060
|
-
/** Coverage report (#396): which declared `points` this answer addressed. */
|
|
4061
|
-
covered: z3.array(z3.string()).optional()
|
|
4062
|
-
})
|
|
4063
|
-
),
|
|
4064
|
-
pending: z3.array(
|
|
4065
|
-
z3.object({ parentId: z3.string(), notificationId: z3.string(), createdAt: z3.string() })
|
|
4066
|
-
),
|
|
4067
|
-
/** WHO YOU ARE on this account (field report 2026-08-28): the name and device the user
|
|
4068
|
-
* sees for this session's identity. From inside a session there was no way to find out —
|
|
4069
|
-
* `pair` with no arguments can HATCH a fresh identity, so it is not a safe probe — and an
|
|
4070
|
-
* agent that cannot tell which agent it is cannot tell whether work addressed to
|
|
4071
|
-
* "Reta" was addressed to it. Absent only for a token with no pairing behind it. */
|
|
4072
|
-
you: z3.object({ name: z3.string(), device: z3.string().nullable(), tokenId: z3.string() }).optional(),
|
|
4073
|
-
/** User-initiated requests addressed to this agent; act on them and reply via
|
|
4074
|
-
* contact on the same parentId. Keeps reappearing until you call
|
|
4075
|
-
* set_work_state on its workId (or notificationId once while bootstrapping). */
|
|
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(),
|
|
4083
|
-
/** The user seeded this request with a past conversation — call get_thread on it
|
|
4084
|
-
* FIRST and treat the transcript as prior context (#57/#251). */
|
|
4085
|
-
contextParentId: z3.string().optional(),
|
|
4086
|
-
/** STRANDED (field report 2026-08-28): this request was addressed to ANOTHER agent on
|
|
4087
|
-
* the account — the name here — which has not been seen since it landed, so nobody
|
|
4088
|
-
* came for it. Handed to you because you are the session that is here. Take it like
|
|
4089
|
-
* any request (set_work_state claims it, reply with contact on its parentId), and say
|
|
4090
|
-
* whose it was, because the user chose that agent on purpose. */
|
|
4091
|
-
stranded: z3.string().optional()
|
|
4092
|
-
})
|
|
4093
|
-
),
|
|
4094
|
-
/** Durable outcomes currently owned by this agent. This is the Work-native queue; flat
|
|
4095
|
-
* notification lists remain during migration so old clients keep their existing view. */
|
|
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()
|
|
4103
|
-
})).optional(),
|
|
4104
|
-
/** Callbacks you owe the user that are now DUE (you said you'd follow up when done,
|
|
4105
|
-
* if blocked, or at a time that has passed). Re-surfaced every sweep until you
|
|
4106
|
-
* fulfill one by calling contact on its parentId. */
|
|
4107
|
-
/** Ride-alongs (RideAlongSchema): notes assigned to this agent that no wake could
|
|
4108
|
-
* reach. Same array the contact/await replies carry — one queue, every carrier. */
|
|
4109
|
-
also: z3.array(RideAlongSchema).optional(),
|
|
4110
|
-
owedCallbacks: z3.array(
|
|
4111
|
-
z3.object({ parentId: z3.string(), trigger: CallbackTriggerSchema, note: z3.string() })
|
|
4112
|
-
),
|
|
4113
|
-
/** Work (either direction) you reported in_progress a while ago and never reported
|
|
4114
|
-
* completed — likely left half-done by this session or a prior one that crashed or
|
|
4115
|
-
* went idle. Report a real state (set_work_state) or continue the work. */
|
|
4116
|
-
stalled: z3.array(
|
|
4117
|
-
z3.object({ parentId: z3.string(), notificationId: z3.string(), title: z3.string().nullable(), startedAt: z3.string() })
|
|
4118
|
-
),
|
|
4119
|
-
/** The queue rail (#614, pending/design.md): the same replies + requests, grouped by
|
|
4120
|
-
* thread and ordered oldest-thread-first, so you work ONE thread at a time — fold all of
|
|
4121
|
-
* a thread's `items` into a single turn rather than interleaving threads. `busy` = the
|
|
4122
|
-
* thread already has a turn in progress (younger than the stall cutoff); let it finish and
|
|
4123
|
-
* ride the next turn. `items` are that thread's replies/requests in arrival order; the
|
|
4124
|
-
* full payload for each is in the flat `replies`/`requests` arrays (matched by
|
|
4125
|
-
* notificationId). Derived, never stored — a crashed agent recomputes it exactly. */
|
|
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()
|
|
4135
|
-
})
|
|
4136
|
-
)
|
|
4137
|
-
})
|
|
4138
|
-
)
|
|
4139
|
-
});
|
|
4140
4057
|
var NotifyResponseSchema = z3.object({
|
|
4141
4058
|
notificationId: z3.string(),
|
|
4142
4059
|
workId: z3.string().uuid().optional(),
|
|
@@ -4364,7 +4281,21 @@ var InboxItemSchema = z3.object({
|
|
|
4364
4281
|
/** E2EE: the agent's device X25519 public key to seal the user's answer BACK to (the
|
|
4365
4282
|
* sender the phone replies to). Sourced server-side from this item's pairing credential.
|
|
4366
4283
|
* Present only alongside `envelope`; the phone seals via sealAnswer(answer, this, id). */
|
|
4367
|
-
agentX25519: z3.string().optional()
|
|
4284
|
+
agentX25519: z3.string().optional(),
|
|
4285
|
+
/** THE TARGET FACTS A CARD RENDERS (#1796 point 5, 2026-09-11): the Delivery it is a view of,
|
|
4286
|
+
* that Delivery's kind, the request Entry, the Goals it answers for, the exact DecisionNeed (none
|
|
4287
|
+
* for a request that asks nothing), whether its content is sealed, and that Goal's state. The
|
|
4288
|
+
* answer writer (`POST /api/entries`) and the disposition (`close_delivery`) take their ids from
|
|
4289
|
+
* here. The server projects it (`apps/api/src/inbox/project.ts`); a client never builds it. */
|
|
4290
|
+
communication: z3.object({
|
|
4291
|
+
deliveryId: z3.string(),
|
|
4292
|
+
kind: z3.enum(["notification", "call"]),
|
|
4293
|
+
entryId: z3.string(),
|
|
4294
|
+
goalIds: z3.array(z3.string()),
|
|
4295
|
+
decisionNeedId: z3.string().optional(),
|
|
4296
|
+
sealed: z3.boolean(),
|
|
4297
|
+
goalState: z3.string().optional()
|
|
4298
|
+
}).optional()
|
|
4368
4299
|
});
|
|
4369
4300
|
var SnoozeRequestSchema = z3.object({
|
|
4370
4301
|
requestId: z3.string(),
|
|
@@ -4600,26 +4531,54 @@ var CreateRequestSchema = z3.object({
|
|
|
4600
4531
|
* the agent reads it via get_thread. Must belong to the requesting user. */
|
|
4601
4532
|
contextParentId: z3.string().optional()
|
|
4602
4533
|
});
|
|
4603
|
-
var
|
|
4604
|
-
/**
|
|
4605
|
-
|
|
4606
|
-
/**
|
|
4607
|
-
|
|
4608
|
-
/**
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
/**
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4534
|
+
var QueueQuestionSchema = z3.object({
|
|
4535
|
+
/** The decision need's id — what an answer is accepted against. */
|
|
4536
|
+
id: z3.string(),
|
|
4537
|
+
/** The words that were asked, from the request Entry that asked them. */
|
|
4538
|
+
question: z3.string(),
|
|
4539
|
+
/** Where it was asked — which is where the ruling goes (`POST /api/entries`). Null only
|
|
4540
|
+
* for a need whose request Entry is carried by no interactive Delivery, which nothing
|
|
4541
|
+
* can answer. */
|
|
4542
|
+
deliveryId: z3.string().nullable().default(null),
|
|
4543
|
+
/** The Entry the ruling is about. */
|
|
4544
|
+
aboutId: z3.string().nullable().default(null),
|
|
4545
|
+
/** Empty for a free-text question. */
|
|
4546
|
+
options: z3.array(OptionSchema).default([]),
|
|
4547
|
+
select: z3.enum(["one", "many", "rank", "confirm", "text"]).default("text"),
|
|
4548
|
+
askedAt: z3.string(),
|
|
4549
|
+
/** Null while the question is open — which is how the page tells the two apart. */
|
|
4550
|
+
answeredAt: z3.string().nullable().default(null),
|
|
4551
|
+
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
4552
|
+
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
4553
|
+
* for a settled question whose reply carried nothing readable. */
|
|
4554
|
+
answer: z3.string().nullable().default(null)
|
|
4555
|
+
});
|
|
4556
|
+
var QueueItemSchema = z3.object({
|
|
4557
|
+
id: z3.string(),
|
|
4558
|
+
/** One-line headline — the first sentence of the outcome. */
|
|
4559
|
+
title: z3.string(),
|
|
4560
|
+
/** The outcome in full, verbatim: the person's own words are what an assignee sees. */
|
|
4561
|
+
intent: z3.string(),
|
|
4562
|
+
/** `ready` | `active` | `waiting` | `done` | `cancelled`, straight off the Goal. */
|
|
4563
|
+
state: z3.string(),
|
|
4564
|
+
/** Who holds it (a participant ref); null when nobody does yet. */
|
|
4565
|
+
assignee: z3.string().nullable().default(null),
|
|
4566
|
+
/** What the agent last said it was doing; null if it has said nothing. */
|
|
4567
|
+
progress: z3.string().nullable().default(null),
|
|
4568
|
+
reviewPending: z3.boolean().default(false),
|
|
4569
|
+
dueAt: z3.string().nullable().default(null),
|
|
4570
|
+
/** The Goal this one was opened under; null at the root. */
|
|
4571
|
+
parentGoalId: z3.string().nullable().default(null),
|
|
4572
|
+
/** Goals opened under this one — only those the same list holds. */
|
|
4573
|
+
childGoalIds: z3.array(z3.string()).default([]),
|
|
4574
|
+
/** Goals this one waits on (start or finish gates). */
|
|
4575
|
+
dependencyGoalIds: z3.array(z3.string()).default([]),
|
|
4576
|
+
/** True while any gate is on a Goal that is not done — the walk draws it dashed. */
|
|
4577
|
+
blocked: z3.boolean().default(false),
|
|
4578
|
+
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
4579
|
+
questions: z3.array(QueueQuestionSchema).default([]),
|
|
4580
|
+
createdAt: z3.string(),
|
|
4581
|
+
updatedAt: z3.string().nullable().default(null)
|
|
4623
4582
|
});
|
|
4624
4583
|
var NoteSourceSchema = z3.enum(["app", "call"]);
|
|
4625
4584
|
var NoteStatusSchema = z3.enum(["open", "assigned", "in_progress", "done"]);
|
|
@@ -4748,6 +4707,8 @@ var AcceptTriageResultSchema = z3.object({
|
|
|
4748
4707
|
failed: z3.array(z3.object({ noteId: z3.string(), reason: z3.string() }))
|
|
4749
4708
|
});
|
|
4750
4709
|
var DeliveryModeSchema = z3.enum(["poll", "self_hosted"]);
|
|
4710
|
+
var WAKE_EVENT = "wake";
|
|
4711
|
+
var wakeChannel = (tokenId) => `wake:${tokenId}`;
|
|
4751
4712
|
var RegisterDeliverySchema = z3.object({ mode: DeliveryModeSchema });
|
|
4752
4713
|
var OAuthStartSchema = z3.object({
|
|
4753
4714
|
provider: z3.enum(["cma"]),
|
|
@@ -4756,8 +4717,9 @@ var OAuthStartSchema = z3.object({
|
|
|
4756
4717
|
var DeliveryConfigSchema = z3.object({
|
|
4757
4718
|
tokenId: z3.string(),
|
|
4758
4719
|
mode: DeliveryModeSchema,
|
|
4759
|
-
/** null when the
|
|
4760
|
-
*
|
|
4720
|
+
/** null when the deployment has no anon key configured. `self_hosted` is then REFUSED
|
|
4721
|
+
* (503 `self_hosted_unavailable`) rather than registered, so a self_hosted config always
|
|
4722
|
+
* carries credentials; only a `poll` registration can come back with null here. */
|
|
4761
4723
|
realtime: z3.object({ url: z3.string(), anonKey: z3.string() }).nullable()
|
|
4762
4724
|
});
|
|
4763
4725
|
var StatusSchema = z3.object({
|
|
@@ -4966,21 +4928,6 @@ function utf8ToBytes(s) {
|
|
|
4966
4928
|
}
|
|
4967
4929
|
return Uint8Array.from(out);
|
|
4968
4930
|
}
|
|
4969
|
-
function bytesToUtf8(b) {
|
|
4970
|
-
let out = "";
|
|
4971
|
-
for (let i = 0; i < b.length; ) {
|
|
4972
|
-
const c = b[i++];
|
|
4973
|
-
if (c < 128) out += String.fromCharCode(c);
|
|
4974
|
-
else if (c < 224) out += String.fromCharCode((c & 31) << 6 | b[i++] & 63);
|
|
4975
|
-
else if (c < 240) out += String.fromCharCode((c & 15) << 12 | (b[i++] & 63) << 6 | b[i++] & 63);
|
|
4976
|
-
else {
|
|
4977
|
-
const cp = (c & 7) << 18 | (b[i++] & 63) << 12 | (b[i++] & 63) << 6 | b[i++] & 63;
|
|
4978
|
-
const u = cp - 65536;
|
|
4979
|
-
out += String.fromCharCode(55296 + (u >> 10), 56320 + (u & 1023));
|
|
4980
|
-
}
|
|
4981
|
-
}
|
|
4982
|
-
return out;
|
|
4983
|
-
}
|
|
4984
4931
|
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4985
4932
|
function toB64(bytes) {
|
|
4986
4933
|
let out = "";
|
|
@@ -5026,15 +4973,9 @@ function concat(parts) {
|
|
|
5026
4973
|
}
|
|
5027
4974
|
return out;
|
|
5028
4975
|
}
|
|
5029
|
-
function u16be(n) {
|
|
5030
|
-
return Uint8Array.of(n >>> 8 & 255, n & 255);
|
|
5031
|
-
}
|
|
5032
4976
|
function u32be(n) {
|
|
5033
4977
|
return Uint8Array.of(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
5034
4978
|
}
|
|
5035
|
-
function readU32be(b, o) {
|
|
5036
|
-
return (b[o] << 24 | b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]) >>> 0;
|
|
5037
|
-
}
|
|
5038
4979
|
function lp(b) {
|
|
5039
4980
|
return concat([u32be(b.length), b]);
|
|
5040
4981
|
}
|
|
@@ -5076,9 +5017,6 @@ function sharedSecret(peerX25519PubB64, mySecretB64) {
|
|
|
5076
5017
|
if (raw.every((b) => b === 0)) throw new Error("low-order X25519 point \u2014 shared secret rejected");
|
|
5077
5018
|
return toB64(raw);
|
|
5078
5019
|
}
|
|
5079
|
-
function keyId(publicKeyB64) {
|
|
5080
|
-
return toB64(import_tweetnacl2.default.hash(fromB64(publicKeyB64)).slice(0, 8));
|
|
5081
|
-
}
|
|
5082
5020
|
function credentialBody(c) {
|
|
5083
5021
|
return concat([
|
|
5084
5022
|
lp(utf8ToBytes("paigy-device-credential-v1")),
|
|
@@ -5096,43 +5034,6 @@ function verifyDeviceCredential(cred, uikPublicB64) {
|
|
|
5096
5034
|
return false;
|
|
5097
5035
|
}
|
|
5098
5036
|
}
|
|
5099
|
-
var ENVELOPE_VERSION = 1;
|
|
5100
|
-
var ENVELOPE_ALG = "x25519-xsalsa20poly1305";
|
|
5101
|
-
function canonicalHeader(msgId, hdr) {
|
|
5102
|
-
const parts = [
|
|
5103
|
-
u16be(ENVELOPE_VERSION),
|
|
5104
|
-
lp(utf8ToBytes(ENVELOPE_ALG)),
|
|
5105
|
-
lp(msgId),
|
|
5106
|
-
lp(utf8ToBytes(hdr.field)),
|
|
5107
|
-
lp(utf8ToBytes(hdr.kind)),
|
|
5108
|
-
lp(utf8ToBytes(hdr.senderRole)),
|
|
5109
|
-
u32be(hdr.seq),
|
|
5110
|
-
u32be(hdr.recipientKeyIds.length),
|
|
5111
|
-
...hdr.recipientKeyIds.map((k) => lp(utf8ToBytes(k)))
|
|
5112
|
-
];
|
|
5113
|
-
return concat(parts);
|
|
5114
|
-
}
|
|
5115
|
-
function open(envelope, myKeyId, mySecretKeyB64) {
|
|
5116
|
-
if (envelope.v !== ENVELOPE_VERSION) throw new Error(`unsupported envelope version ${String(envelope.v)}`);
|
|
5117
|
-
if (envelope.alg !== ENVELOPE_ALG) throw new Error(`unsupported alg ${String(envelope.alg)}`);
|
|
5118
|
-
const rec = envelope.recipients.find((r) => r.keyId === myKeyId);
|
|
5119
|
-
if (!rec) throw new Error("not a recipient of this envelope");
|
|
5120
|
-
const cek = import_tweetnacl3.default.box.open(fromB64(rec.wrap), fromB64(rec.wnonce), fromB64(rec.epk), fromB64(mySecretKeyB64));
|
|
5121
|
-
if (!cek) throw new Error("CEK unwrap failed (wrong key or tampered)");
|
|
5122
|
-
const padded = import_tweetnacl3.default.secretbox.open(fromB64(envelope.ct), fromB64(envelope.nonce), cek);
|
|
5123
|
-
if (!padded) throw new Error("body authentication failed (tampered ciphertext)");
|
|
5124
|
-
if (padded.length < 8) throw new Error("malformed sealed plaintext");
|
|
5125
|
-
const canonLen = readU32be(padded, 0);
|
|
5126
|
-
if (canonLen < 0 || 8 + canonLen > padded.length) throw new Error("malformed sealed plaintext");
|
|
5127
|
-
const sealedCanon = padded.slice(4, 4 + canonLen);
|
|
5128
|
-
const bodyLen = readU32be(padded, 4 + canonLen);
|
|
5129
|
-
const bodyStart = 8 + canonLen;
|
|
5130
|
-
if (bodyStart + bodyLen > padded.length) throw new Error("malformed sealed plaintext");
|
|
5131
|
-
const body = padded.slice(bodyStart, bodyStart + bodyLen);
|
|
5132
|
-
const expected = canonicalHeader(fromB64(envelope.msgId), envelope.hdr);
|
|
5133
|
-
if (!eqCt(sealedCanon, expected)) throw new Error("header mismatch (tampered metadata)");
|
|
5134
|
-
return { header: envelope.hdr, body };
|
|
5135
|
-
}
|
|
5136
5037
|
var SAS_DOMAIN = "paigy-sas-v2";
|
|
5137
5038
|
function deriveSAS(input, bits = 24) {
|
|
5138
5039
|
const bundle = concat([
|
|
@@ -5360,21 +5261,6 @@ async function pairStep(deviceCode, keyFile) {
|
|
|
5360
5261
|
return { kind: "e2ee_aborted", warning: e.message };
|
|
5361
5262
|
}
|
|
5362
5263
|
}
|
|
5363
|
-
function openSealedAnswer(sealed, keypair) {
|
|
5364
|
-
const myKeyId = keyId(keypair.x25519.publicKey);
|
|
5365
|
-
const { header, body } = open(sealed.envelope, myKeyId, keypair.x25519.secretKey);
|
|
5366
|
-
if (header.field !== "answer" || header.senderRole !== "user") {
|
|
5367
|
-
throw new Error("sealed answer has the wrong envelope field/role \u2014 refusing (possible replay/misattribution)");
|
|
5368
|
-
}
|
|
5369
|
-
const parsed = UserAnswerSchema.safeParse(JSON.parse(bytesToUtf8(body)));
|
|
5370
|
-
if (!parsed.success) throw new Error("sealed answer body is not a valid UserAnswer");
|
|
5371
|
-
const answer = parsed.data;
|
|
5372
|
-
const bodyIsIgnored = answer.kind === "ignored";
|
|
5373
|
-
if (sealed.ignored !== bodyIsIgnored) {
|
|
5374
|
-
throw new Error("sealed answer tamper: `ignored` hint disagrees with the decrypted body");
|
|
5375
|
-
}
|
|
5376
|
-
return answer;
|
|
5377
|
-
}
|
|
5378
5264
|
var UnpairedError = class extends Error {
|
|
5379
5265
|
constructor() {
|
|
5380
5266
|
super("Not paired with Paigy \u2014 no valid token (the API answered 401). Run the pairing flow, then retry.");
|
|
@@ -5435,6 +5321,7 @@ async function updateGoal(goalId, input, opts = {}) {
|
|
|
5435
5321
|
if (!res.ok) await fail("update_goal", res);
|
|
5436
5322
|
return await res.json();
|
|
5437
5323
|
}
|
|
5324
|
+
var AWAIT_WINDOW_MS = 45e3;
|
|
5438
5325
|
async function getThread(parentId, opts = {}) {
|
|
5439
5326
|
const res = ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/thread/${encodeURIComponent(parentId)}`, {
|
|
5440
5327
|
headers: { authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" }
|
|
@@ -5449,17 +5336,6 @@ async function searchThreads(q, opts = {}) {
|
|
|
5449
5336
|
if (!res.ok) await fail("search_threads", res);
|
|
5450
5337
|
return await res.json();
|
|
5451
5338
|
}
|
|
5452
|
-
async function sweepLeased(opts = {}) {
|
|
5453
|
-
const token = authToken(opts.token);
|
|
5454
|
-
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
|
|
5455
|
-
headers: {
|
|
5456
|
-
authorization: `Bearer ${token}`,
|
|
5457
|
-
"x-paigy-await-ack": "1"
|
|
5458
|
-
}
|
|
5459
|
-
}));
|
|
5460
|
-
if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
|
|
5461
|
-
return await res.json();
|
|
5462
|
-
}
|
|
5463
5339
|
var tokenOverride = null;
|
|
5464
5340
|
function overrideToken(secret) {
|
|
5465
5341
|
tokenOverride = secret;
|
|
@@ -5505,10 +5381,10 @@ async function heartbeat(runtime, opts = {}) {
|
|
|
5505
5381
|
}));
|
|
5506
5382
|
if (!res.ok) throw new Error(`heartbeat failed: ${res.status}`);
|
|
5507
5383
|
}
|
|
5508
|
-
async function registerDelivery(mode) {
|
|
5384
|
+
async function registerDelivery(mode, opts = {}) {
|
|
5509
5385
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
|
|
5510
5386
|
method: "POST",
|
|
5511
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${authToken()}` },
|
|
5387
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token)}` },
|
|
5512
5388
|
body: JSON.stringify({ mode })
|
|
5513
5389
|
}));
|
|
5514
5390
|
if (!res.ok) throw new Error(`register_delivery failed: ${res.status} ${await res.text()}`);
|
|
@@ -5544,7 +5420,7 @@ Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answe
|
|
|
5544
5420
|
};
|
|
5545
5421
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
5546
5422
|
if (opts.waits === false) return read(opts.signal);
|
|
5547
|
-
const window = AbortSignal.timeout(
|
|
5423
|
+
const window = AbortSignal.timeout(AWAIT_WINDOW_MS);
|
|
5548
5424
|
const signal = opts.signal ? AbortSignal.any([opts.signal, window]) : window;
|
|
5549
5425
|
let latest;
|
|
5550
5426
|
try {
|
|
@@ -5552,7 +5428,7 @@ Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answe
|
|
|
5552
5428
|
signal.throwIfAborted();
|
|
5553
5429
|
latest = await read(signal);
|
|
5554
5430
|
if (settled(latest)) return latest;
|
|
5555
|
-
await
|
|
5431
|
+
await sleep2(5e3, void 0, { signal });
|
|
5556
5432
|
}
|
|
5557
5433
|
} catch (error) {
|
|
5558
5434
|
opts.signal?.throwIfAborted();
|
|
@@ -5604,106 +5480,102 @@ async function runTool(name, args, opts) {
|
|
|
5604
5480
|
throw new Error(`Unknown tool: ${name}`);
|
|
5605
5481
|
}
|
|
5606
5482
|
}
|
|
5607
|
-
var
|
|
5608
|
-
var
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
);
|
|
5613
|
-
this.name = "ReplyLeaseExpiredError";
|
|
5614
|
-
}
|
|
5615
|
-
};
|
|
5616
|
-
var ReplyAckError = class extends Error {
|
|
5617
|
-
constructor(message, retryable) {
|
|
5618
|
-
super(message);
|
|
5619
|
-
this.retryable = retryable;
|
|
5620
|
-
this.name = "ReplyAckError";
|
|
5621
|
-
}
|
|
5622
|
-
retryable;
|
|
5623
|
-
};
|
|
5624
|
-
function decryptAnswer(reply, deps = {}) {
|
|
5625
|
-
if (!reply.sealed) return reply.answer;
|
|
5626
|
-
const keyFile = (deps.readKeyFile ?? readKeyFile)();
|
|
5627
|
-
if (!keyFile?.keypair) {
|
|
5628
|
-
throw new Error(
|
|
5629
|
-
"received a sealed answer but this agent has no device key to open it \u2014 re-pair."
|
|
5630
|
-
);
|
|
5631
|
-
}
|
|
5632
|
-
return openSealedAnswer(reply.sealed, keyFile.keypair);
|
|
5633
|
-
}
|
|
5634
|
-
function decodeReply(item, deps = {}) {
|
|
5635
|
-
const { delivery: _delivery, ...withoutDelivery } = item;
|
|
5636
|
-
const enriched = { ...withoutDelivery };
|
|
5637
|
-
if (!enriched.sealed) return enriched;
|
|
5638
|
-
const answer = decryptAnswer(enriched, deps);
|
|
5639
|
-
const { sealed: _sealed, ...plaintext } = enriched;
|
|
5640
|
-
return { ...plaintext, answer };
|
|
5641
|
-
}
|
|
5642
|
-
function deliveryOf(item) {
|
|
5643
|
-
if (item.type !== "reply" || !("delivery" in item) || item.delivery === void 0) {
|
|
5644
|
-
return null;
|
|
5645
|
-
}
|
|
5646
|
-
const delivery = item.delivery;
|
|
5647
|
-
if (!delivery || typeof delivery.leaseId !== "string" || !delivery.leaseId || typeof delivery.expiresAt !== "string" || !delivery.expiresAt) {
|
|
5648
|
-
throw new Error(
|
|
5649
|
-
"Paigy returned invalid reply-delivery metadata; the answer was not acknowledged and remains recoverable."
|
|
5650
|
-
);
|
|
5651
|
-
}
|
|
5652
|
-
return delivery;
|
|
5653
|
-
}
|
|
5654
|
-
async function responseDetail(res) {
|
|
5483
|
+
var HEARTBEAT_MS = 25e3;
|
|
5484
|
+
var RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
5485
|
+
var JOIN = { config: { broadcast: { self: false } } };
|
|
5486
|
+
var lines = /* @__PURE__ */ new Map();
|
|
5487
|
+
function frame(line, topic, event, payload) {
|
|
5655
5488
|
try {
|
|
5656
|
-
|
|
5489
|
+
line.ws?.send(JSON.stringify({ topic, event, payload, ref: String(++line.ref) }));
|
|
5657
5490
|
} catch {
|
|
5658
|
-
return "";
|
|
5659
5491
|
}
|
|
5660
5492
|
}
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5493
|
+
function stop(line) {
|
|
5494
|
+
line.open = false;
|
|
5495
|
+
clearInterval(line.heart);
|
|
5496
|
+
clearTimeout(line.retry);
|
|
5497
|
+
}
|
|
5498
|
+
function connect(line) {
|
|
5499
|
+
const base = line.url.replace(/^http/, "ws").replace(/\/+$/, "");
|
|
5500
|
+
const ws = line.ws = new WebSocket(`${base}/realtime/v1/websocket?apikey=${encodeURIComponent(line.anonKey)}&vsn=1.0.0`);
|
|
5501
|
+
ws.addEventListener("open", () => {
|
|
5502
|
+
line.open = true;
|
|
5503
|
+
line.attempt = 0;
|
|
5504
|
+
for (const topic of line.topics.keys()) frame(line, `realtime:${topic}`, "phx_join", JOIN);
|
|
5505
|
+
line.heart = setInterval(() => frame(line, "phoenix", "heartbeat", {}), HEARTBEAT_MS);
|
|
5506
|
+
});
|
|
5507
|
+
ws.addEventListener("message", (e) => {
|
|
5508
|
+
let m;
|
|
5667
5509
|
try {
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5510
|
+
m = JSON.parse(String(e.data));
|
|
5511
|
+
} catch {
|
|
5512
|
+
return;
|
|
5513
|
+
}
|
|
5514
|
+
if (m.event !== "broadcast" || !m.topic?.startsWith("realtime:")) return;
|
|
5515
|
+
for (const l of line.topics.get(m.topic.slice("realtime:".length)) ?? []) {
|
|
5516
|
+
if (m.payload?.event === l.event) l.onMessage(m.payload.payload);
|
|
5517
|
+
}
|
|
5518
|
+
});
|
|
5519
|
+
let gone = false;
|
|
5520
|
+
const again = () => {
|
|
5521
|
+
if (gone) return;
|
|
5522
|
+
gone = true;
|
|
5523
|
+
stop(line);
|
|
5524
|
+
if (!line.topics.size) return;
|
|
5525
|
+
line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
|
|
5526
|
+
};
|
|
5527
|
+
ws.addEventListener("close", again);
|
|
5528
|
+
ws.addEventListener("error", again);
|
|
5529
|
+
}
|
|
5530
|
+
function subscribeRealtime(args) {
|
|
5531
|
+
const key = `${args.url} ${args.anonKey}`;
|
|
5532
|
+
let line = lines.get(key);
|
|
5533
|
+
if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
|
|
5534
|
+
let heard = line.topics.get(args.topic);
|
|
5535
|
+
if (!heard) {
|
|
5536
|
+
line.topics.set(args.topic, heard = /* @__PURE__ */ new Set());
|
|
5537
|
+
if (line.open) frame(line, `realtime:${args.topic}`, "phx_join", JOIN);
|
|
5538
|
+
}
|
|
5539
|
+
const me = { event: args.event, onMessage: args.onMessage };
|
|
5540
|
+
heard.add(me);
|
|
5541
|
+
if (!line.ws) connect(line);
|
|
5542
|
+
return {
|
|
5543
|
+
close: () => {
|
|
5544
|
+
if (!heard.delete(me) || heard.size) return;
|
|
5545
|
+
line.topics.delete(args.topic);
|
|
5546
|
+
if (line.topics.size) {
|
|
5547
|
+
if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
|
|
5548
|
+
return;
|
|
5687
5549
|
}
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5550
|
+
lines.delete(key);
|
|
5551
|
+
stop(line);
|
|
5552
|
+
try {
|
|
5553
|
+
line.ws?.close();
|
|
5554
|
+
} catch {
|
|
5691
5555
|
}
|
|
5692
5556
|
}
|
|
5693
|
-
}
|
|
5694
|
-
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
5695
|
-
throw new Error(
|
|
5696
|
-
`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.`
|
|
5697
|
-
);
|
|
5557
|
+
};
|
|
5698
5558
|
}
|
|
5699
|
-
async function
|
|
5700
|
-
const
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5559
|
+
async function subscribeWake(onNudge, opts = {}) {
|
|
5560
|
+
const cfg = await registerDelivery("self_hosted", opts);
|
|
5561
|
+
const { url, anonKey } = cfg.realtime;
|
|
5562
|
+
const channel = wakeChannel(cfg.tokenId);
|
|
5563
|
+
const ch = subscribeRealtime({
|
|
5564
|
+
url,
|
|
5565
|
+
anonKey,
|
|
5566
|
+
topic: channel,
|
|
5567
|
+
event: WAKE_EVENT,
|
|
5568
|
+
onMessage: (payload) => onNudge(payload ?? {})
|
|
5706
5569
|
});
|
|
5570
|
+
return {
|
|
5571
|
+
tokenId: cfg.tokenId,
|
|
5572
|
+
channel,
|
|
5573
|
+
close: async () => {
|
|
5574
|
+
ch.close();
|
|
5575
|
+
await registerDelivery("poll", opts).catch(() => {
|
|
5576
|
+
});
|
|
5577
|
+
}
|
|
5578
|
+
};
|
|
5707
5579
|
}
|
|
5708
5580
|
|
|
5709
5581
|
export {
|
|
@@ -5729,15 +5601,13 @@ export {
|
|
|
5729
5601
|
fetchCredential,
|
|
5730
5602
|
pairStep,
|
|
5731
5603
|
UnpairedError,
|
|
5732
|
-
|
|
5604
|
+
getGoal,
|
|
5733
5605
|
overrideToken,
|
|
5734
|
-
authToken,
|
|
5735
5606
|
hatch,
|
|
5736
5607
|
whoAmI,
|
|
5737
5608
|
setIdentity,
|
|
5738
5609
|
heartbeat,
|
|
5739
|
-
|
|
5610
|
+
checkReplies,
|
|
5740
5611
|
runTool,
|
|
5741
|
-
|
|
5742
|
-
ackReply
|
|
5612
|
+
subscribeWake
|
|
5743
5613
|
};
|