@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
@@ -61,7 +61,7 @@ export async function adoptTaskContract(input) {
61
61
  }
62
62
  const state = await observeTaskContractState(input.repoRoot, input.taskId);
63
63
  if (state.effectiveStatus === "transaction-incomplete") {
64
- throw new ContractMutationError("TRANSACTION_INCOMPLETE", "unfinished transaction; run task contract recover first");
64
+ throw new ContractMutationError("TRANSACTION_INCOMPLETE", "unfinished transaction; re-run task advance (auto recover-contract)");
65
65
  }
66
66
  const currentRevision = logicalRevisionForState(state);
67
67
  if (input.expectedRevision !== currentRevision) {
@@ -180,7 +180,7 @@ export async function applyTaskContract(input) {
180
180
  }
181
181
  const state = await observeTaskContractState(input.repoRoot, input.taskId);
182
182
  if (state.effectiveStatus === "transaction-incomplete") {
183
- throw new ContractMutationError("TRANSACTION_INCOMPLETE", "unfinished transaction; run task contract recover first", { errors: state.observationErrors });
183
+ throw new ContractMutationError("TRANSACTION_INCOMPLETE", "unfinished transaction; re-run task advance (auto recover-contract)", { errors: state.observationErrors });
184
184
  }
185
185
  const currentRevision = logicalRevisionForState(state);
186
186
  if (input.expectedRevision !== currentRevision) {
@@ -17,7 +17,7 @@ export async function refreshManagedContractAfterImport(input) {
17
17
  return null;
18
18
  }
19
19
  if (state.effectiveStatus === "transaction-incomplete") {
20
- throw new Error(`TRANSACTION_INCOMPLETE: cannot refresh contract after import-prd for ${input.taskId}; run task contract recover first`);
20
+ throw new Error(`TRANSACTION_INCOMPLETE: cannot refresh contract after import-prd for ${input.taskId}; re-run task advance (auto recover-contract)`);
21
21
  }
22
22
  const requestId = input.requestId ??
23
23
  `import-prd-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
@@ -27,10 +27,10 @@ export async function doctorTaskContract(input) {
27
27
  }
28
28
  const recommendations = [];
29
29
  if (state.effectiveStatus === "transaction-incomplete") {
30
- recommendations.push("Run: loop-agent task contract recover --task <id> --json");
30
+ recommendations.push("Re-run: loop-agent task advance <id> --json");
31
31
  }
32
32
  if (state.effectiveStatus === "legacy-unversioned") {
33
- recommendations.push("Run apply with expectedRevision=0 or: loop-agent task contract adopt --task <id> ...");
33
+ recommendations.push("Re-run: loop-agent task advance <id> --from-draft <draft.json> --json");
34
34
  }
35
35
  if (state.effectiveStatus === "externally-modified") {
36
36
  recommendations.push("Review out-of-band edits, then apply a new Draft or adopt current facts");
@@ -1,7 +1,7 @@
1
1
  import { access } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { queryCursorRuns } from "../records/one-shot-runs.js";
4
- import { listAllDagRunEntries, readDagRunSpec, } from "../workflows/dag/lifecycle.js";
4
+ import { listExplicitlyAssociatedRuns } from "../application/task-lifecycle/observe.js";
5
5
  import { getTaskDir, getTaskPaths, getTaskStatus } from "./runtime.js";
6
6
  async function exists(filePath) {
7
7
  try {
@@ -13,19 +13,14 @@ async function exists(filePath) {
13
13
  }
14
14
  }
15
15
  async function listRelatedDagRuns(repoRoot, taskId) {
16
- const entries = await listAllDagRunEntries(repoRoot);
17
- const related = [];
18
- for (const entry of entries) {
19
- if (entry.runId.includes(taskId)) {
20
- related.push(entry);
21
- continue;
22
- }
23
- const spec = await readDagRunSpec(entry.runDir).catch(() => null);
24
- if (spec && JSON.stringify(spec).includes(taskId)) {
25
- related.push(entry);
26
- }
27
- }
28
- return related;
16
+ // Explicit association only (taskContractBinding / sourceBinding /
17
+ // workerAssociation / lifecycle.json). No taskId substring or JSON scan.
18
+ const related = await listExplicitlyAssociatedRuns(repoRoot, taskId);
19
+ return related.map((entry) => ({
20
+ runId: entry.runId,
21
+ lifecycle: entry.lifecycle,
22
+ runDir: entry.runDir,
23
+ }));
29
24
  }
30
25
  function closeoutPath(repoRoot, taskId) {
31
26
  return path.join(repoRoot, "docs", "progress", `${taskId}.md`);
@@ -33,42 +28,42 @@ function closeoutPath(repoRoot, taskId) {
33
28
  function deriveNextAction(input) {
34
29
  if (!input.sourceReady) {
35
30
  return {
36
- command: `npm run dev -- instructions source --task ${input.taskId} --json`,
37
- reason: "source materials are not ready",
31
+ command: `loop-agent task advance ${input.taskId} --prd <path> --allowed-path <glob> --json`,
32
+ reason: "source materials are not ready; advance from PRD + boundaries",
38
33
  };
39
34
  }
40
35
  if (!input.dagDraftExists && !input.latestDagRun) {
41
36
  return {
42
- command: `npm run dev -- dag run-task ${input.taskId} --profile auto --strict-models`,
37
+ command: `loop-agent task advance ${input.taskId} --json`,
43
38
  reason: "source ready; DAG draft/run evidence not found",
44
39
  };
45
40
  }
46
41
  if (input.dagDraftExists && !input.latestDagRun) {
47
42
  return {
48
- command: `npm run dev -- dag validate --dag ${input.dagDraftPath} --strict-models --strict-governance`,
49
- reason: "DAG draft exists and needs validation/execution",
43
+ command: `loop-agent task advance ${input.taskId} --json`,
44
+ reason: "DAG draft exists; advance to writeSet gate or continue lifecycle",
50
45
  };
51
46
  }
52
47
  if (input.latestDagRun && input.latestDagRun.lifecycle !== "completed") {
53
48
  return {
54
- command: `npm run dev -- dag status --run-id ${input.latestDagRun.runId}`,
49
+ command: `loop-agent task status ${input.taskId} --json`,
55
50
  reason: "DAG run exists but is not completed",
56
51
  };
57
52
  }
58
53
  if (input.latestDagRun && input.promotionRequired && !input.promotionReady) {
59
54
  return {
60
- command: `npm run dev -- promote-run ${input.taskId} --run-id ${input.latestDagRun.runId}`,
55
+ command: `loop-agent task advance ${input.taskId} --json`,
61
56
  reason: "completed run evidence needs promotion into task artifacts",
62
57
  };
63
58
  }
64
59
  if (!input.closeoutExists) {
65
60
  return {
66
- command: `npm run dev -- closeout task ${input.taskId}`,
61
+ command: `loop-agent task advance ${input.taskId} --json`,
67
62
  reason: "promoted task evidence needs closeout",
68
63
  };
69
64
  }
70
65
  return {
71
- command: `npm run dev -- dag report --task ${input.taskId}`,
66
+ command: `loop-agent task status ${input.taskId} --json`,
72
67
  reason: "task has source, run evidence, promotion, and closeout records",
73
68
  };
74
69
  }
@@ -299,7 +299,7 @@ export function getSupportedNextCommand(step, taskId, status, options) {
299
299
  return `npm run dev -- loop status ${taskId}`;
300
300
  }
301
301
  if (step === 'analyze' || step === 'plan' || step === 'spec' || step === 'implement' || step === 'verify' || step === 'retrospective') {
302
- return `npm run dev -- dag run-task ${taskId} --profile auto --strict-models`;
302
+ return `npm run dev -- task advance ${taskId} --profile auto --json`;
303
303
  }
304
304
  return `${step} step not yet implemented in this prototype; human intervention required`;
305
305
  }
@@ -31,7 +31,7 @@ export function observationBlocksPrepare(state) {
31
31
  return {
32
32
  code: "TRANSACTION_INCOMPLETE",
33
33
  level: "blocking",
34
- message: "unfinished contract transaction; run task contract recover before prepare",
34
+ message: "unfinished contract transaction; re-run task advance (auto recover-contract) before prepare",
35
35
  };
36
36
  }
37
37
  const disallowed = state.observationErrors.filter((e) => !FRESH_SOURCE_MISSING_CODES.has(e.code));
@@ -335,7 +335,12 @@ export function fillMissingRequirementFacts(base, supplemental) {
335
335
  ...base.sourceLabels,
336
336
  ...supplemental.sourceLabels,
337
337
  ]),
338
- acceptanceConflicts: base.acceptanceConflicts ?? [],
338
+ acceptanceConflicts: [
339
+ ...(base.acceptanceConflicts ?? []),
340
+ ...(base.acceptanceCriteria.length === 0
341
+ ? (supplemental.acceptanceConflicts ?? [])
342
+ : []),
343
+ ],
339
344
  };
340
345
  }
341
346
  export function parseVerifyFlag(raw) {
@@ -65,16 +65,16 @@ function buildNextSteps(input) {
65
65
  next.push(`loop-agent task source prepare ${input.taskId} --use-imported-prd ... --apply --json`);
66
66
  }
67
67
  if (input.ok && input.mode === "apply") {
68
- next.push(`loop-agent task contract show ${input.taskId} --json`);
69
- next.push(`loop-agent dag run-task ${input.taskId} --profile auto --strict-models`);
70
- next.push("审查 DAG writeSet 后再 run-dag");
68
+ next.push(`loop-agent task status ${input.taskId} --json`);
69
+ next.push(`loop-agent task advance ${input.taskId} --profile auto --json`);
70
+ next.push("审查 DAG writeSet 后再 task advance --approve-gate ...");
71
71
  }
72
72
  if (input.gaps.some((g) => g.code === "DIRTY_SOURCE")) {
73
- next.push("loop-agent task contract show|diff|adopt|recover");
73
+ next.push("loop-agent task status --json | task advance --from-draft ...");
74
74
  next.push("或显式 --force-overwrite-source 后重跑 prepare");
75
75
  }
76
76
  if (input.gaps.some((g) => g.code === "TRANSACTION_INCOMPLETE")) {
77
- next.push(`loop-agent task contract recover --task ${input.taskId}`);
77
+ next.push(`loop-agent task advance ${input.taskId} --json`);
78
78
  }
79
79
  return next;
80
80
  }
@@ -321,10 +321,18 @@ export async function prepareTaskSource(input) {
321
321
  ok: !blocking,
322
322
  gaps,
323
323
  });
324
+ const blockingCodes = gaps
325
+ .filter((gap) => gap.level === "blocking")
326
+ .map((gap) => gap.code);
327
+ const transactionIncomplete = blockingCodes.includes("TRANSACTION_INCOMPLETE");
324
328
  if (input.mode !== "apply") {
325
329
  return {
326
330
  ok: !blocking,
327
- outcome: blocking ? "invalid" : "succeeded",
331
+ outcome: blocking
332
+ ? transactionIncomplete
333
+ ? "needs-reconcile"
334
+ : "invalid"
335
+ : "succeeded",
328
336
  mode: "dry-run",
329
337
  taskId,
330
338
  taskKind: built.draft.taskKind,
@@ -339,13 +347,11 @@ export async function prepareTaskSource(input) {
339
347
  ...(blocking
340
348
  ? {
341
349
  error: {
342
- code: "INVALID_INPUT",
350
+ code: transactionIncomplete
351
+ ? "TRANSACTION_INCOMPLETE"
352
+ : "INVALID_INPUT",
343
353
  message: "task source draft has blocking gaps",
344
- details: {
345
- gaps: gaps
346
- .filter((g) => g.level === "blocking")
347
- .map((g) => g.code),
348
- },
354
+ details: { gaps: blockingCodes },
349
355
  },
350
356
  }
351
357
  : {}),
@@ -365,19 +371,16 @@ export async function prepareTaskSource(input) {
365
371
  gaps,
366
372
  risks,
367
373
  projected,
368
- next: [`loop-agent task contract recover --task ${taskId}`],
374
+ next: [`loop-agent task advance ${taskId} --json`],
369
375
  contractStatus: state.effectiveStatus,
370
376
  warnings,
371
377
  error: {
372
378
  code: "TRANSACTION_INCOMPLETE",
373
- message: "unfinished transaction; run task contract recover first",
379
+ message: "unfinished transaction; re-run task advance (auto recover-contract)",
374
380
  },
375
381
  };
376
382
  }
377
383
  if (blocking) {
378
- const blockingCodes = gaps
379
- .filter((g) => g.level === "blocking")
380
- .map((g) => g.code);
381
384
  const outcome = blockingCodes.includes("DIRTY_SOURCE")
382
385
  ? "blocked"
383
386
  : "invalid";
@@ -417,7 +420,7 @@ export async function prepareTaskSource(input) {
417
420
  gaps,
418
421
  risks,
419
422
  projected,
420
- next: [`loop-agent task contract doctor --task ${taskId}`],
423
+ next: [`loop-agent task status ${taskId} --details --json`],
421
424
  error: {
422
425
  code: "OBSERVED_HASH_CONFLICT",
423
426
  message: "unable to determine expectedObservedHash",
@@ -477,8 +480,8 @@ export async function prepareTaskSource(input) {
477
480
  risks,
478
481
  projected,
479
482
  next: [
480
- `loop-agent task contract show ${taskId} --json`,
481
- `loop-agent task contract doctor --task ${taskId}`,
483
+ `loop-agent task status ${taskId} --json`,
484
+ `loop-agent task status ${taskId} --details --json`,
482
485
  ],
483
486
  error: {
484
487
  code: error.code,