@openape/nest 2.1.3 → 2.3.0
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 +105 -9
- package/package.json +2 -2
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;
|
|
@@ -658,18 +686,54 @@ var TroopWs = class {
|
|
|
658
686
|
await this.handleSpawnIntent(frame);
|
|
659
687
|
return;
|
|
660
688
|
}
|
|
689
|
+
if (frame.type === "destroy-intent") {
|
|
690
|
+
await this.handleDestroyIntent(frame);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
661
693
|
if (frame.type === "reload-bridge") {
|
|
662
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);
|
|
663
703
|
}
|
|
664
704
|
}
|
|
665
705
|
async handleConfigUpdate(frame) {
|
|
666
|
-
const
|
|
667
|
-
if (!
|
|
668
|
-
const dash = local.lastIndexOf("-");
|
|
669
|
-
const name = dash > 0 ? local.slice(0, dash) : local;
|
|
706
|
+
const name = agentNameFromEmail(frame.agent_email);
|
|
707
|
+
if (!name) return;
|
|
670
708
|
this.opts.log(`troop-ws: config-update for ${name} \u2014 running sync`);
|
|
671
709
|
await this.runApes(["run", "--as", name, "--wait", "--", "apes", "agents", "sync"], `config-update sync ${name}`);
|
|
672
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
|
+
}
|
|
673
737
|
async handleSpawnIntent(frame) {
|
|
674
738
|
this.opts.log(`troop-ws: spawn-intent ${frame.name} (intent ${frame.intent_id})`);
|
|
675
739
|
const args = ["agents", "spawn", frame.name];
|
|
@@ -688,6 +752,18 @@ var TroopWs = class {
|
|
|
688
752
|
this.send({ type: "spawn-result", intent_id: frame.intent_id, ok: false, error });
|
|
689
753
|
}
|
|
690
754
|
}
|
|
755
|
+
async handleDestroyIntent(frame) {
|
|
756
|
+
this.opts.log(`troop-ws: destroy-intent ${frame.name} (intent ${frame.intent_id})`);
|
|
757
|
+
try {
|
|
758
|
+
await runWithCapture(this.opts.apesBin, ["agents", "destroy", frame.name, "--force"]);
|
|
759
|
+
this.opts.log(`troop-ws: destroy-result ${frame.name} ok`);
|
|
760
|
+
this.send({ type: "destroy-result", intent_id: frame.intent_id, ok: true, name: frame.name });
|
|
761
|
+
} catch (err) {
|
|
762
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
763
|
+
this.opts.log(`troop-ws: destroy-result ${frame.name} FAIL: ${error}`);
|
|
764
|
+
this.send({ type: "destroy-result", intent_id: frame.intent_id, ok: false, name: frame.name, error });
|
|
765
|
+
}
|
|
766
|
+
}
|
|
691
767
|
async handleReloadBridge(frame) {
|
|
692
768
|
this.opts.log(`troop-ws: reload-bridge ${frame.name}`);
|
|
693
769
|
await this.runApes(
|
|
@@ -721,14 +797,34 @@ function runWithCapture(bin, args) {
|
|
|
721
797
|
resolve({ stdout: stdout.toString(), stderr: stderr.toString() });
|
|
722
798
|
return;
|
|
723
799
|
}
|
|
724
|
-
const
|
|
725
|
-
|
|
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));
|
|
726
805
|
return;
|
|
727
806
|
}
|
|
728
807
|
resolve({ stdout: stdout.toString(), stderr: stderr.toString() });
|
|
729
808
|
});
|
|
730
809
|
});
|
|
731
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
|
+
}
|
|
732
828
|
function readHostId() {
|
|
733
829
|
try {
|
|
734
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.0",
|
|
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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"ofetch": "^1.4.1",
|
|
20
20
|
"ws": "^8.18.0",
|
|
21
21
|
"@openape/cli-auth": "0.4.0",
|
|
22
|
-
"@openape/core": "0.
|
|
22
|
+
"@openape/core": "0.17.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@antfu/eslint-config": "^7.6.1",
|