@paigy/mcp 0.40.6 → 0.40.8
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 +6 -8
- package/dist/{chunk-MD37G3TI.js → chunk-BT5S7VT2.js} +151 -40
- package/dist/{chunk-7VMR7ONL.js → chunk-DRRAJMEE.js} +1 -1
- package/dist/{chunk-QINMT6C2.js → chunk-GQ5J6WW5.js} +2 -2
- package/dist/{chunk-Z6S4THWU.js → chunk-XOL6UX6P.js} +26 -26
- package/dist/enable.js +3 -3
- package/dist/index.js +4 -4
- package/dist/listen.js +3 -3
- package/dist/onboard.js +4 -4
- package/dist/slot.js +1 -1
- package/dist/statusline.js +5 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,11 +205,11 @@ codex exec "Paigy goal $PAIGY_GOAL_ID on thread $PAIGY_THREAD_ID. claim_goal it
|
|
|
205
205
|
|
|
206
206
|
## Statusline (Claude Code)
|
|
207
207
|
|
|
208
|
-
`paigy-statusline` prints
|
|
209
|
-
|
|
208
|
+
`paigy-statusline` prints which Paigy identity this Claude Code session is, for the
|
|
209
|
+
status bar:
|
|
210
210
|
|
|
211
211
|
```
|
|
212
|
-
paigy:
|
|
212
|
+
paigy: Marlow
|
|
213
213
|
```
|
|
214
214
|
|
|
215
215
|
Enable it in `~/.claude/settings.json`:
|
|
@@ -218,11 +218,9 @@ Enable it in `~/.claude/settings.json`:
|
|
|
218
218
|
"statusLine": { "type": "command", "command": "npx -y -p @paigy/mcp@latest paigy-statusline" }
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
It reads `
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
means there's no token (or the pairing was revoked) — run `/paigy-onboard` to
|
|
225
|
-
pair; `paigy: …` means the status isn't known yet (first run while offline).
|
|
221
|
+
It reads only `~/.paigy` — no network. `paigy: paired · session hatches on first use` means the
|
|
222
|
+
machine is paired and this session's identity is minted by its first tool call;
|
|
223
|
+
`paigy: unpaired` means there's no token — run `/paigy-onboard` to pair.
|
|
226
224
|
|
|
227
225
|
## Publishing (maintainers)
|
|
228
226
|
|
|
@@ -2298,11 +2298,14 @@ function isNetworkError(e) {
|
|
|
2298
2298
|
return e instanceof Error && e.message.startsWith(NETWORK_MSG);
|
|
2299
2299
|
}
|
|
2300
2300
|
async function reach(url, init) {
|
|
2301
|
+
return send(url, init, SESSION_ID);
|
|
2302
|
+
}
|
|
2303
|
+
async function send(url, init, session) {
|
|
2301
2304
|
try {
|
|
2302
2305
|
const headers = {
|
|
2303
2306
|
...init?.headers,
|
|
2304
2307
|
"x-paigy-instance": INSTANCE_ID,
|
|
2305
|
-
"x-paigy-session":
|
|
2308
|
+
"x-paigy-session": session
|
|
2306
2309
|
};
|
|
2307
2310
|
return await fetch(url, { ...init, headers, dispatcher: await proxy() });
|
|
2308
2311
|
} catch (e) {
|
|
@@ -3578,6 +3581,9 @@ var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
|
3578
3581
|
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.";
|
|
3579
3582
|
var CreateGoalSchema = z2.object({
|
|
3580
3583
|
outcome: z2.string().trim().min(1).max(1e4),
|
|
3584
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
3585
|
+
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
3586
|
+
title: z2.string().trim().min(1).max(80).optional(),
|
|
3581
3587
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
3582
3588
|
idempotencyKey: z2.string().trim().min(1).max(200),
|
|
3583
3589
|
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
@@ -3597,11 +3603,14 @@ var CreateGoalSchema = z2.object({
|
|
|
3597
3603
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
3598
3604
|
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
3599
3605
|
}).strict();
|
|
3600
|
-
var CREATE_GOAL_DESCRIPTION =
|
|
3606
|
+
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. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. 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.';
|
|
3601
3607
|
var UpdateGoalSchema = z2.object({
|
|
3602
3608
|
revision: z2.number().int().positive(),
|
|
3603
3609
|
changes: z2.object({
|
|
3604
3610
|
outcome: z2.string().trim().min(1).max(1e4).optional(),
|
|
3611
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
3612
|
+
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
3613
|
+
title: z2.string().trim().min(1).max(80).nullable().optional(),
|
|
3605
3614
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
3606
3615
|
parentGoalId: z2.string().uuid().nullable().optional(),
|
|
3607
3616
|
dependencies: z2.array(z2.object({ goalId: z2.string().uuid(), gate: z2.enum(["start", "finish"]) }).strict()).optional(),
|
|
@@ -3617,10 +3626,10 @@ var UpdateGoalSchema = z2.object({
|
|
|
3617
3626
|
var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z2.string().uuid() }).strict();
|
|
3618
3627
|
var ClaimGoalSchema = z2.object({ goalId: z2.string().uuid().optional() }).strict();
|
|
3619
3628
|
var GetGoalSchema = z2.object({ goalId: z2.string().uuid() }).strict();
|
|
3620
|
-
var GET_GOAL_DESCRIPTION = "Read
|
|
3621
|
-
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. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the
|
|
3622
|
-
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns
|
|
3623
|
-
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 one row each: its Goals,
|
|
3629
|
+
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
|
|
3630
|
+
var UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the Goal as get_goal reads it, at its new revision.";
|
|
3631
|
+
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
3632
|
+
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 one row each: its Goals, how many decisions are still open, and the newest words in brief. 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 in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
|
|
3624
3633
|
var CheckRepliesSchema = z2.object({}).strict();
|
|
3625
3634
|
var GetThreadSchema = z2.object({
|
|
3626
3635
|
parentId: z2.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
@@ -3641,6 +3650,22 @@ var AGENT_TOOLS = [
|
|
|
3641
3650
|
{ name: "update_goal", description: UPDATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(UpdateGoalToolSchema) }
|
|
3642
3651
|
];
|
|
3643
3652
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
3653
|
+
function entryWords(entry) {
|
|
3654
|
+
const content = entry.content;
|
|
3655
|
+
if (content && "sealed" in content) return "";
|
|
3656
|
+
const plain = content?.plain;
|
|
3657
|
+
if (typeof plain === "string") return plain;
|
|
3658
|
+
if (plain && typeof plain === "object") {
|
|
3659
|
+
const text = plain.text;
|
|
3660
|
+
if (typeof text === "string") return text;
|
|
3661
|
+
const title = plain.title;
|
|
3662
|
+
const description = plain.description;
|
|
3663
|
+
const parts = [title, ...Array.isArray(description) ? description : []].filter((v) => typeof v === "string");
|
|
3664
|
+
if (parts.length) return parts.join("\n\n");
|
|
3665
|
+
}
|
|
3666
|
+
return entry.sources.map((source) => source.text).join("\n");
|
|
3667
|
+
}
|
|
3668
|
+
var LIVE_MS = 3 * 6e4;
|
|
3644
3669
|
var ContextSchema = z3.object({
|
|
3645
3670
|
title: z3.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
3646
3671
|
description: z3.array(z3.string().min(1)).describe(
|
|
@@ -4477,7 +4502,19 @@ var QueueQuestionSchema = z3.object({
|
|
|
4477
4502
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
4478
4503
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
4479
4504
|
* for a settled question whose reply carried nothing readable. */
|
|
4480
|
-
answer: z3.string().nullable().default(null)
|
|
4505
|
+
answer: z3.string().nullable().default(null),
|
|
4506
|
+
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
4507
|
+
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
4508
|
+
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
4509
|
+
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
4510
|
+
* this is the field they agree through rather than each re-deriving it from the row it
|
|
4511
|
+
* arrived under. Required because the API projects it on every need it sends. */
|
|
4512
|
+
goalId: z3.string(),
|
|
4513
|
+
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
4514
|
+
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
4515
|
+
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
4516
|
+
* gate at all. */
|
|
4517
|
+
blocked: z3.boolean().default(false)
|
|
4481
4518
|
});
|
|
4482
4519
|
var QueueItemSchema = z3.object({
|
|
4483
4520
|
id: z3.string(),
|
|
@@ -4616,25 +4653,6 @@ var DeliveryConfigSchema = z3.object({
|
|
|
4616
4653
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
4617
4654
|
realtime: z3.object({ url: z3.string(), anonKey: z3.string() }).nullable()
|
|
4618
4655
|
});
|
|
4619
|
-
var StatusSchema = z3.object({
|
|
4620
|
-
name: z3.string(),
|
|
4621
|
-
sessionMode: z3.enum(["default", "all_calls", "silent"]),
|
|
4622
|
-
/** A phone is registered for push/ring (any push token on the account). */
|
|
4623
|
-
phone: z3.boolean(),
|
|
4624
|
-
/** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
|
|
4625
|
-
* it never picked up. THE SAME NUMBER the harness's wake gate reads
|
|
4626
|
-
* (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
|
|
4627
|
-
* zero while the sweep sees one is two ideas of "waiting".
|
|
4628
|
-
*
|
|
4629
|
-
* Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
|
|
4630
|
-
* that nobody spawned, so a terminal session only learns of work by asking. The harness
|
|
4631
|
-
* used to paper over that by spawning a SECOND process on the identity; now it stands
|
|
4632
|
-
* back, correctly, and the person sitting at the terminal is the one who can act. A
|
|
4633
|
-
* coffee-beans request sat unread for three days.
|
|
4634
|
-
*
|
|
4635
|
-
* Optional: an older API sends no field, and the statusline then renders exactly as before. */
|
|
4636
|
-
waiting: z3.number().int().nonnegative().optional()
|
|
4637
|
-
});
|
|
4638
4656
|
var EnvelopeRecipientSchema = z3.object({
|
|
4639
4657
|
keyId: z3.string(),
|
|
4640
4658
|
epk: z3.string(),
|
|
@@ -5251,14 +5269,14 @@ async function registerDelivery(mode, opts = {}) {
|
|
|
5251
5269
|
async function contact(input, opts = {}) {
|
|
5252
5270
|
const parsed = ContactSchema.parse(input);
|
|
5253
5271
|
opts.signal?.throwIfAborted();
|
|
5254
|
-
const
|
|
5272
|
+
const send2 = opts.reach ?? reach;
|
|
5255
5273
|
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
5256
5274
|
let deliveryId;
|
|
5257
5275
|
if ("deliveryId" in parsed) deliveryId = parsed.deliveryId;
|
|
5258
5276
|
else {
|
|
5259
5277
|
if (!opts.reach && readKeyFile()?.e2ee) throw new Error("Goal contact does not support E2EE yet; refusing to send plaintext.");
|
|
5260
5278
|
const shaped = deriveAsk(NotifyRequestSchema.parse({ ask: parsed.ask, waiting: parsed.waiting, options: parsed.options }));
|
|
5261
|
-
const res = ensureAuthed(await
|
|
5279
|
+
const res = ensureAuthed(await send2(`${BACKEND_URL}/api/goals/${parsed.goalIds[0]}/contact`, {
|
|
5262
5280
|
method: "POST",
|
|
5263
5281
|
headers,
|
|
5264
5282
|
signal: opts.signal,
|
|
@@ -5270,11 +5288,9 @@ async function contact(input, opts = {}) {
|
|
|
5270
5288
|
if (!deliveryId) throw new Error("Goal contact returned no Delivery identity");
|
|
5271
5289
|
}
|
|
5272
5290
|
const read = async (signal2) => {
|
|
5273
|
-
const res = ensureAuthed(await
|
|
5291
|
+
const res = ensureAuthed(await send2(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
5274
5292
|
if (!res.ok) await fail("read_delivery", res);
|
|
5275
|
-
|
|
5276
|
-
return delivery.kind === "notification" ? { ...delivery, message: `${delivery.message}
|
|
5277
|
-
Inbox delivery is asynchronous; use claim_goal/get_goal to collect durable answers. Do not poll this Notification.` } : delivery;
|
|
5293
|
+
return await res.json();
|
|
5278
5294
|
};
|
|
5279
5295
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
5280
5296
|
if (opts.waits === false) return read(opts.signal);
|
|
@@ -5301,18 +5317,115 @@ async function checkReplies(opts = {}) {
|
|
|
5301
5317
|
if (!res.ok) await fail("check_replies", res);
|
|
5302
5318
|
return await res.json();
|
|
5303
5319
|
}
|
|
5320
|
+
function compact(o) {
|
|
5321
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0)));
|
|
5322
|
+
}
|
|
5323
|
+
var who = (participant) => participant.startsWith("human:") ? "person" : participant.startsWith("agent:") ? "agent" : participant;
|
|
5324
|
+
var at = (iso) => {
|
|
5325
|
+
const t = Date.parse(iso);
|
|
5326
|
+
return Number.isFinite(t) ? new Date(t).toISOString().replace(/\.\d{3}Z$/, "Z") : iso;
|
|
5327
|
+
};
|
|
5328
|
+
function decided(result) {
|
|
5329
|
+
if ("sealed" in result) return "[encrypted]";
|
|
5330
|
+
switch (result.kind) {
|
|
5331
|
+
case "confirm":
|
|
5332
|
+
return result.approved ? "yes" : "no";
|
|
5333
|
+
case "one":
|
|
5334
|
+
return result.option.label;
|
|
5335
|
+
case "many":
|
|
5336
|
+
return result.options.map((o) => o.label).join(", ");
|
|
5337
|
+
case "rank":
|
|
5338
|
+
return result.options.map((o) => o.label).join(" > ");
|
|
5339
|
+
default:
|
|
5340
|
+
return void 0;
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
function conversation(e) {
|
|
5344
|
+
const answers = new Map((e.answers ?? []).map((a) => [a.decisionNeedId, a]));
|
|
5345
|
+
return (e.entries ?? []).map((entry) => {
|
|
5346
|
+
const sealed = !!entry.content && "sealed" in entry.content;
|
|
5347
|
+
const plain = entry.content && "plain" in entry.content ? entry.content.plain : null;
|
|
5348
|
+
const options = plain && typeof plain === "object" && Array.isArray(plain.options) ? plain.options.map((o) => o.label).filter((l) => typeof l === "string") : [];
|
|
5349
|
+
const need = (e.decisionNeeds ?? []).find((n) => n.requestEntryId === entry.entryId);
|
|
5350
|
+
const answer = need ? answers.get(need.decisionNeedId) : void 0;
|
|
5351
|
+
const answered = answer ? decided(answer.result) : void 0;
|
|
5352
|
+
return compact({
|
|
5353
|
+
id: entry.entryId,
|
|
5354
|
+
from: who(entry.authorParticipant),
|
|
5355
|
+
at: at(entry.createdAt),
|
|
5356
|
+
said: sealed ? "[encrypted]" : entryWords(entry).trim(),
|
|
5357
|
+
re: entry.aboutId ?? void 0,
|
|
5358
|
+
options,
|
|
5359
|
+
decision: need ? compact({ id: need.decisionNeedId, state: need.state, answer: answered }) : void 0
|
|
5360
|
+
});
|
|
5361
|
+
});
|
|
5362
|
+
}
|
|
5363
|
+
function goalView(g) {
|
|
5364
|
+
if (!g.goalId) return compact({ state: g.state, next: g.message });
|
|
5365
|
+
return compact({
|
|
5366
|
+
goalId: g.goalId,
|
|
5367
|
+
revision: g.revision,
|
|
5368
|
+
state: g.state,
|
|
5369
|
+
outcome: g.outcome,
|
|
5370
|
+
progress: g.progress,
|
|
5371
|
+
reviewPending: g.reviewPending || void 0,
|
|
5372
|
+
owner: g.ownerParticipant,
|
|
5373
|
+
parentGoalId: g.parentGoalId,
|
|
5374
|
+
childGoalIds: g.childGoalIds,
|
|
5375
|
+
blockedBy: g.blockedDependencyGoalIds,
|
|
5376
|
+
dueAt: g.dueAt,
|
|
5377
|
+
leaseExpiresAt: g.leaseExpiresAt,
|
|
5378
|
+
contextThreadId: g.contextThreadId,
|
|
5379
|
+
conversation: conversation(g),
|
|
5380
|
+
next: g.message
|
|
5381
|
+
});
|
|
5382
|
+
}
|
|
5383
|
+
function deliveryView(d) {
|
|
5384
|
+
return compact({
|
|
5385
|
+
deliveryId: d.deliveryId,
|
|
5386
|
+
kind: d.kind,
|
|
5387
|
+
state: d.state,
|
|
5388
|
+
goalIds: d.goalIds,
|
|
5389
|
+
threadId: d.threadId,
|
|
5390
|
+
conversation: conversation(d),
|
|
5391
|
+
next: d.kind === "notification" ? "Answers land on the Goal: read them with claim_goal or get_goal. Do not poll this notification." : d.message
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
function threadView(t) {
|
|
5395
|
+
return {
|
|
5396
|
+
parentId: t.parentId,
|
|
5397
|
+
turns: (t.turns ?? []).map((turn) => {
|
|
5398
|
+
const lines2 = turn.role === "user" ? [turn.text ?? ""] : [turn.title ?? "", ...turn.description ?? []];
|
|
5399
|
+
const said = lines2.map((l) => l.trim()).filter((l, i, all) => l && all.indexOf(l) === i).join("\n");
|
|
5400
|
+
return compact({ from: turn.role === "user" ? "person" : "agent", at: turn.at ? at(turn.at) : void 0, said });
|
|
5401
|
+
})
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
function repliesView(r) {
|
|
5405
|
+
const deliveries = r.deliveries ?? [];
|
|
5406
|
+
return {
|
|
5407
|
+
deliveries: deliveries.map((d) => compact({
|
|
5408
|
+
deliveryId: d.deliveryId,
|
|
5409
|
+
kind: d.kind,
|
|
5410
|
+
goalIds: d.goalIds,
|
|
5411
|
+
openDecisions: d.decisionNeeds.filter((n) => n.state === "open").length || void 0,
|
|
5412
|
+
latest: "about" in d ? d.about : void 0
|
|
5413
|
+
})),
|
|
5414
|
+
next: deliveries.length ? "Act on a Goal with claim_goal; reread one Delivery in full with contact({deliveryId})." : "Nothing is waiting."
|
|
5415
|
+
};
|
|
5416
|
+
}
|
|
5304
5417
|
async function runTool(name, args, opts) {
|
|
5305
5418
|
const { waits, signal, ...client } = opts;
|
|
5306
5419
|
const input = args ?? {};
|
|
5307
5420
|
switch (name) {
|
|
5308
5421
|
case "contact":
|
|
5309
|
-
return contact(input, { ...client, signal, waits });
|
|
5422
|
+
return deliveryView(await contact(input, { ...client, signal, waits }));
|
|
5310
5423
|
case "check_replies":
|
|
5311
5424
|
CheckRepliesSchema.parse(input);
|
|
5312
|
-
return checkReplies(client);
|
|
5425
|
+
return repliesView(await checkReplies(client));
|
|
5313
5426
|
case "get_thread": {
|
|
5314
5427
|
const { parentId } = GetThreadSchema.parse(input);
|
|
5315
|
-
return getThread(parentId, client);
|
|
5428
|
+
return threadView(await getThread(parentId, client));
|
|
5316
5429
|
}
|
|
5317
5430
|
case "search_threads": {
|
|
5318
5431
|
const { q } = SearchThreadsSchema.parse(input);
|
|
@@ -5324,15 +5437,15 @@ async function runTool(name, args, opts) {
|
|
|
5324
5437
|
}
|
|
5325
5438
|
case "claim_goal": {
|
|
5326
5439
|
const { goalId } = ClaimGoalSchema.parse(input);
|
|
5327
|
-
return claimGoal(goalId, client);
|
|
5440
|
+
return goalView(await claimGoal(goalId, client));
|
|
5328
5441
|
}
|
|
5329
5442
|
case "get_goal": {
|
|
5330
5443
|
const { goalId } = GetGoalSchema.parse(input);
|
|
5331
|
-
return getGoal(goalId, client);
|
|
5444
|
+
return goalView(await getGoal(goalId, client));
|
|
5332
5445
|
}
|
|
5333
5446
|
case "update_goal": {
|
|
5334
5447
|
const { goalId, ...body } = UpdateGoalToolSchema.parse(input);
|
|
5335
|
-
return updateGoal(goalId, body, client);
|
|
5448
|
+
return goalView(await updateGoal(goalId, body, client));
|
|
5336
5449
|
}
|
|
5337
5450
|
default:
|
|
5338
5451
|
throw new Error(`Unknown tool: ${name}`);
|
|
@@ -5437,9 +5550,7 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
5437
5550
|
}
|
|
5438
5551
|
|
|
5439
5552
|
export {
|
|
5440
|
-
BACKEND_URL,
|
|
5441
5553
|
isNetworkError,
|
|
5442
|
-
reach,
|
|
5443
5554
|
sessionSlot,
|
|
5444
5555
|
AGENT_NAME,
|
|
5445
5556
|
TOKEN_PATH,
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
AGENT_TOOLS,
|
|
3
3
|
mcpInputSchema,
|
|
4
4
|
serverInstructions
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XOL6UX6P.js";
|
|
6
6
|
import {
|
|
7
7
|
AGENT_NAME
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BT5S7VT2.js";
|
|
9
9
|
|
|
10
10
|
// src/toolset.ts
|
|
11
11
|
var ONBOARD_DESCRIPTION = "Get this agent talking to Paigy \u2014 call it FIRST, before contact, and any time you're unsure who you are. One call, and it does whatever the situation needs: NOT SET UP \u2192 hatches an identity instantly if this machine holds a device credential (the user ran the Paigy desktop app or harness), otherwise starts the code ceremony; ALREADY SET UP \u2192 returns your current identity and offers the two things left to decide, renaming it or unpairing; TOKEN NO LONGER VALID \u2192 says so, then re-pairs. Pass { name, voice } to choose who you are when hatching, or to RENAME yourself when already set up (voices: rachel, george, jessica, brian, lily). Safe to call any time: idempotent, and it never writes settings \u2014 the tool-allowlist state it reports is read-only. If it returns a `user_code`, print it to the user immediately and call onboard again with the `device_code`. If it returns `enable_command`, PRINT that command for the user to run \u2014 you cannot apply it yourself (it writes your own permission allowlist, which hosts block as privilege escalation), so print it, don't wait for it, and carry on.";
|
|
@@ -62,6 +62,9 @@ var CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
|
62
62
|
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.";
|
|
63
63
|
var CreateGoalSchema = z2.object({
|
|
64
64
|
outcome: z2.string().trim().min(1).max(1e4),
|
|
65
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
66
|
+
* rings"). Omit it and the brain writes one at admission from the outcome. */
|
|
67
|
+
title: z2.string().trim().min(1).max(80).optional(),
|
|
65
68
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
66
69
|
idempotencyKey: z2.string().trim().min(1).max(200),
|
|
67
70
|
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
@@ -81,11 +84,14 @@ var CreateGoalSchema = z2.object({
|
|
|
81
84
|
var CreateGoalToolSchema = CreateGoalSchema.extend({
|
|
82
85
|
idempotencyKey: CreateGoalSchema.shape.idempotencyKey.optional().describe("Optional. One is minted per call; pass your own only so a retry lands on the same Goal.")
|
|
83
86
|
}).strict();
|
|
84
|
-
var CREATE_GOAL_DESCRIPTION =
|
|
87
|
+
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. Pass title to name it in one to five words, as a person would refer to it out loud ("the night rings") \u2014 it heads every list and is spoken on a call; without one the brain writes it. 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.';
|
|
85
88
|
var UpdateGoalSchema = z2.object({
|
|
86
89
|
revision: z2.number().int().positive(),
|
|
87
90
|
changes: z2.object({
|
|
88
91
|
outcome: z2.string().trim().min(1).max(1e4).optional(),
|
|
92
|
+
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud. The
|
|
93
|
+
* brain writes one at admission; this is the owner saying it better. Null clears it. */
|
|
94
|
+
title: z2.string().trim().min(1).max(80).nullable().optional(),
|
|
89
95
|
ownerParticipant: z2.string().trim().min(1).optional(),
|
|
90
96
|
parentGoalId: z2.string().uuid().nullable().optional(),
|
|
91
97
|
dependencies: z2.array(z2.object({ goalId: z2.string().uuid(), gate: z2.enum(["start", "finish"]) }).strict()).optional(),
|
|
@@ -101,10 +107,10 @@ var UpdateGoalSchema = z2.object({
|
|
|
101
107
|
var UpdateGoalToolSchema = UpdateGoalSchema.omit({ operationId: true }).extend({ goalId: z2.string().uuid() }).strict();
|
|
102
108
|
var ClaimGoalSchema = z2.object({ goalId: z2.string().uuid().optional() }).strict();
|
|
103
109
|
var GetGoalSchema = z2.object({ goalId: z2.string().uuid() }).strict();
|
|
104
|
-
var GET_GOAL_DESCRIPTION = "Read
|
|
105
|
-
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. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the
|
|
106
|
-
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns
|
|
107
|
-
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 one row each: its Goals,
|
|
110
|
+
var GET_GOAL_DESCRIPTION = "Read one Goal without claiming it: its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), and `next`, the one step to take. Foreign or sibling-owned Goals are not disclosed.";
|
|
111
|
+
var UPDATE_GOAL_DESCRIPTION = "Update an owned Goal at an exact revision. State, ownership, dependencies, children, progress, title, and review acknowledgement are explicit; stale revisions are rejected. title is the work's name in one to five words, as a person would refer to it out loud (it is spoken on a call and heads every list); null clears it. reviewed: true acknowledges new evidence and closes the Deliveries addressed to you on that Goal, never over an open decision. dueAt (an ISO instant, or null) makes the Goal wait until then; when it passes you are woken for it \u2014 use it for a promise to follow up later. Returns the Goal as get_goal reads it, at its new revision.";
|
|
112
|
+
var CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
113
|
+
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 one row each: its Goals, how many decisions are still open, and the newest words in brief. 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 in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true closes the Deliveries addressed to you on that Goal. Your runnable and review-pending Goals come from claim_goal, not from here.";
|
|
108
114
|
var CheckRepliesSchema = z2.object({}).strict();
|
|
109
115
|
var GetThreadSchema = z2.object({
|
|
110
116
|
parentId: z2.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
@@ -126,7 +132,7 @@ var AGENT_TOOLS = [
|
|
|
126
132
|
];
|
|
127
133
|
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
128
134
|
function serverInstructions(_opts) {
|
|
129
|
-
return "On startup and after a wake, call check_replies for the open Deliveries addressed to you, and claim_goal for your runnable or review-pending Goal and
|
|
135
|
+
return "On startup and after a wake, call check_replies for the open Deliveries addressed to you, and claim_goal for your runnable or review-pending Goal and the conversation on it. get_goal rereads it without claiming. Every read ends in `next`, the one step to take. Contact only for an existing Goal. Notifications return immediately: keep working and collect answers through claim_goal/get_goal. On stdio, Calls hold one bounded window; on hosted MCP, Calls return after one read; continue with contact({deliveryId}) to reread that exact Delivery. Evidence can repeat: reads do not acknowledge or hide it. Use update_goal to report progress and explicitly acknowledge review. Never infer ringing from an open Call Delivery. Soft waiting, multipart contact, and re-presentation are unsupported. In an interactive session, ask the user directly; use Paigy when they asked for it or are away.";
|
|
130
136
|
}
|
|
131
137
|
function entryWords(entry) {
|
|
132
138
|
const content = entry.content;
|
|
@@ -143,6 +149,7 @@ function entryWords(entry) {
|
|
|
143
149
|
}
|
|
144
150
|
return entry.sources.map((source) => source.text).join("\n");
|
|
145
151
|
}
|
|
152
|
+
var LIVE_MS = 3 * 6e4;
|
|
146
153
|
var ContextSchema = z3.object({
|
|
147
154
|
title: z3.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
148
155
|
description: z3.array(z3.string().min(1)).describe(
|
|
@@ -924,7 +931,19 @@ var QueueQuestionSchema = z3.object({
|
|
|
924
931
|
/** The ruling in the person's own words, from the contribution that replied — not the
|
|
925
932
|
* option id, which is not something anyone reads back. Null while it is open, and null
|
|
926
933
|
* for a settled question whose reply carried nothing readable. */
|
|
927
|
-
answer: z3.string().nullable().default(null)
|
|
934
|
+
answer: z3.string().nullable().default(null),
|
|
935
|
+
/** The Goal this question belongs to — a step knows its Goal on its own, not only through
|
|
936
|
+
* an `InboxItem`'s `communication.goalIds[0]` (walk/design.md §12 item 3).
|
|
937
|
+
* READ BY `apps/client/src/walk/order.ts`, which stamps it onto every `WalkStep`: the walk's
|
|
938
|
+
* order, its route, home's trees and the list of steps all take a step's Goal from here, so
|
|
939
|
+
* this is the field they agree through rather than each re-deriving it from the row it
|
|
940
|
+
* arrived under. Required because the API projects it on every need it sends. */
|
|
941
|
+
goalId: z3.string(),
|
|
942
|
+
/** True only while an unmet START gate holds the Goal — a Goal that merely waits to
|
|
943
|
+
* *finish* does not stop a person from answering (owner, 2026-09-16: "per need gate from
|
|
944
|
+
* the API"; §4's dashed node). Not the same fact as `QueueItem.blocked`, which counts any
|
|
945
|
+
* gate at all. */
|
|
946
|
+
blocked: z3.boolean().default(false)
|
|
928
947
|
});
|
|
929
948
|
var QueueItemSchema = z3.object({
|
|
930
949
|
id: z3.string(),
|
|
@@ -1061,25 +1080,6 @@ var DeliveryConfigSchema = z3.object({
|
|
|
1061
1080
|
* carries credentials; only a `poll` registration can come back with null here. */
|
|
1062
1081
|
realtime: z3.object({ url: z3.string(), anonKey: z3.string() }).nullable()
|
|
1063
1082
|
});
|
|
1064
|
-
var StatusSchema = z3.object({
|
|
1065
|
-
name: z3.string(),
|
|
1066
|
-
sessionMode: z3.enum(["default", "all_calls", "silent"]),
|
|
1067
|
-
/** A phone is registered for push/ring (any push token on the account). */
|
|
1068
|
-
phone: z3.boolean(),
|
|
1069
|
-
/** HOW MANY THINGS ARE WAITING ON THIS IDENTITY — replies it never collected and requests
|
|
1070
|
-
* it never picked up. THE SAME NUMBER the harness's wake gate reads
|
|
1071
|
-
* (`pendingSummary.unacknowledged`), from the same function, because a statusline saying
|
|
1072
|
-
* zero while the sweep sees one is two ideas of "waiting".
|
|
1073
|
-
*
|
|
1074
|
-
* Why it is here at all (owner, 2026-09-07): nothing can interrupt an idle agent process
|
|
1075
|
-
* that nobody spawned, so a terminal session only learns of work by asking. The harness
|
|
1076
|
-
* used to paper over that by spawning a SECOND process on the identity; now it stands
|
|
1077
|
-
* back, correctly, and the person sitting at the terminal is the one who can act. A
|
|
1078
|
-
* coffee-beans request sat unread for three days.
|
|
1079
|
-
*
|
|
1080
|
-
* Optional: an older API sends no field, and the statusline then renders exactly as before. */
|
|
1081
|
-
waiting: z3.number().int().nonnegative().optional()
|
|
1082
|
-
});
|
|
1083
1083
|
var EnvelopeRecipientSchema = z3.object({
|
|
1084
1084
|
keyId: z3.string(),
|
|
1085
1085
|
epk: z3.string(),
|
package/dist/enable.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PAIGY_TOOL_IDS,
|
|
4
4
|
enablePaigyTools
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-GQ5J6WW5.js";
|
|
6
|
+
import "./chunk-XOL6UX6P.js";
|
|
7
|
+
import "./chunk-BT5S7VT2.js";
|
|
8
8
|
|
|
9
9
|
// src/enable.ts
|
|
10
10
|
function main() {
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolvePairing,
|
|
6
6
|
startPairing,
|
|
7
7
|
suggestedAgentName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DRRAJMEE.js";
|
|
9
9
|
import {
|
|
10
10
|
ENABLE_COMMAND,
|
|
11
11
|
OnboardSchema,
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
autoConfigureClients,
|
|
17
17
|
claudeInstallHint,
|
|
18
18
|
paigyToolsAllowlisted
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-GQ5J6WW5.js";
|
|
20
|
+
import "./chunk-XOL6UX6P.js";
|
|
21
21
|
import {
|
|
22
22
|
clearSurface,
|
|
23
23
|
writeSurface
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
setIdentity,
|
|
39
39
|
slotName,
|
|
40
40
|
whoAmI
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-BT5S7VT2.js";
|
|
42
42
|
|
|
43
43
|
// src/index.ts
|
|
44
44
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
package/dist/listen.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
entryWords
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XOL6UX6P.js";
|
|
5
5
|
import {
|
|
6
6
|
checkReplies,
|
|
7
7
|
contact,
|
|
8
8
|
getGoal,
|
|
9
9
|
subscribeWake
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BT5S7VT2.js";
|
|
11
11
|
|
|
12
12
|
// src/listen.ts
|
|
13
13
|
import { spawn } from "child_process";
|
|
@@ -129,7 +129,7 @@ function launchEnv(waiting, reason, goalId) {
|
|
|
129
129
|
put("PAIGY_DELIVERY_ID", lead.deliveryId);
|
|
130
130
|
put("PAIGY_GOAL_ID", waiting.goal?.goalId ?? lead.goalIds[0]);
|
|
131
131
|
put("PAIGY_CONTEXT_THREAD_ID", waiting.goal?.contextThreadId);
|
|
132
|
-
put("PAIGY_TEXT", saidIn(lead) || lead.
|
|
132
|
+
put("PAIGY_TEXT", saidIn(lead) || ("about" in lead ? lead.about : ""));
|
|
133
133
|
return env;
|
|
134
134
|
}
|
|
135
135
|
async function sweep(reason, goalId) {
|
package/dist/onboard.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
resolvePairing,
|
|
4
4
|
startPairing,
|
|
5
5
|
suggestedAgentName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DRRAJMEE.js";
|
|
7
7
|
import {
|
|
8
8
|
autoConfigureClients,
|
|
9
9
|
claudeInstallHint,
|
|
10
10
|
openBrowser
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-GQ5J6WW5.js";
|
|
12
|
+
import "./chunk-XOL6UX6P.js";
|
|
13
13
|
import {
|
|
14
14
|
AGENT_NAME,
|
|
15
15
|
TOKEN_PATH,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
saveToken,
|
|
21
21
|
setIdentity,
|
|
22
22
|
whoAmI
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-BT5S7VT2.js";
|
|
24
24
|
|
|
25
25
|
// src/onboard.ts
|
|
26
26
|
function reportRegistered() {
|
package/dist/slot.js
CHANGED
package/dist/statusline.js
CHANGED
|
@@ -3,27 +3,15 @@ import {
|
|
|
3
3
|
readSurface
|
|
4
4
|
} from "./chunk-3SVNPJPN.js";
|
|
5
5
|
import {
|
|
6
|
-
BACKEND_URL,
|
|
7
6
|
listSlots,
|
|
8
|
-
reach,
|
|
9
7
|
readToken,
|
|
10
8
|
sessionSlot,
|
|
11
9
|
slotName
|
|
12
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BT5S7VT2.js";
|
|
13
11
|
|
|
14
12
|
// src/statusline.ts
|
|
15
|
-
import {
|
|
16
|
-
import { homedir } from "os";
|
|
17
|
-
import { join } from "path";
|
|
13
|
+
import { realpathSync } from "fs";
|
|
18
14
|
import { fileURLToPath } from "url";
|
|
19
|
-
var DIR = join(homedir(), ".paigy");
|
|
20
|
-
var CACHE = join(DIR, "status.json");
|
|
21
|
-
var TTL_MS = 6e4;
|
|
22
|
-
var MODE_LABEL = {
|
|
23
|
-
default: "default",
|
|
24
|
-
all_calls: "all calls",
|
|
25
|
-
silent: "silent"
|
|
26
|
-
};
|
|
27
15
|
function render(v) {
|
|
28
16
|
switch (v.kind) {
|
|
29
17
|
case "surface":
|
|
@@ -32,13 +20,8 @@ function render(v) {
|
|
|
32
20
|
return "paigy: unpaired";
|
|
33
21
|
case "device-only":
|
|
34
22
|
return "paigy: paired \xB7 session hatches on first use";
|
|
35
|
-
case "session":
|
|
36
|
-
|
|
37
|
-
if (!v.status) return `paigy: ${who}revoked`;
|
|
38
|
-
const n = v.status.waiting ?? 0;
|
|
39
|
-
const waiting = n > 0 ? ` \xB7 ${n} waiting` : "";
|
|
40
|
-
return `paigy: ${who}${MODE_LABEL[v.status.sessionMode]} \xB7 ${v.status.phone ? "phone \u2713" : "no phone"}${waiting}`;
|
|
41
|
-
}
|
|
23
|
+
case "session":
|
|
24
|
+
return `paigy: ${v.name ?? "paired"}`;
|
|
42
25
|
}
|
|
43
26
|
}
|
|
44
27
|
async function stdinSessionId(input = process.stdin) {
|
|
@@ -64,27 +47,6 @@ async function stdinSessionId(input = process.stdin) {
|
|
|
64
47
|
function resolveSlot(sid) {
|
|
65
48
|
return process.env.PAIGY_AGENT || sessionSlot(sid);
|
|
66
49
|
}
|
|
67
|
-
function readCache() {
|
|
68
|
-
try {
|
|
69
|
-
return JSON.parse(readFileSync(CACHE, "utf8"));
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function fetchStatus(token) {
|
|
75
|
-
const res = await reach(`${BACKEND_URL}/api/status`, {
|
|
76
|
-
headers: { authorization: `Bearer ${token}` },
|
|
77
|
-
signal: AbortSignal.timeout(2e3)
|
|
78
|
-
// a statusline must render fast or not at all
|
|
79
|
-
});
|
|
80
|
-
if (res.status === 401) return null;
|
|
81
|
-
if (res.status === 404) {
|
|
82
|
-
if ((await res.text()).includes("not_found")) return null;
|
|
83
|
-
throw new Error("status 404");
|
|
84
|
-
}
|
|
85
|
-
if (!res.ok) throw new Error(`status ${res.status}`);
|
|
86
|
-
return await res.json();
|
|
87
|
-
}
|
|
88
50
|
async function main() {
|
|
89
51
|
const surface = readSurface();
|
|
90
52
|
if (surface) return render({ kind: "surface", ...surface });
|
|
@@ -93,17 +55,7 @@ async function main() {
|
|
|
93
55
|
if (!token) {
|
|
94
56
|
return render(listSlots().includes("Desktop") ? { kind: "device-only" } : { kind: "unpaired" });
|
|
95
57
|
}
|
|
96
|
-
|
|
97
|
-
const cached = readCache();
|
|
98
|
-
if (cached && Date.now() - cached.at < TTL_MS) return render({ kind: "session", name, status: cached.status });
|
|
99
|
-
try {
|
|
100
|
-
const status = await fetchStatus(token);
|
|
101
|
-
mkdirSync(DIR, { recursive: true });
|
|
102
|
-
writeFileSync(CACHE, JSON.stringify({ at: Date.now(), status }));
|
|
103
|
-
return render({ kind: "session", name, status });
|
|
104
|
-
} catch {
|
|
105
|
-
return cached ? render({ kind: "session", name, status: cached.status }) : "paigy: \u2026";
|
|
106
|
-
}
|
|
58
|
+
return render({ kind: "session", name: slotName(slot) });
|
|
107
59
|
}
|
|
108
60
|
function isEntry() {
|
|
109
61
|
const arg = process.argv[1];
|