@themoltnet/agent-daemon 0.20.0 → 0.21.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 +11 -17
- package/dist/main.js +246 -235
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -80,8 +80,8 @@ To force a non-repo Pi directory, set
|
|
|
80
80
|
|
|
81
81
|
### Host command auto-approval
|
|
82
82
|
|
|
83
|
-
The daemon reads
|
|
84
|
-
|
|
83
|
+
The daemon reads host-side auto-approval from the selected remote runtime
|
|
84
|
+
profile's sandbox policy. Configure it in the profile, not in task data:
|
|
85
85
|
|
|
86
86
|
```json
|
|
87
87
|
{
|
|
@@ -143,12 +143,11 @@ registered task type; unknown task-type names remain invalid.
|
|
|
143
143
|
gitignored. For API-key auth, keep `.pi/auth.json` absent and export the
|
|
144
144
|
provider key referenced by `.pi/models.json`, for example `OLLAMA_API_KEY`.
|
|
145
145
|
- `ssh-keygen` on `PATH`.
|
|
146
|
-
- A
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
- A runtime profile in the target team. The profile supplies provider, model,
|
|
147
|
+
sandbox policy, and runtime defaults. The daemon mounts the current working
|
|
148
|
+
directory as the VM workspace root.
|
|
149
149
|
|
|
150
|
-
For `themoltnet`, prefer
|
|
151
|
-
the current pnpm/VFS workaround. A minimal `sandbox.json` for another repo:
|
|
150
|
+
For `themoltnet`, prefer a profile sandbox equivalent to this minimal policy:
|
|
152
151
|
|
|
153
152
|
```json
|
|
154
153
|
{
|
|
@@ -251,18 +250,14 @@ pnpm --filter @themoltnet/agent-daemon dev poll \
|
|
|
251
250
|
--agent local-dev \
|
|
252
251
|
--team "$MOLTNET_TEAM_ID" \
|
|
253
252
|
--task-types fulfill_brief \
|
|
254
|
-
--
|
|
255
|
-
--model gpt-5.4-codex \
|
|
253
|
+
--profile "$MOLTNET_AGENT_PROFILE" \
|
|
256
254
|
--debug
|
|
257
255
|
```
|
|
258
256
|
|
|
259
|
-
- If you're starting from a directory without `sandbox.json` at or above it,
|
|
260
|
-
pass `--sandbox <repo-root>/sandbox.json`.
|
|
261
257
|
- `--task-types fulfill_brief` scopes the queue. Omit to accept any
|
|
262
258
|
registered type.
|
|
263
|
-
- Pick provider/model
|
|
264
|
-
|
|
265
|
-
`--provider anthropic --model claude-sonnet-4-6`.
|
|
259
|
+
- Pick a runtime profile whose provider/model matches your Pi auth credits.
|
|
260
|
+
Set `MOLTNET_AGENT_PROFILE` to the profile UUID or team-scoped profile name.
|
|
266
261
|
- `dev` (= `tsx watch src/main.ts`) is fine for local. Use `cli` for a
|
|
267
262
|
one-shot run without watch.
|
|
268
263
|
|
|
@@ -333,8 +328,7 @@ pnpm --filter @themoltnet/agent-daemon dev poll \
|
|
|
333
328
|
--agent local-dev \
|
|
334
329
|
--team "$MOLTNET_TEAM_ID" \
|
|
335
330
|
--task-types pr_review \
|
|
336
|
-
--
|
|
337
|
-
--model gpt-5.4-codex \
|
|
331
|
+
--profile "$MOLTNET_AGENT_PROFILE" \
|
|
338
332
|
--debug
|
|
339
333
|
```
|
|
340
334
|
|
|
@@ -396,7 +390,7 @@ snapshot. The cheap parts of the runtime contract (prompt assembly, tool-side
|
|
|
396
390
|
tests in `libs/pi-extension`. This flow exists for the parts unit tests can't
|
|
397
391
|
reach: real LLM behaviour against the assembled system prompt, real VM, real
|
|
398
392
|
API round-trips, and the interaction between `.moltnet/<agent>/` identity
|
|
399
|
-
material and the
|
|
393
|
+
material and the selected runtime profile.
|
|
400
394
|
|
|
401
395
|
## License
|
|
402
396
|
|
package/dist/main.js
CHANGED
|
@@ -12,7 +12,7 @@ import { parseArgs, promisify } from "node:util";
|
|
|
12
12
|
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource } from "@themoltnet/agent-runtime";
|
|
13
13
|
import { createPiTaskExecutor, findMainWorktree } from "@themoltnet/pi-extension";
|
|
14
14
|
import { execFile, execFileSync } from "node:child_process";
|
|
15
|
-
import { accessSync, constants, existsSync, mkdirSync,
|
|
15
|
+
import { accessSync, constants, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
16
16
|
import { MoltNetError, connect } from "@themoltnet/sdk";
|
|
17
17
|
import { once } from "node:events";
|
|
18
18
|
import { pino, transport } from "pino";
|
|
@@ -21,6 +21,7 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
|
21
21
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
22
22
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
23
23
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
24
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
25
|
//#region ../../libs/observability/src/instrumentation.ts
|
|
25
26
|
/**
|
|
26
27
|
* Register OTel auto-instrumentation for common Node.js modules.
|
|
@@ -4571,7 +4572,7 @@ _Object_({
|
|
|
4571
4572
|
minLength: 1,
|
|
4572
4573
|
maxLength: 100
|
|
4573
4574
|
}),
|
|
4574
|
-
|
|
4575
|
+
runtimeProfileId: Union([String$1({ format: "uuid" }), Null()]),
|
|
4575
4576
|
provider: String$1({
|
|
4576
4577
|
minLength: 1,
|
|
4577
4578
|
maxLength: 100
|
|
@@ -4602,7 +4603,7 @@ _Object_({
|
|
|
4602
4603
|
minLength: 1,
|
|
4603
4604
|
maxLength: 100
|
|
4604
4605
|
}),
|
|
4605
|
-
|
|
4606
|
+
runtimeProfileId: String$1({ format: "uuid" }),
|
|
4606
4607
|
provider: String$1({
|
|
4607
4608
|
minLength: 1,
|
|
4608
4609
|
maxLength: 100
|
|
@@ -4633,6 +4634,7 @@ _Object_({
|
|
|
4633
4634
|
minLength: 1,
|
|
4634
4635
|
maxLength: 100
|
|
4635
4636
|
}),
|
|
4637
|
+
runtimeProfileId: String$1({ format: "uuid" }),
|
|
4636
4638
|
provider: String$1({
|
|
4637
4639
|
minLength: 1,
|
|
4638
4640
|
maxLength: 100
|
|
@@ -9121,23 +9123,14 @@ _Object_({
|
|
|
9121
9123
|
//#region src/lib/help.ts
|
|
9122
9124
|
var COMMON_REQUIRED_FLAGS = `\
|
|
9123
9125
|
-a, --agent <name> MoltNet agent identity. Reads credentials
|
|
9124
|
-
from <repo-root>/.moltnet/<name>/moltnet.json
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
unless --profile is set.`;
|
|
9126
|
+
from <repo-root>/.moltnet/<name>/moltnet.json.
|
|
9127
|
+
--profile <uuid|name> Remote runtime profile. Repeat for poll/drain
|
|
9128
|
+
to declare priority order. Provider, model,
|
|
9129
|
+
sandbox policy, prerequisites, and runtime
|
|
9130
|
+
defaults come from the selected profile.`;
|
|
9130
9131
|
var COMMON_OPTIONAL_FLAGS = `\
|
|
9131
|
-
--sandbox <path>
|
|
9132
|
-
|
|
9133
|
-
containing sandbox.json is also used as the
|
|
9134
|
-
VM mountPath. Cannot be used with --profile.
|
|
9135
|
-
--profile <uuid|name> Remote runtime profile. When set, provider,
|
|
9136
|
-
model, and sandbox policy come from the
|
|
9137
|
-
profile; task listing/claiming is restricted
|
|
9138
|
-
to unrestricted tasks plus tasks allowing this
|
|
9139
|
-
profile. requiredEnv/requiredTools are checked
|
|
9140
|
-
before claiming. Name lookup is team-scoped.
|
|
9132
|
+
--sandbox <path> Deprecated. Remote runtime profiles define
|
|
9133
|
+
sandbox policy.
|
|
9141
9134
|
--lease-ttl-sec <n> Sliding liveness window. Silence longer than
|
|
9142
9135
|
this ends the attempt with lease_expired.
|
|
9143
9136
|
Default: 300.
|
|
@@ -9178,23 +9171,20 @@ Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
|
9178
9171
|
|
|
9179
9172
|
Prerequisites (all subcommands):
|
|
9180
9173
|
- <repo-root>/.moltnet/<agent>/moltnet.json — credentials (see --agent)
|
|
9181
|
-
-
|
|
9182
|
-
|
|
9183
|
-
remote runtime profile supplies provider/model/sandbox policy and CWD
|
|
9184
|
-
is used as the VM mountPath.
|
|
9174
|
+
- --profile — remote runtime profile supplies provider/model/sandbox
|
|
9175
|
+
policy and CWD is used as the VM mountPath.
|
|
9185
9176
|
|
|
9186
9177
|
Registered task types: ${knownTaskTypesList()}`;
|
|
9187
9178
|
var POLL_HELP = `\
|
|
9188
9179
|
agent-daemon poll — long-running task worker.
|
|
9189
9180
|
|
|
9190
9181
|
Usage:
|
|
9191
|
-
agent-daemon poll --team <uuid> --agent <name> --
|
|
9182
|
+
agent-daemon poll --team <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9192
9183
|
|
|
9193
9184
|
Required:
|
|
9194
9185
|
--team <uuid> Team whose queue to serve. The daemon must be
|
|
9195
9186
|
a member of this team (canAccessTeam permit).
|
|
9196
9187
|
${COMMON_REQUIRED_FLAGS}
|
|
9197
|
-
${COMMON_MODEL_FLAGS}
|
|
9198
9188
|
|
|
9199
9189
|
Optional:
|
|
9200
9190
|
--task-types <csv> Whitelist of task types to claim. Default:
|
|
@@ -9211,38 +9201,38 @@ Example:
|
|
|
9211
9201
|
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9212
9202
|
--task-types curate_pack,fulfill_brief \\
|
|
9213
9203
|
--agent legreffier \\
|
|
9214
|
-
--
|
|
9215
|
-
--
|
|
9204
|
+
--profile github-linear \\
|
|
9205
|
+
--profile local-fallback
|
|
9216
9206
|
|
|
9217
9207
|
Stops cleanly on SIGINT/SIGTERM (drains the in-flight task before exit).`;
|
|
9218
9208
|
var ONCE_HELP = `\
|
|
9219
9209
|
agent-daemon once — execute one specific queued task by id, then exit.
|
|
9220
9210
|
|
|
9221
9211
|
Usage:
|
|
9222
|
-
agent-daemon once --task-id <uuid> --agent <name> --
|
|
9212
|
+
agent-daemon once --task-id <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9223
9213
|
|
|
9224
9214
|
Required:
|
|
9225
9215
|
-t, --task-id <uuid> Task to claim and execute. Must already be
|
|
9226
9216
|
in 'queued' status.
|
|
9227
9217
|
${COMMON_REQUIRED_FLAGS}
|
|
9228
|
-
${COMMON_MODEL_FLAGS}
|
|
9229
9218
|
|
|
9230
9219
|
Optional:
|
|
9220
|
+
--team <uuid> Team scope for resolving --profile by name.
|
|
9221
|
+
Required only when --profile is a name.
|
|
9231
9222
|
${COMMON_OPTIONAL_FLAGS}
|
|
9232
9223
|
|
|
9233
9224
|
Example:
|
|
9234
9225
|
agent-daemon once \\
|
|
9235
9226
|
--task-id 26004a77-bc10-43ef-a79f-c8e62faf59b1 \\
|
|
9236
9227
|
--agent legreffier \\
|
|
9237
|
-
--
|
|
9238
|
-
--model claude-sonnet-4-5
|
|
9228
|
+
--profile github-linear
|
|
9239
9229
|
|
|
9240
9230
|
Exits 0 on completed, 1 on failed/cancelled/runtime-error.`;
|
|
9241
9231
|
var DRAIN_HELP = `\
|
|
9242
9232
|
agent-daemon drain — poll until the queue is empty, then exit.
|
|
9243
9233
|
|
|
9244
9234
|
Usage:
|
|
9245
|
-
agent-daemon drain --team <uuid> --agent <name> --
|
|
9235
|
+
agent-daemon drain --team <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9246
9236
|
|
|
9247
9237
|
Same flags as \`poll\`. The only behavioural difference: \`drain\` exits
|
|
9248
9238
|
when a list call confirms no claimable tasks remain (vs \`poll\` which
|
|
@@ -9251,7 +9241,6 @@ sleeps and retries forever).
|
|
|
9251
9241
|
Required:
|
|
9252
9242
|
--team <uuid> Team whose queue to drain.
|
|
9253
9243
|
${COMMON_REQUIRED_FLAGS}
|
|
9254
|
-
${COMMON_MODEL_FLAGS}
|
|
9255
9244
|
|
|
9256
9245
|
Optional:
|
|
9257
9246
|
--task-types <csv> Whitelist. Known types: ${knownTaskTypesList()}
|
|
@@ -9266,8 +9255,7 @@ Example:
|
|
|
9266
9255
|
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9267
9256
|
--task-types judge_pack \\
|
|
9268
9257
|
--agent legreffier \\
|
|
9269
|
-
--
|
|
9270
|
-
--model claude-sonnet-4-5`;
|
|
9258
|
+
--profile eval-judge`;
|
|
9271
9259
|
function isHelpFlag(args) {
|
|
9272
9260
|
return args.includes("--help") || args.includes("-h");
|
|
9273
9261
|
}
|
|
@@ -9472,10 +9460,8 @@ function buildDaemonSlotId(identity, slotKey) {
|
|
|
9472
9460
|
return [
|
|
9473
9461
|
"agent",
|
|
9474
9462
|
slugSlotIdentityComponent(identity.agentName),
|
|
9475
|
-
"
|
|
9476
|
-
slugSlotIdentityComponent(identity.
|
|
9477
|
-
"model",
|
|
9478
|
-
slugSlotIdentityComponent(identity.model),
|
|
9463
|
+
"profile",
|
|
9464
|
+
slugSlotIdentityComponent(identity.runtimeProfileId),
|
|
9479
9465
|
"key",
|
|
9480
9466
|
slotKey
|
|
9481
9467
|
].join(":");
|
|
@@ -9790,7 +9776,6 @@ var MissingRequiredOptionError = class extends Error {
|
|
|
9790
9776
|
}
|
|
9791
9777
|
};
|
|
9792
9778
|
function parseCommonOptions(args, options = {}) {
|
|
9793
|
-
const requireProviderModel = options.requireProviderModel ?? true;
|
|
9794
9779
|
const runtimeDefaults = {
|
|
9795
9780
|
leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
|
|
9796
9781
|
heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
|
|
@@ -9798,13 +9783,9 @@ function parseCommonOptions(args, options = {}) {
|
|
|
9798
9783
|
warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
|
|
9799
9784
|
};
|
|
9800
9785
|
if (!args.agent) throw new MissingRequiredOptionError("agent");
|
|
9801
|
-
if (requireProviderModel && !args.provider) throw new MissingRequiredOptionError("provider");
|
|
9802
|
-
if (requireProviderModel && !args.model) throw new MissingRequiredOptionError("model");
|
|
9803
9786
|
if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
|
|
9804
9787
|
return {
|
|
9805
9788
|
agent: args.agent,
|
|
9806
|
-
...args.provider ? { provider: args.provider } : {},
|
|
9807
|
-
...args.model ? { model: args.model } : {},
|
|
9808
9789
|
leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
|
|
9809
9790
|
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
|
|
9810
9791
|
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
|
|
@@ -9833,14 +9814,6 @@ function commonOptionDefs() {
|
|
|
9833
9814
|
type: "string",
|
|
9834
9815
|
short: "a"
|
|
9835
9816
|
},
|
|
9836
|
-
model: {
|
|
9837
|
-
type: "string",
|
|
9838
|
-
short: "m"
|
|
9839
|
-
},
|
|
9840
|
-
provider: {
|
|
9841
|
-
type: "string",
|
|
9842
|
-
short: "p"
|
|
9843
|
-
},
|
|
9844
9817
|
"lease-ttl-sec": { type: "string" },
|
|
9845
9818
|
"heartbeat-interval-ms": { type: "string" },
|
|
9846
9819
|
"max-batch-size": { type: "string" },
|
|
@@ -9919,6 +9892,12 @@ function ensurePiAgentDir(repoRoot, explicitPath) {
|
|
|
9919
9892
|
};
|
|
9920
9893
|
}
|
|
9921
9894
|
//#endregion
|
|
9895
|
+
//#region src/lib/runtime-context.ts
|
|
9896
|
+
var storage = new AsyncLocalStorage();
|
|
9897
|
+
function runWithDaemonRuntimeContext(context, callback) {
|
|
9898
|
+
return storage.run(context, callback);
|
|
9899
|
+
}
|
|
9900
|
+
//#endregion
|
|
9922
9901
|
//#region src/lib/runtime-profile.ts
|
|
9923
9902
|
var RuntimeProfilePrerequisiteError = class extends Error {
|
|
9924
9903
|
constructor(profileName, missingEnv, missingTools) {
|
|
@@ -9952,6 +9931,22 @@ async function resolveRuntimeProfile(options) {
|
|
|
9952
9931
|
source: `runtime-profile:${profile.id}`
|
|
9953
9932
|
};
|
|
9954
9933
|
}
|
|
9934
|
+
async function resolveRuntimeProfiles(options) {
|
|
9935
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9936
|
+
const out = [];
|
|
9937
|
+
for (const profile of options.profiles) {
|
|
9938
|
+
const resolved = await resolveRuntimeProfile({
|
|
9939
|
+
agent: options.agent,
|
|
9940
|
+
profile,
|
|
9941
|
+
teamId: options.teamId,
|
|
9942
|
+
cwd: options.cwd
|
|
9943
|
+
});
|
|
9944
|
+
if (seen.has(resolved.id)) continue;
|
|
9945
|
+
seen.add(resolved.id);
|
|
9946
|
+
out.push(resolved);
|
|
9947
|
+
}
|
|
9948
|
+
return out;
|
|
9949
|
+
}
|
|
9955
9950
|
function validateRuntimeProfilePrerequisites(profile, env, pathValue) {
|
|
9956
9951
|
const missingEnv = profile.requiredEnv.filter((name) => !env[name]);
|
|
9957
9952
|
const missingTools = profile.requiredTools.filter((tool) => !isExecutableOnPath(tool, pathValue));
|
|
@@ -9986,12 +9981,12 @@ function isExecutable(path) {
|
|
|
9986
9981
|
//#endregion
|
|
9987
9982
|
//#region src/lib/runtime-slots.ts
|
|
9988
9983
|
function createApiRuntimeSlotStore(args) {
|
|
9989
|
-
const { agent
|
|
9984
|
+
const { agent } = args;
|
|
9990
9985
|
return {
|
|
9991
9986
|
async beginSlot(input) {
|
|
9992
9987
|
await agent.runtimeSlots.begin({
|
|
9993
9988
|
agentName: input.agentName,
|
|
9994
|
-
|
|
9989
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
9995
9990
|
lastAttemptN: input.lastAttemptN,
|
|
9996
9991
|
lastTaskId: input.lastTaskId,
|
|
9997
9992
|
model: input.model,
|
|
@@ -10006,12 +10001,13 @@ function createApiRuntimeSlotStore(args) {
|
|
|
10006
10001
|
worktreePath: input.worktreePath ?? void 0
|
|
10007
10002
|
}, { teamId: input.teamId });
|
|
10008
10003
|
},
|
|
10009
|
-
async finishSlot(teamId, taskId, attemptN, identity, slotKey, sessionPath) {
|
|
10004
|
+
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
|
|
10010
10005
|
await agent.runtimeSlots.finish({
|
|
10011
10006
|
agentName: identity.agentName,
|
|
10012
10007
|
attemptN,
|
|
10013
|
-
|
|
10014
|
-
|
|
10008
|
+
runtimeProfileId: identity.runtimeProfileId,
|
|
10009
|
+
model,
|
|
10010
|
+
provider,
|
|
10015
10011
|
sessionPath: sessionPath ?? void 0,
|
|
10016
10012
|
slotKey,
|
|
10017
10013
|
taskId
|
|
@@ -10041,34 +10037,6 @@ function createApiRuntimeSlotStore(args) {
|
|
|
10041
10037
|
};
|
|
10042
10038
|
}
|
|
10043
10039
|
//#endregion
|
|
10044
|
-
//#region src/lib/sandbox.ts
|
|
10045
|
-
function resolveSandbox(startDir, explicitPath) {
|
|
10046
|
-
const path = explicitPath ? isAbsolute(explicitPath) ? explicitPath : resolve(startDir, explicitPath) : findUp(startDir, "sandbox.json");
|
|
10047
|
-
if (!path) throw new Error(`sandbox.json not found in ${startDir} or any parent directory. Pass --sandbox <path> or run the daemon from a directory with sandbox.json at or above it.`);
|
|
10048
|
-
let config;
|
|
10049
|
-
try {
|
|
10050
|
-
config = JSON.parse(readFileSync(path, "utf8"));
|
|
10051
|
-
} catch (err) {
|
|
10052
|
-
const isEnoent = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
10053
|
-
throw new Error(isEnoent ? `sandbox.json not found at ${path}.` : `Failed to read sandbox.json at ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10054
|
-
}
|
|
10055
|
-
return {
|
|
10056
|
-
config,
|
|
10057
|
-
rootDir: dirname(path),
|
|
10058
|
-
path
|
|
10059
|
-
};
|
|
10060
|
-
}
|
|
10061
|
-
function findUp(startDir, filename) {
|
|
10062
|
-
let dir = resolve(startDir);
|
|
10063
|
-
while (true) {
|
|
10064
|
-
const candidate = resolve(dir, filename);
|
|
10065
|
-
if (existsSync(candidate)) return candidate;
|
|
10066
|
-
const parent = dirname(dir);
|
|
10067
|
-
if (parent === dir) return null;
|
|
10068
|
-
dir = parent;
|
|
10069
|
-
}
|
|
10070
|
-
}
|
|
10071
|
-
//#endregion
|
|
10072
10040
|
//#region src/lib/shutdown-signal.ts
|
|
10073
10041
|
function installShutdownSignalHandlers(opts) {
|
|
10074
10042
|
const proc = opts.proc ?? process;
|
|
@@ -10150,7 +10118,10 @@ async function runPolling(opts) {
|
|
|
10150
10118
|
"max-poll-interval-ms": { type: "string" },
|
|
10151
10119
|
"list-limit": { type: "string" },
|
|
10152
10120
|
sandbox: { type: "string" },
|
|
10153
|
-
profile: {
|
|
10121
|
+
profile: {
|
|
10122
|
+
type: "string",
|
|
10123
|
+
multiple: true
|
|
10124
|
+
}
|
|
10154
10125
|
}
|
|
10155
10126
|
});
|
|
10156
10127
|
if (!values.team) {
|
|
@@ -10159,6 +10130,12 @@ async function runPolling(opts) {
|
|
|
10159
10130
|
return 1;
|
|
10160
10131
|
}
|
|
10161
10132
|
const teamId = values.team;
|
|
10133
|
+
const profileValues = parseProfileValues(values.profile);
|
|
10134
|
+
if (profileValues.length === 0) {
|
|
10135
|
+
console.error("Missing required flag: --profile\n");
|
|
10136
|
+
console.error(opts.helpText);
|
|
10137
|
+
return 1;
|
|
10138
|
+
}
|
|
10162
10139
|
let taskTypes;
|
|
10163
10140
|
try {
|
|
10164
10141
|
taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
|
|
@@ -10168,9 +10145,9 @@ async function runPolling(opts) {
|
|
|
10168
10145
|
return 1;
|
|
10169
10146
|
}
|
|
10170
10147
|
const diaryIds = parseCsv(values["diary-ids"]);
|
|
10171
|
-
let
|
|
10148
|
+
let baseCommon;
|
|
10172
10149
|
try {
|
|
10173
|
-
|
|
10150
|
+
baseCommon = parseCommonOptions(values);
|
|
10174
10151
|
} catch (err) {
|
|
10175
10152
|
if (err instanceof MissingRequiredOptionError) {
|
|
10176
10153
|
console.error(`${err.message}\n`);
|
|
@@ -10182,84 +10159,82 @@ async function runPolling(opts) {
|
|
|
10182
10159
|
const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
|
|
10183
10160
|
const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
|
|
10184
10161
|
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
10185
|
-
if (values.
|
|
10186
|
-
console.error(`[${opts.modeLabel}] Cannot use --sandbox
|
|
10162
|
+
if (values.sandbox) {
|
|
10163
|
+
console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
|
|
10187
10164
|
return 1;
|
|
10188
10165
|
}
|
|
10189
10166
|
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).`);
|
|
10190
10167
|
const cfg = loadConfig();
|
|
10191
|
-
const ctx = await resolveAgentContext(
|
|
10192
|
-
const
|
|
10168
|
+
const ctx = await resolveAgentContext(baseCommon.agent);
|
|
10169
|
+
const profiles = await resolveRuntimeProfiles({
|
|
10193
10170
|
agent: ctx.agent,
|
|
10194
|
-
|
|
10171
|
+
profiles: profileValues,
|
|
10195
10172
|
teamId,
|
|
10196
10173
|
cwd: process.cwd()
|
|
10197
|
-
})
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10174
|
+
});
|
|
10175
|
+
for (const profile of profiles) validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
10176
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10177
|
+
const mainRepo = findMainWorktree();
|
|
10178
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
10179
|
+
for (const profile of profiles) {
|
|
10180
|
+
const common = parseCommonOptions(values, { runtimeDefaults: {
|
|
10181
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
10182
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
10183
|
+
maxBatchSize: profile.maxBatchSize,
|
|
10184
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
10185
|
+
} });
|
|
10186
|
+
const sandbox = {
|
|
10187
|
+
config: profile.sandboxConfig,
|
|
10188
|
+
rootDir: profile.mountPath,
|
|
10189
|
+
path: profile.source
|
|
10190
|
+
};
|
|
10191
|
+
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10192
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10193
|
+
const slotIdentity = {
|
|
10194
|
+
agentName: common.agent,
|
|
10195
|
+
runtimeProfileId: profile.id
|
|
10196
|
+
};
|
|
10197
|
+
const executionPlans = createExecutionPlanCache({
|
|
10198
|
+
stateDirs,
|
|
10199
|
+
slotIdentity,
|
|
10200
|
+
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10201
|
+
slotRegistry
|
|
10202
|
+
});
|
|
10203
|
+
runtimes.set(profile.id, {
|
|
10204
|
+
common,
|
|
10205
|
+
profile,
|
|
10206
|
+
sandbox,
|
|
10207
|
+
stateDirs,
|
|
10208
|
+
piAgentDir,
|
|
10209
|
+
slotIdentity,
|
|
10210
|
+
executionPlans
|
|
10208
10211
|
});
|
|
10209
10212
|
}
|
|
10210
|
-
const
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
const sandbox = profile ? {
|
|
10214
|
-
config: profile.sandboxConfig,
|
|
10215
|
-
rootDir: profile.mountPath,
|
|
10216
|
-
path: profile.source
|
|
10217
|
-
} : resolveSandbox(process.cwd(), values.sandbox);
|
|
10218
|
-
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10213
|
+
const firstRuntime = runtimes.get(profiles[0].id);
|
|
10214
|
+
if (!firstRuntime) throw new Error("No runtime profiles resolved");
|
|
10215
|
+
const piAgentDir = firstRuntime.piAgentDir;
|
|
10219
10216
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10220
|
-
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10221
|
-
const slotRegistry = createApiRuntimeSlotStore({
|
|
10222
|
-
agent: ctx.agent,
|
|
10223
|
-
daemonProfileId: profile?.id ?? null
|
|
10224
|
-
});
|
|
10225
|
-
const slotIdentity = {
|
|
10226
|
-
agentName: common.agent,
|
|
10227
|
-
provider,
|
|
10228
|
-
model
|
|
10229
|
-
};
|
|
10230
|
-
const mainRepo = findMainWorktree();
|
|
10231
|
-
const executionPlans = createExecutionPlanCache({
|
|
10232
|
-
stateDirs,
|
|
10233
|
-
slotIdentity,
|
|
10234
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10235
|
-
slotRegistry
|
|
10236
|
-
});
|
|
10237
10217
|
const otelShutdown = await initWorkerOtel({
|
|
10238
10218
|
serviceName: opts.serviceName,
|
|
10239
10219
|
agentDir: ctx.agentDir,
|
|
10240
10220
|
endpoint: cfg.otelEndpoint,
|
|
10241
10221
|
resourceAttributes: {
|
|
10242
10222
|
"moltnet.team.id": teamId,
|
|
10243
|
-
"moltnet.agent.name":
|
|
10244
|
-
"moltnet.
|
|
10245
|
-
"moltnet.
|
|
10246
|
-
...profile ? { "moltnet.daemon_profile.id": profile.id } : {}
|
|
10223
|
+
"moltnet.agent.name": baseCommon.agent,
|
|
10224
|
+
"moltnet.runtime_profile.count": String(profiles.length),
|
|
10225
|
+
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
10247
10226
|
}
|
|
10248
10227
|
});
|
|
10249
10228
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
10250
10229
|
name: `agent-daemon.${opts.modeLabel}`,
|
|
10251
|
-
level: cfg.logLevel || (
|
|
10230
|
+
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
|
|
10252
10231
|
});
|
|
10253
10232
|
const rootLogger = logger.child({
|
|
10254
10233
|
mode: opts.modeLabel,
|
|
10255
|
-
agent:
|
|
10234
|
+
agent: baseCommon.agent,
|
|
10256
10235
|
teamId,
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
...profile ? {
|
|
10260
|
-
daemonProfileId: profile.id,
|
|
10261
|
-
daemonProfileName: profile.name
|
|
10262
|
-
} : {}
|
|
10236
|
+
runtimeProfileIds: profiles.map((p) => p.id),
|
|
10237
|
+
runtimeProfileNames: profiles.map((p) => p.name)
|
|
10263
10238
|
});
|
|
10264
10239
|
const abort = new AbortController();
|
|
10265
10240
|
let runtime = null;
|
|
@@ -10285,90 +10260,91 @@ async function runPolling(opts) {
|
|
|
10285
10260
|
}
|
|
10286
10261
|
});
|
|
10287
10262
|
rootLogger.info({
|
|
10288
|
-
sandbox: sandbox.path,
|
|
10289
10263
|
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
10290
10264
|
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
10291
|
-
leaseTtlSec: common.leaseTtlSec,
|
|
10292
|
-
heartbeatIntervalMs: common.heartbeatIntervalMs,
|
|
10293
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10294
10265
|
pollIntervalMs,
|
|
10295
10266
|
maxPollIntervalMs,
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10267
|
+
profiles: profiles.map((profile) => {
|
|
10268
|
+
const runtime = requireRuntime(runtimes, profile.id);
|
|
10269
|
+
return {
|
|
10270
|
+
id: profile.id,
|
|
10271
|
+
name: profile.name,
|
|
10272
|
+
provider: profile.provider,
|
|
10273
|
+
model: profile.model,
|
|
10274
|
+
sandbox: runtime.sandbox.path,
|
|
10275
|
+
leaseTtlSec: runtime.common.leaseTtlSec,
|
|
10276
|
+
heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
|
|
10277
|
+
warmSessionTtlSec: runtime.common.warmSessionTtlSec,
|
|
10278
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
10279
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
10280
|
+
};
|
|
10281
|
+
}),
|
|
10301
10282
|
piAgentDir: piAgentDir.path,
|
|
10302
10283
|
piAgentDirSource: piAgentDir.source
|
|
10303
10284
|
}, "agent-daemon.starting");
|
|
10304
10285
|
const outputs = [];
|
|
10305
10286
|
try {
|
|
10306
|
-
const rawExecuteTask = createPiTaskExecutor({
|
|
10307
|
-
agentName: common.agent,
|
|
10308
|
-
mountPath: sandbox.rootDir,
|
|
10309
|
-
provider,
|
|
10310
|
-
model,
|
|
10311
|
-
sandboxConfig: sandbox.config,
|
|
10312
|
-
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
10313
|
-
makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger),
|
|
10314
|
-
maxTurns: common.maxTurns,
|
|
10315
|
-
maxBashTimeouts: common.maxBashTimeouts
|
|
10316
|
-
});
|
|
10317
|
-
const executeTask = async (claimedTask, reporter) => {
|
|
10318
|
-
active = {
|
|
10319
|
-
taskId: claimedTask.task.id,
|
|
10320
|
-
attemptN: claimedTask.attemptN
|
|
10321
|
-
};
|
|
10322
|
-
try {
|
|
10323
|
-
return await rawExecuteTask(claimedTask, reporter);
|
|
10324
|
-
} finally {
|
|
10325
|
-
active = null;
|
|
10326
|
-
}
|
|
10327
|
-
};
|
|
10328
10287
|
runtime = new AgentRuntime({
|
|
10329
10288
|
logger: rootLogger,
|
|
10330
10289
|
source: new PollingApiTaskSource({
|
|
10331
10290
|
agent: ctx.agent,
|
|
10332
10291
|
teamId,
|
|
10333
10292
|
taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
|
|
10334
|
-
|
|
10293
|
+
profiles: profiles.map((profile) => ({
|
|
10294
|
+
profileId: profile.id,
|
|
10295
|
+
leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
|
|
10296
|
+
})),
|
|
10335
10297
|
diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
|
|
10336
|
-
leaseTtlSec: common.leaseTtlSec,
|
|
10298
|
+
leaseTtlSec: firstRuntime.common.leaseTtlSec,
|
|
10337
10299
|
listLimit,
|
|
10338
10300
|
pollIntervalMs,
|
|
10339
10301
|
maxPollIntervalMs,
|
|
10340
10302
|
signal: abort.signal,
|
|
10341
10303
|
stopWhenEmpty: opts.stopWhenEmpty,
|
|
10342
|
-
debug:
|
|
10304
|
+
debug: baseCommon.debug,
|
|
10343
10305
|
logger: rootLogger,
|
|
10344
10306
|
slotRegistry
|
|
10345
10307
|
}),
|
|
10346
|
-
makeReporter: () =>
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10308
|
+
makeReporter: (claimedTask) => {
|
|
10309
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10310
|
+
return new ApiTaskReporter({
|
|
10311
|
+
tasks: ctx.agent.tasks,
|
|
10312
|
+
leaseTtlSec: selected.common.leaseTtlSec,
|
|
10313
|
+
heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
|
|
10314
|
+
maxBatchSize: selected.common.maxBatchSize,
|
|
10315
|
+
flushIntervalMs: selected.common.flushIntervalMs
|
|
10316
|
+
});
|
|
10317
|
+
},
|
|
10353
10318
|
onTaskFinished: async (output, claimedTask) => {
|
|
10319
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10354
10320
|
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
10355
10321
|
return finalizeTask(ctx.agent, output, {
|
|
10356
10322
|
task: claimedTask.task,
|
|
10357
10323
|
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
10358
10324
|
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
10359
10325
|
gh: createGhCliClient(),
|
|
10360
|
-
logger: rootLogger
|
|
10326
|
+
logger: rootLogger.child({
|
|
10327
|
+
runtimeProfileId: selected.profile.id,
|
|
10328
|
+
runtimeProfileName: selected.profile.name
|
|
10329
|
+
})
|
|
10361
10330
|
}),
|
|
10362
10331
|
log: (msg, err) => rootLogger.warn({ err }, msg)
|
|
10363
10332
|
});
|
|
10364
10333
|
},
|
|
10365
10334
|
executeTask: async (claimedTask, reporter) => {
|
|
10335
|
+
const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10336
|
+
const taskLogger = rootLogger.child({
|
|
10337
|
+
runtimeProfileId: profile.id,
|
|
10338
|
+
runtimeProfileName: profile.name,
|
|
10339
|
+
provider: profile.provider,
|
|
10340
|
+
model: profile.model
|
|
10341
|
+
});
|
|
10366
10342
|
let executionPlan;
|
|
10367
10343
|
try {
|
|
10368
10344
|
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
10369
10345
|
} catch (err) {
|
|
10370
10346
|
const message = err instanceof Error ? err.message : String(err);
|
|
10371
|
-
|
|
10347
|
+
taskLogger.warn({
|
|
10372
10348
|
taskId: claimedTask.task.id,
|
|
10373
10349
|
attemptN: claimedTask.attemptN,
|
|
10374
10350
|
err: message
|
|
@@ -10392,7 +10368,7 @@ async function runPolling(opts) {
|
|
|
10392
10368
|
};
|
|
10393
10369
|
}
|
|
10394
10370
|
const sessionDescriptor = executionPlan.descriptor;
|
|
10395
|
-
|
|
10371
|
+
taskLogger.debug({
|
|
10396
10372
|
taskId: claimedTask.task.id,
|
|
10397
10373
|
taskType: claimedTask.task.taskType,
|
|
10398
10374
|
resumable: sessionDescriptor.policy.resumable,
|
|
@@ -10441,6 +10417,9 @@ async function runPolling(opts) {
|
|
|
10441
10417
|
};
|
|
10442
10418
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10443
10419
|
...slotIdentity,
|
|
10420
|
+
runtimeProfileId: profile.id,
|
|
10421
|
+
provider: profile.provider,
|
|
10422
|
+
model: profile.model,
|
|
10444
10423
|
teamId: claimedTask.task.teamId,
|
|
10445
10424
|
slotKey: executionPlan.slotKey,
|
|
10446
10425
|
taskType: claimedTask.task.taskType,
|
|
@@ -10453,11 +10432,32 @@ async function runPolling(opts) {
|
|
|
10453
10432
|
lastTaskId: claimedTask.task.id,
|
|
10454
10433
|
lastAttemptN: claimedTask.attemptN
|
|
10455
10434
|
});
|
|
10435
|
+
const rawExecuteTask = createPiTaskExecutor({
|
|
10436
|
+
agentName: common.agent,
|
|
10437
|
+
mountPath: sandbox.rootDir,
|
|
10438
|
+
provider: profile.provider,
|
|
10439
|
+
model: profile.model,
|
|
10440
|
+
sandboxConfig: sandbox.config,
|
|
10441
|
+
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
10442
|
+
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
10443
|
+
maxTurns: common.maxTurns,
|
|
10444
|
+
maxBashTimeouts: common.maxBashTimeouts
|
|
10445
|
+
});
|
|
10456
10446
|
try {
|
|
10457
|
-
|
|
10447
|
+
active = {
|
|
10448
|
+
taskId: claimedTask.task.id,
|
|
10449
|
+
attemptN: claimedTask.attemptN
|
|
10450
|
+
};
|
|
10451
|
+
return await runWithDaemonRuntimeContext({
|
|
10452
|
+
profileId: profile.id,
|
|
10453
|
+
profileName: profile.name,
|
|
10454
|
+
provider: profile.provider,
|
|
10455
|
+
model: profile.model
|
|
10456
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
10458
10457
|
} finally {
|
|
10458
|
+
active = null;
|
|
10459
10459
|
executionPlans.delete(claimedTask);
|
|
10460
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
10460
|
+
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
10461
10461
|
}
|
|
10462
10462
|
}
|
|
10463
10463
|
});
|
|
@@ -10476,6 +10476,18 @@ function resolveRecordedWorkspacePath$1(mainRepo, stateRootDir, executionPlan) {
|
|
|
10476
10476
|
if (!executionPlan.workspaceId) return null;
|
|
10477
10477
|
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(mainRepo, ".worktrees", executionPlan.workspaceId);
|
|
10478
10478
|
}
|
|
10479
|
+
function runtimeForClaimedTask(runtimes, claimedTask) {
|
|
10480
|
+
if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
|
|
10481
|
+
return requireRuntime(runtimes, claimedTask.profileId);
|
|
10482
|
+
}
|
|
10483
|
+
function requireRuntime(runtimes, profileId) {
|
|
10484
|
+
const runtime = runtimes.get(profileId);
|
|
10485
|
+
if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
|
|
10486
|
+
return runtime;
|
|
10487
|
+
}
|
|
10488
|
+
function parseProfileValues(raw) {
|
|
10489
|
+
return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10490
|
+
}
|
|
10479
10491
|
function parseCsv(raw) {
|
|
10480
10492
|
return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10481
10493
|
}
|
|
@@ -10511,6 +10523,7 @@ async function runOnce(argv) {
|
|
|
10511
10523
|
type: "string",
|
|
10512
10524
|
short: "t"
|
|
10513
10525
|
},
|
|
10526
|
+
team: { type: "string" },
|
|
10514
10527
|
sandbox: { type: "string" },
|
|
10515
10528
|
profile: { type: "string" }
|
|
10516
10529
|
}
|
|
@@ -10521,9 +10534,14 @@ async function runOnce(argv) {
|
|
|
10521
10534
|
return 1;
|
|
10522
10535
|
}
|
|
10523
10536
|
const taskId = values["task-id"];
|
|
10537
|
+
if (!values.profile) {
|
|
10538
|
+
console.error("Missing required flag: --profile\n");
|
|
10539
|
+
console.error(ONCE_HELP);
|
|
10540
|
+
return 1;
|
|
10541
|
+
}
|
|
10524
10542
|
let opts;
|
|
10525
10543
|
try {
|
|
10526
|
-
opts = parseCommonOptions(values
|
|
10544
|
+
opts = parseCommonOptions(values);
|
|
10527
10545
|
} catch (err) {
|
|
10528
10546
|
if (err instanceof MissingRequiredOptionError) {
|
|
10529
10547
|
console.error(`${err.message}\n`);
|
|
@@ -10532,48 +10550,37 @@ async function runOnce(argv) {
|
|
|
10532
10550
|
}
|
|
10533
10551
|
throw err;
|
|
10534
10552
|
}
|
|
10535
|
-
if (values.
|
|
10536
|
-
console.error("Cannot use --sandbox
|
|
10553
|
+
if (values.sandbox) {
|
|
10554
|
+
console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
|
|
10537
10555
|
return 1;
|
|
10538
10556
|
}
|
|
10539
10557
|
const cfg = loadConfig();
|
|
10540
10558
|
const ctx = await resolveAgentContext(opts.agent);
|
|
10541
|
-
const profile =
|
|
10559
|
+
const profile = await resolveRuntimeProfile({
|
|
10542
10560
|
agent: ctx.agent,
|
|
10543
10561
|
profile: values.profile,
|
|
10562
|
+
teamId: values.team,
|
|
10544
10563
|
cwd: process.cwd()
|
|
10545
|
-
})
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
10555
|
-
}
|
|
10556
|
-
});
|
|
10557
|
-
}
|
|
10558
|
-
const provider = profile?.provider ?? opts.provider;
|
|
10559
|
-
const model = profile?.model ?? opts.model;
|
|
10560
|
-
if (!provider || !model) throw new Error("provider/model missing after runtime profile resolution");
|
|
10561
|
-
const sandbox = profile ? {
|
|
10564
|
+
});
|
|
10565
|
+
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
10566
|
+
opts = parseCommonOptions(values, { runtimeDefaults: {
|
|
10567
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
10568
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
10569
|
+
maxBatchSize: profile.maxBatchSize,
|
|
10570
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
10571
|
+
} });
|
|
10572
|
+
const sandbox = {
|
|
10562
10573
|
config: profile.sandboxConfig,
|
|
10563
10574
|
rootDir: profile.mountPath,
|
|
10564
10575
|
path: profile.source
|
|
10565
|
-
}
|
|
10576
|
+
};
|
|
10566
10577
|
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10567
10578
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10568
10579
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10569
|
-
const slotRegistry = createApiRuntimeSlotStore({
|
|
10570
|
-
agent: ctx.agent,
|
|
10571
|
-
daemonProfileId: profile?.id ?? null
|
|
10572
|
-
});
|
|
10580
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10573
10581
|
const slotIdentity = {
|
|
10574
10582
|
agentName: opts.agent,
|
|
10575
|
-
|
|
10576
|
-
model
|
|
10583
|
+
runtimeProfileId: profile.id
|
|
10577
10584
|
};
|
|
10578
10585
|
const mainRepo = findMainWorktree();
|
|
10579
10586
|
const executionPlans = createExecutionPlanCache({
|
|
@@ -10589,9 +10596,9 @@ async function runOnce(argv) {
|
|
|
10589
10596
|
resourceAttributes: {
|
|
10590
10597
|
"moltnet.task.id": taskId,
|
|
10591
10598
|
"moltnet.agent.name": opts.agent,
|
|
10592
|
-
"moltnet.llm.provider": provider,
|
|
10593
|
-
"moltnet.llm.model": model,
|
|
10594
|
-
|
|
10599
|
+
"moltnet.llm.provider": profile.provider,
|
|
10600
|
+
"moltnet.llm.model": profile.model,
|
|
10601
|
+
"moltnet.runtime_profile.id": profile.id
|
|
10595
10602
|
}
|
|
10596
10603
|
});
|
|
10597
10604
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
@@ -10601,12 +10608,10 @@ async function runOnce(argv) {
|
|
|
10601
10608
|
const rootLogger = logger.child({
|
|
10602
10609
|
mode: "once",
|
|
10603
10610
|
agent: opts.agent,
|
|
10604
|
-
provider,
|
|
10605
|
-
model,
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
daemonProfileName: profile.name
|
|
10609
|
-
} : {}
|
|
10611
|
+
provider: profile.provider,
|
|
10612
|
+
model: profile.model,
|
|
10613
|
+
runtimeProfileId: profile.id,
|
|
10614
|
+
runtimeProfileName: profile.name
|
|
10610
10615
|
});
|
|
10611
10616
|
rootLogger.info({
|
|
10612
10617
|
sandbox: sandbox.path,
|
|
@@ -10614,11 +10619,9 @@ async function runOnce(argv) {
|
|
|
10614
10619
|
leaseTtlSec: opts.leaseTtlSec,
|
|
10615
10620
|
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
10616
10621
|
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
10621
|
-
} : {},
|
|
10622
|
+
profileId: profile.id,
|
|
10623
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
10624
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
10622
10625
|
piAgentDir: piAgentDir.path,
|
|
10623
10626
|
piAgentDirSource: piAgentDir.source
|
|
10624
10627
|
}, "agent-daemon.starting");
|
|
@@ -10652,8 +10655,8 @@ async function runOnce(argv) {
|
|
|
10652
10655
|
const rawExecuteTask = createPiTaskExecutor({
|
|
10653
10656
|
agentName: opts.agent,
|
|
10654
10657
|
mountPath: sandbox.rootDir,
|
|
10655
|
-
provider,
|
|
10656
|
-
model,
|
|
10658
|
+
provider: profile.provider,
|
|
10659
|
+
model: profile.model,
|
|
10657
10660
|
sandboxConfig: sandbox.config,
|
|
10658
10661
|
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
10659
10662
|
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
@@ -10691,6 +10694,9 @@ async function runOnce(argv) {
|
|
|
10691
10694
|
}
|
|
10692
10695
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10693
10696
|
...slotIdentity,
|
|
10697
|
+
runtimeProfileId: profile.id,
|
|
10698
|
+
provider: profile.provider,
|
|
10699
|
+
model: profile.model,
|
|
10694
10700
|
teamId: claimedTask.task.teamId,
|
|
10695
10701
|
slotKey: executionPlan.slotKey,
|
|
10696
10702
|
taskType: claimedTask.task.taskType,
|
|
@@ -10705,11 +10711,16 @@ async function runOnce(argv) {
|
|
|
10705
10711
|
});
|
|
10706
10712
|
activeAttemptN = claimedTask.attemptN;
|
|
10707
10713
|
try {
|
|
10708
|
-
return await
|
|
10714
|
+
return await runWithDaemonRuntimeContext({
|
|
10715
|
+
profileId: profile.id,
|
|
10716
|
+
profileName: profile.name,
|
|
10717
|
+
provider: profile.provider,
|
|
10718
|
+
model: profile.model
|
|
10719
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
10709
10720
|
} finally {
|
|
10710
10721
|
activeAttemptN = null;
|
|
10711
10722
|
executionPlans.delete(claimedTask);
|
|
10712
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
10723
|
+
if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
|
|
10713
10724
|
}
|
|
10714
10725
|
};
|
|
10715
10726
|
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
@@ -10722,7 +10733,7 @@ async function runOnce(argv) {
|
|
|
10722
10733
|
agent: ctx.agent,
|
|
10723
10734
|
taskId,
|
|
10724
10735
|
leaseTtlSec: opts.leaseTtlSec,
|
|
10725
|
-
|
|
10736
|
+
profileId: profile.id
|
|
10726
10737
|
}),
|
|
10727
10738
|
makeReporter: () => new ApiTaskReporter({
|
|
10728
10739
|
tasks: ctx.agent.tasks,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MoltNet agent daemon — claims and executes tasks (fulfill_brief, assess_brief) from the MoltNet task-service via Pi-headless. CLI: moltnet-agent.",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
46
46
|
"pino": "^10.3.1",
|
|
47
47
|
"pino-pretty": "^13.1.3",
|
|
48
|
-
"@themoltnet/agent-runtime": "0.
|
|
49
|
-
"@themoltnet/pi-extension": "0.26.
|
|
48
|
+
"@themoltnet/agent-runtime": "0.28.0",
|
|
49
|
+
"@themoltnet/pi-extension": "0.26.2",
|
|
50
50
|
"@themoltnet/sdk": "0.109.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"typescript": "~5.9.2",
|
|
55
55
|
"vite": "^8.0.0",
|
|
56
56
|
"vitest": "^3.0.0",
|
|
57
|
-
"@moltnet/bootstrap": "0.1.0",
|
|
58
57
|
"@moltnet/crypto-service": "0.1.0",
|
|
59
58
|
"@moltnet/observability": "0.1.0",
|
|
59
|
+
"@moltnet/bootstrap": "0.1.0",
|
|
60
60
|
"@moltnet/tasks": "0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"nx": {
|