@tetsuo-ai/agenc 0.10.0 → 0.11.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tetsuo-ai/agenc",
3
- "version": "0.10.0",
3
+ "version": "0.11.2",
4
4
  "description": "Public AgenC CLI launcher package",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,6 +23,7 @@
23
23
  "lib/activation-lock-identity.mjs",
24
24
  "lib/generated-wrapper.d.mts",
25
25
  "lib/generated-wrapper.mjs",
26
+ "lib/runtime-archive.d.mts",
26
27
  "lib/runtime-archive.mjs",
27
28
  "lib/runtime-compatibility.mjs",
28
29
  "lib/runtime-manager.mjs",
@@ -47,6 +48,6 @@
47
48
  "@tetsuo-ai/runtime": "file:../../runtime"
48
49
  },
49
50
  "engines": {
50
- "node": ">=25.9.0 <26.0.0"
51
+ "node": ">=26.5.0 <27.0.0"
51
52
  }
52
53
  }
package/src/launcher.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { realpathSync } from "node:fs";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { createRequire } from "node:module";
4
5
  import { homedir } from "node:os";
5
- import { dirname, join, resolve } from "node:path";
6
+ import { delimiter, dirname, join, resolve } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
 
8
9
  const DEFAULT_READY_TIMEOUT_MS = 2000;
@@ -74,11 +75,17 @@ export function isDaemonCommand(argv) {
74
75
  export function resolveRuntimeBin(requireFn = requireFromLauncher) {
75
76
  try {
76
77
  const runtimeEntry = requireFn.resolve("@tetsuo-ai/runtime");
78
+ const runtimeRoot = realpathSync(resolve(dirname(runtimeEntry), ".."));
79
+ const repositoryRuntimeRoot = realpathSync(
80
+ resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "runtime"),
81
+ );
82
+ if (runtimeRoot !== repositoryRuntimeRoot) return null;
77
83
  return resolve(dirname(runtimeEntry), "../bin/agenc");
78
84
  } catch (error) {
79
85
  if (
80
86
  error?.code === "MODULE_NOT_FOUND" ||
81
- error?.code === "ERR_MODULE_NOT_FOUND"
87
+ error?.code === "ERR_MODULE_NOT_FOUND" ||
88
+ error?.code === "ENOENT"
82
89
  ) {
83
90
  return null;
84
91
  }
@@ -86,8 +93,41 @@ export function resolveRuntimeBin(requireFn = requireFromLauncher) {
86
93
  }
87
94
  }
88
95
 
89
- // Resolve the runtime entry for either mode: prefer the dev file:-link, else
90
- // ensure (download + verify + extract) the published per-platform runtime.
96
+ function hostRuntimeLaunch(runtimeBin) {
97
+ return { runtimeBin, nodeBin: process.execPath };
98
+ }
99
+
100
+ function requireRuntimeLaunch(value) {
101
+ if (
102
+ value === null ||
103
+ typeof value !== "object" ||
104
+ typeof value.runtimeBin !== "string" ||
105
+ typeof value.nodeBin !== "string" ||
106
+ (
107
+ value.nodeLibraryPath !== undefined &&
108
+ typeof value.nodeLibraryPath !== "string"
109
+ )
110
+ ) {
111
+ throw new Error("agenc: runtime manager returned an invalid launch contract");
112
+ }
113
+ return value;
114
+ }
115
+
116
+ // Resolve every path needed to execute the runtime. Development file: links
117
+ // intentionally use the current Node process; published artifacts always
118
+ // return their embedded Node (and the Linux compatibility-library directory).
119
+ export async function resolveRuntimeLaunchAsync({
120
+ requireFn = requireFromLauncher,
121
+ ensureFn,
122
+ } = {}) {
123
+ const dev = resolveRuntimeBin(requireFn);
124
+ if (dev !== null) return hostRuntimeLaunch(dev);
125
+ const ensureRuntimeLaunch =
126
+ ensureFn ?? (await import("../lib/runtime-manager.mjs")).ensureRuntimeLaunch;
127
+ return requireRuntimeLaunch(await ensureRuntimeLaunch());
128
+ }
129
+
130
+ // Retained for callers that only need the script path.
91
131
  export async function resolveRuntimeBinAsync({
92
132
  requireFn = requireFromLauncher,
93
133
  ensureFn,
@@ -96,7 +136,10 @@ export async function resolveRuntimeBinAsync({
96
136
  if (dev !== null) return dev;
97
137
  const ensureRuntime =
98
138
  ensureFn ?? (await import("../lib/runtime-manager.mjs")).ensureRuntime;
99
- return ensureRuntime();
139
+ const ensured = await ensureRuntime();
140
+ return typeof ensured === "string"
141
+ ? ensured
142
+ : requireRuntimeLaunch(ensured).runtimeBin;
100
143
  }
101
144
 
102
145
  export async function spawnNodeScript(
@@ -107,12 +150,27 @@ export async function spawnNodeScript(
107
150
  cwd = process.cwd(),
108
151
  stdio = "inherit",
109
152
  spawnFn = spawn,
153
+ nodeBin = process.execPath,
154
+ nodeLibraryPath,
110
155
  } = {},
111
156
  ) {
157
+ const childEnv = { ...env };
158
+ const pathKey =
159
+ Object.keys(childEnv).find((key) => key.toLowerCase() === "path") ?? "PATH";
160
+ const nodeDir = dirname(nodeBin);
161
+ childEnv[pathKey] = childEnv[pathKey]
162
+ ? `${nodeDir}${delimiter}${childEnv[pathKey]}`
163
+ : nodeDir;
164
+ if (nodeLibraryPath !== undefined) {
165
+ // The embedded Linux Node requires the artifact's pinned libatomic. Do not
166
+ // search an operator-controlled ambient library path before that exact
167
+ // directory.
168
+ childEnv.LD_LIBRARY_PATH = nodeLibraryPath;
169
+ }
112
170
  return new Promise((resolveExit, reject) => {
113
- const child = spawnFn(process.execPath, [scriptPath, ...args], {
171
+ const child = spawnFn(nodeBin, [scriptPath, ...args], {
114
172
  cwd,
115
- env,
173
+ env: childEnv,
116
174
  stdio,
117
175
  });
118
176
  child.once("error", reject);
@@ -174,6 +232,8 @@ export async function ensureDaemonForLaunch({
174
232
  env = process.env,
175
233
  cwd = process.cwd(),
176
234
  runtimeBin = resolveRuntimeBin(),
235
+ runtimeNodeBin = process.execPath,
236
+ runtimeNodeLibraryPath,
177
237
  userHome = homedir(),
178
238
  readText = readFile,
179
239
  signalPid = process.kill,
@@ -197,7 +257,12 @@ export async function ensureDaemonForLaunch({
197
257
  return { status: "already-running" };
198
258
  }
199
259
 
200
- await spawnDaemonFn(runtimeBin, { env, cwd });
260
+ await spawnDaemonFn(runtimeBin, {
261
+ env,
262
+ cwd,
263
+ nodeBin: runtimeNodeBin,
264
+ nodeLibraryPath: runtimeNodeLibraryPath,
265
+ });
201
266
  const ready = await waitForReadyFn({ env, userHome, readText, signalPid });
202
267
  if (!ready) {
203
268
  throw new Error("AgenC daemon did not become ready before timeout");
@@ -210,16 +275,29 @@ export async function main(
210
275
  {
211
276
  env = process.env,
212
277
  cwd = process.cwd(),
278
+ runtimeLaunch,
213
279
  runtimeBin,
280
+ runtimeNodeBin = process.execPath,
281
+ runtimeNodeLibraryPath,
214
282
  userHome = homedir(),
215
283
  } = {},
216
284
  ) {
217
285
  // Resolve (and, in a published install, download + verify) the runtime before
218
286
  // anything tries to spawn it. Sync default is avoided: it returns null when
219
287
  // runtime isn't an npm dep, which is the normal published case.
220
- let resolvedBin = runtimeBin;
288
+ let resolvedLaunch = runtimeLaunch ??
289
+ (runtimeBin === undefined
290
+ ? undefined
291
+ : {
292
+ runtimeBin,
293
+ nodeBin: runtimeNodeBin,
294
+ ...(runtimeNodeLibraryPath === undefined
295
+ ? {}
296
+ : { nodeLibraryPath: runtimeNodeLibraryPath }),
297
+ });
221
298
  try {
222
- resolvedBin ??= await resolveRuntimeBinAsync();
299
+ resolvedLaunch ??= await resolveRuntimeLaunchAsync();
300
+ resolvedLaunch = requireRuntimeLaunch(resolvedLaunch);
223
301
  } catch (error) {
224
302
  process.stderr.write(
225
303
  `agenc: could not obtain runtime: ${
@@ -229,7 +307,15 @@ export async function main(
229
307
  return 1;
230
308
  }
231
309
  try {
232
- await ensureDaemonForLaunch({ argv, env, cwd, runtimeBin: resolvedBin, userHome });
310
+ await ensureDaemonForLaunch({
311
+ argv,
312
+ env,
313
+ cwd,
314
+ runtimeBin: resolvedLaunch.runtimeBin,
315
+ runtimeNodeBin: resolvedLaunch.nodeBin,
316
+ runtimeNodeLibraryPath: resolvedLaunch.nodeLibraryPath,
317
+ userHome,
318
+ });
233
319
  } catch (error) {
234
320
  // The launcher is transport, not policy: a daemon that cannot start must
235
321
  // not block daemon-independent commands. `agenc update` is the canonical
@@ -245,7 +331,13 @@ export async function main(
245
331
  "agenc: continuing without the daemon; commands that require it will report the failure\n",
246
332
  );
247
333
  }
248
- return spawnNodeScript(resolvedBin, argv, { env, cwd, stdio: "inherit" });
334
+ return spawnNodeScript(resolvedLaunch.runtimeBin, argv, {
335
+ env,
336
+ cwd,
337
+ stdio: "inherit",
338
+ nodeBin: resolvedLaunch.nodeBin,
339
+ nodeLibraryPath: resolvedLaunch.nodeLibraryPath,
340
+ });
249
341
  }
250
342
 
251
343
  if (process.argv[1] === fileURLToPath(import.meta.url)) {