@paigy/harness 0.2.0 → 0.2.4

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 +40 -10
  2. package/dist/main.js +40 -10
  3. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -30804,7 +30804,7 @@ function landIntents(intents) {
30804
30804
  });
30805
30805
  }
30806
30806
  async function checkReplies(opts = {}) {
30807
- const token = readToken();
30807
+ const token = authToken(opts.token);
30808
30808
  const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
30809
30809
  headers: { authorization: `Bearer ${token}` }
30810
30810
  }));
@@ -31967,7 +31967,7 @@ var clip = (s) => (s.length > 120 ? `${s.slice(0, 117)}\u2026` : s) || "(no text
31967
31967
  import { spawn } from "child_process";
31968
31968
  import { existsSync as existsSync3 } from "fs";
31969
31969
  import { homedir as homedir3 } from "os";
31970
- import { resolve, delimiter } from "path";
31970
+ import { dirname, resolve, delimiter } from "path";
31971
31971
 
31972
31972
  // src/harness/acp.ts
31973
31973
  var none = { events: [], writes: [] };
@@ -32194,9 +32194,15 @@ function startSession(opts) {
32194
32194
  }
32195
32195
  const child = (opts.spawnFn ?? spawn)(opts.bin ?? ADAPTER_BIN[opts.harness], [], {
32196
32196
  cwd,
32197
- // The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI-launched
32198
- // app's PATH won't have it — same reason detection probes these dirs.
32199
- env: { ...process.env, PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter) },
32197
+ // The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI- or
32198
+ // launchd-launched process's PATH won't have it — probe dirs plus the RUNNING
32199
+ // node's own bin dir (nvm installs the adapters next to node; launchd's bare
32200
+ // PATH knows neither — live catch 2026-08-04: the first slot-wake ENOENT'd).
32201
+ env: {
32202
+ ...process.env,
32203
+ PATH: [process.env.PATH, dirname(process.execPath), ...probeDirs()].filter(Boolean).join(delimiter),
32204
+ ...opts.token ? { PAIGY_TOKEN: opts.token } : {}
32205
+ },
32200
32206
  stdio: ["pipe", "pipe", "pipe"]
32201
32207
  });
32202
32208
  const write = (frames) => {
@@ -32209,8 +32215,12 @@ function startSession(opts) {
32209
32215
  });
32210
32216
  child.on("exit", (code) => {
32211
32217
  opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
32218
+ opts.onExit?.();
32219
+ });
32220
+ child.on("error", (e) => {
32221
+ opts.onEvent({ kind: "error", message: e.message });
32222
+ opts.onExit?.();
32212
32223
  });
32213
- child.on("error", (e) => opts.onEvent({ kind: "error", message: e.message }));
32214
32224
  const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.mcp ? { mcp: opts.mcp } : {} });
32215
32225
  let buffer = "";
32216
32226
  child.stdout?.on("data", (chunk) => {
@@ -32293,7 +32303,9 @@ function runHarness(opts) {
32293
32303
  cwd: opts.cwd,
32294
32304
  mode: opts.mode,
32295
32305
  ...paigyMcp ? { mcp: paigyMcp } : {},
32306
+ ...opts.token ? { token: opts.token } : {},
32296
32307
  ...opts.bin ? { bin: opts.bin } : {},
32308
+ ...opts.onExit ? { onExit: opts.onExit } : {},
32297
32309
  onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
32298
32310
  });
32299
32311
  if (opts.prompt) session.send(opts.prompt);
@@ -32317,7 +32329,7 @@ function runHarness(opts) {
32317
32329
 
32318
32330
  // src/workspaces.ts
32319
32331
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
32320
- import { dirname, join as join3, resolve as resolve2 } from "path";
32332
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
32321
32333
  import { homedir as homedir4 } from "os";
32322
32334
  function workspacesFile() {
32323
32335
  return join3(homedir4(), ".paigy", "workspaces.json");
@@ -32335,7 +32347,7 @@ function addWorkspace(dir, deps) {
32335
32347
  const all = listWorkspaces(deps);
32336
32348
  if (!all.includes(next)) {
32337
32349
  all.push(next);
32338
- mkdirSync2(dirname(deps.file), { recursive: true });
32350
+ mkdirSync2(dirname2(deps.file), { recursive: true });
32339
32351
  writeFileSync2(deps.file, JSON.stringify(all, null, 2));
32340
32352
  }
32341
32353
  return all;
@@ -32364,6 +32376,10 @@ function startHost(opts) {
32364
32376
  prompt: spec.prompt ?? "",
32365
32377
  exclusive: true,
32366
32378
  token: spec.token,
32379
+ onExit: () => {
32380
+ runs.delete(spec.sessionId);
32381
+ opts.log(`\u2716 ${label} ended`);
32382
+ },
32367
32383
  log
32368
32384
  });
32369
32385
  runs.set(spec.sessionId, { run, label });
@@ -32371,11 +32387,12 @@ function startHost(opts) {
32371
32387
  }
32372
32388
  }
32373
32389
  const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
32390
+ const WAKE_PROMPT = "You were woken because Paigy work is waiting for you. Call check_replies now and handle everything it returns, then follow your paigy instructions to stay in the loop.";
32374
32391
  async function sweepSlots() {
32375
32392
  const workspace = listWorkspaces(opts.wsDeps)[0];
32376
32393
  if (!workspace) return;
32377
32394
  for (const slot of listSlots()) {
32378
- const harness = SLOT_HARNESS[slot];
32395
+ const harness = SLOT_HARNESS[slot] ?? (slot === "Desktop" ? void 0 : "claude");
32379
32396
  const key = `slot:${slot}`;
32380
32397
  if (!harness || runs.has(key)) continue;
32381
32398
  const token = readToken(slot);
@@ -32383,7 +32400,20 @@ function startHost(opts) {
32383
32400
  const work = await checkReplies({ token }).catch(() => null);
32384
32401
  if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
32385
32402
  const log = (line) => opts.log(`[${slot}] ${line}`);
32386
- const run = runHarness({ harness, cwd: workspace, mode: "bypass", prompt: "", exclusive: true, token, log });
32403
+ const run = runHarness({
32404
+ harness,
32405
+ cwd: workspace,
32406
+ mode: "bypass",
32407
+ prompt: WAKE_PROMPT,
32408
+ exclusive: true,
32409
+ token,
32410
+ // A dead run must not squat the slot — evict so the next wake can respawn.
32411
+ onExit: () => {
32412
+ runs.delete(key);
32413
+ opts.log(`\u2716 ${slot} ended`);
32414
+ },
32415
+ log
32416
+ });
32387
32417
  runs.set(key, { run, label: slot });
32388
32418
  opts.log(`\u25B6 woke ${slot} (${harness}) \u2014 work was waiting in ${workspace}`);
32389
32419
  }
package/dist/main.js CHANGED
@@ -4577,7 +4577,7 @@ var require_lib = __commonJS({
4577
4577
  import { app, BrowserWindow, dialog, ipcMain } from "electron";
4578
4578
  import { spawn as spawn2 } from "child_process";
4579
4579
  import { fileURLToPath } from "url";
4580
- import { dirname as dirname2, join as join4 } from "path";
4580
+ import { dirname as dirname3, join as join4 } from "path";
4581
4581
 
4582
4582
  // ../../packages/sdk/dist/index.js
4583
4583
  import { createRequire as __sdkCreateRequire } from "module";
@@ -12226,7 +12226,7 @@ function landIntents(intents) {
12226
12226
  });
12227
12227
  }
12228
12228
  async function checkReplies(opts = {}) {
12229
- const token = readToken();
12229
+ const token = authToken(opts.token);
12230
12230
  const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
12231
12231
  headers: { authorization: `Bearer ${token}` }
12232
12232
  }));
@@ -12380,7 +12380,7 @@ function removeWorkspace(dir, deps) {
12380
12380
  import { spawn } from "child_process";
12381
12381
  import { existsSync as existsSync4 } from "fs";
12382
12382
  import { homedir as homedir4 } from "os";
12383
- import { resolve as resolve2, delimiter } from "path";
12383
+ import { dirname as dirname2, resolve as resolve2, delimiter } from "path";
12384
12384
 
12385
12385
  // src/harness/acp.ts
12386
12386
  var none = { events: [], writes: [] };
@@ -12607,9 +12607,15 @@ function startSession(opts) {
12607
12607
  }
12608
12608
  const child = (opts.spawnFn ?? spawn)(opts.bin ?? ADAPTER_BIN[opts.harness], [], {
12609
12609
  cwd,
12610
- // The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI-launched
12611
- // app's PATH won't have it — same reason detection probes these dirs.
12612
- env: { ...process.env, PATH: [process.env.PATH, ...probeDirs()].filter(Boolean).join(delimiter) },
12610
+ // The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI- or
12611
+ // launchd-launched process's PATH won't have it — probe dirs plus the RUNNING
12612
+ // node's own bin dir (nvm installs the adapters next to node; launchd's bare
12613
+ // PATH knows neither — live catch 2026-08-04: the first slot-wake ENOENT'd).
12614
+ env: {
12615
+ ...process.env,
12616
+ PATH: [process.env.PATH, dirname2(process.execPath), ...probeDirs()].filter(Boolean).join(delimiter),
12617
+ ...opts.token ? { PAIGY_TOKEN: opts.token } : {}
12618
+ },
12613
12619
  stdio: ["pipe", "pipe", "pipe"]
12614
12620
  });
12615
12621
  const write = (frames) => {
@@ -12622,8 +12628,12 @@ function startSession(opts) {
12622
12628
  });
12623
12629
  child.on("exit", (code) => {
12624
12630
  opts.onEvent({ kind: "idle", failed: code !== 0, ...code ? { result: `exited ${code}` } : {} });
12631
+ opts.onExit?.();
12632
+ });
12633
+ child.on("error", (e) => {
12634
+ opts.onEvent({ kind: "error", message: e.message });
12635
+ opts.onExit?.();
12625
12636
  });
12626
- child.on("error", (e) => opts.onEvent({ kind: "error", message: e.message }));
12627
12637
  const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.mcp ? { mcp: opts.mcp } : {} });
12628
12638
  let buffer = "";
12629
12639
  child.stdout?.on("data", (chunk) => {
@@ -13790,7 +13800,9 @@ function runHarness(opts) {
13790
13800
  cwd: opts.cwd,
13791
13801
  mode: opts.mode,
13792
13802
  ...paigyMcp ? { mcp: paigyMcp } : {},
13803
+ ...opts.token ? { token: opts.token } : {},
13793
13804
  ...opts.bin ? { bin: opts.bin } : {},
13805
+ ...opts.onExit ? { onExit: opts.onExit } : {},
13794
13806
  onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
13795
13807
  });
13796
13808
  if (opts.prompt) session.send(opts.prompt);
@@ -13835,6 +13847,10 @@ function startHost(opts) {
13835
13847
  prompt: spec.prompt ?? "",
13836
13848
  exclusive: true,
13837
13849
  token: spec.token,
13850
+ onExit: () => {
13851
+ runs.delete(spec.sessionId);
13852
+ opts.log(`\u2716 ${label} ended`);
13853
+ },
13838
13854
  log: log2
13839
13855
  });
13840
13856
  runs.set(spec.sessionId, { run, label });
@@ -13842,11 +13858,12 @@ function startHost(opts) {
13842
13858
  }
13843
13859
  }
13844
13860
  const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
13861
+ const WAKE_PROMPT = "You were woken because Paigy work is waiting for you. Call check_replies now and handle everything it returns, then follow your paigy instructions to stay in the loop.";
13845
13862
  async function sweepSlots() {
13846
13863
  const workspace = listWorkspaces(opts.wsDeps)[0];
13847
13864
  if (!workspace) return;
13848
13865
  for (const slot of listSlots()) {
13849
- const harness = SLOT_HARNESS[slot];
13866
+ const harness = SLOT_HARNESS[slot] ?? (slot === "Desktop" ? void 0 : "claude");
13850
13867
  const key = `slot:${slot}`;
13851
13868
  if (!harness || runs.has(key)) continue;
13852
13869
  const token = readToken(slot);
@@ -13854,7 +13871,20 @@ function startHost(opts) {
13854
13871
  const work = await checkReplies({ token }).catch(() => null);
13855
13872
  if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
13856
13873
  const log2 = (line) => opts.log(`[${slot}] ${line}`);
13857
- const run = runHarness({ harness, cwd: workspace, mode: "bypass", prompt: "", exclusive: true, token, log: log2 });
13874
+ const run = runHarness({
13875
+ harness,
13876
+ cwd: workspace,
13877
+ mode: "bypass",
13878
+ prompt: WAKE_PROMPT,
13879
+ exclusive: true,
13880
+ token,
13881
+ // A dead run must not squat the slot — evict so the next wake can respawn.
13882
+ onExit: () => {
13883
+ runs.delete(key);
13884
+ opts.log(`\u2716 ${slot} ended`);
13885
+ },
13886
+ log: log2
13887
+ });
13858
13888
  runs.set(key, { run, label: slot });
13859
13889
  opts.log(`\u25B6 woke ${slot} (${harness}) \u2014 work was waiting in ${workspace}`);
13860
13890
  }
@@ -13884,7 +13914,7 @@ function startHost(opts) {
13884
13914
  }
13885
13915
 
13886
13916
  // src/main.ts
13887
- var here = dirname2(fileURLToPath(import.meta.url));
13917
+ var here = dirname3(fileURLToPath(import.meta.url));
13888
13918
  var wsDeps = { file: workspacesFile() };
13889
13919
  var DEVICE_SLOT = "Desktop";
13890
13920
  var deviceToken = () => readToken(DEVICE_SLOT) || readToken();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paigy/harness",
3
- "version": "0.2.0",
3
+ "version": "0.2.4",
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",
@@ -18,8 +18,8 @@
18
18
  "vitest": "^2.1.8",
19
19
  "qrcode": "^1.5.4",
20
20
  "zod": "^3.24.1",
21
- "@paigy/sdk": "0.1.0",
22
- "@paigy/schema": "0.0.0"
21
+ "@paigy/schema": "0.0.0",
22
+ "@paigy/sdk": "0.1.0"
23
23
  },
24
24
  "files": [
25
25
  "dist/cli.js",