@themoltnet/agent-daemon 0.54.0 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +115 -175
  2. package/package.json +9 -9
package/dist/cli.js CHANGED
@@ -13,8 +13,8 @@ import { parseArgs, promisify } from "node:util";
13
13
  import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
14
14
  import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
15
15
  import { FILE_SECRET_PROVIDER, FileSecretProvider, connect, createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
16
- import { constants, cpSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
17
- import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, agentKeyKey, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, deriveMcpUrl, formatSecretReferenceString, getConfigDir, getIdentityDir, identitySeedKey, parseSecretReferenceString, readConfig, register, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, resolveOAuth2ClientSecret } from "@themoltnet/sdk";
16
+ import { constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
17
+ import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, agentKeyKey, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, deriveMcpUrl, formatSecretReferenceString, getConfigDir, getIdentityDir, identitySeedKey, isCanonicalConfig, parseSecretReferenceString, readConfig, register, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
18
18
  import { execFile, spawn } from "node:child_process";
19
19
  import { X509Certificate, createHash, createPrivateKey, createPublicKey, randomBytes, randomUUID, timingSafeEqual, webcrypto } from "node:crypto";
20
20
  import { once } from "node:events";
@@ -1363,7 +1363,8 @@ Type.Object({ "x-moltnet-team-id": Type.Optional(Type.String({
1363
1363
  })) });
1364
1364
  Type.Object({
1365
1365
  kind: Type.Literal("agent"),
1366
- identityId: UuidSchema,
1366
+ agentId: UuidSchema,
1367
+ identityId: Type.Union([UuidSchema, Type.Null()]),
1367
1368
  fingerprint: FingerprintSchema,
1368
1369
  publicKey: PublicKeySchema
1369
1370
  }, {
@@ -1380,7 +1381,8 @@ Type.Object({
1380
1381
  });
1381
1382
  var principalUnionVariants = [Type.Object({
1382
1383
  kind: Type.Literal("agent"),
1383
- identityId: UuidSchema,
1384
+ agentId: UuidSchema,
1385
+ identityId: Type.Union([UuidSchema, Type.Null()]),
1384
1386
  fingerprint: FingerprintSchema,
1385
1387
  publicKey: PublicKeySchema
1386
1388
  }, { additionalProperties: false }), Type.Object({
@@ -3408,8 +3410,8 @@ var COMMON_OPTIONAL_FLAGS = `\
3408
3410
  --git-author <"Name <email>">
3409
3411
  Non-secret git identity projected into the
3410
3412
  guest for host-brokered commit signing. Default:
3411
- host git config (OAuth2) or
3412
- <identityId>+<agent>[bot]@users.noreply.github.com.
3413
+ host git config. Configless agent-key runs must
3414
+ provide this flag or MOLTNET_GIT_AUTHOR.
3413
3415
  Env: MOLTNET_GIT_AUTHOR.
3414
3416
  --lease-ttl-sec <n> Sliding liveness window. Silence longer than
3415
3417
  this ends the attempt with lease_expired.
@@ -3626,8 +3628,17 @@ remove that exact CA. Linux continues to use the Chromium PNA HTTP path.
3626
3628
  //#region src/lib/identity-pin.ts
3627
3629
  /** Compare every pinned field without choosing a caller-specific error type. */
3628
3630
  function assessIdentityPin(current, expected) {
3631
+ for (const [field, label] of [["publicKey", "public key"], ["fingerprint", "fingerprint"]]) if (!current[field] || current[field] !== expected[field]) return {
3632
+ ok: false,
3633
+ field,
3634
+ label
3635
+ };
3636
+ return { ok: true };
3637
+ }
3638
+ function assessAgentStartupPin(current, expected) {
3629
3639
  for (const [field, label] of [
3630
- ["identityId", "identity id"],
3640
+ ["subjectId", "subject id"],
3641
+ ["subjectType", "subject type"],
3631
3642
  ["publicKey", "public key"],
3632
3643
  ["fingerprint", "fingerprint"]
3633
3644
  ]) if (!current[field] || current[field] !== expected[field]) return {
@@ -3712,8 +3723,8 @@ async function validateStartupBinding(options) {
3712
3723
  }
3713
3724
  const assessment = assessStartupBinding(whoami, options.teamId);
3714
3725
  if (!assessment.ok) throw new Error(`Daemon startup validation failed: ${assessment.reason}`);
3715
- const expected = options.expectedIdentity;
3716
- if (expected && !assessIdentityPin(whoami, expected).ok) throw new Error("Daemon startup validation failed: authenticated identity does not match the Agent Server activation.");
3726
+ const expected = options.expectedAgent;
3727
+ if (expected && !assessAgentStartupPin(whoami, expected).ok) throw new Error("Daemon startup validation failed: authenticated agent does not match the Agent Server activation.");
3717
3728
  return whoami;
3718
3729
  }
3719
3730
  /**
@@ -3843,7 +3854,7 @@ function isTransientWhoamiError(error) {
3843
3854
  function loadConfig() {
3844
3855
  assertSingleCredentialForm("MOLTNET_AGENT_KEY", "MOLTNET_AGENT_KEY_REF");
3845
3856
  assertSingleCredentialForm("MOLTNET_PRIVATE_KEY", "MOLTNET_PRIVATE_KEY_REF");
3846
- const expectedIdentity = readExpectedIdentity();
3857
+ const expectedAgent = readExpectedAgent();
3847
3858
  return {
3848
3859
  otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
3849
3860
  logLevel: process.env["LOG_LEVEL"] ?? "",
@@ -3859,22 +3870,27 @@ function loadConfig() {
3859
3870
  credentialBindings: process.env["MOLTNET_CREDENTIAL_BINDINGS"] ?? "",
3860
3871
  credentialEnforcement: process.env["MOLTNET_CREDENTIAL_ENFORCEMENT"] ?? "",
3861
3872
  traceIdlePolling: readBoolean("MOLTNET_TRACE_IDLE_POLLING", process.env["MOLTNET_TRACE_IDLE_POLLING"]),
3862
- ...expectedIdentity ? { expectedIdentity } : {}
3873
+ ...expectedAgent ? { expectedAgent } : {}
3863
3874
  };
3864
3875
  }
3865
- function readExpectedIdentity() {
3866
- const identityId = process.env["MOLTNET_EXPECTED_IDENTITY_ID"]?.trim() ?? "";
3876
+ function readExpectedAgent() {
3877
+ if (process.env["MOLTNET_EXPECTED_IDENTITY_ID"]?.trim()) throw new Error("MOLTNET_EXPECTED_IDENTITY_ID is no longer supported; set the complete MOLTNET_EXPECTED_SUBJECT_ID, MOLTNET_EXPECTED_SUBJECT_TYPE=agent, MOLTNET_EXPECTED_PUBLIC_KEY, and MOLTNET_EXPECTED_FINGERPRINT pin");
3878
+ const subjectId = process.env["MOLTNET_EXPECTED_SUBJECT_ID"]?.trim() ?? "";
3879
+ const subjectType = process.env["MOLTNET_EXPECTED_SUBJECT_TYPE"]?.trim() ?? "";
3867
3880
  const publicKey = process.env["MOLTNET_EXPECTED_PUBLIC_KEY"]?.trim() ?? "";
3868
3881
  const fingerprint = process.env["MOLTNET_EXPECTED_FINGERPRINT"]?.trim() ?? "";
3869
3882
  const present = [
3870
- identityId,
3883
+ subjectId,
3884
+ subjectType,
3871
3885
  publicKey,
3872
3886
  fingerprint
3873
3887
  ].filter(Boolean).length;
3874
3888
  if (present === 0) return void 0;
3875
- if (present !== 3) throw new Error("MOLTNET_EXPECTED_IDENTITY_ID, MOLTNET_EXPECTED_PUBLIC_KEY, and MOLTNET_EXPECTED_FINGERPRINT must be set together");
3889
+ if (present !== 4) throw new Error("MOLTNET_EXPECTED_SUBJECT_ID, MOLTNET_EXPECTED_SUBJECT_TYPE, MOLTNET_EXPECTED_PUBLIC_KEY, and MOLTNET_EXPECTED_FINGERPRINT must be set together");
3890
+ if (subjectType !== "agent") throw new Error("MOLTNET_EXPECTED_SUBJECT_TYPE must be agent");
3876
3891
  return {
3877
- identityId,
3892
+ subjectId,
3893
+ subjectType,
3878
3894
  publicKey,
3879
3895
  fingerprint
3880
3896
  };
@@ -3896,7 +3912,6 @@ function loadAgentServerEnvConfig() {
3896
3912
  port: process.env["MOLTNET_AGENT_SERVER_PORT"] ?? "",
3897
3913
  allowedOrigins: process.env["MOLTNET_AGENT_SERVER_ALLOWED_ORIGINS"] ?? "",
3898
3914
  root: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3899
- xdgConfigHome: process.env["XDG_CONFIG_HOME"] ?? "",
3900
3915
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3901
3916
  logLevel: process.env["LOG_LEVEL"] ?? ""
3902
3917
  };
@@ -5277,16 +5292,16 @@ function validateTaskTypes(types) {
5277
5292
  /**
5278
5293
  * OTel SDK bootstrap for the agent daemon.
5279
5294
  *
5280
- * const shutdown = await initWorkerOtel({ serviceName, agentDir });
5295
+ * const shutdown = await initWorkerOtel({ serviceName, agent });
5281
5296
  * try { ... } finally { await shutdown(); }
5282
5297
  *
5283
5298
  * No-op when MOLTNET_OTEL_ENDPOINT is unset — `@opentelemetry/api` falls
5284
5299
  * back to no-op tracers and meters, so the runtime + pi extension still
5285
5300
  * call into it but nothing is exported.
5286
5301
  */
5287
- async function initWorkerOtel(options) {
5302
+ function initWorkerOtel(options) {
5288
5303
  const endpoint = options.endpoint;
5289
- if (!endpoint) return async () => {};
5304
+ if (!endpoint) return Promise.resolve(async () => {});
5290
5305
  const resource = resourceFromAttributes({
5291
5306
  [ATTR_SERVICE_NAME]: options.serviceName,
5292
5307
  ...options.serviceVersion && { [ATTR_SERVICE_VERSION]: options.serviceVersion },
@@ -5294,11 +5309,7 @@ async function initWorkerOtel(options) {
5294
5309
  ...options.resourceAttributes
5295
5310
  });
5296
5311
  let headersFactory;
5297
- let agent = options.agent;
5298
- if (!agent && options.agentDir) agent = await connect({
5299
- configDir: options.agentDir,
5300
- secretProviders: createNodeSecretProviderRegistry()
5301
- });
5312
+ const agent = options.agent;
5302
5313
  if (agent) headersFactory = async () => {
5303
5314
  return { Authorization: `Bearer ${await agent.getToken()}` };
5304
5315
  };
@@ -5323,7 +5334,7 @@ async function initWorkerOtel(options) {
5323
5334
  });
5324
5335
  provider.register();
5325
5336
  metrics.setGlobalMeterProvider(meterProvider);
5326
- return async () => {
5337
+ return Promise.resolve(async () => {
5327
5338
  await Promise.all([provider.forceFlush().catch((err) => {
5328
5339
  process.stderr.write(`[otel-bootstrap] trace forceFlush failed: ${err instanceof Error ? err.message : String(err)}\n`);
5329
5340
  }), meterProvider.forceFlush().catch((err) => {
@@ -5334,7 +5345,7 @@ async function initWorkerOtel(options) {
5334
5345
  }), meterProvider.shutdown().catch((err) => {
5335
5346
  process.stderr.write(`[otel-bootstrap] metric shutdown failed: ${err instanceof Error ? err.message : String(err)}\n`);
5336
5347
  })]);
5337
- };
5348
+ });
5338
5349
  }
5339
5350
  //#endregion
5340
5351
  //#region src/lib/pi-agent-dir.ts
@@ -6024,7 +6035,7 @@ async function runPolling(opts) {
6024
6035
  const whoami = await validateStartupBinding({
6025
6036
  agent: resolvedContext.agent,
6026
6037
  teamId,
6027
- expectedIdentity: cfg.expectedIdentity
6038
+ expectedAgent: cfg.expectedAgent
6028
6039
  });
6029
6040
  gate = "resolve_signing_material";
6030
6041
  const privateKey = await resolveExecutorSigningPrivateKey({
@@ -6071,11 +6082,12 @@ async function runPolling(opts) {
6071
6082
  throw error;
6072
6083
  }
6073
6084
  })();
6085
+ const daemonRootDir = explicitAgentRootDir ?? process.cwd();
6074
6086
  const resolvedProfiles = await resolveRuntimeProfiles({
6075
6087
  agent: ctx.agent,
6076
6088
  profiles: profileValues,
6077
6089
  teamId,
6078
- cwd: ctx.agentRootDir
6090
+ cwd: daemonRootDir
6079
6091
  });
6080
6092
  const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
6081
6093
  const preparedRuntimes = /* @__PURE__ */ new Map();
@@ -6682,7 +6694,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6682
6694
  const whoami = await validateStartupBinding({
6683
6695
  agent: resolvedContext.agent,
6684
6696
  teamId: values.team,
6685
- expectedIdentity: cfg.expectedIdentity
6697
+ expectedAgent: cfg.expectedAgent
6686
6698
  });
6687
6699
  gate = "resolve_signing_material";
6688
6700
  const privateKey = await resolveExecutorSigningPrivateKey({
@@ -6728,11 +6740,12 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6728
6740
  throw error;
6729
6741
  }
6730
6742
  })();
6743
+ const daemonRootDir = explicitAgentRootDir ?? process.cwd();
6731
6744
  const profile = await resolveRuntimeProfile({
6732
6745
  agent: ctx.agent,
6733
6746
  profile: values.profile,
6734
6747
  teamId: values.team,
6735
- cwd: ctx.agentRootDir
6748
+ cwd: daemonRootDir
6736
6749
  });
6737
6750
  assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
6738
6751
  const preparedRuntime = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
@@ -7869,39 +7882,13 @@ var AgentServerStoreError = class extends Error {
7869
7882
  * so honouring XDG here gave one application two config roots: on a machine
7870
7883
  * with the variable set, the daemon wrote identities the CLI and SDK could not
7871
7884
  * read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
7872
- * genuinely custom location. The XDG parameter is gone rather than ignored: an
7873
- * accepted-but-discarded argument reads like it still works. Callers adopting
7874
- * state from the old root pass it to ensure() instead.
7885
+ * genuinely custom location.
7875
7886
  */
7876
7887
  function resolveAgentServerRoot(input) {
7877
7888
  const override = input.root?.trim();
7878
7889
  if (override) return override;
7879
7890
  return getConfigDir();
7880
7891
  }
7881
- /** The pre-#1834 root that honoured XDG_CONFIG_HOME, if it differs. */
7882
- function legacyXdgAgentServerRoot(xdgConfigHome) {
7883
- const xdg = xdgConfigHome.trim();
7884
- if (!xdg) return null;
7885
- const legacy = join(xdg, "moltnet");
7886
- return legacy === resolveAgentServerRoot({}) ? null : legacy;
7887
- }
7888
- /** True when a root holds agent-server state worth preserving. */
7889
- function hasAgentServerState(root) {
7890
- for (const child of [
7891
- "agent-server.json",
7892
- "identity-selector.json",
7893
- "identities",
7894
- "agents"
7895
- ]) try {
7896
- if (readdirSync(join(root, child)).length > 0) return true;
7897
- } catch {
7898
- try {
7899
- readFileSync(join(root, child));
7900
- return true;
7901
- } catch {}
7902
- }
7903
- return false;
7904
- }
7905
7892
  function providerEnvName(providerId) {
7906
7893
  return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
7907
7894
  }
@@ -7954,8 +7941,7 @@ var AgentServerStore = class {
7954
7941
  return join(this.piDir, "auth.json");
7955
7942
  }
7956
7943
  /** Create the directory layout (0700) if missing. Idempotent. */
7957
- ensure(options = {}) {
7958
- this.adoptLegacyXdgRoot(options.legacyXdgConfigHome ?? "");
7944
+ ensure() {
7959
7945
  for (const dir of [
7960
7946
  this.root,
7961
7947
  this.identitiesDir,
@@ -7965,94 +7951,28 @@ var AgentServerStore = class {
7965
7951
  recursive: true,
7966
7952
  mode: 448
7967
7953
  });
7968
- this.migrateLegacyAgentDocuments();
7969
7954
  return this;
7970
7955
  }
7971
- /**
7972
- * Adopt state left at the pre-#1834 XDG root.
7973
- *
7974
- * The daemon used to resolve `$XDG_CONFIG_HOME/moltnet` while the CLI and SDK
7975
- * resolved `~/.config/moltnet`. Aligning the root without this would silently
7976
- * orphan an existing daemon's entire state — it would come up reporting zero
7977
- * managed agents.
7978
- */
7979
- adoptLegacyXdgRoot(xdgConfigHome) {
7980
- const legacyRoot = legacyXdgAgentServerRoot(xdgConfigHome);
7981
- if (!legacyRoot || legacyRoot === this.root) return;
7982
- if (!hasAgentServerState(legacyRoot)) return;
7983
- if (hasAgentServerState(this.root)) throw new AgentServerStoreError("invalid_state", `agent server state exists at both ${legacyRoot} (the pre-1834 XDG_CONFIG_HOME location) and ${this.root}. The daemon now shares ${this.root} with the CLI and SDK. Merge or remove one of them, or set MOLTNET_AGENT_SERVER_ROOT to choose explicitly.`);
7984
- mkdirSync(resolve(this.root, ".."), {
7985
- recursive: true,
7986
- mode: 448
7987
- });
7988
- try {
7989
- if (readdirSync(this.root).length === 0) rmSync(this.root, { recursive: true });
7990
- } catch {}
7991
- try {
7992
- renameSync(legacyRoot, this.root);
7993
- } catch (error) {
7994
- if (error.code !== "EXDEV") throw new AgentServerStoreError("io_error", `could not adopt the pre-1834 agent server state at ${legacyRoot}: ${String(error.message)}. Move it to ${this.root} manually, or set MOLTNET_AGENT_SERVER_ROOT to choose a root explicitly.`);
7995
- cpSync(legacyRoot, this.root, { recursive: true });
7996
- rmSync(legacyRoot, {
7997
- recursive: true,
7998
- force: true
7999
- });
8000
- }
8001
- }
8002
- /**
8003
- * Migrate managed documents from the pre-#1834 `agents/<alias>.json` layout
8004
- * to `identities/<alias>/moltnet.json`. Without this an upgraded daemon sees
8005
- * zero managed agents and reports a plain "not found".
8006
- */
8007
- migrateLegacyAgentDocuments() {
8008
- const legacyDir = join(this.root, "agents");
8009
- let entries;
8010
- try {
8011
- entries = readdirSync(legacyDir);
8012
- } catch {
8013
- return;
8014
- }
8015
- for (const entry of entries) {
8016
- if (!entry.endsWith(".json")) continue;
8017
- const alias = entry.slice(0, -5);
8018
- if (!NAME_RE.test(alias)) continue;
8019
- const legacyPath = join(legacyDir, entry);
8020
- const target = this.agentPath(alias);
8021
- if (readJson(target)) continue;
8022
- const config = readJson(legacyPath);
8023
- if (!config) continue;
8024
- mkdirSync(join(this.identitiesDir, alias), {
8025
- recursive: true,
8026
- mode: 448
8027
- });
8028
- writeJsonAtomic(target, config);
8029
- rmSync(legacyPath, { force: true });
8030
- }
8031
- if (readdirSync(legacyDir).length === 0) rmSync(legacyDir, {
8032
- recursive: true,
8033
- force: true
8034
- });
8035
- }
8036
7956
  get statePath() {
8037
7957
  return join(this.root, "agent-server.json");
8038
7958
  }
8039
7959
  readAgentServerState() {
8040
7960
  const state = readJson(this.statePath);
8041
7961
  if (!state) return {
8042
- version: 1,
7962
+ version: 2,
8043
7963
  pendingRegistrations: {},
8044
7964
  activations: {}
8045
7965
  };
8046
- if (!isRecord$1(state) || state.version !== 1) throw new AgentServerStoreError("invalid_state", `agent-server.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported`);
8047
- if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format; clear the unreleased agent server store and reconfigure it");
8048
- if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version 1 activation map; clear the unreleased agent server store and reconfigure it");
7966
+ if (!isRecord$1(state) || state.version !== 2) throw new AgentServerStoreError("invalid_state", `agent-server.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported; move agent-server.json aside, run \`moltnet config migrate\`, then add or attach the agents again`);
7967
+ if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format; move agent-server.json aside and configure the agent server again");
7968
+ if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version 2 activation map; move agent-server.json aside, run `moltnet config migrate`, then add or attach the agents again");
8049
7969
  for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
8050
7970
  for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
8051
7971
  assertStoreName("agent name", alias);
8052
7972
  if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new AgentServerStoreError("invalid_state", `pending registration "${alias}" is not valid`);
8053
7973
  }
8054
7974
  return {
8055
- version: 1,
7975
+ version: 2,
8056
7976
  pendingRegistrations: state.pendingRegistrations,
8057
7977
  activations: state.activations
8058
7978
  };
@@ -8281,13 +8201,13 @@ function isStrictDescendant(root, candidate) {
8281
8201
  }
8282
8202
  function validateActivation(alias, value) {
8283
8203
  const invalid = () => {
8284
- throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 1 activation`);
8204
+ throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
8285
8205
  };
8286
8206
  if (!isRecord$1(value)) invalid();
8287
8207
  const activation = value;
8288
8208
  if (activation.alias !== alias) invalid();
8289
8209
  if (![
8290
- "identityId",
8210
+ "subjectId",
8291
8211
  "publicKey",
8292
8212
  "fingerprint",
8293
8213
  "createdAt"
@@ -8332,7 +8252,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8332
8252
  const alias = assertStoreName("agent name", input.name);
8333
8253
  if (!input.enrollmentToken.trim()) throw new AgentServerIdentityError("enrollment_required", "an enrollment token from the target team is required — a self-registered agent would be stranded in its own personal team");
8334
8254
  const releaseAlias = reserveAlias(store, alias);
8335
- let registeredIdentityId;
8255
+ let registered = false;
8336
8256
  try {
8337
8257
  let apiUrl;
8338
8258
  try {
@@ -8349,18 +8269,19 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8349
8269
  });
8350
8270
  if (result.credentials.type !== "agent_key") throw new AgentServerIdentityError("unsupported_credential", `registration returned credential type "${result.credentials.type}"; agent server manages agent-key credentials only`);
8351
8271
  const now = (/* @__PURE__ */ new Date()).toISOString();
8352
- const { identityId, fingerprint, publicKey, privateKey } = result.identity;
8353
- registeredIdentityId = identityId;
8272
+ const { subjectId, fingerprint, publicKey, privateKey } = result.identity;
8273
+ registered = true;
8354
8274
  const agentKeyReference = {
8355
8275
  provider: FILE_SECRET_PROVIDER,
8356
- key: agentKeyKey(identityId)
8276
+ key: agentKeyKey(subjectId)
8357
8277
  };
8358
8278
  const seedReference = {
8359
8279
  provider: FILE_SECRET_PROVIDER,
8360
8280
  key: identitySeedKey(fingerprint)
8361
8281
  };
8362
8282
  const config = {
8363
- identity_id: identityId,
8283
+ subject_id: subjectId,
8284
+ subject_type: "agent",
8364
8285
  registered_at: now,
8365
8286
  agent_key_ref: agentKeyReference,
8366
8287
  keys: {
@@ -8381,15 +8302,15 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8381
8302
  apiUrl: result.apiUrl
8382
8303
  }, store.agentPath(alias), input.signal);
8383
8304
  assertIdentityMatches(whoami, {
8384
- identityId,
8385
8305
  publicKey,
8386
8306
  fingerprint
8387
8307
  }, "authenticated whoami", `new managed agent "${alias}"`);
8308
+ assertSubjectMatches(whoami, config, "authenticated whoami", `managed config ${store.agentPath(alias)}`);
8388
8309
  const boundTeamId = boundTeamIdFromWhoami(whoami);
8389
8310
  const activation = {
8390
8311
  alias,
8391
8312
  source: "managed",
8392
- identityId,
8313
+ subjectId: whoami.subjectId,
8393
8314
  publicKey,
8394
8315
  fingerprint,
8395
8316
  ...boundTeamId ? { boundTeamId } : {},
@@ -8403,11 +8324,11 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8403
8324
  ...boundTeamId ? { boundTeamId } : {}
8404
8325
  };
8405
8326
  } catch (cause) {
8406
- if (!registeredIdentityId && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
8327
+ if (!registered && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
8407
8328
  store.clearPendingRegistration(alias);
8408
8329
  throw new AgentServerIdentityError("registration_failed", registrationRejectionMessage(alias, cause), { cause });
8409
8330
  }
8410
- if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", registeredIdentityId ? `identity "${registeredIdentityId}" was registered but local activation is incomplete; reconcile or clear its pending Agent Server record before retrying` : `registration for "${alias}" may be incomplete; inspect the remote API before changing its pending Agent Server record`, { cause });
8331
+ if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", registered ? `the remote agent was registered but local activation is incomplete; reconcile or clear its pending Agent Server record before retrying` : `registration for "${alias}" may be incomplete; inspect the remote API before changing its pending Agent Server record`, { cause });
8411
8332
  throw cause;
8412
8333
  } finally {
8413
8334
  releaseAlias();
@@ -8438,17 +8359,19 @@ async function reconcileManagedRegistration(store, secrets, aliasInput, action,
8438
8359
  if (!config?.agent_key_ref || config.agent_key_ref.provider !== FILE_SECRET_PROVIDER || config.keys.private_key_ref?.provider !== FILE_SECRET_PROVIDER) throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" does not have complete managed references`);
8439
8360
  const [agentKey, privateKeyState] = await Promise.all([secrets.read(config.agent_key_ref.key), secrets.probe(config.keys.private_key_ref.key)]);
8440
8361
  if (!agentKey || privateKeyState !== "present") throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" is missing persisted secret material`);
8441
- const identity = identityFromConfig(config);
8442
8362
  const apiUrl = requireConfigApiUrl(config, store.agentPath(alias));
8443
8363
  const whoami = await callWhoami(connectAgent, {
8444
8364
  agentKey,
8445
8365
  apiUrl
8446
8366
  }, store.agentPath(alias), signal);
8367
+ assertSubjectMatches(whoami, config, "authenticated whoami", `pending registration "${alias}" config`);
8368
+ const identity = identityFromConfig(config);
8447
8369
  assertIdentityMatches(whoami, identity, "authenticated whoami", `pending registration "${alias}" config`);
8448
8370
  const boundTeamId = boundTeamIdFromWhoami(whoami);
8449
8371
  const recovered = {
8450
8372
  alias,
8451
8373
  source: "managed",
8374
+ subjectId: whoami.subjectId,
8452
8375
  ...identity,
8453
8376
  ...boundTeamId ? { boundTeamId } : {},
8454
8377
  createdAt: config.registered_at,
@@ -8470,14 +8393,15 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
8470
8393
  externalAgentLocation(configPath);
8471
8394
  const config = await readCurrentConfig(configPath);
8472
8395
  const configApiUrl = requireTrustedConfigApiUrl(config, configPath);
8473
- const effectiveApiUrl = requireTrustedApiOverride(input.apiUrl, configApiUrl, configPath);
8474
- const whoami = await authenticateConfig(input.configDir, effectiveApiUrl, secretProviders, connectAgent, input.signal);
8396
+ const whoami = await authenticateConfig(config, configPath, requireTrustedApiOverride(input.apiUrl, configApiUrl, configPath), secretProviders, connectAgent, input.signal);
8475
8397
  const identity = identityFromConfig(config);
8476
8398
  assertIdentityMatches(identity, whoami, `external config ${configPath}`, "authenticated whoami");
8399
+ assertSubjectMatches(whoami, config, "authenticated whoami", `external config ${configPath}`);
8477
8400
  const boundTeamId = boundTeamIdFromWhoami(whoami);
8478
8401
  const activation = {
8479
8402
  alias,
8480
8403
  source: "external",
8404
+ subjectId: whoami.subjectId,
8481
8405
  ...identity,
8482
8406
  ...boundTeamId ? { boundTeamId } : {},
8483
8407
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8495,15 +8419,26 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
8495
8419
  releaseAlias();
8496
8420
  }
8497
8421
  }
8498
- /** Load and authenticate the current config, then compare all pinned fields. */
8422
+ /** Load and authenticate the current config, then refresh its derived pin. */
8499
8423
  async function verifyAgentActivation(store, alias, managedSecretProviders, externalSecretProviders, connectAgent = connect, signal) {
8500
8424
  const activation = requireActivation(store, alias);
8501
8425
  const verified = activation.source === "managed" ? await verifyManagedActivation(store, activation, managedSecretProviders, connectAgent, signal) : await verifyExternalActivation(activation, externalSecretProviders, connectAgent, signal);
8502
- assertIdentityMatches(verified.whoami, activation, "authenticated whoami", `agent "${activation.alias}" pinned activation`);
8426
+ assertSubjectMatches(verified.whoami, verified.config, "authenticated whoami", `agent "${activation.alias}" config`);
8427
+ if (verified.whoami.subjectId !== activation.subjectId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami subject does not match agent "${activation.alias}" pinned activation`);
8428
+ const identity = identityFromConfig(verified.config);
8429
+ assertIdentityMatches(verified.whoami, identity, "authenticated whoami", `agent "${activation.alias}" config`);
8503
8430
  const boundTeamId = boundTeamIdFromWhoami(verified.whoami);
8504
8431
  if (activation.boundTeamId !== boundTeamId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
8432
+ const refreshed = {
8433
+ ...activation,
8434
+ ...identity
8435
+ };
8436
+ if (activation.publicKey !== refreshed.publicKey || activation.fingerprint !== refreshed.fingerprint) {
8437
+ store.writeActivation(refreshed);
8438
+ process.stderr.write(`agent-server: refreshed the authenticated signing identity for ${JSON.stringify(activation.alias)}\n`);
8439
+ }
8505
8440
  return {
8506
- activation,
8441
+ activation: refreshed,
8507
8442
  config: verified.config,
8508
8443
  ...boundTeamId ? { boundTeamId } : {}
8509
8444
  };
@@ -8535,7 +8470,7 @@ async function verifyExternalActivation(activation, secretProviders, connectAgen
8535
8470
  const effectiveApiUrl = requireTrustedApiOverride(activation.apiUrl, activation.configApiUrl, activation.configPath);
8536
8471
  return {
8537
8472
  config,
8538
- whoami: await authenticateConfig(dirname(activation.configPath), effectiveApiUrl, secretProviders, connectAgent, signal)
8473
+ whoami: await authenticateConfig(config, activation.configPath, effectiveApiUrl, secretProviders, connectAgent, signal)
8539
8474
  };
8540
8475
  }
8541
8476
  /** Never let request or activation metadata redirect persisted credentials. */
@@ -8546,7 +8481,7 @@ function requireTrustedApiOverride(override, configApiUrl, configPath) {
8546
8481
  }
8547
8482
  function assertActivatedConfig(config, activation, configPath, currentApiUrl, pinnedApiUrl) {
8548
8483
  if (currentApiUrl !== pinnedApiUrl) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} API endpoint does not match its pinned activation`);
8549
- assertIdentityMatches(identityFromConfig(config), activation, configPath, `agent "${activation.alias}" pinned activation`);
8484
+ if (config.subject_id !== activation.subjectId) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} subject does not match its pinned activation`);
8550
8485
  }
8551
8486
  function requireConfigApiUrl(config, configPath) {
8552
8487
  const apiUrl = config?.endpoints?.api?.trim();
@@ -8595,12 +8530,18 @@ function externalAgentLocation(configPath) {
8595
8530
  agentRoot: dirname(moltnetDir)
8596
8531
  };
8597
8532
  }
8598
- async function authenticateConfig(configDir, apiUrl, secretProviders, connectAgent, signal) {
8533
+ async function authenticateConfig(config, configPath, apiUrl, secretProviders, connectAgent, signal) {
8534
+ let agentKey;
8535
+ try {
8536
+ agentKey = await resolveAgentKey(config, secretProviders);
8537
+ } catch (cause) {
8538
+ throw verificationError(`could not resolve the daemon agent key from ${configPath}`, cause);
8539
+ }
8540
+ if (!agentKey) throw new AgentServerIdentityError("unsupported_credential", `external daemon config at ${configPath} must contain an agent_key_ref`);
8599
8541
  return callWhoami(connectAgent, {
8600
- configDir,
8601
- ...apiUrl ? { apiUrl } : {},
8602
- secretProviders
8603
- }, configDir, signal);
8542
+ agentKey,
8543
+ ...apiUrl ? { apiUrl } : {}
8544
+ }, configPath, signal);
8604
8545
  }
8605
8546
  async function callWhoami(connectAgent, options, source, signal) {
8606
8547
  try {
@@ -8618,16 +8559,18 @@ function boundedIdentitySignal(signal) {
8618
8559
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
8619
8560
  }
8620
8561
  function identityFromConfig(config) {
8621
- const identityId = config?.identity_id?.trim();
8622
8562
  const publicKey = config?.keys?.public_key?.trim();
8623
8563
  const fingerprint = config?.keys?.fingerprint?.trim();
8624
- if (!identityId || !publicKey || !fingerprint) throw new AgentServerIdentityError("verification_failed", "agent config is missing canonical identity_id, keys.public_key, or keys.fingerprint");
8564
+ if (!publicKey || !fingerprint) throw new AgentServerIdentityError("verification_failed", "agent config is missing keys.public_key or keys.fingerprint");
8625
8565
  return {
8626
- identityId,
8627
8566
  publicKey,
8628
8567
  fingerprint
8629
8568
  };
8630
8569
  }
8570
+ function assertSubjectMatches(current, expected, currentLabel, expectedLabel) {
8571
+ if (!isCanonicalConfig(expected)) throw new AgentServerIdentityError("verification_failed", `${expectedLabel} is missing canonical subject_type=agent and subject_id; run \`moltnet config migrate\` first`);
8572
+ if (current.subjectType !== "agent" || current.subjectId !== expected.subject_id) throw new AgentServerIdentityError("verification_failed", `${currentLabel} subject does not match ${expectedLabel}`);
8573
+ }
8631
8574
  function assertIdentityMatches(current, expected, currentLabel, expectedLabel) {
8632
8575
  const assessment = assessIdentityPin(current, expected);
8633
8576
  if (!assessment.ok) throw new AgentServerIdentityError("verification_failed", `${currentLabel} ${assessment.label} does not match ${expectedLabel}`);
@@ -8642,7 +8585,7 @@ function publicAgentView(store, activation) {
8642
8585
  return {
8643
8586
  kind: "managed",
8644
8587
  agentName: activation.alias,
8645
- identityId: activation.identityId,
8588
+ subjectId: activation.subjectId,
8646
8589
  fingerprint: activation.fingerprint,
8647
8590
  ...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
8648
8591
  apiUrl: activation.apiUrl,
@@ -8656,7 +8599,7 @@ function publicAgentView(store, activation) {
8656
8599
  agentName: activation.alias,
8657
8600
  configDir: dirname(activation.configPath),
8658
8601
  ...activation.apiUrl ? { apiUrl: activation.apiUrl } : {},
8659
- identityId: activation.identityId,
8602
+ subjectId: activation.subjectId,
8660
8603
  fingerprint: activation.fingerprint,
8661
8604
  ...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
8662
8605
  createdAt: activation.createdAt
@@ -8790,19 +8733,15 @@ var RunManager = class {
8790
8733
  env["MOLTNET_API_URL"] = activation.apiUrl ?? activation.configApiUrl;
8791
8734
  try {
8792
8735
  const agentKey = await resolveAgentKey(config, this.options.externalSecretProviders);
8793
- if (agentKey) {
8794
- env["MOLTNET_AGENT_KEY"] = agentKey;
8795
- env["MOLTNET_PRIVATE_KEY"] = await resolveIdentitySeed(config, this.options.externalSecretProviders);
8796
- } else if (config.oauth2?.client_secret_ref) {
8797
- env["MOLTNET_CLIENT_ID"] = config.oauth2.client_id;
8798
- env["MOLTNET_CLIENT_SECRET"] = await resolveOAuth2ClientSecret(config, this.options.externalSecretProviders);
8799
- }
8800
- if (!agentKey && config.keys.private_key_ref) env["MOLTNET_PRIVATE_KEY"] = await resolveIdentitySeed(config, this.options.externalSecretProviders);
8736
+ if (!agentKey) throw new Error("external daemon config has no agent key");
8737
+ env["MOLTNET_AGENT_KEY"] = agentKey;
8738
+ env["MOLTNET_PRIVATE_KEY"] = await resolveIdentitySeed(config, this.options.externalSecretProviders);
8801
8739
  } catch {
8802
8740
  throw new AgentServerRunError("invalid_spec", `external credentials for "${activation.alias}" could not be projected`);
8803
8741
  }
8804
8742
  }
8805
- env["MOLTNET_EXPECTED_IDENTITY_ID"] = activation.identityId;
8743
+ env["MOLTNET_EXPECTED_SUBJECT_ID"] = activation.subjectId;
8744
+ env["MOLTNET_EXPECTED_SUBJECT_TYPE"] = "agent";
8806
8745
  env["MOLTNET_EXPECTED_PUBLIC_KEY"] = activation.publicKey;
8807
8746
  env["MOLTNET_EXPECTED_FINGERPRINT"] = activation.fingerprint;
8808
8747
  env["MOLTNET_SUPERVISED_RUN"] = "1";
@@ -9006,10 +8945,11 @@ var RunManager = class {
9006
8945
  apiUrl: activation.apiUrl
9007
8946
  });
9008
8947
  }
8948
+ const agentKey = await resolveAgentKey(config, this.options.externalSecretProviders);
8949
+ if (!agentKey) throw new AgentServerRunError("invalid_spec", `external agent "${activation.alias}" has no agent key`);
9009
8950
  return connect({
9010
- configDir: dirname(activation.configPath),
9011
- apiUrl: activation.apiUrl ?? activation.configApiUrl,
9012
- secretProviders: this.options.externalSecretProviders
8951
+ agentKey,
8952
+ apiUrl: activation.apiUrl ?? activation.configApiUrl
9013
8953
  });
9014
8954
  }
9015
8955
  stop(id) {
@@ -9459,7 +9399,7 @@ var AgentServerProblemSchema = Type.Object({
9459
9399
  var AgentServerAgentSchema = Type.Object({
9460
9400
  kind: Type.Union([Type.Literal("managed"), Type.Literal("external")]),
9461
9401
  agentName: Type.String(),
9462
- identityId: Type.Optional(Type.String()),
9402
+ subjectId: Type.String(),
9463
9403
  fingerprint: Type.Optional(Type.String()),
9464
9404
  apiUrl: Type.Optional(Type.String()),
9465
9405
  teamId: Type.Optional(Type.String()),
@@ -10624,7 +10564,7 @@ async function runAgentServer(argv) {
10624
10564
  const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
10625
10565
  const root = values.root ?? resolveAgentServerRoot({ root: envConfig.root });
10626
10566
  const defaultApiUrl = values["api-url"] ?? (envConfig.apiUrl || DEFAULT_API_URL);
10627
- const store = new AgentServerStore(root).ensure({ legacyXdgConfigHome: envConfig.xdgConfigHome });
10567
+ const store = new AgentServerStore(root).ensure();
10628
10568
  if (trustRequested) return runTrustCommand(commandArgs, root);
10629
10569
  const { logger, shutdown: shutdownLogger } = createRootLogger({
10630
10570
  name: "agent-daemon.server",
@@ -11112,7 +11052,7 @@ async function writeCache(cache) {
11112
11052
  }
11113
11053
  //#endregion
11114
11054
  //#region src/version.ts
11115
- var DAEMON_VERSION = "0.54.0";
11055
+ var DAEMON_VERSION = "0.55.1";
11116
11056
  //#endregion
11117
11057
  //#region src/cli.ts
11118
11058
  async function runAgentDaemonCli(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.54.0",
3
+ "version": "0.55.1",
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.",
@@ -85,10 +85,10 @@
85
85
  "proper-lockfile": "4.1.2",
86
86
  "reflect-metadata": "^0.2.2",
87
87
  "typebox": "^1.2.8",
88
- "@themoltnet/pi-runtime": "0.14.3",
89
- "@themoltnet/agent-runtime": "0.46.0",
90
- "@themoltnet/sdk": "0.140.2",
91
- "@themoltnet/os-keyring": "0.3.0"
88
+ "@themoltnet/agent-runtime": "1.0.1",
89
+ "@themoltnet/os-keyring": "0.3.0",
90
+ "@themoltnet/pi-runtime": "0.15.1",
91
+ "@themoltnet/sdk": "0.141.1"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@fastify/swagger": "^9.6.1",
@@ -99,15 +99,15 @@
99
99
  "vite-plugin-dts": "^4.5.4",
100
100
  "vitest": "^3.0.0",
101
101
  "@moltnet/bootstrap": "0.1.0",
102
+ "@moltnet/agent-eval": "0.1.0",
102
103
  "@moltnet/crypto-service": "0.1.0",
103
104
  "@moltnet/execution-plan": "0.1.0",
104
105
  "@moltnet/execution-integrations": "0.1.0",
105
- "@moltnet/runtime-profiles": "0.1.0",
106
- "@moltnet/tasks": "0.1.0",
107
106
  "@moltnet/loopback-companion": "0.1.0",
108
- "@moltnet/agent-eval": "0.1.0",
107
+ "@moltnet/models": "0.1.0",
109
108
  "@moltnet/observability": "0.1.0",
110
- "@moltnet/models": "0.1.0"
109
+ "@moltnet/runtime-profiles": "0.1.0",
110
+ "@moltnet/tasks": "0.1.0"
111
111
  },
112
112
  "nx": {
113
113
  "projectType": "application",