@themoltnet/agent-daemon 0.61.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
@@ -15,17 +15,18 @@ from [themolt.net/download](https://themolt.net/download). Opening the app:
15
15
 
16
16
  1. installs or updates the publisher-verified Agent CLI bundle under
17
17
  `~/.local/share/moltnet/agent`;
18
- 2. asks before adding the per-user local HTTPS CA to the login Keychain;
19
- 3. starts a foreground, supervised Agent Server; and
20
- 4. opens Console for OAuth PKCE approval and runtime management.
18
+ 2. starts a foreground, supervised Agent Server over a private native socket;
19
+ and
20
+ 3. opens the browser only when OAuth approval is required.
21
21
 
22
22
  Closing the status window hides it. **Quit and Stop Server** stops the owned
23
- server process before the app exits. After the server restarts, reconnect Console through OAuth PKCE. The local
24
- operator, identities, and provider configuration persist.
23
+ server process before the app exits. Desktop re-establishes its private native
24
+ control channel after a server restart. The local operator, identities, and
25
+ provider configuration persist.
25
26
 
26
27
  Agent CLI updates and desktop-app updates use independent signed channels and
27
28
  always require consent. Removing the Agent CLI bundle preserves
28
- `~/.config/moltnet`. Removing the local CA is a separate opt-in action.
29
+ `~/.config/moltnet`.
29
30
 
30
31
  ### Agent CLI
31
32
 
@@ -123,8 +124,8 @@ For config-based runs, the agent's `moltnet.json` lives in the central store
123
124
  (`~/.config/moltnet/identities/<agent>/`) or, for an external config, next to
124
125
  its gitconfig in `.moltnet/<agent>/`. Three paths create it:
125
126
 
126
- - The Console's Local Runtime page creates a managed agent through the Agent
127
- Server: keypair and agent key generated on this machine, stored under the
127
+ - MoltNet Agent creates a managed agent through the Agent Server: keypair and
128
+ agent key generated on this machine, stored under the
128
129
  store's `secrets/` directory, no CLI involved.
129
130
  - [`moltnet register`](../../docs/start/install-and-initialize.md#register-an-agent)
130
131
  creates an OAuth2 identity from the CLI; add a stored agent key with
@@ -209,7 +210,7 @@ carries them -- read access to the agent's teams and their diaries, and
209
210
  enrollment into a team. Startup does not check for them: a key without them
210
211
  claims and runs work, and the daemon reports which are absent.
211
212
 
212
- The Console selects the full set by default. Knowledge-enabled workers must add
213
+ Desktop selects the full set by default. Knowledge-enabled workers must add
213
214
  `diary:write`, `pack:read`, and `pack:write` when the key is issued.
214
215
 
215
216
  `crypto:sign` is in the minimum because host-capability signing runs on the
@@ -648,10 +649,36 @@ Server configuration root. **Reset to release defaults** clears those overrides
648
649
  when applied; it does not delete agent credentials. Launch environment values
649
650
  have priority and appear read-only. Operators who explicitly set both
650
651
  `MOLTNET_OPERATOR_API_URL` and `MOLTNET_OPERATOR_OAUTH_ISSUER` should also use
651
- `MOLTNET_AGENT_SERVER_ROOT` to select a dedicated environment root.
652
+ `MOLTNET_HOME` to select a dedicated environment root.
652
653
 
653
654
  Release defaults use `https://api.themolt.net`, `https://auth.themolt.net`, and
654
655
  public client IDs `moltnet-native` / `moltnet-console`. Hosted client registration
655
656
  and coordinated configuration are tracked in
656
657
  [moltnet-operations #8](https://github.com/getlarge/moltnet-operations/issues/8).
657
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,12 +11,12 @@ 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";
18
18
  import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
19
- import { X509Certificate, createHash, createPrivateKey, createPublicKey, randomBytes, randomUUID, timingSafeEqual, webcrypto } from "node:crypto";
19
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
20
20
  import { once } from "node:events";
21
21
  import { metrics } from "@opentelemetry/api";
22
22
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
@@ -28,7 +28,7 @@ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
28
28
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
29
29
  import { homedir, platform, tmpdir } from "node:os";
30
30
  import { PI_MODEL_MODALITIES, writePiConfig } from "@themoltnet/pi-runtime/pi-config";
31
- import { mkdir, open, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
31
+ import { chmod, lstat, mkdir, open, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
32
32
  import { AsyncLocalStorage } from "node:async_hooks";
33
33
  import { pipeline } from "node:stream/promises";
34
34
  import { createInterface } from "node:readline/promises";
@@ -45,8 +45,6 @@ import { pathToFileURL } from "node:url";
45
45
  import { StringDecoder } from "node:string_decoder";
46
46
  import rateLimit from "@fastify/rate-limit";
47
47
  import Fastify from "fastify";
48
- import "reflect-metadata";
49
- import { BasicConstraintsExtension, ExtendedKeyUsage, ExtendedKeyUsageExtension, IP, KeyUsageFlags, KeyUsagesExtension, SubjectAlternativeNameExtension, X509CertificateGenerator } from "@peculiar/x509";
50
48
  import { createGzip } from "node:zlib";
51
49
  //#region ../../libs/tasks/src/rubric.ts
52
50
  /**
@@ -3759,6 +3757,10 @@ async function resolveSelectionApiUrl(agentName, options) {
3759
3757
  * here, so the rest of the daemon imports typed values rather than
3760
3758
  * sprinkling string lookups across the codebase.
3761
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
+ }
3762
3764
  function loadConfig() {
3763
3765
  assertSingleCredentialForm("MOLTNET_AGENT_KEY", "MOLTNET_AGENT_KEY_REF");
3764
3766
  assertSingleCredentialForm("MOLTNET_PRIVATE_KEY", "MOLTNET_PRIVATE_KEY_REF");
@@ -3769,7 +3771,7 @@ function loadConfig() {
3769
3771
  profilePrerequisiteEnv: process.env,
3770
3772
  profilePrerequisitePath: process.env.PATH ?? "",
3771
3773
  piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
3772
- agentServerRoot: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3774
+ agentServerRoot: resolveStoreRoot(),
3773
3775
  credentialSource: detectCredentialSource(process.env),
3774
3776
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3775
3777
  signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
@@ -3817,7 +3819,8 @@ function activatePiCodingAgentDir(path, env = {}) {
3817
3819
  process.env["PI_CODING_AGENT_DIR"] = path;
3818
3820
  Object.assign(process.env, env);
3819
3821
  }
3820
- function loadAgentServerEnvConfig() {
3822
+ function loadAgentServerEnvConfig(root) {
3823
+ const selection = resolveStoreSelection({ root });
3821
3824
  const issuer = process.env["MOLTNET_OPERATOR_OAUTH_ISSUER"];
3822
3825
  const publicUrl = process.env["MOLTNET_OPERATOR_OAUTH_PUBLIC_URL"] ?? issuer;
3823
3826
  const nativeClientId = process.env["MOLTNET_NATIVE_OAUTH_CLIENT_ID"];
@@ -3833,7 +3836,8 @@ function loadAgentServerEnvConfig() {
3833
3836
  },
3834
3837
  port: process.env["MOLTNET_AGENT_SERVER_PORT"] ?? "",
3835
3838
  allowedOrigins: process.env["MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS"] ?? "",
3836
- root: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3839
+ root: selection.root,
3840
+ rootSource: selection.source,
3837
3841
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3838
3842
  logLevel: process.env["LOG_LEVEL"] ?? "",
3839
3843
  activeIdentity: process.env["MOLTNET_ACTIVE_IDENTITY"] ?? ""
@@ -3843,21 +3847,22 @@ function loadAgentServerEnvConfig() {
3843
3847
  function processEnvSnapshot() {
3844
3848
  return process.env;
3845
3849
  }
3846
- function loadUpdateEnvConfig() {
3850
+ function loadUpdateEnvConfig(root) {
3847
3851
  return {
3852
+ ...!isDefaultStore({ root }) ? { storeRoot: resolveStoreRoot({ root }) } : {},
3848
3853
  xdgCacheHome: process.env["XDG_CACHE_HOME"] ?? "",
3849
3854
  localAppData: process.env["LOCALAPPDATA"] ?? ""
3850
3855
  };
3851
3856
  }
3852
3857
  //#endregion
3853
3858
  //#region src/lib/run-project-selection.ts
3854
- var execFileAsync$2 = promisify(execFile);
3859
+ var execFileAsync$1 = promisify(execFile);
3855
3860
  var GIT_TIMEOUT_MS = 1e4;
3856
3861
  var GIT_MAX_OUTPUT_BYTES = 64 * 1024;
3857
3862
  async function validateGitSource(source) {
3858
3863
  const inherited = processEnvSnapshot();
3859
3864
  try {
3860
- const { stdout } = await execFileAsync$2("git", [
3865
+ const { stdout } = await execFileAsync$1("git", [
3861
3866
  "rev-parse",
3862
3867
  "--show-toplevel",
3863
3868
  "--verify",
@@ -4015,11 +4020,9 @@ Commands:
4015
4020
  once Claim and execute one specific queued task by id, then exit.
4016
4021
  drain Poll until the queue has nothing claimable, then exit.
4017
4022
  Useful for batch eval runs and demos.
4018
- server Loopback supervisor for console-managed runs: OAuth local control,
4019
- agent/provider config store, and start/stop of poll/drain
4020
- child processes. Binds 127.0.0.1 only.
4021
- server trust
4022
- Install the per-user macOS local-HTTPS CA after explicit consent.
4023
+ server Supervisor for managed runs: authorized standalone local control,
4024
+ agent/provider config store, and start/stop of poll/drain child
4025
+ processes. Binds 127.0.0.1 or a private native socket.
4023
4026
  providers Manage configured endpoints and Pi OAuth subscriptions without
4024
4027
  starting the Agent Server. See \`agent-daemon providers --help\`.
4025
4028
  sync-sessions
@@ -4169,33 +4172,33 @@ function isHelpFlag(args) {
4169
4172
  return args.includes("--help") || args.includes("-h");
4170
4173
  }
4171
4174
  var AGENT_SERVER_HELP = `\
4172
- agent-daemon server — loopback supervisor for console-managed runs.
4175
+ agent-daemon server — local supervisor for managed runs.
4173
4176
 
4174
- Binds 127.0.0.1 only. An authorized Console origin configures agents and
4175
- providers (secret references only) and starts/stops poll/drain runs as
4176
- child processes of this supervisor.
4177
+ Standalone mode binds 127.0.0.1 for authorized local-control clients. MoltNet
4178
+ Agent Desktop instead uses a private Unix socket with a process-scoped grant.
4179
+ Both modes configure agents and providers and start/stop child runs.
4177
4180
 
4178
4181
  Options:
4179
- --port <n> Loopback port. Default: 17374.
4182
+ --port <n> Loopback port. Default store: 17374; isolated stores: an available port.
4180
4183
  Env: MOLTNET_AGENT_SERVER_PORT.
4181
- --allowed-origins <csv> Exact Console origins allowed local control.
4184
+ --allowed-origins <csv> Exact browser-controller origins allowed
4185
+ local control.
4182
4186
  Default: https://console.themolt.net.
4183
4187
  Env: MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS.
4184
4188
  --root <path> Config root. Default: ~/.config/moltnet
4185
- (or MOLTNET_AGENT_SERVER_ROOT).
4189
+ (or MOLTNET_HOME; legacy MOLTNET_AGENT_SERVER_ROOT).
4186
4190
  --api-url <url> Default MoltNet API for new managed agents.
4187
4191
  Default: https://api.themolt.net.
4188
4192
  --heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
4189
4193
  --warm-retention-sec <n> Child session/workspace retention. Default: 1800.
4190
4194
  --supervised Also stop gracefully when stdin reaches EOF.
4195
+ --native-socket <path> Private native-only socket (requires --supervised).
4196
+ Absolute, at most 100 bytes, with a new socket in
4197
+ a caller-owned 0700 directory. TCP flags are not
4198
+ accepted; inherited TCP env settings are ignored.
4191
4199
 
4192
- On macOS, the first interactive run asks to trust a per-user local CA in the
4193
- login keychain and serves HTTPS. Native supervisors use:
4194
- server trust --status --json
4195
- server trust --yes --json
4196
- server trust --remove --yes --json
4197
- Run \`agent-daemon server trust --remove\` interactively to remove that exact
4198
- CA. Linux continues to use the Chromium PNA HTTP path.
4200
+ Standalone mode uses loopback HTTP on every platform. Desktop socket mode does
4201
+ not open a TCP listener.
4199
4202
  `;
4200
4203
  var PROVIDERS_HELP = `\
4201
4204
  moltnet-agent providers — manage local model providers.
@@ -4212,8 +4215,8 @@ Usage:
4212
4215
  [--root <path>]
4213
4216
  moltnet-agent providers logout <id> [--yes] [--root <path>]
4214
4217
 
4215
- The default root is ~/.config/moltnet. MOLTNET_AGENT_SERVER_ROOT remains the
4216
- 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
4217
4220
  are stored separately and providers.json contains only a secret reference.
4218
4221
 
4219
4222
  --model declares a text-only model. --model-input declares a model together
@@ -4258,7 +4261,7 @@ async function resolveDaemonAgentIdentity(input) {
4258
4261
  }
4259
4262
  //#endregion
4260
4263
  //#region src/lib/correlation.ts
4261
- var execFileAsync$1 = promisify(execFile);
4264
+ var execFileAsync = promisify(execFile);
4262
4265
  var CORRELATION_TRAILER_KEY = "Moltnet-Correlation-Id";
4263
4266
  var CORRELATION_MARKER_RE = /<!--\s*moltnet-correlation:\s*([\w-]+)\s*-->/i;
4264
4267
  new RegExp(`^${CORRELATION_TRAILER_KEY}:\\s*(\\S+)\\s*$`, "m");
@@ -4305,7 +4308,7 @@ function makePrBodyAnchorWriter(deps) {
4305
4308
  function createGhCliClient() {
4306
4309
  return {
4307
4310
  async get({ owner, repo, number }) {
4308
- const { stdout } = await execFileAsync$1("gh", [
4311
+ const { stdout } = await execFileAsync("gh", [
4309
4312
  "api",
4310
4313
  `repos/${owner}/${repo}/pulls/${number}`,
4311
4314
  "--jq",
@@ -4314,7 +4317,7 @@ function createGhCliClient() {
4314
4317
  return JSON.parse(stdout);
4315
4318
  },
4316
4319
  async patch({ owner, repo, number }, body) {
4317
- await execFileAsync$1("gh", [
4320
+ await execFileAsync("gh", [
4318
4321
  "api",
4319
4322
  "-X",
4320
4323
  "PATCH",
@@ -5659,20 +5662,9 @@ var AgentServerStoreError = class extends Error {
5659
5662
  this.code = code;
5660
5663
  }
5661
5664
  };
5662
- /**
5663
- * `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
5664
- *
5665
- * Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
5666
- * and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
5667
- * so honouring XDG here gave one application two config roots: on a machine
5668
- * with the variable set, the daemon wrote identities the CLI and SDK could not
5669
- * read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
5670
- * genuinely custom location.
5671
- */
5665
+ /** Explicit root, MOLTNET_HOME, legacy alias, then the unchanged default store. */
5672
5666
  function resolveAgentServerRoot(input) {
5673
- const override = input.root?.trim();
5674
- if (override) return override;
5675
- return getConfigDir();
5667
+ return getConfigDir(input);
5676
5668
  }
5677
5669
  /**
5678
5670
  * Copy a model entry, dropping an empty `input` so it never reaches the wire.
@@ -6106,7 +6098,7 @@ async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
6106
6098
  };
6107
6099
  }
6108
6100
  const repoPiDir = join(agentRoot, ".pi");
6109
- const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
6101
+ const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot || void 0 }));
6110
6102
  const providers = store.readProviders();
6111
6103
  const storeHasAuth = existsSync(store.piAuthJsonPath);
6112
6104
  if (Object.keys(providers).length === 0 && !storeHasAuth) {
@@ -6187,7 +6179,7 @@ function mergePiModels(store, repo) {
6187
6179
  //#endregion
6188
6180
  //#region src/lib/state-dir.ts
6189
6181
  function ensureDaemonStateDirs(mountPath) {
6190
- const rootDir = canonicalStatePath(join(mountPath, ".moltnet", "d"));
6182
+ const rootDir = canonicalStoreRoot(join(mountPath, ".moltnet", "d"));
6191
6183
  const piSessionsDir = join(rootDir, "pi-sessions");
6192
6184
  mkdirSync(rootDir, {
6193
6185
  recursive: true,
@@ -6202,17 +6194,6 @@ function ensureDaemonStateDirs(mountPath) {
6202
6194
  piSessionsDir: realpathSync(piSessionsDir)
6203
6195
  };
6204
6196
  }
6205
- function canonicalStatePath(path) {
6206
- let parent = resolve(path);
6207
- const missing = [];
6208
- while (!existsSync(parent)) {
6209
- missing.unshift(basename(parent));
6210
- const next = dirname(parent);
6211
- if (next === parent) throw new Error(`Cannot resolve daemon state root ${path}`);
6212
- parent = next;
6213
- }
6214
- return join(realpathSync(parent), ...missing);
6215
- }
6216
6197
  //#endregion
6217
6198
  //#region src/lib/prepare-runtime-profile.ts
6218
6199
  /** Validate and prepare a profile through the shared daemon execution path. */
@@ -8869,12 +8850,13 @@ function parseProviderArgs(command, args) {
8869
8850
  }
8870
8851
  }
8871
8852
  async function createContext(rootFlag, dependencies, installInterruptHandler) {
8872
- 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();
8873
8855
  const secrets = new FileSecretProvider({
8874
8856
  root: store.secretsDir,
8875
8857
  writable: true
8876
8858
  });
8877
- const secretProviders = createNodeSecretProviderRegistry().register(secrets);
8859
+ const secretProviders = createNodeSecretProviderRegistry({ store: { root } }).register(secrets);
8878
8860
  const stderr = dependencies.stderr ?? console.error;
8879
8861
  const logger = createCliLogger(stderr);
8880
8862
  const configuration = dependencies.configuration ?? new ProviderConfigurationService({
@@ -9362,11 +9344,67 @@ var ConnectionSettingsStore = class {
9362
9344
  };
9363
9345
  /** A custom service gets its own identities, keys and runtime configuration. */
9364
9346
  function connectionStateRoot(root, settings) {
9365
- const identity = [settings.apiUrl.replace(/\/$/u, ""), settings.issuer.replace(/\/$/u, "")];
9347
+ const identity = [settings.apiUrl.replace(/\/+$/u, ""), settings.issuer.replace(/\/+$/u, "")];
9366
9348
  if (identity[0] === RELEASE_CONNECTION.apiUrl && identity[1] === RELEASE_CONNECTION.issuer) return root;
9367
9349
  return join(root, "environments", createHash("sha256").update(JSON.stringify(identity)).digest("hex"));
9368
9350
  }
9369
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
9370
9408
  //#region src/lib/agent-server/lock.ts
9371
9409
  var AgentServerLockError = class extends Error {
9372
9410
  name = "AgentServerLockError";
@@ -9400,7 +9438,14 @@ async function acquireAgentServerLock(root, options = {}) {
9400
9438
  }
9401
9439
  });
9402
9440
  } catch (cause) {
9403
- 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
+ }
9404
9449
  throw new AgentServerLockError("failed", `could not acquire Agent Server lock ${path}: ${cause.message}`, { cause });
9405
9450
  }
9406
9451
  let released = false;
@@ -9415,11 +9460,15 @@ async function acquireAgentServerLock(root, options = {}) {
9415
9460
  }
9416
9461
  /** Always release after normal completion or startup/runtime failure. */
9417
9462
  async function withAgentServerLock(root, work, options) {
9418
- const held = await acquireAgentServerLock(root, options);
9463
+ const held = [];
9419
9464
  try {
9465
+ held.push(await acquireAgentServerLock(root, options));
9466
+ if (options?.stateRoot && options.stateRoot !== root) held.push(await acquireAgentServerLock(options.stateRoot, options));
9420
9467
  return await work();
9421
9468
  } finally {
9422
- 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
+ });
9423
9472
  }
9424
9473
  }
9425
9474
  //#endregion
@@ -9454,6 +9503,10 @@ function applyNativeClientGrant(options) {
9454
9503
  nativeGrant.grantNative(token);
9455
9504
  return true;
9456
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";
9457
9510
  var NativeGrantError = class extends Error {
9458
9511
  code = "native_token_invalid";
9459
9512
  };
@@ -9469,6 +9522,29 @@ var NativeGrantService = class {
9469
9522
  }
9470
9523
  };
9471
9524
  //#endregion
9525
+ //#region src/lib/agent-server/native-socket.ts
9526
+ /** The supervisor owns the private directory; the server never removes peers. */
9527
+ async function validateNativeSocket(path) {
9528
+ if (!isAbsolute(path) || Buffer.byteLength(path) > 100) throw new Error("Native socket must be an absolute path of at most 100 bytes");
9529
+ const parent = dirname(path);
9530
+ const metadata = await lstat(parent);
9531
+ if (metadata.isSymbolicLink()) throw new Error(`Native socket parent contains a symlink: ${parent}`);
9532
+ if (!metadata.isDirectory()) throw new Error(`Native socket parent is not a directory: ${parent}`);
9533
+ const expectedUid = process.getuid?.();
9534
+ if (expectedUid === void 0 || metadata.uid !== expectedUid) throw new Error(`Native socket parent has uid ${metadata.uid}; expected ${String(expectedUid)}: ${parent}`);
9535
+ const mode = metadata.mode & 511;
9536
+ if (mode !== 448) throw new Error(`Native socket parent has mode ${mode.toString(8)}; expected 700: ${parent}`);
9537
+ const canonical = await realpath(parent);
9538
+ if (canonical !== resolve(parent)) throw new Error(`Native socket parent contains a symlink: ${parent} resolves to ${canonical}`);
9539
+ try {
9540
+ await lstat(path);
9541
+ } catch (error) {
9542
+ if (error.code === "ENOENT") return;
9543
+ throw error;
9544
+ }
9545
+ throw new Error("Native socket path already exists; it was left untouched");
9546
+ }
9547
+ //#endregion
9472
9548
  //#region src/lib/agent-server/operator-oauth.ts
9473
9549
  var InvalidOperatorGrantError = class extends Error {};
9474
9550
  var LOCAL_SCOPE = OPERATOR_OAUTH.localControlScope;
@@ -9671,8 +9747,8 @@ var OperatorOAuth = class {
9671
9747
  /**
9672
9748
  * Subscription-provider OAuth brokering for Agent Server (#2061 slice 4).
9673
9749
  *
9674
- * The console clicks "Connect"; agent server runs the Pi OAuth flow host-side via
9675
- * `ModelRuntime.login()` (which owns persistence into the shared
9750
+ * An authorized controller starts the flow; Agent Server runs Pi OAuth
9751
+ * host-side via `ModelRuntime.login()` (which owns persistence into the shared
9676
9752
  * `pi/auth.json` and token rotation thereafter). The browser only ever sees
9677
9753
  * the provider's authorize URL or device code — never tokens.
9678
9754
  *
@@ -10566,6 +10642,8 @@ var RunManager = class {
10566
10642
  const homeDir = join(dirname(piDir), "home");
10567
10643
  const env = {
10568
10644
  HOME: homeDir,
10645
+ MOLTNET_DEFAULT_STORE_ROOT: defaultStoreRoot({ env: this.options.baseEnv }),
10646
+ MOLTNET_HOME: getConfigDir({ root: this.options.storeRoot ?? this.store.root }),
10569
10647
  PI_CODING_AGENT_DIR: piDir,
10570
10648
  XDG_CACHE_HOME: join(homeDir, ".cache"),
10571
10649
  XDG_CONFIG_HOME: join(homeDir, ".config"),
@@ -10609,7 +10687,7 @@ var RunManager = class {
10609
10687
  env["MOLTNET_AGENT_KEY"] = requireCredentialSnapshot(agent).agentKey;
10610
10688
  env["MOLTNET_API_URL"] = activation.apiUrl ?? (activation.source === "external" ? activation.configApiUrl : "");
10611
10689
  if (activation.source === "managed") {
10612
- 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.");
10613
10691
  env["MOLTNET_PRIVATE_KEY_REF"] = formatSecretReferenceString(config.keys.private_key_ref);
10614
10692
  env["MOLTNET_SECRET_ROOT"] = this.store.secretsDir;
10615
10693
  } else try {
@@ -11212,6 +11290,20 @@ function writeRegistry(path, entries) {
11212
11290
  renameSync(temp, path);
11213
11291
  }
11214
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
11215
11307
  //#region src/lib/agent-server/readiness.ts
11216
11308
  /**
11217
11309
  * Whether a runtime profile can execute on *this* machine.
@@ -11406,17 +11498,17 @@ async function enrollIdentityTeam(options) {
11406
11498
  /**
11407
11499
  * The identity-wide team/diary binding, read from `<identityDir>/env`.
11408
11500
  *
11409
- * This mirrors the Go CLI's `identityDefaultBinding` (`context_store.go:219`),
11501
+ * This mirrors the Go CLI's `identityDefaultBinding` (`project_selection.go`),
11410
11502
  * which is the fallback the CLI uses when a working directory has no
11411
- * location-keyed binding in `contexts.json`.
11503
+ * registered project binding.
11412
11504
  *
11413
11505
  * The desktop cannot use the CLI's *location* bindings at all — its composer
11414
11506
  * has no working directory to key on — but it can honour this identity-wide
11415
- * default, so an operator who ran `moltnet context set` sees their familiar
11416
- * team preselected rather than an arbitrary first entry.
11507
+ * default, so an operator sees their familiar team preselected rather than an
11508
+ * arbitrary first entry.
11417
11509
  *
11418
11510
  * Like the CLI, a half-filled pair is treated as no binding: a team without a
11419
- * diary is exactly the state `validateContextBinding` rejects.
11511
+ * diary is ignored.
11420
11512
  */
11421
11513
  function readIdentityDefaultBinding(identityDir) {
11422
11514
  let contents;
@@ -11978,24 +12070,23 @@ function requestOperationSignal(request, shutdownSignal) {
11978
12070
  else request.raw.once("aborted", () => disconnected.abort({ source: "request" }));
11979
12071
  return shutdownSignal ? AbortSignal.any([disconnected.signal, shutdownSignal]) : disconnected.signal;
11980
12072
  }
11981
- function buildAgentServer(options) {
12073
+ function buildAgentServer(input) {
12074
+ const options = { ...input };
11982
12075
  const { nativeGrant } = options;
11983
12076
  const oauth = options.operatorOAuth;
11984
12077
  let restartRequired = false;
11985
- const fastifyOptions = {
11986
- bodyLimit: BODY_LIMIT,
11987
- ...options.tls ? { https: options.tls } : {}
11988
- };
12078
+ const fastifyOptions = { bodyLimit: BODY_LIMIT };
11989
12079
  const app = options.logger ? Fastify({
11990
12080
  ...fastifyOptions,
11991
12081
  loggerInstance: options.logger
11992
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
+ });
11993
12087
  options.registerOpenApi?.(app);
11994
12088
  for (const schema of AGENT_SERVER_SCHEMAS) app.addSchema(schema);
11995
- const browserOrigins = new OriginAllowlist(options.allowedOrigins);
11996
- registerLoopbackSecurity(app, {
11997
- isOriginAllowed: (origin) => origin === "moltnet-agent-desktop://native" || browserOrigins.has(origin),
11998
- ...options.selfOrigin ? { selfOrigins: [options.selfOrigin] } : {},
12089
+ const securityOptions = {
11999
12090
  allowedHeaders: [AGENT_SERVER_TOKEN_HEADER],
12000
12091
  methods: [
12001
12092
  "GET",
@@ -12004,7 +12095,18 @@ function buildAgentServer(options) {
12004
12095
  "DELETE",
12005
12096
  "OPTIONS"
12006
12097
  ]
12098
+ };
12099
+ if (options.nativeOnly) registerLoopbackSecurity(app, {
12100
+ ...securityOptions,
12101
+ isOriginAllowed: (origin) => origin === NATIVE_CLIENT_ORIGIN
12007
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
+ }
12008
12110
  let verificationWindow = Date.now();
12009
12111
  let verifications = 0;
12010
12112
  const browserVerification = /* @__PURE__ */ new WeakMap();
@@ -12021,6 +12123,31 @@ function buildAgentServer(options) {
12021
12123
  browserVerification.set(request, pending);
12022
12124
  return pending;
12023
12125
  }
12126
+ function hasValidNativeGrant(origin, token) {
12127
+ if (origin !== "moltnet-agent-desktop://native" || typeof token !== "string" || token.length === 0) return false;
12128
+ try {
12129
+ nativeGrant.verify(origin, token);
12130
+ return true;
12131
+ } catch (error) {
12132
+ if (error instanceof NativeGrantError) return false;
12133
+ throw error;
12134
+ }
12135
+ }
12136
+ const nativeVerification = /* @__PURE__ */ new WeakSet();
12137
+ function requireNativeGrant(request) {
12138
+ if (nativeVerification.has(request)) return;
12139
+ const origin = request.headers.origin;
12140
+ const token = request.headers[AGENT_SERVER_TOKEN_HEADER];
12141
+ if (hasValidNativeGrant(origin, token)) {
12142
+ nativeVerification.add(request);
12143
+ return;
12144
+ }
12145
+ request.log.warn({
12146
+ stage: "native-control-authorization",
12147
+ outcome: "rejected"
12148
+ }, "Native control authorization failed");
12149
+ throw new AgentServerHttpError(401, "native_token_invalid", "Native authorization required");
12150
+ }
12024
12151
  app.register(rateLimit, {
12025
12152
  global: true,
12026
12153
  max: options.rateLimitMax ?? RATE_LIMIT_MAX,
@@ -12032,15 +12159,15 @@ function buildAgentServer(options) {
12032
12159
  const presented = request.headers[AGENT_SERVER_TOKEN_HEADER];
12033
12160
  let authenticated = false;
12034
12161
  if (typeof presented === "string" && presented.length > 0) try {
12035
- if (origin === "moltnet-agent-desktop://native") nativeGrant.verify(origin, presented);
12162
+ if (origin === "moltnet-agent-desktop://native") authenticated = hasValidNativeGrant(origin, presented);
12036
12163
  else {
12037
12164
  if (!oauth) return `unauth:${origin}:${request.ip}`;
12038
12165
  await verifyBrowser(request, presented);
12166
+ authenticated = true;
12039
12167
  }
12040
- authenticated = true;
12041
12168
  } catch (error) {
12042
12169
  if (error instanceof AgentServerHttpError && error.statusCode === 429) throw error;
12043
- if (origin === "moltnet-agent-desktop://native" && !(error instanceof NativeGrantError)) throw error;
12170
+ if (origin === "moltnet-agent-desktop://native") throw error;
12044
12171
  }
12045
12172
  return authenticated ? `origin:${origin}` : `unauth:${origin}:${request.ip}`;
12046
12173
  }
@@ -12050,7 +12177,7 @@ function buildAgentServer(options) {
12050
12177
  const origin = requireOriginHeader(request.headers);
12051
12178
  const token = request.headers[AGENT_SERVER_TOKEN_HEADER];
12052
12179
  if (typeof token !== "string" || token.length === 0) throw new AgentServerHttpError(401, "authorization_required", "Local control token is required");
12053
- if (origin === "moltnet-agent-desktop://native") nativeGrant.verify(origin, token);
12180
+ if (origin === "moltnet-agent-desktop://native") requireNativeGrant(request);
12054
12181
  else try {
12055
12182
  if (!oauth) throw new AgentServerHttpError(503, "oauth_unavailable", "Local OAuth is not configured");
12056
12183
  const admission = browserVerification.get(request);
@@ -12079,6 +12206,9 @@ function buildAgentServer(options) {
12079
12206
  return origin;
12080
12207
  };
12081
12208
  app.after(() => {
12209
+ if (options.nativeOnly) app.addHook("preParsing", async (request) => {
12210
+ requireNativeGrant(request);
12211
+ });
12082
12212
  app.get("/health", { schema: AgentServerRouteSchemas.health }, async () => ({ status: "ok" }));
12083
12213
  app.get("/v1/native/connection-settings", { schema: { hide: true } }, async (request) => {
12084
12214
  if (await requireAuthorizedOrigin(request) !== "moltnet-agent-desktop://native" || !options.connectionSettings) throw new AgentServerHttpError(403, "native_required", "Native administration required");
@@ -12626,7 +12756,7 @@ function normalizeAgentServerError(error) {
12626
12756
  message: error.message
12627
12757
  };
12628
12758
  if (error instanceof AgentServerRunError) return {
12629
- statusCode: error.code === "run_not_found" ? 404 : 400,
12759
+ statusCode: error.code === "run_not_found" ? 404 : error.code === "invalid_store" ? 409 : 400,
12630
12760
  code: error.code,
12631
12761
  message: error.message
12632
12762
  };
@@ -12657,196 +12787,40 @@ function normalizeAgentServerError(error) {
12657
12787
  };
12658
12788
  }
12659
12789
  //#endregion
12660
- //#region src/lib/agent-server/tls.ts
12661
- var execFileAsync = promisify(execFile);
12662
- var CA_COMMON_NAME = "MoltNet Local Agent CA";
12663
- var LEAF_COMMON_NAME = "MoltNet Local Agent";
12664
- var RENEW_BEFORE_MS = 720 * 60 * 60 * 1e3;
12665
- function loginKeychainPath() {
12666
- return join(homedir(), "Library", "Keychains", "login.keychain-db");
12667
- }
12668
- function pemPrivateKey(key) {
12669
- return webcrypto.subtle.exportKey("pkcs8", key).then((der) => createPrivateKey({
12670
- key: Buffer.from(der),
12671
- format: "der",
12672
- type: "pkcs8"
12673
- }).export({
12674
- format: "pem",
12675
- type: "pkcs8"
12676
- }).toString());
12677
- }
12678
- async function localTlsMaterialFromDirectory(dir) {
12679
- try {
12680
- const [key, cert, ca] = await Promise.all([
12681
- readFile(join(dir, "loopback-key.pem"), "utf8"),
12682
- readFile(join(dir, "loopback-cert.pem"), "utf8"),
12683
- readFile(join(dir, "local-ca.pem"), "utf8")
12684
- ]);
12685
- const parsed = new X509Certificate(cert);
12686
- const authority = new X509Certificate(ca);
12687
- if (Date.parse(parsed.validTo) - Date.now() > RENEW_BEFORE_MS && Date.parse(authority.validTo) - Date.now() > RENEW_BEFORE_MS && authority.keyUsage?.includes("1.3.6.1.5.5.7.3.1") && parsed.checkIP("127.0.0.1") === "127.0.0.1" && parsed.verify(authority.publicKey) && parsed.publicKey.equals(createPublicKey(key))) return {
12688
- key,
12689
- cert,
12690
- ca,
12691
- fingerprint: new X509Certificate(ca).fingerprint256
12692
- };
12693
- } catch {}
12694
- return null;
12695
- }
12696
- /** Read-only inspection for native status polling. */
12697
- async function inspectLocalTlsMaterial(root) {
12698
- if (await hasStoredSigningKey(join(root, "tls"))) return null;
12699
- return localTlsMaterialFromDirectory(join(root, "tls"));
12700
- }
12701
- async function hasStoredSigningKey(dir) {
12702
- return stat(join(dir, "local-ca-key.pem")).then(() => true, (error) => {
12703
- if (error.code === "ENOENT") return false;
12704
- throw error;
12705
- });
12706
- }
12707
- /** Creates a per-user CA and loopback-only leaf certificate under a 0700 directory. */
12708
- async function ensureLocalTlsMaterial(root) {
12709
- const dir = join(root, "tls");
12710
- await mkdir(dir, {
12711
- recursive: true,
12712
- mode: 448
12713
- });
12714
- const previousSigningKey = join(dir, "local-ca-key.pem");
12715
- const hasPreviousSigningKey = await hasStoredSigningKey(dir);
12716
- const existing = await localTlsMaterialFromDirectory(dir);
12717
- if (existing && !hasPreviousSigningKey) return existing;
12718
- if (isMacos() && await isLocalCaTrusted(root)) await removeLocalCa(root);
12719
- const caKeys = await webcrypto.subtle.generateKey({
12720
- name: "ECDSA",
12721
- namedCurve: "P-256"
12722
- }, false, ["sign", "verify"]);
12723
- const ca = (await X509CertificateGenerator.createSelfSigned({
12724
- name: `CN=${CA_COMMON_NAME}`,
12725
- keys: caKeys,
12726
- notAfter: new Date(Date.now() + 366 * 24 * 60 * 60 * 1e3),
12727
- extensions: [
12728
- new BasicConstraintsExtension(true, 0, true),
12729
- new KeyUsagesExtension(KeyUsageFlags.keyCertSign, true),
12730
- new ExtendedKeyUsageExtension([ExtendedKeyUsage.serverAuth], true)
12731
- ]
12732
- })).toString("pem");
12733
- const leafKeys = await webcrypto.subtle.generateKey({
12734
- name: "ECDSA",
12735
- namedCurve: "P-256"
12736
- }, true, ["sign", "verify"]);
12737
- const leafCert = await X509CertificateGenerator.create({
12738
- subject: `CN=${LEAF_COMMON_NAME}`,
12739
- issuer: `CN=${CA_COMMON_NAME}`,
12740
- publicKey: leafKeys.publicKey,
12741
- signingKey: caKeys.privateKey,
12742
- notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3),
12743
- extensions: [
12744
- new BasicConstraintsExtension(false, void 0, true),
12745
- new KeyUsagesExtension(KeyUsageFlags.digitalSignature, true),
12746
- new ExtendedKeyUsageExtension([ExtendedKeyUsage.serverAuth]),
12747
- new SubjectAlternativeNameExtension([{
12748
- type: IP,
12749
- value: "127.0.0.1"
12750
- }], true)
12751
- ]
12752
- });
12753
- const key = await pemPrivateKey(leafKeys.privateKey);
12754
- const cert = leafCert.toString("pem");
12755
- const writes = [writeFile(join(dir, "loopback-key.pem"), key, { mode: 384 }), writeFile(join(dir, "loopback-cert.pem"), cert, { mode: 384 })];
12756
- writes.push(writeFile(join(dir, "local-ca.pem"), ca, { mode: 384 }));
12757
- await Promise.all(writes);
12758
- await rm(previousSigningKey, { force: true });
12759
- return {
12760
- key,
12761
- cert,
12762
- ca,
12763
- fingerprint: new X509Certificate(ca).fingerprint256
12764
- };
12765
- }
12766
- async function trustLocalCa(root) {
12767
- const caPath = join(root, "tls", "local-ca.pem");
12768
- await execFileAsync("/usr/bin/security", [
12769
- "add-trusted-cert",
12770
- "-r",
12771
- "trustRoot",
12772
- "-p",
12773
- "ssl",
12774
- "-s",
12775
- "127.0.0.1",
12776
- "-k",
12777
- loginKeychainPath(),
12778
- caPath
12779
- ]);
12780
- }
12781
- async function isLocalCaTrusted(root) {
12782
- try {
12783
- const ca = await readFile(join(root, "tls", "local-ca.pem"), "utf8");
12784
- const { stdout } = await execFileAsync("/usr/bin/security", [
12785
- "find-certificate",
12786
- "-a",
12787
- "-p",
12788
- "-c",
12789
- CA_COMMON_NAME,
12790
- loginKeychainPath()
12791
- ]);
12792
- return stdout.includes(ca.trim());
12793
- } catch (error) {
12794
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return false;
12795
- const stderr = error && typeof error === "object" && "stderr" in error ? error.stderr : void 0;
12796
- if (typeof stderr === "string" && stderr.trim() === "security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.") return false;
12797
- throw error;
12798
- }
12799
- }
12800
- async function removeLocalCa(root) {
12801
- const fingerprint = new X509Certificate(await readFile(join(root, "tls", "local-ca.pem"), "utf8")).fingerprint256.replaceAll(":", "");
12802
- const caPath = join(root, "tls", "local-ca.pem");
12803
- await removeTrustSettings([
12804
- "remove-trusted-cert",
12805
- "-d",
12806
- caPath
12807
- ]);
12808
- await removeTrustSettings(["remove-trusted-cert", caPath]);
12809
- await execFileAsync("/usr/bin/security", [
12810
- "delete-certificate",
12811
- "-Z",
12812
- fingerprint,
12813
- loginKeychainPath()
12814
- ]);
12815
- }
12816
- function isMacos() {
12817
- return process.platform === "darwin";
12818
- }
12819
- async function removeTrustSettings(args) {
12820
- try {
12821
- await execFileAsync("/usr/bin/security", args, { env: { LC_ALL: "C" } });
12822
- } catch (error) {
12823
- const stderr = error && typeof error === "object" && "stderr" in error ? error.stderr : void 0;
12824
- if (typeof stderr === "string" && ["SecTrustSettingsRemoveTrustSettings: No Trust Settings were found.", "SecTrustSettingsRemoveTrustSettings: The specified item could not be found in the keychain."].includes(stderr.trim())) return;
12825
- throw error;
12826
- }
12827
- }
12828
- //#endregion
12829
12790
  //#region src/cli/server.ts
12830
12791
  /**
12831
12792
  * `moltnet-agent server` — per-user loopback supervisor (#2061).
12832
12793
  *
12833
- * Starts nothing on its own: it binds 127.0.0.1 and waits for an authorized
12834
- * Console origin to configure agents/providers and start/stop runs.
12794
+ * Starts nothing on its own: it binds 127.0.0.1 or a private native socket and
12795
+ * waits for an authorized controller to configure agents/providers and
12796
+ * start or stop runs.
12835
12797
  */
12836
- var DEFAULT_PORT = OPERATOR_OAUTH.serverPort;
12837
12798
  var DEFAULT_ALLOWED_ORIGINS = "https://console.themolt.net";
12838
12799
  var SHUTDOWN_TIMEOUT_MS = 15e3;
12800
+ var LOCK_HELD_EXIT_CODE = 75;
12801
+ function agentServerLockExitCode(error) {
12802
+ return error.code === "held" ? LOCK_HELD_EXIT_CODE : 1;
12803
+ }
12804
+ function validateNativeSocketOptions(options) {
12805
+ if (!options.nativeSocket) return void 0;
12806
+ if (!options.supervised) return "--native-socket requires --supervised";
12807
+ if (options.port || options.allowedOrigins) return "--native-socket cannot be combined with TCP options";
12808
+ }
12809
+ function nativeSocketValidationOptions(input) {
12810
+ return {
12811
+ ...input.nativeSocket ? { nativeSocket: input.nativeSocket } : {},
12812
+ ...input.supervised ? { supervised: true } : {},
12813
+ ...input.cliPort ? { port: input.cliPort } : {},
12814
+ ...input.cliAllowedOrigins ? { allowedOrigins: input.cliAllowedOrigins } : {}
12815
+ };
12816
+ }
12839
12817
  async function runAgentServer(argv) {
12840
12818
  if (isHelpFlag(argv)) {
12841
12819
  console.log(AGENT_SERVER_HELP);
12842
12820
  return 0;
12843
12821
  }
12844
- const trustRequested = argv[0] === "trust";
12845
- const commandArgs = trustRequested ? argv.slice(1) : argv;
12846
- const envConfig = loadAgentServerEnvConfig();
12847
- if (trustRequested) return runTrustCommand(commandArgs, resolveAgentServerRoot({ root: envConfig.root }));
12848
12822
  const { values } = parseArgs({
12849
- args: commandArgs,
12823
+ args: argv,
12850
12824
  options: {
12851
12825
  port: { type: "string" },
12852
12826
  "allowed-origins": { type: "string" },
@@ -12854,16 +12828,31 @@ async function runAgentServer(argv) {
12854
12828
  "api-url": { type: "string" },
12855
12829
  "heartbeat-interval-ms": { type: "string" },
12856
12830
  "warm-retention-sec": { type: "string" },
12857
- supervised: { type: "boolean" }
12831
+ supervised: { type: "boolean" },
12832
+ "native-socket": { type: "string" }
12858
12833
  }
12859
12834
  });
12860
- const port = Number.parseInt(values.port ?? (envConfig.port || `${DEFAULT_PORT}`), 10);
12861
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
12835
+ const envConfig = loadAgentServerEnvConfig(values.root);
12836
+ const nativeSocket = values["native-socket"];
12837
+ const nativeSocketError = validateNativeSocketOptions(nativeSocketValidationOptions({
12838
+ ...nativeSocket ? { nativeSocket } : {},
12839
+ ...values.supervised ? { supervised: true } : {},
12840
+ ...values.port ? { cliPort: values.port } : {},
12841
+ ...values["allowed-origins"] ? { cliAllowedOrigins: values["allowed-origins"] } : {},
12842
+ ...envConfig.port ? { envPort: envConfig.port } : {},
12843
+ ...envConfig.allowedOrigins ? { envAllowedOrigins: envConfig.allowedOrigins } : {}
12844
+ }));
12845
+ if (nativeSocketError) {
12846
+ console.error(nativeSocketError);
12847
+ return 1;
12848
+ }
12849
+ const port = Number.parseInt(values.port ?? (envConfig.port || `${defaultAgentServerPort(envConfig.root)}`), 10);
12850
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
12862
12851
  console.error(`Invalid --port: ${String(values.port)}`);
12863
12852
  return 1;
12864
12853
  }
12865
12854
  const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
12866
- const settingsRoot = values.root ?? resolveAgentServerRoot({ root: envConfig.root });
12855
+ const settingsRoot = envConfig.root;
12867
12856
  const connectionSettings = new ConnectionSettingsStore(settingsRoot, {
12868
12857
  ...envConfig.operatorOAuth,
12869
12858
  ...values["api-url"] || envConfig.apiUrl ? { apiUrl: values["api-url"] || envConfig.apiUrl } : {}
@@ -12879,20 +12868,15 @@ async function runAgentServer(argv) {
12879
12868
  });
12880
12869
  try {
12881
12870
  try {
12882
- return await withAgentServerLock(root, async () => {
12883
- const secrets = new FileSecretProvider({
12884
- root: store.secretsDir,
12885
- writable: true
12886
- });
12887
- const secretProviders = createNodeSecretProviderRegistry().register(secrets);
12888
- const externalSecretProviders = createNodeSecretProviderRegistry();
12871
+ return await withAgentServerLock(settingsRoot, async () => {
12872
+ const { secrets, secretProviders, externalSecretProviders } = createAgentServerSecretProviders(connectionSettings, store);
12889
12873
  const nativeGrant = new NativeGrantService();
12890
12874
  const nativeClient = applyNativeClientGrant({
12891
12875
  nativeGrant,
12892
12876
  env: processEnvSnapshot()
12893
12877
  });
12894
12878
  if (Boolean(values.supervised) && !nativeClient) {
12895
- console.error(`A supervised Agent Server requires ${NATIVE_TOKEN_ENV}. Start it from MoltNet Agent, or omit --supervised to run it to reconnect Console to an operator already established by Desktop.`);
12879
+ console.error(`A supervised Agent Server requires ${NATIVE_TOKEN_ENV}. Start it from MoltNet Agent, or omit --supervised for an authorized standalone controller.`);
12896
12880
  return 1;
12897
12881
  }
12898
12882
  const shutdownController = new AbortController();
@@ -12909,6 +12893,7 @@ async function runAgentServer(argv) {
12909
12893
  const runtimeRegistry = new RuntimeRegistry(store.root);
12910
12894
  const runs = new RunManager({
12911
12895
  store,
12896
+ storeRoot: settingsRoot,
12912
12897
  secretProviders,
12913
12898
  externalSecretProviders,
12914
12899
  baseEnv: processEnvSnapshot(),
@@ -12916,8 +12901,8 @@ async function runAgentServer(argv) {
12916
12901
  runtimeRegistry,
12917
12902
  runtimeSettings
12918
12903
  });
12919
- const tls = isMacos() ? await ensureTrustedLocalTls(settingsRoot) : void 0;
12920
- const selfOrigin = `${tls ? "https" : "http"}://127.0.0.1:${port}`;
12904
+ if (nativeSocket) await validateNativeSocket(nativeSocket);
12905
+ const selfOrigin = nativeSocket ? void 0 : `http://127.0.0.1:${port}`;
12921
12906
  const app = buildAgentServer({
12922
12907
  operatorOAuth: new OperatorOAuth({
12923
12908
  issuer: connection.issuer,
@@ -12928,6 +12913,7 @@ async function runAgentServer(argv) {
12928
12913
  consoleClientId: connection.consoleClientId,
12929
12914
  callbackPort: OPERATOR_OAUTH.callbackPort
12930
12915
  }, root),
12916
+ nativeOnly: Boolean(nativeSocket),
12931
12917
  connectionSettings,
12932
12918
  operatorApiUrl: connection.apiUrl,
12933
12919
  store,
@@ -12939,12 +12925,8 @@ async function runAgentServer(argv) {
12939
12925
  subscriptions,
12940
12926
  providers,
12941
12927
  runtimeRegistry,
12942
- allowedOrigins,
12943
- selfOrigin,
12944
- ...tls ? { tls: {
12945
- key: tls.key,
12946
- cert: tls.cert
12947
- } } : {},
12928
+ allowedOrigins: nativeSocket ? [] : allowedOrigins,
12929
+ ...selfOrigin ? { selfOrigin } : {},
12948
12930
  defaultApiUrl,
12949
12931
  runtimeSettings,
12950
12932
  ...envConfig.activeIdentity ? { activeIdentity: envConfig.activeIdentity } : {},
@@ -12952,30 +12934,43 @@ async function runAgentServer(argv) {
12952
12934
  logger,
12953
12935
  shutdownSignal: shutdownController.signal
12954
12936
  });
12937
+ let endpoint;
12955
12938
  try {
12956
- const address = await app.listen({
12939
+ const address = await app.listen(nativeSocket ? { path: nativeSocket } : {
12957
12940
  host: "127.0.0.1",
12958
12941
  port
12959
12942
  });
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
+ }
12960
12948
  console.error(`moltnet-agent server listening on ${address}`);
12961
- console.error(`config root: ${root}`);
12962
- console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
12949
+ console.error(`store root: ${settingsRoot} (${envConfig.rootSource})`);
12950
+ console.error(`connection state: ${root}`);
12951
+ if (nativeSocket) console.error(`native control socket: ${nativeSocket}`);
12952
+ else console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
12963
12953
  if (nativeClient) console.error("native desktop client: authorized");
12964
- console.error("Sign in through Desktop, then connect from the Console \"Local runtime\" page.");
12954
+ if (!nativeSocket) console.error("Connect from an allowed local-control client after operator authorization.");
12965
12955
  return await waitForAgentServerShutdown(runs, app, shutdownController, Boolean(values.supervised));
12966
12956
  } catch (cause) {
12967
12957
  await app.close().catch(() => void 0);
12968
12958
  throw cause;
12959
+ } finally {
12960
+ endpoint?.release();
12961
+ }
12962
+ }, {
12963
+ stateRoot: root,
12964
+ onCompromised: (error) => {
12965
+ console.error(error.message);
12966
+ process.exitCode = 1;
12967
+ process.kill(process.pid, "SIGTERM");
12969
12968
  }
12970
- }, { onCompromised: (error) => {
12971
- console.error(error.message);
12972
- process.exitCode = 1;
12973
- process.kill(process.pid, "SIGTERM");
12974
- } });
12969
+ });
12975
12970
  } catch (cause) {
12976
12971
  if (cause instanceof AgentServerLockError) {
12977
12972
  console.error(cause.message);
12978
- return 1;
12973
+ return agentServerLockExitCode(cause);
12979
12974
  }
12980
12975
  throw cause;
12981
12976
  }
@@ -12983,99 +12978,6 @@ async function runAgentServer(argv) {
12983
12978
  await shutdownLogger();
12984
12979
  }
12985
12980
  }
12986
- async function runTrustCommand(argv, defaultRoot) {
12987
- try {
12988
- const { values } = parseArgs({
12989
- args: argv,
12990
- options: {
12991
- root: { type: "string" },
12992
- remove: { type: "boolean" },
12993
- status: { type: "boolean" },
12994
- yes: { type: "boolean" },
12995
- json: { type: "boolean" }
12996
- }
12997
- });
12998
- const root = values.root ?? defaultRoot;
12999
- const statusRequested = Boolean(values.status);
13000
- const removeRequested = Boolean(values.remove);
13001
- const yes = Boolean(values.yes);
13002
- const json = Boolean(values.json);
13003
- if (statusRequested && (removeRequested || yes)) {
13004
- console.error("Usage: moltnet-agent server trust --status [--json]");
13005
- return 1;
13006
- }
13007
- if (!isMacos()) {
13008
- if (json) {
13009
- printTrustStatus({
13010
- supported: false,
13011
- trusted: false,
13012
- fingerprint: null
13013
- });
13014
- return 0;
13015
- }
13016
- console.error("Local HTTPS trust setup is currently supported on macOS only.");
13017
- return 1;
13018
- }
13019
- if (statusRequested) {
13020
- const material = await inspectLocalTlsMaterial(root);
13021
- const trusted = material !== null && await isLocalCaTrusted(root);
13022
- if (json) printTrustStatus({
13023
- supported: true,
13024
- trusted,
13025
- fingerprint: material?.fingerprint ?? null
13026
- });
13027
- else console.log(trusted ? `MoltNet local CA ${material?.fingerprint ?? "(not prepared)"} is trusted.` : `MoltNet local CA ${material?.fingerprint ?? "(not prepared)"} is not trusted.`);
13028
- return 0;
13029
- }
13030
- if (json && !yes) {
13031
- console.error("Machine-readable trust changes require --yes after native app consent.");
13032
- return 1;
13033
- }
13034
- if (removeRequested) {
13035
- await removeLocalCa(root);
13036
- if (json) printTrustStatus({
13037
- supported: true,
13038
- trusted: false,
13039
- fingerprint: null
13040
- });
13041
- else console.log("Removed the MoltNet local CA from your login keychain.");
13042
- return 0;
13043
- }
13044
- const material = await ensureLocalTlsMaterial(root);
13045
- if (yes) await trustLocalCa(root);
13046
- else await ensureTrustedLocalTls(root);
13047
- if (json) printTrustStatus({
13048
- supported: true,
13049
- trusted: await isLocalCaTrusted(root),
13050
- fingerprint: material.fingerprint
13051
- });
13052
- else console.log("MoltNet local HTTPS trust is ready for this macOS user.");
13053
- return 0;
13054
- } catch (cause) {
13055
- console.error(`Agent Server trust command failed: ${cause instanceof Error ? cause.message : String(cause)}`);
13056
- return 1;
13057
- }
13058
- }
13059
- function printTrustStatus(status) {
13060
- console.log(JSON.stringify(status));
13061
- }
13062
- async function ensureTrustedLocalTls(root) {
13063
- const material = await ensureLocalTlsMaterial(root);
13064
- if (await isLocalCaTrusted(root)) return material;
13065
- if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("Local HTTPS trust is not configured. Run `moltnet-agent server trust` from an interactive terminal.");
13066
- const prompt = createInterface({
13067
- input: process.stdin,
13068
- output: process.stdout
13069
- });
13070
- try {
13071
- const answer = await prompt.question(`Trust MoltNet's local CA (${material.fingerprint}) in this macOS login keychain? [y/N] `);
13072
- if (!/^y(es)?$/i.test(answer.trim())) throw new Error("Local HTTPS trust was not approved.");
13073
- } finally {
13074
- prompt.close();
13075
- }
13076
- await trustLocalCa(root);
13077
- return material;
13078
- }
13079
12981
  function waitForAgentServerShutdown(runs, app, shutdownController, supervised) {
13080
12982
  return new Promise((resolvePromise) => {
13081
12983
  let shuttingDown = false;
@@ -13469,6 +13371,7 @@ function compareVersions(a, b) {
13469
13371
  }
13470
13372
  function cachePath() {
13471
13373
  const env = loadUpdateEnvConfig();
13374
+ if (env.storeRoot) return join(env.storeRoot, "cache", "updates", "agent.json");
13472
13375
  return join(env.xdgCacheHome || (platform() === "win32" ? env.localAppData || join(homedir(), "AppData", "Local") : join(homedir(), ".cache")), "moltnet", "updates", "agent.json");
13473
13376
  }
13474
13377
  async function readCache() {
@@ -13488,16 +13391,14 @@ async function writeCache(cache) {
13488
13391
  }
13489
13392
  //#endregion
13490
13393
  //#region src/version.ts
13491
- var DAEMON_VERSION = "0.61.0";
13394
+ var DAEMON_VERSION = "0.63.0";
13492
13395
  //#endregion
13493
13396
  //#region src/cli.ts
13494
13397
  async function runAgentDaemonCli(options) {
13398
+ const notice = legacyStoreNotice();
13399
+ if (notice) console.error(notice);
13495
13400
  const [subcommand, ...rest] = options.argv ?? process.argv.slice(2);
13496
- if ([
13497
- "poll",
13498
- "drain",
13499
- "serve"
13500
- ].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
13401
+ if (["poll", "drain"].includes(subcommand ?? "") && !isWorkspaceInvocation()) checkDaemonUpdate({ currentVersion: DAEMON_VERSION }).then((update) => {
13501
13402
  if (update.updateAvailable) pino({ name: "moltnet.agent-daemon" }).info({
13502
13403
  event: "agent-daemon.update_available",
13503
13404
  update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.61.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.",
@@ -75,7 +75,6 @@
75
75
  "@opentelemetry/sdk-trace-base": "^2.5.1",
76
76
  "@opentelemetry/sdk-trace-node": "^2.5.1",
77
77
  "@opentelemetry/semantic-conventions": "^1.39.0",
78
- "@peculiar/x509": "^2.0.0",
79
78
  "fastify": "^5.8.5",
80
79
  "fastify-plugin": "^5.1.0",
81
80
  "jose": "^6.1.3",
@@ -84,12 +83,11 @@
84
83
  "pino-opentelemetry-transport": "^3.0.0",
85
84
  "pino-pretty": "^13.1.3",
86
85
  "proper-lockfile": "4.1.2",
87
- "reflect-metadata": "^0.2.2",
88
86
  "typebox": "^1.2.8",
89
- "@themoltnet/agent-runtime": "1.2.0",
90
- "@themoltnet/os-keyring": "0.3.0",
91
- "@themoltnet/pi-runtime": "0.18.2",
92
- "@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"
93
91
  },
94
92
  "devDependencies": {
95
93
  "@fastify/swagger": "^9.6.1",
@@ -100,12 +98,12 @@
100
98
  "vite-plugin-dts": "^4.5.4",
101
99
  "vitest": "^3.0.0",
102
100
  "@moltnet/agent-eval": "0.1.0",
103
- "@moltnet/crypto-service": "0.1.0",
104
101
  "@moltnet/api-client": "0.1.0",
105
- "@moltnet/bootstrap": "0.1.0",
102
+ "@moltnet/crypto-service": "0.1.0",
106
103
  "@moltnet/execution-integrations": "0.1.0",
107
104
  "@moltnet/execution-plan": "0.1.0",
108
105
  "@moltnet/loopback-companion": "0.1.0",
106
+ "@moltnet/bootstrap": "0.1.0",
109
107
  "@moltnet/models": "0.1.0",
110
108
  "@moltnet/observability": "0.1.0",
111
109
  "@moltnet/runtime-profiles": "0.1.0",
@@ -139,6 +137,8 @@
139
137
  "inputs": [
140
138
  "default",
141
139
  "^production",
140
+ "{workspaceRoot}/test-fixtures/agent-server-endpoint.json",
141
+ "{workspaceRoot}/test-fixtures/connection-state-root.json",
142
142
  "{workspaceRoot}/docs/operate/*.md",
143
143
  "{workspaceRoot}/docs/.vitepress/config.ts"
144
144
  ]
@@ -147,7 +147,7 @@
147
147
  },
148
148
  "scripts": {
149
149
  "cli": "tsx src/main.ts",
150
- "dev": "tsx watch src/main.ts",
150
+ "dev": "tsx ../../tools/src/desktop-development.ts tsx watch src/main.ts",
151
151
  "start": "node dist/main.js",
152
152
  "build": "vite build",
153
153
  "generate:openapi": "tsx scripts/generate-openapi.ts",