@tea-agent/loop-agent 0.26.5-beta.2 → 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.
Files changed (71) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +13 -23
  4. package/dist/application/dag/args.js +7 -7
  5. package/dist/application/dag/generate-task-dag.js +2 -2
  6. package/dist/application/dag/run-dag.js +3 -4
  7. package/dist/application/dag/validate-dag.js +1 -1
  8. package/dist/application/task-lifecycle/advance.js +845 -0
  9. package/dist/application/task-lifecycle/gates.js +80 -0
  10. package/dist/application/task-lifecycle/index.js +7 -0
  11. package/dist/application/task-lifecycle/observe.js +395 -0
  12. package/dist/application/task-lifecycle/plan-transitions.js +224 -0
  13. package/dist/application/task-lifecycle/recommendations.js +180 -0
  14. package/dist/application/task-lifecycle/record.js +73 -0
  15. package/dist/application/task-lifecycle/types.js +1 -0
  16. package/dist/cli/command-definitions.js +14 -111
  17. package/dist/cli/help.js +6 -7
  18. package/dist/cli/program.js +26 -90
  19. package/dist/cli/update/policy.js +0 -1
  20. package/dist/commands/dag-final-verification.js +1 -1
  21. package/dist/commands/dag-init-hybrid.js +1 -1
  22. package/dist/commands/delegate.js +2 -2
  23. package/dist/commands/init.js +21 -19
  24. package/dist/commands/run-dag-progress.js +1 -1
  25. package/dist/commands/status.js +18 -17
  26. package/dist/commands/study-init.js +2 -2
  27. package/dist/commands/task-advance.js +335 -0
  28. package/dist/commands/task-contract.js +3 -5
  29. package/dist/commands/task-source-prepare.js +9 -4
  30. package/dist/commands/task-status.js +133 -0
  31. package/dist/governance/manifest-types.js +2 -2
  32. package/dist/shared/operator/capabilities.js +1358 -243
  33. package/dist/task/contract/adopt.js +1 -1
  34. package/dist/task/contract/apply.js +1 -1
  35. package/dist/task/contract/import-revision.js +1 -1
  36. package/dist/task/contract/recover.js +2 -2
  37. package/dist/task/read-model.js +18 -23
  38. package/dist/task/runtime.js +1 -1
  39. package/dist/task/source-prepare/completeness.js +1 -1
  40. package/dist/task/source-prepare/parse-intent.js +6 -1
  41. package/dist/task/source-prepare/prepare.js +23 -20
  42. package/dist/worker/console/operator-actions.js +288 -95
  43. package/dist/worker/console/recovery-cta.js +4 -4
  44. package/dist/worker/console/static/assets/{index-CSRIhuzh.js → index-CNO7n6qB.js} +1 -1
  45. package/dist/worker/console/static/index.html +1 -1
  46. package/dist/worker/materialize/harness-task-materializer.js +6 -5
  47. package/dist/worker/run-task/run-task.js +204 -112
  48. package/dist/worker/runner/run-ready.js +1 -1
  49. package/dist/workflows/dag/frontend-implementation-contract.js +6 -72
  50. package/dist/workflows/dag/frontend-prewrite-gate.js +4 -17
  51. package/dist/workflows/dag/init-hybrid.js +11 -66
  52. package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
  53. package/docs/templates/evaluation/agents-map-verbose-v0.md +3 -3
  54. package/docs/templates/harness.schema.json +2 -2
  55. package/docs/templates/init-managed-agents.md +13 -16
  56. package/docs/templates/production-readiness-checklist.md +3 -3
  57. package/harness.json +4 -4
  58. package/package.json +1 -1
  59. package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
  60. package/scripts/kb-graph-incremental-prepare.mjs +2 -2
  61. package/skills/loop-agent/SKILL.md +18 -13
  62. package/skills/loop-agent/references/README.md +1 -1
  63. package/skills/loop-agent/references/command-reference.md +55 -84
  64. package/skills/loop-agent/references/harness-policy.md +19 -23
  65. package/skills/loop-agent/references/hybrid-dag.md +31 -33
  66. package/skills/loop-agent/references/long-running-loop.md +2 -2
  67. package/skills/loop-agent/references/one-shot-runs.md +4 -5
  68. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  69. package/skills/loop-agent/references/post-implementation-and-patterns.md +4 -4
  70. package/skills/loop-agent/references/source-and-plan-practice.md +45 -51
  71. package/skills/loop-agent/references/task-workflow.md +14 -15
@@ -2,6 +2,7 @@ import { access, readFile } from "node:fs/promises";
2
2
  import { buildOperatorCapabilitiesDocument } from "../../shared/operator/capabilities.js";
3
3
  import { buildOperatorResult, operatorFailed, operatorSucceeded, } from "../../shared/operator/envelope.js";
4
4
  import { getTaskPaths } from "../../task/runtime.js";
5
+ import { diffTaskContract, validateDraftForTask, } from "../../task/contract/index.js";
5
6
  import { stageUtf8Text } from "./app-data.js";
6
7
  import { ALL_CONFIRMATION_CHALLENGES, hashDagBytes, } from "./dag-confirmation.js";
7
8
  import { capabilityForObserveTarget, DEFAULT_OBSERVE_BASE_URL, OBSERVE_START_COMMAND, probeAndClassifyObserve, } from "./observe-health-match.js";
@@ -41,6 +42,31 @@ function paramsOf(req) {
41
42
  function str(v) {
42
43
  return typeof v === "string" && v.trim() ? v.trim() : undefined;
43
44
  }
45
+ /** Extract write-set gate token from task advance operator JSON. */
46
+ function extractWriteSetGateTokenFromCliJson(json, taskId) {
47
+ if (!json || typeof json !== "object")
48
+ return undefined;
49
+ const envelope = json;
50
+ const payload = envelope.result && typeof envelope.result === "object"
51
+ ? envelope.result
52
+ : envelope;
53
+ const gate = payload.gate && typeof payload.gate === "object"
54
+ ? payload.gate
55
+ : undefined;
56
+ const id = typeof gate?.id === "string" ? gate.id : undefined;
57
+ const digest = typeof gate?.digest === "string" ? gate.digest : undefined;
58
+ if (!id || !digest)
59
+ return undefined;
60
+ // taskId kept for future binding checks / logging symmetry with worker.
61
+ void taskId;
62
+ return `${id}:${digest}`;
63
+ }
64
+ function readWriteSetGateToken(reviewPacket) {
65
+ if (!reviewPacket)
66
+ return undefined;
67
+ const token = reviewPacket.writeSetGateToken;
68
+ return typeof token === "string" && token.includes(":") ? token : undefined;
69
+ }
44
70
  async function runReadCli(ctx, args, command) {
45
71
  const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
46
72
  try {
@@ -55,7 +81,9 @@ async function runReadCli(ctx, args, command) {
55
81
  if (env.schemaVersion === 1 && typeof env.ok === "boolean") {
56
82
  return env;
57
83
  }
58
- if (command === "dag rerun plan" && raw.plan && typeof raw.plan === "object") {
84
+ if (command === "dag rerun plan" &&
85
+ raw.plan &&
86
+ typeof raw.plan === "object") {
59
87
  const plan = raw.plan;
60
88
  if (raw.ok === true) {
61
89
  return operatorSucceeded(command, plan);
@@ -114,7 +142,9 @@ async function acceptOperation(ctx, input) {
114
142
  actionParams: input.actionParams,
115
143
  taskId: input.taskId,
116
144
  cliArgs: input.cliArgs,
117
- ...(input.externalCommand ? { externalCommand: input.externalCommand } : {}),
145
+ ...(input.externalCommand
146
+ ? { externalCommand: input.externalCommand }
147
+ : {}),
118
148
  });
119
149
  if (created.kind === "conflict") {
120
150
  return {
@@ -297,7 +327,7 @@ export async function dispatchOperatorAction(ctx, req) {
297
327
  if (!taskId) {
298
328
  return invalid(action, "taskId is required");
299
329
  }
300
- const body = await runReadCli(ctx, ["status", taskId, "--json"], "status");
330
+ const body = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
301
331
  return {
302
332
  kind: "sync",
303
333
  status: body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
@@ -322,11 +352,38 @@ export async function dispatchOperatorAction(ctx, req) {
322
352
  const taskId = str(p.taskId);
323
353
  if (!taskId)
324
354
  return invalid(action, "taskId is required");
325
- const body = await runReadCli(ctx, ["task", "contract", "show", taskId, "--json"], "task contract show");
355
+ const body = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
356
+ const snap = (body.result ?? {});
357
+ const c = snap.contract;
358
+ const reshaped = {
359
+ ...body,
360
+ command: "contractShow",
361
+ result: {
362
+ taskId,
363
+ state: {
364
+ effectiveStatus: c?.effectiveStatus ?? "legacy-unversioned",
365
+ observedCanonicalHash: c?.observedCanonicalHash ?? undefined,
366
+ ref: c?.canonicalHash && typeof c.revision === "number"
367
+ ? {
368
+ schemaVersion: 1,
369
+ taskId,
370
+ revision: c.revision,
371
+ canonicalHash: c.canonicalHash,
372
+ taskConfigSha256: c.taskConfigSha256 ?? undefined,
373
+ projectionVersion: c.projectionVersion ?? undefined,
374
+ canonicalizerVersion: c.canonicalizerVersion ?? undefined,
375
+ taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
376
+ }
377
+ : undefined,
378
+ },
379
+ snapshot: snap,
380
+ },
381
+ };
382
+ const notFound = body.ok === true && snap.exists === false;
326
383
  return {
327
384
  kind: "sync",
328
- status: body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
329
- body,
385
+ status: notFound ? 404 : body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
386
+ body: reshaped,
330
387
  };
331
388
  }
332
389
  case "contractValidate":
@@ -565,11 +622,15 @@ export async function dispatchOperatorAction(ctx, req) {
565
622
  const taskId = str(p.taskId);
566
623
  const operatorAction = str(p.operatorAction);
567
624
  const operatorActionArgs = p.operatorActionArgs;
568
- if (operatorActionArgs !== undefined && (!Array.isArray(operatorActionArgs) || operatorActionArgs.some((value) => typeof value !== "string"))) {
625
+ if (operatorActionArgs !== undefined &&
626
+ (!Array.isArray(operatorActionArgs) ||
627
+ operatorActionArgs.some((value) => typeof value !== "string"))) {
569
628
  return invalid(action, "operatorActionArgs must be an array of strings");
570
629
  }
571
630
  if (operatorAction) {
572
- const target = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === operatorAction && entry.description.startsWith("Official CLI action coverage") && entry.humanConfirmation === "required");
631
+ const target = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === operatorAction &&
632
+ entry.description.startsWith("Official CLI action coverage") &&
633
+ entry.humanConfirmation === "required");
573
634
  if (!target)
574
635
  return invalid(action, "operatorAction must name a required official action");
575
636
  }
@@ -577,22 +638,25 @@ export async function dispatchOperatorAction(ctx, req) {
577
638
  return invalid(action, "taskId is required");
578
639
  let dagText = str(p.dagText) ?? str(p.dagJson);
579
640
  let staged;
641
+ // When generated via lifecycle, store writeSet gate token for runDag approve path.
642
+ let writeSetGateToken;
580
643
  if (!dagText) {
581
- // Happy Path: generate a real DAG via CLI; never confirm an empty stub.
644
+ // Happy Path: generate + strict-validate via task advance; never confirm empty stub.
582
645
  staged = await stageUtf8Text(ctx.appData, "", { extension: ".json" });
583
646
  const profile = str(p.profile) ?? "auto";
584
647
  const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
585
648
  const gen = await run([
586
- "dag",
587
- "run-task",
649
+ "task",
650
+ "advance",
588
651
  taskId,
589
652
  "--profile",
590
653
  profile,
591
- "-o",
654
+ "--dag-output",
592
655
  staged.path,
656
+ "--json",
593
657
  ], {
594
658
  cwd: ctx.repoRoot,
595
- artifactName: "prepare-dag-run-task",
659
+ artifactName: "prepare-dag-task-advance",
596
660
  expectJson: true,
597
661
  timeoutMs: 180_000,
598
662
  });
@@ -605,7 +669,7 @@ export async function dispatchOperatorAction(ctx, req) {
605
669
  outcome: "invalid",
606
670
  code: "INVALID_INPUT",
607
671
  message: gen.stderr?.trim() ||
608
- `dag run-task failed with exit ${gen.exitCode}`,
672
+ `task advance (prepare DAG) failed with exit ${gen.exitCode}`,
609
673
  details: {
610
674
  exitCode: gen.exitCode,
611
675
  stdoutPreview: (gen.stdout ?? "").slice(0, 1200),
@@ -622,10 +686,11 @@ export async function dispatchOperatorAction(ctx, req) {
622
686
  command: action,
623
687
  outcome: "invalid",
624
688
  code: "INVALID_INPUT",
625
- message: "dag run-task produced empty DAG output",
689
+ message: "task advance produced empty DAG output",
626
690
  }),
627
691
  };
628
692
  }
693
+ writeSetGateToken = extractWriteSetGateTokenFromCliJson(gen.json, taskId);
629
694
  staged = await stageUtf8Text(ctx.appData, dagText, {
630
695
  extension: ".json",
631
696
  });
@@ -667,9 +732,25 @@ export async function dispatchOperatorAction(ctx, req) {
667
732
  result: validateBody.result ?? null,
668
733
  }));
669
734
  // Prefer live managed contract observation over client-supplied binding fields.
670
- const showBody = await runReadCli(ctx, ["task", "contract", "show", taskId, "--json"], "task contract show");
671
- const showResult = (showBody.result ?? {});
672
- const ref = showBody.ok ? showResult.state?.ref : undefined;
735
+ const showBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
736
+ const statusResult = (showBody.result ?? {});
737
+ const c = showBody.ok ? statusResult.contract : undefined;
738
+ const ref = c?.canonicalHash && typeof c.revision === "number"
739
+ ? {
740
+ revision: c.revision,
741
+ canonicalHash: c.canonicalHash,
742
+ taskConfigSha256: c.taskConfigSha256 ?? undefined,
743
+ projectionVersion: c.projectionVersion ?? undefined,
744
+ canonicalizerVersion: c.canonicalizerVersion ?? undefined,
745
+ taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
746
+ }
747
+ : undefined;
748
+ const showResult = {
749
+ state: {
750
+ effectiveStatus: c?.effectiveStatus,
751
+ ref,
752
+ },
753
+ };
673
754
  if (!ref?.canonicalHash || typeof ref.revision !== "number") {
674
755
  return {
675
756
  kind: "error",
@@ -710,7 +791,13 @@ export async function dispatchOperatorAction(ctx, req) {
710
791
  taskId,
711
792
  dagHandle,
712
793
  validationOk: true,
713
- ...(operatorAction ? { operatorAction, operatorActionArgs: operatorActionArgs ?? [] } : {}),
794
+ ...(writeSetGateToken ? { writeSetGateToken } : {}),
795
+ ...(operatorAction
796
+ ? {
797
+ operatorAction,
798
+ operatorActionArgs: operatorActionArgs ?? [],
799
+ }
800
+ : {}),
714
801
  },
715
802
  });
716
803
  return {
@@ -803,7 +890,7 @@ export async function dispatchOperatorAction(ctx, req) {
803
890
  actionParams: p,
804
891
  clientRequestId,
805
892
  taskId,
806
- cliArgs: ["new-task", taskId, title, "--json"],
893
+ cliArgs: ["task", "advance", taskId, title, "--json"],
807
894
  });
808
895
  }
809
896
  case "bootstrapFromPrd": {
@@ -824,7 +911,8 @@ export async function dispatchOperatorAction(ctx, req) {
824
911
  actionParams: { taskId, stagedSha256: staged.sha256 },
825
912
  clientRequestId,
826
913
  taskId,
827
- cliArgs: ["import-prd", taskId, "--file", staged.path, "--json"],
914
+ // PRD archive via task advance; create if missing.
915
+ cliArgs: ["task", "advance", taskId, "--prd", staged.path, "--json"],
828
916
  });
829
917
  }
830
918
  case "contractApply": {
@@ -888,15 +976,10 @@ export async function dispatchOperatorAction(ctx, req) {
888
976
  };
889
977
  }
890
978
  const staged = await stageUtf8Text(ctx.appData, JSON.stringify(toCanonicalDraftForCli(draftRec.draft), null, 2), { extension: ".json" });
891
- // Optimistic concurrency: observe live revision/hash; never default to "unknown".
892
- const showBody = await runReadCli(ctx, ["task", "contract", "show", taskId, "--json"], "task contract show");
893
- const showState = (showBody.result ?? {}).state;
894
- const emptyObservedHash = "0".repeat(64);
895
- const expectedRevision = String(p.expectedRevision ?? showState?.ref?.revision ?? 0);
896
- const expectedHash = str(p.expectedObservedHash) ??
897
- showState?.observedCanonicalHash ??
898
- emptyObservedHash;
899
- // CLI validates --request-payload-sha256 against input file bytes, not draft projection hash.
979
+ // Lifecycle apply via task advance --from-draft (managed contract projection).
980
+ // Optimistic concurrency remains inside applyTaskContract via prepareTaskSource.
981
+ const statusBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
982
+ const statusContract = (statusBody.result ?? {}).contract;
900
983
  const payloadSha = staged.sha256;
901
984
  return acceptOperation(ctx, {
902
985
  action,
@@ -905,28 +988,20 @@ export async function dispatchOperatorAction(ctx, req) {
905
988
  assessmentId,
906
989
  draftSha256: draftRec.draftSha256,
907
990
  requestPayloadSha256: payloadSha,
908
- expectedRevision: Number(expectedRevision),
909
- expectedObservedHash: expectedHash,
910
- effectiveStatus: showState?.effectiveStatus,
991
+ expectedRevision: Number(p.expectedRevision ?? statusContract?.revision ?? 0),
992
+ expectedObservedHash: str(p.expectedObservedHash) ??
993
+ statusContract?.observedCanonicalHash ??
994
+ null,
995
+ effectiveStatus: statusContract?.effectiveStatus,
911
996
  },
912
997
  clientRequestId,
913
998
  taskId,
914
999
  cliArgs: [
915
1000
  "task",
916
- "contract",
917
- "apply",
918
- "--task",
1001
+ "advance",
919
1002
  taskId,
920
- "--input",
1003
+ "--from-draft",
921
1004
  staged.path,
922
- "--expected-revision",
923
- expectedRevision,
924
- "--expected-observed-hash",
925
- expectedHash,
926
- "--request-id",
927
- clientRequestId,
928
- "--request-payload-sha256",
929
- payloadSha,
930
1005
  "--json",
931
1006
  ],
932
1007
  });
@@ -943,14 +1018,16 @@ export async function dispatchOperatorAction(ctx, req) {
943
1018
  actionParams: p,
944
1019
  clientRequestId,
945
1020
  taskId,
1021
+ // Lifecycle generate + strict validate; stops at writeSet gate.
946
1022
  cliArgs: [
947
- "dag",
948
- "run-task",
1023
+ "task",
1024
+ "advance",
949
1025
  taskId,
950
1026
  "--profile",
951
1027
  str(p.profile) ?? "auto",
952
- "-o",
1028
+ "--dag-output",
953
1029
  out.path,
1030
+ "--json",
954
1031
  ],
955
1032
  });
956
1033
  }
@@ -1017,13 +1094,34 @@ export async function dispatchOperatorAction(ctx, req) {
1017
1094
  }),
1018
1095
  };
1019
1096
  }
1097
+ const taskId = conf.taskContractBinding.taskId;
1098
+ const gateToken = readWriteSetGateToken(conf.reviewPacket);
1099
+ if (gateToken) {
1100
+ // Standard lifecycle path: human confirmation maps to writeSet gate approve.
1101
+ return acceptOperation(ctx, {
1102
+ action,
1103
+ actionParams: { confirmationId },
1104
+ clientRequestId,
1105
+ taskId,
1106
+ cliArgs: [
1107
+ "task",
1108
+ "advance",
1109
+ taskId,
1110
+ "--approve-gate",
1111
+ gateToken,
1112
+ "--json",
1113
+ ],
1114
+ });
1115
+ }
1116
+ // Advanced: arbitrary staged DagSpec (client-supplied dagText) until dag execute rename.
1020
1117
  return acceptOperation(ctx, {
1021
1118
  action,
1022
1119
  actionParams: { confirmationId },
1023
1120
  clientRequestId,
1024
- taskId: conf.taskContractBinding.taskId,
1121
+ taskId,
1025
1122
  cliArgs: [
1026
- "run-dag",
1123
+ "dag",
1124
+ "execute",
1027
1125
  "--dag",
1028
1126
  conf.dagBytesPath,
1029
1127
  "--cwd",
@@ -1156,7 +1254,9 @@ export async function dispatchOperatorAction(ctx, req) {
1156
1254
  }
1157
1255
  case "prepareMutationGate": {
1158
1256
  const targetAction = str(p.action);
1159
- const actionParams = p.actionParams && typeof p.actionParams === "object" && !Array.isArray(p.actionParams)
1257
+ const actionParams = p.actionParams &&
1258
+ typeof p.actionParams === "object" &&
1259
+ !Array.isArray(p.actionParams)
1160
1260
  ? p.actionParams
1161
1261
  : undefined;
1162
1262
  if (!targetAction || !actionParams) {
@@ -1213,10 +1313,13 @@ export async function dispatchOperatorAction(ctx, req) {
1213
1313
  // M5 official-command coverage uses one generic tail for newly registered
1214
1314
  // CLI actions. It remains inside dispatchOperatorAction (the sole dispatch
1215
1315
  // surface) and only accepts argv arrays declared by the capability schema.
1216
- const capability = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === action && entry.description.startsWith("Official CLI action coverage"));
1316
+ const capability = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === action &&
1317
+ entry.description.startsWith("Official CLI action coverage"));
1217
1318
  if (capability) {
1218
1319
  const rawArgs = p.args;
1219
- if (rawArgs !== undefined && (!Array.isArray(rawArgs) || rawArgs.some((value) => typeof value !== "string"))) {
1320
+ if (rawArgs !== undefined &&
1321
+ (!Array.isArray(rawArgs) ||
1322
+ rawArgs.some((value) => typeof value !== "string"))) {
1220
1323
  return invalid(action, "args must be an array of strings");
1221
1324
  }
1222
1325
  if (capability.humanConfirmation === "required") {
@@ -1228,7 +1331,12 @@ export async function dispatchOperatorAction(ctx, req) {
1228
1331
  return {
1229
1332
  kind: "error",
1230
1333
  status: 404,
1231
- body: operatorFailed({ command: action, outcome: "not-found", code: "NOT_FOUND", message: `confirmation not found: ${confirmationId}` }),
1334
+ body: operatorFailed({
1335
+ command: action,
1336
+ outcome: "not-found",
1337
+ code: "NOT_FOUND",
1338
+ message: `confirmation not found: ${confirmationId}`,
1339
+ }),
1232
1340
  };
1233
1341
  }
1234
1342
  const confirmedArgs = conf.reviewPacket?.operatorActionArgs;
@@ -1240,7 +1348,12 @@ export async function dispatchOperatorAction(ctx, req) {
1240
1348
  return {
1241
1349
  kind: "error",
1242
1350
  status: 403,
1243
- body: operatorFailed({ command: action, outcome: "blocked", code: "HUMAN_CONFIRMATION_REQUIRED", message: "confirmation receipt is not bound to this action, arguments, and operator session" }),
1351
+ body: operatorFailed({
1352
+ command: action,
1353
+ outcome: "blocked",
1354
+ code: "HUMAN_CONFIRMATION_REQUIRED",
1355
+ message: "confirmation receipt is not bound to this action, arguments, and operator session",
1356
+ }),
1244
1357
  };
1245
1358
  }
1246
1359
  let receiptBytes;
@@ -1251,7 +1364,12 @@ export async function dispatchOperatorAction(ctx, req) {
1251
1364
  return {
1252
1365
  kind: "error",
1253
1366
  status: 409,
1254
- body: operatorFailed({ command: action, outcome: "conflict", code: "CONFIRMATION_STALE", message: `cannot re-read confirmation receipt bytes: ${error instanceof Error ? error.message : String(error)}` }),
1367
+ body: operatorFailed({
1368
+ command: action,
1369
+ outcome: "conflict",
1370
+ code: "CONFIRMATION_STALE",
1371
+ message: `cannot re-read confirmation receipt bytes: ${error instanceof Error ? error.message : String(error)}`,
1372
+ }),
1255
1373
  };
1256
1374
  }
1257
1375
  const consumed = await ctx.confirmations.consume({
@@ -1261,7 +1379,8 @@ export async function dispatchOperatorAction(ctx, req) {
1261
1379
  dagSha256: hashDagBytes(receiptBytes),
1262
1380
  sourceBindingSha256: conf.sourceBindingSha256,
1263
1381
  taskContractBinding: conf.taskContractBinding,
1264
- controllerFingerprint: ctx.client.getIdentity?.()?.packageFingerprint?.value ?? conf.controllerFingerprint,
1382
+ controllerFingerprint: ctx.client.getIdentity?.()?.packageFingerprint?.value ??
1383
+ conf.controllerFingerprint,
1265
1384
  validationResultSha256: conf.validationResultSha256,
1266
1385
  },
1267
1386
  });
@@ -1269,11 +1388,18 @@ export async function dispatchOperatorAction(ctx, req) {
1269
1388
  return {
1270
1389
  kind: "error",
1271
1390
  status: confirmationErrorStatus(consumed.code),
1272
- body: operatorFailed({ command: action, outcome: "rejected", code: consumed.code, message: consumed.message }),
1391
+ body: operatorFailed({
1392
+ command: action,
1393
+ outcome: "rejected",
1394
+ code: consumed.code,
1395
+ message: consumed.message,
1396
+ }),
1273
1397
  };
1274
1398
  }
1275
1399
  }
1276
- const prefix = capability.cli.replace(/^(loop-agent|agent-worker)\s+/, "").split(/\s+/);
1400
+ const prefix = capability.cli
1401
+ .replace(/^(loop-agent|agent-worker)\s+/, "")
1402
+ .split(/\s+/);
1277
1403
  const args = [...prefix, ...(rawArgs ?? [])];
1278
1404
  if (capability.kind === "read") {
1279
1405
  if (!args.includes("--json"))
@@ -1418,7 +1544,11 @@ async function consumeMutationGateReceipt(ctx, action, p) {
1418
1544
  if (!reserved.ok) {
1419
1545
  return {
1420
1546
  kind: "error",
1421
- status: reserved.code === "NOT_FOUND" ? 404 : reserved.code === "EXPIRED" ? 410 : 409,
1547
+ status: reserved.code === "NOT_FOUND"
1548
+ ? 404
1549
+ : reserved.code === "EXPIRED"
1550
+ ? 410
1551
+ : 409,
1422
1552
  body: operatorFailed({
1423
1553
  command: action,
1424
1554
  outcome: "rejected",
@@ -1518,13 +1648,16 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1518
1648
  }
1519
1649
  const title = identity.title;
1520
1650
  const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
1521
- // new-task prints a path, not operator JSON do not expectJson (that flips ok=false).
1522
- const created = await run(["new-task", taskId, title], {
1651
+ // Standard lifecycle entry: task advance create + PRD import(s).
1652
+ const advanceCreate = await run(["task", "advance", taskId, title, "--json"], {
1523
1653
  cwd: ctx.repoRoot,
1524
- artifactName: "bootstrap-new-task",
1654
+ artifactName: "bootstrap-task-advance-create",
1655
+ expectJson: true,
1525
1656
  timeoutMs: 60_000,
1526
1657
  });
1527
- if (created.exitCode !== 0 || created.timedOut) {
1658
+ if (!advanceCreate.ok ||
1659
+ advanceCreate.exitCode !== 0 ||
1660
+ advanceCreate.timedOut) {
1528
1661
  return {
1529
1662
  kind: "error",
1530
1663
  status: 400,
@@ -1532,37 +1665,36 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1532
1665
  command: "bootstrapFromPrd",
1533
1666
  outcome: "rejected",
1534
1667
  code: "INTERNAL_ERROR",
1535
- message: created.stderr?.trim() ||
1536
- `new-task failed with exit ${created.exitCode}`,
1668
+ message: advanceCreate.stderr?.trim() ||
1669
+ `task advance (create) failed with exit ${advanceCreate.exitCode}`,
1537
1670
  details: {
1538
- exitCode: created.exitCode,
1539
- timedOut: created.timedOut,
1671
+ exitCode: advanceCreate.exitCode,
1672
+ timedOut: advanceCreate.timedOut,
1540
1673
  taskId,
1541
1674
  title,
1542
- stdoutPreview: (created.stdout ?? "").slice(0, 400),
1675
+ stdoutPreview: (advanceCreate.stdout ?? "").slice(0, 400),
1543
1676
  },
1544
1677
  }),
1545
1678
  };
1546
1679
  }
1547
1680
  const succeededImports = [];
1548
1681
  const importResults = [];
1549
- const runImportPrd = async (label, text, name) => {
1682
+ const runImportViaAdvance = async (label, text, name) => {
1550
1683
  const staged = await stageUtf8Text(ctx.appData, text, {
1551
1684
  extension: ".md",
1552
1685
  });
1553
1686
  const imported = await run([
1554
- "import-prd",
1687
+ "task",
1688
+ "advance",
1555
1689
  taskId,
1556
- "--file",
1690
+ "--prd",
1557
1691
  staged.path,
1558
- "--name",
1559
- name,
1560
1692
  "--role",
1561
1693
  "requirement",
1562
1694
  "--json",
1563
1695
  ], {
1564
1696
  cwd: ctx.repoRoot,
1565
- artifactName: `bootstrap-import-prd-${label}`,
1697
+ artifactName: `bootstrap-task-advance-prd-${label}`,
1566
1698
  expectJson: true,
1567
1699
  timeoutMs: 60_000,
1568
1700
  });
@@ -1575,7 +1707,7 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1575
1707
  outcome: "rejected",
1576
1708
  code: "INTERNAL_ERROR",
1577
1709
  message: imported.stderr?.trim() ||
1578
- `import-prd failed for "${name}" with exit ${imported.exitCode}. 任务已创建但不完整,请用 CLI 补 import-prd 或换 taskId 重试。`,
1710
+ `task advance --prd failed for "${name}" with exit ${imported.exitCode}. 任务已创建但不完整,请用 CLI 补 PRD 或换 taskId 重试。`,
1579
1711
  details: {
1580
1712
  exitCode: imported.exitCode,
1581
1713
  taskId,
@@ -1591,11 +1723,11 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1591
1723
  };
1592
1724
  if (documents && documents.length > 0) {
1593
1725
  for (const doc of documents) {
1594
- const failure = await runImportPrd(doc.name, doc.content, doc.name);
1726
+ const failure = await runImportViaAdvance(doc.name, doc.content, doc.name);
1595
1727
  if (failure)
1596
1728
  return failure;
1597
1729
  }
1598
- const composedFailure = await runImportPrd("composed", content, "composed");
1730
+ const composedFailure = await runImportViaAdvance("composed", content, "composed");
1599
1731
  if (composedFailure)
1600
1732
  return composedFailure;
1601
1733
  }
@@ -1603,9 +1735,9 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1603
1735
  const staged = await stageUtf8Text(ctx.appData, content, {
1604
1736
  extension: ".md",
1605
1737
  });
1606
- const imported = await run(["import-prd", taskId, "--file", staged.path, "--json"], {
1738
+ const imported = await run(["task", "advance", taskId, "--prd", staged.path, "--json"], {
1607
1739
  cwd: ctx.repoRoot,
1608
- artifactName: "bootstrap-import-prd",
1740
+ artifactName: "bootstrap-task-advance-prd",
1609
1741
  expectJson: true,
1610
1742
  timeoutMs: 60_000,
1611
1743
  });
@@ -1618,7 +1750,7 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1618
1750
  outcome: "rejected",
1619
1751
  code: "INTERNAL_ERROR",
1620
1752
  message: imported.stderr?.trim() ||
1621
- `import-prd failed with exit ${imported.exitCode}`,
1753
+ `task advance --prd failed with exit ${imported.exitCode}`,
1622
1754
  details: { exitCode: imported.exitCode, taskId },
1623
1755
  }),
1624
1756
  };
@@ -2037,24 +2169,85 @@ async function dispatchContractValidateOrDiff(ctx, action, p) {
2037
2169
  const draftText = str(p.draftJson) ?? str(p.inputText);
2038
2170
  if (!taskId)
2039
2171
  return invalid(action, "taskId is required");
2040
- let inputPath;
2041
- if (!draftText) {
2042
- const stored = await ctx.drafts.get(taskId);
2043
- if (!stored) {
2044
- return invalid(action, "draftJson or stored draft required");
2172
+ let draft;
2173
+ try {
2174
+ if (!draftText) {
2175
+ const stored = await ctx.drafts.get(taskId);
2176
+ if (!stored) {
2177
+ return invalid(action, "draftJson or stored draft required");
2178
+ }
2179
+ draft = toCanonicalDraftForCli(stored.draft);
2180
+ }
2181
+ else {
2182
+ draft = JSON.parse(draftText);
2045
2183
  }
2046
- const staged = await stageUtf8Text(ctx.appData, JSON.stringify(toCanonicalDraftForCli(stored.draft), null, 2), { extension: ".json" });
2047
- inputPath = staged.path;
2048
2184
  }
2049
- else {
2050
- const staged = await stageUtf8Text(ctx.appData, draftText, {
2051
- extension: ".json",
2185
+ catch (error) {
2186
+ const message = error instanceof Error ? error.message : String(error);
2187
+ return {
2188
+ kind: "error",
2189
+ status: 400,
2190
+ body: operatorFailed({
2191
+ command: action,
2192
+ outcome: "invalid",
2193
+ code: "INVALID_INPUT",
2194
+ message: `invalid draft JSON: ${message}`,
2195
+ }),
2196
+ };
2197
+ }
2198
+ try {
2199
+ if (action === "contractValidate") {
2200
+ const validation = await validateDraftForTask({
2201
+ repoRoot: ctx.repoRoot,
2202
+ taskId,
2203
+ draft: draft,
2204
+ });
2205
+ if (!validation.ok) {
2206
+ return {
2207
+ kind: "sync",
2208
+ status: 400,
2209
+ body: operatorFailed({
2210
+ command: action,
2211
+ outcome: "invalid",
2212
+ code: "INVALID_INPUT",
2213
+ message: validation.errors.join("; "),
2214
+ details: { errors: validation.errors },
2215
+ }),
2216
+ };
2217
+ }
2218
+ return {
2219
+ kind: "sync",
2220
+ status: 200,
2221
+ body: operatorSucceeded(action, {
2222
+ taskId,
2223
+ warnings: validation.warnings,
2224
+ }),
2225
+ };
2226
+ }
2227
+ const diff = await diffTaskContract({
2228
+ repoRoot: ctx.repoRoot,
2229
+ taskId,
2230
+ draft: draft,
2052
2231
  });
2053
- inputPath = staged.path;
2232
+ return {
2233
+ kind: "sync",
2234
+ status: 200,
2235
+ body: operatorSucceeded(action, { taskId, diff }),
2236
+ };
2237
+ }
2238
+ catch (error) {
2239
+ const message = error instanceof Error ? error.message : String(error);
2240
+ return {
2241
+ kind: "error",
2242
+ status: 400,
2243
+ body: operatorFailed({
2244
+ command: action,
2245
+ outcome: "blocked",
2246
+ code: "INTERNAL_ERROR",
2247
+ message,
2248
+ }),
2249
+ };
2054
2250
  }
2055
- const sub = action === "contractValidate" ? "validate" : "diff";
2056
- const body = await runReadCli(ctx, ["task", "contract", sub, "--task", taskId, "--input", inputPath, "--json"], `task contract ${sub}`);
2057
- return { kind: "sync", status: body.ok ? 200 : 400, body };
2058
2251
  }
2059
2252
  export function isMutationAction(action) {
2060
2253
  return MUTATION_OR_LONG.has(action);