@paigy/harness 0.3.14 → 0.3.15
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/cli.js +216 -121
- package/dist/main.js +212 -118
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22755,6 +22755,7 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
22755
22755
|
import { closeSync, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync2, rmSync, statSync, writeFileSync } from "fs";
|
|
22756
22756
|
import { homedir as homedir22 } from "os";
|
|
22757
22757
|
import { join as join22 } from "path";
|
|
22758
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
22758
22759
|
async function proxy() {
|
|
22759
22760
|
if (!PROXY_ENV.some((k) => process.env[k])) return void 0;
|
|
22760
22761
|
return agent ??= new (await Promise.resolve().then(() => __toESM(require_undici(), 1))).EnvHttpProxyAgent();
|
|
@@ -23786,6 +23787,9 @@ function ensureAuthed(res) {
|
|
|
23786
23787
|
if (res.status === 401) throw new UnpairedError();
|
|
23787
23788
|
return res;
|
|
23788
23789
|
}
|
|
23790
|
+
function overrideToken(secret) {
|
|
23791
|
+
tokenOverride = secret;
|
|
23792
|
+
}
|
|
23789
23793
|
async function fail(what, res) {
|
|
23790
23794
|
throw new ApiError(what, res.status, await res.text());
|
|
23791
23795
|
}
|
|
@@ -23821,9 +23825,6 @@ async function updateGoal(goalId, input, opts = {}) {
|
|
|
23821
23825
|
if (!res.ok) await fail("update_goal", res);
|
|
23822
23826
|
return await res.json();
|
|
23823
23827
|
}
|
|
23824
|
-
function overrideToken(secret) {
|
|
23825
|
-
tokenOverride = secret;
|
|
23826
|
-
}
|
|
23827
23828
|
async function hatch(name, voice = null) {
|
|
23828
23829
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/hatch`, {
|
|
23829
23830
|
method: "POST",
|
|
@@ -23904,6 +23905,31 @@ async function acceptTriage(proposalId, body, opts = {}) {
|
|
|
23904
23905
|
if (!res.ok) throw new Error(`accept_triage failed: ${res.status} ${await res.text()}`);
|
|
23905
23906
|
return await res.json();
|
|
23906
23907
|
}
|
|
23908
|
+
function repoFromRemote(url) {
|
|
23909
|
+
const said = (url ?? "").trim();
|
|
23910
|
+
if (!said) return null;
|
|
23911
|
+
const path = said.replace(/^[a-z+]+:\/\/[^/]+\//i, "").replace(/^[^@]+@[^:]+:/, "").replace(/\.git$/i, "").replace(/\/+$/, "");
|
|
23912
|
+
const parts = path.split("/").filter(Boolean);
|
|
23913
|
+
if (parts.length < 2) return null;
|
|
23914
|
+
const [owner, name] = parts.slice(-2);
|
|
23915
|
+
if (!owner || !name) return null;
|
|
23916
|
+
return `${owner}/${name}`.toLowerCase();
|
|
23917
|
+
}
|
|
23918
|
+
function currentRepo(cwd = process.cwd()) {
|
|
23919
|
+
if (asked !== void 0) return asked;
|
|
23920
|
+
try {
|
|
23921
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
23922
|
+
cwd,
|
|
23923
|
+
encoding: "utf8",
|
|
23924
|
+
timeout: 2e3,
|
|
23925
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
23926
|
+
});
|
|
23927
|
+
asked = repoFromRemote(url);
|
|
23928
|
+
} catch {
|
|
23929
|
+
asked = null;
|
|
23930
|
+
}
|
|
23931
|
+
return asked;
|
|
23932
|
+
}
|
|
23907
23933
|
async function contact(input, opts = {}) {
|
|
23908
23934
|
const parsed = ContactSchema.parse(input);
|
|
23909
23935
|
opts.signal?.throwIfAborted();
|
|
@@ -23917,7 +23943,11 @@ async function contact(input, opts = {}) {
|
|
|
23917
23943
|
const reqAsks = parsed.asks.map((a) => ({
|
|
23918
23944
|
id: a.id,
|
|
23919
23945
|
parentId: a.parentId,
|
|
23920
|
-
|
|
23946
|
+
// Derived, not asked for (#2315): the agent's own remote, in one spelling, so a project's
|
|
23947
|
+
// work does not grow three trees for "paigy", "mauurda/paigy" and "Paigy". An explicit one
|
|
23948
|
+
// still wins — normalised the same way — because a caller that names one knows something
|
|
23949
|
+
// this cannot see.
|
|
23950
|
+
repo: repoFromRemote(a.repo) ?? a.repo ?? currentRepo() ?? void 0,
|
|
23921
23951
|
ask: a.ask,
|
|
23922
23952
|
options: a.options ?? []
|
|
23923
23953
|
}));
|
|
@@ -23939,10 +23969,12 @@ async function contact(input, opts = {}) {
|
|
|
23939
23969
|
deliveryId = deliveries[0]?.deliveryId ?? "";
|
|
23940
23970
|
if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
|
|
23941
23971
|
}
|
|
23972
|
+
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId);
|
|
23942
23973
|
const read = async (signal2) => {
|
|
23943
23974
|
const res = ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
23944
23975
|
if (!res.ok) await fail("read_delivery", res);
|
|
23945
|
-
|
|
23976
|
+
const one = await res.json();
|
|
23977
|
+
return all.length > 1 ? { ...one, deliveries: all } : one;
|
|
23946
23978
|
};
|
|
23947
23979
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
23948
23980
|
if (opts.waits === false) return read(opts.signal);
|
|
@@ -24042,7 +24074,8 @@ function subscribeRealtime(args) {
|
|
|
24042
24074
|
};
|
|
24043
24075
|
}
|
|
24044
24076
|
async function subscribeWake(onNudge, opts = {}) {
|
|
24045
|
-
const
|
|
24077
|
+
const { release = true, ...client } = opts;
|
|
24078
|
+
const cfg = await registerDelivery("self_hosted", client);
|
|
24046
24079
|
const { url, anonKey } = cfg.realtime;
|
|
24047
24080
|
const channel = wakeChannel(cfg.tokenId);
|
|
24048
24081
|
const ch = subscribeRealtime({
|
|
@@ -24057,17 +24090,18 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
24057
24090
|
channel,
|
|
24058
24091
|
close: async () => {
|
|
24059
24092
|
ch.close();
|
|
24060
|
-
await registerDelivery("poll",
|
|
24093
|
+
if (release) await registerDelivery("poll", client).catch(() => {
|
|
24061
24094
|
});
|
|
24062
24095
|
}
|
|
24063
24096
|
};
|
|
24064
24097
|
}
|
|
24065
|
-
var import_undici, require2, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, lineage, ignoreOverride, defaultOptions, getDefaultOptions, getRefs, getRelativePath, parseCatchDef, integerDateParser, isJsonSchema7AllOfType, emojiRegex2, zodPatterns, ALPHA_NUMERIC, primitiveMappings, asAnyOf, parseOptionalDef, parsePipelineDef, parseReadonlyDef, selectParser, get$ref, addMeta, zodToJsonSchema, OPTIONS_MIN, OPTIONS_MAX, AskInputSchema, StartContactSchema, ContactSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, CreateGoalToolSchema, CREATE_GOAL_DESCRIPTION, UpdateGoalSchema, UpdateGoalToolSchema, ClaimGoalSchema, GetGoalSchema, GET_GOAL_DESCRIPTION, UPDATE_GOAL_DESCRIPTION, CLAIM_GOAL_DESCRIPTION, CHECK_REPLIES_DESCRIPTION, CheckRepliesSchema,
|
|
24098
|
+
var import_undici, require2, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, lineage, ignoreOverride, defaultOptions, getDefaultOptions, getRefs, getRelativePath, parseCatchDef, integerDateParser, isJsonSchema7AllOfType, emojiRegex2, zodPatterns, ALPHA_NUMERIC, primitiveMappings, asAnyOf, parseOptionalDef, parsePipelineDef, parseReadonlyDef, selectParser, get$ref, addMeta, zodToJsonSchema, OPTIONS_MIN, OPTIONS_MAX, OptionSchema, OptionInputSchema, NIGHT, AskInputSchema, StartContactSchema, ContactSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, CreateGoalToolSchema, CREATE_GOAL_DESCRIPTION, UpdateGoalSchema, UpdateGoalToolSchema, ClaimGoalSchema, GetGoalSchema, GET_GOAL_DESCRIPTION, UPDATE_GOAL_DESCRIPTION, CLAIM_GOAL_DESCRIPTION, CHECK_REPLIES_DESCRIPTION, CheckRepliesSchema, AGENT_TOOLS, AGENT_TOOL_NAMES, LIVE_MS, ContextSchema, ParticipantSchema, TransformSchema, VisualSchema, NotifyLevelSchema, SelectShapeSchema, ReceiptEventSchema, AttentionSchema, NotifyRequestFields, NotifyRequestSchema, NotifyStatusSchema, AgentStateSchema, TurnSchema, UserAnswerSchema, IntentSchema, RideAlongSchema, AwaitItemSchema, VoiceKeySchema, AgendaTurnSchema, CLAIM_STALE_MS, InboxItemSchema, APNS_TOKEN_RE, PushTokenSchema, MissedCallSchema, clock, QUIET, BrokerTuningSchema, UserSettingsSchema, HistoryItemSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, MoveRingSchema, MoveSchema, QueueQuestionSchema, QueueItemSchema, COLD_AFTER_MS, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, WakeNudgeSchema, PairingStatusSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenSchema, SupportRequestSchema, NotificationFeedbackKindSchema, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, ApiError, tokenOverride, authToken, AWAIT_WINDOW_MS, asked, HEARTBEAT_MS, RETRY_MS, JOIN, lines;
|
|
24066
24099
|
var init_dist = __esm({
|
|
24067
24100
|
"../../packages/sdk/dist/index.js"() {
|
|
24068
24101
|
"use strict";
|
|
24069
24102
|
init_zod();
|
|
24070
24103
|
init_zod();
|
|
24104
|
+
init_zod();
|
|
24071
24105
|
init_v3();
|
|
24072
24106
|
init_v3();
|
|
24073
24107
|
init_v3();
|
|
@@ -24452,28 +24486,40 @@ var init_dist = __esm({
|
|
|
24452
24486
|
};
|
|
24453
24487
|
OPTIONS_MIN = 2;
|
|
24454
24488
|
OPTIONS_MAX = 6;
|
|
24489
|
+
OptionSchema = external_exports.object({
|
|
24490
|
+
id: external_exports.string(),
|
|
24491
|
+
label: external_exports.string(),
|
|
24492
|
+
hint: external_exports.string().max(500).describe("Optional short projection of consequence or action if this option is chosen (e.g. 'Reruns test suite', 'Merges to main').").optional(),
|
|
24493
|
+
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
24494
|
+
// the constraints below are what an agent reads when deciding to use these.
|
|
24495
|
+
html: external_exports.string().max(16384).describe(
|
|
24496
|
+
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
24497
|
+
).optional(),
|
|
24498
|
+
image: external_exports.string().url().describe(
|
|
24499
|
+
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
24500
|
+
).optional()
|
|
24501
|
+
});
|
|
24502
|
+
OptionInputSchema = OptionSchema.omit({ id: true }).extend({
|
|
24503
|
+
label: external_exports.string().trim().min(1).max(1e3)
|
|
24504
|
+
}).strict();
|
|
24505
|
+
NIGHT = { from: 23, to: 7 };
|
|
24455
24506
|
AskInputSchema = external_exports.object({
|
|
24456
24507
|
id: external_exports.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
|
|
24457
|
-
parentId: external_exports.string().uuid().optional().describe("
|
|
24508
|
+
parentId: external_exports.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
|
|
24458
24509
|
repo: external_exports.string().optional().describe("Optional repository context."),
|
|
24459
24510
|
ask: external_exports.string().trim().min(1).max(1e4).describe(
|
|
24460
24511
|
"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."
|
|
24461
24512
|
),
|
|
24462
|
-
options: external_exports.array(
|
|
24463
|
-
label: external_exports.string().trim().min(1).max(1e3),
|
|
24464
|
-
hint: external_exports.string().trim().max(500).describe("Optional short projection of consequence or action if this option is chosen.").optional(),
|
|
24465
|
-
image: external_exports.string().url().optional(),
|
|
24466
|
-
html: external_exports.string().max(16384).describe("Optional sandboxed HTML/CSS preview. No JavaScript or network; inline CSS and data: URIs only. It renders edge-to-edge in a responsive card 200pt tall (about 320pt wide on a phone, with the next option peeking beside it), so fit the HTML to that viewport.").optional()
|
|
24467
|
-
}).strict()).min(2).max(6).optional()
|
|
24513
|
+
options: external_exports.array(OptionInputSchema).min(2).max(6).optional()
|
|
24468
24514
|
}).strict();
|
|
24469
24515
|
StartContactSchema = external_exports.object({
|
|
24470
|
-
asks: external_exports.array(AskInputSchema).min(1).describe("The
|
|
24516
|
+
asks: external_exports.array(AskInputSchema).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; only an ask naming no Goal is placed in the tree by Paigy."),
|
|
24471
24517
|
waiting: external_exports.enum(["none", "hard"]).default("none"),
|
|
24472
24518
|
channel: external_exports.enum(["notification", "call"]).default("notification")
|
|
24473
24519
|
}).strict();
|
|
24474
24520
|
ContactSchema = external_exports.union([StartContactSchema, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict()]);
|
|
24475
24521
|
CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
24476
|
-
CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'.
|
|
24522
|
+
CONTACT_DESCRIPTION = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Never bundle multiple questions into a single 'ask' string; pass them as separate objects in the 'asks' array.";
|
|
24477
24523
|
CreateGoalSchema = external_exports.object({
|
|
24478
24524
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
24479
24525
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -24481,10 +24527,6 @@ var init_dist = __esm({
|
|
|
24481
24527
|
title: external_exports.string().trim().min(1).max(80).optional(),
|
|
24482
24528
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
24483
24529
|
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
24484
|
-
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
24485
|
-
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
24486
|
-
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
24487
|
-
* the writer refuses a thread belonging to another account. */
|
|
24488
24530
|
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
24489
24531
|
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
24490
24532
|
* worked"). It could not have been: this door took no parent, so the only route was
|
|
@@ -24528,19 +24570,9 @@ var init_dist = __esm({
|
|
|
24528
24570
|
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.";
|
|
24529
24571
|
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.";
|
|
24530
24572
|
CheckRepliesSchema = external_exports.object({}).strict();
|
|
24531
|
-
GetThreadSchema = external_exports.object({
|
|
24532
|
-
parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
24533
|
-
}).strict();
|
|
24534
|
-
GET_THREAD_DESCRIPTION = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
|
|
24535
|
-
SearchThreadsSchema = external_exports.object({
|
|
24536
|
-
q: external_exports.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
|
|
24537
|
-
}).strict();
|
|
24538
|
-
SEARCH_THREADS_DESCRIPTION = `Search your PAST conversations before asking \u2014 "have we discussed this before?". Full-text over your own threads (the asks you sent + the user's answers); returns ranked threads with highlighted snippets, NOT rows: { hits: [{ parentId, at, agentLabel, matches: [{ notificationId, role, snippet }] }] }. The loop this exists for: search first \u2192 get_thread the best hit to rehydrate it \u2192 THEN continue or contact, so you answer with receipts ("last week you said ship it") instead of re-asking. Read-only, safe to call anytime; scoped to your own account's threads.`;
|
|
24539
24573
|
AGENT_TOOLS = [
|
|
24540
24574
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
24541
24575
|
{ name: "check_replies", description: CHECK_REPLIES_DESCRIPTION, inputSchema: mcpInputSchema(CheckRepliesSchema) },
|
|
24542
|
-
{ name: "get_thread", description: GET_THREAD_DESCRIPTION, inputSchema: mcpInputSchema(GetThreadSchema) },
|
|
24543
|
-
{ name: "search_threads", description: SEARCH_THREADS_DESCRIPTION, inputSchema: mcpInputSchema(SearchThreadsSchema) },
|
|
24544
24576
|
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(CreateGoalToolSchema) },
|
|
24545
24577
|
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(ClaimGoalSchema) },
|
|
24546
24578
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
@@ -24574,19 +24606,6 @@ var init_dist = __esm({
|
|
|
24574
24606
|
"summarize"
|
|
24575
24607
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
24576
24608
|
]);
|
|
24577
|
-
OptionSchema = external_exports.object({
|
|
24578
|
-
id: external_exports.string(),
|
|
24579
|
-
label: external_exports.string(),
|
|
24580
|
-
hint: external_exports.string().max(500).describe("Optional short projection of consequence or action if this option is chosen (e.g. 'Reruns test suite', 'Merges to main').").optional(),
|
|
24581
|
-
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
24582
|
-
// the constraints below are what an agent reads when deciding to use these.
|
|
24583
|
-
html: external_exports.string().max(16384).describe(
|
|
24584
|
-
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
24585
|
-
).optional(),
|
|
24586
|
-
image: external_exports.string().url().describe(
|
|
24587
|
-
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
24588
|
-
).optional()
|
|
24589
|
-
});
|
|
24590
24609
|
VisualSchema = external_exports.object({
|
|
24591
24610
|
url: external_exports.string().url(),
|
|
24592
24611
|
label: external_exports.string().optional()
|
|
@@ -24640,7 +24659,7 @@ var init_dist = __esm({
|
|
|
24640
24659
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
24641
24660
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
24642
24661
|
context: ContextSchema.optional(),
|
|
24643
|
-
options: external_exports.array(
|
|
24662
|
+
options: external_exports.array(OptionInputSchema).min(OPTIONS_MIN).max(OPTIONS_MAX).optional().describe(
|
|
24644
24663
|
"The choices, in order \u2014 required when select is 'one'/'many'/'rank', omitted otherwise. Ids are assigned automatically by position ('1', '2', \u2026); the user's answer references them as optionId(s)."
|
|
24645
24664
|
),
|
|
24646
24665
|
points: external_exports.array(external_exports.string().min(1)).optional().describe(
|
|
@@ -24834,7 +24853,7 @@ var init_dist = __esm({
|
|
|
24834
24853
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
24835
24854
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
24836
24855
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
24837
|
-
* re-orient via
|
|
24856
|
+
* re-orient via check_replies). */
|
|
24838
24857
|
external_exports.object({
|
|
24839
24858
|
type: external_exports.literal("superseded"),
|
|
24840
24859
|
parentId: external_exports.string(),
|
|
@@ -24948,10 +24967,6 @@ var init_dist = __esm({
|
|
|
24948
24967
|
options: external_exports.array(OptionSchema).optional(),
|
|
24949
24968
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
24950
24969
|
points: external_exports.array(external_exports.string()).optional(),
|
|
24951
|
-
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
24952
|
-
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
24953
|
-
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
24954
|
-
* a status update stays a statement instead of being shaped into a yes/no. */
|
|
24955
24970
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
24956
24971
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
24957
24972
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
@@ -24972,7 +24987,26 @@ var init_dist = __esm({
|
|
|
24972
24987
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
24973
24988
|
* `createdAt`. */
|
|
24974
24989
|
agentStateAt: external_exports.string().datetime().optional(),
|
|
24975
|
-
agenda
|
|
24990
|
+
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (walk/design.md §11, owner
|
|
24991
|
+
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
24992
|
+
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
24993
|
+
* Call's cards, and every card of that Call carries the same list: the call screen reads it
|
|
24994
|
+
* once, off the one read it already makes (`GET /api/inbox/:callId`).
|
|
24995
|
+
*
|
|
24996
|
+
* It counts DECISIONS, not agenda turns: turns include context-only lines and are re-planned
|
|
24997
|
+
* every cycle, so a spine drawn from them would change length under the caller mid-call.
|
|
24998
|
+
*
|
|
24999
|
+
* `entryId` is the request Entry — the id the bot calls a CLAIM, and the one it names on the
|
|
25000
|
+
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
25001
|
+
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
25002
|
+
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
25003
|
+
questions: external_exports.array(external_exports.object({
|
|
25004
|
+
id: external_exports.string(),
|
|
25005
|
+
entryId: external_exports.string(),
|
|
25006
|
+
title: external_exports.string(),
|
|
25007
|
+
state: external_exports.enum(["open", "answered"]),
|
|
25008
|
+
answer: external_exports.string().nullable()
|
|
25009
|
+
})).optional(),
|
|
24976
25010
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
24977
25011
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
24978
25012
|
* agent's suggestion, or a default silly name). */
|
|
@@ -24993,12 +25027,21 @@ var init_dist = __esm({
|
|
|
24993
25027
|
* client may still flag a stall by age. Drives the inbox error badge + Retry. */
|
|
24994
25028
|
error: external_exports.string().optional(),
|
|
24995
25029
|
clarifies: external_exports.string().optional(),
|
|
24996
|
-
/**
|
|
24997
|
-
*
|
|
24998
|
-
*
|
|
24999
|
-
*
|
|
25000
|
-
*
|
|
25001
|
-
|
|
25030
|
+
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
25031
|
+
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
25032
|
+
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
25033
|
+
* The ladder is ACCOUNT-WIDE (#2259): `anchorAt` and `step` are the account's position;
|
|
25034
|
+
* `nextRingAt` is this card's armed instant (`deliveries.next_ring_at`), null once the cron
|
|
25035
|
+
* has disarmed it — the curve's end, `inbox`/`dismiss`, or a setting that said no more rings.
|
|
25036
|
+
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
25037
|
+
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
25038
|
+
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
25039
|
+
ring: external_exports.object({
|
|
25040
|
+
declinedAt: external_exports.string().datetime(),
|
|
25041
|
+
anchorAt: external_exports.string().datetime(),
|
|
25042
|
+
nextRingAt: external_exports.string().datetime().nullable(),
|
|
25043
|
+
step: external_exports.number().int()
|
|
25044
|
+
}).optional(),
|
|
25002
25045
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
25003
25046
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
25004
25047
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
@@ -25063,6 +25106,8 @@ var init_dist = __esm({
|
|
|
25063
25106
|
"inbox",
|
|
25064
25107
|
"dismiss"
|
|
25065
25108
|
]);
|
|
25109
|
+
clock = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
25110
|
+
QUIET = ` Nothing rings from ${clock(NIGHT.from)} to ${clock(NIGHT.to)} your time; the count waits for morning.`;
|
|
25066
25111
|
BrokerTuningSchema = external_exports.object({
|
|
25067
25112
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
25068
25113
|
ackVerbosity: external_exports.enum(["normal", "none"]).optional(),
|
|
@@ -25097,6 +25142,12 @@ var init_dist = __esm({
|
|
|
25097
25142
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
25098
25143
|
* write, 'hosted' on read (see store.ts). */
|
|
25099
25144
|
voiceMode: external_exports.enum(["hosted", "on_device"]).optional(),
|
|
25145
|
+
/** Talk — after you answer, the next step is read aloud (walk/design.md §6). ALWAYS ON until
|
|
25146
|
+
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
25147
|
+
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
25148
|
+
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
25149
|
+
* read (see store.ts). */
|
|
25150
|
+
talk: external_exports.boolean().optional(),
|
|
25100
25151
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
25101
25152
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
25102
25153
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -25294,6 +25345,9 @@ var init_dist = __esm({
|
|
|
25294
25345
|
assignee: external_exports.string().nullable().default(null),
|
|
25295
25346
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
25296
25347
|
progress: external_exports.string().nullable().default(null),
|
|
25348
|
+
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
25349
|
+
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
25350
|
+
progressLine: external_exports.string().nullable().optional(),
|
|
25297
25351
|
reviewPending: external_exports.boolean().default(false),
|
|
25298
25352
|
dueAt: external_exports.string().nullable().default(null),
|
|
25299
25353
|
/** The Goal this one was opened under; null at the root. */
|
|
@@ -25309,8 +25363,13 @@ var init_dist = __esm({
|
|
|
25309
25363
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
25310
25364
|
repo: external_exports.string().nullable().optional(),
|
|
25311
25365
|
createdAt: external_exports.string(),
|
|
25312
|
-
updatedAt: external_exports.string().nullable().default(null)
|
|
25366
|
+
updatedAt: external_exports.string().nullable().default(null),
|
|
25367
|
+
/** When its owner last SAID something about it (`goals.last_progress_at`, written by every
|
|
25368
|
+
* `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
|
|
25369
|
+
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
25370
|
+
lastProgressAt: external_exports.string().nullable().optional()
|
|
25313
25371
|
});
|
|
25372
|
+
COLD_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
25314
25373
|
NoteSourceSchema = external_exports.enum(["app", "call"]);
|
|
25315
25374
|
NoteStatusSchema = external_exports.enum(["open", "assigned", "in_progress", "done"]);
|
|
25316
25375
|
NoteRepeatSchema = external_exports.enum(["once", "until_done"]);
|
|
@@ -25510,9 +25569,9 @@ var init_dist = __esm({
|
|
|
25510
25569
|
this.body = body;
|
|
25511
25570
|
}
|
|
25512
25571
|
};
|
|
25513
|
-
AWAIT_WINDOW_MS = 45e3;
|
|
25514
25572
|
tokenOverride = null;
|
|
25515
25573
|
authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
25574
|
+
AWAIT_WINDOW_MS = 45e3;
|
|
25516
25575
|
HEARTBEAT_MS = 25e3;
|
|
25517
25576
|
RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
25518
25577
|
JOIN = { config: { broadcast: { self: false } } };
|
|
@@ -30126,9 +30185,9 @@ async function available(deps = {}) {
|
|
|
30126
30185
|
});
|
|
30127
30186
|
return [ollama, ...harnesses];
|
|
30128
30187
|
}
|
|
30129
|
-
function refusal(options,
|
|
30188
|
+
function refusal(options, asked2) {
|
|
30130
30189
|
const lines2 = [
|
|
30131
|
-
|
|
30190
|
+
asked2 ? `Can't judge with --via ${asked2}: ${options.find((o) => o.provider === asked2)?.hint ?? "it isn't available on this machine"}` : "No local judge available \u2014 triage refuses rather than sending your queue to a cloud model.",
|
|
30132
30191
|
"",
|
|
30133
30192
|
"Triage runs the model where your queue already lives (#1106). Your options:"
|
|
30134
30193
|
];
|
|
@@ -30139,10 +30198,10 @@ function refusal(options, asked) {
|
|
|
30139
30198
|
lines2.push("", "Then re-run: paigy-harness triage [--via ollama|claude|codex|agy]");
|
|
30140
30199
|
return lines2.join("\n");
|
|
30141
30200
|
}
|
|
30142
|
-
async function judgeFor(
|
|
30201
|
+
async function judgeFor(asked2, deps = {}) {
|
|
30143
30202
|
const options = await available(deps);
|
|
30144
|
-
const chosen =
|
|
30145
|
-
if (!chosen?.ready) return { error: refusal(options,
|
|
30203
|
+
const chosen = asked2 ? options.find((o) => o.provider === asked2) : options.find((o) => o.ready);
|
|
30204
|
+
if (!chosen?.ready) return { error: refusal(options, asked2) };
|
|
30146
30205
|
if (chosen.provider === "ollama") {
|
|
30147
30206
|
const model = chosen.model;
|
|
30148
30207
|
return {
|
|
@@ -30163,9 +30222,9 @@ async function judgeFor(asked, deps = {}) {
|
|
|
30163
30222
|
};
|
|
30164
30223
|
}
|
|
30165
30224
|
const runner = RUNNERS[chosen.provider];
|
|
30166
|
-
if (!runner) return { error: refusal(options,
|
|
30225
|
+
if (!runner) return { error: refusal(options, asked2) };
|
|
30167
30226
|
const bin = which(runner.cli);
|
|
30168
|
-
if (!bin) return { error: refusal(options,
|
|
30227
|
+
if (!bin) return { error: refusal(options, asked2) };
|
|
30169
30228
|
return {
|
|
30170
30229
|
provider: chosen.provider,
|
|
30171
30230
|
model: null,
|
|
@@ -31024,7 +31083,13 @@ function startSession(opts) {
|
|
|
31024
31083
|
...process.env,
|
|
31025
31084
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
31026
31085
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
31027
|
-
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {}
|
|
31086
|
+
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {},
|
|
31087
|
+
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
31088
|
+
// bridge's pump typing answers in, so nothing inside it may start a second delivery path.
|
|
31089
|
+
// Exported on EVERY spawn — `PAIGY_SESSION_ID` above is the conversation to resume, and a
|
|
31090
|
+
// fresh spawn has none, which is how a fresh run read as "not harness-started" to the
|
|
31091
|
+
// session-start hook and the `listen` tool (they now test either mark; listening.ts).
|
|
31092
|
+
PAIGY_HARNESS: "1"
|
|
31028
31093
|
},
|
|
31029
31094
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31030
31095
|
});
|
|
@@ -31073,6 +31138,7 @@ function startSession(opts) {
|
|
|
31073
31138
|
// ../../packages/schema/dist/index.js
|
|
31074
31139
|
init_zod();
|
|
31075
31140
|
init_zod();
|
|
31141
|
+
init_zod();
|
|
31076
31142
|
|
|
31077
31143
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
31078
31144
|
var ignoreOverride2 = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
@@ -32369,6 +32435,23 @@ var zodToJsonSchema2 = (schema, options) => {
|
|
|
32369
32435
|
init_zod();
|
|
32370
32436
|
var OPTIONS_MIN2 = 2;
|
|
32371
32437
|
var OPTIONS_MAX2 = 6;
|
|
32438
|
+
var OptionSchema2 = external_exports.object({
|
|
32439
|
+
id: external_exports.string(),
|
|
32440
|
+
label: external_exports.string(),
|
|
32441
|
+
hint: external_exports.string().max(500).describe("Optional short projection of consequence or action if this option is chosen (e.g. 'Reruns test suite', 'Merges to main').").optional(),
|
|
32442
|
+
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
32443
|
+
// the constraints below are what an agent reads when deciding to use these.
|
|
32444
|
+
html: external_exports.string().max(16384).describe(
|
|
32445
|
+
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
32446
|
+
).optional(),
|
|
32447
|
+
image: external_exports.string().url().describe(
|
|
32448
|
+
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
32449
|
+
).optional()
|
|
32450
|
+
});
|
|
32451
|
+
var OptionInputSchema2 = OptionSchema2.omit({ id: true }).extend({
|
|
32452
|
+
label: external_exports.string().trim().min(1).max(1e3)
|
|
32453
|
+
}).strict();
|
|
32454
|
+
var NIGHT2 = { from: 23, to: 7 };
|
|
32372
32455
|
function draft20202(node) {
|
|
32373
32456
|
if (Array.isArray(node)) return node.map(draft20202);
|
|
32374
32457
|
if (node && typeof node === "object") {
|
|
@@ -32400,26 +32483,21 @@ function mcpInputSchema2(s) {
|
|
|
32400
32483
|
}
|
|
32401
32484
|
var AskInputSchema2 = external_exports.object({
|
|
32402
32485
|
id: external_exports.string().optional().describe("Optional idempotency key or client-side ID for this specific ask."),
|
|
32403
|
-
parentId: external_exports.string().uuid().optional().describe("
|
|
32486
|
+
parentId: external_exports.string().uuid().optional().describe("The Goal this question is about \u2014 usually the one you are working on. The question goes onto that Goal and its answer comes back there. Omit it and Paigy places the question in the tree itself."),
|
|
32404
32487
|
repo: external_exports.string().optional().describe("Optional repository context."),
|
|
32405
32488
|
ask: external_exports.string().trim().min(1).max(1e4).describe(
|
|
32406
32489
|
"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."
|
|
32407
32490
|
),
|
|
32408
|
-
options: external_exports.array(
|
|
32409
|
-
label: external_exports.string().trim().min(1).max(1e3),
|
|
32410
|
-
hint: external_exports.string().trim().max(500).describe("Optional short projection of consequence or action if this option is chosen.").optional(),
|
|
32411
|
-
image: external_exports.string().url().optional(),
|
|
32412
|
-
html: external_exports.string().max(16384).describe("Optional sandboxed HTML/CSS preview. No JavaScript or network; inline CSS and data: URIs only. It renders edge-to-edge in a responsive card 200pt tall (about 320pt wide on a phone, with the next option peeking beside it), so fit the HTML to that viewport.").optional()
|
|
32413
|
-
}).strict()).min(2).max(6).optional()
|
|
32491
|
+
options: external_exports.array(OptionInputSchema2).min(2).max(6).optional()
|
|
32414
32492
|
}).strict();
|
|
32415
32493
|
var StartContactSchema2 = external_exports.object({
|
|
32416
|
-
asks: external_exports.array(AskInputSchema2).min(1).describe("The
|
|
32494
|
+
asks: external_exports.array(AskInputSchema2).min(1).describe("The questions to pose, one per object. An ask with a parentId is filed on that Goal as it is; only an ask naming no Goal is placed in the tree by Paigy."),
|
|
32417
32495
|
waiting: external_exports.enum(["none", "hard"]).default("none"),
|
|
32418
32496
|
channel: external_exports.enum(["notification", "call"]).default("notification")
|
|
32419
32497
|
}).strict();
|
|
32420
32498
|
var ContactSchema2 = external_exports.union([StartContactSchema2, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict()]);
|
|
32421
32499
|
var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
32422
|
-
var CONTACT_DESCRIPTION2 = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'.
|
|
32500
|
+
var CONTACT_DESCRIPTION2 = "Contact the user with one or more asks/questions. Pass an array of asks (each with 'ask', optional 'options', 'id', 'parentId', 'repo'), plus channel:'notification'|'call', and waiting:'none'|'hard'. An ask that names a Goal (parentId) goes onto that Goal; one that names none is placed in the person's tree by Paigy. Notification returns immediately; collect durable answers with check_replies. On stdio, a Call holds one cancellable ~45s window; continue with ONLY {deliveryId}. Never bundle multiple questions into a single 'ask' string; pass them as separate objects in the 'asks' array.";
|
|
32423
32501
|
var CreateGoalSchema2 = external_exports.object({
|
|
32424
32502
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
32425
32503
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -32427,10 +32505,6 @@ var CreateGoalSchema2 = external_exports.object({
|
|
|
32427
32505
|
title: external_exports.string().trim().min(1).max(80).optional(),
|
|
32428
32506
|
ownerParticipant: external_exports.string().trim().min(1).optional(),
|
|
32429
32507
|
idempotencyKey: external_exports.string().trim().min(1).max(200),
|
|
32430
|
-
/** A past conversation this Goal should be read against — History's "new session from this"
|
|
32431
|
-
* (owner, on the call of 2026-09-14: "let's do the reference with the threading"). A
|
|
32432
|
-
* reference only: the owner reads it through `get_thread`, which does its own scoping, and
|
|
32433
|
-
* the writer refuses a thread belonging to another account. */
|
|
32434
32508
|
/** THE GOAL THIS ONE BELONGS UNDER (owner, 2026-09-15: "the ask I gave for the design doc
|
|
32435
32509
|
* didn't get created as a child goal of the voice UI goal, which is how it should've
|
|
32436
32510
|
* worked"). It could not have been: this door took no parent, so the only route was
|
|
@@ -32474,19 +32548,9 @@ var UPDATE_GOAL_DESCRIPTION2 = "Update an owned Goal at an exact revision. State
|
|
|
32474
32548
|
var CLAIM_GOAL_DESCRIPTION2 = "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.";
|
|
32475
32549
|
var CHECK_REPLIES_DESCRIPTION2 = "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.";
|
|
32476
32550
|
var CheckRepliesSchema2 = external_exports.object({}).strict();
|
|
32477
|
-
var GetThreadSchema2 = external_exports.object({
|
|
32478
|
-
parentId: external_exports.string().describe("The Thread to read \u2014 the threadId a Delivery returned, or the parentId of a search hit.")
|
|
32479
|
-
}).strict();
|
|
32480
|
-
var GET_THREAD_DESCRIPTION2 = "Read the authorized durable Entries on one conversation Thread \u2014 what you wrote there and what was delivered to you, oldest first. Use claim_goal to find the work to resume; use this to rehydrate a Thread that a search hit or a Delivery named.";
|
|
32481
|
-
var SearchThreadsSchema2 = external_exports.object({
|
|
32482
|
-
q: external_exports.string().describe("What to look for \u2014 plain words or a phrase (e.g. 'the livekit timeout', 'deploy to prod').")
|
|
32483
|
-
}).strict();
|
|
32484
|
-
var SEARCH_THREADS_DESCRIPTION2 = `Search your PAST conversations before asking \u2014 "have we discussed this before?". Full-text over your own threads (the asks you sent + the user's answers); returns ranked threads with highlighted snippets, NOT rows: { hits: [{ parentId, at, agentLabel, matches: [{ notificationId, role, snippet }] }] }. The loop this exists for: search first \u2192 get_thread the best hit to rehydrate it \u2192 THEN continue or contact, so you answer with receipts ("last week you said ship it") instead of re-asking. Read-only, safe to call anytime; scoped to your own account's threads.`;
|
|
32485
32551
|
var AGENT_TOOLS2 = [
|
|
32486
32552
|
{ name: "contact", description: CONTACT_DESCRIPTION2, inputSchema: CONTACT_SCHEMA2 },
|
|
32487
32553
|
{ name: "check_replies", description: CHECK_REPLIES_DESCRIPTION2, inputSchema: mcpInputSchema2(CheckRepliesSchema2) },
|
|
32488
|
-
{ name: "get_thread", description: GET_THREAD_DESCRIPTION2, inputSchema: mcpInputSchema2(GetThreadSchema2) },
|
|
32489
|
-
{ name: "search_threads", description: SEARCH_THREADS_DESCRIPTION2, inputSchema: mcpInputSchema2(SearchThreadsSchema2) },
|
|
32490
32554
|
{ name: "create_goal", description: CREATE_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(CreateGoalToolSchema2) },
|
|
32491
32555
|
{ name: "claim_goal", description: CLAIM_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(ClaimGoalSchema2) },
|
|
32492
32556
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(GetGoalSchema2) },
|
|
@@ -32535,19 +32599,6 @@ var TransformSchema2 = external_exports.enum([
|
|
|
32535
32599
|
"summarize"
|
|
32536
32600
|
// reduce volume, keep decision value — 30-turn cap, spoken briefing
|
|
32537
32601
|
]);
|
|
32538
|
-
var OptionSchema2 = external_exports.object({
|
|
32539
|
-
id: external_exports.string(),
|
|
32540
|
-
label: external_exports.string(),
|
|
32541
|
-
hint: external_exports.string().max(500).describe("Optional short projection of consequence or action if this option is chosen (e.g. 'Reruns test suite', 'Merges to main').").optional(),
|
|
32542
|
-
// .describe() flows into the MCP contact JSON schema (zodToJsonSchema), so
|
|
32543
|
-
// the constraints below are what an agent reads when deciding to use these.
|
|
32544
|
-
html: external_exports.string().max(16384).describe(
|
|
32545
|
-
"Optional sandboxed HTML/CSS preview for a visual 'pick one' (shown in the option card). Untrusted-sandboxed: NO JavaScript, NO external network or images \u2014 inline CSS and data: URIs only; <=16KB. Rendered edge-to-edge in a responsive card that is 200pt tall (about 320pt wide on a phone, with the next option peeking beside it); make your HTML fit that viewport. Use for layout/CSS mockups, tables, diffs. For a hosted image use `image` instead."
|
|
32546
|
-
).optional(),
|
|
32547
|
-
image: external_exports.string().url().describe(
|
|
32548
|
-
"Optional image URL rendered as the option's preview (plain image, not sandboxed). For agent-generated HTML/CSS mockups, use `html` instead."
|
|
32549
|
-
).optional()
|
|
32550
|
-
});
|
|
32551
32602
|
var VisualSchema2 = external_exports.object({
|
|
32552
32603
|
url: external_exports.string().url(),
|
|
32553
32604
|
label: external_exports.string().optional()
|
|
@@ -32601,7 +32652,7 @@ var NotifyRequestFields2 = external_exports.object({
|
|
|
32601
32652
|
* ABSENT on the E2EE path, where the sealed `envelope` below carries it. The
|
|
32602
32653
|
* superRefine at the bottom enforces exactly one of the two. */
|
|
32603
32654
|
context: ContextSchema2.optional(),
|
|
32604
|
-
options: external_exports.array(
|
|
32655
|
+
options: external_exports.array(OptionInputSchema2).min(OPTIONS_MIN2).max(OPTIONS_MAX2).optional().describe(
|
|
32605
32656
|
"The choices, in order \u2014 required when select is 'one'/'many'/'rank', omitted otherwise. Ids are assigned automatically by position ('1', '2', \u2026); the user's answer references them as optionId(s)."
|
|
32606
32657
|
),
|
|
32607
32658
|
points: external_exports.array(external_exports.string().min(1)).optional().describe(
|
|
@@ -32795,7 +32846,7 @@ var AwaitItemSchema2 = external_exports.discriminatedUnion("type", [
|
|
|
32795
32846
|
/** The awaited ask was REPLACED by a newer notification on its thread (e.g. a
|
|
32796
32847
|
* post-feedback revision, #633) — the user will never answer this id. Stop
|
|
32797
32848
|
* awaiting it; the live ask is the thread's newest turn (await that one, or
|
|
32798
|
-
* re-orient via
|
|
32849
|
+
* re-orient via check_replies). */
|
|
32799
32850
|
external_exports.object({
|
|
32800
32851
|
type: external_exports.literal("superseded"),
|
|
32801
32852
|
parentId: external_exports.string(),
|
|
@@ -32909,10 +32960,6 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
32909
32960
|
options: external_exports.array(OptionSchema2).optional(),
|
|
32910
32961
|
/** The ask's declared coverage points (#396), when the agent sent them. */
|
|
32911
32962
|
points: external_exports.array(external_exports.string()).optional(),
|
|
32912
|
-
/** The call's AGENDA (broker/agenda-design.md): the ordered turns it is made of, built at
|
|
32913
|
-
* ring/enqueue time. Replaces the condensed line + index-aligned phrased points, which
|
|
32914
|
-
* between them could not express a call as a sequence. `question: null` is a real turn —
|
|
32915
|
-
* a status update stays a statement instead of being shaped into a yes/no. */
|
|
32916
32963
|
/** Does this claim want an ANSWER, or is it telling you something? Written per row from
|
|
32917
32964
|
* `requestAsks` — the agent's own declaration, not a guess. `false` is what earns a card
|
|
32918
32965
|
* its acknowledge affordance: without it a status update offers a text box and a dismiss,
|
|
@@ -32933,7 +32980,26 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
32933
32980
|
* instant the agent picks it up (#997). Absent = pre-trigger row; fall back to
|
|
32934
32981
|
* `createdAt`. */
|
|
32935
32982
|
agentStateAt: external_exports.string().datetime().optional(),
|
|
32936
|
-
agenda
|
|
32983
|
+
/** THE QUESTIONS A CALL CARRIES — the call screen's agenda spine (walk/design.md §11, owner
|
|
32984
|
+
* 2026-09-22). One per DecisionNeed on the Call, in the Call's order, answered or open (a
|
|
32985
|
+
* superseded or cancelled need is no longer a question anyone is asked). Present only on a
|
|
32986
|
+
* Call's cards, and every card of that Call carries the same list: the call screen reads it
|
|
32987
|
+
* once, off the one read it already makes (`GET /api/inbox/:callId`).
|
|
32988
|
+
*
|
|
32989
|
+
* It counts DECISIONS, not agenda turns: turns include context-only lines and are re-planned
|
|
32990
|
+
* every cycle, so a spine drawn from them would change length under the caller mid-call.
|
|
32991
|
+
*
|
|
32992
|
+
* `entryId` is the request Entry — the id the bot calls a CLAIM, and the one it names on the
|
|
32993
|
+
* `turn` topic (`asking`, `settled`), because the bot never sees a DecisionNeed id. `title` is
|
|
32994
|
+
* the card's own concise heading; `answer` the accepted answer in words, null while open. It
|
|
32995
|
+
* REPLACED `agenda` (turns), which nothing ever filled. */
|
|
32996
|
+
questions: external_exports.array(external_exports.object({
|
|
32997
|
+
id: external_exports.string(),
|
|
32998
|
+
entryId: external_exports.string(),
|
|
32999
|
+
title: external_exports.string(),
|
|
33000
|
+
state: external_exports.enum(["open", "answered"]),
|
|
33001
|
+
answer: external_exports.string().nullable()
|
|
33002
|
+
})).optional(),
|
|
32937
33003
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
32938
33004
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
32939
33005
|
* agent's suggestion, or a default silly name). */
|
|
@@ -32954,12 +33020,21 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
32954
33020
|
* client may still flag a stall by age. Drives the inbox error badge + Retry. */
|
|
32955
33021
|
error: external_exports.string().optional(),
|
|
32956
33022
|
clarifies: external_exports.string().optional(),
|
|
32957
|
-
/**
|
|
32958
|
-
*
|
|
32959
|
-
*
|
|
32960
|
-
*
|
|
32961
|
-
*
|
|
32962
|
-
|
|
33023
|
+
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
33024
|
+
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
33025
|
+
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
33026
|
+
* The ladder is ACCOUNT-WIDE (#2259): `anchorAt` and `step` are the account's position;
|
|
33027
|
+
* `nextRingAt` is this card's armed instant (`deliveries.next_ring_at`), null once the cron
|
|
33028
|
+
* has disarmed it — the curve's end, `inbox`/`dismiss`, or a setting that said no more rings.
|
|
33029
|
+
* It replaced `gaveUp` (deleted 2026-09-22): "the ladder spent" was a boolean the projection
|
|
33030
|
+
* never set, and it is `nextRingAt === null` here — the party's *Missed you* (`party/dress.ts`)
|
|
33031
|
+
* and the roster's `unreached` read `declinedAt`, and stand while it does. */
|
|
33032
|
+
ring: external_exports.object({
|
|
33033
|
+
declinedAt: external_exports.string().datetime(),
|
|
33034
|
+
anchorAt: external_exports.string().datetime(),
|
|
33035
|
+
nextRingAt: external_exports.string().datetime().nullable(),
|
|
33036
|
+
step: external_exports.number().int()
|
|
33037
|
+
}).optional(),
|
|
32963
33038
|
/** Why this arrived the way it did, read back off the delivery receipt (`notify/why.ts`).
|
|
32964
33039
|
* Absent for anything never delivered through a push, and for older rows written before
|
|
32965
33040
|
* the reason was recorded. Deliberately a debug affordance, shown small (owner,
|
|
@@ -33024,6 +33099,8 @@ var MissedCallSchema2 = external_exports.enum([
|
|
|
33024
33099
|
"inbox",
|
|
33025
33100
|
"dismiss"
|
|
33026
33101
|
]);
|
|
33102
|
+
var clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
33103
|
+
var QUIET2 = ` Nothing rings from ${clock2(NIGHT2.from)} to ${clock2(NIGHT2.to)} your time; the count waits for morning.`;
|
|
33027
33104
|
var BrokerTuningSchema2 = external_exports.object({
|
|
33028
33105
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
33029
33106
|
ackVerbosity: external_exports.enum(["normal", "none"]).optional(),
|
|
@@ -33058,6 +33135,12 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
33058
33135
|
* must not silently reset this privacy choice. Absent = leave unchanged on
|
|
33059
33136
|
* write, 'hosted' on read (see store.ts). */
|
|
33060
33137
|
voiceMode: external_exports.enum(["hosted", "on_device"]).optional(),
|
|
33138
|
+
/** Talk — after you answer, the next step is read aloud (walk/design.md §6). ALWAYS ON until
|
|
33139
|
+
* turned off (owner, 2026-09-18, #2249): a setting, not a per-walk toggle. Optional, NOT
|
|
33140
|
+
* defaulted, for the same reason `voiceMode` is: a stale client PATCHing the full settings
|
|
33141
|
+
* object must not silently turn it back on. Absent = leave unchanged on write, true on
|
|
33142
|
+
* read (see store.ts). */
|
|
33143
|
+
talk: external_exports.boolean().optional(),
|
|
33061
33144
|
/** Per-user ring budget (#603): calls per rolling day before further calls
|
|
33062
33145
|
* degrade to banner. Absent = the global default (25). A number, never a
|
|
33063
33146
|
* bypass — every account keeps a ceiling. No UI; set per user for testing. */
|
|
@@ -33255,6 +33338,9 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
33255
33338
|
assignee: external_exports.string().nullable().default(null),
|
|
33256
33339
|
/** What the agent last said it was doing; null if it has said nothing. */
|
|
33257
33340
|
progress: external_exports.string().nullable().default(null),
|
|
33341
|
+
/** HOME'S LINE FOR THAT NOTE (owner, 2026-09-23): a few plain words one read wrote from `progress`,
|
|
33342
|
+
* served only while it was written for the current note. Null means show the Goal's name. */
|
|
33343
|
+
progressLine: external_exports.string().nullable().optional(),
|
|
33258
33344
|
reviewPending: external_exports.boolean().default(false),
|
|
33259
33345
|
dueAt: external_exports.string().nullable().default(null),
|
|
33260
33346
|
/** The Goal this one was opened under; null at the root. */
|
|
@@ -33270,8 +33356,13 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
33270
33356
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
33271
33357
|
repo: external_exports.string().nullable().optional(),
|
|
33272
33358
|
createdAt: external_exports.string(),
|
|
33273
|
-
updatedAt: external_exports.string().nullable().default(null)
|
|
33359
|
+
updatedAt: external_exports.string().nullable().default(null),
|
|
33360
|
+
/** When its owner last SAID something about it (`goals.last_progress_at`, written by every
|
|
33361
|
+
* `update_goal` that changes `progress`). `updatedAt` moves for reasons nobody chose — a
|
|
33362
|
+
* state recomputed, a review flag — so it cannot tell work in hand from work gone quiet. */
|
|
33363
|
+
lastProgressAt: external_exports.string().nullable().optional()
|
|
33274
33364
|
});
|
|
33365
|
+
var COLD_AFTER_MS2 = 3 * 24 * 60 * 60 * 1e3;
|
|
33275
33366
|
var NoteSourceSchema2 = external_exports.enum(["app", "call"]);
|
|
33276
33367
|
var NoteStatusSchema2 = external_exports.enum(["open", "assigned", "in_progress", "done"]);
|
|
33277
33368
|
var NoteRepeatSchema2 = external_exports.enum(["once", "until_done"]);
|
|
@@ -33995,9 +34086,9 @@ async function slotHasWaitingWork(token, session) {
|
|
|
33995
34086
|
}
|
|
33996
34087
|
var PRESENCE_FRESH_MS = 3 * 6e4;
|
|
33997
34088
|
function wakeAction(live, now, serverSeenAt) {
|
|
33998
|
-
if (live) return "skip";
|
|
33999
|
-
if (serverSeenAt !== void 0 && now - serverSeenAt < PRESENCE_FRESH_MS) return "skip";
|
|
34000
|
-
return "spawn";
|
|
34089
|
+
if (live) return { action: "skip", why: "run" };
|
|
34090
|
+
if (serverSeenAt !== void 0 && now - serverSeenAt < PRESENCE_FRESH_MS) return { action: "skip", why: "presence" };
|
|
34091
|
+
return { action: "spawn", why: "nobody" };
|
|
34001
34092
|
}
|
|
34002
34093
|
function startHost(opts) {
|
|
34003
34094
|
const runs = /* @__PURE__ */ new Map();
|
|
@@ -34087,7 +34178,11 @@ function startHost(opts) {
|
|
|
34087
34178
|
const live = runs.get(key);
|
|
34088
34179
|
const tokenId = slotIdentity(slot).tokenId;
|
|
34089
34180
|
const seenByServer = tokenId ? presence.get(tokenId) : void 0;
|
|
34090
|
-
|
|
34181
|
+
const decided = wakeAction(!!live, Date.now(), seenByServer);
|
|
34182
|
+
if (decided.action === "skip") {
|
|
34183
|
+
opts.log(decided.why === "run" ? `\u25CC ${slotName(slot) ?? slot} is running here \u2014 its pump types the work in; not spawning` : `\u25CC ${slotName(slot) ?? slot} is working somewhere this host cannot see (heartbeat) \u2014 not spawning`);
|
|
34184
|
+
return;
|
|
34185
|
+
}
|
|
34091
34186
|
const token = readToken(slot);
|
|
34092
34187
|
if (!token) return;
|
|
34093
34188
|
const owner = ownerSession(slot);
|