@paigy/harness 0.3.16 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +78 -17
- package/dist/main.js +73 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25059,7 +25059,13 @@ var init_dist = __esm({
|
|
|
25059
25059
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
25060
25060
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
25061
25061
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
25062
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional()
|
|
25062
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
25063
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
25064
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
25065
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
25066
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
25067
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
25068
|
+
options: external_exports.array(OptionSchema).optional()
|
|
25063
25069
|
})).optional(),
|
|
25064
25070
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
25065
25071
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -30926,6 +30932,7 @@ import { resolve, delimiter as delimiter2 } from "path";
|
|
|
30926
30932
|
|
|
30927
30933
|
// src/harness/acp.ts
|
|
30928
30934
|
var none = { events: [], writes: [] };
|
|
30935
|
+
var NOTHING_TO_RESUME = /* @__PURE__ */ new Set([-32002, -32601]);
|
|
30929
30936
|
function optionFor(options, decision) {
|
|
30930
30937
|
const want = decision.allow ? "allow_once" : "reject_once";
|
|
30931
30938
|
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
@@ -31022,20 +31029,29 @@ var AcpDriver = class {
|
|
|
31022
31029
|
if (msg.id === this.initId) {
|
|
31023
31030
|
this.initId = void 0;
|
|
31024
31031
|
if (msg.error) {
|
|
31025
|
-
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}
|
|
31032
|
+
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}`, fatal: true }], writes: [] };
|
|
31026
31033
|
}
|
|
31027
31034
|
this.sessionNewId = this.nextId++;
|
|
31028
31035
|
return {
|
|
31029
31036
|
events: [],
|
|
31030
|
-
writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } :
|
|
31037
|
+
writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } : this.sessionNew(this.sessionNewId))]
|
|
31031
31038
|
};
|
|
31032
31039
|
}
|
|
31033
31040
|
if (msg.id === this.sessionNewId) {
|
|
31034
31041
|
this.sessionNewId = void 0;
|
|
31042
|
+
const resuming = this.resume !== void 0;
|
|
31043
|
+
if (resuming && msg.error?.code !== void 0 && NOTHING_TO_RESUME.has(msg.error.code)) {
|
|
31044
|
+
this.resume = void 0;
|
|
31045
|
+
this.sessionNewId = this.nextId++;
|
|
31046
|
+
return {
|
|
31047
|
+
events: [{ kind: "error", message: `session/resume found nothing to resume (${msg.error.message}) \u2014 opening a new conversation` }],
|
|
31048
|
+
writes: [frame2(this.sessionNew(this.sessionNewId))]
|
|
31049
|
+
};
|
|
31050
|
+
}
|
|
31035
31051
|
const sessionId2 = msg.error ? void 0 : msg.result?.sessionId ?? this.resume;
|
|
31036
31052
|
if (!sessionId2) {
|
|
31037
31053
|
return {
|
|
31038
|
-
events: [{ kind: "error", message: `${
|
|
31054
|
+
events: [{ kind: "error", message: `${resuming ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}`, fatal: true }],
|
|
31039
31055
|
writes: []
|
|
31040
31056
|
};
|
|
31041
31057
|
}
|
|
@@ -31128,6 +31144,9 @@ var AcpDriver = class {
|
|
|
31128
31144
|
writes: []
|
|
31129
31145
|
};
|
|
31130
31146
|
}
|
|
31147
|
+
sessionNew(id) {
|
|
31148
|
+
return { id, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } };
|
|
31149
|
+
}
|
|
31131
31150
|
/** Send the queued prompts as one turn, if the agent can take one right now. */
|
|
31132
31151
|
flush() {
|
|
31133
31152
|
if (!this.sessionId || this.promptId !== void 0 || !this.queued.length) return [];
|
|
@@ -31173,7 +31192,7 @@ function startSession(opts) {
|
|
|
31173
31192
|
...process.env,
|
|
31174
31193
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
31175
31194
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
31176
|
-
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {},
|
|
31195
|
+
...opts.resume ?? opts.session ? { PAIGY_SESSION_ID: opts.resume ?? opts.session } : {},
|
|
31177
31196
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
31178
31197
|
// bridge's pump typing answers in, so nothing inside it may start a second delivery path.
|
|
31179
31198
|
// Exported on EVERY spawn — `PAIGY_SESSION_ID` above is the conversation to resume, and a
|
|
@@ -31183,7 +31202,11 @@ function startSession(opts) {
|
|
|
31183
31202
|
},
|
|
31184
31203
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31185
31204
|
});
|
|
31205
|
+
let ended2 = false;
|
|
31206
|
+
child.stdin?.on("error", () => {
|
|
31207
|
+
});
|
|
31186
31208
|
const write = (frames) => {
|
|
31209
|
+
if (ended2) return;
|
|
31187
31210
|
for (const f of frames) child.stdin?.write(`${f}
|
|
31188
31211
|
`);
|
|
31189
31212
|
};
|
|
@@ -31192,6 +31215,7 @@ function startSession(opts) {
|
|
|
31192
31215
|
if (text) opts.onEvent({ kind: "error", message: text });
|
|
31193
31216
|
});
|
|
31194
31217
|
child.on("exit", (code) => {
|
|
31218
|
+
ended2 = true;
|
|
31195
31219
|
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
31196
31220
|
opts.onExit?.();
|
|
31197
31221
|
});
|
|
@@ -31220,6 +31244,7 @@ function startSession(opts) {
|
|
|
31220
31244
|
},
|
|
31221
31245
|
stop() {
|
|
31222
31246
|
write(driver.close());
|
|
31247
|
+
ended2 = true;
|
|
31223
31248
|
child.kill();
|
|
31224
31249
|
}
|
|
31225
31250
|
};
|
|
@@ -33067,7 +33092,13 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
33067
33092
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
33068
33093
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
33069
33094
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
33070
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional()
|
|
33095
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
33096
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
33097
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
33098
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
33099
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
33100
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
33101
|
+
options: external_exports.array(OptionSchema2).optional()
|
|
33071
33102
|
})).optional(),
|
|
33072
33103
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
33073
33104
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -33900,6 +33931,23 @@ async function finish(result, state, rail, opts) {
|
|
|
33900
33931
|
opts?.log?.(`\u26A0 finish contact failed: ${error.message}`);
|
|
33901
33932
|
}
|
|
33902
33933
|
}
|
|
33934
|
+
async function failedToStart(reason, state, rail, opts) {
|
|
33935
|
+
try {
|
|
33936
|
+
const goalId = state.goal?.id ?? (await rail.claim()).goalId;
|
|
33937
|
+
state.goal = void 0;
|
|
33938
|
+
state.resting = true;
|
|
33939
|
+
if (!goalId) return;
|
|
33940
|
+
await rail.contact({
|
|
33941
|
+
asks: [{ ask: `The session could not start, so nothing is working on this yet.
|
|
33942
|
+
|
|
33943
|
+
${reason}`, parentId: goalId }],
|
|
33944
|
+
waiting: "none",
|
|
33945
|
+
channel: "notification"
|
|
33946
|
+
});
|
|
33947
|
+
} catch (error) {
|
|
33948
|
+
opts?.log?.(`\u26A0 could not report the failed start: ${error.message}`);
|
|
33949
|
+
}
|
|
33950
|
+
}
|
|
33903
33951
|
async function afterTurn(event, state, rail, opts) {
|
|
33904
33952
|
if (!event.failed && !endsWithQuestion(event.result)) {
|
|
33905
33953
|
await finish(event.result, state, rail, opts);
|
|
@@ -33924,10 +33972,16 @@ function runHarness(opts) {
|
|
|
33924
33972
|
let tail = [];
|
|
33925
33973
|
const state = { resting: true };
|
|
33926
33974
|
let session = null;
|
|
33927
|
-
const
|
|
33975
|
+
const speaksAs = opts.resume ?? opts.session;
|
|
33976
|
+
const rail = railFor({ token: opts.token, ...speaksAs ? { reach: reachAs(speaksAs) } : {} });
|
|
33928
33977
|
async function handle(event) {
|
|
33929
33978
|
if (event.kind === "error") {
|
|
33930
33979
|
opts.log(`\u26A0 ${event.message}`);
|
|
33980
|
+
if (event.fatal && running) {
|
|
33981
|
+
opts.log(`\u2716 could not start: ${event.message}`);
|
|
33982
|
+
await failedToStart(event.message, state, rail, { log: opts.log });
|
|
33983
|
+
stop2();
|
|
33984
|
+
}
|
|
33931
33985
|
return;
|
|
33932
33986
|
}
|
|
33933
33987
|
if (event.kind === "permission") {
|
|
@@ -33973,6 +34027,7 @@ function runHarness(opts) {
|
|
|
33973
34027
|
mode: opts.mode,
|
|
33974
34028
|
...opts.token ? { token: opts.token } : {},
|
|
33975
34029
|
...opts.resume ? { resume: opts.resume } : {},
|
|
34030
|
+
...opts.session ? { session: opts.session } : {},
|
|
33976
34031
|
...opts.bin ? { bin: opts.bin } : {},
|
|
33977
34032
|
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
33978
34033
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
@@ -33981,20 +34036,21 @@ function runHarness(opts) {
|
|
|
33981
34036
|
(e) => opts.log(`\u26A0 conversation loop stopped: ${e.message}`)
|
|
33982
34037
|
);
|
|
33983
34038
|
opts.log(`\u25B6 started ${opts.harness} in ${opts.cwd} (${opts.mode} mode)`);
|
|
34039
|
+
function stop2() {
|
|
34040
|
+
running = false;
|
|
34041
|
+
tail = [];
|
|
34042
|
+
cancelAsks(state);
|
|
34043
|
+
session?.stop();
|
|
34044
|
+
session = null;
|
|
34045
|
+
opts.log("\u25A0 stopped");
|
|
34046
|
+
}
|
|
33984
34047
|
return {
|
|
33985
34048
|
send(text) {
|
|
33986
34049
|
session?.send(text);
|
|
33987
34050
|
},
|
|
33988
34051
|
working: () => running && state.resting !== true,
|
|
33989
34052
|
tail: () => [...tail],
|
|
33990
|
-
stop
|
|
33991
|
-
running = false;
|
|
33992
|
-
tail = [];
|
|
33993
|
-
cancelAsks(state);
|
|
33994
|
-
session?.stop();
|
|
33995
|
-
session = null;
|
|
33996
|
-
opts.log("\u25A0 stopped");
|
|
33997
|
-
}
|
|
34053
|
+
stop: stop2
|
|
33998
34054
|
};
|
|
33999
34055
|
}
|
|
34000
34056
|
|
|
@@ -34185,6 +34241,7 @@ var claimedRun = (sessionId2) => {
|
|
|
34185
34241
|
const slot = sessionSlot(sessionId2);
|
|
34186
34242
|
return { slot, key: runKey(slot) };
|
|
34187
34243
|
};
|
|
34244
|
+
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
34188
34245
|
async function slotHasWaitingWork(token, session) {
|
|
34189
34246
|
return (await checkReplies({ token, ...session ? { reach: reachAs(session) } : {} })).deliveries.length > 0;
|
|
34190
34247
|
}
|
|
@@ -34243,7 +34300,7 @@ function startHost(opts) {
|
|
|
34243
34300
|
mode: "bypass",
|
|
34244
34301
|
prompt: "",
|
|
34245
34302
|
token: spec.token,
|
|
34246
|
-
|
|
34303
|
+
...launchRun(spec),
|
|
34247
34304
|
onExit: () => {
|
|
34248
34305
|
runs.delete(key);
|
|
34249
34306
|
opts.log(`\u2716 ${label} ended`);
|
|
@@ -34799,7 +34856,11 @@ async function main() {
|
|
|
34799
34856
|
if (parsed.hatch) {
|
|
34800
34857
|
overrideToken(readToken("Desktop") || null);
|
|
34801
34858
|
const minted = await hatch(parsed.hatch, parsed.voice ?? null);
|
|
34802
|
-
saveToken({ access_token: minted.token, name: minted.name, device: null }, parsed.slot ?? minted.name);
|
|
34859
|
+
saveToken({ access_token: minted.token, name: minted.name ?? parsed.hatch, device: null }, parsed.slot ?? minted.name ?? parsed.hatch);
|
|
34860
|
+
if (!minted.name) {
|
|
34861
|
+
console.log(`\u2713 hatched, unnamed \u2014 you already have an agent called "${parsed.hatch}". Pick another name in the app's Agents screen.`);
|
|
34862
|
+
return;
|
|
34863
|
+
}
|
|
34803
34864
|
console.log(`\u2713 hatched "${minted.name}" \u2014 it's on your phone's Agents screen now.`);
|
|
34804
34865
|
console.log(` speak as it: PAIGY_AGENT="${minted.name}" <any paigy tool>`);
|
|
34805
34866
|
console.log(` run with it: paigy-harness --identity "${minted.name}" --harness codex "\u2026"`);
|
package/dist/main.js
CHANGED
|
@@ -10449,7 +10449,13 @@ var InboxItemSchema = external_exports.object({
|
|
|
10449
10449
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
10450
10450
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
10451
10451
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
10452
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional()
|
|
10452
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema.optional() }).optional(),
|
|
10453
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
10454
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
10455
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
10456
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
10457
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
10458
|
+
options: external_exports.array(OptionSchema).optional()
|
|
10453
10459
|
})).optional(),
|
|
10454
10460
|
visuals: external_exports.array(VisualSchema).optional(),
|
|
10455
10461
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -13572,7 +13578,13 @@ var InboxItemSchema2 = external_exports.object({
|
|
|
13572
13578
|
* request Entry's author, as the same three facts the item's own `tokenId`/`name`/`voice`
|
|
13573
13579
|
* carry for the call's one agent, so the phone draws it with the same seed. Absent when the
|
|
13574
13580
|
* author is not an agent this account holds (unpaired since, or a person). */
|
|
13575
|
-
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional()
|
|
13581
|
+
agent: external_exports.object({ tokenId: external_exports.string(), name: external_exports.string(), voice: VoiceKeySchema2.optional() }).optional(),
|
|
13582
|
+
/** ITS OPTIONS, WHEN THERE IS SOMETHING TO SEE (owner, 2026-09-25: "Yes, add it"): the options
|
|
13583
|
+
* its need offers, exactly as its own card carries them, present only when one of them has a
|
|
13584
|
+
* preview (`html` or `image`). The call screen opens them from the agenda row, so a preview is
|
|
13585
|
+
* never re-sent as a second card to be seen mid-call. Words-only options are absent — the bot
|
|
13586
|
+
* says those, and the list stays small (an `html` is up to 16 KB). */
|
|
13587
|
+
options: external_exports.array(OptionSchema2).optional()
|
|
13576
13588
|
})).optional(),
|
|
13577
13589
|
visuals: external_exports.array(VisualSchema2).optional(),
|
|
13578
13590
|
/** The connected agent's name (the single pairing name — user-typed, or the
|
|
@@ -14173,6 +14185,7 @@ import { resolve as resolve2, delimiter as delimiter2 } from "path";
|
|
|
14173
14185
|
|
|
14174
14186
|
// src/harness/acp.ts
|
|
14175
14187
|
var none = { events: [], writes: [] };
|
|
14188
|
+
var NOTHING_TO_RESUME = /* @__PURE__ */ new Set([-32002, -32601]);
|
|
14176
14189
|
function optionFor(options, decision) {
|
|
14177
14190
|
const want = decision.allow ? "allow_once" : "reject_once";
|
|
14178
14191
|
return options.find((o) => o.kind === want)?.optionId ?? null;
|
|
@@ -14269,20 +14282,29 @@ var AcpDriver = class {
|
|
|
14269
14282
|
if (msg.id === this.initId) {
|
|
14270
14283
|
this.initId = void 0;
|
|
14271
14284
|
if (msg.error) {
|
|
14272
|
-
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}
|
|
14285
|
+
return { events: [{ kind: "error", message: `initialize failed: ${msg.error.message}`, fatal: true }], writes: [] };
|
|
14273
14286
|
}
|
|
14274
14287
|
this.sessionNewId = this.nextId++;
|
|
14275
14288
|
return {
|
|
14276
14289
|
events: [],
|
|
14277
|
-
writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } :
|
|
14290
|
+
writes: [frame2(this.resume ? { id: this.sessionNewId, method: "session/resume", params: { sessionId: this.resume, cwd: this.cwd, mcpServers: [] } } : this.sessionNew(this.sessionNewId))]
|
|
14278
14291
|
};
|
|
14279
14292
|
}
|
|
14280
14293
|
if (msg.id === this.sessionNewId) {
|
|
14281
14294
|
this.sessionNewId = void 0;
|
|
14295
|
+
const resuming = this.resume !== void 0;
|
|
14296
|
+
if (resuming && msg.error?.code !== void 0 && NOTHING_TO_RESUME.has(msg.error.code)) {
|
|
14297
|
+
this.resume = void 0;
|
|
14298
|
+
this.sessionNewId = this.nextId++;
|
|
14299
|
+
return {
|
|
14300
|
+
events: [{ kind: "error", message: `session/resume found nothing to resume (${msg.error.message}) \u2014 opening a new conversation` }],
|
|
14301
|
+
writes: [frame2(this.sessionNew(this.sessionNewId))]
|
|
14302
|
+
};
|
|
14303
|
+
}
|
|
14282
14304
|
const sessionId2 = msg.error ? void 0 : msg.result?.sessionId ?? this.resume;
|
|
14283
14305
|
if (!sessionId2) {
|
|
14284
14306
|
return {
|
|
14285
|
-
events: [{ kind: "error", message: `${
|
|
14307
|
+
events: [{ kind: "error", message: `${resuming ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}`, fatal: true }],
|
|
14286
14308
|
writes: []
|
|
14287
14309
|
};
|
|
14288
14310
|
}
|
|
@@ -14375,6 +14397,9 @@ var AcpDriver = class {
|
|
|
14375
14397
|
writes: []
|
|
14376
14398
|
};
|
|
14377
14399
|
}
|
|
14400
|
+
sessionNew(id) {
|
|
14401
|
+
return { id, method: "session/new", params: { cwd: this.cwd, mcpServers: [] } };
|
|
14402
|
+
}
|
|
14378
14403
|
/** Send the queued prompts as one turn, if the agent can take one right now. */
|
|
14379
14404
|
flush() {
|
|
14380
14405
|
if (!this.sessionId || this.promptId !== void 0 || !this.queued.length) return [];
|
|
@@ -14419,7 +14444,7 @@ function startSession(opts) {
|
|
|
14419
14444
|
...process.env,
|
|
14420
14445
|
PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter2),
|
|
14421
14446
|
...opts.token ? { PAIGY_TOKEN: opts.token } : {},
|
|
14422
|
-
...opts.resume ? { PAIGY_SESSION_ID: opts.resume } : {},
|
|
14447
|
+
...opts.resume ?? opts.session ? { PAIGY_SESSION_ID: opts.resume ?? opts.session } : {},
|
|
14423
14448
|
// A PUMP DRIVES THIS PROCESS (2026-09-22, #2265): every run `runHarness` starts has the
|
|
14424
14449
|
// bridge's pump typing answers in, so nothing inside it may start a second delivery path.
|
|
14425
14450
|
// Exported on EVERY spawn — `PAIGY_SESSION_ID` above is the conversation to resume, and a
|
|
@@ -14429,7 +14454,11 @@ function startSession(opts) {
|
|
|
14429
14454
|
},
|
|
14430
14455
|
stdio: ["pipe", "pipe", "pipe"]
|
|
14431
14456
|
});
|
|
14457
|
+
let ended2 = false;
|
|
14458
|
+
child.stdin?.on("error", () => {
|
|
14459
|
+
});
|
|
14432
14460
|
const write = (frames) => {
|
|
14461
|
+
if (ended2) return;
|
|
14433
14462
|
for (const f of frames) child.stdin?.write(`${f}
|
|
14434
14463
|
`);
|
|
14435
14464
|
};
|
|
@@ -14438,6 +14467,7 @@ function startSession(opts) {
|
|
|
14438
14467
|
if (text) opts.onEvent({ kind: "error", message: text });
|
|
14439
14468
|
});
|
|
14440
14469
|
child.on("exit", (code) => {
|
|
14470
|
+
ended2 = true;
|
|
14441
14471
|
opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
|
|
14442
14472
|
opts.onExit?.();
|
|
14443
14473
|
});
|
|
@@ -14466,6 +14496,7 @@ function startSession(opts) {
|
|
|
14466
14496
|
},
|
|
14467
14497
|
stop() {
|
|
14468
14498
|
write(driver.close());
|
|
14499
|
+
ended2 = true;
|
|
14469
14500
|
child.kill();
|
|
14470
14501
|
}
|
|
14471
14502
|
};
|
|
@@ -14708,6 +14739,23 @@ async function finish(result, state, rail, opts) {
|
|
|
14708
14739
|
opts?.log?.(`\u26A0 finish contact failed: ${error.message}`);
|
|
14709
14740
|
}
|
|
14710
14741
|
}
|
|
14742
|
+
async function failedToStart(reason, state, rail, opts) {
|
|
14743
|
+
try {
|
|
14744
|
+
const goalId = state.goal?.id ?? (await rail.claim()).goalId;
|
|
14745
|
+
state.goal = void 0;
|
|
14746
|
+
state.resting = true;
|
|
14747
|
+
if (!goalId) return;
|
|
14748
|
+
await rail.contact({
|
|
14749
|
+
asks: [{ ask: `The session could not start, so nothing is working on this yet.
|
|
14750
|
+
|
|
14751
|
+
${reason}`, parentId: goalId }],
|
|
14752
|
+
waiting: "none",
|
|
14753
|
+
channel: "notification"
|
|
14754
|
+
});
|
|
14755
|
+
} catch (error) {
|
|
14756
|
+
opts?.log?.(`\u26A0 could not report the failed start: ${error.message}`);
|
|
14757
|
+
}
|
|
14758
|
+
}
|
|
14711
14759
|
async function afterTurn(event, state, rail, opts) {
|
|
14712
14760
|
if (!event.failed && !endsWithQuestion(event.result)) {
|
|
14713
14761
|
await finish(event.result, state, rail, opts);
|
|
@@ -14732,10 +14780,16 @@ function runHarness(opts) {
|
|
|
14732
14780
|
let tail = [];
|
|
14733
14781
|
const state = { resting: true };
|
|
14734
14782
|
let session = null;
|
|
14735
|
-
const
|
|
14783
|
+
const speaksAs = opts.resume ?? opts.session;
|
|
14784
|
+
const rail = railFor({ token: opts.token, ...speaksAs ? { reach: reachAs(speaksAs) } : {} });
|
|
14736
14785
|
async function handle(event) {
|
|
14737
14786
|
if (event.kind === "error") {
|
|
14738
14787
|
opts.log(`\u26A0 ${event.message}`);
|
|
14788
|
+
if (event.fatal && running) {
|
|
14789
|
+
opts.log(`\u2716 could not start: ${event.message}`);
|
|
14790
|
+
await failedToStart(event.message, state, rail, { log: opts.log });
|
|
14791
|
+
stop2();
|
|
14792
|
+
}
|
|
14739
14793
|
return;
|
|
14740
14794
|
}
|
|
14741
14795
|
if (event.kind === "permission") {
|
|
@@ -14781,6 +14835,7 @@ function runHarness(opts) {
|
|
|
14781
14835
|
mode: opts.mode,
|
|
14782
14836
|
...opts.token ? { token: opts.token } : {},
|
|
14783
14837
|
...opts.resume ? { resume: opts.resume } : {},
|
|
14838
|
+
...opts.session ? { session: opts.session } : {},
|
|
14784
14839
|
...opts.bin ? { bin: opts.bin } : {},
|
|
14785
14840
|
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
14786
14841
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
@@ -14789,20 +14844,21 @@ function runHarness(opts) {
|
|
|
14789
14844
|
(e) => opts.log(`\u26A0 conversation loop stopped: ${e.message}`)
|
|
14790
14845
|
);
|
|
14791
14846
|
opts.log(`\u25B6 started ${opts.harness} in ${opts.cwd} (${opts.mode} mode)`);
|
|
14847
|
+
function stop2() {
|
|
14848
|
+
running = false;
|
|
14849
|
+
tail = [];
|
|
14850
|
+
cancelAsks(state);
|
|
14851
|
+
session?.stop();
|
|
14852
|
+
session = null;
|
|
14853
|
+
opts.log("\u25A0 stopped");
|
|
14854
|
+
}
|
|
14792
14855
|
return {
|
|
14793
14856
|
send(text) {
|
|
14794
14857
|
session?.send(text);
|
|
14795
14858
|
},
|
|
14796
14859
|
working: () => running && state.resting !== true,
|
|
14797
14860
|
tail: () => [...tail],
|
|
14798
|
-
stop
|
|
14799
|
-
running = false;
|
|
14800
|
-
tail = [];
|
|
14801
|
-
cancelAsks(state);
|
|
14802
|
-
session?.stop();
|
|
14803
|
-
session = null;
|
|
14804
|
-
opts.log("\u25A0 stopped");
|
|
14805
|
-
}
|
|
14861
|
+
stop: stop2
|
|
14806
14862
|
};
|
|
14807
14863
|
}
|
|
14808
14864
|
|
|
@@ -14911,6 +14967,7 @@ var claimedRun = (sessionId2) => {
|
|
|
14911
14967
|
const slot = sessionSlot(sessionId2);
|
|
14912
14968
|
return { slot, key: runKey(slot) };
|
|
14913
14969
|
};
|
|
14970
|
+
var launchRun = (spec) => ({ session: spec.sessionId });
|
|
14914
14971
|
async function slotHasWaitingWork(token, session) {
|
|
14915
14972
|
return (await checkReplies({ token, ...session ? { reach: reachAs(session) } : {} })).deliveries.length > 0;
|
|
14916
14973
|
}
|
|
@@ -14969,7 +15026,7 @@ function startHost(opts) {
|
|
|
14969
15026
|
mode: "bypass",
|
|
14970
15027
|
prompt: "",
|
|
14971
15028
|
token: spec.token,
|
|
14972
|
-
|
|
15029
|
+
...launchRun(spec),
|
|
14973
15030
|
onExit: () => {
|
|
14974
15031
|
runs.delete(key);
|
|
14975
15032
|
opts.log(`\u2716 ${label} ended`);
|