@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.
Files changed (2) hide show
  1. package/dist/index.mjs +105 -9
  2. 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
- export HOME="$(dscl . -read /Users/${agentName} NFSHomeDirectory 2>/dev/null | awk '{print $2}')"
88
- test -n "$HOME" || { echo "no NFSHomeDirectory for ${agentName}" >&2; exit 1; }
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 local = frame.agent_email.split("+")[0];
667
- if (!local) return;
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 msg = stderr.toString() || err.message;
725
- reject(new Error(msg.split("\n").filter(Boolean).slice(-3).join(" / ")));
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.1.3",
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.16.0"
22
+ "@openape/core": "0.17.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@antfu/eslint-config": "^7.6.1",