@themoltnet/agent-daemon 0.62.0 → 0.63.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/README.md CHANGED
@@ -649,10 +649,36 @@ Server configuration root. **Reset to release defaults** clears those overrides
649
649
  when applied; it does not delete agent credentials. Launch environment values
650
650
  have priority and appear read-only. Operators who explicitly set both
651
651
  `MOLTNET_OPERATOR_API_URL` and `MOLTNET_OPERATOR_OAUTH_ISSUER` should also use
652
- `MOLTNET_AGENT_SERVER_ROOT` to select a dedicated environment root.
652
+ `MOLTNET_HOME` to select a dedicated environment root.
653
653
 
654
654
  Release defaults use `https://api.themolt.net`, `https://auth.themolt.net`, and
655
655
  public client IDs `moltnet-native` / `moltnet-console`. Hosted client registration
656
656
  and coordinated configuration are tracked in
657
657
  [moltnet-operations #8](https://github.com/getlarge/moltnet-operations/issues/8).
658
658
  These IDs are public configuration; they do not contain a client secret.
659
+
660
+ ### Isolated local development
661
+
662
+ Use the development targets to keep development state and installations in
663
+ separate directories:
664
+
665
+ ```bash
666
+ pnpm exec nx run @moltnet/agent-desktop:tauri:dev
667
+ pnpm exec nx run @themoltnet/agent-daemon:dev -- server
668
+ ```
669
+
670
+ The launchers choose `~/.local/share/moltnet/development/<worktree-id>/store`
671
+ and the sibling `agent` installation directory. The worktree ID is a stable
672
+ hash of the canonical checkout path. Repeated launches reuse that environment;
673
+ another worktree gets its own environment. Use `MOLTNET_DEV_HOME` and `MOLTNET_DEV_AGENT_HOME` to override these development
674
+ locations. Use absolute overrides: relative paths start at the Nx target’s
675
+ working directory (`apps/agent-daemon` for daemon `dev`, the repository root
676
+ for Desktop `tauri:dev`). All daemon `dev` commands, including `poll`, use
677
+ this isolated store.
678
+ The launchers ignore inherited production store and installation selectors.
679
+ The renderer-only Desktop `dev` target starts Vite without a native server.
680
+
681
+ For direct CLI or SDK development commands, set `MOLTNET_HOME` explicitly. See
682
+ [Store selection and keyring namespaces](../../docs/reference/agent-configuration.md#store-selection-and-keyring-namespaces)
683
+ for precedence, secret namespaces and daemon discovery. Automated tests use fresh
684
+ temporary roots.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAaA,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,CA6ClB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAaA,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,CA+ClB"}
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import "typebox/value";
11
11
  import { execFile, spawn } from "node:child_process";
12
12
  import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
13
13
  import { parseArgs, parseEnv, promisify } from "node:util";
14
- import { CredentialPersistenceError, EnrollmentRecoveryError, FILE_SECRET_PROVIDER, FileSecretProvider, ProjectConfigError, ProvisioningNotStartedError, RegisterIdentityError, WORKSPACE_STRATEGIES, boundedIdentitySignal, canonicalDirectory, connect, createNodeSecretProviderRegistry, enrollTeam, getProjectConfigPath, readProjectConfig, register, resolveProjectBinding } from "@themoltnet/sdk/node";
14
+ import { CredentialPersistenceError, EnrollmentRecoveryError, FILE_SECRET_PROVIDER, FileSecretProvider, ProjectConfigError, ProvisioningNotStartedError, RegisterIdentityError, WORKSPACE_STRATEGIES, boundedIdentitySignal, canonicalDirectory, canonicalStoreRoot, connect, createNodeSecretProviderRegistry, defaultStoreRoot, enrollTeam, getProjectConfigPath, isDefaultStore, readProjectConfig, register, resolveProjectBinding, resolveStoreRoot, resolveStoreSelection } from "@themoltnet/sdk/node";
15
15
  import { constants, copyFileSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
16
16
  import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, formatSecretReferenceString, getConfigDir, getIdentityDir, hasAgentKeyConfiguration, isCanonicalConfig, parseSecretReferenceString, readConfig, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, selectAgentKeyReference, signBytes } from "@themoltnet/sdk";
17
17
  import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, RuntimeProfilePrerequisiteError, createLocalSeedSigner, resolveAgentIdentity, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
@@ -3757,6 +3757,10 @@ async function resolveSelectionApiUrl(agentName, options) {
3757
3757
  * here, so the rest of the daemon imports typed values rather than
3758
3758
  * sprinkling string lookups across the codebase.
3759
3759
  */
3760
+ /** All daemon commands announce the same legacy alias policy as Go and Desktop. */
3761
+ function legacyStoreNotice() {
3762
+ return process.env["MOLTNET_AGENT_SERVER_ROOT"] !== void 0 ? "MOLTNET_AGENT_SERVER_ROOT is deprecated; use MOLTNET_HOME. Both select the entire store; no data is migrated." : void 0;
3763
+ }
3760
3764
  function loadConfig() {
3761
3765
  assertSingleCredentialForm("MOLTNET_AGENT_KEY", "MOLTNET_AGENT_KEY_REF");
3762
3766
  assertSingleCredentialForm("MOLTNET_PRIVATE_KEY", "MOLTNET_PRIVATE_KEY_REF");
@@ -3767,7 +3771,7 @@ function loadConfig() {
3767
3771
  profilePrerequisiteEnv: process.env,
3768
3772
  profilePrerequisitePath: process.env.PATH ?? "",
3769
3773
  piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
3770
- agentServerRoot: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3774
+ agentServerRoot: resolveStoreRoot(),
3771
3775
  credentialSource: detectCredentialSource(process.env),
3772
3776
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3773
3777
  signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
@@ -3815,7 +3819,8 @@ function activatePiCodingAgentDir(path, env = {}) {
3815
3819
  process.env["PI_CODING_AGENT_DIR"] = path;
3816
3820
  Object.assign(process.env, env);
3817
3821
  }
3818
- function loadAgentServerEnvConfig() {
3822
+ function loadAgentServerEnvConfig(root) {
3823
+ const selection = resolveStoreSelection({ root });
3819
3824
  const issuer = process.env["MOLTNET_OPERATOR_OAUTH_ISSUER"];
3820
3825
  const publicUrl = process.env["MOLTNET_OPERATOR_OAUTH_PUBLIC_URL"] ?? issuer;
3821
3826
  const nativeClientId = process.env["MOLTNET_NATIVE_OAUTH_CLIENT_ID"];
@@ -3831,7 +3836,8 @@ function loadAgentServerEnvConfig() {
3831
3836
  },
3832
3837
  port: process.env["MOLTNET_AGENT_SERVER_PORT"] ?? "",
3833
3838
  allowedOrigins: process.env["MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS"] ?? "",
3834
- root: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3839
+ root: selection.root,
3840
+ rootSource: selection.source,
3835
3841
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3836
3842
  logLevel: process.env["LOG_LEVEL"] ?? "",
3837
3843
  activeIdentity: process.env["MOLTNET_ACTIVE_IDENTITY"] ?? ""
@@ -3841,8 +3847,9 @@ function loadAgentServerEnvConfig() {
3841
3847
  function processEnvSnapshot() {
3842
3848
  return process.env;
3843
3849
  }
3844
- function loadUpdateEnvConfig() {
3850
+ function loadUpdateEnvConfig(root) {
3845
3851
  return {
3852
+ ...!isDefaultStore({ root }) ? { storeRoot: resolveStoreRoot({ root }) } : {},
3846
3853
  xdgCacheHome: process.env["XDG_CACHE_HOME"] ?? "",
3847
3854
  localAppData: process.env["LOCALAPPDATA"] ?? ""
3848
3855
  };
@@ -4172,14 +4179,14 @@ Agent Desktop instead uses a private Unix socket with a process-scoped grant.
4172
4179
  Both modes configure agents and providers and start/stop child runs.
4173
4180
 
4174
4181
  Options:
4175
- --port <n> Loopback port. Default: 17374.
4182
+ --port <n> Loopback port. Default store: 17374; isolated stores: an available port.
4176
4183
  Env: MOLTNET_AGENT_SERVER_PORT.
4177
4184
  --allowed-origins <csv> Exact browser-controller origins allowed
4178
4185
  local control.
4179
4186
  Default: https://console.themolt.net.
4180
4187
  Env: MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS.
4181
4188
  --root <path> Config root. Default: ~/.config/moltnet
4182
- (or MOLTNET_AGENT_SERVER_ROOT).
4189
+ (or MOLTNET_HOME; legacy MOLTNET_AGENT_SERVER_ROOT).
4183
4190
  --api-url <url> Default MoltNet API for new managed agents.
4184
4191
  Default: https://api.themolt.net.
4185
4192
  --heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
@@ -4208,8 +4215,8 @@ Usage:
4208
4215
  [--root <path>]
4209
4216
  moltnet-agent providers logout <id> [--yes] [--root <path>]
4210
4217
 
4211
- The default root is ~/.config/moltnet. MOLTNET_AGENT_SERVER_ROOT remains the
4212
- environment override. API keys are accepted only from redirected stdin; they
4218
+ The default root is ~/.config/moltnet. MOLTNET_HOME selects another store.
4219
+ MOLTNET_AGENT_SERVER_ROOT is a legacy alias; conflicting values fail. API keys are accepted only from redirected stdin; they
4213
4220
  are stored separately and providers.json contains only a secret reference.
4214
4221
 
4215
4222
  --model declares a text-only model. --model-input declares a model together
@@ -5655,20 +5662,9 @@ var AgentServerStoreError = class extends Error {
5655
5662
  this.code = code;
5656
5663
  }
5657
5664
  };
5658
- /**
5659
- * `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
5660
- *
5661
- * Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
5662
- * and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
5663
- * so honouring XDG here gave one application two config roots: on a machine
5664
- * with the variable set, the daemon wrote identities the CLI and SDK could not
5665
- * read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
5666
- * genuinely custom location.
5667
- */
5665
+ /** Explicit root, MOLTNET_HOME, legacy alias, then the unchanged default store. */
5668
5666
  function resolveAgentServerRoot(input) {
5669
- const override = input.root?.trim();
5670
- if (override) return override;
5671
- return getConfigDir();
5667
+ return getConfigDir(input);
5672
5668
  }
5673
5669
  /**
5674
5670
  * Copy a model entry, dropping an empty `input` so it never reaches the wire.
@@ -6102,7 +6098,7 @@ async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
6102
6098
  };
6103
6099
  }
6104
6100
  const repoPiDir = join(agentRoot, ".pi");
6105
- const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
6101
+ const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot || void 0 }));
6106
6102
  const providers = store.readProviders();
6107
6103
  const storeHasAuth = existsSync(store.piAuthJsonPath);
6108
6104
  if (Object.keys(providers).length === 0 && !storeHasAuth) {
@@ -6183,7 +6179,7 @@ function mergePiModels(store, repo) {
6183
6179
  //#endregion
6184
6180
  //#region src/lib/state-dir.ts
6185
6181
  function ensureDaemonStateDirs(mountPath) {
6186
- const rootDir = canonicalStatePath(join(mountPath, ".moltnet", "d"));
6182
+ const rootDir = canonicalStoreRoot(join(mountPath, ".moltnet", "d"));
6187
6183
  const piSessionsDir = join(rootDir, "pi-sessions");
6188
6184
  mkdirSync(rootDir, {
6189
6185
  recursive: true,
@@ -6198,17 +6194,6 @@ function ensureDaemonStateDirs(mountPath) {
6198
6194
  piSessionsDir: realpathSync(piSessionsDir)
6199
6195
  };
6200
6196
  }
6201
- function canonicalStatePath(path) {
6202
- let parent = resolve(path);
6203
- const missing = [];
6204
- while (!existsSync(parent)) {
6205
- missing.unshift(basename(parent));
6206
- const next = dirname(parent);
6207
- if (next === parent) throw new Error(`Cannot resolve daemon state root ${path}`);
6208
- parent = next;
6209
- }
6210
- return join(realpathSync(parent), ...missing);
6211
- }
6212
6197
  //#endregion
6213
6198
  //#region src/lib/prepare-runtime-profile.ts
6214
6199
  /** Validate and prepare a profile through the shared daemon execution path. */
@@ -8865,12 +8850,13 @@ function parseProviderArgs(command, args) {
8865
8850
  }
8866
8851
  }
8867
8852
  async function createContext(rootFlag, dependencies, installInterruptHandler) {
8868
- const store = new AgentServerStore(resolveAgentServerRoot({ root: rootFlag ?? dependencies.envRoot ?? loadAgentServerEnvConfig().root })).ensure();
8853
+ const root = resolveAgentServerRoot({ root: rootFlag ?? dependencies.envRoot ?? loadAgentServerEnvConfig().root });
8854
+ const store = new AgentServerStore(root).ensure();
8869
8855
  const secrets = new FileSecretProvider({
8870
8856
  root: store.secretsDir,
8871
8857
  writable: true
8872
8858
  });
8873
- const secretProviders = createNodeSecretProviderRegistry().register(secrets);
8859
+ const secretProviders = createNodeSecretProviderRegistry({ store: { root } }).register(secrets);
8874
8860
  const stderr = dependencies.stderr ?? console.error;
8875
8861
  const logger = createCliLogger(stderr);
8876
8862
  const configuration = dependencies.configuration ?? new ProviderConfigurationService({
@@ -9358,11 +9344,67 @@ var ConnectionSettingsStore = class {
9358
9344
  };
9359
9345
  /** A custom service gets its own identities, keys and runtime configuration. */
9360
9346
  function connectionStateRoot(root, settings) {
9361
- const identity = [settings.apiUrl.replace(/\/$/u, ""), settings.issuer.replace(/\/$/u, "")];
9347
+ const identity = [settings.apiUrl.replace(/\/+$/u, ""), settings.issuer.replace(/\/+$/u, "")];
9362
9348
  if (identity[0] === RELEASE_CONNECTION.apiUrl && identity[1] === RELEASE_CONNECTION.issuer) return root;
9363
9349
  return join(root, "environments", createHash("sha256").update(JSON.stringify(identity)).digest("hex"));
9364
9350
  }
9365
9351
  //#endregion
9352
+ //#region src/lib/agent-server/endpoint.ts
9353
+ function defaultAgentServerPort(root) {
9354
+ return isDefaultStore({ root }) ? OPERATOR_OAUTH.serverPort : 0;
9355
+ }
9356
+ function endpointPath(root) {
9357
+ return join(root, "agent-server-endpoint.json");
9358
+ }
9359
+ function validateUrl(value) {
9360
+ const url = new URL(value);
9361
+ if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || url.port === "0" || url.username || url.password || url.pathname !== "/" || url.search || url.hash || !/^http:\/\/127\.0\.0\.1(?::[1-9]\d{0,4})?$/.test(value)) throw new Error("Agent Server discovery requires a loopback HTTP origin");
9362
+ }
9363
+ function readAgentServerEndpoint(root) {
9364
+ let raw;
9365
+ try {
9366
+ raw = readFileSync(endpointPath(root), "utf8");
9367
+ } catch (cause) {
9368
+ if (cause.code === "ENOENT") return null;
9369
+ throw cause;
9370
+ }
9371
+ try {
9372
+ const value = JSON.parse(raw);
9373
+ if (value.version !== 1 || typeof value.instanceId !== "string" || value.instanceId.length !== 36 || !/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/iu.test(value.instanceId) || typeof value.url !== "string") throw new Error("Invalid Agent Server discovery metadata");
9374
+ validateUrl(value.url);
9375
+ if (value.pid !== void 0) {
9376
+ if (!Number.isSafeInteger(value.pid) || value.pid <= 0 || value.pid > 2147483647) throw new Error("Invalid process ID");
9377
+ try {
9378
+ process.kill(value.pid, 0);
9379
+ } catch (cause) {
9380
+ if (cause.code === "ESRCH") return null;
9381
+ }
9382
+ }
9383
+ return value;
9384
+ } catch (cause) {
9385
+ throw new Error(`Invalid Agent Server discovery at ${endpointPath(root)}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
9386
+ }
9387
+ }
9388
+ /** Publish and release only while holding the selected store's singleton lock. */
9389
+ function publishAgentServerEndpoint(root, url) {
9390
+ validateUrl(url);
9391
+ const endpoint = {
9392
+ version: 1,
9393
+ instanceId: randomUUID(),
9394
+ pid: process.pid,
9395
+ url
9396
+ };
9397
+ writeJsonAtomic(endpointPath(root), endpoint);
9398
+ return {
9399
+ record: endpoint,
9400
+ release() {
9401
+ try {
9402
+ if (readAgentServerEndpoint(root)?.instanceId === endpoint.instanceId) rmSync(endpointPath(root), { force: true });
9403
+ } catch {}
9404
+ }
9405
+ };
9406
+ }
9407
+ //#endregion
9366
9408
  //#region src/lib/agent-server/lock.ts
9367
9409
  var AgentServerLockError = class extends Error {
9368
9410
  name = "AgentServerLockError";
@@ -9396,7 +9438,14 @@ async function acquireAgentServerLock(root, options = {}) {
9396
9438
  }
9397
9439
  });
9398
9440
  } catch (cause) {
9399
- if (cause.code === "ELOCKED") throw new AgentServerLockError("held", `another moltnet-agent server process already owns ${path}`, { cause });
9441
+ if (cause.code === "ELOCKED") {
9442
+ let holder = "";
9443
+ try {
9444
+ const endpoint = readAgentServerEndpoint(root);
9445
+ if (endpoint?.pid) holder = ` (PID ${endpoint.pid}, instance ${endpoint.instanceId})`;
9446
+ } catch {}
9447
+ throw new AgentServerLockError("held", `another moltnet-agent server process already owns ${path}${holder}; stop that process before retrying`, { cause });
9448
+ }
9400
9449
  throw new AgentServerLockError("failed", `could not acquire Agent Server lock ${path}: ${cause.message}`, { cause });
9401
9450
  }
9402
9451
  let released = false;
@@ -9411,11 +9460,15 @@ async function acquireAgentServerLock(root, options = {}) {
9411
9460
  }
9412
9461
  /** Always release after normal completion or startup/runtime failure. */
9413
9462
  async function withAgentServerLock(root, work, options) {
9414
- const held = await acquireAgentServerLock(root, options);
9463
+ const held = [];
9415
9464
  try {
9465
+ held.push(await acquireAgentServerLock(root, options));
9466
+ if (options?.stateRoot && options.stateRoot !== root) held.push(await acquireAgentServerLock(options.stateRoot, options));
9416
9467
  return await work();
9417
9468
  } finally {
9418
- await held.release();
9469
+ for (const entry of held.reverse()) await entry.release().catch((error) => {
9470
+ console.error(`Could not release Agent Server lock ${entry.path}: ${String(error)}`);
9471
+ });
9419
9472
  }
9420
9473
  }
9421
9474
  //#endregion
@@ -9450,6 +9503,10 @@ function applyNativeClientGrant(options) {
9450
9503
  nativeGrant.grantNative(token);
9451
9504
  return true;
9452
9505
  }
9506
+ //#endregion
9507
+ //#region src/lib/agent-server/native-grant-service.ts
9508
+ /** Process-only native grant. Browser authorization belongs to OperatorOAuth. */
9509
+ var NATIVE_CLIENT_ORIGIN = "moltnet-agent-desktop://native";
9453
9510
  var NativeGrantError = class extends Error {
9454
9511
  code = "native_token_invalid";
9455
9512
  };
@@ -10585,6 +10642,8 @@ var RunManager = class {
10585
10642
  const homeDir = join(dirname(piDir), "home");
10586
10643
  const env = {
10587
10644
  HOME: homeDir,
10645
+ MOLTNET_DEFAULT_STORE_ROOT: defaultStoreRoot({ env: this.options.baseEnv }),
10646
+ MOLTNET_HOME: getConfigDir({ root: this.options.storeRoot ?? this.store.root }),
10588
10647
  PI_CODING_AGENT_DIR: piDir,
10589
10648
  XDG_CACHE_HOME: join(homeDir, ".cache"),
10590
10649
  XDG_CONFIG_HOME: join(homeDir, ".config"),
@@ -10628,7 +10687,7 @@ var RunManager = class {
10628
10687
  env["MOLTNET_AGENT_KEY"] = requireCredentialSnapshot(agent).agentKey;
10629
10688
  env["MOLTNET_API_URL"] = activation.apiUrl ?? (activation.source === "external" ? activation.configApiUrl : "");
10630
10689
  if (activation.source === "managed") {
10631
- if (!config.keys.private_key_ref) throw new AgentServerRunError("invalid_spec", "The managed signing key reference is missing");
10690
+ if (config.keys.private_key_ref?.provider !== "file") throw new AgentServerRunError("invalid_store", "The managed identity store is inconsistent: its signing key must be file-backed. Restore the identity store or use credential recovery.");
10632
10691
  env["MOLTNET_PRIVATE_KEY_REF"] = formatSecretReferenceString(config.keys.private_key_ref);
10633
10692
  env["MOLTNET_SECRET_ROOT"] = this.store.secretsDir;
10634
10693
  } else try {
@@ -11231,6 +11290,20 @@ function writeRegistry(path, entries) {
11231
11290
  renameSync(temp, path);
11232
11291
  }
11233
11292
  //#endregion
11293
+ //#region src/lib/agent-server/secret-providers.ts
11294
+ function createAgentServerSecretProviders(settings, store) {
11295
+ const secrets = new FileSecretProvider({
11296
+ root: store.secretsDir,
11297
+ writable: true
11298
+ });
11299
+ const registry = () => createNodeSecretProviderRegistry({ store: { root: settings.root } });
11300
+ return {
11301
+ secrets,
11302
+ secretProviders: registry().register(secrets),
11303
+ externalSecretProviders: registry()
11304
+ };
11305
+ }
11306
+ //#endregion
11234
11307
  //#region src/lib/agent-server/readiness.ts
11235
11308
  /**
11236
11309
  * Whether a runtime profile can execute on *this* machine.
@@ -11997,7 +12070,8 @@ function requestOperationSignal(request, shutdownSignal) {
11997
12070
  else request.raw.once("aborted", () => disconnected.abort({ source: "request" }));
11998
12071
  return shutdownSignal ? AbortSignal.any([disconnected.signal, shutdownSignal]) : disconnected.signal;
11999
12072
  }
12000
- function buildAgentServer(options) {
12073
+ function buildAgentServer(input) {
12074
+ const options = { ...input };
12001
12075
  const { nativeGrant } = options;
12002
12076
  const oauth = options.operatorOAuth;
12003
12077
  let restartRequired = false;
@@ -12006,12 +12080,13 @@ function buildAgentServer(options) {
12006
12080
  ...fastifyOptions,
12007
12081
  loggerInstance: options.logger
12008
12082
  }) : Fastify(fastifyOptions);
12083
+ app.addHook("onListen", async () => {
12084
+ const address = app.server.address();
12085
+ if (address && typeof address !== "string") options.selfOrigin = `http://127.0.0.1:${address.port}`;
12086
+ });
12009
12087
  options.registerOpenApi?.(app);
12010
12088
  for (const schema of AGENT_SERVER_SCHEMAS) app.addSchema(schema);
12011
- const browserOrigins = new OriginAllowlist(options.allowedOrigins);
12012
- registerLoopbackSecurity(app, {
12013
- isOriginAllowed: (origin) => origin === "moltnet-agent-desktop://native" || browserOrigins.has(origin),
12014
- ...options.selfOrigin ? { selfOrigins: [options.selfOrigin] } : {},
12089
+ const securityOptions = {
12015
12090
  allowedHeaders: [AGENT_SERVER_TOKEN_HEADER],
12016
12091
  methods: [
12017
12092
  "GET",
@@ -12020,7 +12095,18 @@ function buildAgentServer(options) {
12020
12095
  "DELETE",
12021
12096
  "OPTIONS"
12022
12097
  ]
12098
+ };
12099
+ if (options.nativeOnly) registerLoopbackSecurity(app, {
12100
+ ...securityOptions,
12101
+ isOriginAllowed: (origin) => origin === NATIVE_CLIENT_ORIGIN
12023
12102
  });
12103
+ else {
12104
+ const browserOrigins = new OriginAllowlist(options.allowedOrigins);
12105
+ registerLoopbackSecurity(app, {
12106
+ ...securityOptions,
12107
+ isOriginAllowed: (origin) => origin === "moltnet-agent-desktop://native" || origin === options.selfOrigin || browserOrigins.has(origin)
12108
+ });
12109
+ }
12024
12110
  let verificationWindow = Date.now();
12025
12111
  let verifications = 0;
12026
12112
  const browserVerification = /* @__PURE__ */ new WeakMap();
@@ -12670,7 +12756,7 @@ function normalizeAgentServerError(error) {
12670
12756
  message: error.message
12671
12757
  };
12672
12758
  if (error instanceof AgentServerRunError) return {
12673
- statusCode: error.code === "run_not_found" ? 404 : 400,
12759
+ statusCode: error.code === "run_not_found" ? 404 : error.code === "invalid_store" ? 409 : 400,
12674
12760
  code: error.code,
12675
12761
  message: error.message
12676
12762
  };
@@ -12709,7 +12795,6 @@ function normalizeAgentServerError(error) {
12709
12795
  * waits for an authorized controller to configure agents/providers and
12710
12796
  * start or stop runs.
12711
12797
  */
12712
- var DEFAULT_PORT = OPERATOR_OAUTH.serverPort;
12713
12798
  var DEFAULT_ALLOWED_ORIGINS = "https://console.themolt.net";
12714
12799
  var SHUTDOWN_TIMEOUT_MS = 15e3;
12715
12800
  var LOCK_HELD_EXIT_CODE = 75;
@@ -12734,7 +12819,6 @@ async function runAgentServer(argv) {
12734
12819
  console.log(AGENT_SERVER_HELP);
12735
12820
  return 0;
12736
12821
  }
12737
- const envConfig = loadAgentServerEnvConfig();
12738
12822
  const { values } = parseArgs({
12739
12823
  args: argv,
12740
12824
  options: {
@@ -12748,6 +12832,7 @@ async function runAgentServer(argv) {
12748
12832
  "native-socket": { type: "string" }
12749
12833
  }
12750
12834
  });
12835
+ const envConfig = loadAgentServerEnvConfig(values.root);
12751
12836
  const nativeSocket = values["native-socket"];
12752
12837
  const nativeSocketError = validateNativeSocketOptions(nativeSocketValidationOptions({
12753
12838
  ...nativeSocket ? { nativeSocket } : {},
@@ -12761,13 +12846,14 @@ async function runAgentServer(argv) {
12761
12846
  console.error(nativeSocketError);
12762
12847
  return 1;
12763
12848
  }
12764
- const port = Number.parseInt(values.port ?? (envConfig.port || `${DEFAULT_PORT}`), 10);
12765
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
12849
+ const port = Number.parseInt(values.port ?? (envConfig.port || `${defaultAgentServerPort(envConfig.root)}`), 10);
12850
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
12766
12851
  console.error(`Invalid --port: ${String(values.port)}`);
12767
12852
  return 1;
12768
12853
  }
12769
12854
  const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
12770
- const connectionSettings = new ConnectionSettingsStore(values.root ?? resolveAgentServerRoot({ root: envConfig.root }), {
12855
+ const settingsRoot = envConfig.root;
12856
+ const connectionSettings = new ConnectionSettingsStore(settingsRoot, {
12771
12857
  ...envConfig.operatorOAuth,
12772
12858
  ...values["api-url"] || envConfig.apiUrl ? { apiUrl: values["api-url"] || envConfig.apiUrl } : {}
12773
12859
  });
@@ -12782,13 +12868,8 @@ async function runAgentServer(argv) {
12782
12868
  });
12783
12869
  try {
12784
12870
  try {
12785
- return await withAgentServerLock(root, async () => {
12786
- const secrets = new FileSecretProvider({
12787
- root: store.secretsDir,
12788
- writable: true
12789
- });
12790
- const secretProviders = createNodeSecretProviderRegistry().register(secrets);
12791
- const externalSecretProviders = createNodeSecretProviderRegistry();
12871
+ return await withAgentServerLock(settingsRoot, async () => {
12872
+ const { secrets, secretProviders, externalSecretProviders } = createAgentServerSecretProviders(connectionSettings, store);
12792
12873
  const nativeGrant = new NativeGrantService();
12793
12874
  const nativeClient = applyNativeClientGrant({
12794
12875
  nativeGrant,
@@ -12812,6 +12893,7 @@ async function runAgentServer(argv) {
12812
12893
  const runtimeRegistry = new RuntimeRegistry(store.root);
12813
12894
  const runs = new RunManager({
12814
12895
  store,
12896
+ storeRoot: settingsRoot,
12815
12897
  secretProviders,
12816
12898
  externalSecretProviders,
12817
12899
  baseEnv: processEnvSnapshot(),
@@ -12852,14 +12934,20 @@ async function runAgentServer(argv) {
12852
12934
  logger,
12853
12935
  shutdownSignal: shutdownController.signal
12854
12936
  });
12937
+ let endpoint;
12855
12938
  try {
12856
12939
  const address = await app.listen(nativeSocket ? { path: nativeSocket } : {
12857
12940
  host: "127.0.0.1",
12858
12941
  port
12859
12942
  });
12860
12943
  if (nativeSocket) await chmod(nativeSocket, 384);
12944
+ if (!nativeSocket) {
12945
+ endpoint = publishAgentServerEndpoint(settingsRoot, address);
12946
+ console.error(`discovery: ${join(settingsRoot, "agent-server-endpoint.json")}`);
12947
+ }
12861
12948
  console.error(`moltnet-agent server listening on ${address}`);
12862
- console.error(`config root: ${root}`);
12949
+ console.error(`store root: ${settingsRoot} (${envConfig.rootSource})`);
12950
+ console.error(`connection state: ${root}`);
12863
12951
  if (nativeSocket) console.error(`native control socket: ${nativeSocket}`);
12864
12952
  else console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
12865
12953
  if (nativeClient) console.error("native desktop client: authorized");
@@ -12868,12 +12956,17 @@ async function runAgentServer(argv) {
12868
12956
  } catch (cause) {
12869
12957
  await app.close().catch(() => void 0);
12870
12958
  throw cause;
12959
+ } finally {
12960
+ endpoint?.release();
12871
12961
  }
12872
- }, { onCompromised: (error) => {
12873
- console.error(error.message);
12874
- process.exitCode = 1;
12875
- process.kill(process.pid, "SIGTERM");
12876
- } });
12962
+ }, {
12963
+ stateRoot: root,
12964
+ onCompromised: (error) => {
12965
+ console.error(error.message);
12966
+ process.exitCode = 1;
12967
+ process.kill(process.pid, "SIGTERM");
12968
+ }
12969
+ });
12877
12970
  } catch (cause) {
12878
12971
  if (cause instanceof AgentServerLockError) {
12879
12972
  console.error(cause.message);
@@ -13278,6 +13371,7 @@ function compareVersions(a, b) {
13278
13371
  }
13279
13372
  function cachePath() {
13280
13373
  const env = loadUpdateEnvConfig();
13374
+ if (env.storeRoot) return join(env.storeRoot, "cache", "updates", "agent.json");
13281
13375
  return join(env.xdgCacheHome || (platform() === "win32" ? env.localAppData || join(homedir(), "AppData", "Local") : join(homedir(), ".cache")), "moltnet", "updates", "agent.json");
13282
13376
  }
13283
13377
  async function readCache() {
@@ -13297,16 +13391,14 @@ async function writeCache(cache) {
13297
13391
  }
13298
13392
  //#endregion
13299
13393
  //#region src/version.ts
13300
- var DAEMON_VERSION = "0.62.0";
13394
+ var DAEMON_VERSION = "0.63.0";
13301
13395
  //#endregion
13302
13396
  //#region src/cli.ts
13303
13397
  async function runAgentDaemonCli(options) {
13398
+ const notice = legacyStoreNotice();
13399
+ if (notice) console.error(notice);
13304
13400
  const [subcommand, ...rest] = options.argv ?? process.argv.slice(2);
13305
- if ([
13306
- "poll",
13307
- "drain",
13308
- "serve"
13309
- ].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
13401
+ if (["poll", "drain"].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
13310
13402
  if (update.updateAvailable) pino({ name: "moltnet.agent-daemon" }).info({
13311
13403
  event: "agent-daemon.update_available",
13312
13404
  update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.62.0",
3
+ "version": "0.63.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,10 +84,10 @@
84
84
  "pino-pretty": "^13.1.3",
85
85
  "proper-lockfile": "4.1.2",
86
86
  "typebox": "^1.2.8",
87
- "@themoltnet/agent-runtime": "1.2.0",
88
- "@themoltnet/pi-runtime": "0.18.2",
89
- "@themoltnet/os-keyring": "0.3.0",
90
- "@themoltnet/sdk": "0.144.0"
87
+ "@themoltnet/agent-runtime": "1.3.0",
88
+ "@themoltnet/pi-runtime": "0.18.3",
89
+ "@themoltnet/sdk": "0.145.0",
90
+ "@themoltnet/os-keyring": "0.4.0"
91
91
  },
92
92
  "devDependencies": {
93
93
  "@fastify/swagger": "^9.6.1",
@@ -97,15 +97,15 @@
97
97
  "vite": "^8.0.0",
98
98
  "vite-plugin-dts": "^4.5.4",
99
99
  "vitest": "^3.0.0",
100
- "@moltnet/api-client": "0.1.0",
101
100
  "@moltnet/agent-eval": "0.1.0",
102
- "@moltnet/bootstrap": "0.1.0",
101
+ "@moltnet/api-client": "0.1.0",
103
102
  "@moltnet/crypto-service": "0.1.0",
104
103
  "@moltnet/execution-integrations": "0.1.0",
104
+ "@moltnet/execution-plan": "0.1.0",
105
+ "@moltnet/loopback-companion": "0.1.0",
106
+ "@moltnet/bootstrap": "0.1.0",
105
107
  "@moltnet/models": "0.1.0",
106
108
  "@moltnet/observability": "0.1.0",
107
- "@moltnet/loopback-companion": "0.1.0",
108
- "@moltnet/execution-plan": "0.1.0",
109
109
  "@moltnet/runtime-profiles": "0.1.0",
110
110
  "@moltnet/tasks": "0.1.0"
111
111
  },
@@ -137,6 +137,8 @@
137
137
  "inputs": [
138
138
  "default",
139
139
  "^production",
140
+ "{workspaceRoot}/test-fixtures/agent-server-endpoint.json",
141
+ "{workspaceRoot}/test-fixtures/connection-state-root.json",
140
142
  "{workspaceRoot}/docs/operate/*.md",
141
143
  "{workspaceRoot}/docs/.vitepress/config.ts"
142
144
  ]
@@ -145,7 +147,7 @@
145
147
  },
146
148
  "scripts": {
147
149
  "cli": "tsx src/main.ts",
148
- "dev": "tsx watch src/main.ts",
150
+ "dev": "tsx ../../tools/src/desktop-development.ts tsx watch src/main.ts",
149
151
  "start": "node dist/main.js",
150
152
  "build": "vite build",
151
153
  "generate:openapi": "tsx scripts/generate-openapi.ts",