@paigy/harness 0.1.8 → 0.2.2
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 +64 -10
- package/dist/main.js +63 -9
- package/package.json +13 -13
package/dist/cli.js
CHANGED
|
@@ -30665,6 +30665,9 @@ function readToken(agent2 = AGENT_NAME) {
|
|
|
30665
30665
|
if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
|
|
30666
30666
|
return readTokenFile()[agent2]?.access_token ?? "";
|
|
30667
30667
|
}
|
|
30668
|
+
function listSlots() {
|
|
30669
|
+
return Object.keys(readTokenFile());
|
|
30670
|
+
}
|
|
30668
30671
|
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
30669
30672
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
30670
30673
|
method: "POST",
|
|
@@ -31740,7 +31743,8 @@ async function drainInput(state, deps = {}) {
|
|
|
31740
31743
|
const claimed = [];
|
|
31741
31744
|
for (const item of mine) {
|
|
31742
31745
|
try {
|
|
31743
|
-
await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
31746
|
+
const ack = await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
31747
|
+
if (ack?.won === false) continue;
|
|
31744
31748
|
claimed.push(item);
|
|
31745
31749
|
} catch {
|
|
31746
31750
|
}
|
|
@@ -31963,7 +31967,7 @@ var clip = (s) => (s.length > 120 ? `${s.slice(0, 117)}\u2026` : s) || "(no text
|
|
|
31963
31967
|
import { spawn } from "child_process";
|
|
31964
31968
|
import { existsSync as existsSync3 } from "fs";
|
|
31965
31969
|
import { homedir as homedir3 } from "os";
|
|
31966
|
-
import { resolve, delimiter } from "path";
|
|
31970
|
+
import { dirname, resolve, delimiter } from "path";
|
|
31967
31971
|
|
|
31968
31972
|
// src/harness/acp.ts
|
|
31969
31973
|
var none = { events: [], writes: [] };
|
|
@@ -32190,9 +32194,15 @@ function startSession(opts) {
|
|
|
32190
32194
|
}
|
|
32191
32195
|
const child = (opts.spawnFn ?? spawn)(opts.bin ?? ADAPTER_BIN[opts.harness], [], {
|
|
32192
32196
|
cwd,
|
|
32193
|
-
// The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI-
|
|
32194
|
-
//
|
|
32195
|
-
|
|
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
|
+
},
|
|
32196
32206
|
stdio: ["pipe", "pipe", "pipe"]
|
|
32197
32207
|
});
|
|
32198
32208
|
const write = (frames) => {
|
|
@@ -32205,8 +32215,12 @@ function startSession(opts) {
|
|
|
32205
32215
|
});
|
|
32206
32216
|
child.on("exit", (code) => {
|
|
32207
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?.();
|
|
32208
32223
|
});
|
|
32209
|
-
child.on("error", (e) => opts.onEvent({ kind: "error", message: e.message }));
|
|
32210
32224
|
const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.mcp ? { mcp: opts.mcp } : {} });
|
|
32211
32225
|
let buffer = "";
|
|
32212
32226
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -32289,7 +32303,9 @@ function runHarness(opts) {
|
|
|
32289
32303
|
cwd: opts.cwd,
|
|
32290
32304
|
mode: opts.mode,
|
|
32291
32305
|
...paigyMcp ? { mcp: paigyMcp } : {},
|
|
32306
|
+
...opts.token ? { token: opts.token } : {},
|
|
32292
32307
|
...opts.bin ? { bin: opts.bin } : {},
|
|
32308
|
+
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
32293
32309
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
32294
32310
|
});
|
|
32295
32311
|
if (opts.prompt) session.send(opts.prompt);
|
|
@@ -32313,7 +32329,7 @@ function runHarness(opts) {
|
|
|
32313
32329
|
|
|
32314
32330
|
// src/workspaces.ts
|
|
32315
32331
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
32316
|
-
import { dirname, join as join3, resolve as resolve2 } from "path";
|
|
32332
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
32317
32333
|
import { homedir as homedir4 } from "os";
|
|
32318
32334
|
function workspacesFile() {
|
|
32319
32335
|
return join3(homedir4(), ".paigy", "workspaces.json");
|
|
@@ -32331,7 +32347,7 @@ function addWorkspace(dir, deps) {
|
|
|
32331
32347
|
const all = listWorkspaces(deps);
|
|
32332
32348
|
if (!all.includes(next)) {
|
|
32333
32349
|
all.push(next);
|
|
32334
|
-
mkdirSync2(
|
|
32350
|
+
mkdirSync2(dirname2(deps.file), { recursive: true });
|
|
32335
32351
|
writeFileSync2(deps.file, JSON.stringify(all, null, 2));
|
|
32336
32352
|
}
|
|
32337
32353
|
return all;
|
|
@@ -32360,15 +32376,53 @@ function startHost(opts) {
|
|
|
32360
32376
|
prompt: spec.prompt ?? "",
|
|
32361
32377
|
exclusive: true,
|
|
32362
32378
|
token: spec.token,
|
|
32379
|
+
onExit: () => {
|
|
32380
|
+
runs.delete(spec.sessionId);
|
|
32381
|
+
opts.log(`\u2716 ${label} ended`);
|
|
32382
|
+
},
|
|
32363
32383
|
log
|
|
32364
32384
|
});
|
|
32365
32385
|
runs.set(spec.sessionId, { run, label });
|
|
32366
32386
|
opts.log(`\u25B6 phone-launched ${label} (${spec.harness}) in ${spec.workspace}`);
|
|
32367
32387
|
}
|
|
32368
32388
|
}
|
|
32389
|
+
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
|
|
32390
|
+
async function sweepSlots() {
|
|
32391
|
+
const workspace = listWorkspaces(opts.wsDeps)[0];
|
|
32392
|
+
if (!workspace) return;
|
|
32393
|
+
for (const slot of listSlots()) {
|
|
32394
|
+
const harness = SLOT_HARNESS[slot];
|
|
32395
|
+
const key = `slot:${slot}`;
|
|
32396
|
+
if (!harness || runs.has(key)) continue;
|
|
32397
|
+
const token = readToken(slot);
|
|
32398
|
+
if (!token) continue;
|
|
32399
|
+
const work = await checkReplies({ token }).catch(() => null);
|
|
32400
|
+
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
32401
|
+
const log = (line) => opts.log(`[${slot}] ${line}`);
|
|
32402
|
+
const run = runHarness({
|
|
32403
|
+
harness,
|
|
32404
|
+
cwd: workspace,
|
|
32405
|
+
mode: "bypass",
|
|
32406
|
+
prompt: "",
|
|
32407
|
+
exclusive: true,
|
|
32408
|
+
token,
|
|
32409
|
+
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
32410
|
+
onExit: () => {
|
|
32411
|
+
runs.delete(key);
|
|
32412
|
+
opts.log(`\u2716 ${slot} ended`);
|
|
32413
|
+
},
|
|
32414
|
+
log
|
|
32415
|
+
});
|
|
32416
|
+
runs.set(key, { run, label: slot });
|
|
32417
|
+
opts.log(`\u25B6 woke ${slot} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
32418
|
+
}
|
|
32419
|
+
}
|
|
32369
32420
|
beat();
|
|
32370
32421
|
const pulse = setInterval(beat, 6e4);
|
|
32371
|
-
const spawnPoll = setInterval(() =>
|
|
32422
|
+
const spawnPoll = setInterval(() => {
|
|
32423
|
+
void claimAndSpawn();
|
|
32424
|
+
void sweepSlots();
|
|
32425
|
+
}, 5e3);
|
|
32372
32426
|
const stopSessions = () => {
|
|
32373
32427
|
for (const { run, label } of runs.values()) {
|
|
32374
32428
|
run.stop();
|
|
@@ -32512,7 +32566,7 @@ async function main() {
|
|
|
32512
32566
|
const TERMINAL_AGENTS = [
|
|
32513
32567
|
// Wire = MCP registration + the plugin (skills, hooks, idle-escalation — the
|
|
32514
32568
|
// piece that makes a LIVE session notice your requests without polling).
|
|
32515
|
-
{ cli: "claude", name: "Claude Code", slot: "mcp-agent", wire: "claude mcp add --scope user paigy -- npx -y @paigy/mcp@latest && claude plugin marketplace add paigy-ai/mcp; claude plugin install paigy" },
|
|
32569
|
+
{ cli: "claude", name: "Claude Code", slot: "mcp-agent", wire: "claude mcp add --scope user paigy -- npx -y @paigy/mcp@latest && claude plugin marketplace add paigy-ai/mcp; claude plugin install paigy@paigy" },
|
|
32516
32570
|
{ cli: "codex", name: "Codex", slot: "codex", wire: "codex mcp add paigy -- npx -y @paigy/mcp@latest" },
|
|
32517
32571
|
{ cli: "antigravity", name: "Antigravity", slot: "antigravity" }
|
|
32518
32572
|
];
|
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
|
|
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";
|
|
@@ -12087,6 +12087,9 @@ function readToken(agent2 = AGENT_NAME) {
|
|
|
12087
12087
|
if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
|
|
12088
12088
|
return readTokenFile()[agent2]?.access_token ?? "";
|
|
12089
12089
|
}
|
|
12090
|
+
function listSlots() {
|
|
12091
|
+
return Object.keys(readTokenFile());
|
|
12092
|
+
}
|
|
12090
12093
|
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
12091
12094
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
12092
12095
|
method: "POST",
|
|
@@ -12377,7 +12380,7 @@ function removeWorkspace(dir, deps) {
|
|
|
12377
12380
|
import { spawn } from "child_process";
|
|
12378
12381
|
import { existsSync as existsSync4 } from "fs";
|
|
12379
12382
|
import { homedir as homedir4 } from "os";
|
|
12380
|
-
import { resolve as resolve2, delimiter } from "path";
|
|
12383
|
+
import { dirname as dirname2, resolve as resolve2, delimiter } from "path";
|
|
12381
12384
|
|
|
12382
12385
|
// src/harness/acp.ts
|
|
12383
12386
|
var none = { events: [], writes: [] };
|
|
@@ -12604,9 +12607,15 @@ function startSession(opts) {
|
|
|
12604
12607
|
}
|
|
12605
12608
|
const child = (opts.spawnFn ?? spawn)(opts.bin ?? ADAPTER_BIN[opts.harness], [], {
|
|
12606
12609
|
cwd,
|
|
12607
|
-
// The adapter shells out to its vendor CLI (`claude`, `codex`), and a GUI-
|
|
12608
|
-
//
|
|
12609
|
-
|
|
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
|
+
},
|
|
12610
12619
|
stdio: ["pipe", "pipe", "pipe"]
|
|
12611
12620
|
});
|
|
12612
12621
|
const write = (frames) => {
|
|
@@ -12619,8 +12628,12 @@ function startSession(opts) {
|
|
|
12619
12628
|
});
|
|
12620
12629
|
child.on("exit", (code) => {
|
|
12621
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?.();
|
|
12622
12636
|
});
|
|
12623
|
-
child.on("error", (e) => opts.onEvent({ kind: "error", message: e.message }));
|
|
12624
12637
|
const driver = createAcpDriver({ cwd, mode: opts.mode, ...opts.mcp ? { mcp: opts.mcp } : {} });
|
|
12625
12638
|
let buffer = "";
|
|
12626
12639
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -13522,7 +13535,8 @@ async function drainInput(state, deps = {}) {
|
|
|
13522
13535
|
const claimed = [];
|
|
13523
13536
|
for (const item of mine) {
|
|
13524
13537
|
try {
|
|
13525
|
-
await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
13538
|
+
const ack = await (deps.ack ?? setTaskState)(item.notificationId, "in_progress");
|
|
13539
|
+
if (ack?.won === false) continue;
|
|
13526
13540
|
claimed.push(item);
|
|
13527
13541
|
} catch {
|
|
13528
13542
|
}
|
|
@@ -13786,7 +13800,9 @@ function runHarness(opts) {
|
|
|
13786
13800
|
cwd: opts.cwd,
|
|
13787
13801
|
mode: opts.mode,
|
|
13788
13802
|
...paigyMcp ? { mcp: paigyMcp } : {},
|
|
13803
|
+
...opts.token ? { token: opts.token } : {},
|
|
13789
13804
|
...opts.bin ? { bin: opts.bin } : {},
|
|
13805
|
+
...opts.onExit ? { onExit: opts.onExit } : {},
|
|
13790
13806
|
onEvent: (event) => void handle(event).catch((err) => opts.log(`\u26A0 ${err.message}`))
|
|
13791
13807
|
});
|
|
13792
13808
|
if (opts.prompt) session.send(opts.prompt);
|
|
@@ -13831,15 +13847,53 @@ function startHost(opts) {
|
|
|
13831
13847
|
prompt: spec.prompt ?? "",
|
|
13832
13848
|
exclusive: true,
|
|
13833
13849
|
token: spec.token,
|
|
13850
|
+
onExit: () => {
|
|
13851
|
+
runs.delete(spec.sessionId);
|
|
13852
|
+
opts.log(`\u2716 ${label} ended`);
|
|
13853
|
+
},
|
|
13834
13854
|
log: log2
|
|
13835
13855
|
});
|
|
13836
13856
|
runs.set(spec.sessionId, { run, label });
|
|
13837
13857
|
opts.log(`\u25B6 phone-launched ${label} (${spec.harness}) in ${spec.workspace}`);
|
|
13838
13858
|
}
|
|
13839
13859
|
}
|
|
13860
|
+
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
|
|
13861
|
+
async function sweepSlots() {
|
|
13862
|
+
const workspace = listWorkspaces(opts.wsDeps)[0];
|
|
13863
|
+
if (!workspace) return;
|
|
13864
|
+
for (const slot of listSlots()) {
|
|
13865
|
+
const harness = SLOT_HARNESS[slot];
|
|
13866
|
+
const key = `slot:${slot}`;
|
|
13867
|
+
if (!harness || runs.has(key)) continue;
|
|
13868
|
+
const token = readToken(slot);
|
|
13869
|
+
if (!token) continue;
|
|
13870
|
+
const work = await checkReplies({ token }).catch(() => null);
|
|
13871
|
+
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
13872
|
+
const log2 = (line) => opts.log(`[${slot}] ${line}`);
|
|
13873
|
+
const run = runHarness({
|
|
13874
|
+
harness,
|
|
13875
|
+
cwd: workspace,
|
|
13876
|
+
mode: "bypass",
|
|
13877
|
+
prompt: "",
|
|
13878
|
+
exclusive: true,
|
|
13879
|
+
token,
|
|
13880
|
+
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
13881
|
+
onExit: () => {
|
|
13882
|
+
runs.delete(key);
|
|
13883
|
+
opts.log(`\u2716 ${slot} ended`);
|
|
13884
|
+
},
|
|
13885
|
+
log: log2
|
|
13886
|
+
});
|
|
13887
|
+
runs.set(key, { run, label: slot });
|
|
13888
|
+
opts.log(`\u25B6 woke ${slot} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
13889
|
+
}
|
|
13890
|
+
}
|
|
13840
13891
|
beat();
|
|
13841
13892
|
const pulse = setInterval(beat, 6e4);
|
|
13842
|
-
const spawnPoll = setInterval(() =>
|
|
13893
|
+
const spawnPoll = setInterval(() => {
|
|
13894
|
+
void claimAndSpawn();
|
|
13895
|
+
void sweepSlots();
|
|
13896
|
+
}, 5e3);
|
|
13843
13897
|
const stopSessions = () => {
|
|
13844
13898
|
for (const { run, label } of runs.values()) {
|
|
13845
13899
|
run.stop();
|
|
@@ -13859,7 +13913,7 @@ function startHost(opts) {
|
|
|
13859
13913
|
}
|
|
13860
13914
|
|
|
13861
13915
|
// src/main.ts
|
|
13862
|
-
var here =
|
|
13916
|
+
var here = dirname3(fileURLToPath(import.meta.url));
|
|
13863
13917
|
var wsDeps = { file: workspacesFile() };
|
|
13864
13918
|
var DEVICE_SLOT = "Desktop";
|
|
13865
13919
|
var deviceToken = () => readToken(DEVICE_SLOT) || readToken();
|
package/package.json
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paigy/harness",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Run Claude Code / Codex on this machine, bridged to Paigy
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Run Claude Code / Codex on this machine, bridged to Paigy \u2014 launchable from your phone.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/main.js",
|
|
8
8
|
"bin": {
|
|
9
9
|
"paigy-harness": "dist/cli.js"
|
|
10
10
|
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "pnpm build && electron .",
|
|
14
|
+
"start": "electron .",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
11
18
|
"dependencies": {},
|
|
12
19
|
"devDependencies": {
|
|
13
20
|
"@types/node": "^22.0.0",
|
|
@@ -16,10 +23,10 @@
|
|
|
16
23
|
"tsup": "^8.3.5",
|
|
17
24
|
"typescript": "^5.7.2",
|
|
18
25
|
"vitest": "^2.1.8",
|
|
26
|
+
"@paigy/schema": "workspace:*",
|
|
27
|
+
"@paigy/sdk": "workspace:*",
|
|
19
28
|
"qrcode": "^1.5.4",
|
|
20
|
-
"zod": "^3.24.1"
|
|
21
|
-
"@paigy/sdk": "0.1.0",
|
|
22
|
-
"@paigy/schema": "0.0.0"
|
|
29
|
+
"zod": "^3.24.1"
|
|
23
30
|
},
|
|
24
31
|
"files": [
|
|
25
32
|
"dist/cli.js",
|
|
@@ -27,12 +34,5 @@
|
|
|
27
34
|
],
|
|
28
35
|
"publishConfig": {
|
|
29
36
|
"access": "public"
|
|
30
|
-
},
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "tsup",
|
|
33
|
-
"dev": "pnpm build && electron .",
|
|
34
|
-
"start": "electron .",
|
|
35
|
-
"typecheck": "tsc --noEmit",
|
|
36
|
-
"test": "vitest run"
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|