@themoltnet/agent-daemon 0.25.1 → 0.27.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 +7 -5
- package/dist/main.js +458 -63
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -19,17 +19,19 @@ Run commands through the published package:
|
|
|
19
19
|
|
|
20
20
|
## Modes
|
|
21
21
|
|
|
22
|
-
| Mode
|
|
23
|
-
|
|
|
24
|
-
| `once`
|
|
25
|
-
| `poll`
|
|
26
|
-
| `drain`
|
|
22
|
+
| Mode | Purpose |
|
|
23
|
+
| --------------- | ----------------------------------------------------------------------------- |
|
|
24
|
+
| `once` | Claim a single task by id and exit. Use this in CI. |
|
|
25
|
+
| `poll` | Long-running loop that claims tasks as they appear. Local/long-running hosts. |
|
|
26
|
+
| `drain` | Finalize any tasks already claimed by this agent and exit. |
|
|
27
|
+
| `sync-sessions` | Repair remote runtime-session uploads from local daemon slots. |
|
|
27
28
|
|
|
28
29
|
```bash
|
|
29
30
|
npx @themoltnet/agent-daemon once --task-id <uuid>
|
|
30
31
|
npx @themoltnet/agent-daemon poll --task-types fulfill_brief,assess_brief
|
|
31
32
|
npx @themoltnet/agent-daemon poll --task-types freeform
|
|
32
33
|
npx @themoltnet/agent-daemon drain
|
|
34
|
+
npx @themoltnet/agent-daemon sync-sessions --team <uuid> --agent <name> --dry-run
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
## Configuration
|
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
|
|
|
7
7
|
import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
|
|
8
8
|
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
|
|
9
9
|
import crypto from "crypto";
|
|
10
|
-
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
10
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
11
11
|
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";
|
|
@@ -22,8 +22,11 @@ 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
24
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
|
-
import { mkdir } from "node:fs/promises";
|
|
25
|
+
import { mkdir, realpath, stat } from "node:fs/promises";
|
|
26
26
|
import { pipeline } from "node:stream/promises";
|
|
27
|
+
import { createHash } from "node:crypto";
|
|
28
|
+
import { Writable } from "node:stream";
|
|
29
|
+
import { createGzip } from "node:zlib";
|
|
27
30
|
//#region ../../libs/observability/src/instrumentation.ts
|
|
28
31
|
/**
|
|
29
32
|
* Register OTel auto-instrumentation for common Node.js modules.
|
|
@@ -4411,6 +4414,32 @@ var RuntimeProfileAllowedWorkspaceModes = _Array_(RuntimeProfileWorkspaceMode, {
|
|
|
4411
4414
|
maxItems: 3,
|
|
4412
4415
|
uniqueItems: true
|
|
4413
4416
|
});
|
|
4417
|
+
var RuntimeProfileThinkingLevelOptions = [
|
|
4418
|
+
Literal("off"),
|
|
4419
|
+
Literal("minimal"),
|
|
4420
|
+
Literal("low"),
|
|
4421
|
+
Literal("medium"),
|
|
4422
|
+
Literal("high"),
|
|
4423
|
+
Literal("xhigh")
|
|
4424
|
+
];
|
|
4425
|
+
Union([...RuntimeProfileThinkingLevelOptions]);
|
|
4426
|
+
var RuntimeProfileNullableThinkingLevel = Union([...RuntimeProfileThinkingLevelOptions, Null()]);
|
|
4427
|
+
var RuntimeProfileNullableTemperature = Union([Null(), Number$1({
|
|
4428
|
+
minimum: 0,
|
|
4429
|
+
maximum: 2
|
|
4430
|
+
})]);
|
|
4431
|
+
var RuntimeProfileNullableTopP = Union([Null(), Number$1({
|
|
4432
|
+
minimum: 0,
|
|
4433
|
+
maximum: 1
|
|
4434
|
+
})]);
|
|
4435
|
+
var RuntimeProfileNullableTopK = Union([Integer({
|
|
4436
|
+
minimum: 1,
|
|
4437
|
+
maximum: 1e4
|
|
4438
|
+
}), Null()]);
|
|
4439
|
+
var RuntimeProfileNullableMaxOutputTokens = Union([Integer({
|
|
4440
|
+
minimum: 1,
|
|
4441
|
+
maximum: 1e6
|
|
4442
|
+
}), Null()]);
|
|
4414
4443
|
var SandboxResumeCommandWhenSchema = _Object_({ workspaceMode: Optional(_Array_(Union([
|
|
4415
4444
|
Literal("shared_mount"),
|
|
4416
4445
|
Literal("dedicated_worktree"),
|
|
@@ -4535,6 +4564,11 @@ _Object_({
|
|
|
4535
4564
|
minLength: 1,
|
|
4536
4565
|
maxLength: 200
|
|
4537
4566
|
}),
|
|
4567
|
+
thinkingLevel: RuntimeProfileNullableThinkingLevel,
|
|
4568
|
+
temperature: RuntimeProfileNullableTemperature,
|
|
4569
|
+
topP: RuntimeProfileNullableTopP,
|
|
4570
|
+
topK: RuntimeProfileNullableTopK,
|
|
4571
|
+
maxOutputTokens: RuntimeProfileNullableMaxOutputTokens,
|
|
4538
4572
|
runtimeKind: Literal("gondolin_pi"),
|
|
4539
4573
|
sandbox: RuntimeProfileSandbox,
|
|
4540
4574
|
sessionStorageMode: Literal("local"),
|
|
@@ -4646,7 +4680,7 @@ var RuntimeWorkspace = _Object_({
|
|
|
4646
4680
|
createdAtMs: Integer({ minimum: 0 }),
|
|
4647
4681
|
lastUsedAtMs: Integer({ minimum: 0 })
|
|
4648
4682
|
}, { $id: "RuntimeWorkspace" });
|
|
4649
|
-
_Object_({
|
|
4683
|
+
_Object_({ items: _Array_(_Object_({
|
|
4650
4684
|
slot: _Object_({
|
|
4651
4685
|
id: String$1({ format: "uuid" }),
|
|
4652
4686
|
teamId: String$1({ format: "uuid" }),
|
|
@@ -4679,7 +4713,7 @@ _Object_({
|
|
|
4679
4713
|
expiresAtMs: Integer({ minimum: 0 })
|
|
4680
4714
|
}, { $id: "RuntimeSlot" }),
|
|
4681
4715
|
workspace: Union([RuntimeWorkspace, Null()])
|
|
4682
|
-
}, { $id: "ResolvedRuntimeSlot" });
|
|
4716
|
+
}, { $id: "ResolvedRuntimeSlot" })) }, { $id: "RuntimeSlotListResponse" });
|
|
4683
4717
|
_Object_({
|
|
4684
4718
|
agentName: String$1({
|
|
4685
4719
|
minLength: 1,
|
|
@@ -4740,6 +4774,21 @@ _Object_({
|
|
|
4740
4774
|
$id: "FindLatestRuntimeSlotForAttemptQuery",
|
|
4741
4775
|
additionalProperties: false
|
|
4742
4776
|
});
|
|
4777
|
+
_Object_({
|
|
4778
|
+
agentName: Optional(String$1({
|
|
4779
|
+
minLength: 1,
|
|
4780
|
+
maxLength: 100
|
|
4781
|
+
})),
|
|
4782
|
+
runtimeProfileId: Optional(String$1({ format: "uuid" })),
|
|
4783
|
+
state: Optional(RuntimeSlotState),
|
|
4784
|
+
limit: Optional(Integer({
|
|
4785
|
+
minimum: 1,
|
|
4786
|
+
maximum: 200
|
|
4787
|
+
}))
|
|
4788
|
+
}, {
|
|
4789
|
+
$id: "ListRuntimeSlotsQuery",
|
|
4790
|
+
additionalProperties: false
|
|
4791
|
+
});
|
|
4743
4792
|
//#endregion
|
|
4744
4793
|
//#region ../../libs/tasks/src/success-criteria.ts
|
|
4745
4794
|
/**
|
|
@@ -5613,6 +5662,7 @@ var FreeformArtifact = _Object_({
|
|
|
5613
5662
|
});
|
|
5614
5663
|
var FreeformOutput = _Object_({
|
|
5615
5664
|
summary: String$1({ minLength: 1 }),
|
|
5665
|
+
branch: Optional(String$1({ minLength: 1 })),
|
|
5616
5666
|
artifacts: Optional(_Array_(FreeformArtifact, { maxItems: 20 })),
|
|
5617
5667
|
proposedTaskType: Optional(FreeformTaskTypeProposal),
|
|
5618
5668
|
diaryEntryIds: Optional(_Array_(String$1({ format: "uuid" }))),
|
|
@@ -5625,7 +5675,7 @@ var FreeformOutput = _Object_({
|
|
|
5625
5675
|
* Server-side preflight for `freeform` task-create. Runs after the
|
|
5626
5676
|
* sync TypeBox check passes and only kicks in when
|
|
5627
5677
|
* `input.continueFrom` is set — i.e. the proposer is asking to
|
|
5628
|
-
*
|
|
5678
|
+
* continue from a prior freeform attempt (#1287).
|
|
5629
5679
|
*
|
|
5630
5680
|
* Failure modes, in evaluation order:
|
|
5631
5681
|
* 1. `freeform.sourceTaskNotFound` — source task id does not resolve
|
|
@@ -5633,23 +5683,17 @@ var FreeformOutput = _Object_({
|
|
|
5633
5683
|
* 2. `freeform.sourceTaskTypeNotSupported` — source isn't `freeform`.
|
|
5634
5684
|
* v1 only supports freeform → freeform continuation.
|
|
5635
5685
|
* 3. `freeform.sourceAttemptNotCompleted` — named attempt is missing
|
|
5636
|
-
* or not in `completed` state;
|
|
5686
|
+
* or not in `completed` state; continuation only makes sense
|
|
5637
5687
|
* once the parent has produced a terminal output.
|
|
5638
5688
|
* 4. `freeform.executionWorkspaceNotInheritable` — caller set
|
|
5639
5689
|
* `execution.workspace` together with `continueFrom`. Workspace
|
|
5640
|
-
* mode for a continuation is
|
|
5641
|
-
* (
|
|
5642
|
-
*
|
|
5643
|
-
*
|
|
5644
|
-
*
|
|
5645
|
-
* 5. `freeform.sourceNotResumeEligible` — `daemonState` is null or
|
|
5646
|
-
* `slotResumableUntil` is null. Older completions (pre-#1287) and
|
|
5647
|
-
* daemons that opt out fall here.
|
|
5648
|
-
* 6. `freeform.sourceResumeExpired` — `slotResumableUntil` is in the
|
|
5649
|
-
* past; the warm slot's TTL has elapsed and no daemon is
|
|
5650
|
-
* guaranteed to still hold it.
|
|
5690
|
+
* mode for a continuation is derived by the daemon from parent runtime
|
|
5691
|
+
* context (local slot first, durable session + source attempt branch
|
|
5692
|
+
* second), so any caller-supplied override is silently dropped at the
|
|
5693
|
+
* daemon plan stage. Reject explicitly so misconfiguration surfaces at
|
|
5694
|
+
* create time.
|
|
5651
5695
|
*
|
|
5652
|
-
* Returns on the first failure
|
|
5696
|
+
* Returns on the first failure — the checks
|
|
5653
5697
|
* are sequential preconditions, later ones presume earlier ones hold.
|
|
5654
5698
|
*/
|
|
5655
5699
|
async function validateFreeformInputAsync(input, ctx) {
|
|
@@ -5668,7 +5712,7 @@ async function validateFreeformInputAsync(input, ctx) {
|
|
|
5668
5712
|
}];
|
|
5669
5713
|
if (input.execution?.workspace) return [{
|
|
5670
5714
|
field: "input/execution/workspace",
|
|
5671
|
-
message: "execution.workspace is
|
|
5715
|
+
message: "execution.workspace is derived from parent runtime context when continueFrom is set; omit it",
|
|
5672
5716
|
code: "freeform.executionWorkspaceNotInheritable"
|
|
5673
5717
|
}];
|
|
5674
5718
|
if (ctx.deferReadinessChecks) return [];
|
|
@@ -5678,17 +5722,6 @@ async function validateFreeformInputAsync(input, ctx) {
|
|
|
5678
5722
|
message: `Source attempt ${cf.attemptN} on task ${cf.taskId} is not in 'completed' state`,
|
|
5679
5723
|
code: "freeform.sourceAttemptNotCompleted"
|
|
5680
5724
|
}];
|
|
5681
|
-
if (!attempt.daemonState || attempt.daemonState.slotResumableUntil === null) return [{
|
|
5682
|
-
field: "input/continueFrom",
|
|
5683
|
-
message: "Source attempt did not report continuation eligibility (older completion or daemon opted out)",
|
|
5684
|
-
code: "freeform.sourceNotResumeEligible"
|
|
5685
|
-
}];
|
|
5686
|
-
const expiresAt = new Date(attempt.daemonState.slotResumableUntil).getTime();
|
|
5687
|
-
if (Number.isNaN(expiresAt) || expiresAt <= Date.now()) return [{
|
|
5688
|
-
field: "input/continueFrom",
|
|
5689
|
-
message: `Source attempt's warm slot expired at ${attempt.daemonState.slotResumableUntil} (reported at ${attempt.daemonState.reportedAt})`,
|
|
5690
|
-
code: "freeform.sourceResumeExpired"
|
|
5691
|
-
}];
|
|
5692
5725
|
return [];
|
|
5693
5726
|
}
|
|
5694
5727
|
//#endregion
|
|
@@ -8982,11 +9015,12 @@ var MAX_CLAIM_CONDITION_STATUSES = 8;
|
|
|
8982
9015
|
/**
|
|
8983
9016
|
* Daemon-asserted runtime state stamped onto a `TaskAttemptSummary` at
|
|
8984
9017
|
* attempt-completion time. The server persists this block verbatim and
|
|
8985
|
-
*
|
|
8986
|
-
* eligibility
|
|
8987
|
-
*
|
|
8988
|
-
*
|
|
8989
|
-
*
|
|
9018
|
+
* exposes `slotResumableUntil` as a legacy/local warm-slot hint; task
|
|
9019
|
+
* continuation eligibility is based on the completed source attempt and
|
|
9020
|
+
* daemon-side claim-affinity/runtime-session recovery. The block carries
|
|
9021
|
+
* its own `reportedAt` so consumers can reason about staleness without
|
|
9022
|
+
* reading documentation. All daemon-asserted state lives here —
|
|
9023
|
+
* top-level attempt fields stay server-authoritative.
|
|
8990
9024
|
*
|
|
8991
9025
|
* Adding new fields requires explicit design review (intentional
|
|
8992
9026
|
* boundary; see docs/superpowers/specs/2026-06-04-tasks-continue-design.md).
|
|
@@ -9250,6 +9284,8 @@ Commands:
|
|
|
9250
9284
|
once Claim and execute one specific queued task by id, then exit.
|
|
9251
9285
|
drain Poll until the queue has nothing claimable, then exit.
|
|
9252
9286
|
Useful for batch eval runs and demos.
|
|
9287
|
+
sync-sessions
|
|
9288
|
+
Repair durable runtime-session checkpoints from local slot files.
|
|
9253
9289
|
|
|
9254
9290
|
Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
9255
9291
|
|
|
@@ -9340,6 +9376,34 @@ Example:
|
|
|
9340
9376
|
--task-types judge_pack \\
|
|
9341
9377
|
--agent legreffier \\
|
|
9342
9378
|
--profile eval-judge`;
|
|
9379
|
+
var SYNC_SESSIONS_HELP = `\
|
|
9380
|
+
agent-daemon sync-sessions — repair durable runtime-session checkpoints.
|
|
9381
|
+
|
|
9382
|
+
Usage:
|
|
9383
|
+
agent-daemon sync-sessions --team <uuid> --agent <name> [...]
|
|
9384
|
+
|
|
9385
|
+
Scans this daemon's team-scoped runtime slots, compares local Pi session files
|
|
9386
|
+
with durable runtime-session metadata, and uploads missing or stale checkpoints.
|
|
9387
|
+
|
|
9388
|
+
Required:
|
|
9389
|
+
--team <uuid> Team whose runtime slots to inspect.
|
|
9390
|
+
-a, --agent <name> MoltNet agent identity. Reads credentials
|
|
9391
|
+
from <agent-root>/.moltnet/<name>/moltnet.json.
|
|
9392
|
+
|
|
9393
|
+
Optional:
|
|
9394
|
+
--runtime-profile-id <uuid> Limit repair to one runtime profile.
|
|
9395
|
+
--state <active|idle> Limit scanned slots by state. Default: all.
|
|
9396
|
+
--limit <n> Max slots to scan, 1..200. Default: 100.
|
|
9397
|
+
--dry-run Report missing/stale sessions without uploading.
|
|
9398
|
+
--agent-root <path> Directory that owns .moltnet/<agent>. Default:
|
|
9399
|
+
CWD, with git root fallback when available.
|
|
9400
|
+
--debug Accepted for consistency; no extra output yet.
|
|
9401
|
+
|
|
9402
|
+
Example:
|
|
9403
|
+
agent-daemon sync-sessions \\
|
|
9404
|
+
--team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
|
|
9405
|
+
--agent legreffier \\
|
|
9406
|
+
--state idle`;
|
|
9343
9407
|
function isHelpFlag(args) {
|
|
9344
9408
|
return args.includes("--help") || args.includes("-h");
|
|
9345
9409
|
}
|
|
@@ -9633,12 +9697,13 @@ var ProducerContextResolutionError = class extends Error {
|
|
|
9633
9697
|
function createExecutionPlanCache(args) {
|
|
9634
9698
|
const cache = /* @__PURE__ */ new Map();
|
|
9635
9699
|
const runtimeSessionStore = args.runtimeSessionStore ?? createNullRuntimeSessionStore();
|
|
9700
|
+
const sourceAttemptResolver = args.sourceAttemptResolver ?? createNullSourceAttemptResolver();
|
|
9636
9701
|
return {
|
|
9637
9702
|
async getOrCreate(claimedTask) {
|
|
9638
9703
|
const key = buildClaimedTaskKey(claimedTask);
|
|
9639
9704
|
const existing = cache.get(key);
|
|
9640
9705
|
if (existing) return existing;
|
|
9641
|
-
const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec, args.workspacePolicy), args.stateDirs, args.slotRegistry, runtimeSessionStore);
|
|
9706
|
+
const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec, args.workspacePolicy), args.stateDirs, args.slotRegistry, runtimeSessionStore, sourceAttemptResolver);
|
|
9642
9707
|
assertPlanAllowedByWorkspacePolicy(plan, args.workspacePolicy);
|
|
9643
9708
|
cache.set(key, plan);
|
|
9644
9709
|
return plan;
|
|
@@ -9648,13 +9713,18 @@ function createExecutionPlanCache(args) {
|
|
|
9648
9713
|
}
|
|
9649
9714
|
};
|
|
9650
9715
|
}
|
|
9716
|
+
function createNullSourceAttemptResolver() {
|
|
9717
|
+
return { findOutputBranch() {
|
|
9718
|
+
return Promise.resolve(null);
|
|
9719
|
+
} };
|
|
9720
|
+
}
|
|
9651
9721
|
function createNullRuntimeSessionStore() {
|
|
9652
9722
|
return {
|
|
9653
|
-
|
|
9654
|
-
return null;
|
|
9723
|
+
findRuntimeSessionByTaskAttempt() {
|
|
9724
|
+
return Promise.resolve(null);
|
|
9655
9725
|
},
|
|
9656
|
-
|
|
9657
|
-
|
|
9726
|
+
hydrateSession() {
|
|
9727
|
+
return Promise.reject(new ProducerContextResolutionError("Cannot hydrate runtime session: no runtime session store configured"));
|
|
9658
9728
|
},
|
|
9659
9729
|
async uploadAttemptFinal() {}
|
|
9660
9730
|
};
|
|
@@ -9676,17 +9746,26 @@ function planToRuntimeProfileWorkspaceMode(plan) {
|
|
|
9676
9746
|
function buildClaimedTaskKey(task) {
|
|
9677
9747
|
return `${task.task.id}:${task.attemptN}`;
|
|
9678
9748
|
}
|
|
9679
|
-
async function
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
const localSessionPath = resolveProducerSessionPath(producerContext);
|
|
9683
|
-
const remoteSession = localSessionPath ? null : await runtimeSessionStore.findRuntimeSessionByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
|
|
9684
|
-
const sourceSessionPath = localSessionPath ? localSessionPath : remoteSession ? await runtimeSessionStore.hydrateSession({
|
|
9749
|
+
async function hydrateRemoteRuntimeSession(runtimeSessionStore, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
|
|
9750
|
+
if (!await runtimeSessionStore.findRuntimeSessionByTaskAttempt(teamId, sourceTaskId, sourceAttemptN)) return null;
|
|
9751
|
+
return runtimeSessionStore.hydrateSession({
|
|
9685
9752
|
attemptN: sourceAttemptN,
|
|
9686
9753
|
destinationDir: `${stateDirs.piSessionsDir}/remote-${sourceTaskId}-attempt-${sourceAttemptN}`,
|
|
9687
9754
|
taskId: sourceTaskId,
|
|
9688
9755
|
teamId
|
|
9689
|
-
})
|
|
9756
|
+
});
|
|
9757
|
+
}
|
|
9758
|
+
async function resolveWarmSlot(slotRegistry, runtimeSessionStore, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
|
|
9759
|
+
const producerContext = await slotRegistry.findLatestSlotByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
|
|
9760
|
+
if (!producerContext) {
|
|
9761
|
+
const remoteSessionPath = await hydrateRemoteRuntimeSession(runtimeSessionStore, teamId, sourceTaskId, sourceAttemptN, stateDirs);
|
|
9762
|
+
return remoteSessionPath ? {
|
|
9763
|
+
kind: "remote-session",
|
|
9764
|
+
sessionPath: remoteSessionPath
|
|
9765
|
+
} : { kind: "missing" };
|
|
9766
|
+
}
|
|
9767
|
+
const localSessionPath = resolveProducerSessionPath(producerContext);
|
|
9768
|
+
const sourceSessionPath = localSessionPath ? localSessionPath : await hydrateRemoteRuntimeSession(runtimeSessionStore, teamId, sourceTaskId, sourceAttemptN, stateDirs);
|
|
9690
9769
|
if (!sourceSessionPath) return { kind: "no-session-path" };
|
|
9691
9770
|
return {
|
|
9692
9771
|
kind: "found",
|
|
@@ -9695,19 +9774,54 @@ async function resolveWarmSlot(slotRegistry, runtimeSessionStore, teamId, source
|
|
|
9695
9774
|
workspacePath: resolveProducerWorkspaceCopySource(producerContext, stateDirs)
|
|
9696
9775
|
};
|
|
9697
9776
|
}
|
|
9698
|
-
async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slotRegistry, runtimeSessionStore) {
|
|
9777
|
+
async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slotRegistry, runtimeSessionStore, sourceAttemptResolver) {
|
|
9699
9778
|
if (claimedTask.task.taskType === "freeform") {
|
|
9700
9779
|
const continueFrom = claimedTask.task.input.continueFrom;
|
|
9701
9780
|
if (!continueFrom) return basePlan;
|
|
9702
9781
|
const resolution = await resolveWarmSlot(slotRegistry, runtimeSessionStore, claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN, stateDirs);
|
|
9703
|
-
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no
|
|
9782
|
+
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no local runtime slot or durable runtime session — claim affinity filter should have prevented this claim`);
|
|
9704
9783
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Continuation source attempt ${continueFrom.taskId}/${continueFrom.attemptN} has no persisted Pi session path`);
|
|
9705
9784
|
const sessionDir = `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
|
|
9785
|
+
if (resolution.kind === "remote-session") {
|
|
9786
|
+
const recoveredBranch = await sourceAttemptResolver.findOutputBranch({
|
|
9787
|
+
attemptN: continueFrom.attemptN,
|
|
9788
|
+
taskId: continueFrom.taskId
|
|
9789
|
+
});
|
|
9790
|
+
if (continueFrom.mode === "fork") {
|
|
9791
|
+
if (recoveredBranch) {
|
|
9792
|
+
const forkWorkspaceId = `fork-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
|
|
9793
|
+
const forkBranch = buildForkBranch(recoveredBranch, claimedTask.task.id, claimedTask.attemptN);
|
|
9794
|
+
return {
|
|
9795
|
+
...basePlan,
|
|
9796
|
+
workspaceMode: "dedicated_worktree",
|
|
9797
|
+
workspaceId: forkWorkspaceId,
|
|
9798
|
+
worktreeBranch: forkBranch,
|
|
9799
|
+
worktreeBaseRef: recoveredBranch,
|
|
9800
|
+
workspaceKind: "fork",
|
|
9801
|
+
sessionPersistence: {
|
|
9802
|
+
sessionDir,
|
|
9803
|
+
forkFromSessionPath: resolution.sessionPath
|
|
9804
|
+
}
|
|
9805
|
+
};
|
|
9806
|
+
}
|
|
9807
|
+
throw new ProducerContextResolutionError(`Cannot fork continuation of ${continueFrom.taskId}/${continueFrom.attemptN}: durable runtime session is available but the source attempt output did not report a branch`);
|
|
9808
|
+
}
|
|
9809
|
+
return {
|
|
9810
|
+
...basePlan,
|
|
9811
|
+
workspaceMode: "dedicated_worktree",
|
|
9812
|
+
workspaceId: recoveredBranch ? `extend-${continueFrom.taskId}-attempt-${continueFrom.attemptN}` : null,
|
|
9813
|
+
worktreeBranch: recoveredBranch,
|
|
9814
|
+
sessionPersistence: {
|
|
9815
|
+
sessionDir,
|
|
9816
|
+
forkFromSessionPath: resolution.sessionPath
|
|
9817
|
+
}
|
|
9818
|
+
};
|
|
9819
|
+
}
|
|
9706
9820
|
const parentBranch = resolution.producerSlot.workspace?.worktreeBranch ?? null;
|
|
9707
9821
|
if (continueFrom.mode === "fork") {
|
|
9708
9822
|
if (!parentBranch) throw new ProducerContextResolutionError(`Cannot fork continuation of ${continueFrom.taskId}/${continueFrom.attemptN}: producer slot has no worktree branch to fork from`);
|
|
9709
9823
|
const forkWorkspaceId = `fork-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
|
|
9710
|
-
const forkBranch =
|
|
9824
|
+
const forkBranch = buildForkBranch(parentBranch, claimedTask.task.id, claimedTask.attemptN);
|
|
9711
9825
|
return {
|
|
9712
9826
|
...basePlan,
|
|
9713
9827
|
workspaceMode: "dedicated_worktree",
|
|
@@ -9739,6 +9853,7 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
9739
9853
|
const resolution = await resolveWarmSlot(slotRegistry, runtimeSessionStore, claimedTask.task.teamId, targetTaskId, targetAttemptN, stateDirs);
|
|
9740
9854
|
if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
|
|
9741
9855
|
if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
|
|
9856
|
+
if (resolution.kind === "remote-session") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has a durable runtime session but no workspace metadata to copy`);
|
|
9742
9857
|
return {
|
|
9743
9858
|
...basePlan,
|
|
9744
9859
|
workspaceMode: "scratch_mount",
|
|
@@ -9754,6 +9869,9 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
|
|
|
9754
9869
|
}
|
|
9755
9870
|
};
|
|
9756
9871
|
}
|
|
9872
|
+
function buildForkBranch(parentBranch, childTaskId, childAttemptN) {
|
|
9873
|
+
return `${parentBranch}-fork-${childTaskId.slice(0, 8)}-${childAttemptN}`;
|
|
9874
|
+
}
|
|
9757
9875
|
function resolveProducerSessionPath(producer) {
|
|
9758
9876
|
const explicit = producer.session?.sessionPath ?? null;
|
|
9759
9877
|
if (explicit && existsSync(explicit)) return explicit;
|
|
@@ -9783,12 +9901,11 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
|
9783
9901
|
//#endregion
|
|
9784
9902
|
//#region src/lib/finalize.ts
|
|
9785
9903
|
/**
|
|
9786
|
-
* Build the `daemonState` payload for a `/complete` call.
|
|
9787
|
-
* attempts
|
|
9788
|
-
*
|
|
9789
|
-
*
|
|
9790
|
-
*
|
|
9791
|
-
* fail validation with `freeform.sourceNotResumeEligible`.
|
|
9904
|
+
* Build the `daemonState` payload for a `/complete` call. Freeform
|
|
9905
|
+
* attempts report the local warm-slot hint when one exists; slot-less
|
|
9906
|
+
* freeform completions report `null` for `slotResumableUntil`. The server
|
|
9907
|
+
* persists this verbatim as diagnostic/runtime metadata, not as the
|
|
9908
|
+
* continuation eligibility gate.
|
|
9792
9909
|
*
|
|
9793
9910
|
* Returns `null` for non-freeform task types so the field is omitted
|
|
9794
9911
|
* from the request body (the server treats null and absent the same).
|
|
@@ -10082,6 +10199,11 @@ async function resolveRuntimeProfile(options) {
|
|
|
10082
10199
|
teamId: profile.teamId,
|
|
10083
10200
|
provider: profile.provider,
|
|
10084
10201
|
model: profile.model,
|
|
10202
|
+
thinkingLevel: profile.thinkingLevel ?? null,
|
|
10203
|
+
temperature: profile.temperature ?? null,
|
|
10204
|
+
topP: profile.topP ?? null,
|
|
10205
|
+
topK: profile.topK ?? null,
|
|
10206
|
+
maxOutputTokens: profile.maxOutputTokens ?? null,
|
|
10085
10207
|
leaseTtlSec: profile.leaseTtlSec,
|
|
10086
10208
|
heartbeatIntervalMs: profile.heartbeatIntervalMs,
|
|
10087
10209
|
maxBatchSize: profile.maxBatchSize,
|
|
@@ -10127,7 +10249,15 @@ async function resolveProfileByName(options) {
|
|
|
10127
10249
|
const matches = (await options.agent.runtimeProfiles.list({ teamId: options.teamId })).items.filter((item) => item.name === options.profile);
|
|
10128
10250
|
if (matches.length === 0) throw new Error(`Runtime profile "${options.profile}" was not found in team ${options.teamId}.`);
|
|
10129
10251
|
if (matches.length > 1) throw new Error(`Runtime profile name "${options.profile}" is ambiguous in team ${options.teamId}. Use the profile UUID instead.`);
|
|
10130
|
-
|
|
10252
|
+
const profile = matches[0];
|
|
10253
|
+
return {
|
|
10254
|
+
...profile,
|
|
10255
|
+
thinkingLevel: profile.thinkingLevel ?? null,
|
|
10256
|
+
temperature: profile.temperature ?? null,
|
|
10257
|
+
topP: profile.topP ?? null,
|
|
10258
|
+
topK: profile.topK ?? null,
|
|
10259
|
+
maxOutputTokens: profile.maxOutputTokens ?? null
|
|
10260
|
+
};
|
|
10131
10261
|
}
|
|
10132
10262
|
function isExecutableOnPath(tool, pathValue) {
|
|
10133
10263
|
if (tool.includes("/")) return isExecutable(isAbsolute(tool) ? tool : resolve(process.cwd(), tool));
|
|
@@ -10268,6 +10398,34 @@ function createApiRuntimeSlotStore(args) {
|
|
|
10268
10398
|
} : null
|
|
10269
10399
|
};
|
|
10270
10400
|
},
|
|
10401
|
+
async listSlots(input) {
|
|
10402
|
+
const query = {
|
|
10403
|
+
agentName: input.agentName,
|
|
10404
|
+
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
10405
|
+
...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
|
|
10406
|
+
...input.state === void 0 ? {} : { state: input.state }
|
|
10407
|
+
};
|
|
10408
|
+
return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
|
|
10409
|
+
slot: {
|
|
10410
|
+
expiresAtMs: resolved.slot.expiresAtMs,
|
|
10411
|
+
id: resolved.slot.id,
|
|
10412
|
+
lastAttemptN: resolved.slot.lastAttemptN,
|
|
10413
|
+
lastTaskId: resolved.slot.lastTaskId,
|
|
10414
|
+
runtimeProfileId: resolved.slot.runtimeProfileId,
|
|
10415
|
+
taskType: resolved.slot.taskType
|
|
10416
|
+
},
|
|
10417
|
+
session: resolved.slot.sessionDir ? {
|
|
10418
|
+
sessionDir: resolved.slot.sessionDir,
|
|
10419
|
+
sessionPath: resolved.slot.sessionPath
|
|
10420
|
+
} : null,
|
|
10421
|
+
workspace: resolved.workspace ? {
|
|
10422
|
+
kind: resolved.workspace.kind,
|
|
10423
|
+
workspaceId: resolved.workspace.workspaceId,
|
|
10424
|
+
worktreeBranch: resolved.workspace.worktreeBranch,
|
|
10425
|
+
worktreePath: resolved.workspace.worktreePath
|
|
10426
|
+
} : null
|
|
10427
|
+
}));
|
|
10428
|
+
},
|
|
10271
10429
|
async close() {}
|
|
10272
10430
|
};
|
|
10273
10431
|
}
|
|
@@ -10304,6 +10462,21 @@ function signalExitCode(signal) {
|
|
|
10304
10462
|
return signal === "SIGINT" ? 130 : 143;
|
|
10305
10463
|
}
|
|
10306
10464
|
//#endregion
|
|
10465
|
+
//#region src/lib/source-attempts.ts
|
|
10466
|
+
function createApiSourceAttemptResolver(args) {
|
|
10467
|
+
const { agent } = args;
|
|
10468
|
+
return { async findOutputBranch(input) {
|
|
10469
|
+
const attempt = (await agent.tasks.listAttempts(input.taskId)).find((candidate) => candidate.attemptN === input.attemptN);
|
|
10470
|
+
if (!attempt || attempt.status !== "completed") return null;
|
|
10471
|
+
return resolveOutputBranch(attempt.output);
|
|
10472
|
+
} };
|
|
10473
|
+
}
|
|
10474
|
+
function resolveOutputBranch(output) {
|
|
10475
|
+
if (!output || typeof output !== "object") return null;
|
|
10476
|
+
const branch = output.branch;
|
|
10477
|
+
return typeof branch === "string" && branch.length > 0 ? branch : null;
|
|
10478
|
+
}
|
|
10479
|
+
//#endregion
|
|
10307
10480
|
//#region src/lib/state-dir.ts
|
|
10308
10481
|
function ensureDaemonStateDirs(mountPath) {
|
|
10309
10482
|
const rootDir = join(mountPath, ".moltnet", "d");
|
|
@@ -10411,6 +10584,7 @@ async function runPolling(opts) {
|
|
|
10411
10584
|
for (const profile of profiles) validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
|
|
10412
10585
|
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10413
10586
|
const runtimeSessionStore = createApiRuntimeSessionStore({ agent: ctx.agent });
|
|
10587
|
+
const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
|
|
10414
10588
|
const runtimes = /* @__PURE__ */ new Map();
|
|
10415
10589
|
for (const profile of profiles) {
|
|
10416
10590
|
const common = parseCommonOptions(values, { runtimeDefaults: {
|
|
@@ -10441,7 +10615,8 @@ async function runPolling(opts) {
|
|
|
10441
10615
|
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
10442
10616
|
},
|
|
10443
10617
|
slotRegistry,
|
|
10444
|
-
runtimeSessionStore
|
|
10618
|
+
runtimeSessionStore,
|
|
10619
|
+
sourceAttemptResolver
|
|
10445
10620
|
});
|
|
10446
10621
|
runtimes.set(profile.id, {
|
|
10447
10622
|
common,
|
|
@@ -10514,6 +10689,11 @@ async function runPolling(opts) {
|
|
|
10514
10689
|
name: profile.name,
|
|
10515
10690
|
provider: profile.provider,
|
|
10516
10691
|
model: profile.model,
|
|
10692
|
+
thinkingLevel: profile.thinkingLevel,
|
|
10693
|
+
temperature: profile.temperature,
|
|
10694
|
+
topP: profile.topP,
|
|
10695
|
+
topK: profile.topK,
|
|
10696
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
10517
10697
|
sandbox: runtime.sandbox.path,
|
|
10518
10698
|
leaseTtlSec: runtime.common.leaseTtlSec,
|
|
10519
10699
|
heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
|
|
@@ -10551,7 +10731,8 @@ async function runPolling(opts) {
|
|
|
10551
10731
|
debug: baseCommon.debug,
|
|
10552
10732
|
logger: rootLogger,
|
|
10553
10733
|
slotRegistry,
|
|
10554
|
-
sessionRegistry: runtimeSessionStore
|
|
10734
|
+
sessionRegistry: runtimeSessionStore,
|
|
10735
|
+
sourceAttemptResolver
|
|
10555
10736
|
}),
|
|
10556
10737
|
makeReporter: (claimedTask) => {
|
|
10557
10738
|
const selected = runtimeForClaimedTask(runtimes, claimedTask);
|
|
@@ -10606,7 +10787,12 @@ async function runPolling(opts) {
|
|
|
10606
10787
|
runtimeProfileId: profile.id,
|
|
10607
10788
|
runtimeProfileName: profile.name,
|
|
10608
10789
|
provider: profile.provider,
|
|
10609
|
-
model: profile.model
|
|
10790
|
+
model: profile.model,
|
|
10791
|
+
thinkingLevel: profile.thinkingLevel,
|
|
10792
|
+
temperature: profile.temperature,
|
|
10793
|
+
topP: profile.topP,
|
|
10794
|
+
topK: profile.topK,
|
|
10795
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
10610
10796
|
});
|
|
10611
10797
|
let executionPlan;
|
|
10612
10798
|
try {
|
|
@@ -10707,6 +10893,11 @@ async function runPolling(opts) {
|
|
|
10707
10893
|
mountPath: sandbox.rootDir,
|
|
10708
10894
|
provider: profile.provider,
|
|
10709
10895
|
model: profile.model,
|
|
10896
|
+
thinkingLevel: profile.thinkingLevel,
|
|
10897
|
+
temperature: profile.temperature,
|
|
10898
|
+
topP: profile.topP,
|
|
10899
|
+
topK: profile.topK,
|
|
10900
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
10710
10901
|
sandboxConfig: sandbox.config,
|
|
10711
10902
|
makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
|
|
10712
10903
|
makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
|
|
@@ -10722,7 +10913,12 @@ async function runPolling(opts) {
|
|
|
10722
10913
|
profileId: profile.id,
|
|
10723
10914
|
profileName: profile.name,
|
|
10724
10915
|
provider: profile.provider,
|
|
10725
|
-
model: profile.model
|
|
10916
|
+
model: profile.model,
|
|
10917
|
+
thinkingLevel: profile.thinkingLevel,
|
|
10918
|
+
temperature: profile.temperature,
|
|
10919
|
+
topP: profile.topP,
|
|
10920
|
+
topK: profile.topK,
|
|
10921
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
10726
10922
|
}, () => rawExecuteTask(claimedTask, reporter));
|
|
10727
10923
|
} finally {
|
|
10728
10924
|
active = null;
|
|
@@ -10853,6 +11049,7 @@ async function runOnce(argv) {
|
|
|
10853
11049
|
const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
|
|
10854
11050
|
const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
|
|
10855
11051
|
const runtimeSessionStore = createApiRuntimeSessionStore({ agent: ctx.agent });
|
|
11052
|
+
const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
|
|
10856
11053
|
const slotIdentity = {
|
|
10857
11054
|
agentName: opts.agent,
|
|
10858
11055
|
runtimeProfileId: profile.id
|
|
@@ -10866,7 +11063,8 @@ async function runOnce(argv) {
|
|
|
10866
11063
|
allowedWorkspaceModes: profile.allowedWorkspaceModes
|
|
10867
11064
|
},
|
|
10868
11065
|
slotRegistry,
|
|
10869
|
-
runtimeSessionStore
|
|
11066
|
+
runtimeSessionStore,
|
|
11067
|
+
sourceAttemptResolver
|
|
10870
11068
|
});
|
|
10871
11069
|
const otelShutdown = await initWorkerOtel({
|
|
10872
11070
|
serviceName: "moltnet.agent-daemon.once",
|
|
@@ -10877,6 +11075,11 @@ async function runOnce(argv) {
|
|
|
10877
11075
|
"moltnet.agent.name": opts.agent,
|
|
10878
11076
|
"moltnet.llm.provider": profile.provider,
|
|
10879
11077
|
"moltnet.llm.model": profile.model,
|
|
11078
|
+
...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
|
|
11079
|
+
...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
|
|
11080
|
+
...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
|
|
11081
|
+
...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
|
|
11082
|
+
...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
|
|
10880
11083
|
"moltnet.runtime_profile.id": profile.id
|
|
10881
11084
|
}
|
|
10882
11085
|
});
|
|
@@ -10889,6 +11092,11 @@ async function runOnce(argv) {
|
|
|
10889
11092
|
agent: opts.agent,
|
|
10890
11093
|
provider: profile.provider,
|
|
10891
11094
|
model: profile.model,
|
|
11095
|
+
thinkingLevel: profile.thinkingLevel,
|
|
11096
|
+
temperature: profile.temperature,
|
|
11097
|
+
topP: profile.topP,
|
|
11098
|
+
topK: profile.topK,
|
|
11099
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
10892
11100
|
runtimeProfileId: profile.id,
|
|
10893
11101
|
runtimeProfileName: profile.name
|
|
10894
11102
|
});
|
|
@@ -10939,6 +11147,11 @@ async function runOnce(argv) {
|
|
|
10939
11147
|
mountPath: sandbox.rootDir,
|
|
10940
11148
|
provider: profile.provider,
|
|
10941
11149
|
model: profile.model,
|
|
11150
|
+
thinkingLevel: profile.thinkingLevel,
|
|
11151
|
+
temperature: profile.temperature,
|
|
11152
|
+
topP: profile.topP,
|
|
11153
|
+
topK: profile.topK,
|
|
11154
|
+
maxOutputTokens: profile.maxOutputTokens,
|
|
10942
11155
|
sandboxConfig: sandbox.config,
|
|
10943
11156
|
makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
|
|
10944
11157
|
onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
|
|
@@ -10997,7 +11210,12 @@ async function runOnce(argv) {
|
|
|
10997
11210
|
profileId: profile.id,
|
|
10998
11211
|
profileName: profile.name,
|
|
10999
11212
|
provider: profile.provider,
|
|
11000
|
-
model: profile.model
|
|
11213
|
+
model: profile.model,
|
|
11214
|
+
thinkingLevel: profile.thinkingLevel,
|
|
11215
|
+
temperature: profile.temperature,
|
|
11216
|
+
topP: profile.topP,
|
|
11217
|
+
topK: profile.topK,
|
|
11218
|
+
maxOutputTokens: profile.maxOutputTokens
|
|
11001
11219
|
}, () => rawExecuteTask(claimedTask, reporter));
|
|
11002
11220
|
} finally {
|
|
11003
11221
|
activeAttemptN = null;
|
|
@@ -11087,6 +11305,182 @@ function runPoll(argv) {
|
|
|
11087
11305
|
});
|
|
11088
11306
|
}
|
|
11089
11307
|
//#endregion
|
|
11308
|
+
//#region src/lib/runtime-session-sync.ts
|
|
11309
|
+
async function syncRuntimeSessions(deps, input) {
|
|
11310
|
+
const result = {
|
|
11311
|
+
alreadyCurrent: 0,
|
|
11312
|
+
failedUpload: 0,
|
|
11313
|
+
missingLocalFile: 0,
|
|
11314
|
+
scanned: 0,
|
|
11315
|
+
unsafeSessionPath: 0,
|
|
11316
|
+
uploaded: 0,
|
|
11317
|
+
wouldUpload: 0
|
|
11318
|
+
};
|
|
11319
|
+
const slots = await deps.runtimeSlotStore.listSlots({
|
|
11320
|
+
agentName: input.agentName,
|
|
11321
|
+
limit: input.limit,
|
|
11322
|
+
runtimeProfileId: input.runtimeProfileId,
|
|
11323
|
+
state: input.state,
|
|
11324
|
+
teamId: input.teamId
|
|
11325
|
+
});
|
|
11326
|
+
for (const slot of slots) {
|
|
11327
|
+
result.scanned++;
|
|
11328
|
+
if (!slot.session?.sessionDir) {
|
|
11329
|
+
result.missingLocalFile++;
|
|
11330
|
+
continue;
|
|
11331
|
+
}
|
|
11332
|
+
if (input.sessionRootDir && !await isPathInsideRoot(slot.session.sessionDir, input.sessionRootDir)) {
|
|
11333
|
+
result.unsafeSessionPath++;
|
|
11334
|
+
continue;
|
|
11335
|
+
}
|
|
11336
|
+
const sessionPath = resolveLatestPiSessionPath(slot.session.sessionDir);
|
|
11337
|
+
if (!sessionPath || !await fileExists(sessionPath)) {
|
|
11338
|
+
result.missingLocalFile++;
|
|
11339
|
+
continue;
|
|
11340
|
+
}
|
|
11341
|
+
const remote = await deps.runtimeSessionStore.findRuntimeSessionByTaskAttempt(input.teamId, slot.slot.lastTaskId, slot.slot.lastAttemptN);
|
|
11342
|
+
if (remote) {
|
|
11343
|
+
const local = await computeCompressedSessionFingerprint(sessionPath);
|
|
11344
|
+
if (remote.sha256 === local.sha256 && remote.sizeBytes === local.bytes) {
|
|
11345
|
+
result.alreadyCurrent++;
|
|
11346
|
+
continue;
|
|
11347
|
+
}
|
|
11348
|
+
}
|
|
11349
|
+
if (input.dryRun) {
|
|
11350
|
+
result.wouldUpload++;
|
|
11351
|
+
continue;
|
|
11352
|
+
}
|
|
11353
|
+
try {
|
|
11354
|
+
const task = await deps.taskReader.get(slot.slot.lastTaskId);
|
|
11355
|
+
const claim = {
|
|
11356
|
+
attemptN: slot.slot.lastAttemptN,
|
|
11357
|
+
task: {
|
|
11358
|
+
id: task.id,
|
|
11359
|
+
input: task.input,
|
|
11360
|
+
teamId: input.teamId
|
|
11361
|
+
}
|
|
11362
|
+
};
|
|
11363
|
+
const parent = await resolveParentRuntimeSession(deps.runtimeSessionStore, claim);
|
|
11364
|
+
await deps.runtimeSessionStore.uploadAttemptFinal({
|
|
11365
|
+
attemptN: slot.slot.lastAttemptN,
|
|
11366
|
+
parentSessionId: parent?.id ?? null,
|
|
11367
|
+
sessionDir: slot.session.sessionDir,
|
|
11368
|
+
sessionKind: resolveRuntimeSessionKind(claim),
|
|
11369
|
+
sourceRuntimeProfileId: slot.slot.runtimeProfileId,
|
|
11370
|
+
sourceSlotId: slot.slot.id,
|
|
11371
|
+
taskId: slot.slot.lastTaskId,
|
|
11372
|
+
teamId: input.teamId
|
|
11373
|
+
});
|
|
11374
|
+
result.uploaded++;
|
|
11375
|
+
} catch {
|
|
11376
|
+
result.failedUpload++;
|
|
11377
|
+
}
|
|
11378
|
+
}
|
|
11379
|
+
return result;
|
|
11380
|
+
}
|
|
11381
|
+
async function fileExists(path) {
|
|
11382
|
+
try {
|
|
11383
|
+
return (await stat(path)).isFile();
|
|
11384
|
+
} catch {
|
|
11385
|
+
return false;
|
|
11386
|
+
}
|
|
11387
|
+
}
|
|
11388
|
+
async function isPathInsideRoot(path, root) {
|
|
11389
|
+
if (!isResolvedPathInsideRoot(resolve(path), resolve(root))) return false;
|
|
11390
|
+
let realResolvedPath;
|
|
11391
|
+
let realResolvedRoot;
|
|
11392
|
+
try {
|
|
11393
|
+
[realResolvedPath, realResolvedRoot] = await Promise.all([realpath(path), realpath(root)]);
|
|
11394
|
+
} catch {
|
|
11395
|
+
return true;
|
|
11396
|
+
}
|
|
11397
|
+
return isResolvedPathInsideRoot(realResolvedPath, realResolvedRoot);
|
|
11398
|
+
}
|
|
11399
|
+
function isResolvedPathInsideRoot(path, root) {
|
|
11400
|
+
const rel = relative(root, path);
|
|
11401
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
11402
|
+
}
|
|
11403
|
+
async function computeCompressedSessionFingerprint(path) {
|
|
11404
|
+
const hash = createHash("sha256");
|
|
11405
|
+
let bytes = 0;
|
|
11406
|
+
const sink = new Writable({ write(chunk, _encoding, callback) {
|
|
11407
|
+
bytes += chunk.byteLength;
|
|
11408
|
+
hash.update(chunk);
|
|
11409
|
+
callback();
|
|
11410
|
+
} });
|
|
11411
|
+
await pipeline(createReadStream(path), createGzip(), sink);
|
|
11412
|
+
return {
|
|
11413
|
+
bytes,
|
|
11414
|
+
sha256: hash.digest("hex")
|
|
11415
|
+
};
|
|
11416
|
+
}
|
|
11417
|
+
//#endregion
|
|
11418
|
+
//#region src/cli/sync-sessions.ts
|
|
11419
|
+
async function runSyncSessions(argv) {
|
|
11420
|
+
if (isHelpFlag(argv)) {
|
|
11421
|
+
console.log(SYNC_SESSIONS_HELP);
|
|
11422
|
+
return 0;
|
|
11423
|
+
}
|
|
11424
|
+
const { values } = parseArgs({
|
|
11425
|
+
args: argv,
|
|
11426
|
+
options: {
|
|
11427
|
+
...commonOptionDefs(),
|
|
11428
|
+
team: { type: "string" },
|
|
11429
|
+
"runtime-profile-id": { type: "string" },
|
|
11430
|
+
state: { type: "string" },
|
|
11431
|
+
limit: { type: "string" },
|
|
11432
|
+
"dry-run": { type: "boolean" }
|
|
11433
|
+
}
|
|
11434
|
+
});
|
|
11435
|
+
if (!values.team) {
|
|
11436
|
+
console.error("Missing required flag: --team\n");
|
|
11437
|
+
console.error(SYNC_SESSIONS_HELP);
|
|
11438
|
+
return 1;
|
|
11439
|
+
}
|
|
11440
|
+
let opts;
|
|
11441
|
+
try {
|
|
11442
|
+
opts = parseCommonOptions(values);
|
|
11443
|
+
} catch (err) {
|
|
11444
|
+
if (err instanceof MissingRequiredOptionError) {
|
|
11445
|
+
console.error(`${err.message}\n`);
|
|
11446
|
+
console.error(SYNC_SESSIONS_HELP);
|
|
11447
|
+
return 1;
|
|
11448
|
+
}
|
|
11449
|
+
throw err;
|
|
11450
|
+
}
|
|
11451
|
+
const state = parseState(values.state);
|
|
11452
|
+
const limit = parseLimit(values.limit);
|
|
11453
|
+
const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
|
|
11454
|
+
const ctx = await resolveAgentContext(opts.agent, { agentRootDir });
|
|
11455
|
+
const stateDirs = ensureDaemonStateDirs(agentRootDir);
|
|
11456
|
+
const result = await syncRuntimeSessions({
|
|
11457
|
+
runtimeSessionStore: createApiRuntimeSessionStore({ agent: ctx.agent }),
|
|
11458
|
+
runtimeSlotStore: createApiRuntimeSlotStore({ agent: ctx.agent }),
|
|
11459
|
+
taskReader: ctx.agent.tasks
|
|
11460
|
+
}, {
|
|
11461
|
+
agentName: opts.agent,
|
|
11462
|
+
dryRun: values["dry-run"] === true,
|
|
11463
|
+
limit,
|
|
11464
|
+
runtimeProfileId: values["runtime-profile-id"],
|
|
11465
|
+
sessionRootDir: stateDirs.piSessionsDir,
|
|
11466
|
+
state,
|
|
11467
|
+
teamId: values.team
|
|
11468
|
+
});
|
|
11469
|
+
console.log(JSON.stringify(result, null, 2));
|
|
11470
|
+
return result.failedUpload > 0 || result.unsafeSessionPath > 0 ? 1 : 0;
|
|
11471
|
+
}
|
|
11472
|
+
function parseState(raw) {
|
|
11473
|
+
if (raw === void 0) return void 0;
|
|
11474
|
+
if (raw === "active" || raw === "idle") return raw;
|
|
11475
|
+
throw new Error(`Invalid --state "${raw}": expected active or idle`);
|
|
11476
|
+
}
|
|
11477
|
+
function parseLimit(raw) {
|
|
11478
|
+
if (raw === void 0) return void 0;
|
|
11479
|
+
const value = Number(raw);
|
|
11480
|
+
if (!Number.isInteger(value) || value < 1 || value > 200) throw new Error(`Invalid --limit "${raw}": expected integer 1..200`);
|
|
11481
|
+
return value;
|
|
11482
|
+
}
|
|
11483
|
+
//#endregion
|
|
11090
11484
|
//#region src/main.ts
|
|
11091
11485
|
async function main() {
|
|
11092
11486
|
const [, , subcommand, ...rest] = process.argv;
|
|
@@ -11094,6 +11488,7 @@ async function main() {
|
|
|
11094
11488
|
case "poll": return runPoll(rest);
|
|
11095
11489
|
case "once": return runOnce(rest);
|
|
11096
11490
|
case "drain": return runDrain(rest);
|
|
11491
|
+
case "sync-sessions": return runSyncSessions(rest);
|
|
11097
11492
|
case "-h":
|
|
11098
11493
|
case "--help":
|
|
11099
11494
|
console.log(ROOT_USAGE);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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/
|
|
50
|
-
"@themoltnet/
|
|
48
|
+
"@themoltnet/agent-runtime": "0.31.1",
|
|
49
|
+
"@themoltnet/pi-extension": "0.28.0",
|
|
50
|
+
"@themoltnet/sdk": "0.114.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"tsx": "^4.7.0",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"vite": "^8.0.0",
|
|
56
56
|
"vitest": "^3.0.0",
|
|
57
57
|
"@moltnet/bootstrap": "0.1.0",
|
|
58
|
-
"@moltnet/crypto-service": "0.1.0",
|
|
59
58
|
"@moltnet/observability": "0.1.0",
|
|
60
|
-
"@moltnet/tasks": "0.1.0"
|
|
59
|
+
"@moltnet/tasks": "0.1.0",
|
|
60
|
+
"@moltnet/crypto-service": "0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"nx": {
|
|
63
63
|
"tags": [
|
|
@@ -74,6 +74,9 @@
|
|
|
74
74
|
"^production",
|
|
75
75
|
"{projectRoot}/vitest.config.e2e.ts",
|
|
76
76
|
"{projectRoot}/e2e/**/*",
|
|
77
|
+
{
|
|
78
|
+
"env": "MOLTNET_AGENT_DAEMON_LIVE_LLM_E2E"
|
|
79
|
+
},
|
|
77
80
|
{
|
|
78
81
|
"externalDependencies": [
|
|
79
82
|
"vitest"
|