@themoltnet/agent-daemon 0.44.1 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +347 -201
- package/dist/pi.d.ts.map +1 -1
- package/dist/pi.js +557 -2
- package/package.json +10 -8
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { defaultPiDaemonAdapter } from "./pi.js";
|
|
2
|
+
import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
|
|
3
3
|
import { assertRuntimeAdapterSupportsProfile } from "./runtime.js";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import "multiformats/cid";
|
|
@@ -10,21 +10,11 @@ import { dirname, join, resolve } from "node:path";
|
|
|
10
10
|
import { parseArgs, promisify } from "node:util";
|
|
11
11
|
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
|
|
12
12
|
import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets } from "@themoltnet/pi-runtime";
|
|
13
|
+
import { connect, createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
|
|
13
14
|
import { execFile, execFileSync } from "node:child_process";
|
|
14
15
|
import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
15
|
-
import { AuthenticationError, MoltNetError,
|
|
16
|
-
import { createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
|
|
16
|
+
import { AuthenticationError, MoltNetError, createExecutorAttestor, readConfig, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
|
|
17
17
|
import { createHash, randomUUID } from "node:crypto";
|
|
18
|
-
import * as ed from "@noble/ed25519";
|
|
19
|
-
import { createHash as createHash$1, randomBytes } from "crypto";
|
|
20
|
-
import "multiformats/codecs/raw";
|
|
21
|
-
import "multiformats/hashes/digest";
|
|
22
|
-
import "@noble/hashes/sha2";
|
|
23
|
-
import "multiformats/bases/base32";
|
|
24
|
-
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
25
|
-
import "@ipld/dag-cbor";
|
|
26
|
-
import "@noble/ciphers/chacha";
|
|
27
|
-
import "@noble/hashes/hkdf";
|
|
28
18
|
import { once } from "node:events";
|
|
29
19
|
import { pino, transport } from "pino";
|
|
30
20
|
import { metrics } from "@opentelemetry/api";
|
|
@@ -2405,8 +2395,8 @@ async function validateJudgePackInputAsync(input, ctx) {
|
|
|
2405
2395
|
//#endregion
|
|
2406
2396
|
//#region ../../libs/tasks/src/task-types/judge-eval-attempt.ts
|
|
2407
2397
|
/**
|
|
2408
|
-
* `judge_eval_attempt` — score one completed
|
|
2409
|
-
* hidden judge rubric.
|
|
2398
|
+
* `judge_eval_attempt` — score one completed artifact-producing attempt
|
|
2399
|
+
* against a hidden judge rubric.
|
|
2410
2400
|
*
|
|
2411
2401
|
* output_kind: judgment
|
|
2412
2402
|
* criteria: required (`successCriteria.rubric`)
|
|
@@ -2510,9 +2500,9 @@ async function validateJudgeEvalAttemptInputAsync(input, ctx) {
|
|
|
2510
2500
|
field: "targetTaskId",
|
|
2511
2501
|
message: `targetTaskId=${inp.targetTaskId} does not resolve to a task you can read`
|
|
2512
2502
|
}];
|
|
2513
|
-
if (target.
|
|
2503
|
+
if (target.outputKind !== "artifact") errors.push({
|
|
2514
2504
|
field: "targetTaskId",
|
|
2515
|
-
message: `targetTaskId=${inp.targetTaskId}
|
|
2505
|
+
message: `targetTaskId=${inp.targetTaskId} has outputKind=${target.outputKind}; only artifact-producing tasks can be judged`
|
|
2516
2506
|
});
|
|
2517
2507
|
if (!ctx.deferReadinessChecks && (target.status !== "completed" || target.acceptedAttemptN === null)) errors.push({
|
|
2518
2508
|
field: "targetTaskId",
|
|
@@ -2524,7 +2514,7 @@ async function validateJudgeEvalAttemptInputAsync(input, ctx) {
|
|
|
2524
2514
|
});
|
|
2525
2515
|
if (!target.correlationId) errors.push({
|
|
2526
2516
|
field: "targetTaskId",
|
|
2527
|
-
message: "target
|
|
2517
|
+
message: "target producer has no correlation_id; cannot enforce duplicate-judge protection"
|
|
2528
2518
|
});
|
|
2529
2519
|
if (errors.length > 0 || !target.correlationId) return errors;
|
|
2530
2520
|
const rubric = inp.successCriteria.rubric;
|
|
@@ -3433,7 +3423,8 @@ Commands:
|
|
|
3433
3423
|
Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
3434
3424
|
|
|
3435
3425
|
Prerequisites:
|
|
3436
|
-
- agent-key mode: MOLTNET_AGENT_KEY
|
|
3426
|
+
- agent-key mode: MOLTNET_AGENT_KEY (or MOLTNET_AGENT_KEY_REF) and
|
|
3427
|
+
MOLTNET_PRIVATE_KEY (or MOLTNET_PRIVATE_KEY_REF); no agent files
|
|
3437
3428
|
- OAuth2 and sync-sessions: <agent-root>/.moltnet/<agent>/moltnet.json
|
|
3438
3429
|
- --profile — remote runtime profile supplies provider/model/sandbox
|
|
3439
3430
|
policy and CWD is used as the VM mountPath.
|
|
@@ -3563,7 +3554,8 @@ function isHelpFlag(args) {
|
|
|
3563
3554
|
/**
|
|
3564
3555
|
* Report which auth mode `connect()` will use, without ever reading the secret
|
|
3565
3556
|
* value into anything logged. Agent-key mode is selected when
|
|
3566
|
-
* `MOLTNET_AGENT_KEY` holds a non-blank value —
|
|
3557
|
+
* `MOLTNET_AGENT_KEY` or `MOLTNET_AGENT_KEY_REF` holds a non-blank value —
|
|
3558
|
+
* mirroring the SDK precedence
|
|
3567
3559
|
* where an environment key opts into key mode ahead of the config-file OAuth2
|
|
3568
3560
|
* credentials. The daemon never passes explicit in-code credentials to
|
|
3569
3561
|
* `connect()`, so this env-only check matches what `connect()` actually does.
|
|
@@ -3571,7 +3563,7 @@ function isHelpFlag(args) {
|
|
|
3571
3563
|
* Pure: `env` is passed in (the config module owns the `process.env` read).
|
|
3572
3564
|
*/
|
|
3573
3565
|
function detectAuthMode(env) {
|
|
3574
|
-
return env.MOLTNET_AGENT_KEY?.trim() ? "agent-key" : "oauth2";
|
|
3566
|
+
return env.MOLTNET_AGENT_KEY?.trim() || env.MOLTNET_AGENT_KEY_REF?.trim() ? "agent-key" : "oauth2";
|
|
3575
3567
|
}
|
|
3576
3568
|
/**
|
|
3577
3569
|
* Pure check: may the identity described by `whoami` operate the daemon as
|
|
@@ -3637,7 +3629,9 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3637
3629
|
return {
|
|
3638
3630
|
agentDir: join(rootDir, ".moltnet", agentName),
|
|
3639
3631
|
agentRootDir: rootDir,
|
|
3640
|
-
agent: await connect()
|
|
3632
|
+
agent: await connect({ secretProviders: createNodeSecretProviderRegistry() }),
|
|
3633
|
+
credentialSource: "environment",
|
|
3634
|
+
authMechanism: "agent-key"
|
|
3641
3635
|
};
|
|
3642
3636
|
}
|
|
3643
3637
|
const located = locateAgentConfig(roots, agentName);
|
|
@@ -3646,10 +3640,13 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3646
3640
|
configDir: located.agentDir,
|
|
3647
3641
|
secretProviders: createNodeSecretProviderRegistry()
|
|
3648
3642
|
});
|
|
3643
|
+
const config = await readConfig(located.agentDir);
|
|
3649
3644
|
return {
|
|
3650
3645
|
agentDir: located.agentDir,
|
|
3651
3646
|
agentRootDir: located.rootDir,
|
|
3652
|
-
agent
|
|
3647
|
+
agent,
|
|
3648
|
+
credentialSource: "config",
|
|
3649
|
+
authMechanism: config?.agent_key_ref ? "agent-key" : "oauth2"
|
|
3653
3650
|
};
|
|
3654
3651
|
}
|
|
3655
3652
|
const tried = roots.map((root) => join(root, ".moltnet", agentName));
|
|
@@ -3691,6 +3688,8 @@ function resolveCredentialRoots(agentRootDir) {
|
|
|
3691
3688
|
* sprinkling string lookups across the codebase.
|
|
3692
3689
|
*/
|
|
3693
3690
|
function loadConfig() {
|
|
3691
|
+
assertSingleCredentialForm("MOLTNET_AGENT_KEY", "MOLTNET_AGENT_KEY_REF");
|
|
3692
|
+
assertSingleCredentialForm("MOLTNET_PRIVATE_KEY", "MOLTNET_PRIVATE_KEY_REF");
|
|
3694
3693
|
return {
|
|
3695
3694
|
otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
|
|
3696
3695
|
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
@@ -3699,10 +3698,17 @@ function loadConfig() {
|
|
|
3699
3698
|
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
|
|
3700
3699
|
authMode: detectAuthMode(process.env),
|
|
3701
3700
|
signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
|
|
3701
|
+
signingPrivateKeyRef: process.env["MOLTNET_PRIVATE_KEY_REF"] ?? "",
|
|
3702
3702
|
gitAuthor: process.env["MOLTNET_GIT_AUTHOR"] ?? "",
|
|
3703
|
+
profileCredentialRequirements: process.env["MOLTNET_PROFILE_CREDENTIAL_REQUIREMENTS"] ?? "",
|
|
3704
|
+
credentialBindings: process.env["MOLTNET_CREDENTIAL_BINDINGS"] ?? "",
|
|
3705
|
+
credentialEnforcement: process.env["MOLTNET_CREDENTIAL_ENFORCEMENT"] ?? "",
|
|
3703
3706
|
traceIdlePolling: readBoolean("MOLTNET_TRACE_IDLE_POLLING", process.env["MOLTNET_TRACE_IDLE_POLLING"])
|
|
3704
3707
|
};
|
|
3705
3708
|
}
|
|
3709
|
+
function assertSingleCredentialForm(valueName, refName) {
|
|
3710
|
+
if (process.env[valueName]?.trim() && process.env[refName]?.trim()) throw new Error(`Set only one of ${valueName} or ${refName}`);
|
|
3711
|
+
}
|
|
3706
3712
|
function readBoolean(name, value) {
|
|
3707
3713
|
if (value === void 0 || value === "") return false;
|
|
3708
3714
|
if (value === "true") return true;
|
|
@@ -3910,6 +3916,7 @@ function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmSessionTtlS
|
|
|
3910
3916
|
return {
|
|
3911
3917
|
descriptor,
|
|
3912
3918
|
workspaceMode,
|
|
3919
|
+
workspaceKind: workspaceMode === "scratch_mount" ? "scratch" : void 0,
|
|
3913
3920
|
sessionKey: slotId,
|
|
3914
3921
|
slotKey,
|
|
3915
3922
|
slotId,
|
|
@@ -4203,15 +4210,16 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
4203
4210
|
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
|
|
4204
4211
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
|
|
4205
4212
|
if (resolution.kind === "remote-session") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has a durable runtime session but no workspace metadata to copy`);
|
|
4213
|
+
const producerWorkspaceCopySource = resolveProducerWorkspaceCopySource(resolution.producerSlot, stateDirs);
|
|
4206
4214
|
return {
|
|
4207
4215
|
...basePlan,
|
|
4208
4216
|
workspaceMode: "scratch_mount",
|
|
4209
4217
|
worktreeBranch: null,
|
|
4210
4218
|
workspaceKind: "scratch",
|
|
4211
|
-
workspaceSeed: {
|
|
4212
|
-
copyFromPath:
|
|
4219
|
+
workspaceSeed: producerWorkspaceCopySource ? {
|
|
4220
|
+
copyFromPath: producerWorkspaceCopySource,
|
|
4213
4221
|
source: "producer"
|
|
4214
|
-
},
|
|
4222
|
+
} : null,
|
|
4215
4223
|
sessionPersistence: {
|
|
4216
4224
|
sessionDir: `${stateDirs.piSessionsDir}/judge-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`,
|
|
4217
4225
|
forkFromSessionPath: resolution.sessionPath
|
|
@@ -4254,12 +4262,18 @@ function resolveProducerWorkspaceCopySource(producer, stateDirs) {
|
|
|
4254
4262
|
if (existsSync(workspacePath)) return workspacePath;
|
|
4255
4263
|
const recoveredPath = recoverScratchWorkspacePath(producer, stateDirs);
|
|
4256
4264
|
if (recoveredPath) return recoveredPath;
|
|
4265
|
+
if (isDisposableScratchWorkspace(producer, stateDirs)) return null;
|
|
4257
4266
|
throw new ProducerContextResolutionError(`Producer workspace path is missing on disk: ${workspacePath}`);
|
|
4258
4267
|
}
|
|
4259
4268
|
const sharedMountRoot = dirname(dirname(stateDirs.rootDir));
|
|
4260
4269
|
if (!existsSync(sharedMountRoot)) throw new ProducerContextResolutionError(`Shared producer mount root is missing on disk: ${sharedMountRoot}`);
|
|
4261
4270
|
return sharedMountRoot;
|
|
4262
4271
|
}
|
|
4272
|
+
function isDisposableScratchWorkspace(producer, stateDirs) {
|
|
4273
|
+
if (producer.workspace?.kind === "scratch") return true;
|
|
4274
|
+
if (!producer.workspace?.workspaceId) return false;
|
|
4275
|
+
return producer.workspace.worktreePath === join(stateDirs.rootDir, "task-workspaces", producer.workspace.workspaceId);
|
|
4276
|
+
}
|
|
4263
4277
|
function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
4264
4278
|
if (producer.workspace?.worktreeBranch) return null;
|
|
4265
4279
|
if (!producer.workspace?.workspaceId) return null;
|
|
@@ -4267,183 +4281,30 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
|
4267
4281
|
return existsSync(fallback) ? fallback : null;
|
|
4268
4282
|
}
|
|
4269
4283
|
//#endregion
|
|
4270
|
-
//#region ../../libs/crypto-service/src/ssh.ts
|
|
4271
|
-
/**
|
|
4272
|
-
* SSH key format conversion for MoltNet Ed25519 keys
|
|
4273
|
-
*
|
|
4274
|
-
* Converts MoltNet agent keys (ed25519:<base64>) to OpenSSH format
|
|
4275
|
-
* for use with git commit signing and SSH authentication.
|
|
4276
|
-
*/
|
|
4277
|
-
if (!ed.etc.sha512Sync) ed.etc.sha512Sync = (...m) => {
|
|
4278
|
-
const hash = createHash$1("sha512");
|
|
4279
|
-
m.forEach((msg) => hash.update(msg));
|
|
4280
|
-
return hash.digest();
|
|
4281
|
-
};
|
|
4282
|
-
new TextEncoder();
|
|
4283
|
-
//#endregion
|
|
4284
|
-
//#region ../../libs/crypto-service/src/crypto.service.ts
|
|
4285
|
-
/**
|
|
4286
|
-
* MoltNet Crypto Service
|
|
4287
|
-
*
|
|
4288
|
-
* Ed25519 cryptographic operations for agent identity
|
|
4289
|
-
* Uses @noble/ed25519 for pure TypeScript implementation
|
|
4290
|
-
*/
|
|
4291
|
-
ed.etc.sha512Sync = (...m) => {
|
|
4292
|
-
const hash = createHash$1("sha512");
|
|
4293
|
-
m.forEach((msg) => hash.update(msg));
|
|
4294
|
-
return hash.digest();
|
|
4295
|
-
};
|
|
4296
|
-
/** Domain-separation prefix for the signing payload envelope. */
|
|
4297
|
-
var DOMAIN_PREFIX = "moltnet:v1";
|
|
4298
|
-
/**
|
|
4299
|
-
* Build deterministic signing bytes with domain separation and
|
|
4300
|
-
* length-prefixed binary framing.
|
|
4301
|
-
*
|
|
4302
|
-
* Layout:
|
|
4303
|
-
* UTF-8("moltnet:v1") || u32be(len(msg_hash)) || msg_hash || u32be(len(nonce_bytes)) || nonce_bytes
|
|
4304
|
-
*
|
|
4305
|
-
* Where msg_hash = SHA-256(UTF-8(message)).
|
|
4306
|
-
*
|
|
4307
|
-
* This produces a fixed-structure byte sequence immune to whitespace,
|
|
4308
|
-
* newline, and encoding differences between runtimes.
|
|
4309
|
-
*/
|
|
4310
|
-
function buildSigningBytes(message, nonce) {
|
|
4311
|
-
const msgHash = createHash$1("sha256").update(Buffer.from(message, "utf-8")).digest();
|
|
4312
|
-
const nonceBytes = Buffer.from(nonce, "utf-8");
|
|
4313
|
-
const prefix = Buffer.from(DOMAIN_PREFIX, "utf-8");
|
|
4314
|
-
const buf = Buffer.alloc(prefix.length + 4 + msgHash.length + 4 + nonceBytes.length);
|
|
4315
|
-
let offset = 0;
|
|
4316
|
-
prefix.copy(buf, offset);
|
|
4317
|
-
offset += prefix.length;
|
|
4318
|
-
buf.writeUInt32BE(msgHash.length, offset);
|
|
4319
|
-
offset += 4;
|
|
4320
|
-
msgHash.copy(buf, offset);
|
|
4321
|
-
offset += msgHash.length;
|
|
4322
|
-
buf.writeUInt32BE(nonceBytes.length, offset);
|
|
4323
|
-
offset += 4;
|
|
4324
|
-
nonceBytes.copy(buf, offset);
|
|
4325
|
-
return new Uint8Array(buf);
|
|
4326
|
-
}
|
|
4327
|
-
var cryptoService = {
|
|
4328
|
-
async generateKeyPair() {
|
|
4329
|
-
const privateKeyBytes = ed.utils.randomPrivateKey();
|
|
4330
|
-
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
4331
|
-
const privateKey = Buffer.from(privateKeyBytes).toString("base64");
|
|
4332
|
-
return {
|
|
4333
|
-
publicKey: `ed25519:${Buffer.from(publicKeyBytes).toString("base64")}`,
|
|
4334
|
-
privateKey,
|
|
4335
|
-
fingerprint: this.generateFingerprint(publicKeyBytes)
|
|
4336
|
-
};
|
|
4337
|
-
},
|
|
4338
|
-
generateFingerprint(publicKeyBytes) {
|
|
4339
|
-
return (createHash$1("sha256").update(publicKeyBytes).digest("hex").slice(0, 16).toUpperCase().match(/.{4}/g) ?? []).join("-");
|
|
4340
|
-
},
|
|
4341
|
-
parsePublicKey(publicKey) {
|
|
4342
|
-
const base64 = publicKey.replace(/^ed25519:/, "");
|
|
4343
|
-
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
4344
|
-
},
|
|
4345
|
-
async sign(message, privateKeyBase64) {
|
|
4346
|
-
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyBase64, "base64"));
|
|
4347
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
4348
|
-
const signature = await ed.signAsync(messageBytes, privateKeyBytes);
|
|
4349
|
-
return Buffer.from(signature).toString("base64");
|
|
4350
|
-
},
|
|
4351
|
-
async verify(message, signature, publicKey) {
|
|
4352
|
-
try {
|
|
4353
|
-
const publicKeyBytes = this.parsePublicKey(publicKey);
|
|
4354
|
-
const signatureBytes = new Uint8Array(Buffer.from(signature, "base64"));
|
|
4355
|
-
const messageBytes = new TextEncoder().encode(message);
|
|
4356
|
-
return await ed.verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
|
|
4357
|
-
} catch {
|
|
4358
|
-
return false;
|
|
4359
|
-
}
|
|
4360
|
-
},
|
|
4361
|
-
async signWithNonce(message, nonce, privateKeyBase64) {
|
|
4362
|
-
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyBase64, "base64"));
|
|
4363
|
-
const signingBytes = buildSigningBytes(message, nonce);
|
|
4364
|
-
const signature = await ed.signAsync(signingBytes, privateKeyBytes);
|
|
4365
|
-
return Buffer.from(signature).toString("base64");
|
|
4366
|
-
},
|
|
4367
|
-
async verifyWithNonce(message, nonce, signature, publicKey) {
|
|
4368
|
-
try {
|
|
4369
|
-
const publicKeyBytes = this.parsePublicKey(publicKey);
|
|
4370
|
-
const signatureBytes = new Uint8Array(Buffer.from(signature, "base64"));
|
|
4371
|
-
const signingBytes = buildSigningBytes(message, nonce);
|
|
4372
|
-
return await ed.verifyAsync(signatureBytes, signingBytes, publicKeyBytes);
|
|
4373
|
-
} catch {
|
|
4374
|
-
return false;
|
|
4375
|
-
}
|
|
4376
|
-
},
|
|
4377
|
-
async createSignedMessage(message, privateKeyBase64, publicKey) {
|
|
4378
|
-
return {
|
|
4379
|
-
message,
|
|
4380
|
-
signature: await this.sign(message, privateKeyBase64),
|
|
4381
|
-
publicKey
|
|
4382
|
-
};
|
|
4383
|
-
},
|
|
4384
|
-
async verifySignedMessage(signedMessage) {
|
|
4385
|
-
return this.verify(signedMessage.message, signedMessage.signature, signedMessage.publicKey);
|
|
4386
|
-
},
|
|
4387
|
-
generateChallenge() {
|
|
4388
|
-
return `moltnet:challenge:${randomBytes(32).toString("hex")}:${Date.now()}`;
|
|
4389
|
-
},
|
|
4390
|
-
async derivePublicKey(privateKeyBase64) {
|
|
4391
|
-
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyBase64, "base64"));
|
|
4392
|
-
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
4393
|
-
return `ed25519:${Buffer.from(publicKeyBytes).toString("base64")}`;
|
|
4394
|
-
},
|
|
4395
|
-
getFingerprintFromPublicKey(publicKey) {
|
|
4396
|
-
const publicKeyBytes = this.parsePublicKey(publicKey);
|
|
4397
|
-
return this.generateFingerprint(publicKeyBytes);
|
|
4398
|
-
},
|
|
4399
|
-
deriveX25519PrivateKey(ed25519PrivateKeyBase64) {
|
|
4400
|
-
const seed = new Uint8Array(Buffer.from(ed25519PrivateKeyBase64, "base64"));
|
|
4401
|
-
const x25519Priv = ed25519.utils.toMontgomerySecret(seed);
|
|
4402
|
-
return Buffer.from(x25519Priv).toString("base64");
|
|
4403
|
-
},
|
|
4404
|
-
deriveX25519PublicKey(ed25519PublicKey) {
|
|
4405
|
-
const edPubBytes = this.parsePublicKey(ed25519PublicKey);
|
|
4406
|
-
const x25519Pub = ed25519.utils.toMontgomery(edPubBytes);
|
|
4407
|
-
return `x25519:${Buffer.from(x25519Pub).toString("base64")}`;
|
|
4408
|
-
},
|
|
4409
|
-
async createIdentityProof(identityId, privateKeyBase64) {
|
|
4410
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4411
|
-
const message = `moltnet:register:${identityId}:${timestamp}`;
|
|
4412
|
-
return {
|
|
4413
|
-
message,
|
|
4414
|
-
signature: await this.sign(message, privateKeyBase64),
|
|
4415
|
-
timestamp
|
|
4416
|
-
};
|
|
4417
|
-
},
|
|
4418
|
-
async verifyIdentityProof(proof, publicKey, expectedIdentityId) {
|
|
4419
|
-
if (!await this.verify(proof.message, proof.signature, publicKey)) return false;
|
|
4420
|
-
const expectedPrefix = `moltnet:register:${expectedIdentityId}:`;
|
|
4421
|
-
if (!proof.message.startsWith(expectedPrefix)) return false;
|
|
4422
|
-
const proofTime = new Date(proof.timestamp).getTime();
|
|
4423
|
-
if (Date.now() - proofTime > 300 * 1e3) return false;
|
|
4424
|
-
return true;
|
|
4425
|
-
}
|
|
4426
|
-
};
|
|
4427
|
-
//#endregion
|
|
4428
|
-
//#region ../../libs/crypto-service/src/executor-attestation.ts
|
|
4429
|
-
ed.etc.sha512Sync = (...m) => {
|
|
4430
|
-
const hash = createHash("sha512");
|
|
4431
|
-
m.forEach((msg) => hash.update(msg));
|
|
4432
|
-
return hash.digest();
|
|
4433
|
-
};
|
|
4434
|
-
new TextEncoder().encode("SSHSIG");
|
|
4435
|
-
//#endregion
|
|
4436
4284
|
//#region src/lib/executor-attestation.ts
|
|
4437
4285
|
var DAEMON_REQUIRED_SCOPES = AGENT_CREDENTIAL_SCOPES;
|
|
4438
4286
|
async function resolveExecutorSigningPrivateKey(input) {
|
|
4439
4287
|
if (input.authMode === "agent-key") {
|
|
4440
4288
|
const privateKey = input.configuredPrivateKey.trim();
|
|
4441
|
-
if (
|
|
4442
|
-
|
|
4289
|
+
if (privateKey) return privateKey;
|
|
4290
|
+
const reference = input.configuredPrivateKeyRef?.trim();
|
|
4291
|
+
if (!reference) throw new Error("Agent-key daemon startup requires MOLTNET_PRIVATE_KEY (or MOLTNET_PRIVATE_KEY_REF) containing the base64-encoded Ed25519 private key seed.");
|
|
4292
|
+
let resolved;
|
|
4293
|
+
try {
|
|
4294
|
+
resolved = await resolveEnvSecretReference(reference, createNodeSecretProviderRegistry());
|
|
4295
|
+
} catch (cause) {
|
|
4296
|
+
throw new Error(`Agent-key daemon startup could not resolve MOLTNET_PRIVATE_KEY_REF: ${cause.message}`, { cause });
|
|
4297
|
+
}
|
|
4298
|
+
if (Buffer.from(resolved, "base64").length !== 32) throw new Error("MOLTNET_PRIVATE_KEY_REF must resolve to a base64-encoded 32-byte Ed25519 seed.");
|
|
4299
|
+
return resolved;
|
|
4300
|
+
}
|
|
4301
|
+
const config = await readConfig(input.agentDir);
|
|
4302
|
+
if (!config) throw new Error(`OAuth2 daemon startup requires ${input.agentDir}/moltnet.json.`);
|
|
4303
|
+
try {
|
|
4304
|
+
return await resolveIdentitySeed(config, createNodeSecretProviderRegistry());
|
|
4305
|
+
} catch (cause) {
|
|
4306
|
+
throw new Error(`OAuth2 daemon startup could not resolve the signing seed from ${input.agentDir}/moltnet.json (keys.private_key or keys.private_key_ref): ${cause.message}`, { cause });
|
|
4443
4307
|
}
|
|
4444
|
-
const privateKey = (await readConfig(input.agentDir))?.keys?.private_key?.trim();
|
|
4445
|
-
if (!privateKey) throw new Error(`OAuth2 daemon startup requires keys.private_key in ${input.agentDir}/moltnet.json.`);
|
|
4446
|
-
return privateKey;
|
|
4447
4308
|
}
|
|
4448
4309
|
function validateDaemonScopes(whoami) {
|
|
4449
4310
|
const available = new Set(whoami.scopes ?? []);
|
|
@@ -4825,6 +4686,257 @@ async function maybeWriteAnchors(output, ctx) {
|
|
|
4825
4686
|
}
|
|
4826
4687
|
}
|
|
4827
4688
|
//#endregion
|
|
4689
|
+
//#region ../../libs/execution-integrations/src/credential-broker.ts
|
|
4690
|
+
async function checkCredentialReadiness(requirements, bindings, providers) {
|
|
4691
|
+
const records = [];
|
|
4692
|
+
for (const requirement of requirements) {
|
|
4693
|
+
const binding = ownBinding(bindings, requirement.name);
|
|
4694
|
+
if (binding === void 0) {
|
|
4695
|
+
records.push({
|
|
4696
|
+
name: requirement.name,
|
|
4697
|
+
required: requirement.required,
|
|
4698
|
+
status: requirement.required ? "required_binding_missing" : "binding_absent"
|
|
4699
|
+
});
|
|
4700
|
+
continue;
|
|
4701
|
+
}
|
|
4702
|
+
const bindingDigest = await digestBindingReference(binding.reference);
|
|
4703
|
+
const source = binding.source ?? "local-activation";
|
|
4704
|
+
if (providers.get(binding.reference.provider) === void 0) {
|
|
4705
|
+
records.push({
|
|
4706
|
+
name: requirement.name,
|
|
4707
|
+
required: requirement.required,
|
|
4708
|
+
status: "provider_unavailable",
|
|
4709
|
+
bindingDigest,
|
|
4710
|
+
source
|
|
4711
|
+
});
|
|
4712
|
+
continue;
|
|
4713
|
+
}
|
|
4714
|
+
const probe = await providers.probe(binding.reference);
|
|
4715
|
+
records.push({
|
|
4716
|
+
name: requirement.name,
|
|
4717
|
+
required: requirement.required,
|
|
4718
|
+
status: probe === "present" ? "ready" : probe === "absent" ? "binding_absent" : "host_store_inaccessible",
|
|
4719
|
+
bindingDigest,
|
|
4720
|
+
source
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4723
|
+
return records;
|
|
4724
|
+
}
|
|
4725
|
+
async function digestBindingReference(reference) {
|
|
4726
|
+
const bytes = new TextEncoder().encode(`${reference.provider}\u0000${reference.key}`);
|
|
4727
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
4728
|
+
return `sha256:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16)}`;
|
|
4729
|
+
}
|
|
4730
|
+
function ownBinding(bindings, name) {
|
|
4731
|
+
return Object.hasOwn(bindings, name) ? bindings[name] : void 0;
|
|
4732
|
+
}
|
|
4733
|
+
//#endregion
|
|
4734
|
+
//#region ../../libs/execution-integrations/src/runtime-profile.ts
|
|
4735
|
+
var CURRENT_EFFECTIVE_POLICY_SNAPSHOT_VERSION = "effective-policy:v1";
|
|
4736
|
+
/**
|
|
4737
|
+
* Maps resolved product authority into portable intent. Policy composition is
|
|
4738
|
+
* deliberately upstream: this boundary accepts no policy IDs and performs no
|
|
4739
|
+
* union, lookup, or precedence handling.
|
|
4740
|
+
*/
|
|
4741
|
+
function executionIntentFromRuntimeProfile(input) {
|
|
4742
|
+
if (input.policy.snapshot.runtimeKind !== input.profile.runtimeKind) throw new Error("resolved policy authority does not match runtime profile");
|
|
4743
|
+
const network = input.profile.sandboxConfig.network;
|
|
4744
|
+
return {
|
|
4745
|
+
mode: input.mode,
|
|
4746
|
+
profile: {
|
|
4747
|
+
id: input.profile.id,
|
|
4748
|
+
revision: input.profileRevision,
|
|
4749
|
+
definitionCid: input.profile.definitionCid
|
|
4750
|
+
},
|
|
4751
|
+
authority: {
|
|
4752
|
+
policySnapshotHash: input.policy.hash,
|
|
4753
|
+
policySnapshotVersion: input.policy.snapshot.version,
|
|
4754
|
+
...input.policy.authorizedControls !== void 0 && { authorizedControls: [...input.policy.authorizedControls] }
|
|
4755
|
+
},
|
|
4756
|
+
credentialRequirements: structuredClone(input.credentialRequirements),
|
|
4757
|
+
requiredCapabilities: [...input.requiredCapabilities ?? []],
|
|
4758
|
+
lease: {
|
|
4759
|
+
ttlSec: input.profile.leaseTtlSec,
|
|
4760
|
+
requiredControls: [...input.requiredLeaseControls ?? []]
|
|
4761
|
+
},
|
|
4762
|
+
network: {
|
|
4763
|
+
allowedHosts: [...network?.allowedHosts ?? []],
|
|
4764
|
+
allowedInternalHosts: [...network?.allowedInternalHosts ?? []]
|
|
4765
|
+
},
|
|
4766
|
+
provenance: {
|
|
4767
|
+
profile: input.profile.source,
|
|
4768
|
+
policy: `runtime-policy-snapshot:${input.policy.hash}`,
|
|
4769
|
+
requirements: input.requirementsProvenance
|
|
4770
|
+
}
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
//#endregion
|
|
4774
|
+
//#region src/lib/governance-plan.ts
|
|
4775
|
+
/**
|
|
4776
|
+
* Observe-only governance-plan compilation (#1970 private slice).
|
|
4777
|
+
*
|
|
4778
|
+
* Distinct from `task-execution-plan.ts` (workspace/session planning): this
|
|
4779
|
+
* module compiles the CREDENTIAL/CAPABILITY governance plan from trusted
|
|
4780
|
+
* local configuration and logs its value-free decisions. It gates nothing
|
|
4781
|
+
* yet — enforcement is a separate, later change.
|
|
4782
|
+
*/
|
|
4783
|
+
var DEFAULT_GOVERNANCE_OBSERVE_TIMEOUT_MS = 2e3;
|
|
4784
|
+
/**
|
|
4785
|
+
* Two distinct provenance sources (#2022 review): requirements come from a
|
|
4786
|
+
* profile-side/private input (`MOLTNET_PROFILE_CREDENTIAL_REQUIREMENTS`, a
|
|
4787
|
+
* JSON map of profile id → requirements) and bindings from trusted
|
|
4788
|
+
* local/deployment configuration (`MOLTNET_CREDENTIAL_BINDINGS`). Both raw
|
|
4789
|
+
* values are read once in config.ts. Both empty → null (observer disabled).
|
|
4790
|
+
* Malformed configuration throws — a truncated credential policy must never
|
|
4791
|
+
* be silently ignored.
|
|
4792
|
+
*/
|
|
4793
|
+
function loadRuntimeCredentialConfig(raw) {
|
|
4794
|
+
const hasRequirements = raw.profileRequirements.trim() !== "";
|
|
4795
|
+
const hasBindings = raw.bindings.trim() !== "";
|
|
4796
|
+
if (!hasRequirements && !hasBindings) return null;
|
|
4797
|
+
const requirementsByProfile = {};
|
|
4798
|
+
if (hasRequirements) {
|
|
4799
|
+
const parsed = JSON.parse(raw.profileRequirements);
|
|
4800
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("MOLTNET_PROFILE_CREDENTIAL_REQUIREMENTS must be a JSON object keyed by profile id");
|
|
4801
|
+
for (const [profileId, value] of Object.entries(parsed)) requirementsByProfile[profileId] = parseCredentialRequirements(value);
|
|
4802
|
+
}
|
|
4803
|
+
const bindings = {};
|
|
4804
|
+
if (hasBindings) {
|
|
4805
|
+
const parsed = JSON.parse(raw.bindings);
|
|
4806
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("MOLTNET_CREDENTIAL_BINDINGS must be a JSON object");
|
|
4807
|
+
for (const [name, binding] of Object.entries(parsed)) {
|
|
4808
|
+
if (typeof binding?.reference?.provider !== "string" || typeof binding.reference.key !== "string") throw new Error(`MOLTNET_CREDENTIAL_BINDINGS binding "${name}" must carry a { reference: { provider, key } }`);
|
|
4809
|
+
bindings[name] = {
|
|
4810
|
+
...binding,
|
|
4811
|
+
source: "local-bindings-config"
|
|
4812
|
+
};
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
return {
|
|
4816
|
+
requirementsByProfile,
|
|
4817
|
+
bindings,
|
|
4818
|
+
sources: {
|
|
4819
|
+
requirements: "profile-requirements-config",
|
|
4820
|
+
bindings: "local-bindings-config"
|
|
4821
|
+
}
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4824
|
+
/**
|
|
4825
|
+
* Resolve the credential-governance enforcement mode, mirroring the runtime
|
|
4826
|
+
* tool-policy `off | watch | enforce` ladder. Unset defaults to `watch` when
|
|
4827
|
+
* requirement/binding sources are configured, `off` otherwise. `enforce` is
|
|
4828
|
+
* rejected until the enforcement flip (skip-before-claim) is implemented —
|
|
4829
|
+
* accepting it as a silent watch would misrepresent the deployment.
|
|
4830
|
+
*/
|
|
4831
|
+
function resolveCredentialEnforcement(raw, sources) {
|
|
4832
|
+
const configured = sources.profileRequirements.trim() !== "" || sources.bindings.trim() !== "";
|
|
4833
|
+
const value = raw.trim();
|
|
4834
|
+
if (value === "") return configured ? "watch" : "off";
|
|
4835
|
+
if (value === "off" || value === "watch") return value;
|
|
4836
|
+
if (value === "enforce") throw new Error("MOLTNET_CREDENTIAL_ENFORCEMENT=enforce is not implemented yet: the skip-before-claim flip is a separate change. Use watch (audit only).");
|
|
4837
|
+
throw new Error(`MOLTNET_CREDENTIAL_ENFORCEMENT must be off, watch or enforce; got "${value}"`);
|
|
4838
|
+
}
|
|
4839
|
+
async function observeGovernancePlan(input) {
|
|
4840
|
+
const unavailableReason = authorityUnavailableReason(input);
|
|
4841
|
+
if (unavailableReason !== null) {
|
|
4842
|
+
(input.logger.warn ?? input.logger.info).call(input.logger, {
|
|
4843
|
+
mode: "observe",
|
|
4844
|
+
taskId: input.taskId,
|
|
4845
|
+
attemptN: input.attemptN,
|
|
4846
|
+
profileId: input.profile.id,
|
|
4847
|
+
reason: unavailableReason
|
|
4848
|
+
}, "agent-daemon.governance_plan_unavailable");
|
|
4849
|
+
return {
|
|
4850
|
+
status: "unavailable",
|
|
4851
|
+
snapshot: null,
|
|
4852
|
+
reason: unavailableReason
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
const { runtimeProfileRevision, policySnapshotHash } = input.claimAuthority;
|
|
4856
|
+
if (runtimeProfileRevision === void 0 || policySnapshotHash === void 0) throw new Error("validated claim authority is missing immutable pins");
|
|
4857
|
+
if (input.offer === void 0) throw new Error("validated execution offer is missing");
|
|
4858
|
+
const requirements = input.config.requirementsByProfile[input.profile.id] ?? [];
|
|
4859
|
+
const credentialReadiness = await checkCredentialReadiness(requirements, input.config.bindings, input.registry);
|
|
4860
|
+
const intent = executionIntentFromRuntimeProfile({
|
|
4861
|
+
mode: "watch",
|
|
4862
|
+
profile: input.profile,
|
|
4863
|
+
profileRevision: runtimeProfileRevision,
|
|
4864
|
+
policy: {
|
|
4865
|
+
hash: policySnapshotHash,
|
|
4866
|
+
snapshot: {
|
|
4867
|
+
version: CURRENT_EFFECTIVE_POLICY_SNAPSHOT_VERSION,
|
|
4868
|
+
runtimeKind: input.profile.runtimeKind
|
|
4869
|
+
},
|
|
4870
|
+
authorizedControls: void 0
|
|
4871
|
+
},
|
|
4872
|
+
credentialRequirements: requirements,
|
|
4873
|
+
requiredCapabilities: [],
|
|
4874
|
+
requirementsProvenance: input.config.sources.requirements
|
|
4875
|
+
});
|
|
4876
|
+
const plan = compileExecutionPlan({
|
|
4877
|
+
intent,
|
|
4878
|
+
offer: input.offer,
|
|
4879
|
+
credentialReadiness
|
|
4880
|
+
});
|
|
4881
|
+
const snapshot = await createExecutionPlanSnapshot({
|
|
4882
|
+
intent,
|
|
4883
|
+
offer: input.offer,
|
|
4884
|
+
credentialReadiness,
|
|
4885
|
+
plan
|
|
4886
|
+
});
|
|
4887
|
+
const logObject = {
|
|
4888
|
+
mode: "observe",
|
|
4889
|
+
taskId: input.taskId,
|
|
4890
|
+
attemptN: input.attemptN,
|
|
4891
|
+
profileId: input.profile.id,
|
|
4892
|
+
sources: input.config.sources,
|
|
4893
|
+
snapshotCid: snapshot.cid,
|
|
4894
|
+
profileRevision: snapshot.intent.profile.revision,
|
|
4895
|
+
policySnapshotHash: snapshot.intent.authority.policySnapshotHash,
|
|
4896
|
+
launchable: plan.launchable,
|
|
4897
|
+
decisions: plan.decisions,
|
|
4898
|
+
credentialReadiness
|
|
4899
|
+
};
|
|
4900
|
+
if (plan.launchable) input.logger.info(logObject, "agent-daemon.governance_plan");
|
|
4901
|
+
else (input.logger.warn ?? input.logger.info).call(input.logger, logObject, "agent-daemon.governance_plan_would_block");
|
|
4902
|
+
return {
|
|
4903
|
+
status: "observed",
|
|
4904
|
+
snapshot
|
|
4905
|
+
};
|
|
4906
|
+
}
|
|
4907
|
+
/**
|
|
4908
|
+
* Observe without allowing a stuck keyring/provider to delay task execution.
|
|
4909
|
+
* The observer is intentionally non-gating: failures and deadlines are logged
|
|
4910
|
+
* with claim correlation and converted to `null`.
|
|
4911
|
+
*/
|
|
4912
|
+
async function observeGovernancePlanSafely(input, timeoutMs = DEFAULT_GOVERNANCE_OBSERVE_TIMEOUT_MS) {
|
|
4913
|
+
let timeout;
|
|
4914
|
+
try {
|
|
4915
|
+
return await Promise.race([observeGovernancePlan(input), new Promise((_resolve, reject) => {
|
|
4916
|
+
timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("governance observation timed out")), timeoutMs);
|
|
4917
|
+
})]);
|
|
4918
|
+
} catch (error) {
|
|
4919
|
+
(input.logger.warn ?? input.logger.info).call(input.logger, {
|
|
4920
|
+
mode: "observe",
|
|
4921
|
+
taskId: input.taskId,
|
|
4922
|
+
attemptN: input.attemptN,
|
|
4923
|
+
err: error instanceof Error ? error.message : String(error)
|
|
4924
|
+
}, "agent-daemon.governance_plan_observe_failed");
|
|
4925
|
+
return null;
|
|
4926
|
+
} finally {
|
|
4927
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
function authorityUnavailableReason(input) {
|
|
4931
|
+
const authority = input.claimAuthority;
|
|
4932
|
+
if (authority.runtimeProfileId === void 0 || authority.runtimeProfileRevision === void 0 || authority.policySnapshotHash === void 0 || authority.executorFingerprint === void 0) return "claim_authority_unpinned";
|
|
4933
|
+
if (authority.runtimeProfileId !== input.profile.id) return "runtime_profile_mismatch";
|
|
4934
|
+
if (authority.executorFingerprint !== input.executorFingerprint) return "executor_fingerprint_mismatch";
|
|
4935
|
+
if (input.offer === void 0) return "execution_offer_unavailable";
|
|
4936
|
+
if (input.offer.executor.fingerprint !== input.executorFingerprint) return "executor_fingerprint_mismatch";
|
|
4937
|
+
return null;
|
|
4938
|
+
}
|
|
4939
|
+
//#endregion
|
|
4828
4940
|
//#region src/lib/logger.ts
|
|
4829
4941
|
/**
|
|
4830
4942
|
* Logger setup + teardown for the agent-daemon CLI commands.
|
|
@@ -5662,6 +5774,11 @@ async function runPolling(opts) {
|
|
|
5662
5774
|
}
|
|
5663
5775
|
if (taskTypes.length === 0) console.error(`[${opts.modeLabel}] --task-types is empty — daemon will accept any registered type. Pass an explicit list to limit scope (e.g. --task-types fulfill_brief).`);
|
|
5664
5776
|
const cfg = loadConfig();
|
|
5777
|
+
const credentialSources = {
|
|
5778
|
+
profileRequirements: cfg.profileCredentialRequirements,
|
|
5779
|
+
bindings: cfg.credentialBindings
|
|
5780
|
+
};
|
|
5781
|
+
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
5665
5782
|
const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
|
|
5666
5783
|
const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
5667
5784
|
let gate = "resolve_agent_context";
|
|
@@ -5679,7 +5796,8 @@ async function runPolling(opts) {
|
|
|
5679
5796
|
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
5680
5797
|
authMode: cfg.authMode,
|
|
5681
5798
|
agentDir: resolvedContext.agentDir,
|
|
5682
|
-
configuredPrivateKey: cfg.signingPrivateKey
|
|
5799
|
+
configuredPrivateKey: cfg.signingPrivateKey,
|
|
5800
|
+
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
5683
5801
|
});
|
|
5684
5802
|
gate = "validate_scopes";
|
|
5685
5803
|
validateDaemonScopes(whoami);
|
|
@@ -5814,7 +5932,7 @@ async function runPolling(opts) {
|
|
|
5814
5932
|
resourceAttributes: {
|
|
5815
5933
|
"moltnet.team.id": teamId,
|
|
5816
5934
|
"moltnet.agent.name": baseCommon.agent,
|
|
5817
|
-
"moltnet.auth.mode":
|
|
5935
|
+
"moltnet.auth.mode": ctx.authMechanism,
|
|
5818
5936
|
"moltnet.runtime_profile.count": String(profiles.length),
|
|
5819
5937
|
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
5820
5938
|
}
|
|
@@ -6027,6 +6145,17 @@ async function runPolling(opts) {
|
|
|
6027
6145
|
executeTask: async (claimedTask, reporter) => {
|
|
6028
6146
|
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
6029
6147
|
const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
|
|
6148
|
+
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6149
|
+
config: runtimeCredentialConfig,
|
|
6150
|
+
profile,
|
|
6151
|
+
offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
|
|
6152
|
+
registry: createNodeSecretProviderRegistry(),
|
|
6153
|
+
executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
|
|
6154
|
+
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6155
|
+
taskId: claimedTask.task.id,
|
|
6156
|
+
attemptN: claimedTask.attemptN,
|
|
6157
|
+
logger: rootLogger
|
|
6158
|
+
});
|
|
6030
6159
|
const taskLogger = rootLogger.child({
|
|
6031
6160
|
runtimeProfileId: profile.id,
|
|
6032
6161
|
runtimeProfileName: profile.name,
|
|
@@ -6297,6 +6426,11 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6297
6426
|
return 1;
|
|
6298
6427
|
}
|
|
6299
6428
|
const cfg = loadConfig();
|
|
6429
|
+
const credentialSources = {
|
|
6430
|
+
profileRequirements: cfg.profileCredentialRequirements,
|
|
6431
|
+
bindings: cfg.credentialBindings
|
|
6432
|
+
};
|
|
6433
|
+
const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
|
|
6300
6434
|
const initialOpts = opts;
|
|
6301
6435
|
const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
|
|
6302
6436
|
const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
|
|
@@ -6315,7 +6449,8 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6315
6449
|
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
6316
6450
|
authMode: cfg.authMode,
|
|
6317
6451
|
agentDir: resolvedContext.agentDir,
|
|
6318
|
-
configuredPrivateKey: cfg.signingPrivateKey
|
|
6452
|
+
configuredPrivateKey: cfg.signingPrivateKey,
|
|
6453
|
+
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
6319
6454
|
});
|
|
6320
6455
|
gate = "validate_scopes";
|
|
6321
6456
|
validateDaemonScopes(whoami);
|
|
@@ -6415,7 +6550,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6415
6550
|
resourceAttributes: {
|
|
6416
6551
|
"moltnet.task.id": taskId,
|
|
6417
6552
|
"moltnet.agent.name": opts.agent,
|
|
6418
|
-
"moltnet.auth.mode":
|
|
6553
|
+
"moltnet.auth.mode": ctx.authMechanism,
|
|
6419
6554
|
"moltnet.llm.provider": profile.provider,
|
|
6420
6555
|
"moltnet.llm.model": profile.model,
|
|
6421
6556
|
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
|
@@ -6545,6 +6680,17 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6545
6680
|
maxBashTimeouts: opts.maxBashTimeouts
|
|
6546
6681
|
});
|
|
6547
6682
|
const executeTask = async (claimedTask, reporter) => {
|
|
6683
|
+
if (runtimeCredentialConfig) await observeGovernancePlanSafely({
|
|
6684
|
+
config: runtimeCredentialConfig,
|
|
6685
|
+
profile,
|
|
6686
|
+
offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
|
|
6687
|
+
registry: createNodeSecretProviderRegistry(),
|
|
6688
|
+
executorFingerprint: preparedRuntime.attestor.fingerprint,
|
|
6689
|
+
claimAuthority: claimedTask.claimAuthority ?? {},
|
|
6690
|
+
taskId: claimedTask.task.id,
|
|
6691
|
+
attemptN: claimedTask.attemptN,
|
|
6692
|
+
logger: rootLogger
|
|
6693
|
+
});
|
|
6548
6694
|
let executionPlan;
|
|
6549
6695
|
try {
|
|
6550
6696
|
executionPlan = await executionPlans.getOrCreate(claimedTask);
|