@themoltnet/agent-daemon 0.20.0 → 0.22.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 +287 -254
- package/package.json +6 -6
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,16 @@ _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 <
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
unless --profile is set.`;
|
|
9126
|
+
from <agent-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
|
-
|
|
9134
|
-
|
|
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.
|
|
9134
|
+
--agent-root <path> Directory that owns .moltnet/<agent>. Default:
|
|
9135
|
+
CWD, with git root fallback when available.
|
|
9141
9136
|
--lease-ttl-sec <n> Sliding liveness window. Silence longer than
|
|
9142
9137
|
this ends the attempt with lease_expired.
|
|
9143
9138
|
Default: 300.
|
|
@@ -9177,24 +9172,21 @@ Commands:
|
|
|
9177
9172
|
Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
9178
9173
|
|
|
9179
9174
|
Prerequisites (all subcommands):
|
|
9180
|
-
- <
|
|
9181
|
-
-
|
|
9182
|
-
|
|
9183
|
-
remote runtime profile supplies provider/model/sandbox policy and CWD
|
|
9184
|
-
is used as the VM mountPath.
|
|
9175
|
+
- <agent-root>/.moltnet/<agent>/moltnet.json — credentials (see --agent-root)
|
|
9176
|
+
- --profile — remote runtime profile supplies provider/model/sandbox
|
|
9177
|
+
policy and CWD is used as the VM mountPath.
|
|
9185
9178
|
|
|
9186
9179
|
Registered task types: ${knownTaskTypesList()}`;
|
|
9187
9180
|
var POLL_HELP = `\
|
|
9188
9181
|
agent-daemon poll — long-running task worker.
|
|
9189
9182
|
|
|
9190
9183
|
Usage:
|
|
9191
|
-
agent-daemon poll --team <uuid> --agent <name> --
|
|
9184
|
+
agent-daemon poll --team <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9192
9185
|
|
|
9193
9186
|
Required:
|
|
9194
9187
|
--team <uuid> Team whose queue to serve. The daemon must be
|
|
9195
9188
|
a member of this team (canAccessTeam permit).
|
|
9196
9189
|
${COMMON_REQUIRED_FLAGS}
|
|
9197
|
-
${COMMON_MODEL_FLAGS}
|
|
9198
9190
|
|
|
9199
9191
|
Optional:
|
|
9200
9192
|
--task-types <csv> Whitelist of task types to claim. Default:
|
|
@@ -9211,38 +9203,38 @@ Example:
|
|
|
9211
9203
|
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9212
9204
|
--task-types curate_pack,fulfill_brief \\
|
|
9213
9205
|
--agent legreffier \\
|
|
9214
|
-
--
|
|
9215
|
-
--
|
|
9206
|
+
--profile github-linear \\
|
|
9207
|
+
--profile local-fallback
|
|
9216
9208
|
|
|
9217
9209
|
Stops cleanly on SIGINT/SIGTERM (drains the in-flight task before exit).`;
|
|
9218
9210
|
var ONCE_HELP = `\
|
|
9219
9211
|
agent-daemon once — execute one specific queued task by id, then exit.
|
|
9220
9212
|
|
|
9221
9213
|
Usage:
|
|
9222
|
-
agent-daemon once --task-id <uuid> --agent <name> --
|
|
9214
|
+
agent-daemon once --task-id <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9223
9215
|
|
|
9224
9216
|
Required:
|
|
9225
9217
|
-t, --task-id <uuid> Task to claim and execute. Must already be
|
|
9226
9218
|
in 'queued' status.
|
|
9227
9219
|
${COMMON_REQUIRED_FLAGS}
|
|
9228
|
-
${COMMON_MODEL_FLAGS}
|
|
9229
9220
|
|
|
9230
9221
|
Optional:
|
|
9222
|
+
--team <uuid> Team scope for resolving --profile by name.
|
|
9223
|
+
Required only when --profile is a name.
|
|
9231
9224
|
${COMMON_OPTIONAL_FLAGS}
|
|
9232
9225
|
|
|
9233
9226
|
Example:
|
|
9234
9227
|
agent-daemon once \\
|
|
9235
9228
|
--task-id 26004a77-bc10-43ef-a79f-c8e62faf59b1 \\
|
|
9236
9229
|
--agent legreffier \\
|
|
9237
|
-
--
|
|
9238
|
-
--model claude-sonnet-4-5
|
|
9230
|
+
--profile github-linear
|
|
9239
9231
|
|
|
9240
9232
|
Exits 0 on completed, 1 on failed/cancelled/runtime-error.`;
|
|
9241
9233
|
var DRAIN_HELP = `\
|
|
9242
9234
|
agent-daemon drain — poll until the queue is empty, then exit.
|
|
9243
9235
|
|
|
9244
9236
|
Usage:
|
|
9245
|
-
agent-daemon drain --team <uuid> --agent <name> --
|
|
9237
|
+
agent-daemon drain --team <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9246
9238
|
|
|
9247
9239
|
Same flags as \`poll\`. The only behavioural difference: \`drain\` exits
|
|
9248
9240
|
when a list call confirms no claimable tasks remain (vs \`poll\` which
|
|
@@ -9251,7 +9243,6 @@ sleeps and retries forever).
|
|
|
9251
9243
|
Required:
|
|
9252
9244
|
--team <uuid> Team whose queue to drain.
|
|
9253
9245
|
${COMMON_REQUIRED_FLAGS}
|
|
9254
|
-
${COMMON_MODEL_FLAGS}
|
|
9255
9246
|
|
|
9256
9247
|
Optional:
|
|
9257
9248
|
--task-types <csv> Whitelist. Known types: ${knownTaskTypesList()}
|
|
@@ -9266,8 +9257,7 @@ Example:
|
|
|
9266
9257
|
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9267
9258
|
--task-types judge_pack \\
|
|
9268
9259
|
--agent legreffier \\
|
|
9269
|
-
--
|
|
9270
|
-
--model claude-sonnet-4-5`;
|
|
9260
|
+
--profile eval-judge`;
|
|
9271
9261
|
function isHelpFlag(args) {
|
|
9272
9262
|
return args.includes("--help") || args.includes("-h");
|
|
9273
9263
|
}
|
|
@@ -9290,18 +9280,34 @@ function activatePiCodingAgentDir(path) {
|
|
|
9290
9280
|
/**
|
|
9291
9281
|
* Resolve the agent's MoltNet credentials directory and connect via SDK.
|
|
9292
9282
|
*
|
|
9293
|
-
* Looks under
|
|
9294
|
-
* is missing — credentials are required,
|
|
9295
|
-
* unauthenticated calls.
|
|
9283
|
+
* Looks under an explicit agent root first, then falls back to the git root
|
|
9284
|
+
* when available. Fails fast if the dir is missing — credentials are required,
|
|
9285
|
+
* the daemon never falls back to unauthenticated calls.
|
|
9296
9286
|
*/
|
|
9297
|
-
async function resolveAgentContext(agentName) {
|
|
9287
|
+
async function resolveAgentContext(agentName, options = {}) {
|
|
9298
9288
|
if (!/^[a-zA-Z0-9_-]+$/.test(agentName)) throw new Error(`Invalid agent name "${agentName}": must match /^[a-zA-Z0-9_-]+$/`);
|
|
9299
|
-
const
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
agentDir,
|
|
9303
|
-
|
|
9304
|
-
|
|
9289
|
+
const roots = resolveCredentialRoots(options.agentRootDir);
|
|
9290
|
+
for (const rootDir of roots) {
|
|
9291
|
+
const agentDir = join(rootDir, ".moltnet", agentName);
|
|
9292
|
+
if (existsSync(join(agentDir, "moltnet.json"))) return {
|
|
9293
|
+
agentDir,
|
|
9294
|
+
agentRootDir: rootDir,
|
|
9295
|
+
agent: await connect({ configDir: agentDir })
|
|
9296
|
+
};
|
|
9297
|
+
}
|
|
9298
|
+
const tried = roots.map((root) => join(root, ".moltnet", agentName));
|
|
9299
|
+
throw new Error(`Missing credentials for ${agentName}. Checked ${tried.join(", ")}. Run the agent onboarding flow first.`);
|
|
9300
|
+
}
|
|
9301
|
+
function resolveCredentialRoots(agentRootDir) {
|
|
9302
|
+
const roots = agentRootDir ? [agentRootDir] : [];
|
|
9303
|
+
try {
|
|
9304
|
+
const gitRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
9305
|
+
encoding: "utf8",
|
|
9306
|
+
stdio: "pipe"
|
|
9307
|
+
}).trim();
|
|
9308
|
+
if (!roots.includes(gitRoot)) roots.push(gitRoot);
|
|
9309
|
+
} catch {}
|
|
9310
|
+
return roots;
|
|
9305
9311
|
}
|
|
9306
9312
|
//#endregion
|
|
9307
9313
|
//#region src/lib/correlation.ts
|
|
@@ -9472,10 +9478,8 @@ function buildDaemonSlotId(identity, slotKey) {
|
|
|
9472
9478
|
return [
|
|
9473
9479
|
"agent",
|
|
9474
9480
|
slugSlotIdentityComponent(identity.agentName),
|
|
9475
|
-
"
|
|
9476
|
-
slugSlotIdentityComponent(identity.
|
|
9477
|
-
"model",
|
|
9478
|
-
slugSlotIdentityComponent(identity.model),
|
|
9481
|
+
"profile",
|
|
9482
|
+
slugSlotIdentityComponent(identity.runtimeProfileId),
|
|
9479
9483
|
"key",
|
|
9480
9484
|
slotKey
|
|
9481
9485
|
].join(":");
|
|
@@ -9790,7 +9794,6 @@ var MissingRequiredOptionError = class extends Error {
|
|
|
9790
9794
|
}
|
|
9791
9795
|
};
|
|
9792
9796
|
function parseCommonOptions(args, options = {}) {
|
|
9793
|
-
const requireProviderModel = options.requireProviderModel ?? true;
|
|
9794
9797
|
const runtimeDefaults = {
|
|
9795
9798
|
leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
|
|
9796
9799
|
heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
|
|
@@ -9798,13 +9801,9 @@ function parseCommonOptions(args, options = {}) {
|
|
|
9798
9801
|
warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
|
|
9799
9802
|
};
|
|
9800
9803
|
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
9804
|
if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
|
|
9804
9805
|
return {
|
|
9805
9806
|
agent: args.agent,
|
|
9806
|
-
...args.provider ? { provider: args.provider } : {},
|
|
9807
|
-
...args.model ? { model: args.model } : {},
|
|
9808
9807
|
leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
|
|
9809
9808
|
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
|
|
9810
9809
|
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
|
|
@@ -9833,14 +9832,7 @@ function commonOptionDefs() {
|
|
|
9833
9832
|
type: "string",
|
|
9834
9833
|
short: "a"
|
|
9835
9834
|
},
|
|
9836
|
-
|
|
9837
|
-
type: "string",
|
|
9838
|
-
short: "m"
|
|
9839
|
-
},
|
|
9840
|
-
provider: {
|
|
9841
|
-
type: "string",
|
|
9842
|
-
short: "p"
|
|
9843
|
-
},
|
|
9835
|
+
"agent-root": { type: "string" },
|
|
9844
9836
|
"lease-ttl-sec": { type: "string" },
|
|
9845
9837
|
"heartbeat-interval-ms": { type: "string" },
|
|
9846
9838
|
"max-batch-size": { type: "string" },
|
|
@@ -9919,6 +9911,12 @@ function ensurePiAgentDir(repoRoot, explicitPath) {
|
|
|
9919
9911
|
};
|
|
9920
9912
|
}
|
|
9921
9913
|
//#endregion
|
|
9914
|
+
//#region src/lib/runtime-context.ts
|
|
9915
|
+
var storage = new AsyncLocalStorage();
|
|
9916
|
+
function runWithDaemonRuntimeContext(context, callback) {
|
|
9917
|
+
return storage.run(context, callback);
|
|
9918
|
+
}
|
|
9919
|
+
//#endregion
|
|
9922
9920
|
//#region src/lib/runtime-profile.ts
|
|
9923
9921
|
var RuntimeProfilePrerequisiteError = class extends Error {
|
|
9924
9922
|
constructor(profileName, missingEnv, missingTools) {
|
|
@@ -9952,6 +9950,22 @@ async function resolveRuntimeProfile(options) {
|
|
|
9952
9950
|
source: `runtime-profile:${profile.id}`
|
|
9953
9951
|
};
|
|
9954
9952
|
}
|
|
9953
|
+
async function resolveRuntimeProfiles(options) {
|
|
9954
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9955
|
+
const out = [];
|
|
9956
|
+
for (const profile of options.profiles) {
|
|
9957
|
+
const resolved = await resolveRuntimeProfile({
|
|
9958
|
+
agent: options.agent,
|
|
9959
|
+
profile,
|
|
9960
|
+
teamId: options.teamId,
|
|
9961
|
+
cwd: options.cwd
|
|
9962
|
+
});
|
|
9963
|
+
if (seen.has(resolved.id)) continue;
|
|
9964
|
+
seen.add(resolved.id);
|
|
9965
|
+
out.push(resolved);
|
|
9966
|
+
}
|
|
9967
|
+
return out;
|
|
9968
|
+
}
|
|
9955
9969
|
function validateRuntimeProfilePrerequisites(profile, env, pathValue) {
|
|
9956
9970
|
const missingEnv = profile.requiredEnv.filter((name) => !env[name]);
|
|
9957
9971
|
const missingTools = profile.requiredTools.filter((tool) => !isExecutableOnPath(tool, pathValue));
|
|
@@ -9986,12 +10000,12 @@ function isExecutable(path) {
|
|
|
9986
10000
|
//#endregion
|
|
9987
10001
|
//#region src/lib/runtime-slots.ts
|
|
9988
10002
|
function createApiRuntimeSlotStore(args) {
|
|
9989
|
-
const { agent
|
|
10003
|
+
const { agent } = args;
|
|
9990
10004
|
return {
|
|
9991
10005
|
async beginSlot(input) {
|
|
9992
10006
|
await agent.runtimeSlots.begin({
|
|
9993
10007
|
agentName: input.agentName,
|
|
9994
|
-
|
|
10008
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
9995
10009
|
lastAttemptN: input.lastAttemptN,
|
|
9996
10010
|
lastTaskId: input.lastTaskId,
|
|
9997
10011
|
model: input.model,
|
|
@@ -10006,12 +10020,13 @@ function createApiRuntimeSlotStore(args) {
|
|
|
10006
10020
|
worktreePath: input.worktreePath ?? void 0
|
|
10007
10021
|
}, { teamId: input.teamId });
|
|
10008
10022
|
},
|
|
10009
|
-
async finishSlot(teamId, taskId, attemptN, identity, slotKey, sessionPath) {
|
|
10023
|
+
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
|
|
10010
10024
|
await agent.runtimeSlots.finish({
|
|
10011
10025
|
agentName: identity.agentName,
|
|
10012
10026
|
attemptN,
|
|
10013
|
-
|
|
10014
|
-
|
|
10027
|
+
runtimeProfileId: identity.runtimeProfileId,
|
|
10028
|
+
model,
|
|
10029
|
+
provider,
|
|
10015
10030
|
sessionPath: sessionPath ?? void 0,
|
|
10016
10031
|
slotKey,
|
|
10017
10032
|
taskId
|
|
@@ -10041,34 +10056,6 @@ function createApiRuntimeSlotStore(args) {
|
|
|
10041
10056
|
};
|
|
10042
10057
|
}
|
|
10043
10058
|
//#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
10059
|
//#region src/lib/shutdown-signal.ts
|
|
10073
10060
|
function installShutdownSignalHandlers(opts) {
|
|
10074
10061
|
const proc = opts.proc ?? process;
|
|
@@ -10150,7 +10137,10 @@ async function runPolling(opts) {
|
|
|
10150
10137
|
"max-poll-interval-ms": { type: "string" },
|
|
10151
10138
|
"list-limit": { type: "string" },
|
|
10152
10139
|
sandbox: { type: "string" },
|
|
10153
|
-
profile: {
|
|
10140
|
+
profile: {
|
|
10141
|
+
type: "string",
|
|
10142
|
+
multiple: true
|
|
10143
|
+
}
|
|
10154
10144
|
}
|
|
10155
10145
|
});
|
|
10156
10146
|
if (!values.team) {
|
|
@@ -10159,6 +10149,12 @@ async function runPolling(opts) {
|
|
|
10159
10149
|
return 1;
|
|
10160
10150
|
}
|
|
10161
10151
|
const teamId = values.team;
|
|
10152
|
+
const profileValues = parseProfileValues(values.profile);
|
|
10153
|
+
if (profileValues.length === 0) {
|
|
10154
|
+
console.error("Missing required flag: --profile\n");
|
|
10155
|
+
console.error(opts.helpText);
|
|
10156
|
+
return 1;
|
|
10157
|
+
}
|
|
10162
10158
|
let taskTypes;
|
|
10163
10159
|
try {
|
|
10164
10160
|
taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
|
|
@@ -10168,9 +10164,9 @@ async function runPolling(opts) {
|
|
|
10168
10164
|
return 1;
|
|
10169
10165
|
}
|
|
10170
10166
|
const diaryIds = parseCsv(values["diary-ids"]);
|
|
10171
|
-
let
|
|
10167
|
+
let baseCommon;
|
|
10172
10168
|
try {
|
|
10173
|
-
|
|
10169
|
+
baseCommon = parseCommonOptions(values);
|
|
10174
10170
|
} catch (err) {
|
|
10175
10171
|
if (err instanceof MissingRequiredOptionError) {
|
|
10176
10172
|
console.error(`${err.message}\n`);
|
|
@@ -10182,84 +10178,82 @@ async function runPolling(opts) {
|
|
|
10182
10178
|
const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
|
|
10183
10179
|
const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
|
|
10184
10180
|
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
10185
|
-
if (values.
|
|
10186
|
-
console.error(`[${opts.modeLabel}] Cannot use --sandbox
|
|
10181
|
+
if (values.sandbox) {
|
|
10182
|
+
console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
|
|
10187
10183
|
return 1;
|
|
10188
10184
|
}
|
|
10189
10185
|
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
10186
|
const cfg = loadConfig();
|
|
10191
|
-
const
|
|
10192
|
-
const
|
|
10187
|
+
const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
|
|
10188
|
+
const ctx = await resolveAgentContext(baseCommon.agent, { agentRootDir });
|
|
10189
|
+
const profiles = await resolveRuntimeProfiles({
|
|
10193
10190
|
agent: ctx.agent,
|
|
10194
|
-
|
|
10191
|
+
profiles: profileValues,
|
|
10195
10192
|
teamId,
|
|
10196
10193
|
cwd: process.cwd()
|
|
10197
|
-
})
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10194
|
+
});
|
|
10195
|
+
for (const profile of profiles) validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
10196
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10197
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
10198
|
+
for (const profile of profiles) {
|
|
10199
|
+
const common = parseCommonOptions(values, { runtimeDefaults: {
|
|
10200
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
10201
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
10202
|
+
maxBatchSize: profile.maxBatchSize,
|
|
10203
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
10204
|
+
} });
|
|
10205
|
+
const sandbox = {
|
|
10206
|
+
config: profile.sandboxConfig,
|
|
10207
|
+
rootDir: profile.mountPath,
|
|
10208
|
+
path: profile.source
|
|
10209
|
+
};
|
|
10210
|
+
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10211
|
+
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10212
|
+
const slotIdentity = {
|
|
10213
|
+
agentName: common.agent,
|
|
10214
|
+
runtimeProfileId: profile.id
|
|
10215
|
+
};
|
|
10216
|
+
const executionPlans = createExecutionPlanCache({
|
|
10217
|
+
stateDirs,
|
|
10218
|
+
slotIdentity,
|
|
10219
|
+
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10220
|
+
slotRegistry
|
|
10221
|
+
});
|
|
10222
|
+
runtimes.set(profile.id, {
|
|
10223
|
+
common,
|
|
10224
|
+
profile,
|
|
10225
|
+
sandbox,
|
|
10226
|
+
stateDirs,
|
|
10227
|
+
piAgentDir,
|
|
10228
|
+
slotIdentity,
|
|
10229
|
+
executionPlans
|
|
10208
10230
|
});
|
|
10209
10231
|
}
|
|
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);
|
|
10232
|
+
const firstRuntime = runtimes.get(profiles[0].id);
|
|
10233
|
+
if (!firstRuntime) throw new Error("No runtime profiles resolved");
|
|
10234
|
+
const piAgentDir = firstRuntime.piAgentDir;
|
|
10219
10235
|
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
10236
|
const otelShutdown = await initWorkerOtel({
|
|
10238
10237
|
serviceName: opts.serviceName,
|
|
10239
10238
|
agentDir: ctx.agentDir,
|
|
10240
10239
|
endpoint: cfg.otelEndpoint,
|
|
10241
10240
|
resourceAttributes: {
|
|
10242
10241
|
"moltnet.team.id": teamId,
|
|
10243
|
-
"moltnet.agent.name":
|
|
10244
|
-
"moltnet.
|
|
10245
|
-
"moltnet.
|
|
10246
|
-
...profile ? { "moltnet.daemon_profile.id": profile.id } : {}
|
|
10242
|
+
"moltnet.agent.name": baseCommon.agent,
|
|
10243
|
+
"moltnet.runtime_profile.count": String(profiles.length),
|
|
10244
|
+
"moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
|
|
10247
10245
|
}
|
|
10248
10246
|
});
|
|
10249
10247
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
10250
10248
|
name: `agent-daemon.${opts.modeLabel}`,
|
|
10251
|
-
level: cfg.logLevel || (
|
|
10249
|
+
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
|
|
10252
10250
|
});
|
|
10253
10251
|
const rootLogger = logger.child({
|
|
10254
10252
|
mode: opts.modeLabel,
|
|
10255
|
-
agent:
|
|
10253
|
+
agent: baseCommon.agent,
|
|
10256
10254
|
teamId,
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
...profile ? {
|
|
10260
|
-
daemonProfileId: profile.id,
|
|
10261
|
-
daemonProfileName: profile.name
|
|
10262
|
-
} : {}
|
|
10255
|
+
runtimeProfileIds: profiles.map((p) => p.id),
|
|
10256
|
+
runtimeProfileNames: profiles.map((p) => p.name)
|
|
10263
10257
|
});
|
|
10264
10258
|
const abort = new AbortController();
|
|
10265
10259
|
let runtime = null;
|
|
@@ -10285,90 +10279,91 @@ async function runPolling(opts) {
|
|
|
10285
10279
|
}
|
|
10286
10280
|
});
|
|
10287
10281
|
rootLogger.info({
|
|
10288
|
-
sandbox: sandbox.path,
|
|
10289
10282
|
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
10290
10283
|
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
10291
|
-
leaseTtlSec: common.leaseTtlSec,
|
|
10292
|
-
heartbeatIntervalMs: common.heartbeatIntervalMs,
|
|
10293
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10294
10284
|
pollIntervalMs,
|
|
10295
10285
|
maxPollIntervalMs,
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10286
|
+
profiles: profiles.map((profile) => {
|
|
10287
|
+
const runtime = requireRuntime(runtimes, profile.id);
|
|
10288
|
+
return {
|
|
10289
|
+
id: profile.id,
|
|
10290
|
+
name: profile.name,
|
|
10291
|
+
provider: profile.provider,
|
|
10292
|
+
model: profile.model,
|
|
10293
|
+
sandbox: runtime.sandbox.path,
|
|
10294
|
+
leaseTtlSec: runtime.common.leaseTtlSec,
|
|
10295
|
+
heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
|
|
10296
|
+
warmSessionTtlSec: runtime.common.warmSessionTtlSec,
|
|
10297
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
10298
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
10299
|
+
};
|
|
10300
|
+
}),
|
|
10301
10301
|
piAgentDir: piAgentDir.path,
|
|
10302
10302
|
piAgentDirSource: piAgentDir.source
|
|
10303
10303
|
}, "agent-daemon.starting");
|
|
10304
10304
|
const outputs = [];
|
|
10305
10305
|
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
10306
|
runtime = new AgentRuntime({
|
|
10329
10307
|
logger: rootLogger,
|
|
10330
10308
|
source: new PollingApiTaskSource({
|
|
10331
10309
|
agent: ctx.agent,
|
|
10332
10310
|
teamId,
|
|
10333
10311
|
taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
|
|
10334
|
-
|
|
10312
|
+
profiles: profiles.map((profile) => ({
|
|
10313
|
+
profileId: profile.id,
|
|
10314
|
+
leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
|
|
10315
|
+
})),
|
|
10335
10316
|
diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
|
|
10336
|
-
leaseTtlSec: common.leaseTtlSec,
|
|
10317
|
+
leaseTtlSec: firstRuntime.common.leaseTtlSec,
|
|
10337
10318
|
listLimit,
|
|
10338
10319
|
pollIntervalMs,
|
|
10339
10320
|
maxPollIntervalMs,
|
|
10340
10321
|
signal: abort.signal,
|
|
10341
10322
|
stopWhenEmpty: opts.stopWhenEmpty,
|
|
10342
|
-
debug:
|
|
10323
|
+
debug: baseCommon.debug,
|
|
10343
10324
|
logger: rootLogger,
|
|
10344
10325
|
slotRegistry
|
|
10345
10326
|
}),
|
|
10346
|
-
makeReporter: () =>
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10327
|
+
makeReporter: (claimedTask) => {
|
|
10328
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10329
|
+
return new ApiTaskReporter({
|
|
10330
|
+
tasks: ctx.agent.tasks,
|
|
10331
|
+
leaseTtlSec: selected.common.leaseTtlSec,
|
|
10332
|
+
heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
|
|
10333
|
+
maxBatchSize: selected.common.maxBatchSize,
|
|
10334
|
+
flushIntervalMs: selected.common.flushIntervalMs
|
|
10335
|
+
});
|
|
10336
|
+
},
|
|
10353
10337
|
onTaskFinished: async (output, claimedTask) => {
|
|
10338
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10354
10339
|
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
10355
10340
|
return finalizeTask(ctx.agent, output, {
|
|
10356
10341
|
task: claimedTask.task,
|
|
10357
10342
|
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
10358
10343
|
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
10359
10344
|
gh: createGhCliClient(),
|
|
10360
|
-
logger: rootLogger
|
|
10345
|
+
logger: rootLogger.child({
|
|
10346
|
+
runtimeProfileId: selected.profile.id,
|
|
10347
|
+
runtimeProfileName: selected.profile.name
|
|
10348
|
+
})
|
|
10361
10349
|
}),
|
|
10362
10350
|
log: (msg, err) => rootLogger.warn({ err }, msg)
|
|
10363
10351
|
});
|
|
10364
10352
|
},
|
|
10365
10353
|
executeTask: async (claimedTask, reporter) => {
|
|
10354
|
+
const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10355
|
+
const taskLogger = rootLogger.child({
|
|
10356
|
+
runtimeProfileId: profile.id,
|
|
10357
|
+
runtimeProfileName: profile.name,
|
|
10358
|
+
provider: profile.provider,
|
|
10359
|
+
model: profile.model
|
|
10360
|
+
});
|
|
10366
10361
|
let executionPlan;
|
|
10367
10362
|
try {
|
|
10368
10363
|
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
10369
10364
|
} catch (err) {
|
|
10370
10365
|
const message = err instanceof Error ? err.message : String(err);
|
|
10371
|
-
|
|
10366
|
+
taskLogger.warn({
|
|
10372
10367
|
taskId: claimedTask.task.id,
|
|
10373
10368
|
attemptN: claimedTask.attemptN,
|
|
10374
10369
|
err: message
|
|
@@ -10392,7 +10387,7 @@ async function runPolling(opts) {
|
|
|
10392
10387
|
};
|
|
10393
10388
|
}
|
|
10394
10389
|
const sessionDescriptor = executionPlan.descriptor;
|
|
10395
|
-
|
|
10390
|
+
taskLogger.debug({
|
|
10396
10391
|
taskId: claimedTask.task.id,
|
|
10397
10392
|
taskType: claimedTask.task.taskType,
|
|
10398
10393
|
resumable: sessionDescriptor.policy.resumable,
|
|
@@ -10441,23 +10436,48 @@ async function runPolling(opts) {
|
|
|
10441
10436
|
};
|
|
10442
10437
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10443
10438
|
...slotIdentity,
|
|
10439
|
+
runtimeProfileId: profile.id,
|
|
10440
|
+
provider: profile.provider,
|
|
10441
|
+
model: profile.model,
|
|
10444
10442
|
teamId: claimedTask.task.teamId,
|
|
10445
10443
|
slotKey: executionPlan.slotKey,
|
|
10446
10444
|
taskType: claimedTask.task.taskType,
|
|
10447
10445
|
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
10448
10446
|
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
10449
10447
|
workspaceId: executionPlan.workspaceId,
|
|
10450
|
-
worktreePath: resolveRecordedWorkspacePath$1(
|
|
10448
|
+
worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, executionPlan),
|
|
10451
10449
|
worktreeBranch: executionPlan.worktreeBranch,
|
|
10452
10450
|
workspaceKind: executionPlan.workspaceKind,
|
|
10453
10451
|
lastTaskId: claimedTask.task.id,
|
|
10454
10452
|
lastAttemptN: claimedTask.attemptN
|
|
10455
10453
|
});
|
|
10454
|
+
const rawExecuteTask = createPiTaskExecutor({
|
|
10455
|
+
agentName: common.agent,
|
|
10456
|
+
agentRootDir: ctx.agentRootDir,
|
|
10457
|
+
mountPath: sandbox.rootDir,
|
|
10458
|
+
provider: profile.provider,
|
|
10459
|
+
model: profile.model,
|
|
10460
|
+
sandboxConfig: sandbox.config,
|
|
10461
|
+
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
10462
|
+
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
10463
|
+
maxTurns: common.maxTurns,
|
|
10464
|
+
maxBashTimeouts: common.maxBashTimeouts
|
|
10465
|
+
});
|
|
10456
10466
|
try {
|
|
10457
|
-
|
|
10467
|
+
active = {
|
|
10468
|
+
taskId: claimedTask.task.id,
|
|
10469
|
+
attemptN: claimedTask.attemptN
|
|
10470
|
+
};
|
|
10471
|
+
return await runWithDaemonRuntimeContext({
|
|
10472
|
+
profileId: profile.id,
|
|
10473
|
+
profileName: profile.name,
|
|
10474
|
+
provider: profile.provider,
|
|
10475
|
+
model: profile.model
|
|
10476
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
10458
10477
|
} finally {
|
|
10478
|
+
active = null;
|
|
10459
10479
|
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);
|
|
10480
|
+
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
10481
|
}
|
|
10462
10482
|
}
|
|
10463
10483
|
});
|
|
@@ -10472,9 +10492,21 @@ async function runPolling(opts) {
|
|
|
10472
10492
|
await shutdownLogger();
|
|
10473
10493
|
}
|
|
10474
10494
|
}
|
|
10475
|
-
function resolveRecordedWorkspacePath$1(
|
|
10495
|
+
function resolveRecordedWorkspacePath$1(stateRootDir, executionPlan) {
|
|
10476
10496
|
if (!executionPlan.workspaceId) return null;
|
|
10477
|
-
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(
|
|
10497
|
+
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(), ".worktrees", executionPlan.workspaceId);
|
|
10498
|
+
}
|
|
10499
|
+
function runtimeForClaimedTask(runtimes, claimedTask) {
|
|
10500
|
+
if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
|
|
10501
|
+
return requireRuntime(runtimes, claimedTask.profileId);
|
|
10502
|
+
}
|
|
10503
|
+
function requireRuntime(runtimes, profileId) {
|
|
10504
|
+
const runtime = runtimes.get(profileId);
|
|
10505
|
+
if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
|
|
10506
|
+
return runtime;
|
|
10507
|
+
}
|
|
10508
|
+
function parseProfileValues(raw) {
|
|
10509
|
+
return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10478
10510
|
}
|
|
10479
10511
|
function parseCsv(raw) {
|
|
10480
10512
|
return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -10511,6 +10543,7 @@ async function runOnce(argv) {
|
|
|
10511
10543
|
type: "string",
|
|
10512
10544
|
short: "t"
|
|
10513
10545
|
},
|
|
10546
|
+
team: { type: "string" },
|
|
10514
10547
|
sandbox: { type: "string" },
|
|
10515
10548
|
profile: { type: "string" }
|
|
10516
10549
|
}
|
|
@@ -10521,9 +10554,14 @@ async function runOnce(argv) {
|
|
|
10521
10554
|
return 1;
|
|
10522
10555
|
}
|
|
10523
10556
|
const taskId = values["task-id"];
|
|
10557
|
+
if (!values.profile) {
|
|
10558
|
+
console.error("Missing required flag: --profile\n");
|
|
10559
|
+
console.error(ONCE_HELP);
|
|
10560
|
+
return 1;
|
|
10561
|
+
}
|
|
10524
10562
|
let opts;
|
|
10525
10563
|
try {
|
|
10526
|
-
opts = parseCommonOptions(values
|
|
10564
|
+
opts = parseCommonOptions(values);
|
|
10527
10565
|
} catch (err) {
|
|
10528
10566
|
if (err instanceof MissingRequiredOptionError) {
|
|
10529
10567
|
console.error(`${err.message}\n`);
|
|
@@ -10532,50 +10570,40 @@ async function runOnce(argv) {
|
|
|
10532
10570
|
}
|
|
10533
10571
|
throw err;
|
|
10534
10572
|
}
|
|
10535
|
-
if (values.
|
|
10536
|
-
console.error("Cannot use --sandbox
|
|
10573
|
+
if (values.sandbox) {
|
|
10574
|
+
console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
|
|
10537
10575
|
return 1;
|
|
10538
10576
|
}
|
|
10539
10577
|
const cfg = loadConfig();
|
|
10540
|
-
const
|
|
10541
|
-
const
|
|
10578
|
+
const initialOpts = opts;
|
|
10579
|
+
const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
|
|
10580
|
+
const ctx = await resolveAgentContext(initialOpts.agent, { agentRootDir });
|
|
10581
|
+
const profile = await resolveRuntimeProfile({
|
|
10542
10582
|
agent: ctx.agent,
|
|
10543
10583
|
profile: values.profile,
|
|
10584
|
+
teamId: values.team,
|
|
10544
10585
|
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 ? {
|
|
10586
|
+
});
|
|
10587
|
+
validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
10588
|
+
opts = parseCommonOptions(values, { runtimeDefaults: {
|
|
10589
|
+
leaseTtlSec: profile.leaseTtlSec,
|
|
10590
|
+
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
10591
|
+
maxBatchSize: profile.maxBatchSize,
|
|
10592
|
+
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
10593
|
+
} });
|
|
10594
|
+
const sandbox = {
|
|
10562
10595
|
config: profile.sandboxConfig,
|
|
10563
10596
|
rootDir: profile.mountPath,
|
|
10564
10597
|
path: profile.source
|
|
10565
|
-
}
|
|
10598
|
+
};
|
|
10566
10599
|
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10567
10600
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10568
10601
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10569
|
-
const slotRegistry = createApiRuntimeSlotStore({
|
|
10570
|
-
agent: ctx.agent,
|
|
10571
|
-
daemonProfileId: profile?.id ?? null
|
|
10572
|
-
});
|
|
10602
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10573
10603
|
const slotIdentity = {
|
|
10574
10604
|
agentName: opts.agent,
|
|
10575
|
-
|
|
10576
|
-
model
|
|
10605
|
+
runtimeProfileId: profile.id
|
|
10577
10606
|
};
|
|
10578
|
-
const mainRepo = findMainWorktree();
|
|
10579
10607
|
const executionPlans = createExecutionPlanCache({
|
|
10580
10608
|
stateDirs,
|
|
10581
10609
|
slotIdentity,
|
|
@@ -10589,9 +10617,9 @@ async function runOnce(argv) {
|
|
|
10589
10617
|
resourceAttributes: {
|
|
10590
10618
|
"moltnet.task.id": taskId,
|
|
10591
10619
|
"moltnet.agent.name": opts.agent,
|
|
10592
|
-
"moltnet.llm.provider": provider,
|
|
10593
|
-
"moltnet.llm.model": model,
|
|
10594
|
-
|
|
10620
|
+
"moltnet.llm.provider": profile.provider,
|
|
10621
|
+
"moltnet.llm.model": profile.model,
|
|
10622
|
+
"moltnet.runtime_profile.id": profile.id
|
|
10595
10623
|
}
|
|
10596
10624
|
});
|
|
10597
10625
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
@@ -10601,12 +10629,10 @@ async function runOnce(argv) {
|
|
|
10601
10629
|
const rootLogger = logger.child({
|
|
10602
10630
|
mode: "once",
|
|
10603
10631
|
agent: opts.agent,
|
|
10604
|
-
provider,
|
|
10605
|
-
model,
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
daemonProfileName: profile.name
|
|
10609
|
-
} : {}
|
|
10632
|
+
provider: profile.provider,
|
|
10633
|
+
model: profile.model,
|
|
10634
|
+
runtimeProfileId: profile.id,
|
|
10635
|
+
runtimeProfileName: profile.name
|
|
10610
10636
|
});
|
|
10611
10637
|
rootLogger.info({
|
|
10612
10638
|
sandbox: sandbox.path,
|
|
@@ -10614,11 +10640,9 @@ async function runOnce(argv) {
|
|
|
10614
10640
|
leaseTtlSec: opts.leaseTtlSec,
|
|
10615
10641
|
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
10616
10642
|
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
10621
|
-
} : {},
|
|
10643
|
+
profileId: profile.id,
|
|
10644
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
10645
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
10622
10646
|
piAgentDir: piAgentDir.path,
|
|
10623
10647
|
piAgentDirSource: piAgentDir.source
|
|
10624
10648
|
}, "agent-daemon.starting");
|
|
@@ -10651,9 +10675,10 @@ async function runOnce(argv) {
|
|
|
10651
10675
|
try {
|
|
10652
10676
|
const rawExecuteTask = createPiTaskExecutor({
|
|
10653
10677
|
agentName: opts.agent,
|
|
10678
|
+
agentRootDir: ctx.agentRootDir,
|
|
10654
10679
|
mountPath: sandbox.rootDir,
|
|
10655
|
-
provider,
|
|
10656
|
-
model,
|
|
10680
|
+
provider: profile.provider,
|
|
10681
|
+
model: profile.model,
|
|
10657
10682
|
sandboxConfig: sandbox.config,
|
|
10658
10683
|
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
10659
10684
|
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
@@ -10691,13 +10716,16 @@ async function runOnce(argv) {
|
|
|
10691
10716
|
}
|
|
10692
10717
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10693
10718
|
...slotIdentity,
|
|
10719
|
+
runtimeProfileId: profile.id,
|
|
10720
|
+
provider: profile.provider,
|
|
10721
|
+
model: profile.model,
|
|
10694
10722
|
teamId: claimedTask.task.teamId,
|
|
10695
10723
|
slotKey: executionPlan.slotKey,
|
|
10696
10724
|
taskType: claimedTask.task.taskType,
|
|
10697
10725
|
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
10698
10726
|
sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
|
|
10699
10727
|
workspaceId: executionPlan.workspaceId,
|
|
10700
|
-
worktreePath: resolveRecordedWorkspacePath(
|
|
10728
|
+
worktreePath: resolveRecordedWorkspacePath(stateDirs.rootDir, executionPlan),
|
|
10701
10729
|
worktreeBranch: executionPlan.worktreeBranch,
|
|
10702
10730
|
workspaceKind: executionPlan.workspaceKind,
|
|
10703
10731
|
lastTaskId: claimedTask.task.id,
|
|
@@ -10705,11 +10733,16 @@ async function runOnce(argv) {
|
|
|
10705
10733
|
});
|
|
10706
10734
|
activeAttemptN = claimedTask.attemptN;
|
|
10707
10735
|
try {
|
|
10708
|
-
return await
|
|
10736
|
+
return await runWithDaemonRuntimeContext({
|
|
10737
|
+
profileId: profile.id,
|
|
10738
|
+
profileName: profile.name,
|
|
10739
|
+
provider: profile.provider,
|
|
10740
|
+
model: profile.model
|
|
10741
|
+
}, () => rawExecuteTask(claimedTask, reporter));
|
|
10709
10742
|
} finally {
|
|
10710
10743
|
activeAttemptN = null;
|
|
10711
10744
|
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);
|
|
10745
|
+
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
10746
|
}
|
|
10714
10747
|
};
|
|
10715
10748
|
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
@@ -10722,7 +10755,7 @@ async function runOnce(argv) {
|
|
|
10722
10755
|
agent: ctx.agent,
|
|
10723
10756
|
taskId,
|
|
10724
10757
|
leaseTtlSec: opts.leaseTtlSec,
|
|
10725
|
-
|
|
10758
|
+
profileId: profile.id
|
|
10726
10759
|
}),
|
|
10727
10760
|
makeReporter: () => new ApiTaskReporter({
|
|
10728
10761
|
tasks: ctx.agent.tasks,
|
|
@@ -10757,9 +10790,9 @@ async function runOnce(argv) {
|
|
|
10757
10790
|
await shutdownLogger();
|
|
10758
10791
|
}
|
|
10759
10792
|
}
|
|
10760
|
-
function resolveRecordedWorkspacePath(
|
|
10793
|
+
function resolveRecordedWorkspacePath(stateRootDir, executionPlan) {
|
|
10761
10794
|
if (!executionPlan.workspaceId) return null;
|
|
10762
|
-
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(
|
|
10795
|
+
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(), ".worktrees", executionPlan.workspaceId);
|
|
10763
10796
|
}
|
|
10764
10797
|
//#endregion
|
|
10765
10798
|
//#region src/cli/poll.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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,9 +45,9 @@
|
|
|
45
45
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
46
46
|
"pino": "^10.3.1",
|
|
47
47
|
"pino-pretty": "^13.1.3",
|
|
48
|
-
"@themoltnet/
|
|
49
|
-
"@themoltnet/pi-extension": "0.26.
|
|
50
|
-
"@themoltnet/
|
|
48
|
+
"@themoltnet/sdk": "0.109.0",
|
|
49
|
+
"@themoltnet/pi-extension": "0.26.3",
|
|
50
|
+
"@themoltnet/agent-runtime": "0.28.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"tsx": "^4.7.0",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"vitest": "^3.0.0",
|
|
57
57
|
"@moltnet/bootstrap": "0.1.0",
|
|
58
58
|
"@moltnet/crypto-service": "0.1.0",
|
|
59
|
-
"@moltnet/
|
|
60
|
-
"@moltnet/
|
|
59
|
+
"@moltnet/tasks": "0.1.0",
|
|
60
|
+
"@moltnet/observability": "0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"nx": {
|
|
63
63
|
"tags": [
|