@themoltnet/agent-daemon 0.44.1 → 0.45.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 +59 -23
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ All config flows from environment variables. The daemon reads them in
|
|
|
91
91
|
|
|
92
92
|
For OAuth2/local mode, the agent's `moltnet.json` and gitconfig live next to
|
|
93
93
|
each other in `.moltnet/<agent>/`. Provision them once via
|
|
94
|
-
[`
|
|
94
|
+
[`moltnet agents init`](../../docs/start/install-and-initialize.md#initialize-an-autonomous-agent).
|
|
95
95
|
|
|
96
96
|
**Auth mode.** When `MOLTNET_AGENT_KEY` is set the daemon authenticates with
|
|
97
97
|
that key as an opaque bearer token (no OAuth2 exchange); otherwise it uses the
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, cre
|
|
|
12
12
|
import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets } from "@themoltnet/pi-runtime";
|
|
13
13
|
import { execFile, execFileSync } from "node:child_process";
|
|
14
14
|
import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
15
|
-
import { AuthenticationError, MoltNetError, connect, createExecutorAttestor, readConfig } from "@themoltnet/sdk";
|
|
15
|
+
import { AuthenticationError, MoltNetError, connect, createExecutorAttestor, readConfig, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
|
|
16
16
|
import { createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
|
|
17
17
|
import { createHash, randomUUID } from "node:crypto";
|
|
18
18
|
import * as ed from "@noble/ed25519";
|
|
@@ -2405,8 +2405,8 @@ async function validateJudgePackInputAsync(input, ctx) {
|
|
|
2405
2405
|
//#endregion
|
|
2406
2406
|
//#region ../../libs/tasks/src/task-types/judge-eval-attempt.ts
|
|
2407
2407
|
/**
|
|
2408
|
-
* `judge_eval_attempt` — score one completed
|
|
2409
|
-
* hidden judge rubric.
|
|
2408
|
+
* `judge_eval_attempt` — score one completed artifact-producing attempt
|
|
2409
|
+
* against a hidden judge rubric.
|
|
2410
2410
|
*
|
|
2411
2411
|
* output_kind: judgment
|
|
2412
2412
|
* criteria: required (`successCriteria.rubric`)
|
|
@@ -2510,9 +2510,9 @@ async function validateJudgeEvalAttemptInputAsync(input, ctx) {
|
|
|
2510
2510
|
field: "targetTaskId",
|
|
2511
2511
|
message: `targetTaskId=${inp.targetTaskId} does not resolve to a task you can read`
|
|
2512
2512
|
}];
|
|
2513
|
-
if (target.
|
|
2513
|
+
if (target.outputKind !== "artifact") errors.push({
|
|
2514
2514
|
field: "targetTaskId",
|
|
2515
|
-
message: `targetTaskId=${inp.targetTaskId}
|
|
2515
|
+
message: `targetTaskId=${inp.targetTaskId} has outputKind=${target.outputKind}; only artifact-producing tasks can be judged`
|
|
2516
2516
|
});
|
|
2517
2517
|
if (!ctx.deferReadinessChecks && (target.status !== "completed" || target.acceptedAttemptN === null)) errors.push({
|
|
2518
2518
|
field: "targetTaskId",
|
|
@@ -2524,7 +2524,7 @@ async function validateJudgeEvalAttemptInputAsync(input, ctx) {
|
|
|
2524
2524
|
});
|
|
2525
2525
|
if (!target.correlationId) errors.push({
|
|
2526
2526
|
field: "targetTaskId",
|
|
2527
|
-
message: "target
|
|
2527
|
+
message: "target producer has no correlation_id; cannot enforce duplicate-judge protection"
|
|
2528
2528
|
});
|
|
2529
2529
|
if (errors.length > 0 || !target.correlationId) return errors;
|
|
2530
2530
|
const rubric = inp.successCriteria.rubric;
|
|
@@ -3433,7 +3433,8 @@ Commands:
|
|
|
3433
3433
|
Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
3434
3434
|
|
|
3435
3435
|
Prerequisites:
|
|
3436
|
-
- agent-key mode: MOLTNET_AGENT_KEY
|
|
3436
|
+
- agent-key mode: MOLTNET_AGENT_KEY (or MOLTNET_AGENT_KEY_REF) and
|
|
3437
|
+
MOLTNET_PRIVATE_KEY (or MOLTNET_PRIVATE_KEY_REF); no agent files
|
|
3437
3438
|
- OAuth2 and sync-sessions: <agent-root>/.moltnet/<agent>/moltnet.json
|
|
3438
3439
|
- --profile — remote runtime profile supplies provider/model/sandbox
|
|
3439
3440
|
policy and CWD is used as the VM mountPath.
|
|
@@ -3563,7 +3564,8 @@ function isHelpFlag(args) {
|
|
|
3563
3564
|
/**
|
|
3564
3565
|
* Report which auth mode `connect()` will use, without ever reading the secret
|
|
3565
3566
|
* value into anything logged. Agent-key mode is selected when
|
|
3566
|
-
* `MOLTNET_AGENT_KEY` holds a non-blank value —
|
|
3567
|
+
* `MOLTNET_AGENT_KEY` or `MOLTNET_AGENT_KEY_REF` holds a non-blank value —
|
|
3568
|
+
* mirroring the SDK precedence
|
|
3567
3569
|
* where an environment key opts into key mode ahead of the config-file OAuth2
|
|
3568
3570
|
* credentials. The daemon never passes explicit in-code credentials to
|
|
3569
3571
|
* `connect()`, so this env-only check matches what `connect()` actually does.
|
|
@@ -3571,7 +3573,7 @@ function isHelpFlag(args) {
|
|
|
3571
3573
|
* Pure: `env` is passed in (the config module owns the `process.env` read).
|
|
3572
3574
|
*/
|
|
3573
3575
|
function detectAuthMode(env) {
|
|
3574
|
-
return env.MOLTNET_AGENT_KEY?.trim() ? "agent-key" : "oauth2";
|
|
3576
|
+
return env.MOLTNET_AGENT_KEY?.trim() || env.MOLTNET_AGENT_KEY_REF?.trim() ? "agent-key" : "oauth2";
|
|
3575
3577
|
}
|
|
3576
3578
|
/**
|
|
3577
3579
|
* Pure check: may the identity described by `whoami` operate the daemon as
|
|
@@ -3637,7 +3639,9 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3637
3639
|
return {
|
|
3638
3640
|
agentDir: join(rootDir, ".moltnet", agentName),
|
|
3639
3641
|
agentRootDir: rootDir,
|
|
3640
|
-
agent: await connect()
|
|
3642
|
+
agent: await connect({ secretProviders: createNodeSecretProviderRegistry() }),
|
|
3643
|
+
credentialSource: "environment",
|
|
3644
|
+
authMechanism: "agent-key"
|
|
3641
3645
|
};
|
|
3642
3646
|
}
|
|
3643
3647
|
const located = locateAgentConfig(roots, agentName);
|
|
@@ -3646,10 +3650,13 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3646
3650
|
configDir: located.agentDir,
|
|
3647
3651
|
secretProviders: createNodeSecretProviderRegistry()
|
|
3648
3652
|
});
|
|
3653
|
+
const config = await readConfig(located.agentDir);
|
|
3649
3654
|
return {
|
|
3650
3655
|
agentDir: located.agentDir,
|
|
3651
3656
|
agentRootDir: located.rootDir,
|
|
3652
|
-
agent
|
|
3657
|
+
agent,
|
|
3658
|
+
credentialSource: "config",
|
|
3659
|
+
authMechanism: config?.agent_key_ref ? "agent-key" : "oauth2"
|
|
3653
3660
|
};
|
|
3654
3661
|
}
|
|
3655
3662
|
const tried = roots.map((root) => join(root, ".moltnet", agentName));
|
|
@@ -3691,6 +3698,8 @@ function resolveCredentialRoots(agentRootDir) {
|
|
|
3691
3698
|
* sprinkling string lookups across the codebase.
|
|
3692
3699
|
*/
|
|
3693
3700
|
function loadConfig() {
|
|
3701
|
+
assertSingleCredentialForm("MOLTNET_AGENT_KEY", "MOLTNET_AGENT_KEY_REF");
|
|
3702
|
+
assertSingleCredentialForm("MOLTNET_PRIVATE_KEY", "MOLTNET_PRIVATE_KEY_REF");
|
|
3694
3703
|
return {
|
|
3695
3704
|
otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
|
|
3696
3705
|
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
@@ -3699,10 +3708,14 @@ function loadConfig() {
|
|
|
3699
3708
|
piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
|
|
3700
3709
|
authMode: detectAuthMode(process.env),
|
|
3701
3710
|
signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
|
|
3711
|
+
signingPrivateKeyRef: process.env["MOLTNET_PRIVATE_KEY_REF"] ?? "",
|
|
3702
3712
|
gitAuthor: process.env["MOLTNET_GIT_AUTHOR"] ?? "",
|
|
3703
3713
|
traceIdlePolling: readBoolean("MOLTNET_TRACE_IDLE_POLLING", process.env["MOLTNET_TRACE_IDLE_POLLING"])
|
|
3704
3714
|
};
|
|
3705
3715
|
}
|
|
3716
|
+
function assertSingleCredentialForm(valueName, refName) {
|
|
3717
|
+
if (process.env[valueName]?.trim() && process.env[refName]?.trim()) throw new Error(`Set only one of ${valueName} or ${refName}`);
|
|
3718
|
+
}
|
|
3706
3719
|
function readBoolean(name, value) {
|
|
3707
3720
|
if (value === void 0 || value === "") return false;
|
|
3708
3721
|
if (value === "true") return true;
|
|
@@ -3910,6 +3923,7 @@ function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmSessionTtlS
|
|
|
3910
3923
|
return {
|
|
3911
3924
|
descriptor,
|
|
3912
3925
|
workspaceMode,
|
|
3926
|
+
workspaceKind: workspaceMode === "scratch_mount" ? "scratch" : void 0,
|
|
3913
3927
|
sessionKey: slotId,
|
|
3914
3928
|
slotKey,
|
|
3915
3929
|
slotId,
|
|
@@ -4203,15 +4217,16 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
4203
4217
|
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
|
|
4204
4218
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
|
|
4205
4219
|
if (resolution.kind === "remote-session") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has a durable runtime session but no workspace metadata to copy`);
|
|
4220
|
+
const producerWorkspaceCopySource = resolveProducerWorkspaceCopySource(resolution.producerSlot, stateDirs);
|
|
4206
4221
|
return {
|
|
4207
4222
|
...basePlan,
|
|
4208
4223
|
workspaceMode: "scratch_mount",
|
|
4209
4224
|
worktreeBranch: null,
|
|
4210
4225
|
workspaceKind: "scratch",
|
|
4211
|
-
workspaceSeed: {
|
|
4212
|
-
copyFromPath:
|
|
4226
|
+
workspaceSeed: producerWorkspaceCopySource ? {
|
|
4227
|
+
copyFromPath: producerWorkspaceCopySource,
|
|
4213
4228
|
source: "producer"
|
|
4214
|
-
},
|
|
4229
|
+
} : null,
|
|
4215
4230
|
sessionPersistence: {
|
|
4216
4231
|
sessionDir: `${stateDirs.piSessionsDir}/judge-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`,
|
|
4217
4232
|
forkFromSessionPath: resolution.sessionPath
|
|
@@ -4254,12 +4269,18 @@ function resolveProducerWorkspaceCopySource(producer, stateDirs) {
|
|
|
4254
4269
|
if (existsSync(workspacePath)) return workspacePath;
|
|
4255
4270
|
const recoveredPath = recoverScratchWorkspacePath(producer, stateDirs);
|
|
4256
4271
|
if (recoveredPath) return recoveredPath;
|
|
4272
|
+
if (isDisposableScratchWorkspace(producer, stateDirs)) return null;
|
|
4257
4273
|
throw new ProducerContextResolutionError(`Producer workspace path is missing on disk: ${workspacePath}`);
|
|
4258
4274
|
}
|
|
4259
4275
|
const sharedMountRoot = dirname(dirname(stateDirs.rootDir));
|
|
4260
4276
|
if (!existsSync(sharedMountRoot)) throw new ProducerContextResolutionError(`Shared producer mount root is missing on disk: ${sharedMountRoot}`);
|
|
4261
4277
|
return sharedMountRoot;
|
|
4262
4278
|
}
|
|
4279
|
+
function isDisposableScratchWorkspace(producer, stateDirs) {
|
|
4280
|
+
if (producer.workspace?.kind === "scratch") return true;
|
|
4281
|
+
if (!producer.workspace?.workspaceId) return false;
|
|
4282
|
+
return producer.workspace.worktreePath === join(stateDirs.rootDir, "task-workspaces", producer.workspace.workspaceId);
|
|
4283
|
+
}
|
|
4263
4284
|
function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
4264
4285
|
if (producer.workspace?.worktreeBranch) return null;
|
|
4265
4286
|
if (!producer.workspace?.workspaceId) return null;
|
|
@@ -4438,12 +4459,25 @@ var DAEMON_REQUIRED_SCOPES = AGENT_CREDENTIAL_SCOPES;
|
|
|
4438
4459
|
async function resolveExecutorSigningPrivateKey(input) {
|
|
4439
4460
|
if (input.authMode === "agent-key") {
|
|
4440
4461
|
const privateKey = input.configuredPrivateKey.trim();
|
|
4441
|
-
if (
|
|
4442
|
-
|
|
4462
|
+
if (privateKey) return privateKey;
|
|
4463
|
+
const reference = input.configuredPrivateKeyRef?.trim();
|
|
4464
|
+
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.");
|
|
4465
|
+
let resolved;
|
|
4466
|
+
try {
|
|
4467
|
+
resolved = await resolveEnvSecretReference(reference, createNodeSecretProviderRegistry());
|
|
4468
|
+
} catch (cause) {
|
|
4469
|
+
throw new Error(`Agent-key daemon startup could not resolve MOLTNET_PRIVATE_KEY_REF: ${cause.message}`, { cause });
|
|
4470
|
+
}
|
|
4471
|
+
if (Buffer.from(resolved, "base64").length !== 32) throw new Error("MOLTNET_PRIVATE_KEY_REF must resolve to a base64-encoded 32-byte Ed25519 seed.");
|
|
4472
|
+
return resolved;
|
|
4473
|
+
}
|
|
4474
|
+
const config = await readConfig(input.agentDir);
|
|
4475
|
+
if (!config) throw new Error(`OAuth2 daemon startup requires ${input.agentDir}/moltnet.json.`);
|
|
4476
|
+
try {
|
|
4477
|
+
return await resolveIdentitySeed(config, createNodeSecretProviderRegistry());
|
|
4478
|
+
} catch (cause) {
|
|
4479
|
+
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
4480
|
}
|
|
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
4481
|
}
|
|
4448
4482
|
function validateDaemonScopes(whoami) {
|
|
4449
4483
|
const available = new Set(whoami.scopes ?? []);
|
|
@@ -5679,7 +5713,8 @@ async function runPolling(opts) {
|
|
|
5679
5713
|
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
5680
5714
|
authMode: cfg.authMode,
|
|
5681
5715
|
agentDir: resolvedContext.agentDir,
|
|
5682
|
-
configuredPrivateKey: cfg.signingPrivateKey
|
|
5716
|
+
configuredPrivateKey: cfg.signingPrivateKey,
|
|
5717
|
+
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
5683
5718
|
});
|
|
5684
5719
|
gate = "validate_scopes";
|
|
5685
5720
|
validateDaemonScopes(whoami);
|
|
@@ -5814,7 +5849,7 @@ async function runPolling(opts) {
|
|
|
5814
5849
|
resourceAttributes: {
|
|
5815
5850
|
"moltnet.team.id": teamId,
|
|
5816
5851
|
"moltnet.agent.name": baseCommon.agent,
|
|
5817
|
-
"moltnet.auth.mode":
|
|
5852
|
+
"moltnet.auth.mode": ctx.authMechanism,
|
|
5818
5853
|
"moltnet.runtime_profile.count": String(profiles.length),
|
|
5819
5854
|
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
5820
5855
|
}
|
|
@@ -6315,7 +6350,8 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6315
6350
|
const privateKey = await resolveExecutorSigningPrivateKey({
|
|
6316
6351
|
authMode: cfg.authMode,
|
|
6317
6352
|
agentDir: resolvedContext.agentDir,
|
|
6318
|
-
configuredPrivateKey: cfg.signingPrivateKey
|
|
6353
|
+
configuredPrivateKey: cfg.signingPrivateKey,
|
|
6354
|
+
configuredPrivateKeyRef: cfg.signingPrivateKeyRef
|
|
6319
6355
|
});
|
|
6320
6356
|
gate = "validate_scopes";
|
|
6321
6357
|
validateDaemonScopes(whoami);
|
|
@@ -6415,7 +6451,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
6415
6451
|
resourceAttributes: {
|
|
6416
6452
|
"moltnet.task.id": taskId,
|
|
6417
6453
|
"moltnet.agent.name": opts.agent,
|
|
6418
|
-
"moltnet.auth.mode":
|
|
6454
|
+
"moltnet.auth.mode": ctx.authMechanism,
|
|
6419
6455
|
"moltnet.llm.provider": profile.provider,
|
|
6420
6456
|
"moltnet.llm.model": profile.model,
|
|
6421
6457
|
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.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.",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"pino-opentelemetry-transport": "^3.0.0",
|
|
79
79
|
"pino-pretty": "^13.1.3",
|
|
80
80
|
"typebox": "^1.2.8",
|
|
81
|
-
"@themoltnet/
|
|
82
|
-
"@themoltnet/
|
|
83
|
-
"@themoltnet/pi-runtime": "0.12.
|
|
84
|
-
"@themoltnet/sdk": "0.
|
|
81
|
+
"@themoltnet/os-keyring": "0.3.0",
|
|
82
|
+
"@themoltnet/agent-runtime": "0.45.0",
|
|
83
|
+
"@themoltnet/pi-runtime": "0.12.2",
|
|
84
|
+
"@themoltnet/sdk": "0.138.0"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"tsx": "^4.7.0",
|