@themoltnet/agent-daemon 0.19.1 → 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 +14 -20
- package/dist/main.js +429 -263
- package/package.json +8 -10
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
|
{
|
|
@@ -181,9 +180,9 @@ workspace shape in `input.execution.workspace`: `none` becomes a
|
|
|
181
180
|
`scratch_mount`, `shared_mount` uses the daemon mount, and
|
|
182
181
|
`dedicated_worktree` uses an isolated checkout. Downstream
|
|
183
182
|
`judge_eval_attempt` tasks only resolve against a still-live producer
|
|
184
|
-
session/workspace slot. If the producer slot
|
|
185
|
-
|
|
186
|
-
claim in time, it immediately forks the producer session and copies the
|
|
183
|
+
session/workspace slot. If the producer slot or its local session/workspace
|
|
184
|
+
files cannot be resolved, the judge fails with `producer_context_missing`.
|
|
185
|
+
When the judge does claim in time, it immediately forks the producer session and copies the
|
|
187
186
|
producer workspace into judge-owned scratch state so the running judge no
|
|
188
187
|
longer depends on the producer slot after claim time. Repo-specific
|
|
189
188
|
`resumeCommands` that should not run in scratch mode must still be guarded
|
|
@@ -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
|
@@ -9,11 +9,10 @@ import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
|
|
|
9
9
|
import crypto from "crypto";
|
|
10
10
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
import { parseArgs, promisify } from "node:util";
|
|
12
|
-
import { DaemonSlotRegistry, resolveDaemonStateStorageConfig, resolveLatestPiSessionPath } from "@themoltnet/agent-daemon-state";
|
|
13
12
|
import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource } from "@themoltnet/agent-runtime";
|
|
14
13
|
import { createPiTaskExecutor, findMainWorktree } from "@themoltnet/pi-extension";
|
|
15
14
|
import { execFile, execFileSync } from "node:child_process";
|
|
16
|
-
import { accessSync, constants, existsSync, mkdirSync,
|
|
15
|
+
import { accessSync, constants, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
17
16
|
import { MoltNetError, connect } from "@themoltnet/sdk";
|
|
18
17
|
import { once } from "node:events";
|
|
19
18
|
import { pino, transport } from "pino";
|
|
@@ -22,6 +21,7 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
|
22
21
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
23
22
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
24
23
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
24
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
25
|
//#region ../../libs/observability/src/instrumentation.ts
|
|
26
26
|
/**
|
|
27
27
|
* Register OTel auto-instrumentation for common Node.js modules.
|
|
@@ -4547,6 +4547,118 @@ _Object_({
|
|
|
4547
4547
|
additionalProperties: false
|
|
4548
4548
|
});
|
|
4549
4549
|
//#endregion
|
|
4550
|
+
//#region ../../libs/tasks/src/runtime-slots.ts
|
|
4551
|
+
var RuntimeWorkspaceKind = Union([
|
|
4552
|
+
Literal("origin"),
|
|
4553
|
+
Literal("fork"),
|
|
4554
|
+
Literal("scratch")
|
|
4555
|
+
]);
|
|
4556
|
+
var RuntimeSlotState = Union([Literal("active"), Literal("idle")]);
|
|
4557
|
+
var RuntimeWorkspace = _Object_({
|
|
4558
|
+
id: String$1({ format: "uuid" }),
|
|
4559
|
+
teamId: String$1({ format: "uuid" }),
|
|
4560
|
+
workspaceId: String$1({ minLength: 1 }),
|
|
4561
|
+
worktreePath: String$1({ minLength: 1 }),
|
|
4562
|
+
worktreeBranch: Union([String$1({ minLength: 1 }), Null()]),
|
|
4563
|
+
kind: RuntimeWorkspaceKind,
|
|
4564
|
+
createdAtMs: Integer({ minimum: 0 }),
|
|
4565
|
+
lastUsedAtMs: Integer({ minimum: 0 })
|
|
4566
|
+
}, { $id: "RuntimeWorkspace" });
|
|
4567
|
+
_Object_({
|
|
4568
|
+
slot: _Object_({
|
|
4569
|
+
id: String$1({ format: "uuid" }),
|
|
4570
|
+
teamId: String$1({ format: "uuid" }),
|
|
4571
|
+
agentName: String$1({
|
|
4572
|
+
minLength: 1,
|
|
4573
|
+
maxLength: 100
|
|
4574
|
+
}),
|
|
4575
|
+
runtimeProfileId: Union([String$1({ format: "uuid" }), Null()]),
|
|
4576
|
+
provider: String$1({
|
|
4577
|
+
minLength: 1,
|
|
4578
|
+
maxLength: 100
|
|
4579
|
+
}),
|
|
4580
|
+
model: String$1({
|
|
4581
|
+
minLength: 1,
|
|
4582
|
+
maxLength: 200
|
|
4583
|
+
}),
|
|
4584
|
+
slotKey: String$1({ minLength: 1 }),
|
|
4585
|
+
taskType: String$1({
|
|
4586
|
+
minLength: 1,
|
|
4587
|
+
maxLength: 100
|
|
4588
|
+
}),
|
|
4589
|
+
state: RuntimeSlotState,
|
|
4590
|
+
lastTaskId: String$1({ format: "uuid" }),
|
|
4591
|
+
lastAttemptN: Integer({ minimum: 1 }),
|
|
4592
|
+
sessionDir: Union([String$1({ minLength: 1 }), Null()]),
|
|
4593
|
+
sessionPath: Union([String$1({ minLength: 1 }), Null()]),
|
|
4594
|
+
workspaceRowId: Union([String$1({ format: "uuid" }), Null()]),
|
|
4595
|
+
createdAtMs: Integer({ minimum: 0 }),
|
|
4596
|
+
lastUsedAtMs: Integer({ minimum: 0 }),
|
|
4597
|
+
expiresAtMs: Integer({ minimum: 0 })
|
|
4598
|
+
}, { $id: "RuntimeSlot" }),
|
|
4599
|
+
workspace: Union([RuntimeWorkspace, Null()])
|
|
4600
|
+
}, { $id: "ResolvedRuntimeSlot" });
|
|
4601
|
+
_Object_({
|
|
4602
|
+
agentName: String$1({
|
|
4603
|
+
minLength: 1,
|
|
4604
|
+
maxLength: 100
|
|
4605
|
+
}),
|
|
4606
|
+
runtimeProfileId: String$1({ format: "uuid" }),
|
|
4607
|
+
provider: String$1({
|
|
4608
|
+
minLength: 1,
|
|
4609
|
+
maxLength: 100
|
|
4610
|
+
}),
|
|
4611
|
+
model: String$1({
|
|
4612
|
+
minLength: 1,
|
|
4613
|
+
maxLength: 200
|
|
4614
|
+
}),
|
|
4615
|
+
slotKey: String$1({ minLength: 1 }),
|
|
4616
|
+
taskType: String$1({
|
|
4617
|
+
minLength: 1,
|
|
4618
|
+
maxLength: 100
|
|
4619
|
+
}),
|
|
4620
|
+
sessionDir: Optional(String$1({ minLength: 1 })),
|
|
4621
|
+
sessionPath: Optional(String$1({ minLength: 1 })),
|
|
4622
|
+
workspaceId: Optional(String$1({ minLength: 1 })),
|
|
4623
|
+
worktreePath: Optional(String$1({ minLength: 1 })),
|
|
4624
|
+
worktreeBranch: Optional(String$1({ minLength: 1 })),
|
|
4625
|
+
workspaceKind: Optional(RuntimeWorkspaceKind),
|
|
4626
|
+
lastTaskId: String$1({ format: "uuid" }),
|
|
4627
|
+
lastAttemptN: Integer({ minimum: 1 })
|
|
4628
|
+
}, {
|
|
4629
|
+
$id: "BeginRuntimeSlotBody",
|
|
4630
|
+
additionalProperties: false
|
|
4631
|
+
});
|
|
4632
|
+
_Object_({
|
|
4633
|
+
agentName: String$1({
|
|
4634
|
+
minLength: 1,
|
|
4635
|
+
maxLength: 100
|
|
4636
|
+
}),
|
|
4637
|
+
runtimeProfileId: String$1({ format: "uuid" }),
|
|
4638
|
+
provider: String$1({
|
|
4639
|
+
minLength: 1,
|
|
4640
|
+
maxLength: 100
|
|
4641
|
+
}),
|
|
4642
|
+
model: String$1({
|
|
4643
|
+
minLength: 1,
|
|
4644
|
+
maxLength: 200
|
|
4645
|
+
}),
|
|
4646
|
+
slotKey: String$1({ minLength: 1 }),
|
|
4647
|
+
taskId: String$1({ format: "uuid" }),
|
|
4648
|
+
attemptN: Integer({ minimum: 1 }),
|
|
4649
|
+
sessionPath: Optional(String$1({ minLength: 1 }))
|
|
4650
|
+
}, {
|
|
4651
|
+
$id: "FinishRuntimeSlotBody",
|
|
4652
|
+
additionalProperties: false
|
|
4653
|
+
});
|
|
4654
|
+
_Object_({
|
|
4655
|
+
taskId: String$1({ format: "uuid" }),
|
|
4656
|
+
attemptN: Integer({ minimum: 1 })
|
|
4657
|
+
}, {
|
|
4658
|
+
$id: "FindLatestRuntimeSlotForAttemptQuery",
|
|
4659
|
+
additionalProperties: false
|
|
4660
|
+
});
|
|
4661
|
+
//#endregion
|
|
4550
4662
|
//#region ../../libs/tasks/src/success-criteria.ts
|
|
4551
4663
|
/**
|
|
4552
4664
|
* SuccessCriteria — proposer-stated acceptance criteria, evaluated in two
|
|
@@ -9011,23 +9123,14 @@ _Object_({
|
|
|
9011
9123
|
//#region src/lib/help.ts
|
|
9012
9124
|
var COMMON_REQUIRED_FLAGS = `\
|
|
9013
9125
|
-a, --agent <name> MoltNet agent identity. Reads credentials
|
|
9014
|
-
from <repo-root>/.moltnet/<name>/moltnet.json
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
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.`;
|
|
9020
9131
|
var COMMON_OPTIONAL_FLAGS = `\
|
|
9021
|
-
--sandbox <path>
|
|
9022
|
-
|
|
9023
|
-
containing sandbox.json is also used as the
|
|
9024
|
-
VM mountPath. Cannot be used with --profile.
|
|
9025
|
-
--profile <uuid|name> Remote runtime profile. When set, provider,
|
|
9026
|
-
model, and sandbox policy come from the
|
|
9027
|
-
profile; task listing/claiming is restricted
|
|
9028
|
-
to unrestricted tasks plus tasks allowing this
|
|
9029
|
-
profile. requiredEnv/requiredTools are checked
|
|
9030
|
-
before claiming. Name lookup is team-scoped.
|
|
9132
|
+
--sandbox <path> Deprecated. Remote runtime profiles define
|
|
9133
|
+
sandbox policy.
|
|
9031
9134
|
--lease-ttl-sec <n> Sliding liveness window. Silence longer than
|
|
9032
9135
|
this ends the attempt with lease_expired.
|
|
9033
9136
|
Default: 300.
|
|
@@ -9042,9 +9145,8 @@ var COMMON_OPTIONAL_FLAGS = `\
|
|
|
9042
9145
|
this, the pi session aborts and the attempt
|
|
9043
9146
|
fails with code 'max_bash_timeouts_exceeded'.
|
|
9044
9147
|
0 = disabled. Default: 3.
|
|
9045
|
-
--warm-session-ttl-sec <n>
|
|
9046
|
-
reusable worktrees)
|
|
9047
|
-
this many seconds after use. 0 = disable reuse.
|
|
9148
|
+
--warm-session-ttl-sec <n> Resumability window for runtime slots
|
|
9149
|
+
(Pi sessions + reusable worktrees) after use.
|
|
9048
9150
|
Default: 1800, or min(profile session/workspace
|
|
9049
9151
|
TTL) when --profile is set.
|
|
9050
9152
|
--debug Verbose logging: also log successful list/claim
|
|
@@ -9069,23 +9171,20 @@ Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
|
9069
9171
|
|
|
9070
9172
|
Prerequisites (all subcommands):
|
|
9071
9173
|
- <repo-root>/.moltnet/<agent>/moltnet.json — credentials (see --agent)
|
|
9072
|
-
-
|
|
9073
|
-
|
|
9074
|
-
remote runtime profile supplies provider/model/sandbox policy and CWD
|
|
9075
|
-
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.
|
|
9076
9176
|
|
|
9077
9177
|
Registered task types: ${knownTaskTypesList()}`;
|
|
9078
9178
|
var POLL_HELP = `\
|
|
9079
9179
|
agent-daemon poll — long-running task worker.
|
|
9080
9180
|
|
|
9081
9181
|
Usage:
|
|
9082
|
-
agent-daemon poll --team <uuid> --agent <name> --
|
|
9182
|
+
agent-daemon poll --team <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9083
9183
|
|
|
9084
9184
|
Required:
|
|
9085
9185
|
--team <uuid> Team whose queue to serve. The daemon must be
|
|
9086
9186
|
a member of this team (canAccessTeam permit).
|
|
9087
9187
|
${COMMON_REQUIRED_FLAGS}
|
|
9088
|
-
${COMMON_MODEL_FLAGS}
|
|
9089
9188
|
|
|
9090
9189
|
Optional:
|
|
9091
9190
|
--task-types <csv> Whitelist of task types to claim. Default:
|
|
@@ -9102,38 +9201,38 @@ Example:
|
|
|
9102
9201
|
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9103
9202
|
--task-types curate_pack,fulfill_brief \\
|
|
9104
9203
|
--agent legreffier \\
|
|
9105
|
-
--
|
|
9106
|
-
--
|
|
9204
|
+
--profile github-linear \\
|
|
9205
|
+
--profile local-fallback
|
|
9107
9206
|
|
|
9108
9207
|
Stops cleanly on SIGINT/SIGTERM (drains the in-flight task before exit).`;
|
|
9109
9208
|
var ONCE_HELP = `\
|
|
9110
9209
|
agent-daemon once — execute one specific queued task by id, then exit.
|
|
9111
9210
|
|
|
9112
9211
|
Usage:
|
|
9113
|
-
agent-daemon once --task-id <uuid> --agent <name> --
|
|
9212
|
+
agent-daemon once --task-id <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9114
9213
|
|
|
9115
9214
|
Required:
|
|
9116
9215
|
-t, --task-id <uuid> Task to claim and execute. Must already be
|
|
9117
9216
|
in 'queued' status.
|
|
9118
9217
|
${COMMON_REQUIRED_FLAGS}
|
|
9119
|
-
${COMMON_MODEL_FLAGS}
|
|
9120
9218
|
|
|
9121
9219
|
Optional:
|
|
9220
|
+
--team <uuid> Team scope for resolving --profile by name.
|
|
9221
|
+
Required only when --profile is a name.
|
|
9122
9222
|
${COMMON_OPTIONAL_FLAGS}
|
|
9123
9223
|
|
|
9124
9224
|
Example:
|
|
9125
9225
|
agent-daemon once \\
|
|
9126
9226
|
--task-id 26004a77-bc10-43ef-a79f-c8e62faf59b1 \\
|
|
9127
9227
|
--agent legreffier \\
|
|
9128
|
-
--
|
|
9129
|
-
--model claude-sonnet-4-5
|
|
9228
|
+
--profile github-linear
|
|
9130
9229
|
|
|
9131
9230
|
Exits 0 on completed, 1 on failed/cancelled/runtime-error.`;
|
|
9132
9231
|
var DRAIN_HELP = `\
|
|
9133
9232
|
agent-daemon drain — poll until the queue is empty, then exit.
|
|
9134
9233
|
|
|
9135
9234
|
Usage:
|
|
9136
|
-
agent-daemon drain --team <uuid> --agent <name> --
|
|
9235
|
+
agent-daemon drain --team <uuid> --agent <name> --profile <uuid|name> [...]
|
|
9137
9236
|
|
|
9138
9237
|
Same flags as \`poll\`. The only behavioural difference: \`drain\` exits
|
|
9139
9238
|
when a list call confirms no claimable tasks remain (vs \`poll\` which
|
|
@@ -9142,7 +9241,6 @@ sleeps and retries forever).
|
|
|
9142
9241
|
Required:
|
|
9143
9242
|
--team <uuid> Team whose queue to drain.
|
|
9144
9243
|
${COMMON_REQUIRED_FLAGS}
|
|
9145
|
-
${COMMON_MODEL_FLAGS}
|
|
9146
9244
|
|
|
9147
9245
|
Optional:
|
|
9148
9246
|
--task-types <csv> Whitelist. Known types: ${knownTaskTypesList()}
|
|
@@ -9157,8 +9255,7 @@ Example:
|
|
|
9157
9255
|
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9158
9256
|
--task-types judge_pack \\
|
|
9159
9257
|
--agent legreffier \\
|
|
9160
|
-
--
|
|
9161
|
-
--model claude-sonnet-4-5`;
|
|
9258
|
+
--profile eval-judge`;
|
|
9162
9259
|
function isHelpFlag(args) {
|
|
9163
9260
|
return args.includes("--help") || args.includes("-h");
|
|
9164
9261
|
}
|
|
@@ -9166,7 +9263,6 @@ function isHelpFlag(args) {
|
|
|
9166
9263
|
//#region src/config.ts
|
|
9167
9264
|
function loadConfig() {
|
|
9168
9265
|
return {
|
|
9169
|
-
agentDaemonStateDatabaseUrl: process.env["MOLTNET_AGENT_DAEMON_STATE_DATABASE_URL"] ?? "",
|
|
9170
9266
|
otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
|
|
9171
9267
|
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
9172
9268
|
profilePrerequisiteEnv: process.env,
|
|
@@ -9265,6 +9361,16 @@ function createGhCliClient() {
|
|
|
9265
9361
|
};
|
|
9266
9362
|
}
|
|
9267
9363
|
//#endregion
|
|
9364
|
+
//#region src/lib/session-files.ts
|
|
9365
|
+
function resolveLatestPiSessionPath(sessionDir) {
|
|
9366
|
+
try {
|
|
9367
|
+
const latestEntry = readdirSync(sessionDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => entry.name).sort().at(-1);
|
|
9368
|
+
return latestEntry ? join(sessionDir, latestEntry) : null;
|
|
9369
|
+
} catch {
|
|
9370
|
+
return null;
|
|
9371
|
+
}
|
|
9372
|
+
}
|
|
9373
|
+
//#endregion
|
|
9268
9374
|
//#region src/lib/slugify.ts
|
|
9269
9375
|
function slugifyAsciiLower(input, maxLen, preserveChars = []) {
|
|
9270
9376
|
const preserved = new Set(preserveChars.map((char) => char.toLowerCase()));
|
|
@@ -9354,10 +9460,8 @@ function buildDaemonSlotId(identity, slotKey) {
|
|
|
9354
9460
|
return [
|
|
9355
9461
|
"agent",
|
|
9356
9462
|
slugSlotIdentityComponent(identity.agentName),
|
|
9357
|
-
"
|
|
9358
|
-
slugSlotIdentityComponent(identity.
|
|
9359
|
-
"model",
|
|
9360
|
-
slugSlotIdentityComponent(identity.model),
|
|
9463
|
+
"profile",
|
|
9464
|
+
slugSlotIdentityComponent(identity.runtimeProfileId),
|
|
9361
9465
|
"key",
|
|
9362
9466
|
slotKey
|
|
9363
9467
|
].join(":");
|
|
@@ -9419,8 +9523,8 @@ function createExecutionPlanCache(args) {
|
|
|
9419
9523
|
function buildClaimedTaskKey(task) {
|
|
9420
9524
|
return `${task.task.id}:${task.attemptN}`;
|
|
9421
9525
|
}
|
|
9422
|
-
async function resolveWarmSlot(slotRegistry, sourceTaskId, sourceAttemptN, stateDirs) {
|
|
9423
|
-
const producerContext = await slotRegistry.
|
|
9526
|
+
async function resolveWarmSlot(slotRegistry, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
|
|
9527
|
+
const producerContext = await slotRegistry.findLatestSlotByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
|
|
9424
9528
|
if (!producerContext) return { kind: "missing" };
|
|
9425
9529
|
const sourceSessionPath = resolveProducerSessionPath(producerContext);
|
|
9426
9530
|
if (!sourceSessionPath) return { kind: "no-session-path" };
|
|
@@ -9435,8 +9539,8 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
9435
9539
|
if (claimedTask.task.taskType === "freeform") {
|
|
9436
9540
|
const continueFrom = claimedTask.task.input.continueFrom;
|
|
9437
9541
|
if (!continueFrom) return basePlan;
|
|
9438
|
-
const resolution = await resolveWarmSlot(slotRegistry, continueFrom.taskId, continueFrom.attemptN, stateDirs);
|
|
9439
|
-
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no live
|
|
9542
|
+
const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN, stateDirs);
|
|
9543
|
+
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no live runtime slot on this daemon — claim affinity filter should have prevented this claim`);
|
|
9440
9544
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Continuation source attempt ${continueFrom.taskId}/${continueFrom.attemptN} has no persisted Pi session path`);
|
|
9441
9545
|
const sessionDir = `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
|
|
9442
9546
|
const parentBranch = resolution.producerSlot.workspace?.worktreeBranch ?? null;
|
|
@@ -9472,8 +9576,8 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
9472
9576
|
const targetTaskId = typeof claimedTask.task.input.targetTaskId === "string" ? claimedTask.task.input.targetTaskId : null;
|
|
9473
9577
|
const targetAttemptN = typeof claimedTask.task.input.targetAttemptN === "number" ? claimedTask.task.input.targetAttemptN : null;
|
|
9474
9578
|
if (!targetTaskId || !targetAttemptN) throw new ProducerContextResolutionError("judge_eval_attempt is missing targetTaskId/targetAttemptN");
|
|
9475
|
-
const resolution = await resolveWarmSlot(slotRegistry, targetTaskId, targetAttemptN, stateDirs);
|
|
9476
|
-
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer
|
|
9579
|
+
const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, targetTaskId, targetAttemptN, stateDirs);
|
|
9580
|
+
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
|
|
9477
9581
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
|
|
9478
9582
|
return {
|
|
9479
9583
|
...basePlan,
|
|
@@ -9520,7 +9624,7 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
|
9520
9624
|
//#region src/lib/finalize.ts
|
|
9521
9625
|
/**
|
|
9522
9626
|
* Build the `daemonState` payload for a `/complete` call. Only freeform
|
|
9523
|
-
* attempts that ran with a
|
|
9627
|
+
* attempts that ran with a runtime slot are eligible for continuation
|
|
9524
9628
|
* (`tasks_continue`, see issue #1287). Other task types and slot-less
|
|
9525
9629
|
* freeform completions report `null` for `slotResumableUntil`, which
|
|
9526
9630
|
* the server persists verbatim — continuations against such attempts
|
|
@@ -9672,7 +9776,6 @@ var MissingRequiredOptionError = class extends Error {
|
|
|
9672
9776
|
}
|
|
9673
9777
|
};
|
|
9674
9778
|
function parseCommonOptions(args, options = {}) {
|
|
9675
|
-
const requireProviderModel = options.requireProviderModel ?? true;
|
|
9676
9779
|
const runtimeDefaults = {
|
|
9677
9780
|
leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
|
|
9678
9781
|
heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
|
|
@@ -9680,13 +9783,9 @@ function parseCommonOptions(args, options = {}) {
|
|
|
9680
9783
|
warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
|
|
9681
9784
|
};
|
|
9682
9785
|
if (!args.agent) throw new MissingRequiredOptionError("agent");
|
|
9683
|
-
if (requireProviderModel && !args.provider) throw new MissingRequiredOptionError("provider");
|
|
9684
|
-
if (requireProviderModel && !args.model) throw new MissingRequiredOptionError("model");
|
|
9685
9786
|
if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
|
|
9686
9787
|
return {
|
|
9687
9788
|
agent: args.agent,
|
|
9688
|
-
...args.provider ? { provider: args.provider } : {},
|
|
9689
|
-
...args.model ? { model: args.model } : {},
|
|
9690
9789
|
leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
|
|
9691
9790
|
heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
|
|
9692
9791
|
maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
|
|
@@ -9715,14 +9814,6 @@ function commonOptionDefs() {
|
|
|
9715
9814
|
type: "string",
|
|
9716
9815
|
short: "a"
|
|
9717
9816
|
},
|
|
9718
|
-
model: {
|
|
9719
|
-
type: "string",
|
|
9720
|
-
short: "m"
|
|
9721
|
-
},
|
|
9722
|
-
provider: {
|
|
9723
|
-
type: "string",
|
|
9724
|
-
short: "p"
|
|
9725
|
-
},
|
|
9726
9817
|
"lease-ttl-sec": { type: "string" },
|
|
9727
9818
|
"heartbeat-interval-ms": { type: "string" },
|
|
9728
9819
|
"max-batch-size": { type: "string" },
|
|
@@ -9801,6 +9892,12 @@ function ensurePiAgentDir(repoRoot, explicitPath) {
|
|
|
9801
9892
|
};
|
|
9802
9893
|
}
|
|
9803
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
|
|
9804
9901
|
//#region src/lib/runtime-profile.ts
|
|
9805
9902
|
var RuntimeProfilePrerequisiteError = class extends Error {
|
|
9806
9903
|
constructor(profileName, missingEnv, missingTools) {
|
|
@@ -9834,6 +9931,22 @@ async function resolveRuntimeProfile(options) {
|
|
|
9834
9931
|
source: `runtime-profile:${profile.id}`
|
|
9835
9932
|
};
|
|
9836
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
|
+
}
|
|
9837
9950
|
function validateRuntimeProfilePrerequisites(profile, env, pathValue) {
|
|
9838
9951
|
const missingEnv = profile.requiredEnv.filter((name) => !env[name]);
|
|
9839
9952
|
const missingTools = profile.requiredTools.filter((tool) => !isExecutableOnPath(tool, pathValue));
|
|
@@ -9866,33 +9979,63 @@ function isExecutable(path) {
|
|
|
9866
9979
|
}
|
|
9867
9980
|
}
|
|
9868
9981
|
//#endregion
|
|
9869
|
-
//#region src/lib/
|
|
9870
|
-
function
|
|
9871
|
-
const
|
|
9872
|
-
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.`);
|
|
9873
|
-
let config;
|
|
9874
|
-
try {
|
|
9875
|
-
config = JSON.parse(readFileSync(path, "utf8"));
|
|
9876
|
-
} catch (err) {
|
|
9877
|
-
const isEnoent = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
9878
|
-
throw new Error(isEnoent ? `sandbox.json not found at ${path}.` : `Failed to read sandbox.json at ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9879
|
-
}
|
|
9982
|
+
//#region src/lib/runtime-slots.ts
|
|
9983
|
+
function createApiRuntimeSlotStore(args) {
|
|
9984
|
+
const { agent } = args;
|
|
9880
9985
|
return {
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9986
|
+
async beginSlot(input) {
|
|
9987
|
+
await agent.runtimeSlots.begin({
|
|
9988
|
+
agentName: input.agentName,
|
|
9989
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
9990
|
+
lastAttemptN: input.lastAttemptN,
|
|
9991
|
+
lastTaskId: input.lastTaskId,
|
|
9992
|
+
model: input.model,
|
|
9993
|
+
provider: input.provider,
|
|
9994
|
+
sessionDir: input.sessionDir ?? void 0,
|
|
9995
|
+
sessionPath: input.sessionPath ?? void 0,
|
|
9996
|
+
slotKey: input.slotKey,
|
|
9997
|
+
taskType: input.taskType,
|
|
9998
|
+
workspaceId: input.workspaceId ?? void 0,
|
|
9999
|
+
workspaceKind: input.workspaceKind,
|
|
10000
|
+
worktreeBranch: input.worktreeBranch ?? void 0,
|
|
10001
|
+
worktreePath: input.worktreePath ?? void 0
|
|
10002
|
+
}, { teamId: input.teamId });
|
|
10003
|
+
},
|
|
10004
|
+
async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
|
|
10005
|
+
await agent.runtimeSlots.finish({
|
|
10006
|
+
agentName: identity.agentName,
|
|
10007
|
+
attemptN,
|
|
10008
|
+
runtimeProfileId: identity.runtimeProfileId,
|
|
10009
|
+
model,
|
|
10010
|
+
provider,
|
|
10011
|
+
sessionPath: sessionPath ?? void 0,
|
|
10012
|
+
slotKey,
|
|
10013
|
+
taskId
|
|
10014
|
+
}, { teamId });
|
|
10015
|
+
},
|
|
10016
|
+
async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
|
|
10017
|
+
const resolved = await agent.runtimeSlots.findLatestForAttempt({
|
|
10018
|
+
attemptN,
|
|
10019
|
+
taskId
|
|
10020
|
+
}, { teamId });
|
|
10021
|
+
if (!resolved) return null;
|
|
10022
|
+
return {
|
|
10023
|
+
slot: { expiresAtMs: resolved.slot.expiresAtMs },
|
|
10024
|
+
session: resolved.slot.sessionDir ? {
|
|
10025
|
+
sessionDir: resolved.slot.sessionDir,
|
|
10026
|
+
sessionPath: resolved.slot.sessionPath
|
|
10027
|
+
} : null,
|
|
10028
|
+
workspace: resolved.workspace ? {
|
|
10029
|
+
kind: resolved.workspace.kind,
|
|
10030
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
10031
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
10032
|
+
worktreePath: resolved.workspace.worktreePath
|
|
10033
|
+
} : null
|
|
10034
|
+
};
|
|
10035
|
+
},
|
|
10036
|
+
async close() {}
|
|
9884
10037
|
};
|
|
9885
10038
|
}
|
|
9886
|
-
function findUp(startDir, filename) {
|
|
9887
|
-
let dir = resolve(startDir);
|
|
9888
|
-
while (true) {
|
|
9889
|
-
const candidate = resolve(dir, filename);
|
|
9890
|
-
if (existsSync(candidate)) return candidate;
|
|
9891
|
-
const parent = dirname(dir);
|
|
9892
|
-
if (parent === dir) return null;
|
|
9893
|
-
dir = parent;
|
|
9894
|
-
}
|
|
9895
|
-
}
|
|
9896
10039
|
//#endregion
|
|
9897
10040
|
//#region src/lib/shutdown-signal.ts
|
|
9898
10041
|
function installShutdownSignalHandlers(opts) {
|
|
@@ -9933,8 +10076,7 @@ function ensureDaemonStateDirs(mountPath) {
|
|
|
9933
10076
|
mkdirSync(piSessionsDir, { recursive: true });
|
|
9934
10077
|
return {
|
|
9935
10078
|
rootDir,
|
|
9936
|
-
piSessionsDir
|
|
9937
|
-
registryDbPath: join(rootDir, "daemon-state.sqlite")
|
|
10079
|
+
piSessionsDir
|
|
9938
10080
|
};
|
|
9939
10081
|
}
|
|
9940
10082
|
//#endregion
|
|
@@ -9976,7 +10118,10 @@ async function runPolling(opts) {
|
|
|
9976
10118
|
"max-poll-interval-ms": { type: "string" },
|
|
9977
10119
|
"list-limit": { type: "string" },
|
|
9978
10120
|
sandbox: { type: "string" },
|
|
9979
|
-
profile: {
|
|
10121
|
+
profile: {
|
|
10122
|
+
type: "string",
|
|
10123
|
+
multiple: true
|
|
10124
|
+
}
|
|
9980
10125
|
}
|
|
9981
10126
|
});
|
|
9982
10127
|
if (!values.team) {
|
|
@@ -9985,6 +10130,12 @@ async function runPolling(opts) {
|
|
|
9985
10130
|
return 1;
|
|
9986
10131
|
}
|
|
9987
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
|
+
}
|
|
9988
10139
|
let taskTypes;
|
|
9989
10140
|
try {
|
|
9990
10141
|
taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
|
|
@@ -9994,9 +10145,9 @@ async function runPolling(opts) {
|
|
|
9994
10145
|
return 1;
|
|
9995
10146
|
}
|
|
9996
10147
|
const diaryIds = parseCsv(values["diary-ids"]);
|
|
9997
|
-
let
|
|
10148
|
+
let baseCommon;
|
|
9998
10149
|
try {
|
|
9999
|
-
|
|
10150
|
+
baseCommon = parseCommonOptions(values);
|
|
10000
10151
|
} catch (err) {
|
|
10001
10152
|
if (err instanceof MissingRequiredOptionError) {
|
|
10002
10153
|
console.error(`${err.message}\n`);
|
|
@@ -10008,81 +10159,82 @@ async function runPolling(opts) {
|
|
|
10008
10159
|
const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
|
|
10009
10160
|
const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
|
|
10010
10161
|
const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
|
|
10011
|
-
if (values.
|
|
10012
|
-
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.`);
|
|
10013
10164
|
return 1;
|
|
10014
10165
|
}
|
|
10015
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).`);
|
|
10016
10167
|
const cfg = loadConfig();
|
|
10017
|
-
const ctx = await resolveAgentContext(
|
|
10018
|
-
const
|
|
10168
|
+
const ctx = await resolveAgentContext(baseCommon.agent);
|
|
10169
|
+
const profiles = await resolveRuntimeProfiles({
|
|
10019
10170
|
agent: ctx.agent,
|
|
10020
|
-
|
|
10171
|
+
profiles: profileValues,
|
|
10021
10172
|
teamId,
|
|
10022
10173
|
cwd: process.cwd()
|
|
10023
|
-
})
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
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
|
|
10034
10211
|
});
|
|
10035
10212
|
}
|
|
10036
|
-
const
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
const sandbox = profile ? {
|
|
10040
|
-
config: profile.sandboxConfig,
|
|
10041
|
-
rootDir: profile.mountPath,
|
|
10042
|
-
path: profile.source
|
|
10043
|
-
} : resolveSandbox(process.cwd(), values.sandbox);
|
|
10044
|
-
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;
|
|
10045
10216
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10046
|
-
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10047
|
-
const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
|
|
10048
|
-
const slotIdentity = {
|
|
10049
|
-
agentName: common.agent,
|
|
10050
|
-
provider,
|
|
10051
|
-
model
|
|
10052
|
-
};
|
|
10053
|
-
const mainRepo = findMainWorktree();
|
|
10054
|
-
const executionPlans = createExecutionPlanCache({
|
|
10055
|
-
stateDirs,
|
|
10056
|
-
slotIdentity,
|
|
10057
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10058
|
-
slotRegistry
|
|
10059
|
-
});
|
|
10060
10217
|
const otelShutdown = await initWorkerOtel({
|
|
10061
10218
|
serviceName: opts.serviceName,
|
|
10062
10219
|
agentDir: ctx.agentDir,
|
|
10063
10220
|
endpoint: cfg.otelEndpoint,
|
|
10064
10221
|
resourceAttributes: {
|
|
10065
10222
|
"moltnet.team.id": teamId,
|
|
10066
|
-
"moltnet.agent.name":
|
|
10067
|
-
"moltnet.
|
|
10068
|
-
"moltnet.
|
|
10069
|
-
...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(",")
|
|
10070
10226
|
}
|
|
10071
10227
|
});
|
|
10072
10228
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
10073
10229
|
name: `agent-daemon.${opts.modeLabel}`,
|
|
10074
|
-
level: cfg.logLevel || (
|
|
10230
|
+
level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
|
|
10075
10231
|
});
|
|
10076
10232
|
const rootLogger = logger.child({
|
|
10077
10233
|
mode: opts.modeLabel,
|
|
10078
|
-
agent:
|
|
10234
|
+
agent: baseCommon.agent,
|
|
10079
10235
|
teamId,
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
...profile ? {
|
|
10083
|
-
daemonProfileId: profile.id,
|
|
10084
|
-
daemonProfileName: profile.name
|
|
10085
|
-
} : {}
|
|
10236
|
+
runtimeProfileIds: profiles.map((p) => p.id),
|
|
10237
|
+
runtimeProfileNames: profiles.map((p) => p.name)
|
|
10086
10238
|
});
|
|
10087
10239
|
const abort = new AbortController();
|
|
10088
10240
|
let runtime = null;
|
|
@@ -10108,90 +10260,91 @@ async function runPolling(opts) {
|
|
|
10108
10260
|
}
|
|
10109
10261
|
});
|
|
10110
10262
|
rootLogger.info({
|
|
10111
|
-
sandbox: sandbox.path,
|
|
10112
10263
|
taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
|
|
10113
10264
|
diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
|
|
10114
|
-
leaseTtlSec: common.leaseTtlSec,
|
|
10115
|
-
heartbeatIntervalMs: common.heartbeatIntervalMs,
|
|
10116
|
-
warmSessionTtlSec: common.warmSessionTtlSec,
|
|
10117
10265
|
pollIntervalMs,
|
|
10118
10266
|
maxPollIntervalMs,
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
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
|
+
}),
|
|
10124
10282
|
piAgentDir: piAgentDir.path,
|
|
10125
10283
|
piAgentDirSource: piAgentDir.source
|
|
10126
10284
|
}, "agent-daemon.starting");
|
|
10127
10285
|
const outputs = [];
|
|
10128
10286
|
try {
|
|
10129
|
-
const rawExecuteTask = createPiTaskExecutor({
|
|
10130
|
-
agentName: common.agent,
|
|
10131
|
-
mountPath: sandbox.rootDir,
|
|
10132
|
-
provider,
|
|
10133
|
-
model,
|
|
10134
|
-
sandboxConfig: sandbox.config,
|
|
10135
|
-
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
10136
|
-
makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger),
|
|
10137
|
-
maxTurns: common.maxTurns,
|
|
10138
|
-
maxBashTimeouts: common.maxBashTimeouts
|
|
10139
|
-
});
|
|
10140
|
-
const executeTask = async (claimedTask, reporter) => {
|
|
10141
|
-
active = {
|
|
10142
|
-
taskId: claimedTask.task.id,
|
|
10143
|
-
attemptN: claimedTask.attemptN
|
|
10144
|
-
};
|
|
10145
|
-
try {
|
|
10146
|
-
return await rawExecuteTask(claimedTask, reporter);
|
|
10147
|
-
} finally {
|
|
10148
|
-
active = null;
|
|
10149
|
-
}
|
|
10150
|
-
};
|
|
10151
10287
|
runtime = new AgentRuntime({
|
|
10152
10288
|
logger: rootLogger,
|
|
10153
10289
|
source: new PollingApiTaskSource({
|
|
10154
10290
|
agent: ctx.agent,
|
|
10155
10291
|
teamId,
|
|
10156
10292
|
taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
|
|
10157
|
-
|
|
10293
|
+
profiles: profiles.map((profile) => ({
|
|
10294
|
+
profileId: profile.id,
|
|
10295
|
+
leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
|
|
10296
|
+
})),
|
|
10158
10297
|
diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
|
|
10159
|
-
leaseTtlSec: common.leaseTtlSec,
|
|
10298
|
+
leaseTtlSec: firstRuntime.common.leaseTtlSec,
|
|
10160
10299
|
listLimit,
|
|
10161
10300
|
pollIntervalMs,
|
|
10162
10301
|
maxPollIntervalMs,
|
|
10163
10302
|
signal: abort.signal,
|
|
10164
10303
|
stopWhenEmpty: opts.stopWhenEmpty,
|
|
10165
|
-
debug:
|
|
10304
|
+
debug: baseCommon.debug,
|
|
10166
10305
|
logger: rootLogger,
|
|
10167
10306
|
slotRegistry
|
|
10168
10307
|
}),
|
|
10169
|
-
makeReporter: () =>
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
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
|
+
},
|
|
10176
10318
|
onTaskFinished: async (output, claimedTask) => {
|
|
10177
|
-
const
|
|
10319
|
+
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
10320
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
10178
10321
|
return finalizeTask(ctx.agent, output, {
|
|
10179
10322
|
task: claimedTask.task,
|
|
10180
10323
|
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
10181
10324
|
writeCorrelationAnchors: makePrBodyAnchorWriter({
|
|
10182
10325
|
gh: createGhCliClient(),
|
|
10183
|
-
logger: rootLogger
|
|
10326
|
+
logger: rootLogger.child({
|
|
10327
|
+
runtimeProfileId: selected.profile.id,
|
|
10328
|
+
runtimeProfileName: selected.profile.name
|
|
10329
|
+
})
|
|
10184
10330
|
}),
|
|
10185
10331
|
log: (msg, err) => rootLogger.warn({ err }, msg)
|
|
10186
10332
|
});
|
|
10187
10333
|
},
|
|
10188
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
|
+
});
|
|
10189
10342
|
let executionPlan;
|
|
10190
10343
|
try {
|
|
10191
10344
|
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
10192
10345
|
} catch (err) {
|
|
10193
10346
|
const message = err instanceof Error ? err.message : String(err);
|
|
10194
|
-
|
|
10347
|
+
taskLogger.warn({
|
|
10195
10348
|
taskId: claimedTask.task.id,
|
|
10196
10349
|
attemptN: claimedTask.attemptN,
|
|
10197
10350
|
err: message
|
|
@@ -10215,20 +10368,7 @@ async function runPolling(opts) {
|
|
|
10215
10368
|
};
|
|
10216
10369
|
}
|
|
10217
10370
|
const sessionDescriptor = executionPlan.descriptor;
|
|
10218
|
-
|
|
10219
|
-
try {
|
|
10220
|
-
expired = await slotRegistry.reapExpiredSlots();
|
|
10221
|
-
if (expired.length > 0) rootLogger.info({
|
|
10222
|
-
expiredCount: expired.length,
|
|
10223
|
-
slotKeys: expired.map((item) => item.slot.slotKey)
|
|
10224
|
-
}, "agent-daemon.daemon_slots_reaped");
|
|
10225
|
-
} catch (err) {
|
|
10226
|
-
rootLogger.error({
|
|
10227
|
-
phase: "daemon_slot_reap",
|
|
10228
|
-
err: err instanceof Error ? err.message : String(err)
|
|
10229
|
-
}, "agent-daemon.daemon_slot_reap_failed");
|
|
10230
|
-
}
|
|
10231
|
-
rootLogger.debug({
|
|
10371
|
+
taskLogger.debug({
|
|
10232
10372
|
taskId: claimedTask.task.id,
|
|
10233
10373
|
taskType: claimedTask.task.taskType,
|
|
10234
10374
|
resumable: sessionDescriptor.policy.resumable,
|
|
@@ -10277,6 +10417,10 @@ async function runPolling(opts) {
|
|
|
10277
10417
|
};
|
|
10278
10418
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10279
10419
|
...slotIdentity,
|
|
10420
|
+
runtimeProfileId: profile.id,
|
|
10421
|
+
provider: profile.provider,
|
|
10422
|
+
model: profile.model,
|
|
10423
|
+
teamId: claimedTask.task.teamId,
|
|
10280
10424
|
slotKey: executionPlan.slotKey,
|
|
10281
10425
|
taskType: claimedTask.task.taskType,
|
|
10282
10426
|
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
@@ -10286,14 +10430,34 @@ async function runPolling(opts) {
|
|
|
10286
10430
|
worktreeBranch: executionPlan.worktreeBranch,
|
|
10287
10431
|
workspaceKind: executionPlan.workspaceKind,
|
|
10288
10432
|
lastTaskId: claimedTask.task.id,
|
|
10289
|
-
lastAttemptN: claimedTask.attemptN
|
|
10290
|
-
|
|
10433
|
+
lastAttemptN: claimedTask.attemptN
|
|
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
|
|
10291
10445
|
});
|
|
10292
10446
|
try {
|
|
10293
|
-
|
|
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));
|
|
10294
10457
|
} finally {
|
|
10458
|
+
active = null;
|
|
10295
10459
|
executionPlans.delete(claimedTask);
|
|
10296
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey,
|
|
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);
|
|
10297
10461
|
}
|
|
10298
10462
|
}
|
|
10299
10463
|
});
|
|
@@ -10312,6 +10476,18 @@ function resolveRecordedWorkspacePath$1(mainRepo, stateRootDir, executionPlan) {
|
|
|
10312
10476
|
if (!executionPlan.workspaceId) return null;
|
|
10313
10477
|
return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(mainRepo, ".worktrees", executionPlan.workspaceId);
|
|
10314
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
|
+
}
|
|
10315
10491
|
function parseCsv(raw) {
|
|
10316
10492
|
return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10317
10493
|
}
|
|
@@ -10347,6 +10523,7 @@ async function runOnce(argv) {
|
|
|
10347
10523
|
type: "string",
|
|
10348
10524
|
short: "t"
|
|
10349
10525
|
},
|
|
10526
|
+
team: { type: "string" },
|
|
10350
10527
|
sandbox: { type: "string" },
|
|
10351
10528
|
profile: { type: "string" }
|
|
10352
10529
|
}
|
|
@@ -10357,9 +10534,14 @@ async function runOnce(argv) {
|
|
|
10357
10534
|
return 1;
|
|
10358
10535
|
}
|
|
10359
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
|
+
}
|
|
10360
10542
|
let opts;
|
|
10361
10543
|
try {
|
|
10362
|
-
opts = parseCommonOptions(values
|
|
10544
|
+
opts = parseCommonOptions(values);
|
|
10363
10545
|
} catch (err) {
|
|
10364
10546
|
if (err instanceof MissingRequiredOptionError) {
|
|
10365
10547
|
console.error(`${err.message}\n`);
|
|
@@ -10368,45 +10550,37 @@ async function runOnce(argv) {
|
|
|
10368
10550
|
}
|
|
10369
10551
|
throw err;
|
|
10370
10552
|
}
|
|
10371
|
-
if (values.
|
|
10372
|
-
console.error("Cannot use --sandbox
|
|
10553
|
+
if (values.sandbox) {
|
|
10554
|
+
console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
|
|
10373
10555
|
return 1;
|
|
10374
10556
|
}
|
|
10375
10557
|
const cfg = loadConfig();
|
|
10376
10558
|
const ctx = await resolveAgentContext(opts.agent);
|
|
10377
|
-
const profile =
|
|
10559
|
+
const profile = await resolveRuntimeProfile({
|
|
10378
10560
|
agent: ctx.agent,
|
|
10379
10561
|
profile: values.profile,
|
|
10562
|
+
teamId: values.team,
|
|
10380
10563
|
cwd: process.cwd()
|
|
10381
|
-
})
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
|
|
10391
|
-
}
|
|
10392
|
-
});
|
|
10393
|
-
}
|
|
10394
|
-
const provider = profile?.provider ?? opts.provider;
|
|
10395
|
-
const model = profile?.model ?? opts.model;
|
|
10396
|
-
if (!provider || !model) throw new Error("provider/model missing after runtime profile resolution");
|
|
10397
|
-
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 = {
|
|
10398
10573
|
config: profile.sandboxConfig,
|
|
10399
10574
|
rootDir: profile.mountPath,
|
|
10400
10575
|
path: profile.source
|
|
10401
|
-
}
|
|
10576
|
+
};
|
|
10402
10577
|
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10403
10578
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10404
10579
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10405
|
-
const slotRegistry =
|
|
10580
|
+
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10406
10581
|
const slotIdentity = {
|
|
10407
10582
|
agentName: opts.agent,
|
|
10408
|
-
|
|
10409
|
-
model
|
|
10583
|
+
runtimeProfileId: profile.id
|
|
10410
10584
|
};
|
|
10411
10585
|
const mainRepo = findMainWorktree();
|
|
10412
10586
|
const executionPlans = createExecutionPlanCache({
|
|
@@ -10422,9 +10596,9 @@ async function runOnce(argv) {
|
|
|
10422
10596
|
resourceAttributes: {
|
|
10423
10597
|
"moltnet.task.id": taskId,
|
|
10424
10598
|
"moltnet.agent.name": opts.agent,
|
|
10425
|
-
"moltnet.llm.provider": provider,
|
|
10426
|
-
"moltnet.llm.model": model,
|
|
10427
|
-
|
|
10599
|
+
"moltnet.llm.provider": profile.provider,
|
|
10600
|
+
"moltnet.llm.model": profile.model,
|
|
10601
|
+
"moltnet.runtime_profile.id": profile.id
|
|
10428
10602
|
}
|
|
10429
10603
|
});
|
|
10430
10604
|
const { logger, shutdown: shutdownLogger } = createRootLogger({
|
|
@@ -10434,12 +10608,10 @@ async function runOnce(argv) {
|
|
|
10434
10608
|
const rootLogger = logger.child({
|
|
10435
10609
|
mode: "once",
|
|
10436
10610
|
agent: opts.agent,
|
|
10437
|
-
provider,
|
|
10438
|
-
model,
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
daemonProfileName: profile.name
|
|
10442
|
-
} : {}
|
|
10611
|
+
provider: profile.provider,
|
|
10612
|
+
model: profile.model,
|
|
10613
|
+
runtimeProfileId: profile.id,
|
|
10614
|
+
runtimeProfileName: profile.name
|
|
10443
10615
|
});
|
|
10444
10616
|
rootLogger.info({
|
|
10445
10617
|
sandbox: sandbox.path,
|
|
@@ -10447,11 +10619,9 @@ async function runOnce(argv) {
|
|
|
10447
10619
|
leaseTtlSec: opts.leaseTtlSec,
|
|
10448
10620
|
heartbeatIntervalMs: opts.heartbeatIntervalMs,
|
|
10449
10621
|
warmSessionTtlSec: opts.warmSessionTtlSec,
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
profileWorkspaceTtlSec: profile.workspaceTtlSec
|
|
10454
|
-
} : {},
|
|
10622
|
+
profileId: profile.id,
|
|
10623
|
+
profileSessionTtlSec: profile.sessionTtlSec,
|
|
10624
|
+
profileWorkspaceTtlSec: profile.workspaceTtlSec,
|
|
10455
10625
|
piAgentDir: piAgentDir.path,
|
|
10456
10626
|
piAgentDirSource: piAgentDir.source
|
|
10457
10627
|
}, "agent-daemon.starting");
|
|
@@ -10485,8 +10655,8 @@ async function runOnce(argv) {
|
|
|
10485
10655
|
const rawExecuteTask = createPiTaskExecutor({
|
|
10486
10656
|
agentName: opts.agent,
|
|
10487
10657
|
mountPath: sandbox.rootDir,
|
|
10488
|
-
provider,
|
|
10489
|
-
model,
|
|
10658
|
+
provider: profile.provider,
|
|
10659
|
+
model: profile.model,
|
|
10490
10660
|
sandboxConfig: sandbox.config,
|
|
10491
10661
|
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
10492
10662
|
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
@@ -10494,18 +10664,6 @@ async function runOnce(argv) {
|
|
|
10494
10664
|
maxBashTimeouts: opts.maxBashTimeouts
|
|
10495
10665
|
});
|
|
10496
10666
|
const executeTask = async (claimedTask, reporter) => {
|
|
10497
|
-
try {
|
|
10498
|
-
const expired = await slotRegistry.reapExpiredSlots();
|
|
10499
|
-
if (expired.length > 0) rootLogger.info({
|
|
10500
|
-
expiredCount: expired.length,
|
|
10501
|
-
slotKeys: expired.map((item) => item.slot.slotKey)
|
|
10502
|
-
}, "agent-daemon.daemon_slots_reaped");
|
|
10503
|
-
} catch (err) {
|
|
10504
|
-
rootLogger.error({
|
|
10505
|
-
phase: "daemon_slot_reap",
|
|
10506
|
-
err: err instanceof Error ? err.message : String(err)
|
|
10507
|
-
}, "agent-daemon.daemon_slot_reap_failed");
|
|
10508
|
-
}
|
|
10509
10667
|
let executionPlan;
|
|
10510
10668
|
try {
|
|
10511
10669
|
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
@@ -10536,6 +10694,10 @@ async function runOnce(argv) {
|
|
|
10536
10694
|
}
|
|
10537
10695
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10538
10696
|
...slotIdentity,
|
|
10697
|
+
runtimeProfileId: profile.id,
|
|
10698
|
+
provider: profile.provider,
|
|
10699
|
+
model: profile.model,
|
|
10700
|
+
teamId: claimedTask.task.teamId,
|
|
10539
10701
|
slotKey: executionPlan.slotKey,
|
|
10540
10702
|
taskType: claimedTask.task.taskType,
|
|
10541
10703
|
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
@@ -10545,16 +10707,20 @@ async function runOnce(argv) {
|
|
|
10545
10707
|
worktreeBranch: executionPlan.worktreeBranch,
|
|
10546
10708
|
workspaceKind: executionPlan.workspaceKind,
|
|
10547
10709
|
lastTaskId: claimedTask.task.id,
|
|
10548
|
-
lastAttemptN: claimedTask.attemptN
|
|
10549
|
-
ttlSec: opts.warmSessionTtlSec
|
|
10710
|
+
lastAttemptN: claimedTask.attemptN
|
|
10550
10711
|
});
|
|
10551
10712
|
activeAttemptN = claimedTask.attemptN;
|
|
10552
10713
|
try {
|
|
10553
|
-
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));
|
|
10554
10720
|
} finally {
|
|
10555
10721
|
activeAttemptN = null;
|
|
10556
10722
|
executionPlans.delete(claimedTask);
|
|
10557
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey,
|
|
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);
|
|
10558
10724
|
}
|
|
10559
10725
|
};
|
|
10560
10726
|
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
@@ -10567,7 +10733,7 @@ async function runOnce(argv) {
|
|
|
10567
10733
|
agent: ctx.agent,
|
|
10568
10734
|
taskId,
|
|
10569
10735
|
leaseTtlSec: opts.leaseTtlSec,
|
|
10570
|
-
|
|
10736
|
+
profileId: profile.id
|
|
10571
10737
|
}),
|
|
10572
10738
|
makeReporter: () => new ApiTaskReporter({
|
|
10573
10739
|
tasks: ctx.agent.tasks,
|
|
@@ -10577,7 +10743,7 @@ async function runOnce(argv) {
|
|
|
10577
10743
|
flushIntervalMs: opts.flushIntervalMs
|
|
10578
10744
|
}),
|
|
10579
10745
|
onTaskFinished: async (output, claimedTask) => {
|
|
10580
|
-
const resolved = await slotRegistry.
|
|
10746
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
10581
10747
|
return finalizeTask(ctx.agent, output, {
|
|
10582
10748
|
task: claimedTask.task,
|
|
10583
10749
|
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
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,10 +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/agent-
|
|
49
|
-
"@themoltnet/
|
|
50
|
-
"@themoltnet/
|
|
51
|
-
"@themoltnet/pi-extension": "0.26.0"
|
|
48
|
+
"@themoltnet/agent-runtime": "0.28.0",
|
|
49
|
+
"@themoltnet/pi-extension": "0.26.2",
|
|
50
|
+
"@themoltnet/sdk": "0.109.0"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
54
53
|
"tsx": "^4.7.0",
|
|
@@ -57,8 +56,8 @@
|
|
|
57
56
|
"vitest": "^3.0.0",
|
|
58
57
|
"@moltnet/crypto-service": "0.1.0",
|
|
59
58
|
"@moltnet/observability": "0.1.0",
|
|
60
|
-
"@moltnet/
|
|
61
|
-
"@moltnet/
|
|
59
|
+
"@moltnet/bootstrap": "0.1.0",
|
|
60
|
+
"@moltnet/tasks": "0.1.0"
|
|
62
61
|
},
|
|
63
62
|
"nx": {
|
|
64
63
|
"tags": [
|
|
@@ -94,8 +93,7 @@
|
|
|
94
93
|
"dev": "tsx watch src/main.ts",
|
|
95
94
|
"start": "node dist/main.js",
|
|
96
95
|
"build": "vite build",
|
|
97
|
-
"check:pack": "tsx ../../tools/src/check-pack.ts --package . && pnpm run smoke:pack
|
|
98
|
-
"smoke:pack": "tsx ../../tools/src/smoke-pack.ts --package . --bin moltnet-agent --args --help --expect \"long-running task worker for MoltNet\""
|
|
99
|
-
"smoke:pack:state": "tsx ../../tools/src/smoke-agent-daemon-state-pack.ts"
|
|
96
|
+
"check:pack": "tsx ../../tools/src/check-pack.ts --package . && pnpm run smoke:pack",
|
|
97
|
+
"smoke:pack": "tsx ../../tools/src/smoke-pack.ts --package . --bin moltnet-agent --args --help --expect \"long-running task worker for MoltNet\""
|
|
100
98
|
}
|
|
101
99
|
}
|