@paigy/harness 0.3.38 → 0.3.39
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 +212 -72
- package/dist/main.js +2506 -2375
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -77,7 +77,7 @@ function runProbe(argv) {
|
|
|
77
77
|
function detect(entry, deps = {}) {
|
|
78
78
|
const find = deps.which ?? which;
|
|
79
79
|
const probe = deps.probe ?? runProbe;
|
|
80
|
-
const base = { name: entry.name, label: entry.label
|
|
80
|
+
const base = { name: entry.name, label: entry.label };
|
|
81
81
|
const cliPath = find(entry.cli);
|
|
82
82
|
if (!cliPath) return { ...base, status: "missing", hint: `${entry.label} is not installed.`, install: entry.installCli };
|
|
83
83
|
const adapterPath = entry.adapter ? find(entry.adapter) : null;
|
|
@@ -109,8 +109,7 @@ var init_catalog = __esm({
|
|
|
109
109
|
authProbe: ["claude", "auth", "status"],
|
|
110
110
|
loginHint: "Run `claude` once in a terminal and sign in.",
|
|
111
111
|
installCli: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
112
|
-
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
113
|
-
models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"]
|
|
112
|
+
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
114
113
|
},
|
|
115
114
|
{
|
|
116
115
|
name: "codex",
|
|
@@ -120,8 +119,7 @@ var init_catalog = __esm({
|
|
|
120
119
|
authProbe: ["codex", "login", "status"],
|
|
121
120
|
loginHint: "Run `codex login` to authenticate.",
|
|
122
121
|
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
123
|
-
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
124
|
-
models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"]
|
|
122
|
+
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
125
123
|
},
|
|
126
124
|
{
|
|
127
125
|
name: "agy",
|
|
@@ -131,8 +129,7 @@ var init_catalog = __esm({
|
|
|
131
129
|
adapter: "paigy-agy-acp",
|
|
132
130
|
authProbe: ["agy", "help"],
|
|
133
131
|
loginHint: "Install and set up Antigravity (`agy`).",
|
|
134
|
-
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
135
|
-
models: ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-pro-exp"]
|
|
132
|
+
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
136
133
|
}
|
|
137
134
|
];
|
|
138
135
|
}
|
|
@@ -301,8 +298,8 @@ var init_ZodError = __esm({
|
|
|
301
298
|
"not_finite"
|
|
302
299
|
]);
|
|
303
300
|
quotelessJson = (obj) => {
|
|
304
|
-
const
|
|
305
|
-
return
|
|
301
|
+
const json3 = JSON.stringify(obj, null, 2);
|
|
302
|
+
return json3.replace(/"([^"]+)":/g, "$1:");
|
|
306
303
|
};
|
|
307
304
|
ZodError = class _ZodError extends Error {
|
|
308
305
|
get errors() {
|
|
@@ -23927,14 +23924,18 @@ async function hatch(name, voice = null, opts = {}) {
|
|
|
23927
23924
|
}
|
|
23928
23925
|
async function whoAmI(opts = {}) {
|
|
23929
23926
|
try {
|
|
23930
|
-
|
|
23931
|
-
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
23932
|
-
});
|
|
23933
|
-
return res.ok ? await res.json() : null;
|
|
23927
|
+
return await readIdentity(opts);
|
|
23934
23928
|
} catch {
|
|
23935
23929
|
return null;
|
|
23936
23930
|
}
|
|
23937
23931
|
}
|
|
23932
|
+
async function readIdentity(opts = {}) {
|
|
23933
|
+
const res = await ensureAuthed(await (opts.reach ?? reach)(`${BACKEND_URL}/api/identity`, {
|
|
23934
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
23935
|
+
}));
|
|
23936
|
+
if (!res.ok) await fail("read_identity", res);
|
|
23937
|
+
return await res.json();
|
|
23938
|
+
}
|
|
23938
23939
|
async function claimSessions(opts = {}) {
|
|
23939
23940
|
const res = await ensureAuthed(await reach(`${BACKEND_URL}/api/sessions/claim`, {
|
|
23940
23941
|
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
@@ -24294,7 +24295,7 @@ ${block2}`;
|
|
|
24294
24295
|
}
|
|
24295
24296
|
return text.slice(0, start) + updated + text.slice(end);
|
|
24296
24297
|
}
|
|
24297
|
-
var import_undici, require2, CODEX_ENV, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, lineage, CLIENT, 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, OPTION_LABEL_MAX, OPTION_HINT_MAX, OptionSchema, OptionInputSchema, NIGHT, ASK_MAX, UNITS_MAX, UNIT_TITLE_MAX, UNIT_BODY_MAX, UnitInputSchema, questionHandle, NodeRef, BlockedAction, reason, actionFits, ACTION_FITS, userExplicitlyRequested, units, UpdateInputSchema, questionFields, refineQuestion, QuestionInputSchema, QuestionWireSchema, AnswerInputSchema, QuestionDependencySchema, sendFields, ContactSchema, sendsAnything, ContactWireSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, UpdateGoalSchema, changedGoal, goalTitle, GOAL_OUTCOME_MAX, goalOutcome, CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange, read, GoalChangeSchema, editNames, ManageGoalsSchema, ManageGoalsToolSchema, MANAGE_GOALS_DESCRIPTION, GetGoalSchema, GET_GOAL_DESCRIPTION, SearchToolSchema, SEARCH_DESCRIPTION, CheckActivitySchema, FEEDBACK_TEXT_MAX, SendFeedbackSchema, SEND_FEEDBACK_DESCRIPTION, AGENT_TOOLS, AGENT_TOOL_NAMES, id, key, participant, SourceSchema, ResultRefSchema, NodeRefSchema, DependencySchema, goalState, existingGoalChanges, BrainGoalChangeSchema, option, QuestionChangeSchema, lessonScope, LessonChangeSchema, BrainNextSchema, messageBase, BrainMessageSchema, BrainSearchSchema, BrainResultSchema, str, strs, CompactResultSchema, COMPACT_RESULT_JSON_SCHEMA, str2, strs2, TalkerReplySchema, TALKER_REPLY_JSON_SCHEMA, str3, strs3, FilerReplySchema, FILER_REPLY_JSON_SCHEMA, ExplainerReplySchema, EXPLAINER_REPLY_JSON_SCHEMA, DISTILLER_DEFECTS, DistillerReplySchema, DISTILLER_REPLY_JSON_SCHEMA, TIDY_AUDIENCES, TidyReplySchema, TIDY_REPLY_JSON_SCHEMA, LIVE_MS, WORKING_MS, WORKING_TIER_MS, RECENT_TIER_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,
|
|
24298
|
+
var import_undici, require2, CODEX_ENV, BACKEND_URL, NETWORK_MSG, PROXY_ENV, agent, INSTANCE_ID, WORKSPACE_ID, envSession, lineage, CLIENT, 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, OPTION_LABEL_MAX, OPTION_HINT_MAX, OptionSchema, OptionInputSchema, NIGHT, ASKING_RULES, ASK_MAX, UNITS_MAX, UNIT_TITLE_MAX, UNIT_BODY_MAX, UnitInputSchema, questionHandle, NodeRef, BlockedAction, reason, actionFits, ACTION_FITS, userExplicitlyRequested, units, UpdateInputSchema, questionFields, refineQuestion, QuestionInputSchema, QuestionWireSchema, AnswerInputSchema, QuestionDependencySchema, sendFields, ContactSchema, sendsAnything, ContactWireSchema, CONTACT_SCHEMA, CONTACT_DESCRIPTION, CreateGoalSchema, UpdateGoalSchema, changedGoal, goalTitle, GOAL_OUTCOME_MAX, goalOutcome, CreateChange, EditChange, StateChange, AssignChange, DeferChange, MoveChange, DependencyChange, read, GoalChangeSchema, editNames, ManageGoalsSchema, ManageGoalsToolSchema, MANAGE_GOALS_DESCRIPTION, GetGoalSchema, GetDeliverySchema, GET_DELIVERY_DESCRIPTION, GET_GOAL_DESCRIPTION, SearchToolSchema, SEARCH_DESCRIPTION, ConfigureIdentitySchema, CONFIGURE_IDENTITY_DESCRIPTION, CheckActivitySchema, FEEDBACK_TEXT_MAX, SendFeedbackSchema, SEND_FEEDBACK_DESCRIPTION, AGENT_TOOLS, AGENT_TOOL_NAMES, id, key, participant, SourceSchema, ResultRefSchema, NodeRefSchema, DependencySchema, goalState, existingGoalChanges, BrainGoalChangeSchema, option, QuestionChangeSchema, lessonScope, LessonChangeSchema, BrainNextSchema, messageBase, BrainMessageSchema, BrainSearchSchema, BrainResultSchema, str, strs, CompactResultSchema, COMPACT_RESULT_JSON_SCHEMA, str2, strs2, TalkerReplySchema, TALKER_REPLY_JSON_SCHEMA, str3, strs3, FilerReplySchema, FILER_REPLY_JSON_SCHEMA, ExplainerReplySchema, EXPLAINER_REPLY_JSON_SCHEMA, DISTILLER_DEFECTS, DistillerReplySchema, DISTILLER_REPLY_JSON_SCHEMA, TIDY_AUDIENCES, TidyReplySchema, TIDY_REPLY_JSON_SCHEMA, LIVE_MS, WORKING_MS, WORKING_TIER_MS, RECENT_TIER_MS, json, clock, HeardEventSchema, HeardCursorSchema, HeardPageSchema, MAX_EVENTS, MAX_BYTES, batchBytes, HeardBatchSchema, 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, clock2, QUIET, BrokerTuningSchema, UserSettingsSchema, HistoryWorkSchema, HistoryEntrySchema, HistoryPageSchema, ACTIVITY_LINES, ACTIVITY_LINE_MAX, AgentActivitySchema, ConnectionSummarySchema, LedgerItemSchema, AgentLedgerSchema, ReassignResultSchema, LessonStateSchema, LessonViewSchema, QueueQuestionSchema, QueueReplySchema, QueueItemSchema, QueueDeltaSchema, COLD_AFTER_MS, NoteSourceSchema, NoteStatusSchema, NoteRepeatSchema, DecisionSchema, NoteSchema, TriageItemSchema, TriageAssignmentSchema, TriageStatusSchema, SubmitTriageSchema, TriageProposalSchema, AcceptTriageSchema, AcceptTriageResultSchema, DeliveryModeSchema, WAKE_EVENT, wakeChannel, RegisterDeliverySchema, OAuthStartSchema, DeliveryConfigSchema, HostDecisionSchema, WakeNudgeSchema, PairingStatusSchema, DeviceCodeSchema, DeviceInfoSchema, DeviceTokenSchema, SupportRequestSchema, NotificationFeedbackKindSchema, SlimOptionSchema, QuestionRowSchema, WorkStateSchema, WorkRowSchema, ComputerRowSchema, AgentRowSchema, SnapshotSchema, CallRecapSchema, TOKEN_PATH, KEY_PATH, sleep, UnpairedError, RevokedError, ApiError, tokenOverride, authToken, AWAIT_WINDOW_MS, NOT_SENT, APPLIED, DEMOTED, HEARTBEAT_MS, RETRY_MS, JOIN, JOIN_TIMEOUT_MS, lines;
|
|
24298
24299
|
var init_dist = __esm({
|
|
24299
24300
|
"../../packages/sdk/dist/index.js"() {
|
|
24300
24301
|
"use strict";
|
|
@@ -24310,6 +24311,7 @@ var init_dist = __esm({
|
|
|
24310
24311
|
init_zod();
|
|
24311
24312
|
init_zod();
|
|
24312
24313
|
init_zod();
|
|
24314
|
+
init_zod();
|
|
24313
24315
|
import_undici = __toESM(require_undici(), 1);
|
|
24314
24316
|
require2 = __sdkCreateRequire(import.meta.url);
|
|
24315
24317
|
CODEX_ENV = ["CODEX_THREAD_ID", "CODEX_SESSION_ID", "CODEX_HOME", "PAIGY_TOKEN", "PAIGY_SESSION_ID", "PAIGY_HARNESS", "PAIGY_INSTANCE_ID", "PAIGY_ON_WAKE"];
|
|
@@ -24718,6 +24720,16 @@ var init_dist = __esm({
|
|
|
24718
24720
|
imgUrl: OptionSchema.shape.image
|
|
24719
24721
|
}).strict();
|
|
24720
24722
|
NIGHT = { from: 23, to: 7 };
|
|
24723
|
+
ASKING_RULES = `HOW TO ASK:
|
|
24724
|
+
1. Write one question per object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
24725
|
+
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
24726
|
+
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
24727
|
+
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. While a Call you placed is open, keep receiving window after window until it ends, even when a window is empty: the person may give follow-ups or instructions. With no open Call, STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goal\` on that wake.
|
|
24728
|
+
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
24729
|
+
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
24730
|
+
7. A question carries its options when you have them; a free-text question may omit them. Each option\u2019s label must stand on its own and name its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Each question is sent exactly as written, with no reading in between. They can always answer in their own words, so never add an 'Other' option.
|
|
24731
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
|
|
24732
|
+
9. GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. What they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Requested delivery and completed-Goal reports require a reply (reply_required); ordinary progress may omit it.`;
|
|
24721
24733
|
ASK_MAX = 250;
|
|
24722
24734
|
UNITS_MAX = 12;
|
|
24723
24735
|
UNIT_TITLE_MAX = 50;
|
|
@@ -24766,10 +24778,18 @@ var init_dist = __esm({
|
|
|
24766
24778
|
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
24767
24779
|
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
24768
24780
|
reply: external_exports.string().trim().min(1).describe(
|
|
24769
|
-
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer.
|
|
24781
|
+
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer. The reply is required for requested delivery and completed Goals; ordinary progress may omit it.`
|
|
24770
24782
|
).optional(),
|
|
24771
24783
|
userExplicitlyRequested
|
|
24772
24784
|
}).strict().superRefine((u, ctx) => {
|
|
24785
|
+
if (u.userExplicitlyRequested && !u.reply) {
|
|
24786
|
+
ctx.addIssue({
|
|
24787
|
+
code: external_exports.ZodIssueCode.custom,
|
|
24788
|
+
path: ["reply"],
|
|
24789
|
+
params: { refusal: "reply_required" },
|
|
24790
|
+
message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
|
|
24791
|
+
});
|
|
24792
|
+
}
|
|
24773
24793
|
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
24774
24794
|
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
24775
24795
|
ctx.addIssue({
|
|
@@ -24857,14 +24877,10 @@ var init_dist = __esm({
|
|
|
24857
24877
|
questions: external_exports.array(QuestionWireSchema).min(1).max(20).optional()
|
|
24858
24878
|
}).strict().refine(sendsAnything, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
24859
24879
|
CONTACT_SCHEMA = { type: "object", ...mcpInputSchema(ContactSchema) };
|
|
24860
|
-
CONTACT_DESCRIPTION =
|
|
24861
|
-
|
|
24862
|
-
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
24880
|
+
CONTACT_DESCRIPTION = "Send to and receive from the person: updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read `results`, where a failed item names why while the rest still apply.\n\n" + ASKING_RULES + `
|
|
24863
24881
|
|
|
24864
24882
|
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX} characters; everything else it needs goes in 'units', up to ${UNITS_MAX} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX} characters and every body at most ${UNIT_BODY_MAX}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
24865
24883
|
|
|
24866
|
-
GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. It is what their button says instead of "Got it", and what they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Nothing is owed on it either way, and a report that writes none offers "Got it".
|
|
24867
|
-
|
|
24868
24884
|
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
24869
24885
|
|
|
24870
24886
|
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
@@ -24970,6 +24986,10 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
24970
24986
|
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
24971
24987
|
diagnose: external_exports.boolean().optional()
|
|
24972
24988
|
}).strict();
|
|
24989
|
+
GetDeliverySchema = external_exports.object({
|
|
24990
|
+
deliveryId: external_exports.string().uuid().describe("The Delivery id returned by contact or receive.")
|
|
24991
|
+
}).strict();
|
|
24992
|
+
GET_DELIVERY_DESCRIPTION = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
|
|
24973
24993
|
GET_GOAL_DESCRIPTION = "Read one Goal (a read never puts you on it), including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
24974
24994
|
SearchToolSchema = external_exports.object({
|
|
24975
24995
|
query: external_exports.string().trim().min(1).max(500),
|
|
@@ -24978,6 +24998,8 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
24978
24998
|
limit: external_exports.number().int().min(1).max(20).optional()
|
|
24979
24999
|
}).strict();
|
|
24980
25000
|
SEARCH_DESCRIPTION = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and replies -- the person's answers, acknowledgements and deferrals (type answer; found by their summary, by the question or report they reply to, or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, the replies to its items, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (a reply carries its kind -- answered, acknowledged or deferred -- whether it is closed (a deferral is not), its summary in under ten words, its question or report, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
25001
|
+
ConfigureIdentitySchema = external_exports.object({}).strict();
|
|
25002
|
+
CONFIGURE_IDENTITY_DESCRIPTION = "Read this authenticated agent\u2019s current name, voice and token ID. Read-only; no arguments. Pairing, renaming and logout are available only through the local configure driver.";
|
|
24981
25003
|
CheckActivitySchema = external_exports.object({}).strict();
|
|
24982
25004
|
FEEDBACK_TEXT_MAX = 5e4;
|
|
24983
25005
|
SendFeedbackSchema = external_exports.object({
|
|
@@ -24987,8 +25009,10 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
24987
25009
|
}).strict();
|
|
24988
25010
|
SEND_FEEDBACK_DESCRIPTION = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
|
|
24989
25011
|
AGENT_TOOLS = [
|
|
25012
|
+
{ name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION, inputSchema: mcpInputSchema(ConfigureIdentitySchema) },
|
|
24990
25013
|
{ name: "contact", description: CONTACT_DESCRIPTION, inputSchema: CONTACT_SCHEMA },
|
|
24991
25014
|
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION, inputSchema: mcpInputSchema(ManageGoalsToolSchema) },
|
|
25015
|
+
{ name: "get_delivery", description: GET_DELIVERY_DESCRIPTION, inputSchema: mcpInputSchema(GetDeliverySchema) },
|
|
24992
25016
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION, inputSchema: mcpInputSchema(GetGoalSchema) },
|
|
24993
25017
|
{ name: "search", description: SEARCH_DESCRIPTION, inputSchema: mcpInputSchema(SearchToolSchema) },
|
|
24994
25018
|
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema(CheckActivitySchema) },
|
|
@@ -25255,6 +25279,42 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
25255
25279
|
WORKING_MS = 60 * 6e4;
|
|
25256
25280
|
WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
25257
25281
|
RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
25282
|
+
json = external_exports.lazy(() => external_exports.union([
|
|
25283
|
+
external_exports.string(),
|
|
25284
|
+
external_exports.number().finite(),
|
|
25285
|
+
external_exports.boolean(),
|
|
25286
|
+
external_exports.null(),
|
|
25287
|
+
external_exports.array(json),
|
|
25288
|
+
external_exports.record(json)
|
|
25289
|
+
]));
|
|
25290
|
+
clock = external_exports.number().int().nonnegative().safe();
|
|
25291
|
+
HeardEventSchema = external_exports.object({
|
|
25292
|
+
eventId: external_exports.string().uuid(),
|
|
25293
|
+
source: external_exports.enum(["phone", "bot"]),
|
|
25294
|
+
sequence: clock,
|
|
25295
|
+
kind: external_exports.string().min(1),
|
|
25296
|
+
phoneAtMs: clock.optional(),
|
|
25297
|
+
botAtMs: clock.optional(),
|
|
25298
|
+
payload: external_exports.record(json)
|
|
25299
|
+
}).strict();
|
|
25300
|
+
HeardCursorSchema = HeardEventSchema.pick({ source: true, sequence: true, eventId: true });
|
|
25301
|
+
HeardPageSchema = external_exports.object({
|
|
25302
|
+
events: external_exports.array(HeardEventSchema).max(200),
|
|
25303
|
+
next: HeardCursorSchema.nullable()
|
|
25304
|
+
}).strict();
|
|
25305
|
+
MAX_EVENTS = 50;
|
|
25306
|
+
MAX_BYTES = 8e4;
|
|
25307
|
+
batchBytes = (events) => {
|
|
25308
|
+
let bytes = 0;
|
|
25309
|
+
for (const char of JSON.stringify({ events })) {
|
|
25310
|
+
const point = char.codePointAt(0);
|
|
25311
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
25312
|
+
}
|
|
25313
|
+
return bytes;
|
|
25314
|
+
};
|
|
25315
|
+
HeardBatchSchema = external_exports.object({
|
|
25316
|
+
events: external_exports.array(HeardEventSchema).min(1).max(MAX_EVENTS)
|
|
25317
|
+
}).strict().refine((value) => batchBytes(value.events) <= MAX_BYTES, "heard_batch_too_large");
|
|
25258
25318
|
ContextSchema = external_exports.object({
|
|
25259
25319
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
25260
25320
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -25811,8 +25871,8 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
25811
25871
|
"inbox",
|
|
25812
25872
|
"dismiss"
|
|
25813
25873
|
]);
|
|
25814
|
-
|
|
25815
|
-
QUIET = ` Nothing rings from ${
|
|
25874
|
+
clock2 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
25875
|
+
QUIET = ` Nothing rings from ${clock2(NIGHT.from)} to ${clock2(NIGHT.to)} your time; the count waits for morning.`;
|
|
25816
25876
|
BrokerTuningSchema = external_exports.object({
|
|
25817
25877
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
25818
25878
|
ackVerbosity: external_exports.enum(["normal", "none"]).optional(),
|
|
@@ -26127,7 +26187,13 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
26127
26187
|
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
26128
26188
|
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
26129
26189
|
* none was. The Work row reads it before `said`. */
|
|
26130
|
-
summary: external_exports.string().optional()
|
|
26190
|
+
summary: external_exports.string().optional(),
|
|
26191
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
26192
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
26193
|
+
titles: external_exports.array(external_exports.string()).optional(),
|
|
26194
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
26195
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
26196
|
+
line: external_exports.string().optional()
|
|
26131
26197
|
}).nullable().optional(),
|
|
26132
26198
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
26133
26199
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -26501,7 +26567,9 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
26501
26567
|
words: external_exports.string(),
|
|
26502
26568
|
atMs: external_exports.number()
|
|
26503
26569
|
})),
|
|
26504
|
-
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
26570
|
+
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() }),
|
|
26571
|
+
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
26572
|
+
next: external_exports.number().int().nonnegative().nullable().optional()
|
|
26505
26573
|
});
|
|
26506
26574
|
TOKEN_PATH = join22(homedir22(), ".paigy", "token.json");
|
|
26507
26575
|
KEY_PATH = join22(homedir22(), ".paigy", "key.json");
|
|
@@ -31430,11 +31498,11 @@ function extractJson(raw) {
|
|
|
31430
31498
|
return null;
|
|
31431
31499
|
}
|
|
31432
31500
|
function parseVerdicts(raw, knownIds, knownAgents) {
|
|
31433
|
-
const
|
|
31434
|
-
if (!
|
|
31501
|
+
const json3 = extractJson(raw);
|
|
31502
|
+
if (!json3) return { ok: false, error: "no JSON found in the model's answer", raw };
|
|
31435
31503
|
let parsed;
|
|
31436
31504
|
try {
|
|
31437
|
-
parsed = JSON.parse(
|
|
31505
|
+
parsed = JSON.parse(json3);
|
|
31438
31506
|
} catch (e) {
|
|
31439
31507
|
return { ok: false, error: `JSON.parse failed: ${e.message}`, raw };
|
|
31440
31508
|
}
|
|
@@ -31680,13 +31748,17 @@ var init_triage = __esm({
|
|
|
31680
31748
|
init_catalog();
|
|
31681
31749
|
|
|
31682
31750
|
// src/cli-args.ts
|
|
31751
|
+
init_zod();
|
|
31683
31752
|
var TRIAGE_VIA = ["ollama", "claude", "codex", "agy"];
|
|
31684
31753
|
var USAGE = "usage: paigy-harness setup | pair | host [--grant DIR]\u2026 | service | enable-tools [--scope user|project] | hatch NAME [--voice KEY] [--slot SLOT] | launch NAME [--under GOAL] [--voice KEY] [--harness \u2026] [--mode \u2026] [--cwd DIR] BRIEF\u2026 | handoff | triage [--via ollama|claude|codex|agy] [--json] [--apply [--yes-close] [--yes-stale] [--yes-assign]] | [--harness claude|codex|agy] [--mode bypass|ask] [--cwd DIR] [--grace SECONDS] [--doctor] PROMPT\u2026";
|
|
31685
31754
|
function parseArgs(argv) {
|
|
31686
31755
|
const args = { harness: "claude", mode: "bypass", cwd: process.cwd(), doctor: false, help: false, host: false, pair: false, service: false, setup: false, enableTools: false, scope: "user", grant: [], hatch: null, voice: null, slot: null, launch: null, under: null, graceSeconds: 90, prompt: "", handoff: false, triage: false, via: null, json: false, apply: false, yesClose: false, yesStale: false, yesAssign: false };
|
|
31687
31756
|
const words2 = [];
|
|
31757
|
+
let commandPosition = true;
|
|
31688
31758
|
for (let i = 0; i < argv.length; i++) {
|
|
31689
31759
|
const arg = argv[i];
|
|
31760
|
+
const command = commandPosition && !!arg && !arg.startsWith("-");
|
|
31761
|
+
if (command) commandPosition = false;
|
|
31690
31762
|
const next = () => argv[++i];
|
|
31691
31763
|
if (arg === "--harness") {
|
|
31692
31764
|
const v = next();
|
|
@@ -31702,7 +31774,7 @@ function parseArgs(argv) {
|
|
|
31702
31774
|
args.cwd = v;
|
|
31703
31775
|
} else if (arg === "--doctor") {
|
|
31704
31776
|
args.doctor = true;
|
|
31705
|
-
} else if (arg === "-h" || arg === "--help" || arg === "help") {
|
|
31777
|
+
} else if (arg === "-h" || arg === "--help" || command && arg === "help") {
|
|
31706
31778
|
args.help = true;
|
|
31707
31779
|
} else if (arg === "--scope") {
|
|
31708
31780
|
const v = next();
|
|
@@ -31712,21 +31784,21 @@ function parseArgs(argv) {
|
|
|
31712
31784
|
const v = Number(next());
|
|
31713
31785
|
if (!Number.isFinite(v) || v < 0) return { error: "--grace needs seconds (0 = straight to phone)" };
|
|
31714
31786
|
args.graceSeconds = v;
|
|
31715
|
-
} else if (arg === "host") {
|
|
31787
|
+
} else if (command && arg === "host") {
|
|
31716
31788
|
args.host = true;
|
|
31717
|
-
} else if (arg === "pair") {
|
|
31789
|
+
} else if (command && arg === "pair") {
|
|
31718
31790
|
args.pair = true;
|
|
31719
|
-
} else if (arg === "setup") {
|
|
31791
|
+
} else if (command && arg === "setup") {
|
|
31720
31792
|
args.setup = true;
|
|
31721
|
-
} else if (arg === "service") {
|
|
31793
|
+
} else if (command && arg === "service") {
|
|
31722
31794
|
args.service = true;
|
|
31723
31795
|
} else if (arg === "--grant") {
|
|
31724
31796
|
const v = next();
|
|
31725
31797
|
if (!v) return { error: "--grant needs a folder to allow" };
|
|
31726
31798
|
args.grant.push(v);
|
|
31727
|
-
} else if (arg === "handoff") {
|
|
31799
|
+
} else if (command && arg === "handoff") {
|
|
31728
31800
|
args.handoff = true;
|
|
31729
|
-
} else if (arg === "triage") {
|
|
31801
|
+
} else if (command && arg === "triage") {
|
|
31730
31802
|
args.triage = true;
|
|
31731
31803
|
} else if (arg === "--via") {
|
|
31732
31804
|
const v = next();
|
|
@@ -31743,9 +31815,9 @@ function parseArgs(argv) {
|
|
|
31743
31815
|
args.yesStale = true;
|
|
31744
31816
|
} else if (arg === "--yes-assign") {
|
|
31745
31817
|
args.yesAssign = true;
|
|
31746
|
-
} else if (arg === "enable-tools") {
|
|
31818
|
+
} else if (command && arg === "enable-tools") {
|
|
31747
31819
|
args.enableTools = true;
|
|
31748
|
-
} else if (arg === "hatch") {
|
|
31820
|
+
} else if (command && arg === "hatch") {
|
|
31749
31821
|
const v = next();
|
|
31750
31822
|
if (!v) return { error: 'hatch needs a name: paigy-harness hatch "Voice bug hunter"' };
|
|
31751
31823
|
args.hatch = v;
|
|
@@ -31755,12 +31827,14 @@ function parseArgs(argv) {
|
|
|
31755
31827
|
args.slot = next() ?? null;
|
|
31756
31828
|
} else if (arg === "--identity") {
|
|
31757
31829
|
return { error: '--identity is gone: start a helper with `paigy-harness launch NAME "brief"`, which gives it its own session' };
|
|
31758
|
-
} else if (arg === "launch") {
|
|
31830
|
+
} else if (command && arg === "launch") {
|
|
31759
31831
|
const v = next();
|
|
31760
31832
|
if (!v) return { error: 'launch needs a name: paigy-harness launch "Deploy check" "brief"' };
|
|
31761
31833
|
args.launch = v;
|
|
31762
31834
|
} else if (arg === "--under") {
|
|
31763
|
-
|
|
31835
|
+
const v = next();
|
|
31836
|
+
if (!v || !external_exports.string().uuid().safeParse(v).success) return { error: "--under needs a Goal UUID" };
|
|
31837
|
+
args.under = v;
|
|
31764
31838
|
} else if (arg?.startsWith("--")) {
|
|
31765
31839
|
return { error: `unknown flag ${arg}` };
|
|
31766
31840
|
} else if (arg) {
|
|
@@ -31769,6 +31843,7 @@ function parseArgs(argv) {
|
|
|
31769
31843
|
}
|
|
31770
31844
|
args.prompt = words2.join(" ");
|
|
31771
31845
|
if (args.help) return args;
|
|
31846
|
+
if (args.under && !args.launch) return { error: "--under requires launch NAME" };
|
|
31772
31847
|
if (args.launch && !args.prompt) return { error: 'launch needs a brief: paigy-harness launch "Deploy check" "brief"' };
|
|
31773
31848
|
if (!args.doctor && !args.handoff && !args.hatch && !args.host && !args.pair && !args.service && !args.setup && !args.enableTools && !args.triage && !args.prompt) return { error: "a prompt is required (or setup / --doctor / pair / host / service / enable-tools / hatch NAME / handoff / triage)" };
|
|
31774
31849
|
if ((args.yesClose || args.yesStale || args.yesAssign) && !args.apply)
|
|
@@ -32192,8 +32267,7 @@ function startSession(opts) {
|
|
|
32192
32267
|
if (!existsSync3(cwd)) {
|
|
32193
32268
|
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
32194
32269
|
}
|
|
32195
|
-
|
|
32196
|
-
if (opts.model && opts.harness === "codex") codexConfig = { ...codexConfig, model: opts.model };
|
|
32270
|
+
const codexConfig = opts.harness === "codex" ? JSON.parse(process.env.CODEX_CONFIG || "{}") : null;
|
|
32197
32271
|
if (codexConfig) {
|
|
32198
32272
|
const key3 = "mcp_servers.paigy.env_vars";
|
|
32199
32273
|
const configPath = join5(process.env.CODEX_HOME || join5(homedir4(), ".codex"), "config.toml");
|
|
@@ -32219,8 +32293,6 @@ function startSession(opts) {
|
|
|
32219
32293
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
32220
32294
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
32221
32295
|
...opts.instance ? { PAIGY_INSTANCE_ID: opts.instance } : {},
|
|
32222
|
-
...opts.model && opts.harness === "claude" ? { CLAUDE_MODEL_CONFIG: JSON.stringify({ provider: "anthropic", model: opts.model }) } : {},
|
|
32223
|
-
...opts.model && opts.harness === "agy" ? { AGY_MODEL_CONFIG: JSON.stringify({ model: opts.model }) } : {},
|
|
32224
32296
|
...codexConfig ? { CODEX_CONFIG: JSON.stringify(codexConfig) } : {},
|
|
32225
32297
|
...opts.session ?? opts.resume ? { PAIGY_SESSION_ID: opts.session ?? opts.resume } : {},
|
|
32226
32298
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
@@ -33599,6 +33671,7 @@ init_zod();
|
|
|
33599
33671
|
init_zod();
|
|
33600
33672
|
init_zod();
|
|
33601
33673
|
init_zod();
|
|
33674
|
+
init_zod();
|
|
33602
33675
|
var OPTIONS_MIN2 = 2;
|
|
33603
33676
|
var OPTIONS_MAX2 = 6;
|
|
33604
33677
|
var OPTION_LABEL_MAX2 = 50;
|
|
@@ -33628,6 +33701,16 @@ var OptionInputSchema2 = external_exports.object({
|
|
|
33628
33701
|
imgUrl: OptionSchema2.shape.image
|
|
33629
33702
|
}).strict();
|
|
33630
33703
|
var NIGHT2 = { from: 23, to: 7 };
|
|
33704
|
+
var ASKING_RULES2 = `HOW TO ASK:
|
|
33705
|
+
1. Write one question per object. Five questions are five objects in \`questions\`, in one contact, so each can be answered on its own; one question with five parts settles nothing until all five are answered.
|
|
33706
|
+
2. Name the Goal you are working on as the question's \`goalId\`: the question goes onto that Goal and its answer comes back there. It must exist: create it with manage_goals first.
|
|
33707
|
+
3. \`workItBlocks\` only for work you are truly blocked on (it asks for a call); leave it out for a question you can keep working around.
|
|
33708
|
+
4. Never hold the process open with while-loops. A wait that comes back \`expired\` is not a finished conversation: wait again with contact({wait:true}) only while you have nothing else to do. While a Call you placed is open, keep receiving window after window until it ends, even when a window is empty: the person may give follow-ups or instructions. With no open Call, STOP WAITING when a window comes back with nothing new \u2014 the question stays open, and its answer reaches you on the Goal. Otherwise, yield only with a working listener or scheduled wakeup, and collect answers with \`contact({wait:false})\` or \`get_goal\` on that wake.
|
|
33709
|
+
5. One question, one row. Never restate a question that is still waiting inside a new contact: keep waiting on the original, or the answer lands on one copy and the other stays open. Take back one that no longer matters with \`withdrawQuestionIds\`.
|
|
33710
|
+
6. Read the Goal's conversation before asking. Never ask again what was answered or already shipped \u2014 and tell its states apart: \`answered\` carries the choice in \`answer\`, while \`replied\` means they said something and chose none of your options, so that question is still yours to settle and their words are the line beside it.
|
|
33711
|
+
7. A question carries its options when you have them; a free-text question may omit them. Each option\u2019s label must stand on its own and name its choice in full -- never \`All three\` or \`Option 2\`. \`pickMode: "many"\` makes the card a checklist (options they may want several of); \`"one"\`, the default, a pick (one rules out the others); \`"rank"\` an ordering. Each question is sent exactly as written, with no reading in between. They can always answer in their own words, so never add an 'Other' option.
|
|
33712
|
+
8. A diagnosis says when, why and how it happens, then proposes one fix. Never options first.
|
|
33713
|
+
9. GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. What they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Requested delivery and completed-Goal reports require a reply (reply_required); ordinary progress may omit it.`;
|
|
33631
33714
|
function draft20202(node) {
|
|
33632
33715
|
if (Array.isArray(node)) return node.map(draft20202);
|
|
33633
33716
|
if (node && typeof node === "object") {
|
|
@@ -33746,10 +33829,18 @@ var UpdateInputSchema2 = external_exports.object({
|
|
|
33746
33829
|
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
33747
33830
|
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
33748
33831
|
reply: external_exports.string().trim().min(1).describe(
|
|
33749
|
-
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX2} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer.
|
|
33832
|
+
`What this report's button says instead of "Got it", at most ${OPTION_LABEL_MAX2} characters, written in THEIR voice for THIS report ("Sounds good, I'll test later") -- the care you give a question's options, so what they tap tells you whether they verified your work or only received it. One tap sends exactly these words back to you. Words that would CHANGE what happens next are a question, not a reply to a report: it still asks nothing and still owes you no answer. The reply is required for requested delivery and completed Goals; ordinary progress may omit it.`
|
|
33750
33833
|
).optional(),
|
|
33751
33834
|
userExplicitlyRequested: userExplicitlyRequested2
|
|
33752
33835
|
}).strict().superRefine((u, ctx) => {
|
|
33836
|
+
if (u.userExplicitlyRequested && !u.reply) {
|
|
33837
|
+
ctx.addIssue({
|
|
33838
|
+
code: external_exports.ZodIssueCode.custom,
|
|
33839
|
+
path: ["reply"],
|
|
33840
|
+
params: { refusal: "reply_required" },
|
|
33841
|
+
message: "reply_required: an update requested for delivery needs one reply, written in the person\u2019s voice for this report."
|
|
33842
|
+
});
|
|
33843
|
+
}
|
|
33753
33844
|
refuseOverCaps2(u.message, u.units, "message", ctx);
|
|
33754
33845
|
if (u.reply && u.reply.length > OPTION_LABEL_MAX2) {
|
|
33755
33846
|
ctx.addIssue({
|
|
@@ -33837,14 +33928,10 @@ var ContactWireSchema2 = external_exports.object({
|
|
|
33837
33928
|
questions: external_exports.array(QuestionWireSchema2).min(1).max(20).optional()
|
|
33838
33929
|
}).strict().refine(sendsAnything2, "a contact sends at least one update, question, answer, withdrawal or question dependency");
|
|
33839
33930
|
var CONTACT_SCHEMA2 = { type: "object", ...mcpInputSchema2(ContactSchema2) };
|
|
33840
|
-
var CONTACT_DESCRIPTION2 =
|
|
33841
|
-
|
|
33842
|
-
QUESTIONS: one question per object; several questions are several objects in one contact, so each can be answered on its own. Each is sent exactly as written, with no reading in between: a bundled question arrives as one card. Give options when you have them. Each option's label must stand on its own \u2014 the person may see only the labels \u2014 so never a label that points into your text ('All three', 'Option 2', '1 and 3 only'). pickMode:'one' (the default) makes a pick, for alternatives; 'many' a checklist, for independent options they may want several of; 'rank' an ordering. The person can always answer in their own words, so never add an 'Other' option. workItBlocks names the work a question holds up, only when you are truly blocked: it asks for a call. Answers land on the question's Goal: collect them by receiving (below) or with get_goal.
|
|
33931
|
+
var CONTACT_DESCRIPTION2 = "Send to and receive from the person: updates, questions, answers to questions addressed to you, withdrawals of your own questions, and what a question holds up. Every Goal you name must already exist: create it with manage_goals first; an unknown Goal is refused and nothing is sent. Every item applies on its own: read `results`, where a failed item names why while the rest still apply.\n\n" + ASKING_RULES2 + `
|
|
33843
33932
|
|
|
33844
33933
|
CUT A LONG MESSAGE BEFORE YOU SEND IT: a question, an update's message or an answer is the one point, in at most ${ASK_MAX2} characters; everything else it needs goes in 'units', up to ${UNITS_MAX2} {title, body} in reading order, one idea each, every title at most ${UNIT_TITLE_MAX2} characters and every body at most ${UNIT_BODY_MAX2}. The person sees the main text and the titles, and opens a unit to read its body. Over a cap is refused by name (ask_too_long, too_many_units, unit_title_too_long, unit_body_too_long) and nothing is sent.
|
|
33845
33934
|
|
|
33846
|
-
GIVE A REPORT ITS REPLY: an update takes \`reply\` too, one line the person can tap instead of typing, written in THEIR voice for THAT report ("Sounds good, I'll test later") \u2014 the same care you give a question's options. It is what their button says instead of "Got it", and what they tap is what you read back, which tells you whether they verified your work or only received it. One line, because a report has one way to close, and it may not change what happens next: a reply that would is a question. Nothing is owed on it either way, and a report that writes none offers "Got it".
|
|
33847
|
-
|
|
33848
33935
|
UPDATES ask nothing, so no answer is owed and none should be awaited. An update reaches the person only when they asked you for it (userExplicitlyRequested), when it answers something they said, or once its Goal is done; any other is recorded as the Goal's progress and nobody is notified. So when the work is finished, mark the Goal done first (manage_goals), then send one update saying what is done and anything they need to do or check. On a Goal whose report card is still open, an update that reaches them is added to that card, with no new push. If the person is already on a call, anything that reaches them joins that call, with no ring.
|
|
33849
33936
|
|
|
33850
33937
|
RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of what is addressed to you (not a history page): \`question\`, a Question you owe (answer it in answers, with its questionId); \`update\`, something new on one of your Goals (a reply, an answer: read it with get_goal); \`instruction\`, a request or note sent to you. \`hasMore\` says more are waiting. Reading acknowledges nothing: once you have handled events, confirm their eventIds with contact({ackEventIds}), and the next batch can come. contact({}) waits up to about 45 seconds for something to arrive; contact({wait:false}) returns at once. It also lists work given to you that nobody has started (\`assigned\`; your first write to it starts it) and your work gone quiet (\`stalled\`).`;
|
|
@@ -33950,6 +34037,10 @@ var GetGoalSchema2 = external_exports.object({
|
|
|
33950
34037
|
* between two of them named. Off by default; `docs/model/goal/diagnose-design.md`. */
|
|
33951
34038
|
diagnose: external_exports.boolean().optional()
|
|
33952
34039
|
}).strict();
|
|
34040
|
+
var GetDeliverySchema2 = external_exports.object({
|
|
34041
|
+
deliveryId: external_exports.string().uuid().describe("The Delivery id returned by contact or receive.")
|
|
34042
|
+
}).strict();
|
|
34043
|
+
var GET_DELIVERY_DESCRIPTION2 = "Read a Delivery in full: its whole ask, units, options and answers, plus current callState and callOutcome for a Call. Any agent of the same account may read it; another account is not disclosed. Reading never acknowledges or resends anything. Use this after a call or when receive names a Call you are waiting on.";
|
|
33953
34044
|
var GET_GOAL_DESCRIPTION2 = "Read one Goal (a read never puts you on it), including others (the ten most recent other contributors, with names, latest entry headlines and times): its outcome, state, revision, progress, blockers, the conversation on it (each question with its options and what was decided), `next`, the one step to take, and `more`, where to read further. A question in that conversation reads `open` (nothing yet), `answered` (a choice was made, and `answer` carries it), `replied` (they said something and the read settled the question on their words \u2014 NO option of yours was chosen, and the words are the reply line beside it, so read that before you act), or `closed`. The conversation carries everything the person said, every open question and your newest entry; your older entries are left out and counted \u2014 history: true reads every entry in full. Every Goal of your person is readable, whichever of their agents owns it; another account's Goals are not disclosed.\n\ndiagnose: true is for when a read has CONFUSED you \u2014 not for the working loop. It adds `diagnosis`, which asks every reader that already answers a question about your work and NAMES the reader behind every value, so you never guess which of two places to look: what state your work is really in (the stored `goals.state` beside the derived `goal_execution_state`, which can differ); whether anything is armed to ring and when (`ladder_candidates`, with the ladder's own last decision); whether a wake fired for you and what it did (durable `wake.*` events, beside when your process was last heard from); and what has reached you (`list_waiting` \u2014 review flags, unstarted work, open questions, the person's newest words). Read `diagnosis.disagreements` FIRST: each one is two readers giving different values for the same fact, with both values and both sources, and it never chooses between them \u2014 that is yours to do, from the Goal's own history. `diagnosis.looked` names every reader asked, including any that could not answer, so an empty answer is never confused with a broken one.";
|
|
33954
34045
|
var SearchToolSchema2 = external_exports.object({
|
|
33955
34046
|
query: external_exports.string().trim().min(1).max(500),
|
|
@@ -33958,6 +34049,8 @@ var SearchToolSchema2 = external_exports.object({
|
|
|
33958
34049
|
limit: external_exports.number().int().min(1).max(20).optional()
|
|
33959
34050
|
}).strict();
|
|
33960
34051
|
var SEARCH_DESCRIPTION2 = "Search your person's history across all of their agents: Entries (what anyone said or wrote, typed or spoken on a call), Goals (by title and outcome) and replies -- the person's answers, acknowledgements and deferrals (type answer; found by their summary, by the question or report they reply to, or by the words that gave them). query is words to look for; records sharing more of its words rank first, and exact names work. types narrows it to entry, goal and/or answer (default: all three). goalId searches under one Goal: its Entries, the replies to its items, and it and its immediate children. limit is matches per type, 1 to 20 (default 8). Read-only. Each match carries its whole saved words, its ID and its links (a reply carries its kind -- answered, acknowledged or deferred -- whether it is closed (a deferral is not), its summary in under ten words, its question or report, the choice made and the Entries that support it); `omitted` counts what matched but was left out, so narrow the words or add a goalId to see it. Nothing found is not proof that nothing exists; a refused search says why. Sealed (encrypted) content is never searched or returned.";
|
|
34052
|
+
var ConfigureIdentitySchema2 = external_exports.object({}).strict();
|
|
34053
|
+
var CONFIGURE_IDENTITY_DESCRIPTION2 = "Read this authenticated agent\u2019s current name, voice and token ID. Read-only; no arguments. Pairing, renaming and logout are available only through the local configure driver.";
|
|
33961
34054
|
var CheckActivitySchema2 = external_exports.object({}).strict();
|
|
33962
34055
|
var FEEDBACK_TEXT_MAX2 = 5e4;
|
|
33963
34056
|
var SendFeedbackSchema2 = external_exports.object({
|
|
@@ -33967,8 +34060,10 @@ var SendFeedbackSchema2 = external_exports.object({
|
|
|
33967
34060
|
}).strict();
|
|
33968
34061
|
var SEND_FEEDBACK_DESCRIPTION2 = "Send feedback about Paigy itself (a bug report, an idea) straight to the Paigy team. Send it only when the person asked you to, or agreed when you offered: it goes to Paigy, never to the person, and nobody answers it here. Not for your work or a question for the person: use contact for those. Returns whether it was stored.";
|
|
33969
34062
|
var AGENT_TOOLS2 = [
|
|
34063
|
+
{ name: "configure", description: CONFIGURE_IDENTITY_DESCRIPTION2, inputSchema: mcpInputSchema2(ConfigureIdentitySchema2) },
|
|
33970
34064
|
{ name: "contact", description: CONTACT_DESCRIPTION2, inputSchema: CONTACT_SCHEMA2 },
|
|
33971
34065
|
{ name: "manage_goals", description: MANAGE_GOALS_DESCRIPTION2, inputSchema: mcpInputSchema2(ManageGoalsToolSchema2) },
|
|
34066
|
+
{ name: "get_delivery", description: GET_DELIVERY_DESCRIPTION2, inputSchema: mcpInputSchema2(GetDeliverySchema2) },
|
|
33972
34067
|
{ name: "get_goal", description: GET_GOAL_DESCRIPTION2, inputSchema: mcpInputSchema2(GetGoalSchema2) },
|
|
33973
34068
|
{ name: "search", description: SEARCH_DESCRIPTION2, inputSchema: mcpInputSchema2(SearchToolSchema2) },
|
|
33974
34069
|
{ name: "check_activity", description: "Read your person's agents that contributed in the last 24 hours, newest first: at most 30 agents and their five most recently touched Goals, with their latest entry headline and time. Use get_goal to read a Goal and its other contributors before starting overlapping work. Read-only; no arguments.", inputSchema: mcpInputSchema2(CheckActivitySchema2) },
|
|
@@ -34250,8 +34345,45 @@ function entryWords(entry) {
|
|
|
34250
34345
|
}
|
|
34251
34346
|
var LIVE_MS2 = 3 * 6e4;
|
|
34252
34347
|
var WORKING_MS2 = 60 * 6e4;
|
|
34348
|
+
var isWorking = (c, now) => !!c.lastWorkedAt && now - Date.parse(c.lastWorkedAt) < WORKING_MS2;
|
|
34253
34349
|
var WORKING_TIER_MS2 = 2 * 60 * 6e4;
|
|
34254
34350
|
var RECENT_TIER_MS2 = 24 * 60 * 6e4;
|
|
34351
|
+
var json2 = external_exports.lazy(() => external_exports.union([
|
|
34352
|
+
external_exports.string(),
|
|
34353
|
+
external_exports.number().finite(),
|
|
34354
|
+
external_exports.boolean(),
|
|
34355
|
+
external_exports.null(),
|
|
34356
|
+
external_exports.array(json2),
|
|
34357
|
+
external_exports.record(json2)
|
|
34358
|
+
]));
|
|
34359
|
+
var clock3 = external_exports.number().int().nonnegative().safe();
|
|
34360
|
+
var HeardEventSchema2 = external_exports.object({
|
|
34361
|
+
eventId: external_exports.string().uuid(),
|
|
34362
|
+
source: external_exports.enum(["phone", "bot"]),
|
|
34363
|
+
sequence: clock3,
|
|
34364
|
+
kind: external_exports.string().min(1),
|
|
34365
|
+
phoneAtMs: clock3.optional(),
|
|
34366
|
+
botAtMs: clock3.optional(),
|
|
34367
|
+
payload: external_exports.record(json2)
|
|
34368
|
+
}).strict();
|
|
34369
|
+
var HeardCursorSchema2 = HeardEventSchema2.pick({ source: true, sequence: true, eventId: true });
|
|
34370
|
+
var HeardPageSchema2 = external_exports.object({
|
|
34371
|
+
events: external_exports.array(HeardEventSchema2).max(200),
|
|
34372
|
+
next: HeardCursorSchema2.nullable()
|
|
34373
|
+
}).strict();
|
|
34374
|
+
var MAX_EVENTS2 = 50;
|
|
34375
|
+
var MAX_BYTES2 = 8e4;
|
|
34376
|
+
var batchBytes2 = (events) => {
|
|
34377
|
+
let bytes = 0;
|
|
34378
|
+
for (const char of JSON.stringify({ events })) {
|
|
34379
|
+
const point = char.codePointAt(0);
|
|
34380
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
34381
|
+
}
|
|
34382
|
+
return bytes;
|
|
34383
|
+
};
|
|
34384
|
+
var HeardBatchSchema2 = external_exports.object({
|
|
34385
|
+
events: external_exports.array(HeardEventSchema2).min(1).max(MAX_EVENTS2)
|
|
34386
|
+
}).strict().refine((value) => batchBytes2(value.events) <= MAX_BYTES2, "heard_batch_too_large");
|
|
34255
34387
|
var ContextSchema2 = external_exports.object({
|
|
34256
34388
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
34257
34389
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -34808,8 +34940,8 @@ var MissedCallSchema2 = external_exports.enum([
|
|
|
34808
34940
|
"inbox",
|
|
34809
34941
|
"dismiss"
|
|
34810
34942
|
]);
|
|
34811
|
-
var
|
|
34812
|
-
var QUIET2 = ` Nothing rings from ${
|
|
34943
|
+
var clock22 = (h) => h === 0 ? "midnight" : h === 12 ? "noon" : h < 12 ? `${h} am` : `${h - 12} pm`;
|
|
34944
|
+
var QUIET2 = ` Nothing rings from ${clock22(NIGHT2.from)} to ${clock22(NIGHT2.to)} your time; the count waits for morning.`;
|
|
34813
34945
|
var BrokerTuningSchema2 = external_exports.object({
|
|
34814
34946
|
/** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
|
|
34815
34947
|
ackVerbosity: external_exports.enum(["normal", "none"]).optional(),
|
|
@@ -35124,7 +35256,13 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
35124
35256
|
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
35125
35257
|
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
35126
35258
|
* none was. The Work row reads it before `said`. */
|
|
35127
|
-
summary: external_exports.string().optional()
|
|
35259
|
+
summary: external_exports.string().optional(),
|
|
35260
|
+
/** AN AGENT'S UPDATE OR QUESTION AS ITS UNITS' TITLES (owner, 2026-10-08: "send the 50 chars from the title
|
|
35261
|
+
* of each unit"): each at most 50 characters, in order; absent when it has none. */
|
|
35262
|
+
titles: external_exports.array(external_exports.string()).optional(),
|
|
35263
|
+
/** A PROGRESS NOTE'S ONE POINT ("An agent's progress note -> maybe let's cap it at 200 chars"): the
|
|
35264
|
+
* update's own line, before its units; the row caps it at 200. Absent on anything else. */
|
|
35265
|
+
line: external_exports.string().optional()
|
|
35128
35266
|
}).nullable().optional(),
|
|
35129
35267
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
35130
35268
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -35496,7 +35634,9 @@ var CallRecapSchema2 = external_exports.object({
|
|
|
35496
35634
|
words: external_exports.string(),
|
|
35497
35635
|
atMs: external_exports.number()
|
|
35498
35636
|
})),
|
|
35499
|
-
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
35637
|
+
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() }),
|
|
35638
|
+
/** Last stored position on this page when another exists; absent before paging shipped. */
|
|
35639
|
+
next: external_exports.number().int().nonnegative().nullable().optional()
|
|
35500
35640
|
});
|
|
35501
35641
|
|
|
35502
35642
|
// src/paigy/activity.ts
|
|
@@ -35570,9 +35710,9 @@ function limitIn(text, now) {
|
|
|
35570
35710
|
if (!line) return null;
|
|
35571
35711
|
const reset = resetOf(line, now);
|
|
35572
35712
|
if (reset !== null && reset > now && reset - now <= FARTHEST_MS) {
|
|
35573
|
-
return { until: reset + MARGIN_MS, resets: /resets\s+(?:at\s+)?(.+?)\s*(?:\(|$)/i.exec(line)?.[1]?.trim() ??
|
|
35713
|
+
return { until: reset + MARGIN_MS, resets: /resets\s+(?:at\s+)?(.+?)\s*(?:\(|$)/i.exec(line)?.[1]?.trim() ?? clock4(reset) };
|
|
35574
35714
|
}
|
|
35575
|
-
return { until: now + RETRY_MS2, resets:
|
|
35715
|
+
return { until: now + RETRY_MS2, resets: clock4(now + RETRY_MS2) };
|
|
35576
35716
|
}
|
|
35577
35717
|
function resetOf(line, now) {
|
|
35578
35718
|
const m = /resets\s+(?:at\s+)?(?:([a-z]{3})[a-z]*\s+(\d{1,2}),?\s+(?:at\s+)?)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b(?:\s*\(([^)]+)\))?/i.exec(line);
|
|
@@ -35616,7 +35756,7 @@ function wallToEpoch(year, month, day, hour, minute, zone) {
|
|
|
35616
35756
|
const first = wall - offset(wall);
|
|
35617
35757
|
return wall - offset(first);
|
|
35618
35758
|
}
|
|
35619
|
-
function
|
|
35759
|
+
function clock4(t) {
|
|
35620
35760
|
return new Date(t).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }).replace(/\s?([AP])M$/i, (_, x) => `${x.toLowerCase()}m`);
|
|
35621
35761
|
}
|
|
35622
35762
|
var pausedLine = (limit) => `Paused by the usage limit until ${limit.resets}`;
|
|
@@ -35660,7 +35800,7 @@ function episodes(opts = {}) {
|
|
|
35660
35800
|
episode.called = true;
|
|
35661
35801
|
const rest = episode.paused.filter((n) => !episode.running.includes(n));
|
|
35662
35802
|
const text = [
|
|
35663
|
-
`Claude's usage limit paused ${names(episode.paused)} from ${
|
|
35803
|
+
`Claude's usage limit paused ${names(episode.paused)} from ${clock4(episode.since)}.`,
|
|
35664
35804
|
rest.length ? `It has reset: ${names(episode.running)} ${episode.running.length > 1 ? "are" : "is"} running again, and ${names(rest)} ${rest.length > 1 ? "haven't" : "hasn't"} started again yet.` : `It has reset, and ${episode.paused.length > 1 ? "they have all" : "it has"} carried on.`
|
|
35665
35805
|
].join(" ");
|
|
35666
35806
|
void call(text).then(
|
|
@@ -36153,7 +36293,7 @@ function send2(session, state, text) {
|
|
|
36153
36293
|
session.send(text);
|
|
36154
36294
|
}
|
|
36155
36295
|
async function pump(session, state, rail, opts) {
|
|
36156
|
-
const
|
|
36296
|
+
const clock5 = opts.beat ?? beat();
|
|
36157
36297
|
const now = opts.now ?? Date.now;
|
|
36158
36298
|
const counts = { wake: 0, rest: 0, safety: 0, skipped: 0 };
|
|
36159
36299
|
const tally = { noted: 0, seen: 0, announced: 0 };
|
|
@@ -36185,7 +36325,7 @@ async function pump(session, state, rail, opts) {
|
|
|
36185
36325
|
counts.wake = counts.rest = counts.safety = counts.skipped = tally.noted = tally.seen = tally.announced = tally.paused = 0;
|
|
36186
36326
|
since = now();
|
|
36187
36327
|
}
|
|
36188
|
-
cause = await
|
|
36328
|
+
cause = await clock5.next(state.asks?.size ? ASK_BEAT_MS : state.paused ? Math.max(1e3, Math.min(SAFETY_MS, state.paused.until - now())) : SAFETY_MS);
|
|
36189
36329
|
if (!await opts.onIdle()) return;
|
|
36190
36330
|
}
|
|
36191
36331
|
}
|
|
@@ -36195,7 +36335,7 @@ function runHarness(opts) {
|
|
|
36195
36335
|
let running = true;
|
|
36196
36336
|
let tail = [];
|
|
36197
36337
|
const state = { resting: true };
|
|
36198
|
-
const
|
|
36338
|
+
const clock5 = beat();
|
|
36199
36339
|
let session = null;
|
|
36200
36340
|
const speaksAs = opts.session ?? opts.resume ?? sessionId();
|
|
36201
36341
|
const instance = randomUUID4();
|
|
@@ -36256,7 +36396,7 @@ ${event.message}`, state, rail, { log: opts.log });
|
|
|
36256
36396
|
stop2();
|
|
36257
36397
|
return;
|
|
36258
36398
|
} else if (carried) runningAgain();
|
|
36259
|
-
|
|
36399
|
+
clock5.nudge("rest");
|
|
36260
36400
|
}
|
|
36261
36401
|
}
|
|
36262
36402
|
session = startSession({
|
|
@@ -36275,13 +36415,13 @@ ${event.message}`, state, rail, { log: opts.log });
|
|
|
36275
36415
|
},
|
|
36276
36416
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
36277
36417
|
});
|
|
36278
|
-
void pump(session, state, rail, { onIdle: () => running, beat:
|
|
36418
|
+
void pump(session, state, rail, { onIdle: () => running, beat: clock5, initialPrompt: opts.prompt, log: opts.log, ...inbox ? { inbox } : {} }).catch(
|
|
36279
36419
|
(e) => opts.log(`\u26A0 conversation loop stopped: ${e.message}`)
|
|
36280
36420
|
);
|
|
36281
36421
|
opts.log(`\u25B6 started ${opts.harness} in ${opts.cwd} (${opts.mode} mode)`);
|
|
36282
36422
|
function stop2() {
|
|
36283
36423
|
running = false;
|
|
36284
|
-
|
|
36424
|
+
clock5.nudge("wake");
|
|
36285
36425
|
tail = [];
|
|
36286
36426
|
cancelAsks(state);
|
|
36287
36427
|
session?.stop();
|
|
@@ -36296,7 +36436,7 @@ ${event.message}`, state, rail, { log: opts.log });
|
|
|
36296
36436
|
working: () => running && state.resting !== true,
|
|
36297
36437
|
// Paused by a usage limit, the activity line says so: the quiet place the pause is shown.
|
|
36298
36438
|
tail: () => state.paused ? [pausedLine(state.paused)] : [...tail],
|
|
36299
|
-
nudge: () =>
|
|
36439
|
+
nudge: () => clock5.nudge("wake"),
|
|
36300
36440
|
stop: stop2
|
|
36301
36441
|
};
|
|
36302
36442
|
}
|
|
@@ -36727,7 +36867,7 @@ var claimedRun = (sessionId2) => {
|
|
|
36727
36867
|
const slot = sessionSlot(sessionId2);
|
|
36728
36868
|
return { slot, key: runKey(slot) };
|
|
36729
36869
|
};
|
|
36730
|
-
var launchRun = (spec) => ({ session: spec.sessionId
|
|
36870
|
+
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
36731
36871
|
var BOUND_ELSEWHERE_MS = 10 * 6e4;
|
|
36732
36872
|
var boundElsewhere = /* @__PURE__ */ new Map();
|
|
36733
36873
|
var BOUND_SKIP = "is bound to the session it was paired in, which this host cannot name \u2014 run `paigy-harness handoff` as it, in its folder, to record it";
|
|
@@ -36757,13 +36897,12 @@ function evict(runs, key3, run) {
|
|
|
36757
36897
|
if (runs.get(key3)?.run === run) runs.delete(key3);
|
|
36758
36898
|
}
|
|
36759
36899
|
var PRESENCE_FRESH_MS = 3 * 6e4;
|
|
36760
|
-
var IDLE_HOLDER_MS = 30 * 6e4;
|
|
36761
36900
|
function wakeAction(live, now, serverSeenAt, here = {}) {
|
|
36762
36901
|
if (live) return { action: "skip", why: "run" };
|
|
36763
36902
|
if (here.holder) return { action: "skip", why: "holder" };
|
|
36764
36903
|
if (serverSeenAt !== void 0 && now - serverSeenAt < PRESENCE_FRESH_MS) {
|
|
36765
36904
|
if (here.goneAt !== void 0 && serverSeenAt <= here.goneAt) return { action: "spawn", why: "gone" };
|
|
36766
|
-
if (here.movedAt !== void 0 && now - here.movedAt >=
|
|
36905
|
+
if (here.movedAt !== void 0 && now - here.movedAt >= WORKING_MS2 && !isWorking(here, now)) return { action: "spawn", why: "idle" };
|
|
36767
36906
|
return { action: "skip", why: "presence" };
|
|
36768
36907
|
}
|
|
36769
36908
|
return { action: "spawn", why: "nobody" };
|
|
@@ -36836,7 +36975,7 @@ function startHost(opts) {
|
|
|
36836
36975
|
});
|
|
36837
36976
|
void heartbeat({
|
|
36838
36977
|
version: VERSION,
|
|
36839
|
-
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status
|
|
36978
|
+
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
36840
36979
|
workspaces: listWorkspaces(opts.wsDeps),
|
|
36841
36980
|
// The repos inside them, so a session started for work on one opens IN it (Goal 26982211).
|
|
36842
36981
|
repos: listRepos(opts.wsDeps)
|
|
@@ -36918,7 +37057,7 @@ function startHost(opts) {
|
|
|
36918
37057
|
const presence = /* @__PURE__ */ new Map();
|
|
36919
37058
|
try {
|
|
36920
37059
|
for (const c of (await listConnections(asHost)).items) {
|
|
36921
|
-
|
|
37060
|
+
presence.set(c.id, c);
|
|
36922
37061
|
}
|
|
36923
37062
|
lastPresence = presence;
|
|
36924
37063
|
} catch {
|
|
@@ -36951,11 +37090,12 @@ function startHost(opts) {
|
|
|
36951
37090
|
}
|
|
36952
37091
|
}
|
|
36953
37092
|
const tokenId = slotIdentity(slot).tokenId;
|
|
36954
|
-
const
|
|
37093
|
+
const connection = tokenId ? presence.get(tokenId) : void 0;
|
|
37094
|
+
const seenByServer = connection?.lastSeenAt ? Date.parse(connection.lastSeenAt) : void 0;
|
|
36955
37095
|
const holder = holders.get(slot);
|
|
36956
37096
|
if (holder) heldHere.add(slot);
|
|
36957
37097
|
else if (heldHere.delete(slot)) goneAt.set(slot, Date.now());
|
|
36958
|
-
const here = { holder, goneAt: goneAt.get(slot) };
|
|
37098
|
+
const here = { holder, goneAt: goneAt.get(slot), lastWorkedAt: connection?.lastWorkedAt };
|
|
36959
37099
|
let decided = wakeAction(!!live, Date.now(), seenByServer, here);
|
|
36960
37100
|
const label = slotName(slot) ?? slot;
|
|
36961
37101
|
if (decided.action === "skip" && decided.why !== "presence") {
|
|
@@ -36998,7 +37138,7 @@ function startHost(opts) {
|
|
|
36998
37138
|
const since = found.since;
|
|
36999
37139
|
decided = wakeAction(false, Date.now(), seenByServer, { ...here, ...since ? { movedAt: Date.parse(since) } : {} });
|
|
37000
37140
|
if (decided.action === "skip") {
|
|
37001
|
-
const said = `is
|
|
37141
|
+
const said = `is connected somewhere this host cannot see (heartbeat) \u2014 ${found.waiting}${since ? ` last moved ${ago(since, Date.now())}; takeover requires ${WORKING_MS2 / 6e4} min without own activity or waiting-work movement` : ", with nothing to say when it last moved"}`;
|
|
37002
37142
|
opts.log(`\u25CC ${label} ${said} \u2014 not spawning`);
|
|
37003
37143
|
decide(slot, "stood_back", found, seenByServer, said);
|
|
37004
37144
|
return;
|
|
@@ -37167,7 +37307,7 @@ function startHost(opts) {
|
|
|
37167
37307
|
// sessions we started ourselves. The server already knows: `last_seen_at` on the
|
|
37168
37308
|
// agent's own token, the same evidence `wakeAction` stands back for. So: a run we
|
|
37169
37309
|
// hold, OR a process the server heard from inside `PRESENCE_FRESH_MS`.
|
|
37170
|
-
running: live.has(name) || !!slot && lastHolders.has(slot) || !!id3.tokenId && Date.now() - (lastPresence.get(id3.tokenId) ??
|
|
37310
|
+
running: live.has(name) || !!slot && lastHolders.has(slot) || !!id3.tokenId && Date.now() - Date.parse(lastPresence.get(id3.tokenId)?.lastSeenAt ?? "") < PRESENCE_FRESH_MS,
|
|
37171
37311
|
// `working` stays ours alone — it means a run WE hold is mid-turn, which is the only
|
|
37172
37312
|
// thing a tail can tell us. A process we did not spawn has no tail here to read.
|
|
37173
37313
|
working: live.get(name)?.working() ?? false
|