@paigy/harness 0.3.37 → 0.3.38
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 +245 -27
- package/dist/main.js +244 -26
- 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, models: entry.models };
|
|
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,7 +109,8 @@ 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"
|
|
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"]
|
|
113
114
|
},
|
|
114
115
|
{
|
|
115
116
|
name: "codex",
|
|
@@ -119,7 +120,8 @@ var init_catalog = __esm({
|
|
|
119
120
|
authProbe: ["codex", "login", "status"],
|
|
120
121
|
loginHint: "Run `codex login` to authenticate.",
|
|
121
122
|
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
122
|
-
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
123
|
+
installAdapter: "npm install -g @agentclientprotocol/codex-acp",
|
|
124
|
+
models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"]
|
|
123
125
|
},
|
|
124
126
|
{
|
|
125
127
|
name: "agy",
|
|
@@ -129,7 +131,8 @@ var init_catalog = __esm({
|
|
|
129
131
|
adapter: "paigy-agy-acp",
|
|
130
132
|
authProbe: ["agy", "help"],
|
|
131
133
|
loginHint: "Install and set up Antigravity (`agy`).",
|
|
132
|
-
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
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"]
|
|
133
136
|
}
|
|
134
137
|
];
|
|
135
138
|
}
|
|
@@ -22777,10 +22780,47 @@ function claudeConversation() {
|
|
|
22777
22780
|
if (!envSession("CLAUDE_CODE_SESSION_ID") || process.env.CLAUDE_CODE_CHILD_SESSION) return void 0;
|
|
22778
22781
|
const dir = envSession("CLAUDE_CONFIG_DIR") ?? join2(homedir2(), ".claude");
|
|
22779
22782
|
for (const pid of ancestors()) {
|
|
22783
|
+
let text;
|
|
22780
22784
|
try {
|
|
22781
|
-
|
|
22782
|
-
return typeof sessionId2 === "string" && sessionId2.trim() ? sessionId2 : void 0;
|
|
22785
|
+
text = readFileSync(join2(dir, "sessions", `${pid}.json`), "utf8");
|
|
22783
22786
|
} catch {
|
|
22787
|
+
continue;
|
|
22788
|
+
}
|
|
22789
|
+
const record = parseSessionRecord(text);
|
|
22790
|
+
if (!record) continue;
|
|
22791
|
+
return typeof record.sessionId === "string" && record.sessionId.trim() ? record.sessionId : void 0;
|
|
22792
|
+
}
|
|
22793
|
+
return void 0;
|
|
22794
|
+
}
|
|
22795
|
+
function parseSessionRecord(text) {
|
|
22796
|
+
const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
22797
|
+
try {
|
|
22798
|
+
return object(JSON.parse(text));
|
|
22799
|
+
} catch {
|
|
22800
|
+
}
|
|
22801
|
+
let depth = 0;
|
|
22802
|
+
let start = -1;
|
|
22803
|
+
let quoted = false;
|
|
22804
|
+
let escaped = false;
|
|
22805
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
22806
|
+
const c = text[i];
|
|
22807
|
+
if (quoted) {
|
|
22808
|
+
if (escaped) escaped = false;
|
|
22809
|
+
else if (c === "\\") escaped = true;
|
|
22810
|
+
else if (c === '"') quoted = false;
|
|
22811
|
+
} else if (c === '"') quoted = true;
|
|
22812
|
+
else if (c === "{") {
|
|
22813
|
+
if (depth === 0) start = i;
|
|
22814
|
+
depth += 1;
|
|
22815
|
+
} else if (c === "}" && depth > 0) {
|
|
22816
|
+
depth -= 1;
|
|
22817
|
+
if (depth === 0) {
|
|
22818
|
+
try {
|
|
22819
|
+
return object(JSON.parse(text.slice(start, i + 1)));
|
|
22820
|
+
} catch {
|
|
22821
|
+
return void 0;
|
|
22822
|
+
}
|
|
22823
|
+
}
|
|
22784
22824
|
}
|
|
22785
22825
|
}
|
|
22786
22826
|
return void 0;
|
|
@@ -24254,7 +24294,7 @@ ${block2}`;
|
|
|
24254
24294
|
}
|
|
24255
24295
|
return text.slice(0, start) + updated + text.slice(end);
|
|
24256
24296
|
}
|
|
24257
|
-
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, 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, 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;
|
|
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, clock, 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;
|
|
24258
24298
|
var init_dist = __esm({
|
|
24259
24299
|
"../../packages/sdk/dist/index.js"() {
|
|
24260
24300
|
"use strict";
|
|
@@ -24705,8 +24745,41 @@ var init_dist = __esm({
|
|
|
24705
24745
|
message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
24706
24746
|
goalIds: external_exports.array(external_exports.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
24707
24747
|
units,
|
|
24748
|
+
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
24749
|
+
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
24750
|
+
// still waits to be acknowledged, and it carries the ONE tap that closes it. Never a decision: the
|
|
24751
|
+
// tap lands as a contribution, the person's own words on the Goal, because there is no Question
|
|
24752
|
+
// for it to answer.
|
|
24753
|
+
//
|
|
24754
|
+
// AND ITS WORDS ARE WRITTEN FOR THIS REPORT (owner, 2026-10-08, Goal 3c12d3c6: "Agent
|
|
24755
|
+
// acknowledgments vary (e.g. 'sounds good, I will test later') instead of always 'got it'"). `Got
|
|
24756
|
+
// it` and `Noted` say only that a card was cleared, where "Sounds good, I'll test later" says the
|
|
24757
|
+
// work was RECEIVED and not verified -- the distinction a session spent an hour recovering from a
|
|
24758
|
+
// call transcript on 2026-10-07, having read "not quite" as a defect report when it meant *not
|
|
24759
|
+
// tested yet*. The words are worth writing because the agent reads them back.
|
|
24760
|
+
//
|
|
24761
|
+
// ONE STRING, NOT A LIST (owner, 2026-10-08 15:47, Goal 31a1e2ac, asked whether an agent should
|
|
24762
|
+
// write up to four of them: "just on the cards, but it should be more like just they should only
|
|
24763
|
+
// get one basically string value that they can enter that replaces got it"). A report has exactly
|
|
24764
|
+
// one way to close, so the only thing an agent writes is what that button SAYS. #3260's 1..6
|
|
24765
|
+
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
24766
|
+
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
24767
|
+
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
24768
|
+
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. Left out, the button says "Got it", as every card did.`
|
|
24770
|
+
).optional(),
|
|
24708
24771
|
userExplicitlyRequested
|
|
24709
|
-
}).strict().superRefine((u, ctx) =>
|
|
24772
|
+
}).strict().superRefine((u, ctx) => {
|
|
24773
|
+
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
24774
|
+
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
24775
|
+
ctx.addIssue({
|
|
24776
|
+
code: external_exports.ZodIssueCode.custom,
|
|
24777
|
+
path: ["reply"],
|
|
24778
|
+
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX },
|
|
24779
|
+
message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX}. It is a button's words, so say it in a breath.`
|
|
24780
|
+
});
|
|
24781
|
+
}
|
|
24782
|
+
});
|
|
24710
24783
|
questionFields = {
|
|
24711
24784
|
question: external_exports.string().trim().min(1).describe(
|
|
24712
24785
|
`ONE question, in at most ${ASK_MAX} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
@@ -24790,6 +24863,8 @@ QUESTIONS: one question per object; several questions are several objects in one
|
|
|
24790
24863
|
|
|
24791
24864
|
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.
|
|
24792
24865
|
|
|
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
|
+
|
|
24793
24868
|
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.
|
|
24794
24869
|
|
|
24795
24870
|
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\`).`;
|
|
@@ -25144,8 +25219,10 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
25144
25219
|
// something the person answered or asked for did not save, was refused or dropped
|
|
25145
25220
|
"ignored",
|
|
25146
25221
|
// the person asked or said something and was never answered (the critic's ignored_a_question)
|
|
25147
|
-
"promised"
|
|
25222
|
+
"promised",
|
|
25148
25223
|
// said it did or would do something, and nothing shows it done (the critic's broken_promise)
|
|
25224
|
+
"dropped"
|
|
25225
|
+
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
25149
25226
|
];
|
|
25150
25227
|
DistillerReplySchema = external_exports.object({
|
|
25151
25228
|
defects: external_exports.array(external_exports.object({
|
|
@@ -25176,6 +25253,8 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
25176
25253
|
);
|
|
25177
25254
|
LIVE_MS = 3 * 6e4;
|
|
25178
25255
|
WORKING_MS = 60 * 6e4;
|
|
25256
|
+
WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
25257
|
+
RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
25179
25258
|
ContextSchema = external_exports.object({
|
|
25180
25259
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
25181
25260
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -25520,6 +25599,10 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
25520
25599
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
25521
25600
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
25522
25601
|
agentId: external_exports.string().optional(),
|
|
25602
|
+
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
25603
|
+
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
25604
|
+
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
25605
|
+
onTable: external_exports.boolean().optional(),
|
|
25523
25606
|
select: SelectShapeSchema.optional(),
|
|
25524
25607
|
options: external_exports.array(OptionSchema.omit({ id: true })).optional(),
|
|
25525
25608
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
@@ -25594,9 +25677,16 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
25594
25677
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
25595
25678
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
25596
25679
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
25597
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are
|
|
25598
|
-
|
|
25599
|
-
|
|
25680
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
25681
|
+
options: external_exports.array(OptionSchema).optional(),
|
|
25682
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
25683
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
25684
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
25685
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
25686
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
25687
|
+
choices: external_exports.array(OptionSchema).optional(),
|
|
25688
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
25689
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
25600
25690
|
})).optional(),
|
|
25601
25691
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
25602
25692
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -26033,7 +26123,11 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
26033
26123
|
from: external_exports.enum(["person", "agent"]),
|
|
26034
26124
|
said: external_exports.string(),
|
|
26035
26125
|
at: external_exports.string(),
|
|
26036
|
-
entryId: external_exports.string()
|
|
26126
|
+
entryId: external_exports.string(),
|
|
26127
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
26128
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
26129
|
+
* none was. The Work row reads it before `said`. */
|
|
26130
|
+
summary: external_exports.string().optional()
|
|
26037
26131
|
}).nullable().optional(),
|
|
26038
26132
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
26039
26133
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -26370,6 +26464,9 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
26370
26464
|
title: external_exports.string(),
|
|
26371
26465
|
owner: external_exports.string(),
|
|
26372
26466
|
state: external_exports.string(),
|
|
26467
|
+
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
26468
|
+
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
26469
|
+
presented: external_exports.boolean(),
|
|
26373
26470
|
questions: external_exports.array(external_exports.object({ id: external_exports.string().uuid(), state: external_exports.string(), title: external_exports.string() })),
|
|
26374
26471
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
26375
26472
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
@@ -26377,9 +26474,33 @@ RECEIVING: a contact that sends nothing returns \`events\`, a limited batch of w
|
|
|
26377
26474
|
* neither wrote one. */
|
|
26378
26475
|
/** `about` is the request the line answered (its question), null for words that answered none —
|
|
26379
26476
|
* the key the screen groups on, so one question is one row however many times it was answered. */
|
|
26380
|
-
|
|
26477
|
+
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
26478
|
+
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
26479
|
+
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
26480
|
+
lines: external_exports.array(external_exports.object({
|
|
26481
|
+
entryId: external_exports.string().uuid(),
|
|
26482
|
+
words: external_exports.string(),
|
|
26483
|
+
summary: external_exports.string().optional(),
|
|
26484
|
+
about: external_exports.string().nullable().optional(),
|
|
26485
|
+
reply: external_exports.string().nullable().optional(),
|
|
26486
|
+
replyKind: external_exports.string().nullable().optional(),
|
|
26487
|
+
at: external_exports.array(external_exports.number()).optional()
|
|
26488
|
+
}))
|
|
26381
26489
|
})),
|
|
26382
26490
|
unfiled: external_exports.array(external_exports.object({ lineId: external_exports.string().uuid(), words: external_exports.string(), atMs: external_exports.number() })),
|
|
26491
|
+
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
26492
|
+
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
26493
|
+
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
26494
|
+
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
26495
|
+
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
26496
|
+
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
26497
|
+
said: external_exports.array(external_exports.object({
|
|
26498
|
+
lineId: external_exports.string().uuid(),
|
|
26499
|
+
speaker: external_exports.enum(["user", "agent"]),
|
|
26500
|
+
agent: external_exports.object({ id: external_exports.string().nullable(), name: external_exports.string().nullable() }).nullable(),
|
|
26501
|
+
words: external_exports.string(),
|
|
26502
|
+
atMs: external_exports.number()
|
|
26503
|
+
})),
|
|
26383
26504
|
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
26384
26505
|
});
|
|
26385
26506
|
TOKEN_PATH = join22(homedir22(), ".paigy", "token.json");
|
|
@@ -32071,7 +32192,8 @@ function startSession(opts) {
|
|
|
32071
32192
|
if (!existsSync3(cwd)) {
|
|
32072
32193
|
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
32073
32194
|
}
|
|
32074
|
-
|
|
32195
|
+
let codexConfig = opts.harness === "codex" ? JSON.parse(process.env.CODEX_CONFIG || "{}") : null;
|
|
32196
|
+
if (opts.model && opts.harness === "codex") codexConfig = { ...codexConfig, model: opts.model };
|
|
32075
32197
|
if (codexConfig) {
|
|
32076
32198
|
const key3 = "mcp_servers.paigy.env_vars";
|
|
32077
32199
|
const configPath = join5(process.env.CODEX_HOME || join5(homedir4(), ".codex"), "config.toml");
|
|
@@ -32097,6 +32219,8 @@ function startSession(opts) {
|
|
|
32097
32219
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
32098
32220
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
32099
32221
|
...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 }) } : {},
|
|
32100
32224
|
...codexConfig ? { CODEX_CONFIG: JSON.stringify(codexConfig) } : {},
|
|
32101
32225
|
...opts.session ?? opts.resume ? { PAIGY_SESSION_ID: opts.session ?? opts.resume } : {},
|
|
32102
32226
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
@@ -33601,8 +33725,41 @@ var UpdateInputSchema2 = external_exports.object({
|
|
|
33601
33725
|
message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX2} characters; the context goes in units.`),
|
|
33602
33726
|
goalIds: external_exports.array(external_exports.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
33603
33727
|
units: units2,
|
|
33728
|
+
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
33729
|
+
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
33730
|
+
// still waits to be acknowledged, and it carries the ONE tap that closes it. Never a decision: the
|
|
33731
|
+
// tap lands as a contribution, the person's own words on the Goal, because there is no Question
|
|
33732
|
+
// for it to answer.
|
|
33733
|
+
//
|
|
33734
|
+
// AND ITS WORDS ARE WRITTEN FOR THIS REPORT (owner, 2026-10-08, Goal 3c12d3c6: "Agent
|
|
33735
|
+
// acknowledgments vary (e.g. 'sounds good, I will test later') instead of always 'got it'"). `Got
|
|
33736
|
+
// it` and `Noted` say only that a card was cleared, where "Sounds good, I'll test later" says the
|
|
33737
|
+
// work was RECEIVED and not verified -- the distinction a session spent an hour recovering from a
|
|
33738
|
+
// call transcript on 2026-10-07, having read "not quite" as a defect report when it meant *not
|
|
33739
|
+
// tested yet*. The words are worth writing because the agent reads them back.
|
|
33740
|
+
//
|
|
33741
|
+
// ONE STRING, NOT A LIST (owner, 2026-10-08 15:47, Goal 31a1e2ac, asked whether an agent should
|
|
33742
|
+
// write up to four of them: "just on the cards, but it should be more like just they should only
|
|
33743
|
+
// get one basically string value that they can enter that replaces got it"). A report has exactly
|
|
33744
|
+
// one way to close, so the only thing an agent writes is what that button SAYS. #3260's 1..6
|
|
33745
|
+
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
33746
|
+
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
33747
|
+
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
33748
|
+
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. Left out, the button says "Got it", as every card did.`
|
|
33750
|
+
).optional(),
|
|
33604
33751
|
userExplicitlyRequested: userExplicitlyRequested2
|
|
33605
|
-
}).strict().superRefine((u, ctx) =>
|
|
33752
|
+
}).strict().superRefine((u, ctx) => {
|
|
33753
|
+
refuseOverCaps2(u.message, u.units, "message", ctx);
|
|
33754
|
+
if (u.reply && u.reply.length > OPTION_LABEL_MAX2) {
|
|
33755
|
+
ctx.addIssue({
|
|
33756
|
+
code: external_exports.ZodIssueCode.custom,
|
|
33757
|
+
path: ["reply"],
|
|
33758
|
+
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX2 },
|
|
33759
|
+
message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX2}. It is a button's words, so say it in a breath.`
|
|
33760
|
+
});
|
|
33761
|
+
}
|
|
33762
|
+
});
|
|
33606
33763
|
var questionFields2 = {
|
|
33607
33764
|
question: external_exports.string().trim().min(1).describe(
|
|
33608
33765
|
`ONE question, in at most ${ASK_MAX2} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
@@ -33686,6 +33843,8 @@ QUESTIONS: one question per object; several questions are several objects in one
|
|
|
33686
33843
|
|
|
33687
33844
|
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.
|
|
33688
33845
|
|
|
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
|
+
|
|
33689
33848
|
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.
|
|
33690
33849
|
|
|
33691
33850
|
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\`).`;
|
|
@@ -34040,8 +34199,10 @@ var DISTILLER_DEFECTS2 = [
|
|
|
34040
34199
|
// something the person answered or asked for did not save, was refused or dropped
|
|
34041
34200
|
"ignored",
|
|
34042
34201
|
// the person asked or said something and was never answered (the critic's ignored_a_question)
|
|
34043
|
-
"promised"
|
|
34202
|
+
"promised",
|
|
34044
34203
|
// said it did or would do something, and nothing shows it done (the critic's broken_promise)
|
|
34204
|
+
"dropped"
|
|
34205
|
+
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
34045
34206
|
];
|
|
34046
34207
|
var DistillerReplySchema2 = external_exports.object({
|
|
34047
34208
|
defects: external_exports.array(external_exports.object({
|
|
@@ -34089,6 +34250,8 @@ function entryWords(entry) {
|
|
|
34089
34250
|
}
|
|
34090
34251
|
var LIVE_MS2 = 3 * 6e4;
|
|
34091
34252
|
var WORKING_MS2 = 60 * 6e4;
|
|
34253
|
+
var WORKING_TIER_MS2 = 2 * 60 * 6e4;
|
|
34254
|
+
var RECENT_TIER_MS2 = 24 * 60 * 6e4;
|
|
34092
34255
|
var ContextSchema2 = external_exports.object({
|
|
34093
34256
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
34094
34257
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -34433,6 +34596,10 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
34433
34596
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
34434
34597
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
34435
34598
|
agentId: external_exports.string().optional(),
|
|
34599
|
+
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
34600
|
+
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
34601
|
+
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
34602
|
+
onTable: external_exports.boolean().optional(),
|
|
34436
34603
|
select: SelectShapeSchema2.optional(),
|
|
34437
34604
|
options: external_exports.array(OptionSchema2.omit({ id: true })).optional(),
|
|
34438
34605
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
@@ -34507,9 +34674,16 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
34507
34674
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
34508
34675
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
34509
34676
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
34510
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are
|
|
34511
|
-
|
|
34512
|
-
|
|
34677
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
34678
|
+
options: external_exports.array(OptionSchema2).optional(),
|
|
34679
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
34680
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
34681
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
34682
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
34683
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
34684
|
+
choices: external_exports.array(OptionSchema2).optional(),
|
|
34685
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
34686
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
34513
34687
|
})).optional(),
|
|
34514
34688
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
34515
34689
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -34946,7 +35120,11 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
34946
35120
|
from: external_exports.enum(["person", "agent"]),
|
|
34947
35121
|
said: external_exports.string(),
|
|
34948
35122
|
at: external_exports.string(),
|
|
34949
|
-
entryId: external_exports.string()
|
|
35123
|
+
entryId: external_exports.string(),
|
|
35124
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
35125
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
35126
|
+
* none was. The Work row reads it before `said`. */
|
|
35127
|
+
summary: external_exports.string().optional()
|
|
34950
35128
|
}).nullable().optional(),
|
|
34951
35129
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
34952
35130
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -35281,6 +35459,9 @@ var CallRecapSchema2 = external_exports.object({
|
|
|
35281
35459
|
title: external_exports.string(),
|
|
35282
35460
|
owner: external_exports.string(),
|
|
35283
35461
|
state: external_exports.string(),
|
|
35462
|
+
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
35463
|
+
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
35464
|
+
presented: external_exports.boolean(),
|
|
35284
35465
|
questions: external_exports.array(external_exports.object({ id: external_exports.string().uuid(), state: external_exports.string(), title: external_exports.string() })),
|
|
35285
35466
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
35286
35467
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
@@ -35288,9 +35469,33 @@ var CallRecapSchema2 = external_exports.object({
|
|
|
35288
35469
|
* neither wrote one. */
|
|
35289
35470
|
/** `about` is the request the line answered (its question), null for words that answered none —
|
|
35290
35471
|
* the key the screen groups on, so one question is one row however many times it was answered. */
|
|
35291
|
-
|
|
35472
|
+
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
35473
|
+
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
35474
|
+
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
35475
|
+
lines: external_exports.array(external_exports.object({
|
|
35476
|
+
entryId: external_exports.string().uuid(),
|
|
35477
|
+
words: external_exports.string(),
|
|
35478
|
+
summary: external_exports.string().optional(),
|
|
35479
|
+
about: external_exports.string().nullable().optional(),
|
|
35480
|
+
reply: external_exports.string().nullable().optional(),
|
|
35481
|
+
replyKind: external_exports.string().nullable().optional(),
|
|
35482
|
+
at: external_exports.array(external_exports.number()).optional()
|
|
35483
|
+
}))
|
|
35292
35484
|
})),
|
|
35293
35485
|
unfiled: external_exports.array(external_exports.object({ lineId: external_exports.string().uuid(), words: external_exports.string(), atMs: external_exports.number() })),
|
|
35486
|
+
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
35487
|
+
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
35488
|
+
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
35489
|
+
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
35490
|
+
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
35491
|
+
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
35492
|
+
said: external_exports.array(external_exports.object({
|
|
35493
|
+
lineId: external_exports.string().uuid(),
|
|
35494
|
+
speaker: external_exports.enum(["user", "agent"]),
|
|
35495
|
+
agent: external_exports.object({ id: external_exports.string().nullable(), name: external_exports.string().nullable() }).nullable(),
|
|
35496
|
+
words: external_exports.string(),
|
|
35497
|
+
atMs: external_exports.number()
|
|
35498
|
+
})),
|
|
35294
35499
|
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
35295
35500
|
});
|
|
35296
35501
|
|
|
@@ -36245,10 +36450,14 @@ function localHolderPids(deps = {}) {
|
|
|
36245
36450
|
if (!/^\d+\.json$/.test(file)) continue;
|
|
36246
36451
|
let row;
|
|
36247
36452
|
try {
|
|
36248
|
-
row =
|
|
36453
|
+
row = parseSessionRecord(read3(join7(dir, file)));
|
|
36249
36454
|
} catch {
|
|
36250
36455
|
continue;
|
|
36251
36456
|
}
|
|
36457
|
+
if (!row) {
|
|
36458
|
+
deps.unreadable?.(file);
|
|
36459
|
+
continue;
|
|
36460
|
+
}
|
|
36252
36461
|
const pid = typeof row.pid === "number" ? row.pid : Number(file.slice(0, -5));
|
|
36253
36462
|
if (typeof row.sessionId !== "string" || !row.sessionId.trim() || !Number.isInteger(pid) || pid <= 0) continue;
|
|
36254
36463
|
if (!holds(pid, row.procStart, check)) continue;
|
|
@@ -36290,10 +36499,14 @@ function localHolders(deps = {}) {
|
|
|
36290
36499
|
if (!/^\d+\.json$/.test(file)) continue;
|
|
36291
36500
|
let row;
|
|
36292
36501
|
try {
|
|
36293
|
-
row =
|
|
36502
|
+
row = parseSessionRecord(read3(join7(dir, file)));
|
|
36294
36503
|
} catch {
|
|
36295
36504
|
continue;
|
|
36296
36505
|
}
|
|
36506
|
+
if (!row) {
|
|
36507
|
+
deps.unreadable?.(file);
|
|
36508
|
+
continue;
|
|
36509
|
+
}
|
|
36297
36510
|
const pid = typeof row.pid === "number" ? row.pid : Number(file.slice(0, -5));
|
|
36298
36511
|
if (typeof row.sessionId !== "string" || !row.sessionId.trim() || !Number.isInteger(pid) || pid <= 0) continue;
|
|
36299
36512
|
const slot = sessionSlot(row.sessionId);
|
|
@@ -36514,7 +36727,7 @@ var claimedRun = (sessionId2) => {
|
|
|
36514
36727
|
const slot = sessionSlot(sessionId2);
|
|
36515
36728
|
return { slot, key: runKey(slot) };
|
|
36516
36729
|
};
|
|
36517
|
-
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
36730
|
+
var launchRun = (spec) => ({ session: spec.sessionId, model: spec.model });
|
|
36518
36731
|
var BOUND_ELSEWHERE_MS = 10 * 6e4;
|
|
36519
36732
|
var boundElsewhere = /* @__PURE__ */ new Map();
|
|
36520
36733
|
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";
|
|
@@ -36623,7 +36836,7 @@ function startHost(opts) {
|
|
|
36623
36836
|
});
|
|
36624
36837
|
void heartbeat({
|
|
36625
36838
|
version: VERSION,
|
|
36626
|
-
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
36839
|
+
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status, models: a.models })),
|
|
36627
36840
|
workspaces: listWorkspaces(opts.wsDeps),
|
|
36628
36841
|
// The repos inside them, so a session started for work on one opens IN it (Goal 26982211).
|
|
36629
36842
|
repos: listRepos(opts.wsDeps)
|
|
@@ -36715,9 +36928,14 @@ function startHost(opts) {
|
|
|
36715
36928
|
let lastPresence = /* @__PURE__ */ new Map();
|
|
36716
36929
|
async function evidence() {
|
|
36717
36930
|
const presence = await presenceMap();
|
|
36718
|
-
lastHolders = localHolders()
|
|
36931
|
+
lastHolders = localHolders({ unreadable: (file) => {
|
|
36932
|
+
if (unreadable.has(file)) return;
|
|
36933
|
+
unreadable.add(file);
|
|
36934
|
+
opts.log(`\u25CC session record ${file} has no object in it \u2014 the process it names reads as not running here`);
|
|
36935
|
+
} });
|
|
36719
36936
|
return { presence, holders: lastHolders };
|
|
36720
36937
|
}
|
|
36938
|
+
const unreadable = /* @__PURE__ */ new Set();
|
|
36721
36939
|
let lastHolders = /* @__PURE__ */ new Map();
|
|
36722
36940
|
async function wakeSlot(slot, { presence, holders }, cause = "sweep") {
|
|
36723
36941
|
{
|
package/dist/main.js
CHANGED
|
@@ -8680,10 +8680,47 @@ function claudeConversation() {
|
|
|
8680
8680
|
if (!envSession("CLAUDE_CODE_SESSION_ID") || process.env.CLAUDE_CODE_CHILD_SESSION) return void 0;
|
|
8681
8681
|
const dir = envSession("CLAUDE_CONFIG_DIR") ?? join(homedir(), ".claude");
|
|
8682
8682
|
for (const pid of ancestors()) {
|
|
8683
|
+
let text;
|
|
8683
8684
|
try {
|
|
8684
|
-
|
|
8685
|
-
return typeof sessionId2 === "string" && sessionId2.trim() ? sessionId2 : void 0;
|
|
8685
|
+
text = readFileSync(join(dir, "sessions", `${pid}.json`), "utf8");
|
|
8686
8686
|
} catch {
|
|
8687
|
+
continue;
|
|
8688
|
+
}
|
|
8689
|
+
const record = parseSessionRecord(text);
|
|
8690
|
+
if (!record) continue;
|
|
8691
|
+
return typeof record.sessionId === "string" && record.sessionId.trim() ? record.sessionId : void 0;
|
|
8692
|
+
}
|
|
8693
|
+
return void 0;
|
|
8694
|
+
}
|
|
8695
|
+
function parseSessionRecord(text) {
|
|
8696
|
+
const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8697
|
+
try {
|
|
8698
|
+
return object(JSON.parse(text));
|
|
8699
|
+
} catch {
|
|
8700
|
+
}
|
|
8701
|
+
let depth = 0;
|
|
8702
|
+
let start = -1;
|
|
8703
|
+
let quoted = false;
|
|
8704
|
+
let escaped = false;
|
|
8705
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
8706
|
+
const c = text[i];
|
|
8707
|
+
if (quoted) {
|
|
8708
|
+
if (escaped) escaped = false;
|
|
8709
|
+
else if (c === "\\") escaped = true;
|
|
8710
|
+
else if (c === '"') quoted = false;
|
|
8711
|
+
} else if (c === '"') quoted = true;
|
|
8712
|
+
else if (c === "{") {
|
|
8713
|
+
if (depth === 0) start = i;
|
|
8714
|
+
depth += 1;
|
|
8715
|
+
} else if (c === "}" && depth > 0) {
|
|
8716
|
+
depth -= 1;
|
|
8717
|
+
if (depth === 0) {
|
|
8718
|
+
try {
|
|
8719
|
+
return object(JSON.parse(text.slice(start, i + 1)));
|
|
8720
|
+
} catch {
|
|
8721
|
+
return void 0;
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8687
8724
|
}
|
|
8688
8725
|
}
|
|
8689
8726
|
return void 0;
|
|
@@ -10047,8 +10084,41 @@ var UpdateInputSchema = external_exports.object({
|
|
|
10047
10084
|
message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX} characters; the context goes in units.`),
|
|
10048
10085
|
goalIds: external_exports.array(external_exports.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
10049
10086
|
units,
|
|
10087
|
+
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
10088
|
+
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
10089
|
+
// still waits to be acknowledged, and it carries the ONE tap that closes it. Never a decision: the
|
|
10090
|
+
// tap lands as a contribution, the person's own words on the Goal, because there is no Question
|
|
10091
|
+
// for it to answer.
|
|
10092
|
+
//
|
|
10093
|
+
// AND ITS WORDS ARE WRITTEN FOR THIS REPORT (owner, 2026-10-08, Goal 3c12d3c6: "Agent
|
|
10094
|
+
// acknowledgments vary (e.g. 'sounds good, I will test later') instead of always 'got it'"). `Got
|
|
10095
|
+
// it` and `Noted` say only that a card was cleared, where "Sounds good, I'll test later" says the
|
|
10096
|
+
// work was RECEIVED and not verified -- the distinction a session spent an hour recovering from a
|
|
10097
|
+
// call transcript on 2026-10-07, having read "not quite" as a defect report when it meant *not
|
|
10098
|
+
// tested yet*. The words are worth writing because the agent reads them back.
|
|
10099
|
+
//
|
|
10100
|
+
// ONE STRING, NOT A LIST (owner, 2026-10-08 15:47, Goal 31a1e2ac, asked whether an agent should
|
|
10101
|
+
// write up to four of them: "just on the cards, but it should be more like just they should only
|
|
10102
|
+
// get one basically string value that they can enter that replaces got it"). A report has exactly
|
|
10103
|
+
// one way to close, so the only thing an agent writes is what that button SAYS. #3260's 1..6
|
|
10104
|
+
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
10105
|
+
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
10106
|
+
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
10107
|
+
reply: external_exports.string().trim().min(1).describe(
|
|
10108
|
+
`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. Left out, the button says "Got it", as every card did.`
|
|
10109
|
+
).optional(),
|
|
10050
10110
|
userExplicitlyRequested
|
|
10051
|
-
}).strict().superRefine((u, ctx) =>
|
|
10111
|
+
}).strict().superRefine((u, ctx) => {
|
|
10112
|
+
refuseOverCaps(u.message, u.units, "message", ctx);
|
|
10113
|
+
if (u.reply && u.reply.length > OPTION_LABEL_MAX) {
|
|
10114
|
+
ctx.addIssue({
|
|
10115
|
+
code: external_exports.ZodIssueCode.custom,
|
|
10116
|
+
path: ["reply"],
|
|
10117
|
+
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX },
|
|
10118
|
+
message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX}. It is a button's words, so say it in a breath.`
|
|
10119
|
+
});
|
|
10120
|
+
}
|
|
10121
|
+
});
|
|
10052
10122
|
var questionFields = {
|
|
10053
10123
|
question: external_exports.string().trim().min(1).describe(
|
|
10054
10124
|
`ONE question, in at most ${ASK_MAX} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
@@ -10132,6 +10202,8 @@ QUESTIONS: one question per object; several questions are several objects in one
|
|
|
10132
10202
|
|
|
10133
10203
|
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.
|
|
10134
10204
|
|
|
10205
|
+
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".
|
|
10206
|
+
|
|
10135
10207
|
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.
|
|
10136
10208
|
|
|
10137
10209
|
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\`).`;
|
|
@@ -10486,8 +10558,10 @@ var DISTILLER_DEFECTS = [
|
|
|
10486
10558
|
// something the person answered or asked for did not save, was refused or dropped
|
|
10487
10559
|
"ignored",
|
|
10488
10560
|
// the person asked or said something and was never answered (the critic's ignored_a_question)
|
|
10489
|
-
"promised"
|
|
10561
|
+
"promised",
|
|
10490
10562
|
// said it did or would do something, and nothing shows it done (the critic's broken_promise)
|
|
10563
|
+
"dropped"
|
|
10564
|
+
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
10491
10565
|
];
|
|
10492
10566
|
var DistillerReplySchema = external_exports.object({
|
|
10493
10567
|
defects: external_exports.array(external_exports.object({
|
|
@@ -10525,6 +10599,8 @@ function entryUnit(entry) {
|
|
|
10525
10599
|
}
|
|
10526
10600
|
var LIVE_MS = 3 * 6e4;
|
|
10527
10601
|
var WORKING_MS = 60 * 6e4;
|
|
10602
|
+
var WORKING_TIER_MS = 2 * 60 * 6e4;
|
|
10603
|
+
var RECENT_TIER_MS = 24 * 60 * 6e4;
|
|
10528
10604
|
var ContextSchema = external_exports.object({
|
|
10529
10605
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
10530
10606
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -10869,6 +10945,10 @@ var AgendaTurnSchema = external_exports.object({
|
|
|
10869
10945
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
10870
10946
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
10871
10947
|
agentId: external_exports.string().optional(),
|
|
10948
|
+
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
10949
|
+
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
10950
|
+
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
10951
|
+
onTable: external_exports.boolean().optional(),
|
|
10872
10952
|
select: SelectShapeSchema.optional(),
|
|
10873
10953
|
options: external_exports.array(OptionSchema.omit({ id: true })).optional(),
|
|
10874
10954
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
@@ -10943,9 +11023,16 @@ var InboxItemSchema = external_exports.object({
|
|
|
10943
11023
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
10944
11024
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
10945
11025
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
10946
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are
|
|
10947
|
-
|
|
10948
|
-
|
|
11026
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
11027
|
+
options: external_exports.array(OptionSchema).optional(),
|
|
11028
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
11029
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
11030
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
11031
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
11032
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
11033
|
+
choices: external_exports.array(OptionSchema).optional(),
|
|
11034
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
11035
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
10949
11036
|
})).optional(),
|
|
10950
11037
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
10951
11038
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -11382,7 +11469,11 @@ var QueueItemSchema = external_exports.object({
|
|
|
11382
11469
|
from: external_exports.enum(["person", "agent"]),
|
|
11383
11470
|
said: external_exports.string(),
|
|
11384
11471
|
at: external_exports.string(),
|
|
11385
|
-
entryId: external_exports.string()
|
|
11472
|
+
entryId: external_exports.string(),
|
|
11473
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
11474
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
11475
|
+
* none was. The Work row reads it before `said`. */
|
|
11476
|
+
summary: external_exports.string().optional()
|
|
11386
11477
|
}).nullable().optional(),
|
|
11387
11478
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
11388
11479
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -11719,6 +11810,9 @@ var CallRecapSchema = external_exports.object({
|
|
|
11719
11810
|
title: external_exports.string(),
|
|
11720
11811
|
owner: external_exports.string(),
|
|
11721
11812
|
state: external_exports.string(),
|
|
11813
|
+
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
11814
|
+
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
11815
|
+
presented: external_exports.boolean(),
|
|
11722
11816
|
questions: external_exports.array(external_exports.object({ id: external_exports.string().uuid(), state: external_exports.string(), title: external_exports.string() })),
|
|
11723
11817
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
11724
11818
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
@@ -11726,9 +11820,33 @@ var CallRecapSchema = external_exports.object({
|
|
|
11726
11820
|
* neither wrote one. */
|
|
11727
11821
|
/** `about` is the request the line answered (its question), null for words that answered none —
|
|
11728
11822
|
* the key the screen groups on, so one question is one row however many times it was answered. */
|
|
11729
|
-
|
|
11823
|
+
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
11824
|
+
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
11825
|
+
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
11826
|
+
lines: external_exports.array(external_exports.object({
|
|
11827
|
+
entryId: external_exports.string().uuid(),
|
|
11828
|
+
words: external_exports.string(),
|
|
11829
|
+
summary: external_exports.string().optional(),
|
|
11830
|
+
about: external_exports.string().nullable().optional(),
|
|
11831
|
+
reply: external_exports.string().nullable().optional(),
|
|
11832
|
+
replyKind: external_exports.string().nullable().optional(),
|
|
11833
|
+
at: external_exports.array(external_exports.number()).optional()
|
|
11834
|
+
}))
|
|
11730
11835
|
})),
|
|
11731
11836
|
unfiled: external_exports.array(external_exports.object({ lineId: external_exports.string().uuid(), words: external_exports.string(), atMs: external_exports.number() })),
|
|
11837
|
+
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
11838
|
+
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
11839
|
+
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
11840
|
+
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
11841
|
+
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
11842
|
+
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
11843
|
+
said: external_exports.array(external_exports.object({
|
|
11844
|
+
lineId: external_exports.string().uuid(),
|
|
11845
|
+
speaker: external_exports.enum(["user", "agent"]),
|
|
11846
|
+
agent: external_exports.object({ id: external_exports.string().nullable(), name: external_exports.string().nullable() }).nullable(),
|
|
11847
|
+
words: external_exports.string(),
|
|
11848
|
+
atMs: external_exports.number()
|
|
11849
|
+
})),
|
|
11732
11850
|
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
11733
11851
|
});
|
|
11734
11852
|
function sessionSlot(sessionId2) {
|
|
@@ -12283,7 +12401,8 @@ var CATALOG = [
|
|
|
12283
12401
|
authProbe: ["claude", "auth", "status"],
|
|
12284
12402
|
loginHint: "Run `claude` once in a terminal and sign in.",
|
|
12285
12403
|
installCli: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
12286
|
-
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp"
|
|
12404
|
+
installAdapter: "npm install -g @agentclientprotocol/claude-agent-acp",
|
|
12405
|
+
models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"]
|
|
12287
12406
|
},
|
|
12288
12407
|
{
|
|
12289
12408
|
name: "codex",
|
|
@@ -12293,7 +12412,8 @@ var CATALOG = [
|
|
|
12293
12412
|
authProbe: ["codex", "login", "status"],
|
|
12294
12413
|
loginHint: "Run `codex login` to authenticate.",
|
|
12295
12414
|
installCli: "curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
12296
|
-
installAdapter: "npm install -g @agentclientprotocol/codex-acp"
|
|
12415
|
+
installAdapter: "npm install -g @agentclientprotocol/codex-acp",
|
|
12416
|
+
models: ["gpt-4o", "gpt-4o-mini", "o1", "o1-mini"]
|
|
12297
12417
|
},
|
|
12298
12418
|
{
|
|
12299
12419
|
name: "agy",
|
|
@@ -12303,7 +12423,8 @@ var CATALOG = [
|
|
|
12303
12423
|
adapter: "paigy-agy-acp",
|
|
12304
12424
|
authProbe: ["agy", "help"],
|
|
12305
12425
|
loginHint: "Install and set up Antigravity (`agy`).",
|
|
12306
|
-
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash"
|
|
12426
|
+
installCli: "curl -fsSL https://antigravity.dev/install.sh | bash",
|
|
12427
|
+
models: ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-pro-exp"]
|
|
12307
12428
|
}
|
|
12308
12429
|
];
|
|
12309
12430
|
function probeDirs(home = homedir3()) {
|
|
@@ -12339,7 +12460,7 @@ function runProbe(argv) {
|
|
|
12339
12460
|
function detect(entry, deps = {}) {
|
|
12340
12461
|
const find = deps.which ?? which;
|
|
12341
12462
|
const probe = deps.probe ?? runProbe;
|
|
12342
|
-
const base = { name: entry.name, label: entry.label };
|
|
12463
|
+
const base = { name: entry.name, label: entry.label, models: entry.models };
|
|
12343
12464
|
const cliPath = find(entry.cli);
|
|
12344
12465
|
if (!cliPath) return { ...base, status: "missing", hint: `${entry.label} is not installed.`, install: entry.installCli };
|
|
12345
12466
|
const adapterPath = entry.adapter ? find(entry.adapter) : null;
|
|
@@ -13059,10 +13180,14 @@ function localHolderPids(deps = {}) {
|
|
|
13059
13180
|
if (!/^\d+\.json$/.test(file)) continue;
|
|
13060
13181
|
let row;
|
|
13061
13182
|
try {
|
|
13062
|
-
row =
|
|
13183
|
+
row = parseSessionRecord(read3(join6(dir, file)));
|
|
13063
13184
|
} catch {
|
|
13064
13185
|
continue;
|
|
13065
13186
|
}
|
|
13187
|
+
if (!row) {
|
|
13188
|
+
deps.unreadable?.(file);
|
|
13189
|
+
continue;
|
|
13190
|
+
}
|
|
13066
13191
|
const pid = typeof row.pid === "number" ? row.pid : Number(file.slice(0, -5));
|
|
13067
13192
|
if (typeof row.sessionId !== "string" || !row.sessionId.trim() || !Number.isInteger(pid) || pid <= 0) continue;
|
|
13068
13193
|
if (!holds(pid, row.procStart, check)) continue;
|
|
@@ -13104,10 +13229,14 @@ function localHolders(deps = {}) {
|
|
|
13104
13229
|
if (!/^\d+\.json$/.test(file)) continue;
|
|
13105
13230
|
let row;
|
|
13106
13231
|
try {
|
|
13107
|
-
row =
|
|
13232
|
+
row = parseSessionRecord(read3(join6(dir, file)));
|
|
13108
13233
|
} catch {
|
|
13109
13234
|
continue;
|
|
13110
13235
|
}
|
|
13236
|
+
if (!row) {
|
|
13237
|
+
deps.unreadable?.(file);
|
|
13238
|
+
continue;
|
|
13239
|
+
}
|
|
13111
13240
|
const pid = typeof row.pid === "number" ? row.pid : Number(file.slice(0, -5));
|
|
13112
13241
|
if (typeof row.sessionId !== "string" || !row.sessionId.trim() || !Number.isInteger(pid) || pid <= 0) continue;
|
|
13113
13242
|
const slot = sessionSlot(row.sessionId);
|
|
@@ -14643,8 +14772,41 @@ var UpdateInputSchema2 = external_exports.object({
|
|
|
14643
14772
|
message: external_exports.string().trim().min(1).describe(`The one point, at most ${ASK_MAX2} characters; the context goes in units.`),
|
|
14644
14773
|
goalIds: external_exports.array(external_exports.string().uuid()).min(1).max(10).describe("The Goals it is about, each one that exists: the one it is mainly about first."),
|
|
14645
14774
|
units: units2,
|
|
14775
|
+
// A REPORT KEEPS ITS TAP (#2799, #3260; owner, 2026-10-08, settling #2776: "keep the reports open
|
|
14776
|
+
// until the user acknowledges them"). A report asks nothing, so it opens no Question -- but it
|
|
14777
|
+
// still waits to be acknowledged, and it carries the ONE tap that closes it. Never a decision: the
|
|
14778
|
+
// tap lands as a contribution, the person's own words on the Goal, because there is no Question
|
|
14779
|
+
// for it to answer.
|
|
14780
|
+
//
|
|
14781
|
+
// AND ITS WORDS ARE WRITTEN FOR THIS REPORT (owner, 2026-10-08, Goal 3c12d3c6: "Agent
|
|
14782
|
+
// acknowledgments vary (e.g. 'sounds good, I will test later') instead of always 'got it'"). `Got
|
|
14783
|
+
// it` and `Noted` say only that a card was cleared, where "Sounds good, I'll test later" says the
|
|
14784
|
+
// work was RECEIVED and not verified -- the distinction a session spent an hour recovering from a
|
|
14785
|
+
// call transcript on 2026-10-07, having read "not quite" as a defect report when it meant *not
|
|
14786
|
+
// tested yet*. The words are worth writing because the agent reads them back.
|
|
14787
|
+
//
|
|
14788
|
+
// ONE STRING, NOT A LIST (owner, 2026-10-08 15:47, Goal 31a1e2ac, asked whether an agent should
|
|
14789
|
+
// write up to four of them: "just on the cards, but it should be more like just they should only
|
|
14790
|
+
// get one basically string value that they can enter that replaces got it"). A report has exactly
|
|
14791
|
+
// one way to close, so the only thing an agent writes is what that button SAYS. #3260's 1..6
|
|
14792
|
+
// `options` and this field are one job: the list is deleted, not deprecated (ONE JOB, ONE
|
|
14793
|
+
// MECHANISM). It rides the door as the report's single stored option (`storedOption`,
|
|
14794
|
+
// `apps/api/src/goal/router.ts`), so no reader below the door learns a second shape.
|
|
14795
|
+
reply: external_exports.string().trim().min(1).describe(
|
|
14796
|
+
`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. Left out, the button says "Got it", as every card did.`
|
|
14797
|
+
).optional(),
|
|
14646
14798
|
userExplicitlyRequested: userExplicitlyRequested2
|
|
14647
|
-
}).strict().superRefine((u, ctx) =>
|
|
14799
|
+
}).strict().superRefine((u, ctx) => {
|
|
14800
|
+
refuseOverCaps2(u.message, u.units, "message", ctx);
|
|
14801
|
+
if (u.reply && u.reply.length > OPTION_LABEL_MAX2) {
|
|
14802
|
+
ctx.addIssue({
|
|
14803
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14804
|
+
path: ["reply"],
|
|
14805
|
+
params: { refusal: "reply_too_long", length: u.reply.length, cap: OPTION_LABEL_MAX2 },
|
|
14806
|
+
message: `reply_too_long: the reply is ${u.reply.length} characters; the cap is ${OPTION_LABEL_MAX2}. It is a button's words, so say it in a breath.`
|
|
14807
|
+
});
|
|
14808
|
+
}
|
|
14809
|
+
});
|
|
14648
14810
|
var questionFields2 = {
|
|
14649
14811
|
question: external_exports.string().trim().min(1).describe(
|
|
14650
14812
|
`ONE question, in at most ${ASK_MAX2} characters, and only what is needed to answer it; the context goes in units. Several questions are several objects, one each: an answer settles the one question it was given, so a person who answers the part of a bundled question that interested them settles nothing and is asked again. Separate questions cost nothing: ANY contact for a person already on a call JOINS that call, so several arrive as one call.`
|
|
@@ -14728,6 +14890,8 @@ QUESTIONS: one question per object; several questions are several objects in one
|
|
|
14728
14890
|
|
|
14729
14891
|
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.
|
|
14730
14892
|
|
|
14893
|
+
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".
|
|
14894
|
+
|
|
14731
14895
|
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.
|
|
14732
14896
|
|
|
14733
14897
|
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\`).`;
|
|
@@ -15082,8 +15246,10 @@ var DISTILLER_DEFECTS2 = [
|
|
|
15082
15246
|
// something the person answered or asked for did not save, was refused or dropped
|
|
15083
15247
|
"ignored",
|
|
15084
15248
|
// the person asked or said something and was never answered (the critic's ignored_a_question)
|
|
15085
|
-
"promised"
|
|
15249
|
+
"promised",
|
|
15086
15250
|
// said it did or would do something, and nothing shows it done (the critic's broken_promise)
|
|
15251
|
+
"dropped"
|
|
15252
|
+
// something it had to say never played and was never said later (owner, 2026-10-08, call 65433a9d)
|
|
15087
15253
|
];
|
|
15088
15254
|
var DistillerReplySchema2 = external_exports.object({
|
|
15089
15255
|
defects: external_exports.array(external_exports.object({
|
|
@@ -15131,6 +15297,8 @@ function entryWords(entry) {
|
|
|
15131
15297
|
}
|
|
15132
15298
|
var LIVE_MS2 = 3 * 6e4;
|
|
15133
15299
|
var WORKING_MS2 = 60 * 6e4;
|
|
15300
|
+
var WORKING_TIER_MS2 = 2 * 60 * 6e4;
|
|
15301
|
+
var RECENT_TIER_MS2 = 24 * 60 * 6e4;
|
|
15134
15302
|
var ContextSchema2 = external_exports.object({
|
|
15135
15303
|
title: external_exports.string().min(1).describe("One-line headline of what you need (required, non-empty)."),
|
|
15136
15304
|
description: external_exports.array(external_exports.string().min(1)).describe(
|
|
@@ -15475,6 +15643,10 @@ var AgendaTurnSchema2 = external_exports.object({
|
|
|
15475
15643
|
* so "who said that" survives the call, and it rides PER TURN because a coalesced call
|
|
15476
15644
|
* speaks for several agents — the turn is the only place that knows which. */
|
|
15477
15645
|
agentId: external_exports.string().optional(),
|
|
15646
|
+
/** ITS QUESTION IS ON THE TABLE (#3292): a talker line about a question the person owes. The walk tells
|
|
15647
|
+
* the phone (`asking`, so the agenda goes gold and its options show) without treating the line as a
|
|
15648
|
+
* question turn — the talker owns re-asking and the waits, so `question` stays null. */
|
|
15649
|
+
onTable: external_exports.boolean().optional(),
|
|
15478
15650
|
select: SelectShapeSchema2.optional(),
|
|
15479
15651
|
options: external_exports.array(OptionSchema2.omit({ id: true })).optional(),
|
|
15480
15652
|
/* `pace` STOOD HERE (#826). A turn could carry seconds and the model chose them. The walk
|
|
@@ -15549,9 +15721,16 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
15549
15721
|
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
15550
15722
|
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
15551
15723
|
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
15552
|
-
* never re-sent as a second card to be seen mid-call. Words-only options are
|
|
15553
|
-
|
|
15554
|
-
|
|
15724
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are `choices`. */
|
|
15725
|
+
options: external_exports.array(OptionSchema2).optional(),
|
|
15726
|
+
/** ITS OPTIONS WHEN THEY ARE WORDS (owner, 2026-10-02, on a call: "whenever there are pre-made
|
|
15727
|
+
* options … now for a checklist, we should also show them on the screen"; design C, 2026-10-08):
|
|
15728
|
+
* numbered chips above the call's controls, so a checklist is never only something read aloud. A
|
|
15729
|
+
* field of its own so `options` keeps meaning "previews": a phone on an older bundle opens the
|
|
15730
|
+
* preview layout for any `options`, and must see exactly what it did. */
|
|
15731
|
+
choices: external_exports.array(OptionSchema2).optional(),
|
|
15732
|
+
/** HOW MANY MAY BE PICKED, beside its `choices`: one, any (`many`) or an order (`rank`). */
|
|
15733
|
+
select: external_exports.enum(["one", "many", "rank"]).optional()
|
|
15555
15734
|
})).optional(),
|
|
15556
15735
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
15557
15736
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -15988,7 +16167,11 @@ var QueueItemSchema2 = external_exports.object({
|
|
|
15988
16167
|
from: external_exports.enum(["person", "agent"]),
|
|
15989
16168
|
said: external_exports.string(),
|
|
15990
16169
|
at: external_exports.string(),
|
|
15991
|
-
entryId: external_exports.string()
|
|
16170
|
+
entryId: external_exports.string(),
|
|
16171
|
+
/** ITS SUMMARY IN AT MOST TEN WORDS (owner, 2026-10-08: "the same switch to the more summarized sources
|
|
16172
|
+
* at the work tab top level screen too"): what the talker or filer wrote on the Entry; absent when
|
|
16173
|
+
* none was. The Work row reads it before `said`. */
|
|
16174
|
+
summary: external_exports.string().optional()
|
|
15992
16175
|
}).nullable().optional(),
|
|
15993
16176
|
/** WHEN THIS PERSON LAST PUT A HAND ON IT THEMSELVES (owner, Paigy Goal 16d18f51, 2026-09-30):
|
|
15994
16177
|
* the newest Entry on the Goal they wrote, of any kind — a line they added, a reply to a note, an
|
|
@@ -16323,6 +16506,9 @@ var CallRecapSchema2 = external_exports.object({
|
|
|
16323
16506
|
title: external_exports.string(),
|
|
16324
16507
|
owner: external_exports.string(),
|
|
16325
16508
|
state: external_exports.string(),
|
|
16509
|
+
/** Whether the call put it to the person. An item it carried and never said has no row on the
|
|
16510
|
+
* review (owner, 2026-10-08: "why do we get the third one when it didn't happen in the call?"). */
|
|
16511
|
+
presented: external_exports.boolean(),
|
|
16326
16512
|
questions: external_exports.array(external_exports.object({ id: external_exports.string().uuid(), state: external_exports.string(), title: external_exports.string() })),
|
|
16327
16513
|
/** `words` is always what they SAID, verbatim — the record, never replaced. `summary` is what
|
|
16328
16514
|
* the line says in ten words (owner, 2026-10-07): the filer's, or the talker's for the answer it
|
|
@@ -16330,9 +16516,33 @@ var CallRecapSchema2 = external_exports.object({
|
|
|
16330
16516
|
* neither wrote one. */
|
|
16331
16517
|
/** `about` is the request the line answered (its question), null for words that answered none —
|
|
16332
16518
|
* the key the screen groups on, so one question is one row however many times it was answered. */
|
|
16333
|
-
|
|
16519
|
+
/** `reply` is the reply the line makes up (an answer, an ok, a deferral), so one reply is one row
|
|
16520
|
+
* however many lines it cites; `at` is the call lines it came from, so a filed line an answer already
|
|
16521
|
+
* said is not drawn again (owner, 2026-10-07: "fix them all"); `replyKind` says which replies are answers. */
|
|
16522
|
+
lines: external_exports.array(external_exports.object({
|
|
16523
|
+
entryId: external_exports.string().uuid(),
|
|
16524
|
+
words: external_exports.string(),
|
|
16525
|
+
summary: external_exports.string().optional(),
|
|
16526
|
+
about: external_exports.string().nullable().optional(),
|
|
16527
|
+
reply: external_exports.string().nullable().optional(),
|
|
16528
|
+
replyKind: external_exports.string().nullable().optional(),
|
|
16529
|
+
at: external_exports.array(external_exports.number()).optional()
|
|
16530
|
+
}))
|
|
16334
16531
|
})),
|
|
16335
16532
|
unfiled: external_exports.array(external_exports.object({ lineId: external_exports.string().uuid(), words: external_exports.string(), atMs: external_exports.number() })),
|
|
16533
|
+
/** THE PUBLIC TRANSCRIPT — every line said on the call, in the order it was said, and WHO SAID IT
|
|
16534
|
+
* (owner, 2026-10-08: "the public view should only have the lines and who said them, and that's
|
|
16535
|
+
* it"; `docs/brain/phase-three/modular.md` build step 3). `speaker` is the role: "user" is the
|
|
16536
|
+
* person, "agent" an agent speaking, named in `agent`. The timeline's action lines are ours, not
|
|
16537
|
+
* theirs, and are not here; nor is a sealed line, which the server has no words for. It stands
|
|
16538
|
+
* BESIDE `topics` and `unfiled` — what the call did with the words — never instead of them. */
|
|
16539
|
+
said: external_exports.array(external_exports.object({
|
|
16540
|
+
lineId: external_exports.string().uuid(),
|
|
16541
|
+
speaker: external_exports.enum(["user", "agent"]),
|
|
16542
|
+
agent: external_exports.object({ id: external_exports.string().nullable(), name: external_exports.string().nullable() }).nullable(),
|
|
16543
|
+
words: external_exports.string(),
|
|
16544
|
+
atMs: external_exports.number()
|
|
16545
|
+
})),
|
|
16336
16546
|
more: external_exports.object({ lines: external_exports.number(), entries: external_exports.number(), topics: external_exports.number() })
|
|
16337
16547
|
});
|
|
16338
16548
|
|
|
@@ -16734,7 +16944,8 @@ function startSession(opts) {
|
|
|
16734
16944
|
if (!existsSync4(cwd)) {
|
|
16735
16945
|
queueMicrotask(() => opts.onEvent({ kind: "error", message: `workspace does not exist: ${cwd}` }));
|
|
16736
16946
|
}
|
|
16737
|
-
|
|
16947
|
+
let codexConfig = opts.harness === "codex" ? JSON.parse(process.env.CODEX_CONFIG || "{}") : null;
|
|
16948
|
+
if (opts.model && opts.harness === "codex") codexConfig = { ...codexConfig, model: opts.model };
|
|
16738
16949
|
if (codexConfig) {
|
|
16739
16950
|
const key3 = "mcp_servers.paigy.env_vars";
|
|
16740
16951
|
const configPath = join9(process.env.CODEX_HOME || join9(homedir9(), ".codex"), "config.toml");
|
|
@@ -16760,6 +16971,8 @@ function startSession(opts) {
|
|
|
16760
16971
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter3),
|
|
16761
16972
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
16762
16973
|
...opts.instance ? { PAIGY_INSTANCE_ID: opts.instance } : {},
|
|
16974
|
+
...opts.model && opts.harness === "claude" ? { CLAUDE_MODEL_CONFIG: JSON.stringify({ provider: "anthropic", model: opts.model }) } : {},
|
|
16975
|
+
...opts.model && opts.harness === "agy" ? { AGY_MODEL_CONFIG: JSON.stringify({ model: opts.model }) } : {},
|
|
16763
16976
|
...codexConfig ? { CODEX_CONFIG: JSON.stringify(codexConfig) } : {},
|
|
16764
16977
|
...opts.session ?? opts.resume ? { PAIGY_SESSION_ID: opts.session ?? opts.resume } : {},
|
|
16765
16978
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
@@ -17728,7 +17941,7 @@ var claimedRun = (sessionId2) => {
|
|
|
17728
17941
|
const slot = sessionSlot(sessionId2);
|
|
17729
17942
|
return { slot, key: runKey(slot) };
|
|
17730
17943
|
};
|
|
17731
|
-
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
17944
|
+
var launchRun = (spec) => ({ session: spec.sessionId, model: spec.model });
|
|
17732
17945
|
var BOUND_ELSEWHERE_MS = 10 * 6e4;
|
|
17733
17946
|
var boundElsewhere = /* @__PURE__ */ new Map();
|
|
17734
17947
|
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";
|
|
@@ -17837,7 +18050,7 @@ function startHost(opts) {
|
|
|
17837
18050
|
});
|
|
17838
18051
|
void heartbeat({
|
|
17839
18052
|
version: VERSION,
|
|
17840
|
-
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
18053
|
+
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status, models: a.models })),
|
|
17841
18054
|
workspaces: listWorkspaces(opts.wsDeps),
|
|
17842
18055
|
// The repos inside them, so a session started for work on one opens IN it (Goal 26982211).
|
|
17843
18056
|
repos: listRepos(opts.wsDeps)
|
|
@@ -17929,9 +18142,14 @@ function startHost(opts) {
|
|
|
17929
18142
|
let lastPresence = /* @__PURE__ */ new Map();
|
|
17930
18143
|
async function evidence() {
|
|
17931
18144
|
const presence = await presenceMap();
|
|
17932
|
-
lastHolders = localHolders()
|
|
18145
|
+
lastHolders = localHolders({ unreadable: (file) => {
|
|
18146
|
+
if (unreadable.has(file)) return;
|
|
18147
|
+
unreadable.add(file);
|
|
18148
|
+
opts.log(`\u25CC session record ${file} has no object in it \u2014 the process it names reads as not running here`);
|
|
18149
|
+
} });
|
|
17933
18150
|
return { presence, holders: lastHolders };
|
|
17934
18151
|
}
|
|
18152
|
+
const unreadable = /* @__PURE__ */ new Set();
|
|
17935
18153
|
let lastHolders = /* @__PURE__ */ new Map();
|
|
17936
18154
|
async function wakeSlot(slot, { presence, holders }, cause = "sweep") {
|
|
17937
18155
|
{
|