@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.
- package/dist/cli.js +85 -144
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3412
|
-
|
|
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
|
-
["
|
|
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.
|
|
3716
|
-
if (expected && !
|
|
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
|
|
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
|
-
...
|
|
3873
|
+
...expectedAgent ? { expectedAgent } : {}
|
|
3863
3874
|
};
|
|
3864
3875
|
}
|
|
3865
|
-
function
|
|
3866
|
-
|
|
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
|
-
|
|
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 !==
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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:
|
|
7964
|
+
version: 2,
|
|
8043
7965
|
pendingRegistrations: {},
|
|
8044
7966
|
activations: {}
|
|
8045
7967
|
};
|
|
8046
|
-
if (!isRecord$1(state) || state.version !==
|
|
8047
|
-
if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format;
|
|
8048
|
-
if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version
|
|
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:
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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 {
|
|
8353
|
-
|
|
8274
|
+
const { subjectId, fingerprint, publicKey, privateKey } = result.identity;
|
|
8275
|
+
registered = true;
|
|
8354
8276
|
const agentKeyReference = {
|
|
8355
8277
|
provider: FILE_SECRET_PROVIDER,
|
|
8356
|
-
key: agentKeyKey(
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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",
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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/
|
|
89
|
-
"@themoltnet/
|
|
90
|
-
"@themoltnet/
|
|
91
|
-
"@themoltnet/
|
|
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/
|
|
110
|
+
"@moltnet/runtime-profiles": "0.1.0"
|
|
111
111
|
},
|
|
112
112
|
"nx": {
|
|
113
113
|
"projectType": "application",
|