@openape/nest 2.2.0 → 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 +89 -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;
@@ -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 local = frame.agent_email.split("+")[0];
671
- if (!local) return;
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 msg = stderr.toString() || err.message;
741
- 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));
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.2.0",
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",