@openape/nest 1.1.0 → 1.1.2

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 +62 -11
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -239,7 +239,7 @@ import { join as join3 } from "path";
239
239
  import process from "process";
240
240
  import { promisify as promisify2 } from "util";
241
241
  var execFileAsync2 = promisify2(execFile2);
242
- var AGENTS_DIR = "/var/openape/nest/agents";
242
+ var AGENTS_DIR = "/var/openape/agents";
243
243
  function pm2AppName(agentName) {
244
244
  return `openape-bridge-${agentName}`;
245
245
  }
@@ -264,6 +264,21 @@ module.exports = {
264
264
  }
265
265
  `;
266
266
  }
267
+ function startScriptPath(agentName) {
268
+ return join3(AGENTS_DIR, agentName, "start.sh");
269
+ }
270
+ function startScriptContents(agentName) {
271
+ const ecosystem = ecosystemPath(agentName);
272
+ const log2 = `/var/log/openape/${agentName}-pm2.log`;
273
+ return `#!/bin/bash
274
+ # Auto-generated by Pm2Supervisor for agent '${agentName}'.
275
+ set -e
276
+ export HOME="/Users/${agentName}"
277
+ export PM2_HOME="$HOME/.pm2"
278
+ mkdir -p "$(dirname "${log2}")"
279
+ exec pm2 startOrReload ${ecosystem} >> ${log2} 2>&1 < /dev/null
280
+ `;
281
+ }
267
282
  var Pm2Supervisor = class {
268
283
  constructor(deps) {
269
284
  this.deps = deps;
@@ -278,7 +293,7 @@ var Pm2Supervisor = class {
278
293
  try {
279
294
  await this.startOrReload(agent.name);
280
295
  } catch (err) {
281
- this.deps.log(`pm2-supervisor: ${agent.name} startOrReload failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
296
+ this.deps.log(`pm2-supervisor: ${agent.name} reconcile errored: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
282
297
  } finally {
283
298
  this.inflight.delete(agent.name);
284
299
  }
@@ -300,21 +315,57 @@ var Pm2Supervisor = class {
300
315
  async stopAll() {
301
316
  }
302
317
  async startOrReload(agentName) {
318
+ mkdirSync3(AGENTS_DIR, { recursive: true, mode: 493 });
303
319
  const dir = join3(AGENTS_DIR, agentName);
304
- mkdirSync3(dir, { recursive: true });
320
+ mkdirSync3(dir, { recursive: true, mode: 493 });
305
321
  const path = ecosystemPath(agentName);
306
322
  writeFileSync3(path, ecosystemContents(this.deps.apesBin, agentName), { mode: 420 });
307
- await this.runAsAgent(agentName, ["pm2", "startOrReload", path]);
308
- this.deps.log(`pm2-supervisor: ${agentName} bridge (re)started via pm2`);
323
+ const startPath = startScriptPath(agentName);
324
+ writeFileSync3(startPath, startScriptContents(agentName), { mode: 493 });
325
+ void path;
326
+ try {
327
+ await this.runAsAgent(agentName, ["bash", startPath]);
328
+ } catch {
329
+ }
330
+ let online = false;
331
+ try {
332
+ const { stdout } = await this.runAsAgent(agentName, ["pm2", "jlist"]);
333
+ const json = stdout.match(/\[\s*\{.*\}\s*\]/s)?.[0];
334
+ if (json) {
335
+ const list = JSON.parse(json);
336
+ online = list.some((p) => p.name === pm2AppName(agentName) && p.pm2_env?.status === "online");
337
+ }
338
+ } catch {
339
+ }
340
+ if (online) {
341
+ this.deps.log(`pm2-supervisor: ${agentName} bridge online (pm2)`);
342
+ } else {
343
+ this.deps.log(`pm2-supervisor: ${agentName} bridge NOT online \u2014 see /var/log/openape/${agentName}-pm2.log`);
344
+ }
309
345
  }
310
346
  /** Run a pm2 subcommand AS the agent — escapes-helper does the
311
- * setuid switch, then exec's pm2 in the agent's uid. */
347
+ * setuid switch, then exec's pm2 in the agent's uid.
348
+ *
349
+ * cwd: the agent process inherits cwd from the spawning Nest
350
+ * daemon, whose cwd is /var/openape/nest (mode 750, no access for
351
+ * other uids). Without setting cwd to a world-readable dir, the
352
+ * child's first `process.cwd()` call (which Node does internally
353
+ * during module loading) throws EACCES. /tmp is the most portable
354
+ * always-writable location.
355
+ */
312
356
  async runAsAgent(agentName, args) {
313
- return execFileAsync2(
314
- this.deps.apesBin,
315
- ["run", "--as", agentName, "--wait", "--", ...args],
316
- { maxBuffer: 1024 * 1024, env: process.env, timeout: 6e4 }
317
- );
357
+ try {
358
+ return await execFileAsync2(
359
+ this.deps.apesBin,
360
+ ["run", "--as", agentName, "--wait", "--", ...args],
361
+ { maxBuffer: 1024 * 1024, env: process.env, timeout: 6e4, cwd: "/tmp" }
362
+ );
363
+ } catch (err) {
364
+ const e = err;
365
+ const detail = (e.stderr ?? "").trim().split("\n").slice(-3).join(" / ");
366
+ const stdoutDetail = (e.stdout ?? "").trim().split("\n").slice(-2).join(" / ");
367
+ throw new Error(`${e.message?.split("\n")[0] ?? "execFile failed"} | stderr: ${detail || "<empty>"} | stdout: ${stdoutDetail || "<empty>"}`);
368
+ }
318
369
  }
319
370
  };
320
371
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/nest",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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",