@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.
Files changed (3) hide show
  1. package/dist/cli.js +78 -17
  2. package/dist/main.js +73 -16
  3. 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}` }], writes: [] };
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: [] } } : { id: this.sessionNewId, method: "session/new", params: { 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: `${this.resume ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}` }],
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 rail = railFor({ token: opts.token, ...opts.resume ? { reach: reachAs(opts.resume) } : {} });
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
- resume: spec.sessionId,
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}` }], writes: [] };
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: [] } } : { id: this.sessionNewId, method: "session/new", params: { 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: `${this.resume ? "session/resume" : "session/new"} failed: ${msg.error?.message ?? "no sessionId"}` }],
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 rail = railFor({ token: opts.token, ...opts.resume ? { reach: reachAs(opts.resume) } : {} });
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
- resume: spec.sessionId,
15029
+ ...launchRun(spec),
14973
15030
  onExit: () => {
14974
15031
  runs.delete(key);
14975
15032
  opts.log(`\u2716 ${label} ended`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paigy/harness",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "Run Claude Code / Codex on this machine, bridged to Paigy — launchable from your phone.",
5
5
  "license": "MIT",
6
6
  "type": "module",