@themoltnet/agent-daemon 0.19.1 → 0.20.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 +3 -3
- package/dist/main.js +205 -50
- package/package.json +8 -10
package/README.md
CHANGED
|
@@ -181,9 +181,9 @@ workspace shape in `input.execution.workspace`: `none` becomes a
|
|
|
181
181
|
`scratch_mount`, `shared_mount` uses the daemon mount, and
|
|
182
182
|
`dedicated_worktree` uses an isolated checkout. Downstream
|
|
183
183
|
`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
|
|
184
|
+
session/workspace slot. If the producer slot or its local session/workspace
|
|
185
|
+
files cannot be resolved, the judge fails with `producer_context_missing`.
|
|
186
|
+
When the judge does claim in time, it immediately forks the producer session and copies the
|
|
187
187
|
producer workspace into judge-owned scratch state so the running judge no
|
|
188
188
|
longer depends on the producer slot after claim time. Repo-specific
|
|
189
189
|
`resumeCommands` that should not run in scratch mode must still be guarded
|
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, readFileSync } from "node:fs";
|
|
15
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync, 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";
|
|
@@ -4547,6 +4546,117 @@ _Object_({
|
|
|
4547
4546
|
additionalProperties: false
|
|
4548
4547
|
});
|
|
4549
4548
|
//#endregion
|
|
4549
|
+
//#region ../../libs/tasks/src/runtime-slots.ts
|
|
4550
|
+
var RuntimeWorkspaceKind = Union([
|
|
4551
|
+
Literal("origin"),
|
|
4552
|
+
Literal("fork"),
|
|
4553
|
+
Literal("scratch")
|
|
4554
|
+
]);
|
|
4555
|
+
var RuntimeSlotState = Union([Literal("active"), Literal("idle")]);
|
|
4556
|
+
var RuntimeWorkspace = _Object_({
|
|
4557
|
+
id: String$1({ format: "uuid" }),
|
|
4558
|
+
teamId: String$1({ format: "uuid" }),
|
|
4559
|
+
workspaceId: String$1({ minLength: 1 }),
|
|
4560
|
+
worktreePath: String$1({ minLength: 1 }),
|
|
4561
|
+
worktreeBranch: Union([String$1({ minLength: 1 }), Null()]),
|
|
4562
|
+
kind: RuntimeWorkspaceKind,
|
|
4563
|
+
createdAtMs: Integer({ minimum: 0 }),
|
|
4564
|
+
lastUsedAtMs: Integer({ minimum: 0 })
|
|
4565
|
+
}, { $id: "RuntimeWorkspace" });
|
|
4566
|
+
_Object_({
|
|
4567
|
+
slot: _Object_({
|
|
4568
|
+
id: String$1({ format: "uuid" }),
|
|
4569
|
+
teamId: String$1({ format: "uuid" }),
|
|
4570
|
+
agentName: String$1({
|
|
4571
|
+
minLength: 1,
|
|
4572
|
+
maxLength: 100
|
|
4573
|
+
}),
|
|
4574
|
+
daemonProfileId: Union([String$1({ format: "uuid" }), Null()]),
|
|
4575
|
+
provider: String$1({
|
|
4576
|
+
minLength: 1,
|
|
4577
|
+
maxLength: 100
|
|
4578
|
+
}),
|
|
4579
|
+
model: String$1({
|
|
4580
|
+
minLength: 1,
|
|
4581
|
+
maxLength: 200
|
|
4582
|
+
}),
|
|
4583
|
+
slotKey: String$1({ minLength: 1 }),
|
|
4584
|
+
taskType: String$1({
|
|
4585
|
+
minLength: 1,
|
|
4586
|
+
maxLength: 100
|
|
4587
|
+
}),
|
|
4588
|
+
state: RuntimeSlotState,
|
|
4589
|
+
lastTaskId: String$1({ format: "uuid" }),
|
|
4590
|
+
lastAttemptN: Integer({ minimum: 1 }),
|
|
4591
|
+
sessionDir: Union([String$1({ minLength: 1 }), Null()]),
|
|
4592
|
+
sessionPath: Union([String$1({ minLength: 1 }), Null()]),
|
|
4593
|
+
workspaceRowId: Union([String$1({ format: "uuid" }), Null()]),
|
|
4594
|
+
createdAtMs: Integer({ minimum: 0 }),
|
|
4595
|
+
lastUsedAtMs: Integer({ minimum: 0 }),
|
|
4596
|
+
expiresAtMs: Integer({ minimum: 0 })
|
|
4597
|
+
}, { $id: "RuntimeSlot" }),
|
|
4598
|
+
workspace: Union([RuntimeWorkspace, Null()])
|
|
4599
|
+
}, { $id: "ResolvedRuntimeSlot" });
|
|
4600
|
+
_Object_({
|
|
4601
|
+
agentName: String$1({
|
|
4602
|
+
minLength: 1,
|
|
4603
|
+
maxLength: 100
|
|
4604
|
+
}),
|
|
4605
|
+
daemonProfileId: Optional(String$1({ format: "uuid" })),
|
|
4606
|
+
provider: String$1({
|
|
4607
|
+
minLength: 1,
|
|
4608
|
+
maxLength: 100
|
|
4609
|
+
}),
|
|
4610
|
+
model: String$1({
|
|
4611
|
+
minLength: 1,
|
|
4612
|
+
maxLength: 200
|
|
4613
|
+
}),
|
|
4614
|
+
slotKey: String$1({ minLength: 1 }),
|
|
4615
|
+
taskType: String$1({
|
|
4616
|
+
minLength: 1,
|
|
4617
|
+
maxLength: 100
|
|
4618
|
+
}),
|
|
4619
|
+
sessionDir: Optional(String$1({ minLength: 1 })),
|
|
4620
|
+
sessionPath: Optional(String$1({ minLength: 1 })),
|
|
4621
|
+
workspaceId: Optional(String$1({ minLength: 1 })),
|
|
4622
|
+
worktreePath: Optional(String$1({ minLength: 1 })),
|
|
4623
|
+
worktreeBranch: Optional(String$1({ minLength: 1 })),
|
|
4624
|
+
workspaceKind: Optional(RuntimeWorkspaceKind),
|
|
4625
|
+
lastTaskId: String$1({ format: "uuid" }),
|
|
4626
|
+
lastAttemptN: Integer({ minimum: 1 })
|
|
4627
|
+
}, {
|
|
4628
|
+
$id: "BeginRuntimeSlotBody",
|
|
4629
|
+
additionalProperties: false
|
|
4630
|
+
});
|
|
4631
|
+
_Object_({
|
|
4632
|
+
agentName: String$1({
|
|
4633
|
+
minLength: 1,
|
|
4634
|
+
maxLength: 100
|
|
4635
|
+
}),
|
|
4636
|
+
provider: String$1({
|
|
4637
|
+
minLength: 1,
|
|
4638
|
+
maxLength: 100
|
|
4639
|
+
}),
|
|
4640
|
+
model: String$1({
|
|
4641
|
+
minLength: 1,
|
|
4642
|
+
maxLength: 200
|
|
4643
|
+
}),
|
|
4644
|
+
slotKey: String$1({ minLength: 1 }),
|
|
4645
|
+
taskId: String$1({ format: "uuid" }),
|
|
4646
|
+
attemptN: Integer({ minimum: 1 }),
|
|
4647
|
+
sessionPath: Optional(String$1({ minLength: 1 }))
|
|
4648
|
+
}, {
|
|
4649
|
+
$id: "FinishRuntimeSlotBody",
|
|
4650
|
+
additionalProperties: false
|
|
4651
|
+
});
|
|
4652
|
+
_Object_({
|
|
4653
|
+
taskId: String$1({ format: "uuid" }),
|
|
4654
|
+
attemptN: Integer({ minimum: 1 })
|
|
4655
|
+
}, {
|
|
4656
|
+
$id: "FindLatestRuntimeSlotForAttemptQuery",
|
|
4657
|
+
additionalProperties: false
|
|
4658
|
+
});
|
|
4659
|
+
//#endregion
|
|
4550
4660
|
//#region ../../libs/tasks/src/success-criteria.ts
|
|
4551
4661
|
/**
|
|
4552
4662
|
* SuccessCriteria — proposer-stated acceptance criteria, evaluated in two
|
|
@@ -9042,9 +9152,8 @@ var COMMON_OPTIONAL_FLAGS = `\
|
|
|
9042
9152
|
this, the pi session aborts and the attempt
|
|
9043
9153
|
fails with code 'max_bash_timeouts_exceeded'.
|
|
9044
9154
|
0 = disabled. Default: 3.
|
|
9045
|
-
--warm-session-ttl-sec <n>
|
|
9046
|
-
reusable worktrees)
|
|
9047
|
-
this many seconds after use. 0 = disable reuse.
|
|
9155
|
+
--warm-session-ttl-sec <n> Resumability window for runtime slots
|
|
9156
|
+
(Pi sessions + reusable worktrees) after use.
|
|
9048
9157
|
Default: 1800, or min(profile session/workspace
|
|
9049
9158
|
TTL) when --profile is set.
|
|
9050
9159
|
--debug Verbose logging: also log successful list/claim
|
|
@@ -9166,7 +9275,6 @@ function isHelpFlag(args) {
|
|
|
9166
9275
|
//#region src/config.ts
|
|
9167
9276
|
function loadConfig() {
|
|
9168
9277
|
return {
|
|
9169
|
-
agentDaemonStateDatabaseUrl: process.env["MOLTNET_AGENT_DAEMON_STATE_DATABASE_URL"] ?? "",
|
|
9170
9278
|
otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
|
|
9171
9279
|
logLevel: process.env["LOG_LEVEL"] ?? "",
|
|
9172
9280
|
profilePrerequisiteEnv: process.env,
|
|
@@ -9265,6 +9373,16 @@ function createGhCliClient() {
|
|
|
9265
9373
|
};
|
|
9266
9374
|
}
|
|
9267
9375
|
//#endregion
|
|
9376
|
+
//#region src/lib/session-files.ts
|
|
9377
|
+
function resolveLatestPiSessionPath(sessionDir) {
|
|
9378
|
+
try {
|
|
9379
|
+
const latestEntry = readdirSync(sessionDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => entry.name).sort().at(-1);
|
|
9380
|
+
return latestEntry ? join(sessionDir, latestEntry) : null;
|
|
9381
|
+
} catch {
|
|
9382
|
+
return null;
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
//#endregion
|
|
9268
9386
|
//#region src/lib/slugify.ts
|
|
9269
9387
|
function slugifyAsciiLower(input, maxLen, preserveChars = []) {
|
|
9270
9388
|
const preserved = new Set(preserveChars.map((char) => char.toLowerCase()));
|
|
@@ -9419,8 +9537,8 @@ function createExecutionPlanCache(args) {
|
|
|
9419
9537
|
function buildClaimedTaskKey(task) {
|
|
9420
9538
|
return `${task.task.id}:${task.attemptN}`;
|
|
9421
9539
|
}
|
|
9422
|
-
async function resolveWarmSlot(slotRegistry, sourceTaskId, sourceAttemptN, stateDirs) {
|
|
9423
|
-
const producerContext = await slotRegistry.
|
|
9540
|
+
async function resolveWarmSlot(slotRegistry, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
|
|
9541
|
+
const producerContext = await slotRegistry.findLatestSlotByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
|
|
9424
9542
|
if (!producerContext) return { kind: "missing" };
|
|
9425
9543
|
const sourceSessionPath = resolveProducerSessionPath(producerContext);
|
|
9426
9544
|
if (!sourceSessionPath) return { kind: "no-session-path" };
|
|
@@ -9435,8 +9553,8 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
9435
9553
|
if (claimedTask.task.taskType === "freeform") {
|
|
9436
9554
|
const continueFrom = claimedTask.task.input.continueFrom;
|
|
9437
9555
|
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
|
|
9556
|
+
const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN, stateDirs);
|
|
9557
|
+
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
9558
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Continuation source attempt ${continueFrom.taskId}/${continueFrom.attemptN} has no persisted Pi session path`);
|
|
9441
9559
|
const sessionDir = `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
|
|
9442
9560
|
const parentBranch = resolution.producerSlot.workspace?.worktreeBranch ?? null;
|
|
@@ -9472,8 +9590,8 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
9472
9590
|
const targetTaskId = typeof claimedTask.task.input.targetTaskId === "string" ? claimedTask.task.input.targetTaskId : null;
|
|
9473
9591
|
const targetAttemptN = typeof claimedTask.task.input.targetAttemptN === "number" ? claimedTask.task.input.targetAttemptN : null;
|
|
9474
9592
|
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
|
|
9593
|
+
const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, targetTaskId, targetAttemptN, stateDirs);
|
|
9594
|
+
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
|
|
9477
9595
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
|
|
9478
9596
|
return {
|
|
9479
9597
|
...basePlan,
|
|
@@ -9520,7 +9638,7 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
|
9520
9638
|
//#region src/lib/finalize.ts
|
|
9521
9639
|
/**
|
|
9522
9640
|
* Build the `daemonState` payload for a `/complete` call. Only freeform
|
|
9523
|
-
* attempts that ran with a
|
|
9641
|
+
* attempts that ran with a runtime slot are eligible for continuation
|
|
9524
9642
|
* (`tasks_continue`, see issue #1287). Other task types and slot-less
|
|
9525
9643
|
* freeform completions report `null` for `slotResumableUntil`, which
|
|
9526
9644
|
* the server persists verbatim — continuations against such attempts
|
|
@@ -9866,6 +9984,63 @@ function isExecutable(path) {
|
|
|
9866
9984
|
}
|
|
9867
9985
|
}
|
|
9868
9986
|
//#endregion
|
|
9987
|
+
//#region src/lib/runtime-slots.ts
|
|
9988
|
+
function createApiRuntimeSlotStore(args) {
|
|
9989
|
+
const { agent, daemonProfileId } = args;
|
|
9990
|
+
return {
|
|
9991
|
+
async beginSlot(input) {
|
|
9992
|
+
await agent.runtimeSlots.begin({
|
|
9993
|
+
agentName: input.agentName,
|
|
9994
|
+
daemonProfileId: daemonProfileId ?? void 0,
|
|
9995
|
+
lastAttemptN: input.lastAttemptN,
|
|
9996
|
+
lastTaskId: input.lastTaskId,
|
|
9997
|
+
model: input.model,
|
|
9998
|
+
provider: input.provider,
|
|
9999
|
+
sessionDir: input.sessionDir ?? void 0,
|
|
10000
|
+
sessionPath: input.sessionPath ?? void 0,
|
|
10001
|
+
slotKey: input.slotKey,
|
|
10002
|
+
taskType: input.taskType,
|
|
10003
|
+
workspaceId: input.workspaceId ?? void 0,
|
|
10004
|
+
workspaceKind: input.workspaceKind,
|
|
10005
|
+
worktreeBranch: input.worktreeBranch ?? void 0,
|
|
10006
|
+
worktreePath: input.worktreePath ?? void 0
|
|
10007
|
+
}, { teamId: input.teamId });
|
|
10008
|
+
},
|
|
10009
|
+
async finishSlot(teamId, taskId, attemptN, identity, slotKey, sessionPath) {
|
|
10010
|
+
await agent.runtimeSlots.finish({
|
|
10011
|
+
agentName: identity.agentName,
|
|
10012
|
+
attemptN,
|
|
10013
|
+
model: identity.model,
|
|
10014
|
+
provider: identity.provider,
|
|
10015
|
+
sessionPath: sessionPath ?? void 0,
|
|
10016
|
+
slotKey,
|
|
10017
|
+
taskId
|
|
10018
|
+
}, { teamId });
|
|
10019
|
+
},
|
|
10020
|
+
async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
|
|
10021
|
+
const resolved = await agent.runtimeSlots.findLatestForAttempt({
|
|
10022
|
+
attemptN,
|
|
10023
|
+
taskId
|
|
10024
|
+
}, { teamId });
|
|
10025
|
+
if (!resolved) return null;
|
|
10026
|
+
return {
|
|
10027
|
+
slot: { expiresAtMs: resolved.slot.expiresAtMs },
|
|
10028
|
+
session: resolved.slot.sessionDir ? {
|
|
10029
|
+
sessionDir: resolved.slot.sessionDir,
|
|
10030
|
+
sessionPath: resolved.slot.sessionPath
|
|
10031
|
+
} : null,
|
|
10032
|
+
workspace: resolved.workspace ? {
|
|
10033
|
+
kind: resolved.workspace.kind,
|
|
10034
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
10035
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
10036
|
+
worktreePath: resolved.workspace.worktreePath
|
|
10037
|
+
} : null
|
|
10038
|
+
};
|
|
10039
|
+
},
|
|
10040
|
+
async close() {}
|
|
10041
|
+
};
|
|
10042
|
+
}
|
|
10043
|
+
//#endregion
|
|
9869
10044
|
//#region src/lib/sandbox.ts
|
|
9870
10045
|
function resolveSandbox(startDir, explicitPath) {
|
|
9871
10046
|
const path = explicitPath ? isAbsolute(explicitPath) ? explicitPath : resolve(startDir, explicitPath) : findUp(startDir, "sandbox.json");
|
|
@@ -9933,8 +10108,7 @@ function ensureDaemonStateDirs(mountPath) {
|
|
|
9933
10108
|
mkdirSync(piSessionsDir, { recursive: true });
|
|
9934
10109
|
return {
|
|
9935
10110
|
rootDir,
|
|
9936
|
-
piSessionsDir
|
|
9937
|
-
registryDbPath: join(rootDir, "daemon-state.sqlite")
|
|
10111
|
+
piSessionsDir
|
|
9938
10112
|
};
|
|
9939
10113
|
}
|
|
9940
10114
|
//#endregion
|
|
@@ -10044,7 +10218,10 @@ async function runPolling(opts) {
|
|
|
10044
10218
|
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10045
10219
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10046
10220
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10047
|
-
const slotRegistry =
|
|
10221
|
+
const slotRegistry = createApiRuntimeSlotStore({
|
|
10222
|
+
agent: ctx.agent,
|
|
10223
|
+
daemonProfileId: profile?.id ?? null
|
|
10224
|
+
});
|
|
10048
10225
|
const slotIdentity = {
|
|
10049
10226
|
agentName: common.agent,
|
|
10050
10227
|
provider,
|
|
@@ -10174,7 +10351,7 @@ async function runPolling(opts) {
|
|
|
10174
10351
|
flushIntervalMs: common.flushIntervalMs
|
|
10175
10352
|
}),
|
|
10176
10353
|
onTaskFinished: async (output, claimedTask) => {
|
|
10177
|
-
const resolved = await slotRegistry.
|
|
10354
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
10178
10355
|
return finalizeTask(ctx.agent, output, {
|
|
10179
10356
|
task: claimedTask.task,
|
|
10180
10357
|
slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
|
|
@@ -10215,19 +10392,6 @@ async function runPolling(opts) {
|
|
|
10215
10392
|
};
|
|
10216
10393
|
}
|
|
10217
10394
|
const sessionDescriptor = executionPlan.descriptor;
|
|
10218
|
-
let expired;
|
|
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
10395
|
rootLogger.debug({
|
|
10232
10396
|
taskId: claimedTask.task.id,
|
|
10233
10397
|
taskType: claimedTask.task.taskType,
|
|
@@ -10277,6 +10441,7 @@ async function runPolling(opts) {
|
|
|
10277
10441
|
};
|
|
10278
10442
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10279
10443
|
...slotIdentity,
|
|
10444
|
+
teamId: claimedTask.task.teamId,
|
|
10280
10445
|
slotKey: executionPlan.slotKey,
|
|
10281
10446
|
taskType: claimedTask.task.taskType,
|
|
10282
10447
|
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
@@ -10286,14 +10451,13 @@ async function runPolling(opts) {
|
|
|
10286
10451
|
worktreeBranch: executionPlan.worktreeBranch,
|
|
10287
10452
|
workspaceKind: executionPlan.workspaceKind,
|
|
10288
10453
|
lastTaskId: claimedTask.task.id,
|
|
10289
|
-
lastAttemptN: claimedTask.attemptN
|
|
10290
|
-
ttlSec: common.warmSessionTtlSec
|
|
10454
|
+
lastAttemptN: claimedTask.attemptN
|
|
10291
10455
|
});
|
|
10292
10456
|
try {
|
|
10293
10457
|
return await executeTask(claimedTask, reporter);
|
|
10294
10458
|
} finally {
|
|
10295
10459
|
executionPlans.delete(claimedTask);
|
|
10296
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(
|
|
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);
|
|
10297
10461
|
}
|
|
10298
10462
|
}
|
|
10299
10463
|
});
|
|
@@ -10402,7 +10566,10 @@ async function runOnce(argv) {
|
|
|
10402
10566
|
const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
|
|
10403
10567
|
activatePiCodingAgentDir(piAgentDir.path);
|
|
10404
10568
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10405
|
-
const slotRegistry =
|
|
10569
|
+
const slotRegistry = createApiRuntimeSlotStore({
|
|
10570
|
+
agent: ctx.agent,
|
|
10571
|
+
daemonProfileId: profile?.id ?? null
|
|
10572
|
+
});
|
|
10406
10573
|
const slotIdentity = {
|
|
10407
10574
|
agentName: opts.agent,
|
|
10408
10575
|
provider,
|
|
@@ -10494,18 +10661,6 @@ async function runOnce(argv) {
|
|
|
10494
10661
|
maxBashTimeouts: opts.maxBashTimeouts
|
|
10495
10662
|
});
|
|
10496
10663
|
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
10664
|
let executionPlan;
|
|
10510
10665
|
try {
|
|
10511
10666
|
executionPlan = await executionPlans.getOrCreate(claimedTask);
|
|
@@ -10536,6 +10691,7 @@ async function runOnce(argv) {
|
|
|
10536
10691
|
}
|
|
10537
10692
|
if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
|
|
10538
10693
|
...slotIdentity,
|
|
10694
|
+
teamId: claimedTask.task.teamId,
|
|
10539
10695
|
slotKey: executionPlan.slotKey,
|
|
10540
10696
|
taskType: claimedTask.task.taskType,
|
|
10541
10697
|
sessionDir: executionPlan.sessionPersistence.sessionDir,
|
|
@@ -10545,8 +10701,7 @@ async function runOnce(argv) {
|
|
|
10545
10701
|
worktreeBranch: executionPlan.worktreeBranch,
|
|
10546
10702
|
workspaceKind: executionPlan.workspaceKind,
|
|
10547
10703
|
lastTaskId: claimedTask.task.id,
|
|
10548
|
-
lastAttemptN: claimedTask.attemptN
|
|
10549
|
-
ttlSec: opts.warmSessionTtlSec
|
|
10704
|
+
lastAttemptN: claimedTask.attemptN
|
|
10550
10705
|
});
|
|
10551
10706
|
activeAttemptN = claimedTask.attemptN;
|
|
10552
10707
|
try {
|
|
@@ -10554,7 +10709,7 @@ async function runOnce(argv) {
|
|
|
10554
10709
|
} finally {
|
|
10555
10710
|
activeAttemptN = null;
|
|
10556
10711
|
executionPlans.delete(claimedTask);
|
|
10557
|
-
if (executionPlan.slotKey) await slotRegistry.finishSlot(
|
|
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);
|
|
10558
10713
|
}
|
|
10559
10714
|
};
|
|
10560
10715
|
const writeCorrelationAnchors = makePrBodyAnchorWriter({
|
|
@@ -10577,7 +10732,7 @@ async function runOnce(argv) {
|
|
|
10577
10732
|
flushIntervalMs: opts.flushIntervalMs
|
|
10578
10733
|
}),
|
|
10579
10734
|
onTaskFinished: async (output, claimedTask) => {
|
|
10580
|
-
const resolved = await slotRegistry.
|
|
10735
|
+
const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
|
|
10581
10736
|
return finalizeTask(ctx.agent, output, {
|
|
10582
10737
|
task: claimedTask.task,
|
|
10583
10738
|
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.20.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,20 +45,19 @@
|
|
|
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.27.0",
|
|
49
|
+
"@themoltnet/pi-extension": "0.26.1",
|
|
50
|
+
"@themoltnet/sdk": "0.109.0"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
54
53
|
"tsx": "^4.7.0",
|
|
55
54
|
"typescript": "~5.9.2",
|
|
56
55
|
"vite": "^8.0.0",
|
|
57
56
|
"vitest": "^3.0.0",
|
|
57
|
+
"@moltnet/bootstrap": "0.1.0",
|
|
58
58
|
"@moltnet/crypto-service": "0.1.0",
|
|
59
59
|
"@moltnet/observability": "0.1.0",
|
|
60
|
-
"@moltnet/tasks": "0.1.0"
|
|
61
|
-
"@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
|
}
|