@paigy/harness 0.3.15 → 0.3.17
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 +302 -137
- package/dist/main.js +297 -136
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -23937,6 +23937,7 @@ async function contact(input, opts = {}) {
|
|
|
23937
23937
|
const headers = { "content-type": "application/json", authorization: `Bearer ${authToken(opts.token) ?? ""}`, "x-paigy-model": "goal-entry-v1" };
|
|
23938
23938
|
let deliveryId;
|
|
23939
23939
|
let deliveries = [];
|
|
23940
|
+
let recorded = [];
|
|
23940
23941
|
if ("deliveryId" in parsed) {
|
|
23941
23942
|
deliveryId = parsed.deliveryId;
|
|
23942
23943
|
} else {
|
|
@@ -23965,16 +23966,24 @@ async function contact(input, opts = {}) {
|
|
|
23965
23966
|
if (!res.ok) await fail("batch contact", res);
|
|
23966
23967
|
const receipt = await res.json();
|
|
23967
23968
|
deliveries = receipt.deliveries ?? [];
|
|
23969
|
+
recorded = receipt.recorded ?? [];
|
|
23970
|
+
if (deliveries.length === 0 && recorded.length) return { sent: false, recorded, message: NOT_SENT };
|
|
23968
23971
|
if (deliveries.length === 0) throw new Error("Goal contact returned no Delivery identities");
|
|
23969
23972
|
deliveryId = deliveries[0]?.deliveryId ?? "";
|
|
23970
23973
|
if (!deliveryId) throw new Error("Goal contact returned no valid Delivery identity");
|
|
23971
23974
|
}
|
|
23972
|
-
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId);
|
|
23975
|
+
const all = deliveries.filter((d) => !!d.deliveryId && !!d.goalId).map(({ id, deliveryId: deliveryId2, goalId }) => ({ ...id ? { id } : {}, deliveryId: deliveryId2, goalId }));
|
|
23976
|
+
const joinedCard = deliveries[0]?.joinedCard === true;
|
|
23973
23977
|
const read = async (signal2) => {
|
|
23974
23978
|
const res = ensureAuthed(await send22(`${BACKEND_URL}/api/deliveries/${encodeURIComponent(deliveryId)}`, { headers, signal: signal2 }));
|
|
23975
23979
|
if (!res.ok) await fail("read_delivery", res);
|
|
23976
23980
|
const one = await res.json();
|
|
23977
|
-
return
|
|
23981
|
+
return {
|
|
23982
|
+
...one,
|
|
23983
|
+
...all.length > 1 ? { deliveries: all } : {},
|
|
23984
|
+
...recorded.length ? { recorded, notSent: NOT_SENT } : {},
|
|
23985
|
+
...joinedCard ? { joinedCard: true } : {}
|
|
23986
|
+
};
|
|
23978
23987
|
};
|
|
23979
23988
|
const settled = (d) => d.kind === "notification" || d.state === "closed" || d.answers.length > 0 || d.entries.some((e) => e.kind === "contribution");
|
|
23980
23989
|
if (opts.waits === false) return read(opts.signal);
|
|
@@ -24002,45 +24011,105 @@ async function checkReplies(opts = {}) {
|
|
|
24002
24011
|
return await res.json();
|
|
24003
24012
|
}
|
|
24004
24013
|
function frame(line, topic, event, payload) {
|
|
24014
|
+
const ref = String(++line.ref);
|
|
24005
24015
|
try {
|
|
24006
|
-
line.ws?.send(JSON.stringify({ topic, event, payload, ref
|
|
24016
|
+
line.ws?.send(JSON.stringify({ topic, event, payload, ref }));
|
|
24007
24017
|
} catch {
|
|
24008
24018
|
}
|
|
24019
|
+
return ref;
|
|
24009
24020
|
}
|
|
24010
24021
|
function stop(line) {
|
|
24011
24022
|
line.open = false;
|
|
24023
|
+
line.beat = void 0;
|
|
24012
24024
|
clearInterval(line.heart);
|
|
24013
24025
|
clearTimeout(line.retry);
|
|
24026
|
+
for (const t of line.topics.values()) {
|
|
24027
|
+
clearTimeout(t.timer);
|
|
24028
|
+
t.joined = false;
|
|
24029
|
+
t.ref = void 0;
|
|
24030
|
+
}
|
|
24031
|
+
}
|
|
24032
|
+
function join3(line, name, t) {
|
|
24033
|
+
clearTimeout(t.timer);
|
|
24034
|
+
t.joined = false;
|
|
24035
|
+
t.ref = frame(line, `realtime:${name}`, "phx_join", JOIN);
|
|
24036
|
+
t.timer = setTimeout(() => rejoin(line, name, t), JOIN_TIMEOUT_MS);
|
|
24037
|
+
}
|
|
24038
|
+
function rejoin(line, name, t) {
|
|
24039
|
+
clearTimeout(t.timer);
|
|
24040
|
+
t.joined = false;
|
|
24041
|
+
t.ref = void 0;
|
|
24042
|
+
if (!line.open || line.topics.get(name) !== t) return;
|
|
24043
|
+
t.timer = setTimeout(() => {
|
|
24044
|
+
if (line.open && line.topics.get(name) === t) join3(line, name, t);
|
|
24045
|
+
}, RETRY_MS[Math.min(t.attempt++, RETRY_MS.length - 1)]);
|
|
24046
|
+
}
|
|
24047
|
+
function joined(t) {
|
|
24048
|
+
clearTimeout(t.timer);
|
|
24049
|
+
t.joined = true;
|
|
24050
|
+
t.attempt = 0;
|
|
24051
|
+
for (const l of t.listeners) l.onJoined?.(++l.joins);
|
|
24014
24052
|
}
|
|
24015
24053
|
function connect(line) {
|
|
24016
24054
|
const base = line.url.replace(/^http/, "ws").replace(/\/+$/, "");
|
|
24017
24055
|
const ws = line.ws = new import_undici.WebSocket(`${base}/realtime/v1/websocket?apikey=${encodeURIComponent(line.anonKey)}&vsn=1.0.0`);
|
|
24056
|
+
let gone = false;
|
|
24057
|
+
const again = () => {
|
|
24058
|
+
if (gone) return;
|
|
24059
|
+
gone = true;
|
|
24060
|
+
stop(line);
|
|
24061
|
+
if (!line.topics.size) return;
|
|
24062
|
+
line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
|
|
24063
|
+
};
|
|
24018
24064
|
ws.addEventListener("open", () => {
|
|
24065
|
+
if (gone) return;
|
|
24019
24066
|
line.open = true;
|
|
24020
24067
|
line.attempt = 0;
|
|
24021
|
-
for (const
|
|
24022
|
-
line.heart = setInterval(() =>
|
|
24068
|
+
for (const [name, t] of line.topics) join3(line, name, t);
|
|
24069
|
+
line.heart = setInterval(() => {
|
|
24070
|
+
if (line.beat) {
|
|
24071
|
+
try {
|
|
24072
|
+
ws.close();
|
|
24073
|
+
} catch {
|
|
24074
|
+
}
|
|
24075
|
+
again();
|
|
24076
|
+
return;
|
|
24077
|
+
}
|
|
24078
|
+
line.beat = frame(line, "phoenix", "heartbeat", {});
|
|
24079
|
+
}, HEARTBEAT_MS);
|
|
24023
24080
|
});
|
|
24024
24081
|
ws.addEventListener("message", (e) => {
|
|
24082
|
+
if (gone) return;
|
|
24025
24083
|
let m;
|
|
24026
24084
|
try {
|
|
24027
24085
|
m = JSON.parse(String(e.data));
|
|
24028
24086
|
} catch {
|
|
24029
24087
|
return;
|
|
24030
24088
|
}
|
|
24031
|
-
if (m.
|
|
24032
|
-
|
|
24089
|
+
if (m.topic === "phoenix") {
|
|
24090
|
+
if (m.event === "phx_reply" && m.ref === line.beat) line.beat = void 0;
|
|
24091
|
+
return;
|
|
24092
|
+
}
|
|
24093
|
+
if (!m.topic?.startsWith("realtime:")) return;
|
|
24094
|
+
const name = m.topic.slice("realtime:".length);
|
|
24095
|
+
const t = line.topics.get(name);
|
|
24096
|
+
if (!t) return;
|
|
24097
|
+
if (m.event === "phx_reply") {
|
|
24098
|
+
if (!t.ref || m.ref !== t.ref) return;
|
|
24099
|
+
if (m.payload?.status === "ok") joined(t);
|
|
24100
|
+
else rejoin(line, name, t);
|
|
24101
|
+
return;
|
|
24102
|
+
}
|
|
24103
|
+
if (m.event === "phx_error" || m.event === "phx_close") {
|
|
24104
|
+
if (m.ref && m.ref !== t.ref) return;
|
|
24105
|
+
rejoin(line, name, t);
|
|
24106
|
+
return;
|
|
24107
|
+
}
|
|
24108
|
+
if (m.event !== "broadcast") return;
|
|
24109
|
+
for (const l of t.listeners) {
|
|
24033
24110
|
if (m.payload?.event === l.event) l.onMessage(m.payload.payload);
|
|
24034
24111
|
}
|
|
24035
24112
|
});
|
|
24036
|
-
let gone = false;
|
|
24037
|
-
const again = () => {
|
|
24038
|
-
if (gone) return;
|
|
24039
|
-
gone = true;
|
|
24040
|
-
stop(line);
|
|
24041
|
-
if (!line.topics.size) return;
|
|
24042
|
-
line.retry = setTimeout(() => connect(line), RETRY_MS[Math.min(line.attempt++, RETRY_MS.length - 1)]);
|
|
24043
|
-
};
|
|
24044
24113
|
ws.addEventListener("close", again);
|
|
24045
24114
|
ws.addEventListener("error", again);
|
|
24046
24115
|
}
|
|
@@ -24048,17 +24117,22 @@ function subscribeRealtime(args) {
|
|
|
24048
24117
|
const key = `${args.url} ${args.anonKey}`;
|
|
24049
24118
|
let line = lines.get(key);
|
|
24050
24119
|
if (!line) lines.set(key, line = { url: args.url, anonKey: args.anonKey, open: false, attempt: 0, ref: 0, topics: /* @__PURE__ */ new Map() });
|
|
24051
|
-
let
|
|
24052
|
-
if (!
|
|
24053
|
-
line.topics.set(args.topic,
|
|
24054
|
-
if (line.open)
|
|
24055
|
-
}
|
|
24056
|
-
const
|
|
24057
|
-
|
|
24120
|
+
let t = line.topics.get(args.topic);
|
|
24121
|
+
if (!t) {
|
|
24122
|
+
line.topics.set(args.topic, t = { listeners: /* @__PURE__ */ new Set(), joined: false, attempt: 0 });
|
|
24123
|
+
if (line.open) join3(line, args.topic, t);
|
|
24124
|
+
}
|
|
24125
|
+
const topic = t;
|
|
24126
|
+
const me = { event: args.event, onMessage: args.onMessage, onJoined: args.onJoined, joins: 0 };
|
|
24127
|
+
topic.listeners.add(me);
|
|
24128
|
+
if (topic.joined && me.onJoined) queueMicrotask(() => {
|
|
24129
|
+
if (topic.joined && topic.listeners.has(me)) me.onJoined?.(++me.joins);
|
|
24130
|
+
});
|
|
24058
24131
|
if (!line.ws) connect(line);
|
|
24059
24132
|
return {
|
|
24060
24133
|
close: () => {
|
|
24061
|
-
if (!
|
|
24134
|
+
if (!topic.listeners.delete(me) || topic.listeners.size) return;
|
|
24135
|
+
clearTimeout(topic.timer);
|
|
24062
24136
|
line.topics.delete(args.topic);
|
|
24063
24137
|
if (line.topics.size) {
|
|
24064
24138
|
if (line.open) frame(line, `realtime:${args.topic}`, "phx_leave", {});
|
|
@@ -24074,7 +24148,7 @@ function subscribeRealtime(args) {
|
|
|
24074
24148
|
};
|
|
24075
24149
|
}
|
|
24076
24150
|
async function subscribeWake(onNudge, opts = {}) {
|
|
24077
|
-
const { release = true, ...client } = opts;
|
|
24151
|
+
const { release = true, onJoined, ...client } = opts;
|
|
24078
24152
|
const cfg = await registerDelivery("self_hosted", client);
|
|
24079
24153
|
const { url, anonKey } = cfg.realtime;
|
|
24080
24154
|
const channel = wakeChannel(cfg.tokenId);
|
|
@@ -24083,7 +24157,8 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
24083
24157
|
anonKey,
|
|
24084
24158
|
topic: channel,
|
|
24085
24159
|
event: WAKE_EVENT,
|
|
24086
|
-
onMessage: (payload) => onNudge(payload ?? {})
|
|
24160
|
+
onMessage: (payload) => onNudge(payload ?? {}),
|
|
24161
|
+
onJoined
|
|
24087
24162
|
});
|
|
24088
24163
|
return {
|
|
24089
24164
|
tokenId: cfg.tokenId,
|
|
@@ -24095,7 +24170,7 @@ async function subscribeWake(onNudge, opts = {}) {
|
|
|
24095
24170
|
}
|
|
24096
24171
|
};
|
|
24097
24172
|
}
|
|
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;
|
|
24173
|
+
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, QueueReplySchema, 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, NOT_SENT, HEARTBEAT_MS, RETRY_MS, JOIN, JOIN_TIMEOUT_MS, lines;
|
|
24099
24174
|
var init_dist = __esm({
|
|
24100
24175
|
"../../packages/sdk/dist/index.js"() {
|
|
24101
24176
|
"use strict";
|
|
@@ -24508,7 +24583,7 @@ var init_dist = __esm({
|
|
|
24508
24583
|
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."),
|
|
24509
24584
|
repo: external_exports.string().optional().describe("Optional repository context."),
|
|
24510
24585
|
ask: external_exports.string().trim().min(1).max(1e4).describe(
|
|
24511
|
-
"
|
|
24586
|
+
"ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 parts of one piece of work under one Goal, separate things as separate Goals \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; a call contact JOINS a call already happening, so several arrive as one call."
|
|
24512
24587
|
),
|
|
24513
24588
|
options: external_exports.array(OptionInputSchema).min(2).max(6).optional()
|
|
24514
24589
|
}).strict();
|
|
@@ -24519,7 +24594,7 @@ var init_dist = __esm({
|
|
|
24519
24594
|
}).strict();
|
|
24520
24595
|
ContactSchema = external_exports.union([StartContactSchema, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict()]);
|
|
24521
24596
|
CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
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}.
|
|
24597
|
+
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}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals. An ask with no options that is not blocking is a report: on a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so).";
|
|
24523
24598
|
CreateGoalSchema = external_exports.object({
|
|
24524
24599
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
24525
24600
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -24566,9 +24641,9 @@ var init_dist = __esm({
|
|
|
24566
24641
|
ClaimGoalSchema = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
24567
24642
|
GetGoalSchema = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
24568
24643
|
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.";
|
|
24569
|
-
UPDATE_GOAL_DESCRIPTION =
|
|
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.";
|
|
24571
|
-
CHECK_REPLIES_DESCRIPTION = "
|
|
24644
|
+
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. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
|
|
24645
|
+
CLAIM_GOAL_DESCRIPTION = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
24646
|
+
CHECK_REPLIES_DESCRIPTION = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. 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 a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
|
|
24572
24647
|
CheckRepliesSchema = external_exports.object({}).strict();
|
|
24573
24648
|
AGENT_TOOLS = [
|
|
24574
24649
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
@@ -24898,9 +24973,9 @@ var init_dist = __esm({
|
|
|
24898
24973
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
24899
24974
|
* the planner declares duplicates instead of asking them twice. */
|
|
24900
24975
|
coveredIds: external_exports.array(external_exports.string()).optional(),
|
|
24901
|
-
/**
|
|
24902
|
-
*
|
|
24903
|
-
info: external_exports.array(external_exports.string().min(1)).
|
|
24976
|
+
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
24977
|
+
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
24978
|
+
info: external_exports.array(external_exports.string().min(1)).default([]),
|
|
24904
24979
|
question: external_exports.string().min(1).nullable(),
|
|
24905
24980
|
/** True on the one turn carrying the agent's own declared question. */
|
|
24906
24981
|
asks: external_exports.boolean().optional(),
|
|
@@ -24935,32 +25010,6 @@ var init_dist = __esm({
|
|
|
24935
25010
|
CLAIM_STALE_MS = 30 * 6e4;
|
|
24936
25011
|
InboxItemSchema = external_exports.object({
|
|
24937
25012
|
id: external_exports.string(),
|
|
24938
|
-
/** The conversation thread + connection this item lives on. Present on the replied
|
|
24939
|
-
* detail — they power History's "Continue" / "New session from this" (#57/#251). */
|
|
24940
|
-
parentId: external_exports.string().optional(),
|
|
24941
|
-
/** THE ARRIVAL this row is one unit of (`notifications.ask_id` → `asks`). A claim is one
|
|
24942
|
-
* arrival and its units are N rows of it, so this — not `parentId` — is what makes a
|
|
24943
|
-
* multi-part notification one thing on screen. The thread is the whole CONVERSATION: it
|
|
24944
|
-
* accumulates every message an agent ever sent, so grouping by it renders a day of
|
|
24945
|
-
* unrelated updates as a single "12-part request". Absent on rows written before the
|
|
24946
|
-
* `asks` table, and on anything that never went through `notify` — both fall back to the
|
|
24947
|
-
* thread, which is what the client did for all rows until now. */
|
|
24948
|
-
askId: external_exports.string().optional(),
|
|
24949
|
-
/** WHERE this unit sat in the message it was cut from (`notifications.seq`). The batch
|
|
24950
|
-
* shares one `created_at` to the microsecond, so without it the author's order is
|
|
24951
|
-
* unrecoverable client-side — a four-paragraph briefing rendered opening-paragraph-last
|
|
24952
|
-
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
24953
|
-
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
24954
|
-
seq: external_exports.number().int().optional(),
|
|
24955
|
-
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
24956
|
-
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
24957
|
-
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
24958
|
-
* screen read "1 of 3", then "1 of 2", then no chip at all, each answer having removed the
|
|
24959
|
-
* only evidence of itself. How big an arrival is, is a fact about the arrival, so the
|
|
24960
|
-
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
24961
|
-
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
24962
|
-
* the one part of its own arrival a single row cannot answer. */
|
|
24963
|
-
units: external_exports.number().int().positive().optional(),
|
|
24964
25013
|
tokenId: external_exports.string().optional(),
|
|
24965
25014
|
status: NotifyStatusSchema,
|
|
24966
25015
|
context: ContextSchema,
|
|
@@ -25005,7 +25054,18 @@ var init_dist = __esm({
|
|
|
25005
25054
|
entryId: external_exports.string(),
|
|
25006
25055
|
title: external_exports.string(),
|
|
25007
25056
|
state: external_exports.enum(["open", "answered"]),
|
|
25008
|
-
answer: external_exports.string().nullable()
|
|
25057
|
+
answer: external_exports.string().nullable(),
|
|
25058
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
25059
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
25060
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
25061
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
25062
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
25063
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
25064
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
25065
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
25066
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
25067
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
25068
|
+
options: external_exports.array(OptionSchema).optional()
|
|
25009
25069
|
})).optional(),
|
|
25010
25070
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
25011
25071
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -25023,10 +25083,20 @@ var init_dist = __esm({
|
|
|
25023
25083
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
25024
25084
|
turn: external_exports.enum(["you", "agent"]).default("you"),
|
|
25025
25085
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
25026
|
-
* the agent (provider-agnostic; set server-side). Absent = no hard error
|
|
25027
|
-
*
|
|
25086
|
+
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
25087
|
+
* error badge + Retry. */
|
|
25028
25088
|
error: external_exports.string().optional(),
|
|
25089
|
+
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
25090
|
+
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
25091
|
+
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
25092
|
+
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
25093
|
+
cold: external_exports.string().datetime().optional(),
|
|
25029
25094
|
clarifies: external_exports.string().optional(),
|
|
25095
|
+
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
25096
|
+
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
25097
|
+
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
25098
|
+
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
25099
|
+
onCall: external_exports.literal(true).optional(),
|
|
25030
25100
|
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
25031
25101
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
25032
25102
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -25075,7 +25145,10 @@ var init_dist = __esm({
|
|
|
25075
25145
|
goalIds: external_exports.array(external_exports.string()),
|
|
25076
25146
|
decisionNeedId: external_exports.string().optional(),
|
|
25077
25147
|
sealed: external_exports.boolean(),
|
|
25078
|
-
goalState: external_exports.string().optional()
|
|
25148
|
+
goalState: external_exports.string().optional(),
|
|
25149
|
+
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
25150
|
+
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
25151
|
+
goalTitle: external_exports.string().optional()
|
|
25079
25152
|
}).optional()
|
|
25080
25153
|
});
|
|
25081
25154
|
APNS_TOKEN_RE = /^[0-9a-fA-F]{64}$/;
|
|
@@ -25174,7 +25247,6 @@ var init_dist = __esm({
|
|
|
25174
25247
|
});
|
|
25175
25248
|
HistoryItemSchema = external_exports.object({
|
|
25176
25249
|
id: external_exports.string(),
|
|
25177
|
-
parentId: external_exports.string(),
|
|
25178
25250
|
/** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
|
|
25179
25251
|
initiator: external_exports.enum(["user", "agent"]),
|
|
25180
25252
|
title: external_exports.string(),
|
|
@@ -25333,6 +25405,24 @@ var init_dist = __esm({
|
|
|
25333
25405
|
* gate at all. */
|
|
25334
25406
|
blocked: external_exports.boolean().default(false)
|
|
25335
25407
|
});
|
|
25408
|
+
QueueReplySchema = external_exports.object({
|
|
25409
|
+
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
25410
|
+
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
25411
|
+
* walk can name it in its zoom. */
|
|
25412
|
+
id: external_exports.string(),
|
|
25413
|
+
/** The Goal the note is on. */
|
|
25414
|
+
goalId: external_exports.string(),
|
|
25415
|
+
/** What the note said. */
|
|
25416
|
+
note: external_exports.string(),
|
|
25417
|
+
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
25418
|
+
deliveryId: external_exports.string(),
|
|
25419
|
+
requestEntryId: external_exports.string(),
|
|
25420
|
+
askedAt: external_exports.string(),
|
|
25421
|
+
/** When the person last replied — the window's start. */
|
|
25422
|
+
repliedAt: external_exports.string(),
|
|
25423
|
+
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
25424
|
+
reply: external_exports.string().nullable()
|
|
25425
|
+
});
|
|
25336
25426
|
QueueItemSchema = external_exports.object({
|
|
25337
25427
|
id: external_exports.string(),
|
|
25338
25428
|
/** One-line headline — the first sentence of the outcome. */
|
|
@@ -25360,6 +25450,10 @@ var init_dist = __esm({
|
|
|
25360
25450
|
blocked: external_exports.boolean().default(false),
|
|
25361
25451
|
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
25362
25452
|
questions: external_exports.array(QueueQuestionSchema).default([]),
|
|
25453
|
+
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
25454
|
+
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
25455
|
+
* not spell an empty list. */
|
|
25456
|
+
replies: external_exports.array(QueueReplySchema).optional(),
|
|
25363
25457
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
25364
25458
|
repo: external_exports.string().nullable().optional(),
|
|
25365
25459
|
createdAt: external_exports.string(),
|
|
@@ -25572,9 +25666,11 @@ var init_dist = __esm({
|
|
|
25572
25666
|
tokenOverride = null;
|
|
25573
25667
|
authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
25574
25668
|
AWAIT_WINDOW_MS = 45e3;
|
|
25669
|
+
NOT_SENT = "Recorded as progress, not sent: nobody was notified. If the person should see it (a result, something to try or check, a finding), contact again and say so; report progress with update_goal.";
|
|
25575
25670
|
HEARTBEAT_MS = 25e3;
|
|
25576
25671
|
RETRY_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
25577
25672
|
JOIN = { config: { broadcast: { self: false } } };
|
|
25673
|
+
JOIN_TIMEOUT_MS = 1e4;
|
|
25578
25674
|
lines = /* @__PURE__ */ new Map();
|
|
25579
25675
|
}
|
|
25580
25676
|
});
|
|
@@ -30823,7 +30919,7 @@ import { hostname } from "os";
|
|
|
30823
30919
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
30824
30920
|
import { execFile as execFile2, execSync } from "child_process";
|
|
30825
30921
|
import { homedir as homedir8 } from "os";
|
|
30826
|
-
import { join as
|
|
30922
|
+
import { join as join8 } from "path";
|
|
30827
30923
|
|
|
30828
30924
|
// src/run.ts
|
|
30829
30925
|
init_dist();
|
|
@@ -30836,6 +30932,7 @@ import { resolve, delimiter as delimiter2 } from "path";
|
|
|
30836
30932
|
|
|
30837
30933
|
// src/harness/acp.ts
|
|
30838
30934
|
var none = { events: [], writes: [] };
|
|
30935
|
+
var NOTHING_TO_RESUME = /* @__PURE__ */ new Set([-32002, -32601]);
|
|
30839
30936
|
function optionFor(options, decision) {
|
|
30840
30937
|
const want = decision.allow ? "allow_once" : "reject_once";
|
|
30841
30938
|
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
@@ -30932,20 +31029,29 @@ var AcpDriver = class {
|
|
|
30932
31029
|
if (msg.id === this.initId) {
|
|
30933
31030
|
this.initId = void 0;
|
|
30934
31031
|
if (msg.error) {
|
|
30935
|
-
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}
|
|
31032
|
+
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}`, fatal: true }], writes: [] };
|
|
30936
31033
|
}
|
|
30937
31034
|
this.sessionNewId = this.nextId++;
|
|
30938
31035
|
return {
|
|
30939
31036
|
events: [],
|
|
30940
|
-
writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } :
|
|
31037
|
+
writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } : this.sessionNew(this.sessionNewId))]
|
|
30941
31038
|
};
|
|
30942
31039
|
}
|
|
30943
31040
|
if (msg.id === this.sessionNewId) {
|
|
30944
31041
|
this.sessionNewId = void 0;
|
|
31042
|
+
const resuming = this.resume !== void 0;
|
|
31043
|
+
if (resuming && msg.error?.code !== void 0 && NOTHING_TO_RESUME.has(msg.error.code)) {
|
|
31044
|
+
this.resume = void 0;
|
|
31045
|
+
this.sessionNewId = this.nextId++;
|
|
31046
|
+
return {
|
|
31047
|
+
events: [{ kind: "error", message: `session/resume found nothing to resume (${msg.error.message}) \u2014 opening a new conversation` }],
|
|
31048
|
+
writes: [frame2(this.sessionNew(this.sessionNewId))]
|
|
31049
|
+
};
|
|
31050
|
+
}
|
|
30945
31051
|
const sessionId2 = msg.error ? void 0 : msg.result?.sessionId ?? this.resume;
|
|
30946
31052
|
if (!sessionId2) {
|
|
30947
31053
|
return {
|
|
30948
|
-
events: [{ kind: "error", message: `${
|
|
31054
|
+
events: [{ kind: "error", message: `${resuming ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}`, fatal: true }],
|
|
30949
31055
|
writes: []
|
|
30950
31056
|
};
|
|
30951
31057
|
}
|
|
@@ -31038,6 +31144,9 @@ var AcpDriver = class {
|
|
|
31038
31144
|
writes: []
|
|
31039
31145
|
};
|
|
31040
31146
|
}
|
|
31147
|
+
sessionNew(id) {
|
|
31148
|
+
return { id, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } };
|
|
31149
|
+
}
|
|
31041
31150
|
/** Send the queued prompts as one turn, if the agent can take one right now. */
|
|
31042
31151
|
flush() {
|
|
31043
31152
|
if (!this.sessionId || this.promptId !== void 0 || !this.queued.length) return [];
|
|
@@ -31083,7 +31192,7 @@ function startSession(opts) {
|
|
|
31083
31192
|
...process.env,
|
|
31084
31193
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
31085
31194
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
31086
|
-
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {},
|
|
31195
|
+
...opts.resume ?? opts.session ? { PAIGY_SESSION_ID: opts.resume ?? opts.session } : {},
|
|
31087
31196
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
31088
31197
|
// bridge's pump typing answers in, so nothing inside it may start a second delivery path.
|
|
31089
31198
|
// Exported on EVERY spawn — `PAIGY_SESSION_ID` above is the conversation to resume, and a
|
|
@@ -31093,7 +31202,11 @@ function startSession(opts) {
|
|
|
31093
31202
|
},
|
|
31094
31203
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31095
31204
|
});
|
|
31205
|
+
let ended2 = false;
|
|
31206
|
+
child.stdin?.on("error", () => {
|
|
31207
|
+
});
|
|
31096
31208
|
const write = (frames) => {
|
|
31209
|
+
if (ended2) return;
|
|
31097
31210
|
for (const f of frames) child.stdin?.write(`${f}
|
|
31098
31211
|
`);
|
|
31099
31212
|
};
|
|
@@ -31102,6 +31215,7 @@ function startSession(opts) {
|
|
|
31102
31215
|
if (text) opts.onEvent({ kind: "error", message: text });
|
|
31103
31216
|
});
|
|
31104
31217
|
child.on("exit", (code) => {
|
|
31218
|
+
ended2 = true;
|
|
31105
31219
|
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
31106
31220
|
opts.onExit?.();
|
|
31107
31221
|
});
|
|
@@ -31130,6 +31244,7 @@ function startSession(opts) {
|
|
|
31130
31244
|
},
|
|
31131
31245
|
stop() {
|
|
31132
31246
|
write(driver.close());
|
|
31247
|
+
ended2 = true;
|
|
31133
31248
|
child.kill();
|
|
31134
31249
|
}
|
|
31135
31250
|
};
|
|
@@ -32486,7 +32601,7 @@ var AskInputSchema2 = external_exports.object({
|
|
|
32486
32601
|
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."),
|
|
32487
32602
|
repo: external_exports.string().optional().describe("Optional repository context."),
|
|
32488
32603
|
ask: external_exports.string().trim().min(1).max(1e4).describe(
|
|
32489
|
-
"
|
|
32604
|
+
"ONE question, and only what is needed to answer it. Several questions are several asks in the array, one each: an answer settles the one ask it was given in the shape that ask declares, so a person who answers the part of a bundled ask that interested them settles nothing and is asked again. Paigy reads every ask once and takes a bundled one apart into its own cards anyway \u2014 parts of one piece of work under one Goal, separate things as separate Goals \u2014 but the words it splits are its reading, not yours. News, progress and findings are their own contact; a call contact JOINS a call already happening, so several arrive as one call."
|
|
32490
32605
|
),
|
|
32491
32606
|
options: external_exports.array(OptionInputSchema2).min(2).max(6).optional()
|
|
32492
32607
|
}).strict();
|
|
@@ -32497,7 +32612,7 @@ var StartContactSchema2 = external_exports.object({
|
|
|
32497
32612
|
}).strict();
|
|
32498
32613
|
var ContactSchema2 = external_exports.union([StartContactSchema2, external_exports.object({ deliveryId: external_exports.string().uuid() }).strict()]);
|
|
32499
32614
|
var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
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}.
|
|
32615
|
+
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}. One question per 'ask'; several questions are several objects in 'asks'. A bundled ask is taken apart into its own cards by Paigy's one intake read \u2014 parts of one piece of work under one Goal, separate things as separate Goals. An ask with no options that is not blocking is a report: on a Goal whose report card is still open it is added to that card, with no new push; one that only says where your work stands while under way is recorded as the Goal's progress, not sent (the result says so).";
|
|
32501
32616
|
var CreateGoalSchema2 = external_exports.object({
|
|
32502
32617
|
outcome: external_exports.string().trim().min(1).max(1e4),
|
|
32503
32618
|
/** The work's NAME (#2115) — one to five words, how a person refers to it out loud ("the night
|
|
@@ -32544,9 +32659,9 @@ var UpdateGoalToolSchema2 = UpdateGoalSchema2.omit({ operationId: true }).extend
|
|
|
32544
32659
|
var ClaimGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid().optional() }).strict();
|
|
32545
32660
|
var GetGoalSchema2 = external_exports.object({ goalId: external_exports.string().uuid() }).strict();
|
|
32546
32661
|
var GET_GOAL_DESCRIPTION2 = "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.";
|
|
32547
|
-
var UPDATE_GOAL_DESCRIPTION2 =
|
|
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.";
|
|
32549
|
-
var CHECK_REPLIES_DESCRIPTION2 = "
|
|
32662
|
+
var UPDATE_GOAL_DESCRIPTION2 = `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. state: done means the work was accomplished, and it can be reopened: when the person says it is not done ("it's not working"), set state: active on that same Goal instead of starting a new one; its parent reopens with it. cancelled is final. Returns the Goal as get_goal reads it, at its new revision.`;
|
|
32663
|
+
var CLAIM_GOAL_DESCRIPTION2 = "Claim the oldest runnable or review-pending Goal you own, or pass goalId to claim that Goal. Another agent's Goal that has gone quiet for 3 days (check_replies lists them as stalledOthers) is taken over when you claim it, and becomes yours to finish or cancel. Returns the Goal as get_goal reads it, and creates or renews the execution lease.";
|
|
32664
|
+
var CHECK_REPLIES_DESCRIPTION2 = "What is waiting for you: your open Deliveries (a request the user started toward you, a handoff), one row each with its Goals, how many decisions are still open, and the newest words in brief; `assigned`, your Goals nobody has started yet, however they became yours (claim_goal({goalId}) starts one); and `review`, your Goals with something new on them \u2014 the user's answers and notes are Entries on the Goal, not Deliveries. 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 a Delivery in full with contact({deliveryId}). Once you have acted on what arrived, update_goal with reviewed: true clears the Goal from `review` and closes the Deliveries addressed to you on it.";
|
|
32550
32665
|
var CheckRepliesSchema2 = external_exports.object({}).strict();
|
|
32551
32666
|
var AGENT_TOOLS2 = [
|
|
32552
32667
|
{ name: "contact", description: CONTACT_DESCRIPTION2, inputSchema: CONTACT_SCHEMA2 },
|
|
@@ -32891,9 +33006,9 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
32891
33006
|
/** Twin coverage (#1089): sibling claim ids this asking turn's answer ALSO settles —
|
|
32892
33007
|
* the planner declares duplicates instead of asking them twice. */
|
|
32893
33008
|
coveredIds: external_exports.array(external_exports.string()).optional(),
|
|
32894
|
-
/**
|
|
32895
|
-
*
|
|
32896
|
-
info: external_exports.array(external_exports.string().min(1)).
|
|
33009
|
+
/** The spoken sentences of the turn, in order. No count: how long a turn is is the brain's call
|
|
33010
|
+
* (owner, 2026-09-25), and a count here refused whole plans. */
|
|
33011
|
+
info: external_exports.array(external_exports.string().min(1)).default([]),
|
|
32897
33012
|
question: external_exports.string().min(1).nullable(),
|
|
32898
33013
|
/** True on the one turn carrying the agent's own declared question. */
|
|
32899
33014
|
asks: external_exports.boolean().optional(),
|
|
@@ -32928,32 +33043,6 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
32928
33043
|
var CLAIM_STALE_MS2 = 30 * 6e4;
|
|
32929
33044
|
var InboxItemSchema2 = external_exports.object({
|
|
32930
33045
|
id: external_exports.string(),
|
|
32931
|
-
/** The conversation thread + connection this item lives on. Present on the replied
|
|
32932
|
-
* detail — they power History's "Continue" / "New session from this" (#57/#251). */
|
|
32933
|
-
parentId: external_exports.string().optional(),
|
|
32934
|
-
/** THE ARRIVAL this row is one unit of (`notifications.ask_id` → `asks`). A claim is one
|
|
32935
|
-
* arrival and its units are N rows of it, so this — not `parentId` — is what makes a
|
|
32936
|
-
* multi-part notification one thing on screen. The thread is the whole CONVERSATION: it
|
|
32937
|
-
* accumulates every message an agent ever sent, so grouping by it renders a day of
|
|
32938
|
-
* unrelated updates as a single "12-part request". Absent on rows written before the
|
|
32939
|
-
* `asks` table, and on anything that never went through `notify` — both fall back to the
|
|
32940
|
-
* thread, which is what the client did for all rows until now. */
|
|
32941
|
-
askId: external_exports.string().optional(),
|
|
32942
|
-
/** WHERE this unit sat in the message it was cut from (`notifications.seq`). The batch
|
|
32943
|
-
* shares one `created_at` to the microsecond, so without it the author's order is
|
|
32944
|
-
* unrecoverable client-side — a four-paragraph briefing rendered opening-paragraph-last
|
|
32945
|
-
* (live 2026-08-10, D35). The API already orders by it; this lets a reader that
|
|
32946
|
-
* re-sorts (grouping, filtering) put an arrival back in the order it was written. */
|
|
32947
|
-
seq: external_exports.number().int().optional(),
|
|
32948
|
-
/** HOW MANY units the arrival was cut into. A device reads a LENS, never the arrival —
|
|
32949
|
-
* `/api/inbox` serves `open`, so the units already settled are gone from it — and a client
|
|
32950
|
-
* counting what it can see is counting what is LEFT. Walking a three-unit ask on the answer
|
|
32951
|
-
* screen read "1 of 3", then "1 of 2", then no chip at all, each answer having removed the
|
|
32952
|
-
* only evidence of itself. How big an arrival is, is a fact about the arrival, so the
|
|
32953
|
-
* server that can still see every row states it. Absent on any row with no `askId`: a
|
|
32954
|
-
* unit knows WHICH ask it came from and WHERE it sat in it, and how many there were is
|
|
32955
|
-
* the one part of its own arrival a single row cannot answer. */
|
|
32956
|
-
units: external_exports.number().int().positive().optional(),
|
|
32957
33046
|
tokenId: external_exports.string().optional(),
|
|
32958
33047
|
status: NotifyStatusSchema2,
|
|
32959
33048
|
context: ContextSchema2,
|
|
@@ -32998,7 +33087,18 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
32998
33087
|
entryId: external_exports.string(),
|
|
32999
33088
|
title: external_exports.string(),
|
|
33000
33089
|
state: external_exports.enum(["open", "answered"]),
|
|
33001
|
-
answer: external_exports.string().nullable()
|
|
33090
|
+
answer: external_exports.string().nullable(),
|
|
33091
|
+
/** WHO ASKED IT (owner, 2026-09-23, Goal a345e906: each agenda row wears its agent's face) — the
|
|
33092
|
+
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
33093
|
+
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
33094
|
+
* author is not an agent this account holds (unpaired since, or a person). */
|
|
33095
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
33096
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
33097
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
33098
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
33099
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
33100
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
33101
|
+
options: external_exports.array(OptionSchema2).optional()
|
|
33002
33102
|
})).optional(),
|
|
33003
33103
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
33004
33104
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -33016,10 +33116,20 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
33016
33116
|
* agent (held in the inbox until the agent replies on the thread). */
|
|
33017
33117
|
turn: external_exports.enum(["you", "agent"]).default("you"),
|
|
33018
33118
|
/** Hard error reason on an awaiting request (turn="agent") — the wake failed to reach
|
|
33019
|
-
* the agent (provider-agnostic; set server-side). Absent = no hard error
|
|
33020
|
-
*
|
|
33119
|
+
* the agent (provider-agnostic; set server-side). Absent = no hard error. Drives the inbox
|
|
33120
|
+
* error badge + Retry. */
|
|
33021
33121
|
error: external_exports.string().optional(),
|
|
33122
|
+
/** WHEN THIS AGENT WORK WENT QUIET (turn="agent"), by the one rule (`coldSince`: three days
|
|
33123
|
+
* with nothing said), or absent while it is not stalled. The inbox's stalled badge reads
|
|
33124
|
+
* this and nothing else (2026-09-23: a 3-minute age rule badged every live Goal stalled,
|
|
33125
|
+
* and "dismiss the stalled ones" cancelled 37 pieces of live work). */
|
|
33126
|
+
cold: external_exports.string().datetime().optional(),
|
|
33022
33127
|
clarifies: external_exports.string().optional(),
|
|
33128
|
+
/** THIS CARD'S QUESTION IS ON A LIVE CALL (owner, 2026-09-24: "Mark it while the call is
|
|
33129
|
+
* live"). Present only while an open Call Delivery carries the card's request Entry — read
|
|
33130
|
+
* off the same open list the card came from, so it clears when the Call does. A card is the
|
|
33131
|
+
* backup for a call not taken; while the call has it, the call is where it is answered. */
|
|
33132
|
+
onCall: external_exports.literal(true).optional(),
|
|
33023
33133
|
/** THE RING, ON THE ITEM (walk/design.md §12 §17, #2251): the last ring on this card was
|
|
33024
33134
|
* declined, and what the ladder will do next — read off the cron's own row, never computed
|
|
33025
33135
|
* on the phone. Present only while a `declined` receipt stands on the card's last Call.
|
|
@@ -33068,7 +33178,10 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
33068
33178
|
goalIds: external_exports.array(external_exports.string()),
|
|
33069
33179
|
decisionNeedId: external_exports.string().optional(),
|
|
33070
33180
|
sealed: external_exports.boolean(),
|
|
33071
|
-
goalState: external_exports.string().optional()
|
|
33181
|
+
goalState: external_exports.string().optional(),
|
|
33182
|
+
/** THAT GOAL'S NAME (#2416) — what Activity's row is headed by, since a row there is one Goal
|
|
33183
|
+
* and the cards it holds sit behind it. Stamped by the same read as `goalState`. */
|
|
33184
|
+
goalTitle: external_exports.string().optional()
|
|
33072
33185
|
}).optional()
|
|
33073
33186
|
});
|
|
33074
33187
|
var APNS_TOKEN_RE2 = /^[0-9a-fA-F]{64}$/;
|
|
@@ -33167,7 +33280,6 @@ var UserSettingsSchema2 = external_exports.object({
|
|
|
33167
33280
|
});
|
|
33168
33281
|
var HistoryItemSchema2 = external_exports.object({
|
|
33169
33282
|
id: external_exports.string(),
|
|
33170
|
-
parentId: external_exports.string(),
|
|
33171
33283
|
/** 'user' = a request you sent; 'agent' = a notification an agent sent you. */
|
|
33172
33284
|
initiator: external_exports.enum(["user", "agent"]),
|
|
33173
33285
|
title: external_exports.string(),
|
|
@@ -33326,6 +33438,24 @@ var QueueQuestionSchema2 = external_exports.object({
|
|
|
33326
33438
|
* gate at all. */
|
|
33327
33439
|
blocked: external_exports.boolean().default(false)
|
|
33328
33440
|
});
|
|
33441
|
+
var QueueReplySchema2 = external_exports.object({
|
|
33442
|
+
/** The card this note was (`deliveryId:requestEntryId`, minted by the server like every card
|
|
33443
|
+
* id) — so the phone can tell a reply it just sent from one the queue already carries, and the
|
|
33444
|
+
* walk can name it in its zoom. */
|
|
33445
|
+
id: external_exports.string(),
|
|
33446
|
+
/** The Goal the note is on. */
|
|
33447
|
+
goalId: external_exports.string(),
|
|
33448
|
+
/** What the note said. */
|
|
33449
|
+
note: external_exports.string(),
|
|
33450
|
+
/** Where it was carried — where a second reply goes (`POST /api/entries`, #2252). */
|
|
33451
|
+
deliveryId: external_exports.string(),
|
|
33452
|
+
requestEntryId: external_exports.string(),
|
|
33453
|
+
askedAt: external_exports.string(),
|
|
33454
|
+
/** When the person last replied — the window's start. */
|
|
33455
|
+
repliedAt: external_exports.string(),
|
|
33456
|
+
/** The person's latest words about it; null when there is nothing readable in them. */
|
|
33457
|
+
reply: external_exports.string().nullable()
|
|
33458
|
+
});
|
|
33329
33459
|
var QueueItemSchema2 = external_exports.object({
|
|
33330
33460
|
id: external_exports.string(),
|
|
33331
33461
|
/** One-line headline — the first sentence of the outcome. */
|
|
@@ -33353,6 +33483,10 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
33353
33483
|
blocked: external_exports.boolean().default(false),
|
|
33354
33484
|
/** Every decision need on it, open or settled — the page decides which to show. */
|
|
33355
33485
|
questions: external_exports.array(QueueQuestionSchema2).default([]),
|
|
33486
|
+
/** Every note on it the person replied to (`QueueReplySchema`) — the page decides which to show.
|
|
33487
|
+
* Optional, not defaulted: absent is none, and every hand-built queue (fixtures, the demo) need
|
|
33488
|
+
* not spell an empty list. */
|
|
33489
|
+
replies: external_exports.array(QueueReplySchema2).optional(),
|
|
33356
33490
|
/** The repository or project identifier this Goal belongs to (#2280), null if untracked. */
|
|
33357
33491
|
repo: external_exports.string().nullable().optional(),
|
|
33358
33492
|
createdAt: external_exports.string(),
|
|
@@ -33721,6 +33855,7 @@ async function ask(state, rail, input) {
|
|
|
33721
33855
|
waiting: "hard",
|
|
33722
33856
|
channel: input.call ? "call" : "notification"
|
|
33723
33857
|
});
|
|
33858
|
+
if ("recorded" in sent) return null;
|
|
33724
33859
|
const entryId = sent.requestEntryIds[0];
|
|
33725
33860
|
if (!entryId) return null;
|
|
33726
33861
|
(state.seen ??= /* @__PURE__ */ new Set()).add(entryId);
|
|
@@ -33796,6 +33931,23 @@ async function finish(result, state, rail, opts) {
|
|
|
33796
33931
|
opts?.log?.(`\u26A0 finish contact failed: ${error.message}`);
|
|
33797
33932
|
}
|
|
33798
33933
|
}
|
|
33934
|
+
async function failedToStart(reason, state, rail, opts) {
|
|
33935
|
+
try {
|
|
33936
|
+
const goalId = state.goal?.id ?? (await rail.claim()).goalId;
|
|
33937
|
+
state.goal = void 0;
|
|
33938
|
+
state.resting = true;
|
|
33939
|
+
if (!goalId) return;
|
|
33940
|
+
await rail.contact({
|
|
33941
|
+
asks: [{ ask: `The session could not start, so nothing is working on this yet.
|
|
33942
|
+
|
|
33943
|
+
${reason}`, parentId: goalId }],
|
|
33944
|
+
waiting: "none",
|
|
33945
|
+
channel: "notification"
|
|
33946
|
+
});
|
|
33947
|
+
} catch (error) {
|
|
33948
|
+
opts?.log?.(`\u26A0 could not report the failed start: ${error.message}`);
|
|
33949
|
+
}
|
|
33950
|
+
}
|
|
33799
33951
|
async function afterTurn(event, state, rail, opts) {
|
|
33800
33952
|
if (!event.failed && !endsWithQuestion(event.result)) {
|
|
33801
33953
|
await finish(event.result, state, rail, opts);
|
|
@@ -33820,10 +33972,16 @@ function runHarness(opts) {
|
|
|
33820
33972
|
let tail = [];
|
|
33821
33973
|
const state = { resting: true };
|
|
33822
33974
|
let session = null;
|
|
33823
|
-
const
|
|
33975
|
+
const speaksAs = opts.resume ?? opts.session;
|
|
33976
|
+
const rail = railFor({ token: opts.token, ...speaksAs ? { reach: reachAs(speaksAs) } : {} });
|
|
33824
33977
|
async function handle(event) {
|
|
33825
33978
|
if (event.kind === "error") {
|
|
33826
33979
|
opts.log(`\u26A0 ${event.message}`);
|
|
33980
|
+
if (event.fatal && running) {
|
|
33981
|
+
opts.log(`\u2716 could not start: ${event.message}`);
|
|
33982
|
+
await failedToStart(event.message, state, rail, { log: opts.log });
|
|
33983
|
+
stop2();
|
|
33984
|
+
}
|
|
33827
33985
|
return;
|
|
33828
33986
|
}
|
|
33829
33987
|
if (event.kind === "permission") {
|
|
@@ -33869,6 +34027,7 @@ function runHarness(opts) {
|
|
|
33869
34027
|
mode: opts.mode,
|
|
33870
34028
|
...opts.token ? { token: opts.token } : {},
|
|
33871
34029
|
...opts.resume ? { resume: opts.resume } : {},
|
|
34030
|
+
...opts.session ? { session: opts.session } : {},
|
|
33872
34031
|
...opts.bin ? { bin: opts.bin } : {},
|
|
33873
34032
|
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
33874
34033
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
@@ -33877,20 +34036,21 @@ function runHarness(opts) {
|
|
|
33877
34036
|
(e) => opts.log(`\u26A0 conversation loop stopped: ${e.message}`)
|
|
33878
34037
|
);
|
|
33879
34038
|
opts.log(`\u25B6 started ${opts.harness} in ${opts.cwd} (${opts.mode} mode)`);
|
|
34039
|
+
function stop2() {
|
|
34040
|
+
running = false;
|
|
34041
|
+
tail = [];
|
|
34042
|
+
cancelAsks(state);
|
|
34043
|
+
session?.stop();
|
|
34044
|
+
session = null;
|
|
34045
|
+
opts.log("\u25A0 stopped");
|
|
34046
|
+
}
|
|
33880
34047
|
return {
|
|
33881
34048
|
send(text) {
|
|
33882
34049
|
session?.send(text);
|
|
33883
34050
|
},
|
|
33884
34051
|
working: () => running && state.resting !== true,
|
|
33885
34052
|
tail: () => [...tail],
|
|
33886
|
-
stop
|
|
33887
|
-
running = false;
|
|
33888
|
-
tail = [];
|
|
33889
|
-
cancelAsks(state);
|
|
33890
|
-
session?.stop();
|
|
33891
|
-
session = null;
|
|
33892
|
-
opts.log("\u25A0 stopped");
|
|
33893
|
-
}
|
|
34053
|
+
stop: stop2
|
|
33894
34054
|
};
|
|
33895
34055
|
}
|
|
33896
34056
|
|
|
@@ -33899,14 +34059,14 @@ init_dist();
|
|
|
33899
34059
|
init_catalog();
|
|
33900
34060
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
33901
34061
|
import { homedir as homedir6 } from "os";
|
|
33902
|
-
import { join as
|
|
34062
|
+
import { join as join7 } from "path";
|
|
33903
34063
|
|
|
33904
34064
|
// src/workspaces.ts
|
|
33905
34065
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
33906
|
-
import { dirname as dirname2, join as
|
|
34066
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
|
|
33907
34067
|
import { homedir as homedir4 } from "os";
|
|
33908
34068
|
function workspacesFile() {
|
|
33909
|
-
return
|
|
34069
|
+
return join4(homedir4(), ".paigy", "workspaces.json");
|
|
33910
34070
|
}
|
|
33911
34071
|
function listWorkspaces(deps) {
|
|
33912
34072
|
try {
|
|
@@ -33941,7 +34101,7 @@ function resolveWakeDir(pinned, deps) {
|
|
|
33941
34101
|
// src/owner.ts
|
|
33942
34102
|
import { closeSync as closeSync2, openSync as openSync2, readSync, readdirSync } from "fs";
|
|
33943
34103
|
import { homedir as homedir5 } from "os";
|
|
33944
|
-
import { join as
|
|
34104
|
+
import { join as join5 } from "path";
|
|
33945
34105
|
var HEAD_BYTES = 64e3;
|
|
33946
34106
|
function readHead(file) {
|
|
33947
34107
|
const fd = openSync2(file, "r");
|
|
@@ -33957,7 +34117,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
33957
34117
|
const m = /^session:([0-9a-f]{8})$/.exec(slot);
|
|
33958
34118
|
if (!m) return null;
|
|
33959
34119
|
const prefix = m[1];
|
|
33960
|
-
const root = deps.projectsDir ??
|
|
34120
|
+
const root = deps.projectsDir ?? join5(homedir5(), ".claude", "projects");
|
|
33961
34121
|
const list = deps.list ?? ((dir) => readdirSync(dir));
|
|
33962
34122
|
const head = deps.head ?? readHead;
|
|
33963
34123
|
let folders;
|
|
@@ -33969,7 +34129,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
33969
34129
|
for (const folder of folders) {
|
|
33970
34130
|
let files;
|
|
33971
34131
|
try {
|
|
33972
|
-
files = list(
|
|
34132
|
+
files = list(join5(root, folder));
|
|
33973
34133
|
} catch {
|
|
33974
34134
|
continue;
|
|
33975
34135
|
}
|
|
@@ -33977,7 +34137,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
33977
34137
|
if (!file) continue;
|
|
33978
34138
|
let text;
|
|
33979
34139
|
try {
|
|
33980
|
-
text = head(
|
|
34140
|
+
text = head(join5(root, folder, file));
|
|
33981
34141
|
} catch {
|
|
33982
34142
|
continue;
|
|
33983
34143
|
}
|
|
@@ -33997,7 +34157,7 @@ function ownerSession(slot, deps = {}) {
|
|
|
33997
34157
|
// src/update.ts
|
|
33998
34158
|
import { execFile } from "child_process";
|
|
33999
34159
|
import { createRequire } from "module";
|
|
34000
|
-
import { dirname as dirname3, join as
|
|
34160
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
34001
34161
|
import { promisify } from "util";
|
|
34002
34162
|
var SERVICE_LABEL = "ai.paigy.harness";
|
|
34003
34163
|
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
@@ -34024,7 +34184,7 @@ async function latestVersion() {
|
|
|
34024
34184
|
}
|
|
34025
34185
|
async function install(version) {
|
|
34026
34186
|
const bin = dirname3(process.execPath);
|
|
34027
|
-
await promisify(execFile)(
|
|
34187
|
+
await promisify(execFile)(join6(bin, "npm"), ["install", "-g", `@paigy/harness@${version}`], {
|
|
34028
34188
|
env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}`, npm_config_update_notifier: "false" },
|
|
34029
34189
|
timeout: 5 * 6e4
|
|
34030
34190
|
});
|
|
@@ -34075,12 +34235,13 @@ function selfUpdater(deps) {
|
|
|
34075
34235
|
}
|
|
34076
34236
|
|
|
34077
34237
|
// src/host.ts
|
|
34078
|
-
var HOST_FILE =
|
|
34238
|
+
var HOST_FILE = join7(homedir6(), ".paigy", "host.json");
|
|
34079
34239
|
var runKey = (slot) => `slot:${slot}`;
|
|
34080
34240
|
var claimedRun = (sessionId2) => {
|
|
34081
34241
|
const slot = sessionSlot(sessionId2);
|
|
34082
34242
|
return { slot, key: runKey(slot) };
|
|
34083
34243
|
};
|
|
34244
|
+
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
34084
34245
|
async function slotHasWaitingWork(token, session) {
|
|
34085
34246
|
return (await checkReplies({ token, ...session ? { reach: reachAs(session) } : {} })).deliveries.length > 0;
|
|
34086
34247
|
}
|
|
@@ -34139,7 +34300,7 @@ function startHost(opts) {
|
|
|
34139
34300
|
mode: "bypass",
|
|
34140
34301
|
prompt: "",
|
|
34141
34302
|
token: spec.token,
|
|
34142
|
-
|
|
34303
|
+
...launchRun(spec),
|
|
34143
34304
|
onExit: () => {
|
|
34144
34305
|
runs.delete(key);
|
|
34145
34306
|
opts.log(`\u2716 ${label} ended`);
|
|
@@ -34441,7 +34602,7 @@ async function pairThisMachine() {
|
|
|
34441
34602
|
}
|
|
34442
34603
|
}
|
|
34443
34604
|
function forwardCodexThreadId() {
|
|
34444
|
-
const cfg =
|
|
34605
|
+
const cfg = join8(process.env.CODEX_HOME || join8(homedir8(), ".codex"), "config.toml");
|
|
34445
34606
|
if (!existsSync5(cfg)) return;
|
|
34446
34607
|
const text = readFileSync5(cfg, "utf8");
|
|
34447
34608
|
const head = "[mcp_servers.paigy]\n";
|
|
@@ -34543,7 +34704,7 @@ async function main() {
|
|
|
34543
34704
|
const wsDeps = { file: workspacesFile() };
|
|
34544
34705
|
for (const dir of parsed.grant) addWorkspace(dir, wsDeps);
|
|
34545
34706
|
if (!listWorkspaces(wsDeps).length) {
|
|
34546
|
-
const guess =
|
|
34707
|
+
const guess = join8(homedir8(), "projects");
|
|
34547
34708
|
if (existsSync5(guess)) {
|
|
34548
34709
|
addWorkspace(guess, wsDeps);
|
|
34549
34710
|
console.log(`\u2713 workspace granted: ${guess} (change with --grant DIR)`);
|
|
@@ -34584,7 +34745,7 @@ async function main() {
|
|
|
34584
34745
|
}
|
|
34585
34746
|
}
|
|
34586
34747
|
try {
|
|
34587
|
-
const settings =
|
|
34748
|
+
const settings = join8(homedir8(), ".claude", "settings.json");
|
|
34588
34749
|
if (which("claude") && existsSync5(settings)) {
|
|
34589
34750
|
const cfg = JSON.parse(readFileSync5(settings, "utf8"));
|
|
34590
34751
|
if (!cfg["statusLine"]) {
|
|
@@ -34642,13 +34803,13 @@ async function main() {
|
|
|
34642
34803
|
<key>SoftResourceLimits</key><dict>
|
|
34643
34804
|
<key>NumberOfFiles</key><integer>65536</integer>
|
|
34644
34805
|
</dict>
|
|
34645
|
-
<key>StandardOutPath</key><string>${
|
|
34646
|
-
<key>StandardErrorPath</key><string>${
|
|
34806
|
+
<key>StandardOutPath</key><string>${join8(homedir8(), ".paigy", "host.log")}</string>
|
|
34807
|
+
<key>StandardErrorPath</key><string>${join8(homedir8(), ".paigy", "host.log")}</string>
|
|
34647
34808
|
</dict></plist>
|
|
34648
34809
|
`;
|
|
34649
|
-
const dir =
|
|
34810
|
+
const dir = join8(homedir8(), "Library", "LaunchAgents");
|
|
34650
34811
|
mkdirSync3(dir, { recursive: true });
|
|
34651
|
-
const path =
|
|
34812
|
+
const path = join8(dir, `${SERVICE_LABEL}.plist`);
|
|
34652
34813
|
writeFileSync4(path, plist);
|
|
34653
34814
|
execSync(`launchctl unload ${path} 2>/dev/null; launchctl load ${path}`, { shell: "/bin/sh" });
|
|
34654
34815
|
console.log(`\u2713 host installed as a login service (${path}) \u2014 logs at ~/.paigy/host.log`);
|
|
@@ -34695,7 +34856,11 @@ async function main() {
|
|
|
34695
34856
|
if (parsed.hatch) {
|
|
34696
34857
|
overrideToken(readToken("Desktop") || null);
|
|
34697
34858
|
const minted = await hatch(parsed.hatch, parsed.voice ?? null);
|
|
34698
|
-
saveToken({ access_token: minted.token, name: minted.name, device: null }, parsed.slot ?? minted.name);
|
|
34859
|
+
saveToken({ access_token: minted.token, name: minted.name ?? parsed.hatch, device: null }, parsed.slot ?? minted.name ?? parsed.hatch);
|
|
34860
|
+
if (!minted.name) {
|
|
34861
|
+
console.log(`\u2713 hatched, unnamed \u2014 you already have an agent called "${parsed.hatch}". Pick another name in the app's Agents screen.`);
|
|
34862
|
+
return;
|
|
34863
|
+
}
|
|
34699
34864
|
console.log(`\u2713 hatched "${minted.name}" \u2014 it's on your phone's Agents screen now.`);
|
|
34700
34865
|
console.log(` speak as it: PAIGY_AGENT="${minted.name}" <any paigy tool>`);
|
|
34701
34866
|
console.log(` run with it: paigy-harness --identity "${minted.name}" --harness codex "\u2026"`);
|