@themoltnet/agent-daemon 0.54.0 → 0.55.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.
Files changed (2) hide show
  1. package/dist/cli.js +85 -144
  2. package/package.json +10 -10
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, resolveOAuth2ClientSecret } 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
  };
@@ -6024,7 +6039,7 @@ async function runPolling(opts) {
6024
6039
  const whoami = await validateStartupBinding({
6025
6040
  agent: resolvedContext.agent,
6026
6041
  teamId,
6027
- expectedIdentity: cfg.expectedIdentity
6042
+ expectedAgent: cfg.expectedAgent
6028
6043
  });
6029
6044
  gate = "resolve_signing_material";
6030
6045
  const privateKey = await resolveExecutorSigningPrivateKey({
@@ -6682,7 +6697,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6682
6697
  const whoami = await validateStartupBinding({
6683
6698
  agent: resolvedContext.agent,
6684
6699
  teamId: values.team,
6685
- expectedIdentity: cfg.expectedIdentity
6700
+ expectedAgent: cfg.expectedAgent
6686
6701
  });
6687
6702
  gate = "resolve_signing_material";
6688
6703
  const privateKey = await resolveExecutorSigningPrivateKey({
@@ -7869,39 +7884,13 @@ var AgentServerStoreError = class extends Error {
7869
7884
  * so honouring XDG here gave one application two config roots: on a machine
7870
7885
  * with the variable set, the daemon wrote identities the CLI and SDK could not
7871
7886
  * 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.
7887
+ * genuinely custom location.
7875
7888
  */
7876
7889
  function resolveAgentServerRoot(input) {
7877
7890
  const override = input.root?.trim();
7878
7891
  if (override) return override;
7879
7892
  return getConfigDir();
7880
7893
  }
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
7894
  function providerEnvName(providerId) {
7906
7895
  return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
7907
7896
  }
@@ -7954,8 +7943,7 @@ var AgentServerStore = class {
7954
7943
  return join(this.piDir, "auth.json");
7955
7944
  }
7956
7945
  /** Create the directory layout (0700) if missing. Idempotent. */
7957
- ensure(options = {}) {
7958
- this.adoptLegacyXdgRoot(options.legacyXdgConfigHome ?? "");
7946
+ ensure() {
7959
7947
  for (const dir of [
7960
7948
  this.root,
7961
7949
  this.identitiesDir,
@@ -7965,94 +7953,28 @@ var AgentServerStore = class {
7965
7953
  recursive: true,
7966
7954
  mode: 448
7967
7955
  });
7968
- this.migrateLegacyAgentDocuments();
7969
7956
  return this;
7970
7957
  }
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
7958
  get statePath() {
8037
7959
  return join(this.root, "agent-server.json");
8038
7960
  }
8039
7961
  readAgentServerState() {
8040
7962
  const state = readJson(this.statePath);
8041
7963
  if (!state) return {
8042
- version: 1,
7964
+ version: 2,
8043
7965
  pendingRegistrations: {},
8044
7966
  activations: {}
8045
7967
  };
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");
7968
+ 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`);
7969
+ 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");
7970
+ 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
7971
  for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
8050
7972
  for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
8051
7973
  assertStoreName("agent name", alias);
8052
7974
  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
7975
  }
8054
7976
  return {
8055
- version: 1,
7977
+ version: 2,
8056
7978
  pendingRegistrations: state.pendingRegistrations,
8057
7979
  activations: state.activations
8058
7980
  };
@@ -8281,13 +8203,13 @@ function isStrictDescendant(root, candidate) {
8281
8203
  }
8282
8204
  function validateActivation(alias, value) {
8283
8205
  const invalid = () => {
8284
- throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 1 activation`);
8206
+ throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
8285
8207
  };
8286
8208
  if (!isRecord$1(value)) invalid();
8287
8209
  const activation = value;
8288
8210
  if (activation.alias !== alias) invalid();
8289
8211
  if (![
8290
- "identityId",
8212
+ "subjectId",
8291
8213
  "publicKey",
8292
8214
  "fingerprint",
8293
8215
  "createdAt"
@@ -8332,7 +8254,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8332
8254
  const alias = assertStoreName("agent name", input.name);
8333
8255
  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
8256
  const releaseAlias = reserveAlias(store, alias);
8335
- let registeredIdentityId;
8257
+ let registered = false;
8336
8258
  try {
8337
8259
  let apiUrl;
8338
8260
  try {
@@ -8349,18 +8271,19 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8349
8271
  });
8350
8272
  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
8273
  const now = (/* @__PURE__ */ new Date()).toISOString();
8352
- const { identityId, fingerprint, publicKey, privateKey } = result.identity;
8353
- registeredIdentityId = identityId;
8274
+ const { subjectId, fingerprint, publicKey, privateKey } = result.identity;
8275
+ registered = true;
8354
8276
  const agentKeyReference = {
8355
8277
  provider: FILE_SECRET_PROVIDER,
8356
- key: agentKeyKey(identityId)
8278
+ key: agentKeyKey(subjectId)
8357
8279
  };
8358
8280
  const seedReference = {
8359
8281
  provider: FILE_SECRET_PROVIDER,
8360
8282
  key: identitySeedKey(fingerprint)
8361
8283
  };
8362
8284
  const config = {
8363
- identity_id: identityId,
8285
+ subject_id: subjectId,
8286
+ subject_type: "agent",
8364
8287
  registered_at: now,
8365
8288
  agent_key_ref: agentKeyReference,
8366
8289
  keys: {
@@ -8381,15 +8304,15 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8381
8304
  apiUrl: result.apiUrl
8382
8305
  }, store.agentPath(alias), input.signal);
8383
8306
  assertIdentityMatches(whoami, {
8384
- identityId,
8385
8307
  publicKey,
8386
8308
  fingerprint
8387
8309
  }, "authenticated whoami", `new managed agent "${alias}"`);
8310
+ assertSubjectMatches(whoami, config, "authenticated whoami", `managed config ${store.agentPath(alias)}`);
8388
8311
  const boundTeamId = boundTeamIdFromWhoami(whoami);
8389
8312
  const activation = {
8390
8313
  alias,
8391
8314
  source: "managed",
8392
- identityId,
8315
+ subjectId: whoami.subjectId,
8393
8316
  publicKey,
8394
8317
  fingerprint,
8395
8318
  ...boundTeamId ? { boundTeamId } : {},
@@ -8403,11 +8326,11 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
8403
8326
  ...boundTeamId ? { boundTeamId } : {}
8404
8327
  };
8405
8328
  } catch (cause) {
8406
- if (!registeredIdentityId && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
8329
+ if (!registered && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
8407
8330
  store.clearPendingRegistration(alias);
8408
8331
  throw new AgentServerIdentityError("registration_failed", registrationRejectionMessage(alias, cause), { cause });
8409
8332
  }
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 });
8333
+ 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
8334
  throw cause;
8412
8335
  } finally {
8413
8336
  releaseAlias();
@@ -8438,17 +8361,19 @@ async function reconcileManagedRegistration(store, secrets, aliasInput, action,
8438
8361
  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
8362
  const [agentKey, privateKeyState] = await Promise.all([secrets.read(config.agent_key_ref.key), secrets.probe(config.keys.private_key_ref.key)]);
8440
8363
  if (!agentKey || privateKeyState !== "present") throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" is missing persisted secret material`);
8441
- const identity = identityFromConfig(config);
8442
8364
  const apiUrl = requireConfigApiUrl(config, store.agentPath(alias));
8443
8365
  const whoami = await callWhoami(connectAgent, {
8444
8366
  agentKey,
8445
8367
  apiUrl
8446
8368
  }, store.agentPath(alias), signal);
8369
+ assertSubjectMatches(whoami, config, "authenticated whoami", `pending registration "${alias}" config`);
8370
+ const identity = identityFromConfig(config);
8447
8371
  assertIdentityMatches(whoami, identity, "authenticated whoami", `pending registration "${alias}" config`);
8448
8372
  const boundTeamId = boundTeamIdFromWhoami(whoami);
8449
8373
  const recovered = {
8450
8374
  alias,
8451
8375
  source: "managed",
8376
+ subjectId: whoami.subjectId,
8452
8377
  ...identity,
8453
8378
  ...boundTeamId ? { boundTeamId } : {},
8454
8379
  createdAt: config.registered_at,
@@ -8474,10 +8399,12 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
8474
8399
  const whoami = await authenticateConfig(input.configDir, effectiveApiUrl, secretProviders, connectAgent, input.signal);
8475
8400
  const identity = identityFromConfig(config);
8476
8401
  assertIdentityMatches(identity, whoami, `external config ${configPath}`, "authenticated whoami");
8402
+ assertSubjectMatches(whoami, config, "authenticated whoami", `external config ${configPath}`);
8477
8403
  const boundTeamId = boundTeamIdFromWhoami(whoami);
8478
8404
  const activation = {
8479
8405
  alias,
8480
8406
  source: "external",
8407
+ subjectId: whoami.subjectId,
8481
8408
  ...identity,
8482
8409
  ...boundTeamId ? { boundTeamId } : {},
8483
8410
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8495,15 +8422,26 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
8495
8422
  releaseAlias();
8496
8423
  }
8497
8424
  }
8498
- /** Load and authenticate the current config, then compare all pinned fields. */
8425
+ /** Load and authenticate the current config, then refresh its derived pin. */
8499
8426
  async function verifyAgentActivation(store, alias, managedSecretProviders, externalSecretProviders, connectAgent = connect, signal) {
8500
8427
  const activation = requireActivation(store, alias);
8501
8428
  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`);
8429
+ assertSubjectMatches(verified.whoami, verified.config, "authenticated whoami", `agent "${activation.alias}" config`);
8430
+ if (verified.whoami.subjectId !== activation.subjectId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami subject does not match agent "${activation.alias}" pinned activation`);
8431
+ const identity = identityFromConfig(verified.config);
8432
+ assertIdentityMatches(verified.whoami, identity, "authenticated whoami", `agent "${activation.alias}" config`);
8503
8433
  const boundTeamId = boundTeamIdFromWhoami(verified.whoami);
8504
8434
  if (activation.boundTeamId !== boundTeamId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
8435
+ const refreshed = {
8436
+ ...activation,
8437
+ ...identity
8438
+ };
8439
+ if (activation.publicKey !== refreshed.publicKey || activation.fingerprint !== refreshed.fingerprint) {
8440
+ store.writeActivation(refreshed);
8441
+ process.stderr.write(`agent-server: refreshed the authenticated signing identity for ${JSON.stringify(activation.alias)}\n`);
8442
+ }
8505
8443
  return {
8506
- activation,
8444
+ activation: refreshed,
8507
8445
  config: verified.config,
8508
8446
  ...boundTeamId ? { boundTeamId } : {}
8509
8447
  };
@@ -8546,7 +8484,7 @@ function requireTrustedApiOverride(override, configApiUrl, configPath) {
8546
8484
  }
8547
8485
  function assertActivatedConfig(config, activation, configPath, currentApiUrl, pinnedApiUrl) {
8548
8486
  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`);
8487
+ if (config.subject_id !== activation.subjectId) throw new AgentServerIdentityError("verification_failed", `agent config at ${configPath} subject does not match its pinned activation`);
8550
8488
  }
8551
8489
  function requireConfigApiUrl(config, configPath) {
8552
8490
  const apiUrl = config?.endpoints?.api?.trim();
@@ -8618,16 +8556,18 @@ function boundedIdentitySignal(signal) {
8618
8556
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
8619
8557
  }
8620
8558
  function identityFromConfig(config) {
8621
- const identityId = config?.identity_id?.trim();
8622
8559
  const publicKey = config?.keys?.public_key?.trim();
8623
8560
  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");
8561
+ if (!publicKey || !fingerprint) throw new AgentServerIdentityError("verification_failed", "agent config is missing keys.public_key or keys.fingerprint");
8625
8562
  return {
8626
- identityId,
8627
8563
  publicKey,
8628
8564
  fingerprint
8629
8565
  };
8630
8566
  }
8567
+ function assertSubjectMatches(current, expected, currentLabel, expectedLabel) {
8568
+ if (!isCanonicalConfig(expected)) throw new AgentServerIdentityError("verification_failed", `${expectedLabel} is missing canonical subject_type=agent and subject_id; run \`moltnet config migrate\` first`);
8569
+ if (current.subjectType !== "agent" || current.subjectId !== expected.subject_id) throw new AgentServerIdentityError("verification_failed", `${currentLabel} subject does not match ${expectedLabel}`);
8570
+ }
8631
8571
  function assertIdentityMatches(current, expected, currentLabel, expectedLabel) {
8632
8572
  const assessment = assessIdentityPin(current, expected);
8633
8573
  if (!assessment.ok) throw new AgentServerIdentityError("verification_failed", `${currentLabel} ${assessment.label} does not match ${expectedLabel}`);
@@ -8642,7 +8582,7 @@ function publicAgentView(store, activation) {
8642
8582
  return {
8643
8583
  kind: "managed",
8644
8584
  agentName: activation.alias,
8645
- identityId: activation.identityId,
8585
+ subjectId: activation.subjectId,
8646
8586
  fingerprint: activation.fingerprint,
8647
8587
  ...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
8648
8588
  apiUrl: activation.apiUrl,
@@ -8656,7 +8596,7 @@ function publicAgentView(store, activation) {
8656
8596
  agentName: activation.alias,
8657
8597
  configDir: dirname(activation.configPath),
8658
8598
  ...activation.apiUrl ? { apiUrl: activation.apiUrl } : {},
8659
- identityId: activation.identityId,
8599
+ subjectId: activation.subjectId,
8660
8600
  fingerprint: activation.fingerprint,
8661
8601
  ...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
8662
8602
  createdAt: activation.createdAt
@@ -8802,7 +8742,8 @@ var RunManager = class {
8802
8742
  throw new AgentServerRunError("invalid_spec", `external credentials for "${activation.alias}" could not be projected`);
8803
8743
  }
8804
8744
  }
8805
- env["MOLTNET_EXPECTED_IDENTITY_ID"] = activation.identityId;
8745
+ env["MOLTNET_EXPECTED_SUBJECT_ID"] = activation.subjectId;
8746
+ env["MOLTNET_EXPECTED_SUBJECT_TYPE"] = "agent";
8806
8747
  env["MOLTNET_EXPECTED_PUBLIC_KEY"] = activation.publicKey;
8807
8748
  env["MOLTNET_EXPECTED_FINGERPRINT"] = activation.fingerprint;
8808
8749
  env["MOLTNET_SUPERVISED_RUN"] = "1";
@@ -9459,7 +9400,7 @@ var AgentServerProblemSchema = Type.Object({
9459
9400
  var AgentServerAgentSchema = Type.Object({
9460
9401
  kind: Type.Union([Type.Literal("managed"), Type.Literal("external")]),
9461
9402
  agentName: Type.String(),
9462
- identityId: Type.Optional(Type.String()),
9403
+ subjectId: Type.String(),
9463
9404
  fingerprint: Type.Optional(Type.String()),
9464
9405
  apiUrl: Type.Optional(Type.String()),
9465
9406
  teamId: Type.Optional(Type.String()),
@@ -10624,7 +10565,7 @@ async function runAgentServer(argv) {
10624
10565
  const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
10625
10566
  const root = values.root ?? resolveAgentServerRoot({ root: envConfig.root });
10626
10567
  const defaultApiUrl = values["api-url"] ?? (envConfig.apiUrl || DEFAULT_API_URL);
10627
- const store = new AgentServerStore(root).ensure({ legacyXdgConfigHome: envConfig.xdgConfigHome });
10568
+ const store = new AgentServerStore(root).ensure();
10628
10569
  if (trustRequested) return runTrustCommand(commandArgs, root);
10629
10570
  const { logger, shutdown: shutdownLogger } = createRootLogger({
10630
10571
  name: "agent-daemon.server",
@@ -11112,7 +11053,7 @@ async function writeCache(cache) {
11112
11053
  }
11113
11054
  //#endregion
11114
11055
  //#region src/version.ts
11115
- var DAEMON_VERSION = "0.54.0";
11056
+ var DAEMON_VERSION = "0.55.0";
11116
11057
  //#endregion
11117
11058
  //#region src/cli.ts
11118
11059
  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.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.",
@@ -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.0",
89
+ "@themoltnet/pi-runtime": "0.15.0",
90
+ "@themoltnet/os-keyring": "0.3.0",
91
+ "@themoltnet/sdk": "0.141.0"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@fastify/swagger": "^9.6.1",
@@ -100,14 +100,14 @@
100
100
  "vitest": "^3.0.0",
101
101
  "@moltnet/bootstrap": "0.1.0",
102
102
  "@moltnet/crypto-service": "0.1.0",
103
- "@moltnet/execution-plan": "0.1.0",
104
103
  "@moltnet/execution-integrations": "0.1.0",
105
- "@moltnet/runtime-profiles": "0.1.0",
106
- "@moltnet/tasks": "0.1.0",
107
- "@moltnet/loopback-companion": "0.1.0",
108
104
  "@moltnet/agent-eval": "0.1.0",
105
+ "@moltnet/execution-plan": "0.1.0",
106
+ "@moltnet/loopback-companion": "0.1.0",
107
+ "@moltnet/models": "0.1.0",
108
+ "@moltnet/tasks": "0.1.0",
109
109
  "@moltnet/observability": "0.1.0",
110
- "@moltnet/models": "0.1.0"
110
+ "@moltnet/runtime-profiles": "0.1.0"
111
111
  },
112
112
  "nx": {
113
113
  "projectType": "application",