@themoltnet/agent-daemon 0.35.0 → 0.36.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.
Files changed (2) hide show
  1. package/dist/cli.js +73 -28
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -253,7 +253,7 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
253
253
  fragments: {
254
254
  "artifact-planner-v1": {
255
255
  binding: "prompt_prefix",
256
- content: "# Bounded artifact planner\n\n- The typed task facts, embedded bounded manifest, exact bound artifact references, registered tools, and runtime capability section are the complete contract. Do not search diaries, inspect a mounted repository, enumerate unrelated tasks or artifacts, use shell commands, modify files, commit, branch, push, or contact GitHub.\n- Read only the exact artifact CIDs named by the task, and only when the embedded manifest does not provide enough evidence. Never paginate or discover artifacts speculatively.\n- Perform semantic classification and planning from supplied content and producer/consumer evidence. Do not substitute filename, directory, language, ecosystem, or repository-specific exclusion rules for evidence.\n- Return exactly the requested versioned structured plan through the registered submit-output tool. Do not emit a second prose or JSON representation.",
256
+ content: "# Bounded artifact planner\n\n- The typed task facts, embedded bounded manifest, exact bound artifact references, registered tools, and runtime capability section are the complete contract. Do not search diaries, inspect a mounted repository, enumerate unrelated tasks or artifacts, modify a checkout, commit, branch, push, or contact GitHub.\n- Read only the exact artifact CIDs named by the task, and only when the embedded manifest does not provide enough evidence. Use the registered task-artifact tools for artifact access; never use shell or CLI wrappers to fetch artifacts, paginate, or discover them speculatively.\n- If the effective runtime exposes a local calculator or shell, use it only inside scratch for coverage accounting, budget arithmetic, and JSON validation. The runtime capability section and policy are authoritative; do not assume a static executable list.\n- Perform semantic classification and planning from supplied content and producer/consumer evidence. Do not substitute filename, directory, language, ecosystem, or repository-specific exclusion rules for evidence.\n- Write and upload exactly the requested versioned plan artifact, then reference its returned metadata through the registered submit-output tool. Do not emit a second prose or JSON representation.",
257
257
  slug: "artifact-planner-v1"
258
258
  },
259
259
  "accountable-delivery-v1": {
@@ -283,7 +283,7 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
283
283
  },
284
284
  "verification-and-artifacts-v1": {
285
285
  binding: "prompt_prefix",
286
- content: "# Verification and artifacts\n\n- Run relevant verification before submitting. When task facts include `successCriteria`, assess them honestly in the generated verification contract; a fail or skip with evidence is better than a fabricated pass.\n- The registered submit-output tool owns the exact agent submission schema and validation recovery. Use that schema; do not invent a JSON shape in prose.\n- Upload large files, binary files, logs, reports, screenshots, traces, bundles, or datasets before submitting. Include artifact metadata only where the typed submit contract permits it.\n- If the task depends on prior artifacts, list and download the exact referenced artifact before judging or continuing that work.",
286
+ content: "# Verification and artifacts\n\n- Run relevant verification before submitting. When task facts include `successCriteria`, assess them honestly in the generated verification contract; a fail or skip with evidence is better than a fabricated pass.\n- The registered submit-output tool owns the exact agent submission schema and validation recovery. Use that schema; do not invent a JSON shape in prose.\n- Upload only task-relevant artifacts, and inspect each before uploading. Never upload secrets, credentials, API keys, auth tokens or headers, .env files, or personal or customer data; redact sensitive values, and prefer minimal, sanitized excerpts over whole logs, bundles, or datasets. Include artifact metadata only where the typed submit contract permits it.\n- If the task depends on prior artifacts, list and download the exact referenced artifact before judging or continuing that work.",
287
287
  slug: "verification-and-artifacts-v1"
288
288
  }
289
289
  },
@@ -2538,11 +2538,14 @@ var CuratePackOutput = Type.Object({
2538
2538
  //#endregion
2539
2539
  //#region ../../libs/tasks/src/task-types/freeform.ts
2540
2540
  var FREEFORM_TYPE = "freeform";
2541
- var FreeformExecutionOptions = Type.Object({ workspace: Type.Optional(Type.Union([
2542
- Type.Literal("none"),
2543
- Type.Literal("shared_mount"),
2544
- Type.Literal("dedicated_worktree")
2545
- ])) }, {
2541
+ var FreeformExecutionOptions = Type.Object({
2542
+ workspace: Type.Optional(Type.Union([
2543
+ Type.Literal("none"),
2544
+ Type.Literal("shared_mount"),
2545
+ Type.Literal("dedicated_worktree")
2546
+ ])),
2547
+ revision: Type.Optional(Type.String({ pattern: "^[0-9a-fA-F]{40}$" }))
2548
+ }, {
2546
2549
  $id: "FreeformExecutionOptions",
2547
2550
  additionalProperties: false
2548
2551
  });
@@ -2628,6 +2631,12 @@ var FreeformOutput = Type.Object({
2628
2631
  * are sequential preconditions, later ones presume earlier ones hold.
2629
2632
  */
2630
2633
  async function validateFreeformInputAsync(input, ctx) {
2634
+ const execution = input.execution;
2635
+ if (execution?.revision && execution.workspace === "none") return [{
2636
+ field: "input/execution/revision",
2637
+ message: "execution.revision requires a repository workspace; use shared_mount or dedicated_worktree",
2638
+ code: "freeform.executionRevisionRequiresRepository"
2639
+ }];
2631
2640
  const cf = input.continueFrom;
2632
2641
  if (!cf) return [];
2633
2642
  const source = await ctx.resolveTask(cf.taskId);
@@ -2641,11 +2650,16 @@ async function validateFreeformInputAsync(input, ctx) {
2641
2650
  message: `Source task type '${source.taskType}' is not continuable; only freeform → freeform is supported in v1`,
2642
2651
  code: "freeform.sourceTaskTypeNotSupported"
2643
2652
  }];
2644
- if (input.execution?.workspace) return [{
2653
+ if (execution?.workspace) return [{
2645
2654
  field: "input/execution/workspace",
2646
2655
  message: "execution.workspace is derived from parent runtime context when continueFrom is set; omit it",
2647
2656
  code: "freeform.executionWorkspaceNotInheritable"
2648
2657
  }];
2658
+ if (execution?.revision) return [{
2659
+ field: "input/execution/revision",
2660
+ message: "execution.revision is derived from parent runtime context when continueFrom is set; omit it",
2661
+ code: "freeform.executionRevisionNotInheritable"
2662
+ }];
2649
2663
  if (ctx.deferReadinessChecks) return [];
2650
2664
  const attempt = (await ctx.listAttempts(cf.taskId)).find((a) => a.attemptN === cf.attemptN);
2651
2665
  if (!attempt || attempt.status !== "completed") return [{
@@ -4246,6 +4260,7 @@ function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmSessionTtlS
4246
4260
  const slotId = slotKey ? buildDaemonSlotId(identity, slotKey) : null;
4247
4261
  const sessionDir = slotId ? `${stateDirs.piSessionsDir}/${boundedKeyDirComponent(slotId)}` : null;
4248
4262
  const worktreeBranch = resolveTaskWorktreeBranch(task, workspaceMode);
4263
+ const workspaceRevision = resolveTaskWorkspaceRevision(task.input);
4249
4264
  const workspaceId = workspaceMode !== "shared_mount" ? resolveTaskWorkspaceId(task, {
4250
4265
  sessionKey: slotId,
4251
4266
  workspaceScope,
@@ -4261,9 +4276,14 @@ function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmSessionTtlS
4261
4276
  workspaceScope,
4262
4277
  sessionPersistence: sessionDir ? { sessionDir } : null,
4263
4278
  workspaceId,
4264
- worktreeBranch
4279
+ worktreeBranch,
4280
+ workspaceRevision
4265
4281
  };
4266
4282
  }
4283
+ function resolveTaskWorkspaceRevision(input) {
4284
+ const value = input?.execution?.revision;
4285
+ return typeof value === "string" && /^[0-9a-fA-F]{40}$/.test(value) ? value.toLowerCase() : null;
4286
+ }
4267
4287
  function buildDaemonSlotId(identity, slotKey) {
4268
4288
  return [
4269
4289
  "agent",
@@ -4289,6 +4309,7 @@ function slugSlotIdentityComponent(input) {
4289
4309
  }
4290
4310
  function resolveTaskWorktreeBranch(task, workspaceMode) {
4291
4311
  if (workspaceMode !== "dedicated_worktree") return null;
4312
+ if (typeof task.input.execution?.revision === "string") return null;
4292
4313
  if (task.taskType === "fulfill_brief") {
4293
4314
  const input = task.input;
4294
4315
  const slug = slugifyAsciiLower(typeof task.title === "string" && task.title.trim().length > 0 ? task.title : typeof input.brief === "string" && input.brief.trim().length > 0 ? input.brief : task.taskType, 60) || "task";
@@ -4383,9 +4404,14 @@ function createExecutionPlanCache(args) {
4383
4404
  };
4384
4405
  }
4385
4406
  function createNullSourceAttemptResolver() {
4386
- return { findOutputBranch() {
4387
- return Promise.resolve(null);
4388
- } };
4407
+ return {
4408
+ findOutputBranch() {
4409
+ return Promise.resolve(null);
4410
+ },
4411
+ findInputRevision() {
4412
+ return Promise.resolve(null);
4413
+ }
4414
+ };
4389
4415
  }
4390
4416
  function createNullRuntimeSessionStore() {
4391
4417
  return {
@@ -4409,7 +4435,7 @@ function assertPlanAllowedByWorkspacePolicy(plan, policy) {
4409
4435
  }
4410
4436
  function planToRuntimeProfileWorkspaceMode(plan) {
4411
4437
  if (plan.workspaceMode === "scratch_mount") return "none";
4412
- if (plan.workspaceMode === "dedicated_worktree" && !plan.worktreeBranch) return "shared_mount";
4438
+ if (plan.workspaceMode === "dedicated_worktree" && !plan.worktreeBranch && !plan.workspaceRevision) return "shared_mount";
4413
4439
  return plan.workspaceMode;
4414
4440
  }
4415
4441
  function buildClaimedTaskKey(task) {
@@ -4455,16 +4481,20 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
4455
4481
  attemptN: continueFrom.attemptN,
4456
4482
  taskId: continueFrom.taskId
4457
4483
  });
4484
+ const recoveredRevision = recoveredBranch ? null : await sourceAttemptResolver.findInputRevision({
4485
+ attemptN: continueFrom.attemptN,
4486
+ taskId: continueFrom.taskId
4487
+ });
4458
4488
  if (continueFrom.mode === "fork") {
4459
- if (recoveredBranch) {
4489
+ if (recoveredBranch || recoveredRevision) {
4460
4490
  const forkWorkspaceId = `fork-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
4461
- const forkBranch = buildForkBranch(recoveredBranch, claimedTask.task.id, claimedTask.attemptN);
4491
+ const forkBranch = buildForkBranch(recoveredBranch ?? `detached-${continueFrom.taskId.slice(0, 8)}`, claimedTask.task.id, claimedTask.attemptN);
4462
4492
  return {
4463
4493
  ...basePlan,
4464
4494
  workspaceMode: "dedicated_worktree",
4465
4495
  workspaceId: forkWorkspaceId,
4466
4496
  worktreeBranch: forkBranch,
4467
- worktreeBaseRef: recoveredBranch,
4497
+ worktreeBaseRef: recoveredBranch ?? recoveredRevision,
4468
4498
  workspaceKind: "fork",
4469
4499
  sessionPersistence: {
4470
4500
  sessionDir,
@@ -4474,11 +4504,13 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
4474
4504
  }
4475
4505
  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`);
4476
4506
  }
4507
+ const hasDedicatedWorkspace = Boolean(recoveredBranch || recoveredRevision);
4477
4508
  return {
4478
4509
  ...basePlan,
4479
- workspaceMode: "dedicated_worktree",
4480
- workspaceId: recoveredBranch ? buildAttemptWorkspaceId(claimedTask) : null,
4510
+ workspaceMode: hasDedicatedWorkspace ? "dedicated_worktree" : "shared_mount",
4511
+ workspaceId: hasDedicatedWorkspace ? buildAttemptWorkspaceId(claimedTask) : null,
4481
4512
  worktreeBranch: recoveredBranch,
4513
+ workspaceRevision: recoveredRevision,
4482
4514
  sessionPersistence: {
4483
4515
  sessionDir,
4484
4516
  forkFromSessionPath: resolution.sessionPath
@@ -4486,16 +4518,20 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
4486
4518
  };
4487
4519
  }
4488
4520
  const parentBranch = resolution.producerSlot.workspace?.worktreeBranch ?? null;
4521
+ const parentRevision = parentBranch ? null : await sourceAttemptResolver.findInputRevision({
4522
+ attemptN: continueFrom.attemptN,
4523
+ taskId: continueFrom.taskId
4524
+ });
4489
4525
  if (continueFrom.mode === "fork") {
4490
- if (!parentBranch) throw new ProducerContextResolutionError(`Cannot fork continuation of ${continueFrom.taskId}/${continueFrom.attemptN}: producer slot has no worktree branch to fork from`);
4526
+ if (!parentBranch && !parentRevision) throw new ProducerContextResolutionError(`Cannot fork continuation of ${continueFrom.taskId}/${continueFrom.attemptN}: producer slot has no worktree branch or immutable revision to fork from`);
4491
4527
  const forkWorkspaceId = `fork-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
4492
- const forkBranch = buildForkBranch(parentBranch, claimedTask.task.id, claimedTask.attemptN);
4528
+ const forkBranch = buildForkBranch(parentBranch ?? `detached-${continueFrom.taskId.slice(0, 8)}`, claimedTask.task.id, claimedTask.attemptN);
4493
4529
  return {
4494
4530
  ...basePlan,
4495
4531
  workspaceMode: "dedicated_worktree",
4496
4532
  workspaceId: forkWorkspaceId,
4497
4533
  worktreeBranch: forkBranch,
4498
- worktreeBaseRef: parentBranch,
4534
+ worktreeBaseRef: parentBranch ?? parentRevision,
4499
4535
  workspaceKind: "fork",
4500
4536
  sessionPersistence: {
4501
4537
  sessionDir,
@@ -4503,11 +4539,13 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
4503
4539
  }
4504
4540
  };
4505
4541
  }
4542
+ const hasDedicatedWorkspace = Boolean(parentBranch || parentRevision);
4506
4543
  return {
4507
4544
  ...basePlan,
4508
- workspaceMode: "dedicated_worktree",
4509
- workspaceId: parentBranch ? buildAttemptWorkspaceId(claimedTask) : null,
4545
+ workspaceMode: hasDedicatedWorkspace ? "dedicated_worktree" : "shared_mount",
4546
+ workspaceId: hasDedicatedWorkspace ? buildAttemptWorkspaceId(claimedTask) : null,
4510
4547
  worktreeBranch: parentBranch,
4548
+ workspaceRevision: parentRevision,
4511
4549
  sessionPersistence: {
4512
4550
  sessionDir,
4513
4551
  forkFromSessionPath: resolution.sessionPath
@@ -5702,11 +5740,18 @@ function signalExitCode(signal) {
5702
5740
  //#region src/lib/source-attempts.ts
5703
5741
  function createApiSourceAttemptResolver(args) {
5704
5742
  const { agent } = args;
5705
- return { async findOutputBranch(input) {
5706
- const attempt = (await agent.tasks.listAttempts(input.taskId)).find((candidate) => candidate.attemptN === input.attemptN);
5707
- if (!attempt || attempt.status !== "completed") return null;
5708
- return resolveOutputBranch(attempt.output);
5709
- } };
5743
+ return {
5744
+ async findOutputBranch(input) {
5745
+ const attempt = (await agent.tasks.listAttempts(input.taskId)).find((candidate) => candidate.attemptN === input.attemptN);
5746
+ if (!attempt || attempt.status !== "completed") return null;
5747
+ return resolveOutputBranch(attempt.output);
5748
+ },
5749
+ async findInputRevision(input) {
5750
+ const task = await agent.tasks.get(input.taskId);
5751
+ if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
5752
+ return resolveTaskWorkspaceRevision(task.input);
5753
+ }
5754
+ };
5710
5755
  }
5711
5756
  function resolveOutputBranch(output) {
5712
5757
  if (!output || typeof output !== "object") return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.",
@@ -62,8 +62,8 @@
62
62
  "pino": "^10.3.1",
63
63
  "pino-pretty": "^13.1.3",
64
64
  "typebox": "^1.2.8",
65
- "@themoltnet/agent-runtime": "0.39.1",
66
- "@themoltnet/pi-runtime": "0.5.0",
65
+ "@themoltnet/pi-runtime": "0.6.0",
66
+ "@themoltnet/agent-runtime": "0.40.0",
67
67
  "@themoltnet/sdk": "0.128.0"
68
68
  },
69
69
  "devDependencies": {
@@ -74,8 +74,8 @@
74
74
  "vitest": "^3.0.0",
75
75
  "@moltnet/bootstrap": "0.1.0",
76
76
  "@moltnet/crypto-service": "0.1.0",
77
- "@moltnet/observability": "0.1.0",
78
- "@moltnet/tasks": "0.1.0"
77
+ "@moltnet/tasks": "0.1.0",
78
+ "@moltnet/observability": "0.1.0"
79
79
  },
80
80
  "nx": {
81
81
  "projectType": "application",