@openape/nest 2.3.4 → 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.
- package/dist/index.mjs +65 -25
- package/package.json +2 -2
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 '${
|
|
108
|
+
return `// Auto-generated by Pm2Supervisor for agent '${agent.name}'.
|
|
81
109
|
// Edit at runtime via:
|
|
82
|
-
// apes run --as ${
|
|
110
|
+
// apes run --as ${agent.name} -- pm2 reload ${pm2AppName(agent.name)}
|
|
83
111
|
module.exports = {
|
|
84
112
|
apps: [{
|
|
85
|
-
name: '${pm2AppName(
|
|
86
|
-
script: '
|
|
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
|
|
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(
|
|
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(
|
|
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
|
+
"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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"ofetch": "^1.4.1",
|
|
20
20
|
"ws": "^8.18.0",
|
|
21
21
|
"@openape/cli-auth": "0.5.0",
|
|
22
|
-
"@openape/core": "0.
|
|
22
|
+
"@openape/core": "0.18.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@antfu/eslint-config": "^7.6.1",
|