@paigy/harness 0.2.8 → 0.2.12
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 +75 -18
- package/dist/main.js +95 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -30693,6 +30693,9 @@ function readToken(agent2 = AGENT_NAME) {
|
|
|
30693
30693
|
function listSlots() {
|
|
30694
30694
|
return Object.keys(readTokenFile());
|
|
30695
30695
|
}
|
|
30696
|
+
function slotName(agent2) {
|
|
30697
|
+
return readTokenFile()[agent2]?.name ?? null;
|
|
30698
|
+
}
|
|
30696
30699
|
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
30697
30700
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
30698
30701
|
method: "POST",
|
|
@@ -30859,6 +30862,16 @@ async function hatch(name, voice = null) {
|
|
|
30859
30862
|
if (!res.ok) throw new Error(`hatch failed: ${res.status} ${await res.text()}`);
|
|
30860
30863
|
return await res.json();
|
|
30861
30864
|
}
|
|
30865
|
+
async function whoAmI(opts = {}) {
|
|
30866
|
+
try {
|
|
30867
|
+
const res = await reach(`${BACKEND_URL}/api/identity`, {
|
|
30868
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
30869
|
+
});
|
|
30870
|
+
return res.ok ? await res.json() : null;
|
|
30871
|
+
} catch {
|
|
30872
|
+
return null;
|
|
30873
|
+
}
|
|
30874
|
+
}
|
|
30862
30875
|
async function claimSessions(opts = {}) {
|
|
30863
30876
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/sessions/claim`, {
|
|
30864
30877
|
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
@@ -30888,10 +30901,10 @@ async function setTaskState(notificationId, state, opts = {}) {
|
|
|
30888
30901
|
// src/cli.ts
|
|
30889
30902
|
var import_qrcode = __toESM(require_lib(), 1);
|
|
30890
30903
|
import { hostname } from "os";
|
|
30891
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as
|
|
30904
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
30892
30905
|
import { execSync } from "child_process";
|
|
30893
|
-
import { homedir as
|
|
30894
|
-
import { join as
|
|
30906
|
+
import { homedir as homedir6 } from "os";
|
|
30907
|
+
import { join as join5 } from "path";
|
|
30895
30908
|
|
|
30896
30909
|
// ../../packages/schema/dist/index.js
|
|
30897
30910
|
var ContextSchema2 = external_exports.object({
|
|
@@ -32365,6 +32378,7 @@ function runHarness(opts) {
|
|
|
32365
32378
|
send(text) {
|
|
32366
32379
|
session?.send(text);
|
|
32367
32380
|
},
|
|
32381
|
+
working: () => running && state.resting !== true,
|
|
32368
32382
|
stop() {
|
|
32369
32383
|
running = false;
|
|
32370
32384
|
cancelAsks(state);
|
|
@@ -32375,6 +32389,11 @@ function runHarness(opts) {
|
|
|
32375
32389
|
};
|
|
32376
32390
|
}
|
|
32377
32391
|
|
|
32392
|
+
// src/host.ts
|
|
32393
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
32394
|
+
import { homedir as homedir5 } from "os";
|
|
32395
|
+
import { join as join4 } from "path";
|
|
32396
|
+
|
|
32378
32397
|
// src/workspaces.ts
|
|
32379
32398
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
32380
32399
|
import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
|
|
@@ -32402,10 +32421,20 @@ function addWorkspace(dir, deps) {
|
|
|
32402
32421
|
}
|
|
32403
32422
|
|
|
32404
32423
|
// src/host.ts
|
|
32424
|
+
var HOST_FILE = join4(homedir5(), ".paigy", "host.json");
|
|
32405
32425
|
function startHost(opts) {
|
|
32406
32426
|
const runs = /* @__PURE__ */ new Map();
|
|
32407
32427
|
const asHost = { token: opts.token };
|
|
32428
|
+
const refreshNames = async () => {
|
|
32429
|
+
for (const slot of listSlots()) {
|
|
32430
|
+
const token = readToken(slot);
|
|
32431
|
+
if (!token) continue;
|
|
32432
|
+
const me = await whoAmI({ token }).catch(() => null);
|
|
32433
|
+
if (me?.name && me.name !== slotName(slot)) saveToken({ access_token: token, name: me.name, device: null }, slot);
|
|
32434
|
+
}
|
|
32435
|
+
};
|
|
32408
32436
|
const beat = () => {
|
|
32437
|
+
void refreshNames();
|
|
32409
32438
|
void heartbeat({
|
|
32410
32439
|
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
32411
32440
|
workspaces: listWorkspaces(opts.wsDeps)
|
|
@@ -32439,7 +32468,7 @@ function startHost(opts) {
|
|
|
32439
32468
|
}
|
|
32440
32469
|
}
|
|
32441
32470
|
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
|
|
32442
|
-
const WAKE_PROMPT = "You were woken because Paigy work is waiting for you. Call check_replies
|
|
32471
|
+
const WAKE_PROMPT = "You were woken because Paigy work is waiting for you. This is a fresh session with your existing identity, so you may already have work in flight that you can't remember. Call check_replies FIRST, then get_thread on each conversation it returns and read it before you touch anything: it holds what you were doing, where (a repo or worktree may not be this folder), and what the owner already decided. Then handle what's waiting and follow your paigy instructions to stay in the loop.";
|
|
32443
32472
|
async function sweepSlots() {
|
|
32444
32473
|
const workspace = listWorkspaces(opts.wsDeps)[0];
|
|
32445
32474
|
if (!workspace) return;
|
|
@@ -32451,7 +32480,8 @@ function startHost(opts) {
|
|
|
32451
32480
|
if (!token) continue;
|
|
32452
32481
|
const work = await checkReplies({ token }).catch(() => null);
|
|
32453
32482
|
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
32454
|
-
const
|
|
32483
|
+
const label = slotName(slot) ?? slot;
|
|
32484
|
+
const log = (line) => opts.log(`[${label}] ${line}`);
|
|
32455
32485
|
const run = runHarness({
|
|
32456
32486
|
harness,
|
|
32457
32487
|
cwd: workspace,
|
|
@@ -32462,19 +32492,26 @@ function startHost(opts) {
|
|
|
32462
32492
|
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
32463
32493
|
onExit: () => {
|
|
32464
32494
|
runs.delete(key);
|
|
32465
|
-
opts.log(`\u2716 ${
|
|
32495
|
+
opts.log(`\u2716 ${label} ended`);
|
|
32466
32496
|
},
|
|
32467
32497
|
log
|
|
32468
32498
|
});
|
|
32469
|
-
runs.set(key, { run, label
|
|
32470
|
-
opts.log(`\u25B6 woke ${
|
|
32499
|
+
runs.set(key, { run, label });
|
|
32500
|
+
opts.log(`\u25B6 woke ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
32471
32501
|
}
|
|
32472
32502
|
}
|
|
32503
|
+
const publish = () => {
|
|
32504
|
+
try {
|
|
32505
|
+
writeFileSync3(HOST_FILE, JSON.stringify({ pid: process.pid, at: Date.now(), roster: api.roster() }));
|
|
32506
|
+
} catch {
|
|
32507
|
+
}
|
|
32508
|
+
};
|
|
32473
32509
|
beat();
|
|
32474
32510
|
const pulse = setInterval(beat, 6e4);
|
|
32475
32511
|
const spawnPoll = setInterval(() => {
|
|
32476
32512
|
void claimAndSpawn();
|
|
32477
32513
|
void sweepSlots();
|
|
32514
|
+
publish();
|
|
32478
32515
|
}, 5e3);
|
|
32479
32516
|
const stopSessions = () => {
|
|
32480
32517
|
for (const { run, label } of runs.values()) {
|
|
@@ -32483,15 +32520,35 @@ function startHost(opts) {
|
|
|
32483
32520
|
}
|
|
32484
32521
|
runs.clear();
|
|
32485
32522
|
};
|
|
32486
|
-
|
|
32523
|
+
const api = {
|
|
32487
32524
|
sessions: () => [...runs.entries()].map(([id, r]) => [id, r.label]),
|
|
32525
|
+
// Every identity this machine holds, plus what it's doing right now. Slots are the
|
|
32526
|
+
// roster (hatched agents, the terminal tools, claimed sessions); a run makes one of
|
|
32527
|
+
// them live, and a run mid-turn makes it working.
|
|
32528
|
+
roster: () => {
|
|
32529
|
+
const live = new Map([...runs.values()].map((r) => [r.label, r.run]));
|
|
32530
|
+
const names = /* @__PURE__ */ new Map();
|
|
32531
|
+
for (const key of listSlots()) if (key !== "Desktop") names.set(slotName(key) ?? key, key);
|
|
32532
|
+
for (const label of live.keys()) if (!names.has(label)) names.set(label, label);
|
|
32533
|
+
return [...names.keys()].map((name) => ({
|
|
32534
|
+
name,
|
|
32535
|
+
running: live.has(name),
|
|
32536
|
+
working: live.get(name)?.working() ?? false
|
|
32537
|
+
}));
|
|
32538
|
+
},
|
|
32488
32539
|
stopSessions,
|
|
32489
32540
|
stop() {
|
|
32490
32541
|
clearInterval(pulse);
|
|
32491
32542
|
clearInterval(spawnPoll);
|
|
32492
32543
|
stopSessions();
|
|
32544
|
+
try {
|
|
32545
|
+
writeFileSync3(HOST_FILE, JSON.stringify({ pid: process.pid, at: 0, roster: [] }));
|
|
32546
|
+
} catch {
|
|
32547
|
+
}
|
|
32493
32548
|
}
|
|
32494
32549
|
};
|
|
32550
|
+
publish();
|
|
32551
|
+
return api;
|
|
32495
32552
|
}
|
|
32496
32553
|
|
|
32497
32554
|
// src/cli.ts
|
|
@@ -32603,7 +32660,7 @@ async function main() {
|
|
|
32603
32660
|
const wsDeps = { file: workspacesFile() };
|
|
32604
32661
|
for (const dir of parsed.grant) addWorkspace(dir, wsDeps);
|
|
32605
32662
|
if (!listWorkspaces(wsDeps).length) {
|
|
32606
|
-
const guess =
|
|
32663
|
+
const guess = join5(homedir6(), "projects");
|
|
32607
32664
|
if (existsSync5(guess)) {
|
|
32608
32665
|
addWorkspace(guess, wsDeps);
|
|
32609
32666
|
console.log(`\u2713 workspace granted: ${guess} (change with --grant DIR)`);
|
|
@@ -32641,12 +32698,12 @@ async function main() {
|
|
|
32641
32698
|
}
|
|
32642
32699
|
}
|
|
32643
32700
|
try {
|
|
32644
|
-
const settings =
|
|
32701
|
+
const settings = join5(homedir6(), ".claude", "settings.json");
|
|
32645
32702
|
if (which("claude") && existsSync5(settings)) {
|
|
32646
|
-
const cfg = JSON.parse(
|
|
32703
|
+
const cfg = JSON.parse(readFileSync4(settings, "utf8"));
|
|
32647
32704
|
if (!cfg["statusLine"]) {
|
|
32648
32705
|
cfg["statusLine"] = { type: "command", command: "npx -y -p @paigy/mcp@latest paigy-statusline" };
|
|
32649
|
-
|
|
32706
|
+
writeFileSync4(settings, JSON.stringify(cfg, null, 2));
|
|
32650
32707
|
console.log("\u2713 statusline wired");
|
|
32651
32708
|
}
|
|
32652
32709
|
}
|
|
@@ -32696,14 +32753,14 @@ async function main() {
|
|
|
32696
32753
|
</array>
|
|
32697
32754
|
<key>RunAtLoad</key><true/>
|
|
32698
32755
|
<key>KeepAlive</key><true/>
|
|
32699
|
-
<key>StandardOutPath</key><string>${
|
|
32700
|
-
<key>StandardErrorPath</key><string>${
|
|
32756
|
+
<key>StandardOutPath</key><string>${join5(homedir6(), ".paigy", "host.log")}</string>
|
|
32757
|
+
<key>StandardErrorPath</key><string>${join5(homedir6(), ".paigy", "host.log")}</string>
|
|
32701
32758
|
</dict></plist>
|
|
32702
32759
|
`;
|
|
32703
|
-
const dir =
|
|
32760
|
+
const dir = join5(homedir6(), "Library", "LaunchAgents");
|
|
32704
32761
|
mkdirSync3(dir, { recursive: true });
|
|
32705
|
-
const path =
|
|
32706
|
-
|
|
32762
|
+
const path = join5(dir, "ai.paigy.harness.plist");
|
|
32763
|
+
writeFileSync4(path, plist);
|
|
32707
32764
|
execSync(`launchctl unload ${path} 2>/dev/null; launchctl load ${path}`, { shell: "/bin/sh" });
|
|
32708
32765
|
console.log(`\u2713 host installed as a login service (${path}) \u2014 logs at ~/.paigy/host.log`);
|
|
32709
32766
|
return;
|
package/dist/main.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
1
2
|
import {
|
|
2
3
|
__commonJS,
|
|
3
4
|
__export,
|
|
4
5
|
__require,
|
|
5
6
|
__toESM
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OBWUN26Y.js";
|
|
7
8
|
|
|
8
9
|
// ../../node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/can-promise.js
|
|
9
10
|
var require_can_promise = __commonJS({
|
|
@@ -4577,7 +4578,7 @@ var require_lib = __commonJS({
|
|
|
4577
4578
|
import { app, BrowserWindow, dialog, ipcMain } from "electron";
|
|
4578
4579
|
import { spawn as spawn2 } from "child_process";
|
|
4579
4580
|
import { fileURLToPath } from "url";
|
|
4580
|
-
import { dirname as dirname4, join as
|
|
4581
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
4581
4582
|
|
|
4582
4583
|
// ../../packages/sdk/dist/index.js
|
|
4583
4584
|
import { createRequire as __sdkCreateRequire } from "module";
|
|
@@ -10888,7 +10889,7 @@ var PROXY_ENV = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
|
10888
10889
|
var agent;
|
|
10889
10890
|
async function proxy() {
|
|
10890
10891
|
if (!PROXY_ENV.some((k) => process.env[k])) return void 0;
|
|
10891
|
-
return agent ??= new (await import("./undici-
|
|
10892
|
+
return agent ??= new (await import("./undici-GCFUZISI.js")).EnvHttpProxyAgent();
|
|
10892
10893
|
}
|
|
10893
10894
|
async function reach(url, init) {
|
|
10894
10895
|
try {
|
|
@@ -12105,6 +12106,9 @@ function readToken(agent2 = AGENT_NAME) {
|
|
|
12105
12106
|
function listSlots() {
|
|
12106
12107
|
return Object.keys(readTokenFile());
|
|
12107
12108
|
}
|
|
12109
|
+
function slotName(agent2) {
|
|
12110
|
+
return readTokenFile()[agent2]?.name ?? null;
|
|
12111
|
+
}
|
|
12108
12112
|
async function requestCode(suggestedName = AGENT_NAME, e2ee) {
|
|
12109
12113
|
const res = await reach(`${BACKEND_URL}/api/device/code`, {
|
|
12110
12114
|
method: "POST",
|
|
@@ -12259,6 +12263,16 @@ async function checkReplies(opts = {}) {
|
|
|
12259
12263
|
}
|
|
12260
12264
|
var tokenOverride = null;
|
|
12261
12265
|
var authToken = (explicit) => explicit ?? tokenOverride ?? readToken();
|
|
12266
|
+
async function whoAmI(opts = {}) {
|
|
12267
|
+
try {
|
|
12268
|
+
const res = await reach(`${BACKEND_URL}/api/identity`, {
|
|
12269
|
+
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
12270
|
+
});
|
|
12271
|
+
return res.ok ? await res.json() : null;
|
|
12272
|
+
} catch {
|
|
12273
|
+
return null;
|
|
12274
|
+
}
|
|
12275
|
+
}
|
|
12262
12276
|
async function claimSessions(opts = {}) {
|
|
12263
12277
|
const res = ensureAuthed(await reach(`${BACKEND_URL}/api/sessions/claim`, {
|
|
12264
12278
|
headers: { authorization: `Bearer ${authToken(opts.token)}` }
|
|
@@ -12369,6 +12383,11 @@ function detectAll(deps = {}) {
|
|
|
12369
12383
|
return CATALOG.map((entry) => detect(entry, deps));
|
|
12370
12384
|
}
|
|
12371
12385
|
|
|
12386
|
+
// src/host.ts
|
|
12387
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
12388
|
+
import { homedir as homedir5 } from "os";
|
|
12389
|
+
import { join as join4 } from "path";
|
|
12390
|
+
|
|
12372
12391
|
// src/workspaces.ts
|
|
12373
12392
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
12374
12393
|
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
@@ -13862,6 +13881,7 @@ function runHarness(opts) {
|
|
|
13862
13881
|
send(text) {
|
|
13863
13882
|
session?.send(text);
|
|
13864
13883
|
},
|
|
13884
|
+
working: () => running && state.resting !== true,
|
|
13865
13885
|
stop() {
|
|
13866
13886
|
running = false;
|
|
13867
13887
|
cancelAsks(state);
|
|
@@ -13873,10 +13893,38 @@ function runHarness(opts) {
|
|
|
13873
13893
|
}
|
|
13874
13894
|
|
|
13875
13895
|
// src/host.ts
|
|
13896
|
+
var HOST_FILE = join4(homedir5(), ".paigy", "host.json");
|
|
13897
|
+
function readHostState() {
|
|
13898
|
+
try {
|
|
13899
|
+
const s = JSON.parse(readFileSync3(HOST_FILE, "utf8"));
|
|
13900
|
+
if (Date.now() - s.at > 2e4) return null;
|
|
13901
|
+
try {
|
|
13902
|
+
process.kill(s.pid, 0);
|
|
13903
|
+
} catch {
|
|
13904
|
+
return null;
|
|
13905
|
+
}
|
|
13906
|
+
return s;
|
|
13907
|
+
} catch {
|
|
13908
|
+
return null;
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
function hostElsewhere() {
|
|
13912
|
+
const s = readHostState();
|
|
13913
|
+
return !!s && s.pid !== process.pid;
|
|
13914
|
+
}
|
|
13876
13915
|
function startHost(opts) {
|
|
13877
13916
|
const runs = /* @__PURE__ */ new Map();
|
|
13878
13917
|
const asHost = { token: opts.token };
|
|
13918
|
+
const refreshNames = async () => {
|
|
13919
|
+
for (const slot of listSlots()) {
|
|
13920
|
+
const token = readToken(slot);
|
|
13921
|
+
if (!token) continue;
|
|
13922
|
+
const me = await whoAmI({ token }).catch(() => null);
|
|
13923
|
+
if (me?.name && me.name !== slotName(slot)) saveToken({ access_token: token, name: me.name, device: null }, slot);
|
|
13924
|
+
}
|
|
13925
|
+
};
|
|
13879
13926
|
const beat = () => {
|
|
13927
|
+
void refreshNames();
|
|
13880
13928
|
void heartbeat({
|
|
13881
13929
|
harnesses: detectAll().map((a) => ({ name: a.name, label: a.label, status: a.status })),
|
|
13882
13930
|
workspaces: listWorkspaces(opts.wsDeps)
|
|
@@ -13910,7 +13958,7 @@ function startHost(opts) {
|
|
|
13910
13958
|
}
|
|
13911
13959
|
}
|
|
13912
13960
|
const SLOT_HARNESS = { "mcp-agent": "claude", codex: "codex" };
|
|
13913
|
-
const WAKE_PROMPT = "You were woken because Paigy work is waiting for you. Call check_replies
|
|
13961
|
+
const WAKE_PROMPT = "You were woken because Paigy work is waiting for you. This is a fresh session with your existing identity, so you may already have work in flight that you can't remember. Call check_replies FIRST, then get_thread on each conversation it returns and read it before you touch anything: it holds what you were doing, where (a repo or worktree may not be this folder), and what the owner already decided. Then handle what's waiting and follow your paigy instructions to stay in the loop.";
|
|
13914
13962
|
async function sweepSlots() {
|
|
13915
13963
|
const workspace = listWorkspaces(opts.wsDeps)[0];
|
|
13916
13964
|
if (!workspace) return;
|
|
@@ -13922,7 +13970,8 @@ function startHost(opts) {
|
|
|
13922
13970
|
if (!token) continue;
|
|
13923
13971
|
const work = await checkReplies({ token }).catch(() => null);
|
|
13924
13972
|
if (!work || work.requests.length === 0 && work.replies.length === 0) continue;
|
|
13925
|
-
const
|
|
13973
|
+
const label = slotName(slot) ?? slot;
|
|
13974
|
+
const log2 = (line) => opts.log(`[${label}] ${line}`);
|
|
13926
13975
|
const run = runHarness({
|
|
13927
13976
|
harness,
|
|
13928
13977
|
cwd: workspace,
|
|
@@ -13933,19 +13982,26 @@ function startHost(opts) {
|
|
|
13933
13982
|
// A dead run must not squat the slot — evict so the next wake can respawn.
|
|
13934
13983
|
onExit: () => {
|
|
13935
13984
|
runs.delete(key);
|
|
13936
|
-
opts.log(`\u2716 ${
|
|
13985
|
+
opts.log(`\u2716 ${label} ended`);
|
|
13937
13986
|
},
|
|
13938
13987
|
log: log2
|
|
13939
13988
|
});
|
|
13940
|
-
runs.set(key, { run, label
|
|
13941
|
-
opts.log(`\u25B6 woke ${
|
|
13989
|
+
runs.set(key, { run, label });
|
|
13990
|
+
opts.log(`\u25B6 woke ${label} (${harness}) \u2014 work was waiting in ${workspace}`);
|
|
13942
13991
|
}
|
|
13943
13992
|
}
|
|
13993
|
+
const publish = () => {
|
|
13994
|
+
try {
|
|
13995
|
+
writeFileSync3(HOST_FILE, JSON.stringify({ pid: process.pid, at: Date.now(), roster: api.roster() }));
|
|
13996
|
+
} catch {
|
|
13997
|
+
}
|
|
13998
|
+
};
|
|
13944
13999
|
beat();
|
|
13945
14000
|
const pulse = setInterval(beat, 6e4);
|
|
13946
14001
|
const spawnPoll = setInterval(() => {
|
|
13947
14002
|
void claimAndSpawn();
|
|
13948
14003
|
void sweepSlots();
|
|
14004
|
+
publish();
|
|
13949
14005
|
}, 5e3);
|
|
13950
14006
|
const stopSessions = () => {
|
|
13951
14007
|
for (const { run, label } of runs.values()) {
|
|
@@ -13954,15 +14010,35 @@ function startHost(opts) {
|
|
|
13954
14010
|
}
|
|
13955
14011
|
runs.clear();
|
|
13956
14012
|
};
|
|
13957
|
-
|
|
14013
|
+
const api = {
|
|
13958
14014
|
sessions: () => [...runs.entries()].map(([id, r]) => [id, r.label]),
|
|
14015
|
+
// Every identity this machine holds, plus what it's doing right now. Slots are the
|
|
14016
|
+
// roster (hatched agents, the terminal tools, claimed sessions); a run makes one of
|
|
14017
|
+
// them live, and a run mid-turn makes it working.
|
|
14018
|
+
roster: () => {
|
|
14019
|
+
const live = new Map([...runs.values()].map((r) => [r.label, r.run]));
|
|
14020
|
+
const names = /* @__PURE__ */ new Map();
|
|
14021
|
+
for (const key of listSlots()) if (key !== "Desktop") names.set(slotName(key) ?? key, key);
|
|
14022
|
+
for (const label of live.keys()) if (!names.has(label)) names.set(label, label);
|
|
14023
|
+
return [...names.keys()].map((name) => ({
|
|
14024
|
+
name,
|
|
14025
|
+
running: live.has(name),
|
|
14026
|
+
working: live.get(name)?.working() ?? false
|
|
14027
|
+
}));
|
|
14028
|
+
},
|
|
13959
14029
|
stopSessions,
|
|
13960
14030
|
stop() {
|
|
13961
14031
|
clearInterval(pulse);
|
|
13962
14032
|
clearInterval(spawnPoll);
|
|
13963
14033
|
stopSessions();
|
|
14034
|
+
try {
|
|
14035
|
+
writeFileSync3(HOST_FILE, JSON.stringify({ pid: process.pid, at: 0, roster: [] }));
|
|
14036
|
+
} catch {
|
|
14037
|
+
}
|
|
13964
14038
|
}
|
|
13965
14039
|
};
|
|
14040
|
+
publish();
|
|
14041
|
+
return api;
|
|
13966
14042
|
}
|
|
13967
14043
|
|
|
13968
14044
|
// src/main.ts
|
|
@@ -13974,22 +14050,23 @@ var win = null;
|
|
|
13974
14050
|
function log(message) {
|
|
13975
14051
|
win?.webContents.send("paigy:log", message);
|
|
13976
14052
|
}
|
|
13977
|
-
var ICON =
|
|
14053
|
+
var ICON = join5(here, "..", "assets", "icon.png");
|
|
13978
14054
|
function createWindow() {
|
|
13979
14055
|
win = new BrowserWindow({
|
|
13980
14056
|
width: 720,
|
|
13981
14057
|
height: 640,
|
|
13982
14058
|
icon: ICON,
|
|
13983
14059
|
// Windows/Linux window icon; macOS uses the dock icon below
|
|
13984
|
-
webPreferences: { preload:
|
|
14060
|
+
webPreferences: { preload: join5(here, "preload.cjs"), contextIsolation: true, nodeIntegration: false }
|
|
13985
14061
|
});
|
|
13986
|
-
void win.loadFile(
|
|
14062
|
+
void win.loadFile(join5(here, "..", "renderer", "index.html"));
|
|
13987
14063
|
}
|
|
13988
14064
|
var host = null;
|
|
13989
14065
|
app.whenReady().then(() => {
|
|
13990
14066
|
if (process.platform === "darwin") app.dock?.setIcon(ICON);
|
|
13991
14067
|
createWindow();
|
|
13992
|
-
if (deviceToken()) host = startHost({ wsDeps, token: deviceToken(), log });
|
|
14068
|
+
if (deviceToken() && !hostElsewhere()) host = startHost({ wsDeps, token: deviceToken(), log });
|
|
14069
|
+
else if (hostElsewhere()) log("\u25C9 this Mac is already hosting (the background service) \u2014 showing its agents");
|
|
13993
14070
|
app.on("activate", () => {
|
|
13994
14071
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
13995
14072
|
});
|
|
@@ -14007,7 +14084,10 @@ ipcMain.handle("paigy:status", () => ({
|
|
|
14007
14084
|
// (works, but the beacon upgrades it); none = show the beacon.
|
|
14008
14085
|
paired: Boolean(deviceToken()),
|
|
14009
14086
|
device: Boolean(readToken(DEVICE_SLOT)),
|
|
14010
|
-
sessions: host?.sessions().map(([, label]) => label) ?? []
|
|
14087
|
+
sessions: host?.sessions().map(([, label]) => label) ?? [],
|
|
14088
|
+
// The party (companion.md / party design.md): who lives on this machine and what
|
|
14089
|
+
// each one is doing THIS SECOND — the one thing the laptop knows better than the phone.
|
|
14090
|
+
roster: host?.roster() ?? readHostState()?.roster ?? []
|
|
14011
14091
|
}));
|
|
14012
14092
|
var beaconLive = false;
|
|
14013
14093
|
ipcMain.handle("paigy:beacon", async () => {
|
|
@@ -14022,7 +14102,7 @@ ipcMain.handle("paigy:beacon", async () => {
|
|
|
14022
14102
|
saveToken(token, DEVICE_SLOT);
|
|
14023
14103
|
beaconLive = false;
|
|
14024
14104
|
host?.stop();
|
|
14025
|
-
host = startHost({ wsDeps, token: deviceToken(), log });
|
|
14105
|
+
if (!hostElsewhere()) host = startHost({ wsDeps, token: deviceToken(), log });
|
|
14026
14106
|
win?.webContents.send("paigy:paired", { name: token.name });
|
|
14027
14107
|
log(`\u2713 paired as "${token.name}" \u2014 this machine is now launchable from your phone`);
|
|
14028
14108
|
return;
|