@openape/nest 2.3.5 → 2.4.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 +65 -25
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { watch } from "fs";
4
+ import { readFileSync as readFileSync4, watch } from "fs";
5
5
  import process4 from "process";
6
6
 
7
7
  // src/lib/registry.ts
@@ -57,33 +57,61 @@ function pm2AppName(agentName) {
57
57
  function ecosystemPath(agentName) {
58
58
  return join2(AGENTS_DIR, agentName, "ecosystem.config.js");
59
59
  }
60
- function ecosystemContents(apesBin, agentName) {
61
- void apesBin;
62
- const envForwards = [
63
- "APE_CHAT_BRIDGE_MODEL",
64
- "LITELLM_BASE_URL",
65
- "LITELLM_API_KEY",
66
- "APE_CHAT_BRIDGE_TOOLS",
67
- "APE_CHAT_BRIDGE_MAX_STEPS",
68
- "APE_CHAT_BRIDGE_SYSTEM_PROMPT",
69
- // Chat backend selection (chat.openape.ai vs troop.openape.ai) —
70
- // honoured by the bridge at startup. See ape-agent/src/bridge.ts.
71
- "OPENAPE_BRIDGE_TARGET",
72
- "APE_CHAT_ENDPOINT"
73
- ];
74
- const envLines = envForwards.filter((k) => process2.env[k] !== void 0).map((k) => ` ${k}: ${JSON.stringify(process2.env[k])},`).join("\n");
60
+ var CHAT_ENV_FORWARDS = [
61
+ "APE_CHAT_BRIDGE_MODEL",
62
+ "LITELLM_BASE_URL",
63
+ "LITELLM_API_KEY",
64
+ "APE_CHAT_BRIDGE_TOOLS",
65
+ "APE_CHAT_BRIDGE_MAX_STEPS",
66
+ "APE_CHAT_BRIDGE_SYSTEM_PROMPT",
67
+ // Chat backend selection (chat.openape.ai vs troop.openape.ai) —
68
+ // honoured by the bridge at startup. See ape-agent/src/bridge.ts.
69
+ "OPENAPE_BRIDGE_TARGET",
70
+ "APE_CHAT_ENDPOINT",
71
+ // The bridge's actual troop endpoint (bridge.ts readConfig → endpoint).
72
+ // Unset in prod → defaults to https://troop.openape.ai; the local stack
73
+ // sets it to https://troop.openape.test so the bridge talks to the local
74
+ // control plane, not production.
75
+ "OPENAPE_TROOP_URL"
76
+ ];
77
+ function ecosystemEnvLines(agent) {
78
+ let pairs;
79
+ if (agent.kind === "service") {
80
+ const br = agent.bridge ?? {};
81
+ const candidates = {
82
+ OPENAPE_SP_BASE_URL: agent.service?.spBaseUrl,
83
+ LITELLM_BASE_URL: br.baseUrl ?? process2.env.LITELLM_BASE_URL,
84
+ LITELLM_API_KEY: br.apiKey ?? process2.env.LITELLM_API_KEY ?? process2.env.LITELLM_MASTER_KEY,
85
+ APE_SERVICE_MODEL: br.model ?? process2.env.APE_SERVICE_MODEL ?? process2.env.APE_CHAT_BRIDGE_MODEL,
86
+ APE_SERVICE_POLL_MS: agent.service?.pollIntervalMs != null ? String(agent.service.pollIntervalMs) : void 0
87
+ };
88
+ pairs = Object.entries(candidates).filter((e) => e[1] !== void 0);
89
+ } else {
90
+ pairs = CHAT_ENV_FORWARDS.filter((k) => process2.env[k] !== void 0).map((k) => [k, process2.env[k]]);
91
+ }
92
+ if (process2.env.OPENAPE_BYPASS_APE_SHELL === "1")
93
+ pairs.push(["OPENAPE_BYPASS_APE_SHELL", "1"]);
94
+ if (process2.env.OPENAPE_RECIPE_DEV_DIR)
95
+ pairs.push(["OPENAPE_RECIPE_DEV_DIR", process2.env.OPENAPE_RECIPE_DEV_DIR]);
96
+ if (process2.env.NODE_EXTRA_CA_CERTS)
97
+ pairs.push(["NODE_EXTRA_CA_CERTS", process2.env.NODE_EXTRA_CA_CERTS]);
98
+ return pairs.map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
99
+ }
100
+ function ecosystemContents(agent) {
101
+ const script = agent.kind === "service" ? "ape-agent-service" : "ape-agent";
102
+ const envLines = ecosystemEnvLines(agent);
75
103
  const envBlock = envLines ? `
76
104
  env: {
77
105
  ${envLines}
78
106
  },
79
107
  ` : "";
80
- return `// Auto-generated by Pm2Supervisor for agent '${agentName}'.
108
+ return `// Auto-generated by Pm2Supervisor for agent '${agent.name}'.
81
109
  // Edit at runtime via:
82
- // apes run --as ${agentName} -- pm2 reload ${pm2AppName(agentName)}
110
+ // apes run --as ${agent.name} -- pm2 reload ${pm2AppName(agent.name)}
83
111
  module.exports = {
84
112
  apps: [{
85
- name: '${pm2AppName(agentName)}',
86
- script: 'ape-agent',
113
+ name: '${pm2AppName(agent.name)}',
114
+ script: '${script}',
87
115
  autorestart: true,
88
116
  max_restarts: 10,
89
117
  min_uptime: '30s',
@@ -123,11 +151,11 @@ var Pm2Supervisor = class {
123
151
  /** Bring per-agent pm2 state in line with the registry. Idempotent. */
124
152
  async reconcile(desired) {
125
153
  for (const agent of desired) {
126
- if (agent.bridge == null) continue;
154
+ if (agent.kind !== "service" && agent.bridge == null) continue;
127
155
  if (this.inflight.has(agent.name)) continue;
128
156
  this.inflight.add(agent.name);
129
157
  try {
130
- await this.startOrReload(agent.name);
158
+ await this.startOrReload(agent);
131
159
  } catch (err) {
132
160
  this.deps.log(`pm2-supervisor: ${agent.name} reconcile errored: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
133
161
  } finally {
@@ -152,12 +180,13 @@ var Pm2Supervisor = class {
152
180
  */
153
181
  async stopAll() {
154
182
  }
155
- async startOrReload(agentName) {
183
+ async startOrReload(agent) {
184
+ const agentName = agent.name;
156
185
  ensureSharedDir(AGENTS_DIR);
157
186
  const dir = join2(AGENTS_DIR, agentName);
158
187
  ensureSharedDir(dir);
159
188
  const path = ecosystemPath(agentName);
160
- writeFileSync2(path, ecosystemContents(this.deps.apesBin, agentName), { mode: 436 });
189
+ writeFileSync2(path, ecosystemContents(agent), { mode: 436 });
161
190
  const startPath = startScriptPath(agentName);
162
191
  writeFileSync2(startPath, startScriptContents(agentName), { mode: 509 });
163
192
  void path;
@@ -644,11 +673,22 @@ try {
644
673
  });
645
674
  log(`nest: watching ${REGISTRY_PATH} for registry changes`);
646
675
  } catch (err) {
647
- log(`nest: registry watch failed (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to 5s poll`);
676
+ log(`nest: registry watch failed (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to 5s change-detecting poll`);
677
+ let lastSig = registrySignature();
648
678
  setInterval(() => {
679
+ const sig = registrySignature();
680
+ if (sig === lastSig) return;
681
+ lastSig = sig;
649
682
  void reconcile();
650
683
  }, 5e3).unref();
651
684
  }
685
+ function registrySignature() {
686
+ try {
687
+ return readFileSync4(REGISTRY_PATH, "utf8");
688
+ } catch {
689
+ return "";
690
+ }
691
+ }
652
692
  process4.on("SIGTERM", () => {
653
693
  log("nest: SIGTERM \u2014 stopping");
654
694
  troopSync.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/nest",
3
- "version": "2.3.5",
3
+ "version": "2.4.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",