@themoltnet/agent-daemon 0.50.0 → 0.51.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/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBlB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGzD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ClB"}
package/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
3
3
  import { assertRuntimeAdapterSupportsProfile } from "./runtime.js";
4
+ import { createRequire } from "node:module";
5
+ import { pino, transport } from "pino";
4
6
  import { Type } from "typebox";
5
7
  import "multiformats/cid";
6
8
  import "multiformats/codecs/json";
@@ -16,7 +18,6 @@ import { constants, createReadStream, createWriteStream, existsSync, lstatSync,
16
18
  import { AuthenticationError, MoltNetError, agentKeyKey, assertTrustedConfigApiUrl, createExecutorAttestor, deriveMcpUrl, formatSecretReferenceString, identitySeedKey, parseSecretReferenceString, readConfig, register, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, resolveOAuth2ClientSecret } from "@themoltnet/sdk";
17
19
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
18
20
  import { once } from "node:events";
19
- import { pino, transport } from "pino";
20
21
  import { metrics } from "@opentelemetry/api";
21
22
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
22
23
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
@@ -26,7 +27,7 @@ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
26
27
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
27
28
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
28
29
  import { AsyncLocalStorage } from "node:async_hooks";
29
- import { mkdir, open, realpath, stat } from "node:fs/promises";
30
+ import { mkdir, open, readFile, realpath, stat, writeFile } from "node:fs/promises";
30
31
  import { pipeline } from "node:stream/promises";
31
32
  import cors from "@fastify/cors";
32
33
  import helmet from "@fastify/helmet";
@@ -34,7 +35,8 @@ import { lock, lockSync } from "proper-lockfile";
34
35
  import { ModelRuntime, readStoredCredential } from "@earendil-works/pi-coding-agent";
35
36
  import { Transform, Writable } from "node:stream";
36
37
  import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
37
- import { homedir } from "node:os";
38
+ import { homedir, platform } from "node:os";
39
+ import { pathToFileURL } from "node:url";
38
40
  import { StringDecoder } from "node:string_decoder";
39
41
  import rateLimit from "@fastify/rate-limit";
40
42
  import Fastify from "fastify";
@@ -3431,6 +3433,8 @@ Commands:
3431
3433
  child processes. Binds 127.0.0.1 only.
3432
3434
  sync-sessions
3433
3435
  Repair durable runtime-session checkpoints from local slot files.
3436
+ update check
3437
+ Check the stable MoltNet agent release without reading credentials.
3434
3438
 
3435
3439
  Run \`agent-daemon <command> --help\` for command-specific flags.
3436
3440
 
@@ -3798,6 +3802,12 @@ function loadAgentServerEnvConfig() {
3798
3802
  function processEnvSnapshot() {
3799
3803
  return process.env;
3800
3804
  }
3805
+ function loadUpdateEnvConfig() {
3806
+ return {
3807
+ xdgCacheHome: process.env["XDG_CACHE_HOME"] ?? "",
3808
+ localAppData: process.env["LOCALAPPDATA"] ?? ""
3809
+ };
3810
+ }
3801
3811
  //#endregion
3802
3812
  //#region src/lib/abort-active-attempt.ts
3803
3813
  /** Best-effort signal cleanup; lease expiry remains the final backstop. */
@@ -8475,7 +8485,7 @@ var RunManager = class {
8475
8485
  };
8476
8486
  }
8477
8487
  /** Assemble child env + args for a run. Exposed for tests. */
8478
- async prepare(spec, agent, piDir, providers = this.store.readProviders()) {
8488
+ async prepare(spec, agent, piDir, providers = this.store.readProviders(), runtimeModule) {
8479
8489
  const { activation, config } = agent;
8480
8490
  const homeDir = join(dirname(piDir), "home");
8481
8491
  const env = {
@@ -8499,6 +8509,7 @@ var RunManager = class {
8499
8509
  };
8500
8510
  })();
8501
8511
  const args = [
8512
+ ...runtimeModule ? ["--runtime", runtimeModule] : [],
8502
8513
  spec.mode,
8503
8514
  "--agent",
8504
8515
  target.agentName,
@@ -8569,7 +8580,8 @@ var RunManager = class {
8569
8580
  const runDir = this.store.runDir(id);
8570
8581
  const piDir = join(runDir, "pi");
8571
8582
  const providers = this.store.readProviders();
8572
- const { args, env, cwd } = await this.prepare(spec, agent, piDir, providers);
8583
+ const runtimeModule = this.options.resolveRuntimeModule ? await this.options.resolveRuntimeModule(spec, agent, dirname(piDir)) : this.options.runtimeRegistry ? await this.resolveRuntimeModule(spec, agent, dirname(piDir)) : void 0;
8584
+ const { args, env, cwd } = await this.prepare(spec, agent, piDir, providers, runtimeModule);
8573
8585
  this.assertStartOpen(signal);
8574
8586
  let child;
8575
8587
  let logStream;
@@ -8700,6 +8712,46 @@ var RunManager = class {
8700
8712
  throw cause;
8701
8713
  }
8702
8714
  }
8715
+ async resolveRuntimeModule(spec, activated, cwd) {
8716
+ const profiles = await resolveRuntimeProfiles({
8717
+ agent: await this.connectActivatedAgent(activated),
8718
+ profiles: spec.profiles,
8719
+ teamId: spec.teamId,
8720
+ cwd
8721
+ });
8722
+ const kinds = [...new Set(profiles.map((profile) => profile.runtimeKind))];
8723
+ if (kinds.length !== 1) throw new AgentServerRunError("invalid_spec", "All profiles in a server-managed run must use the same runtime kind.");
8724
+ const kind = kinds[0];
8725
+ let registration;
8726
+ try {
8727
+ registration = this.options.runtimeRegistry?.resolve(kind);
8728
+ } catch (cause) {
8729
+ this.log("warn", "registered runtime unavailable locally", {
8730
+ runtimeKind: kind,
8731
+ ...runtimeRegistryFailure(cause)
8732
+ });
8733
+ throw new AgentServerRunError("invalid_spec", `Registered runtime "${kind}" is unavailable locally; re-register it before starting a run.`);
8734
+ }
8735
+ if (registration) return registration.moduleUrl;
8736
+ if (kind === "gondolin_pi") return void 0;
8737
+ throw new AgentServerRunError("invalid_spec", `No local runtime is registered for profile kind "${kind}".`);
8738
+ }
8739
+ async connectActivatedAgent(activated) {
8740
+ const { activation, config } = activated;
8741
+ if (activation.source === "managed") {
8742
+ const agentKey = await resolveAgentKey(config, this.options.secretProviders);
8743
+ if (!agentKey) throw new AgentServerRunError("invalid_spec", `managed agent "${activation.alias}" has no agent key`);
8744
+ return connect({
8745
+ agentKey,
8746
+ apiUrl: activation.apiUrl
8747
+ });
8748
+ }
8749
+ return connect({
8750
+ configDir: dirname(activation.configPath),
8751
+ apiUrl: activation.apiUrl ?? activation.configApiUrl,
8752
+ secretProviders: this.options.externalSecretProviders
8753
+ });
8754
+ }
8703
8755
  stop(id) {
8704
8756
  const record = this.store.readRun(id);
8705
8757
  if (!record) throw new AgentServerRunError("run_not_found", `run "${id}" was not found`);
@@ -8882,6 +8934,14 @@ function safeRunError(error) {
8882
8934
  ...typeof code === "string" ? { errorCode: code } : {}
8883
8935
  };
8884
8936
  }
8937
+ function runtimeRegistryFailure(error) {
8938
+ const code = error?.code;
8939
+ return {
8940
+ errorType: error instanceof Error ? error.name : typeof error,
8941
+ ...typeof code === "string" ? { errorCode: code } : {},
8942
+ ...error instanceof Error ? { errorMessage: error.message.slice(0, 512) } : {}
8943
+ };
8944
+ }
8885
8945
  function sanitizeChildBaseEnv(baseEnv) {
8886
8946
  return Object.fromEntries(Object.entries(baseEnv).filter(([name]) => INHERITED_ENV_NAMES.has(name) || INHERITED_MOLTNET_ENV_NAMES.has(name)));
8887
8947
  }
@@ -8897,6 +8957,168 @@ async function terminateChild(child) {
8897
8957
  child.kill("SIGKILL");
8898
8958
  });
8899
8959
  }
8960
+ //#endregion
8961
+ //#region src/runtime-loader.ts
8962
+ function extractRuntimeModule(argv) {
8963
+ const remaining = [];
8964
+ let specifier;
8965
+ let parsingOptions = true;
8966
+ for (let index = 0; index < argv.length; index += 1) {
8967
+ const argument = argv[index];
8968
+ if (argument === "--") {
8969
+ parsingOptions = false;
8970
+ remaining.push(argument);
8971
+ continue;
8972
+ }
8973
+ if (!parsingOptions) {
8974
+ remaining.push(argument);
8975
+ continue;
8976
+ }
8977
+ let candidate;
8978
+ if (argument === "--runtime") {
8979
+ candidate = argv[index + 1];
8980
+ if (!candidate) throw new Error("Missing value for --runtime");
8981
+ index += 1;
8982
+ } else if (argument.startsWith("--runtime=")) {
8983
+ candidate = argument.slice(10);
8984
+ if (!candidate) throw new Error("Missing value for --runtime");
8985
+ } else {
8986
+ remaining.push(argument);
8987
+ continue;
8988
+ }
8989
+ if (specifier) throw new Error("--runtime may be specified only once");
8990
+ specifier = candidate;
8991
+ }
8992
+ return {
8993
+ argv: remaining,
8994
+ specifier
8995
+ };
8996
+ }
8997
+ function resolveRuntimeModuleUrl(specifier, cwd = process.cwd()) {
8998
+ if (specifier.startsWith("file:")) return new URL(specifier).href;
8999
+ if (isAbsolute(specifier) || specifier.startsWith("./") || specifier.startsWith("../")) return pathToFileURL(resolve(cwd, specifier)).href;
9000
+ return pathToFileURL(createRequire(resolve(cwd, "package.json")).resolve(specifier)).href;
9001
+ }
9002
+ async function loadDaemonRuntimeAdapter(specifier, options = {}) {
9003
+ let moduleUrl;
9004
+ try {
9005
+ moduleUrl = resolveRuntimeModuleUrl(specifier, options.cwd);
9006
+ } catch (cause) {
9007
+ throw new Error(`Unable to resolve runtime module "${specifier}" from ${options.cwd ?? process.cwd()}`, { cause });
9008
+ }
9009
+ let namespace;
9010
+ try {
9011
+ namespace = await import(moduleUrl);
9012
+ } catch (cause) {
9013
+ throw new Error(`Unable to load runtime module "${specifier}"`, { cause });
9014
+ }
9015
+ const adapter = namespace.default;
9016
+ if (!isDaemonRuntimeAdapter(adapter)) throw new Error(`Runtime module "${specifier}" must default-export a DaemonRuntimeAdapter`);
9017
+ return adapter;
9018
+ }
9019
+ function isDaemonRuntimeAdapter(value) {
9020
+ if (!value || typeof value !== "object") return false;
9021
+ const candidate = value;
9022
+ return typeof candidate.runtimeKind === "string" && candidate.runtimeKind.length > 0 && typeof candidate.prepare === "function";
9023
+ }
9024
+ //#endregion
9025
+ //#region src/lib/agent-server/runtime-registry.ts
9026
+ var LOCKFILE_NAMES = [
9027
+ "pnpm-lock.yaml",
9028
+ "package-lock.json",
9029
+ "yarn.lock",
9030
+ "bun.lockb"
9031
+ ];
9032
+ /** Local, operator-owned allowlist for executable daemon runtime modules. */
9033
+ var RuntimeRegistry = class {
9034
+ constructor(root) {
9035
+ this.root = root;
9036
+ }
9037
+ get path() {
9038
+ return join(this.root, "runtime-registry.json");
9039
+ }
9040
+ list() {
9041
+ if (!existsSync(this.path)) return [];
9042
+ const value = JSON.parse(readFileSync(this.path, "utf8"));
9043
+ if (!Array.isArray(value) || !value.every(isRegistration)) throw new AgentServerStoreError("invalid_state", "runtime registry contains an invalid registration");
9044
+ return value;
9045
+ }
9046
+ async register(kind, specifier, cwd = process.cwd()) {
9047
+ kind = assertStoreName("runtime kind", kind);
9048
+ mkdirSync(this.root, {
9049
+ recursive: true,
9050
+ mode: 448
9051
+ });
9052
+ const moduleUrl = resolveRuntimeModuleUrl(specifier, cwd);
9053
+ const adapter = await loadDaemonRuntimeAdapter(specifier, { cwd });
9054
+ if (adapter.runtimeKind !== kind) throw new Error(`Runtime module provides "${adapter.runtimeKind}", not registered kind "${kind}".`);
9055
+ if (!moduleUrl.startsWith("file:")) throw new Error("Runtime registration must resolve to a local file URL.");
9056
+ const entryHash = hashFile(new URL(moduleUrl));
9057
+ const lockfilePath = isPackageSpecifier(specifier) ? findLockfile(cwd) : void 0;
9058
+ if (isPackageSpecifier(specifier) && !lockfilePath) throw new Error("Package runtime registration requires a pnpm, npm, Yarn, or Bun lockfile in the current project or a parent directory.");
9059
+ const entry = {
9060
+ kind,
9061
+ moduleUrl,
9062
+ entryHash,
9063
+ ...lockfilePath ? {
9064
+ lockfilePath,
9065
+ lockfileHash: hashPath(lockfilePath)
9066
+ } : {},
9067
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
9068
+ };
9069
+ const entries = this.list().filter((candidate) => candidate.kind !== kind);
9070
+ entries.push(entry);
9071
+ writeRegistry(this.path, entries);
9072
+ return entry;
9073
+ }
9074
+ unregister(kind) {
9075
+ kind = assertStoreName("runtime kind", kind);
9076
+ const entries = this.list();
9077
+ const next = entries.filter((entry) => entry.kind !== kind);
9078
+ if (next.length === entries.length) return false;
9079
+ writeRegistry(this.path, next);
9080
+ return true;
9081
+ }
9082
+ resolve(kind) {
9083
+ kind = assertStoreName("runtime kind", kind);
9084
+ const entry = this.list().find((candidate) => candidate.kind === kind);
9085
+ if (!entry) return void 0;
9086
+ if (hashFile(new URL(entry.moduleUrl)) !== entry.entryHash) throw new Error(`Registered runtime "${kind}" has changed; re-register it before starting a run.`);
9087
+ if (entry.lockfilePath && (!existsSync(entry.lockfilePath) || hashPath(entry.lockfilePath) !== entry.lockfileHash)) throw new Error(`Registered runtime "${kind}" lockfile has changed; re-register it before starting a run.`);
9088
+ return entry;
9089
+ }
9090
+ };
9091
+ function hashFile(url) {
9092
+ return createHash("sha256").update(readFileSync(url)).digest("hex");
9093
+ }
9094
+ function hashPath(path) {
9095
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
9096
+ }
9097
+ function isPackageSpecifier(specifier) {
9098
+ return !specifier.startsWith("file:") && !specifier.startsWith(".") && !specifier.startsWith("/");
9099
+ }
9100
+ function findLockfile(cwd) {
9101
+ let current = resolve(cwd);
9102
+ for (;;) {
9103
+ for (const name of LOCKFILE_NAMES) {
9104
+ const candidate = join(current, name);
9105
+ if (existsSync(candidate)) return candidate;
9106
+ }
9107
+ const parent = dirname(current);
9108
+ if (parent === current) return void 0;
9109
+ current = parent;
9110
+ }
9111
+ }
9112
+ function isRegistration(value) {
9113
+ if (!value || typeof value !== "object") return false;
9114
+ const entry = value;
9115
+ return typeof entry.kind === "string" && typeof entry.moduleUrl === "string" && entry.moduleUrl.startsWith("file:") && typeof entry.entryHash === "string" && typeof entry.registeredAt === "string" && (entry.lockfilePath === void 0 || typeof entry.lockfilePath === "string") && (entry.lockfileHash === void 0 || typeof entry.lockfileHash === "string") && entry.lockfilePath === void 0 === (entry.lockfileHash === void 0);
9116
+ }
9117
+ function writeRegistry(path, entries) {
9118
+ const temp = `${path}.${process.pid}.tmp`;
9119
+ writeFileSync(temp, `${JSON.stringify(entries, null, 2)}\n`, { mode: 384 });
9120
+ renameSync(temp, path);
9121
+ }
8900
9122
  var AgentServerModelDiscoveryError = class extends Error {
8901
9123
  name = "AgentServerModelDiscoveryError";
8902
9124
  constructor(code, message, statusCode, options) {
@@ -9968,7 +10190,8 @@ async function runAgentServer(argv) {
9968
10190
  secretProviders,
9969
10191
  externalSecretProviders,
9970
10192
  baseEnv: processEnvSnapshot(),
9971
- logger
10193
+ logger,
10194
+ runtimeRegistry: new RuntimeRegistry(store.root)
9972
10195
  });
9973
10196
  const app = buildAgentServer({
9974
10197
  store,
@@ -10233,15 +10456,128 @@ function parseLimit(raw) {
10233
10456
  return value;
10234
10457
  }
10235
10458
  //#endregion
10459
+ //#region src/lib/update.ts
10460
+ var UPDATE_MANIFEST_URL = "https://themolt.net/download/manifest.json";
10461
+ var UPDATE_CACHE_TTL_MS = 1440 * 60 * 1e3;
10462
+ var UPDATE_ERROR_CACHE_TTL_MS = 300 * 1e3;
10463
+ function detectDaemonInstallMethod(executable = process.argv[1] ?? "") {
10464
+ const path = executable.replaceAll("\\", "/");
10465
+ if (path.includes("/node_modules/@themoltnet/agent-daemon/")) return "npm";
10466
+ if (path.includes("/.local/share/moltnet/") || path.includes("/opt/moltnet/")) return "bundle";
10467
+ return "direct";
10468
+ }
10469
+ function daemonUpdateCommand(method) {
10470
+ if (method === "npm") return "npm install -g @themoltnet/agent-daemon@latest";
10471
+ return "curl -fsSL https://themolt.net/install/agent | sh";
10472
+ }
10473
+ async function checkDaemonUpdate(input) {
10474
+ const installMethod = detectDaemonInstallMethod(input.executable ?? process.argv[1] ?? "moltnet-agent");
10475
+ const result = {
10476
+ currentVersion: normalizeVersion(input.currentVersion),
10477
+ updateAvailable: false,
10478
+ installMethod,
10479
+ releaseUrl: "https://themolt.net/download",
10480
+ command: daemonUpdateCommand(installMethod)
10481
+ };
10482
+ const now = input.now ?? /* @__PURE__ */ new Date();
10483
+ if (!input.force) {
10484
+ const cached = await readCache();
10485
+ const ttl = cached?.latest ? UPDATE_CACHE_TTL_MS : UPDATE_ERROR_CACHE_TTL_MS;
10486
+ if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttl) {
10487
+ result.latestVersion = cached.latest;
10488
+ result.updateAvailable = compareVersions(cached.latest, result.currentVersion) > 0;
10489
+ return result;
10490
+ }
10491
+ }
10492
+ try {
10493
+ const response = await (input.fetchFn ?? fetch)(UPDATE_MANIFEST_URL, { signal: AbortSignal.timeout(5e3) });
10494
+ if (!response.ok) throw new Error(`manifest returned HTTP ${response.status}`);
10495
+ const latest = manifestVersion(await response.json());
10496
+ if (!latest) throw new Error("manifest has no valid agent version");
10497
+ await writeCache({
10498
+ checkedAt: now.toISOString(),
10499
+ latest
10500
+ }).catch(() => void 0);
10501
+ result.latestVersion = latest;
10502
+ result.updateAvailable = compareVersions(latest, result.currentVersion) > 0;
10503
+ return result;
10504
+ } catch (error) {
10505
+ await writeCache({
10506
+ checkedAt: now.toISOString(),
10507
+ error: error instanceof Error ? error.message : String(error)
10508
+ }).catch(() => void 0);
10509
+ throw new Error(`could not check for MoltNet agent updates: ${error instanceof Error ? error.message : String(error)}`);
10510
+ }
10511
+ }
10512
+ function manifestVersion(value) {
10513
+ if (!value || typeof value !== "object") return void 0;
10514
+ const agent = value.agent;
10515
+ if (!agent || typeof agent !== "object") return void 0;
10516
+ const version = agent.version;
10517
+ return typeof version === "string" && validVersion(version) ? normalizeVersion(version) : void 0;
10518
+ }
10519
+ function normalizeVersion(value) {
10520
+ return value.trim().replace(/^v/, "");
10521
+ }
10522
+ function validVersion(value) {
10523
+ return /^v?\d+\.\d+\.\d+$/.test(value);
10524
+ }
10525
+ function compareVersions(a, b) {
10526
+ if (!a || !validVersion(a) || !validVersion(b)) return 0;
10527
+ const left = normalizeVersion(a).split(".").map(Number);
10528
+ const right = normalizeVersion(b).split(".").map(Number);
10529
+ for (let i = 0; i < 3; i += 1) {
10530
+ const leftPart = left[i] ?? 0;
10531
+ const rightPart = right[i] ?? 0;
10532
+ if (leftPart > rightPart) return 1;
10533
+ if (leftPart < rightPart) return -1;
10534
+ }
10535
+ return 0;
10536
+ }
10537
+ function cachePath() {
10538
+ const env = loadUpdateEnvConfig();
10539
+ return join(env.xdgCacheHome || (platform() === "win32" ? env.localAppData || join(homedir(), "AppData", "Local") : join(homedir(), ".cache")), "moltnet", "updates", "agent.json");
10540
+ }
10541
+ async function readCache() {
10542
+ try {
10543
+ return JSON.parse(await readFile(cachePath(), "utf8"));
10544
+ } catch {
10545
+ return;
10546
+ }
10547
+ }
10548
+ async function writeCache(cache) {
10549
+ const path = cachePath();
10550
+ await mkdir(dirname(path), {
10551
+ recursive: true,
10552
+ mode: 448
10553
+ });
10554
+ await writeFile(path, JSON.stringify(cache), { mode: 384 });
10555
+ }
10556
+ //#endregion
10557
+ //#region src/version.ts
10558
+ var DAEMON_VERSION = "0.51.0";
10559
+ //#endregion
10236
10560
  //#region src/cli.ts
10237
10561
  async function runAgentDaemonCli(options) {
10238
10562
  const [subcommand, ...rest] = options.argv ?? process.argv.slice(2);
10563
+ if ([
10564
+ "poll",
10565
+ "drain",
10566
+ "serve"
10567
+ ].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
10568
+ if (update.updateAvailable) pino({ name: "moltnet.agent-daemon" }).info({
10569
+ event: "agent-daemon.update_available",
10570
+ update
10571
+ }, "A MoltNet agent update is available");
10572
+ }).catch(() => void 0);
10239
10573
  switch (subcommand) {
10240
10574
  case "poll": return runPoll(rest, options.runtime);
10241
10575
  case "once": return runOnce(rest, options.runtime);
10242
10576
  case "drain": return runDrain(rest, options.runtime);
10243
10577
  case "server": return runAgentServer(rest);
10244
10578
  case "sync-sessions": return runSyncSessions(rest);
10579
+ case "update": return runUpdate(rest);
10580
+ case "runtime": return runRuntime(rest);
10245
10581
  case "-h":
10246
10582
  case "--help":
10247
10583
  console.log(ROOT_USAGE);
@@ -10254,5 +10590,49 @@ async function runAgentDaemonCli(options) {
10254
10590
  return 1;
10255
10591
  }
10256
10592
  }
10593
+ function isWorkspaceInvocation() {
10594
+ return (process.argv[1] ?? "").replaceAll("\\", "/").includes("/apps/agent-daemon/src/");
10595
+ }
10596
+ async function runUpdate(argv) {
10597
+ if (argv[0] !== "check" || argv.slice(1).some((arg) => arg !== "--json")) {
10598
+ console.error("Usage: moltnet-agent update check [--json]");
10599
+ return 1;
10600
+ }
10601
+ try {
10602
+ const update = await checkDaemonUpdate({
10603
+ currentVersion: DAEMON_VERSION,
10604
+ force: true
10605
+ });
10606
+ if (argv.includes("--json")) console.log(JSON.stringify(update));
10607
+ else if (update.updateAvailable) console.log(`MoltNet agent ${update.latestVersion} is available (you have ${update.currentVersion}). Run: ${update.command}`);
10608
+ else console.log(`MoltNet agent ${update.currentVersion} is up to date.`);
10609
+ return 0;
10610
+ } catch (error) {
10611
+ console.error(error instanceof Error ? error.message : String(error));
10612
+ return 1;
10613
+ }
10614
+ }
10615
+ async function runRuntime(argv) {
10616
+ const config = loadAgentServerEnvConfig();
10617
+ const registry = new RuntimeRegistry(resolveAgentServerRoot({
10618
+ root: config.root,
10619
+ xdgConfigHome: config.xdgConfigHome
10620
+ }));
10621
+ const [command, ...rest] = argv;
10622
+ if (command === "list") {
10623
+ console.log(JSON.stringify(registry.list(), null, 2));
10624
+ return 0;
10625
+ }
10626
+ if (command === "unregister" && rest.length === 1) {
10627
+ if (!registry.unregister(rest[0])) throw new Error(`No runtime registered for "${rest[0]}".`);
10628
+ return 0;
10629
+ }
10630
+ if (command === "register" && rest.length === 2) {
10631
+ console.log(JSON.stringify(await registry.register(rest[0], rest[1]), null, 2));
10632
+ return 0;
10633
+ }
10634
+ console.error("Usage: moltnet-agent runtime <list|register <kind> <module>|unregister <kind>>");
10635
+ return 1;
10636
+ }
10257
10637
  //#endregion
10258
- export { runAgentDaemonCli };
10638
+ export { loadDaemonRuntimeAdapter as n, runAgentDaemonCli, extractRuntimeModule as t };
package/dist/main.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { defaultPiDaemonAdapter } from "./pi.js";
3
- import { runAgentDaemonCli } from "./cli.js";
4
- import { createRequire } from "node:module";
3
+ import { n as loadDaemonRuntimeAdapter, runAgentDaemonCli, t as extractRuntimeModule } from "./cli.js";
5
4
  import { registerInstrumentations } from "@opentelemetry/instrumentation";
6
5
  import { DnsInstrumentation } from "@opentelemetry/instrumentation-dns";
7
6
  import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
@@ -9,8 +8,6 @@ import { NetInstrumentation } from "@opentelemetry/instrumentation-net";
9
8
  import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
10
9
  import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
11
10
  import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
12
- import { isAbsolute, resolve } from "node:path";
13
- import { pathToFileURL } from "node:url";
14
11
  //#region ../../libs/observability/src/instrumentation.ts
15
12
  /**
16
13
  * Register OTel auto-instrumentation for common Node.js modules.
@@ -101,70 +98,6 @@ function installSupervisorParentGuard(proc = process) {
101
98
  return true;
102
99
  }
103
100
  //#endregion
104
- //#region src/runtime-loader.ts
105
- function extractRuntimeModule(argv) {
106
- const remaining = [];
107
- let specifier;
108
- let parsingOptions = true;
109
- for (let index = 0; index < argv.length; index += 1) {
110
- const argument = argv[index];
111
- if (argument === "--") {
112
- parsingOptions = false;
113
- remaining.push(argument);
114
- continue;
115
- }
116
- if (!parsingOptions) {
117
- remaining.push(argument);
118
- continue;
119
- }
120
- let candidate;
121
- if (argument === "--runtime") {
122
- candidate = argv[index + 1];
123
- if (!candidate) throw new Error("Missing value for --runtime");
124
- index += 1;
125
- } else if (argument.startsWith("--runtime=")) {
126
- candidate = argument.slice(10);
127
- if (!candidate) throw new Error("Missing value for --runtime");
128
- } else {
129
- remaining.push(argument);
130
- continue;
131
- }
132
- if (specifier) throw new Error("--runtime may be specified only once");
133
- specifier = candidate;
134
- }
135
- return {
136
- argv: remaining,
137
- specifier
138
- };
139
- }
140
- function resolveRuntimeModuleUrl(specifier, cwd = process.cwd()) {
141
- if (specifier.startsWith("file:")) return new URL(specifier).href;
142
- if (isAbsolute(specifier) || specifier.startsWith("./") || specifier.startsWith("../")) return pathToFileURL(resolve(cwd, specifier)).href;
143
- return pathToFileURL(createRequire(resolve(cwd, "package.json")).resolve(specifier)).href;
144
- }
145
- async function loadDaemonRuntimeAdapter(specifier, options = {}) {
146
- let moduleUrl;
147
- try {
148
- moduleUrl = resolveRuntimeModuleUrl(specifier, options.cwd);
149
- } catch (cause) {
150
- throw new Error(`Unable to resolve runtime module "${specifier}" from ${options.cwd ?? process.cwd()}`, { cause });
151
- }
152
- let namespace;
153
- try {
154
- namespace = await import(moduleUrl);
155
- } catch (cause) {
156
- throw new Error(`Unable to load runtime module "${specifier}"`, { cause });
157
- }
158
- const adapter = namespace.default;
159
- if (!isDaemonRuntimeAdapter(adapter)) throw new Error(`Runtime module "${specifier}" must default-export a DaemonRuntimeAdapter`);
160
- return adapter;
161
- }
162
- function isDaemonRuntimeAdapter(value) {
163
- if (!value || typeof value !== "object") return false;
164
- const candidate = value;
165
- return typeof candidate.runtimeKind === "string" && candidate.runtimeKind.length > 0 && typeof candidate.prepare === "function";
166
- }
167
- //#endregion
168
101
  //#region src/main.ts
169
102
  installSupervisorParentGuard();
170
103
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.",
@@ -84,9 +84,9 @@
84
84
  "proper-lockfile": "4.1.2",
85
85
  "typebox": "^1.2.8",
86
86
  "@themoltnet/agent-runtime": "0.45.2",
87
+ "@themoltnet/pi-runtime": "0.14.1",
87
88
  "@themoltnet/os-keyring": "0.3.0",
88
- "@themoltnet/sdk": "0.140.0",
89
- "@themoltnet/pi-runtime": "0.14.1"
89
+ "@themoltnet/sdk": "0.140.0"
90
90
  },
91
91
  "devDependencies": {
92
92
  "@fastify/swagger": "^9.6.1",
@@ -97,15 +97,15 @@
97
97
  "vite-plugin-dts": "^4.5.4",
98
98
  "vitest": "^3.0.0",
99
99
  "@moltnet/agent-eval": "0.1.0",
100
- "@moltnet/crypto-service": "0.1.0",
101
100
  "@moltnet/bootstrap": "0.1.0",
101
+ "@moltnet/crypto-service": "0.1.0",
102
+ "@moltnet/execution-plan": "0.1.0",
102
103
  "@moltnet/execution-integrations": "0.1.0",
104
+ "@moltnet/models": "0.1.0",
103
105
  "@moltnet/loopback-companion": "0.1.0",
104
- "@moltnet/execution-plan": "0.1.0",
106
+ "@moltnet/tasks": "0.1.0",
105
107
  "@moltnet/observability": "0.1.0",
106
- "@moltnet/models": "0.1.0",
107
- "@moltnet/runtime-profiles": "0.1.0",
108
- "@moltnet/tasks": "0.1.0"
108
+ "@moltnet/runtime-profiles": "0.1.0"
109
109
  },
110
110
  "nx": {
111
111
  "projectType": "application",