@openape/nest 2.2.0 → 2.3.1
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/index.mjs +89 -9
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -84,8 +84,9 @@ function startScriptContents(agentName) {
|
|
|
84
84
|
return `#!/bin/bash
|
|
85
85
|
# Auto-generated by Pm2Supervisor for agent '${agentName}'.
|
|
86
86
|
set -e
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
ME="$(whoami)"
|
|
88
|
+
export HOME="$(dscl . -read "/Users/$ME" NFSHomeDirectory 2>/dev/null | awk '{print $2}')"
|
|
89
|
+
test -n "$HOME" || { echo "no NFSHomeDirectory for $ME (agent ${agentName})" >&2; exit 1; }
|
|
89
90
|
export PM2_HOME="$HOME/.pm2"
|
|
90
91
|
mkdir -p "$(dirname "${log2}")"
|
|
91
92
|
exec pm2 startOrReload ${ecosystem} >> ${log2} 2>&1 < /dev/null
|
|
@@ -95,6 +96,7 @@ var Pm2Supervisor = class {
|
|
|
95
96
|
constructor(deps) {
|
|
96
97
|
this.deps = deps;
|
|
97
98
|
}
|
|
99
|
+
deps;
|
|
98
100
|
inflight = /* @__PURE__ */ new Set();
|
|
99
101
|
/** Bring per-agent pm2 state in line with the registry. Idempotent. */
|
|
100
102
|
async reconcile(desired) {
|
|
@@ -194,6 +196,7 @@ var TroopSync = class {
|
|
|
194
196
|
constructor(deps) {
|
|
195
197
|
this.deps = deps;
|
|
196
198
|
}
|
|
199
|
+
deps;
|
|
197
200
|
timer;
|
|
198
201
|
inflight = false;
|
|
199
202
|
start() {
|
|
@@ -234,7 +237,7 @@ var TroopSync = class {
|
|
|
234
237
|
};
|
|
235
238
|
|
|
236
239
|
// src/lib/troop-ws.ts
|
|
237
|
-
import { execFile as execFile3, execFileSync } from "child_process";
|
|
240
|
+
import { execFile as execFile3, execFileSync, spawn } from "child_process";
|
|
238
241
|
import { createHash } from "crypto";
|
|
239
242
|
import { readFileSync as readFileSync3 } from "fs";
|
|
240
243
|
import { hostname, networkInterfaces } from "os";
|
|
@@ -536,6 +539,30 @@ async function ensureFreshIdpAuth(now = Math.floor(Date.now() / 1e3)) {
|
|
|
536
539
|
|
|
537
540
|
// src/lib/troop-ws.ts
|
|
538
541
|
import WebSocket from "ws";
|
|
542
|
+
|
|
543
|
+
// src/lib/secret-relay.ts
|
|
544
|
+
var SECRETS_REL_DIR = ".config/openape/secrets.d";
|
|
545
|
+
var ENV_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
546
|
+
function agentNameFromEmail(email) {
|
|
547
|
+
const local = email.split("+")[0];
|
|
548
|
+
if (!local) return null;
|
|
549
|
+
const dash = local.lastIndexOf("-");
|
|
550
|
+
return dash > 0 ? local.slice(0, dash) : local;
|
|
551
|
+
}
|
|
552
|
+
function planSecretWrite(env) {
|
|
553
|
+
if (!ENV_RE.test(env)) return { ok: false, reason: `invalid env name: ${env}` };
|
|
554
|
+
const path = `"$HOME/${SECRETS_REL_DIR}/${env}.blob"`;
|
|
555
|
+
return {
|
|
556
|
+
ok: true,
|
|
557
|
+
script: `mkdir -p "$HOME/${SECRETS_REL_DIR}" && chmod 700 "$HOME/${SECRETS_REL_DIR}" && umask 077 && cat > ${path}`
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function planSecretRevoke(env) {
|
|
561
|
+
if (!ENV_RE.test(env)) return { ok: false, reason: `invalid env name: ${env}` };
|
|
562
|
+
return { ok: true, script: `rm -f "$HOME/${SECRETS_REL_DIR}/${env}.blob"` };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/lib/troop-ws.ts
|
|
539
566
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
540
567
|
var RECONNECT_BASE_MS = 1e3;
|
|
541
568
|
var RECONNECT_MAX_MS = 3e4;
|
|
@@ -546,6 +573,7 @@ var TroopWs = class {
|
|
|
546
573
|
this.hostId = readHostId();
|
|
547
574
|
this.hostname = hostname();
|
|
548
575
|
}
|
|
576
|
+
opts;
|
|
549
577
|
socket = null;
|
|
550
578
|
heartbeatTimer = null;
|
|
551
579
|
reconnectTimer = null;
|
|
@@ -664,16 +692,48 @@ var TroopWs = class {
|
|
|
664
692
|
}
|
|
665
693
|
if (frame.type === "reload-bridge") {
|
|
666
694
|
await this.handleReloadBridge(frame);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (frame.type === "secret-update") {
|
|
698
|
+
await this.handleSecretUpdate(frame);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (frame.type === "secret-revoke") {
|
|
702
|
+
await this.handleSecretRevoke(frame);
|
|
667
703
|
}
|
|
668
704
|
}
|
|
669
705
|
async handleConfigUpdate(frame) {
|
|
670
|
-
const
|
|
671
|
-
if (!
|
|
672
|
-
const dash = local.lastIndexOf("-");
|
|
673
|
-
const name = dash > 0 ? local.slice(0, dash) : local;
|
|
706
|
+
const name = agentNameFromEmail(frame.agent_email);
|
|
707
|
+
if (!name) return;
|
|
674
708
|
this.opts.log(`troop-ws: config-update for ${name} \u2014 running sync`);
|
|
675
709
|
await this.runApes(["run", "--as", name, "--wait", "--", "apes", "agents", "sync"], `config-update sync ${name}`);
|
|
676
710
|
}
|
|
711
|
+
async handleSecretUpdate(frame) {
|
|
712
|
+
const name = agentNameFromEmail(frame.agent_email);
|
|
713
|
+
if (!name) return;
|
|
714
|
+
const plan = planSecretWrite(frame.env);
|
|
715
|
+
if (!plan.ok) {
|
|
716
|
+
this.opts.log(`troop-ws: secret-update ${frame.agent_email}/${frame.env} rejected: ${plan.reason}`);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
this.opts.log(`troop-ws: secret-update ${name}/${frame.env}`);
|
|
720
|
+
try {
|
|
721
|
+
await runWithInput(this.opts.apesBin, ["run", "--as", name, "--wait", "--", "sh", "-c", plan.script], frame.blob);
|
|
722
|
+
} catch (err) {
|
|
723
|
+
this.opts.log(`troop-ws: secret-update ${name}/${frame.env} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
async handleSecretRevoke(frame) {
|
|
727
|
+
const name = agentNameFromEmail(frame.agent_email);
|
|
728
|
+
if (!name) return;
|
|
729
|
+
const plan = planSecretRevoke(frame.env);
|
|
730
|
+
if (!plan.ok) {
|
|
731
|
+
this.opts.log(`troop-ws: secret-revoke ${frame.agent_email}/${frame.env} rejected: ${plan.reason}`);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
this.opts.log(`troop-ws: secret-revoke ${name}/${frame.env}`);
|
|
735
|
+
await this.runApes(["run", "--as", name, "--wait", "--", "sh", "-c", plan.script], `secret-revoke ${name}/${frame.env}`);
|
|
736
|
+
}
|
|
677
737
|
async handleSpawnIntent(frame) {
|
|
678
738
|
this.opts.log(`troop-ws: spawn-intent ${frame.name} (intent ${frame.intent_id})`);
|
|
679
739
|
const args = ["agents", "spawn", frame.name];
|
|
@@ -737,14 +797,34 @@ function runWithCapture(bin, args) {
|
|
|
737
797
|
resolve({ stdout: stdout.toString(), stderr: stderr.toString() });
|
|
738
798
|
return;
|
|
739
799
|
}
|
|
740
|
-
const
|
|
741
|
-
|
|
800
|
+
const raw = stderr.toString().trim() || stdout.toString().trim() || err.message;
|
|
801
|
+
const meaningful = raw.split("\n").map((l) => l.replace(/\s+$/, "")).filter((l) => l.trim() && !/^\s*at\s/.test(l));
|
|
802
|
+
const picked = meaningful.length > 0 ? meaningful : raw.split("\n").filter(Boolean);
|
|
803
|
+
const msg = picked.slice(-15).join("\n").slice(-2500);
|
|
804
|
+
reject(new Error(msg || err.message));
|
|
742
805
|
return;
|
|
743
806
|
}
|
|
744
807
|
resolve({ stdout: stdout.toString(), stderr: stderr.toString() });
|
|
745
808
|
});
|
|
746
809
|
});
|
|
747
810
|
}
|
|
811
|
+
function runWithInput(bin, args, input) {
|
|
812
|
+
return new Promise((resolve, reject) => {
|
|
813
|
+
const child = spawn(bin, args, { stdio: ["pipe", "ignore", "pipe"], timeout: 3e4 });
|
|
814
|
+
let stderr = "";
|
|
815
|
+
child.stderr?.on("data", (d) => {
|
|
816
|
+
stderr += d.toString();
|
|
817
|
+
});
|
|
818
|
+
child.on("error", reject);
|
|
819
|
+
child.on("close", (code) => {
|
|
820
|
+
if (code === 0) resolve();
|
|
821
|
+
else reject(new Error(stderr.split("\n").filter(Boolean).slice(-3).join(" / ") || `exit ${code}`));
|
|
822
|
+
});
|
|
823
|
+
child.stdin?.on("error", () => {
|
|
824
|
+
});
|
|
825
|
+
child.stdin?.end(input);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
748
828
|
function readHostId() {
|
|
749
829
|
try {
|
|
750
830
|
if (process.platform === "darwin") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openape/nest",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "OpenApe Nest — local control-plane daemon that supervises agent processes on this computer. Talks to troop SP for ownership state, spawns/destroys agents via DDISA always-grants, supervises chat-bridge children (replacing per-agent launchd plists).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ofetch": "^1.4.1",
|
|
20
20
|
"ws": "^8.18.0",
|
|
21
|
-
"@openape/cli-auth": "0.4.
|
|
22
|
-
"@openape/core": "0.
|
|
21
|
+
"@openape/cli-auth": "0.4.1",
|
|
22
|
+
"@openape/core": "0.17.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@antfu/eslint-config": "^7.6.1",
|
|
26
26
|
"@types/node": "^22.19.13",
|
|
27
27
|
"@types/ws": "^8.5.13",
|
|
28
|
-
"eslint": "^
|
|
28
|
+
"eslint": "^10.4.0",
|
|
29
29
|
"tsup": "^8.5.1",
|
|
30
30
|
"typescript": "^5.9.3",
|
|
31
|
-
"vitest": "^
|
|
31
|
+
"vitest": "^4.1.7"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=22"
|