@themoltnet/agent-daemon 0.10.1 → 0.10.3
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 +35 -5
- package/dist/main.js +121 -34
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -158,9 +158,14 @@ This matters for evals in particular. `run_eval` tasks declare their intended
|
|
|
158
158
|
workspace shape in `input.execution.workspace`: `none` becomes a
|
|
159
159
|
`scratch_mount`, `shared_mount` uses the daemon mount, and
|
|
160
160
|
`dedicated_worktree` uses an isolated checkout. Downstream
|
|
161
|
-
`judge_eval_attempt` tasks
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
`judge_eval_attempt` tasks only resolve against a still-live producer
|
|
162
|
+
session/workspace slot. If the producer slot has already expired and been
|
|
163
|
+
reaped, the judge fails with `producer_context_missing`. When the judge does
|
|
164
|
+
claim in time, it immediately forks the producer session and copies the
|
|
165
|
+
producer workspace into judge-owned scratch state so the running judge no
|
|
166
|
+
longer depends on the producer slot after claim time. Repo-specific
|
|
167
|
+
`resumeCommands` that should not run in scratch mode must still be guarded
|
|
168
|
+
with `when.workspaceMode`.
|
|
164
169
|
|
|
165
170
|
### 1. Start the local stack
|
|
166
171
|
|
|
@@ -243,9 +248,32 @@ Leave it running. It idles until a task lands in its queue.
|
|
|
243
248
|
|
|
244
249
|
### 4. Create a task
|
|
245
250
|
|
|
246
|
-
In another terminal, with `.moltnet/local-dev/env` sourced
|
|
251
|
+
In another terminal, with `.moltnet/local-dev/env` sourced. Pick the CLI
|
|
252
|
+
form (recommended — schema-validates locally, no Node dependency in the
|
|
253
|
+
imposer path) or the template-driven TS form (legacy path that supports
|
|
254
|
+
`{{placeholder}}` substitution via `--set`).
|
|
255
|
+
|
|
256
|
+
::: code-group
|
|
257
|
+
|
|
258
|
+
```bash [CLI (recommended)]
|
|
259
|
+
BRIEF="Create a feature branch named feat/smoke-hello, write \
|
|
260
|
+
/workspace/demo/out/hello.txt with the single line 'hi from local-dev', \
|
|
261
|
+
commit the file with a signed diary entry per the runtime instructor, \
|
|
262
|
+
and report the branch name and commit sha in the final \
|
|
263
|
+
FulfillBriefOutput JSON. There is no remote to push to — leave \
|
|
264
|
+
pullRequestUrl null."
|
|
265
|
+
|
|
266
|
+
jq -n --arg brief "$BRIEF" \
|
|
267
|
+
--arg title "Smoke: hello file in a feature branch" \
|
|
268
|
+
'{brief: $brief, title: $title, scopeHint: "feature"}' \
|
|
269
|
+
| moltnet task create \
|
|
270
|
+
--task-type fulfill_brief \
|
|
271
|
+
--team-id "$MOLTNET_TEAM_ID" \
|
|
272
|
+
--diary-id "$MOLTNET_DIARY_ID" \
|
|
273
|
+
--credentials "$PWD/.moltnet/local-dev/moltnet.json"
|
|
274
|
+
```
|
|
247
275
|
|
|
248
|
-
```bash
|
|
276
|
+
```bash [tsx (legacy)]
|
|
249
277
|
pnpm exec tsx tools/src/tasks/create-task.ts \
|
|
250
278
|
--agent local-dev \
|
|
251
279
|
--task-file examples/tasks/api/fulfill-brief.create.template.json \
|
|
@@ -255,6 +283,8 @@ pnpm exec tsx tools/src/tasks/create-task.ts \
|
|
|
255
283
|
--set brief="Create a feature branch named feat/smoke-hello, write /workspace/demo/out/hello.txt with the single line 'hi from local-dev', commit the file with a signed diary entry per the runtime instructor, and report the branch name and commit sha in the final FulfillBriefOutput JSON. There is no remote to push to — leave pullRequestUrl null."
|
|
256
284
|
```
|
|
257
285
|
|
|
286
|
+
:::
|
|
287
|
+
|
|
258
288
|
> **Why a real coding brief**: `fulfill_brief` requires the agent to emit a
|
|
259
289
|
> structured `FulfillBriefOutput` JSON
|
|
260
290
|
> (`{ branch, commits, pullRequestUrl, diaryEntryIds, summary }`) as its
|
package/dist/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import crypto, { createHash } from "crypto";
|
|
3
|
-
import path, { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parseArgs, parseEnv, promisify } from "node:util";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
6
6
|
import { ROOT_CONTEXT, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
7
7
|
import { pino, transport } from "pino";
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
@@ -4510,7 +4510,8 @@ var PrReviewSubject = Type$2.Object({
|
|
|
4510
4510
|
var PrReviewInput = Type$2.Object({
|
|
4511
4511
|
subject: PrReviewSubject,
|
|
4512
4512
|
taskPrompt: Type$2.Optional(Type$2.String({ minLength: 1 })),
|
|
4513
|
-
successCriteria: SuccessCriteria
|
|
4513
|
+
successCriteria: SuccessCriteria,
|
|
4514
|
+
context: Type$2.Optional(TaskContext)
|
|
4514
4515
|
}, {
|
|
4515
4516
|
$id: "PrReviewInput",
|
|
4516
4517
|
additionalProperties: false
|
|
@@ -6794,7 +6795,7 @@ function buildJudgeEvalAttemptUserPrompt(input, ctx) {
|
|
|
6794
6795
|
"If the accepted attempt output lists `artifacts[].path`, treat those",
|
|
6795
6796
|
"paths as relative to the current workspace root unless the output",
|
|
6796
6797
|
"explicitly says otherwise.",
|
|
6797
|
-
ctx.workspace.mode === "dedicated_worktree" ? `This attachment is a dedicated producer worktree${ctx.workspace.branch ? ` on branch \`${ctx.workspace.branch}\`` : ""}.` : ctx.workspace.mode === "scratch_mount" ? "This
|
|
6798
|
+
ctx.workspace.mode === "dedicated_worktree" ? `This attachment is a dedicated producer worktree${ctx.workspace.branch ? ` on branch \`${ctx.workspace.branch}\`` : ""}.` : ctx.workspace.mode === "scratch_mount" ? "This workspace is a fresh judge-owned scratch copy of the producer workspace." : "This attachment is the producer shared workspace mounted with shadow writes for safe inspection.",
|
|
6798
6799
|
""
|
|
6799
6800
|
].join("\n") : "";
|
|
6800
6801
|
return [
|
|
@@ -9482,6 +9483,32 @@ var removeTeamMember = (options) => (options.client ?? client).delete({
|
|
|
9482
9483
|
...options
|
|
9483
9484
|
});
|
|
9484
9485
|
/**
|
|
9486
|
+
* Update a member role between member and manager. Requires manage_members permission.
|
|
9487
|
+
*/
|
|
9488
|
+
var updateTeamMemberRole = (options) => (options.client ?? client).patch({
|
|
9489
|
+
security: [
|
|
9490
|
+
{
|
|
9491
|
+
scheme: "bearer",
|
|
9492
|
+
type: "http"
|
|
9493
|
+
},
|
|
9494
|
+
{
|
|
9495
|
+
name: "X-Moltnet-Session-Token",
|
|
9496
|
+
type: "apiKey"
|
|
9497
|
+
},
|
|
9498
|
+
{
|
|
9499
|
+
in: "cookie",
|
|
9500
|
+
name: "ory_kratos_session",
|
|
9501
|
+
type: "apiKey"
|
|
9502
|
+
}
|
|
9503
|
+
],
|
|
9504
|
+
url: "/teams/{id}/members/{subjectId}",
|
|
9505
|
+
...options,
|
|
9506
|
+
headers: {
|
|
9507
|
+
"Content-Type": "application/json",
|
|
9508
|
+
...options.headers
|
|
9509
|
+
}
|
|
9510
|
+
});
|
|
9511
|
+
/**
|
|
9485
9512
|
* List invite codes. Requires manage_members permission.
|
|
9486
9513
|
*/
|
|
9487
9514
|
var listTeamInvites = (options) => (options.client ?? client).get({
|
|
@@ -11770,6 +11797,17 @@ function createTeamsNamespace(context) {
|
|
|
11770
11797
|
}
|
|
11771
11798
|
}));
|
|
11772
11799
|
},
|
|
11800
|
+
async updateMemberRole(teamId, subjectId, role) {
|
|
11801
|
+
return unwrapResult(await updateTeamMemberRole({
|
|
11802
|
+
client,
|
|
11803
|
+
auth,
|
|
11804
|
+
path: {
|
|
11805
|
+
id: teamId,
|
|
11806
|
+
subjectId
|
|
11807
|
+
},
|
|
11808
|
+
body: { role }
|
|
11809
|
+
}));
|
|
11810
|
+
},
|
|
11773
11811
|
invites: {
|
|
11774
11812
|
async create(teamId, body) {
|
|
11775
11813
|
return unwrapResult(await createTeamInvite({
|
|
@@ -16293,6 +16331,8 @@ function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
|
|
|
16293
16331
|
});
|
|
16294
16332
|
mkdirSync(scratchDir, { recursive: true });
|
|
16295
16333
|
}
|
|
16334
|
+
const workspaceSeed = executionPlan?.workspaceSeed ?? null;
|
|
16335
|
+
if (workspaceSeed) copyDirectoryContents(workspaceSeed.copyFromPath, scratchDir);
|
|
16296
16336
|
return {
|
|
16297
16337
|
mountPath: scratchDir,
|
|
16298
16338
|
cwdPath: scratchDir,
|
|
@@ -16414,6 +16454,68 @@ function gitRefExists(mainRepo, ref) {
|
|
|
16414
16454
|
return false;
|
|
16415
16455
|
}
|
|
16416
16456
|
}
|
|
16457
|
+
function copyDirectoryContents(sourceDir, targetDir) {
|
|
16458
|
+
if (!existsSync(sourceDir)) throw new Error(`Workspace seed source is missing: ${sourceDir}`);
|
|
16459
|
+
if (existsSync(join(sourceDir, ".git"))) initializeScratchGitRepo(sourceDir, targetDir);
|
|
16460
|
+
const resolvedTargetDir = realpathSync(targetDir);
|
|
16461
|
+
for (const entry of readdirSync(sourceDir)) {
|
|
16462
|
+
const sourceEntry = join(sourceDir, entry);
|
|
16463
|
+
if (shouldSkipSeedEntry(sourceEntry, entry, resolvedTargetDir)) continue;
|
|
16464
|
+
cpSync(sourceEntry, join(targetDir, entry), { recursive: true });
|
|
16465
|
+
}
|
|
16466
|
+
}
|
|
16467
|
+
function initializeScratchGitRepo(sourceDir, targetDir) {
|
|
16468
|
+
execFileSync("git", [
|
|
16469
|
+
"-C",
|
|
16470
|
+
targetDir,
|
|
16471
|
+
"init"
|
|
16472
|
+
], { stdio: "pipe" });
|
|
16473
|
+
let headCommit = null;
|
|
16474
|
+
try {
|
|
16475
|
+
headCommit = execFileSync("git", [
|
|
16476
|
+
"-C",
|
|
16477
|
+
sourceDir,
|
|
16478
|
+
"rev-parse",
|
|
16479
|
+
"HEAD"
|
|
16480
|
+
], {
|
|
16481
|
+
encoding: "utf8",
|
|
16482
|
+
stdio: "pipe"
|
|
16483
|
+
}).trim();
|
|
16484
|
+
} catch {
|
|
16485
|
+
headCommit = null;
|
|
16486
|
+
}
|
|
16487
|
+
if (!headCommit) return;
|
|
16488
|
+
execFileSync("git", [
|
|
16489
|
+
"-C",
|
|
16490
|
+
targetDir,
|
|
16491
|
+
"remote",
|
|
16492
|
+
"add",
|
|
16493
|
+
"origin",
|
|
16494
|
+
sourceDir
|
|
16495
|
+
], { stdio: "pipe" });
|
|
16496
|
+
execFileSync("git", [
|
|
16497
|
+
"-C",
|
|
16498
|
+
targetDir,
|
|
16499
|
+
"fetch",
|
|
16500
|
+
"--quiet",
|
|
16501
|
+
"--depth=1",
|
|
16502
|
+
"origin",
|
|
16503
|
+
headCommit
|
|
16504
|
+
], { stdio: "pipe" });
|
|
16505
|
+
execFileSync("git", [
|
|
16506
|
+
"-C",
|
|
16507
|
+
targetDir,
|
|
16508
|
+
"checkout",
|
|
16509
|
+
"--quiet",
|
|
16510
|
+
"--detach",
|
|
16511
|
+
"FETCH_HEAD"
|
|
16512
|
+
], { stdio: "pipe" });
|
|
16513
|
+
}
|
|
16514
|
+
function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
|
|
16515
|
+
if (entryName === ".git") return true;
|
|
16516
|
+
const resolvedSourceEntry = realpathSync(sourceEntry);
|
|
16517
|
+
return resolvedTargetDir === resolvedSourceEntry || resolvedTargetDir.startsWith(`${resolvedSourceEntry}${sep}`);
|
|
16518
|
+
}
|
|
16417
16519
|
//#endregion
|
|
16418
16520
|
//#region ../../libs/pi-extension/src/runtime/execute-pi-task.ts
|
|
16419
16521
|
/**
|
|
@@ -16619,7 +16721,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16619
16721
|
workspace: {
|
|
16620
16722
|
mode: activeWorkspace.mode,
|
|
16621
16723
|
branch: activeWorkspace.branch,
|
|
16622
|
-
attached: executionPlan?.workspaceAttachment !== void 0
|
|
16724
|
+
attached: executionPlan?.workspaceAttachment !== void 0 || executionPlan?.workspaceSeed?.source === "producer",
|
|
16725
|
+
source: executionPlan?.workspaceSeed?.source === "producer" ? "producer_copy" : executionPlan?.workspaceAttachment !== void 0 ? "producer_attachment" : void 0
|
|
16623
16726
|
},
|
|
16624
16727
|
extras: opts.promptExtras
|
|
16625
16728
|
});
|
|
@@ -17690,19 +17793,18 @@ function maybeAttachProducerContext(claimedTask, basePlan, stateDirs, slotRegist
|
|
|
17690
17793
|
const targetTaskId = typeof claimedTask.task.input.targetTaskId === "string" ? claimedTask.task.input.targetTaskId : null;
|
|
17691
17794
|
const targetAttemptN = typeof claimedTask.task.input.targetAttemptN === "number" ? claimedTask.task.input.targetAttemptN : null;
|
|
17692
17795
|
if (!targetTaskId || !targetAttemptN) throw new ProducerContextResolutionError("judge_eval_attempt is missing targetTaskId/targetAttemptN");
|
|
17693
|
-
const
|
|
17694
|
-
if (!
|
|
17695
|
-
const sourceSessionPath = resolveProducerSessionPath(
|
|
17796
|
+
const producerContext = slotRegistry.findLatestProducerSlotByTaskAttempt(targetTaskId, targetAttemptN);
|
|
17797
|
+
if (!producerContext) throw new ProducerContextResolutionError(`No live producer daemon slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
|
|
17798
|
+
const sourceSessionPath = resolveProducerSessionPath(producerContext);
|
|
17696
17799
|
if (!sourceSessionPath) throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
|
|
17697
|
-
const
|
|
17800
|
+
const copiedWorkspaceSource = resolveProducerWorkspaceCopySource(producerContext, stateDirs);
|
|
17698
17801
|
return {
|
|
17699
17802
|
...basePlan,
|
|
17700
|
-
workspaceMode:
|
|
17701
|
-
worktreeBranch:
|
|
17702
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
shadowWrites: "tmpfs"
|
|
17803
|
+
workspaceMode: "scratch_mount",
|
|
17804
|
+
worktreeBranch: null,
|
|
17805
|
+
workspaceSeed: {
|
|
17806
|
+
copyFromPath: copiedWorkspaceSource,
|
|
17807
|
+
source: "producer"
|
|
17706
17808
|
},
|
|
17707
17809
|
sessionPersistence: {
|
|
17708
17810
|
sessionDir: `${stateDirs.piSessionsDir}/judge-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`,
|
|
@@ -17718,32 +17820,17 @@ function resolveProducerSessionPath(producer) {
|
|
|
17718
17820
|
const latest = resolveLatestPiSessionPath(sessionDir);
|
|
17719
17821
|
return latest && existsSync(latest) ? latest : null;
|
|
17720
17822
|
}
|
|
17721
|
-
function
|
|
17823
|
+
function resolveProducerWorkspaceCopySource(producer, stateDirs) {
|
|
17722
17824
|
const workspacePath = producer.workspace?.worktreePath ?? null;
|
|
17723
17825
|
if (workspacePath) {
|
|
17724
|
-
if (existsSync(workspacePath)) return
|
|
17725
|
-
mountPath: workspacePath,
|
|
17726
|
-
cwdPath: workspacePath,
|
|
17727
|
-
mode: producer.workspace?.worktreeBranch ? "dedicated_worktree" : "scratch_mount",
|
|
17728
|
-
branch: producer.workspace?.worktreeBranch ?? null
|
|
17729
|
-
};
|
|
17826
|
+
if (existsSync(workspacePath)) return workspacePath;
|
|
17730
17827
|
const recoveredPath = recoverScratchWorkspacePath(producer, stateDirs);
|
|
17731
|
-
if (recoveredPath) return
|
|
17732
|
-
mountPath: recoveredPath,
|
|
17733
|
-
cwdPath: recoveredPath,
|
|
17734
|
-
mode: "scratch_mount",
|
|
17735
|
-
branch: null
|
|
17736
|
-
};
|
|
17828
|
+
if (recoveredPath) return recoveredPath;
|
|
17737
17829
|
throw new ProducerContextResolutionError(`Producer workspace path is missing on disk: ${workspacePath}`);
|
|
17738
17830
|
}
|
|
17739
17831
|
const sharedMountRoot = dirname(dirname(stateDirs.rootDir));
|
|
17740
17832
|
if (!existsSync(sharedMountRoot)) throw new ProducerContextResolutionError(`Shared producer mount root is missing on disk: ${sharedMountRoot}`);
|
|
17741
|
-
return
|
|
17742
|
-
mountPath: sharedMountRoot,
|
|
17743
|
-
cwdPath: sharedMountRoot,
|
|
17744
|
-
mode: "shared_mount",
|
|
17745
|
-
branch: null
|
|
17746
|
-
};
|
|
17833
|
+
return sharedMountRoot;
|
|
17747
17834
|
}
|
|
17748
17835
|
function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
17749
17836
|
if (producer.workspace?.worktreeBranch) return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
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.",
|
|
@@ -33,19 +33,19 @@
|
|
|
33
33
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
34
34
|
"pino": "^10.3.1",
|
|
35
35
|
"pino-pretty": "^13.1.3",
|
|
36
|
-
"@themoltnet/agent-runtime": "0.18.
|
|
37
|
-
"@themoltnet/pi-extension": "0.19.
|
|
38
|
-
"@themoltnet/sdk": "0.
|
|
36
|
+
"@themoltnet/agent-runtime": "0.18.2",
|
|
37
|
+
"@themoltnet/pi-extension": "0.19.3",
|
|
38
|
+
"@themoltnet/sdk": "0.104.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"tsx": "^4.7.0",
|
|
42
42
|
"typescript": "^5.3.3",
|
|
43
43
|
"vite": "^8.0.0",
|
|
44
44
|
"vitest": "^3.0.0",
|
|
45
|
-
"@moltnet/database": "0.1.0",
|
|
46
|
-
"@moltnet/tasks": "0.1.0",
|
|
47
45
|
"@moltnet/bootstrap": "0.1.0",
|
|
48
|
-
"@moltnet/crypto-service": "0.1.0"
|
|
46
|
+
"@moltnet/crypto-service": "0.1.0",
|
|
47
|
+
"@moltnet/database": "0.1.0",
|
|
48
|
+
"@moltnet/tasks": "0.1.0"
|
|
49
49
|
},
|
|
50
50
|
"nx": {
|
|
51
51
|
"tags": [
|