@tea-agent/loop-agent 0.38.1 → 0.38.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.
Files changed (180) hide show
  1. package/CHANGELOG.md +126 -12
  2. package/README.md +2 -0
  3. package/bin/loop-agent.js +7 -3
  4. package/dist/application/dag/generate-task-dag.js +9 -0
  5. package/dist/application/task-lifecycle/advance.js +4 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/commands/init.js +2 -14
  8. package/dist/commands/task-advance.js +31 -0
  9. package/dist/executors/dag-pi-executor.js +99 -5
  10. package/dist/executors/pi-event-serializer.js +42 -1
  11. package/dist/executors/pi-executor.js +51 -0
  12. package/dist/executors/pi-extension-resolver.js +233 -0
  13. package/dist/executors/pi-sdk-executor.js +187 -59
  14. package/dist/executors/shell-executor.js +324 -16
  15. package/dist/executors/shell-write-guard.js +7 -0
  16. package/dist/governance/manifest-types.js +0 -2
  17. package/dist/shared/operator/capabilities.js +87 -22
  18. package/dist/task/config-types.js +22 -0
  19. package/dist/task/contract/constants.js +1 -0
  20. package/dist/task/contract/diff.js +26 -0
  21. package/dist/task/contract/project.js +5 -0
  22. package/dist/task/contract/schema.js +6 -1
  23. package/dist/task/source-prepare/build-draft.js +15 -0
  24. package/dist/worker/console/chat/deferred-turn.js +91 -0
  25. package/dist/worker/console/chat/pi-runtime.js +10 -5
  26. package/dist/worker/console/chat/routes.js +458 -47
  27. package/dist/worker/console/chat/semantic-activity.js +13 -13
  28. package/dist/worker/console/chat/session-store.js +146 -2
  29. package/dist/worker/console/harness-pi-model-matrix.js +2 -11
  30. package/dist/worker/console/operator-actions.js +5 -4
  31. package/dist/worker/console/pi-readiness.js +5 -4
  32. package/dist/worker/console/recovery-cta.js +4 -4
  33. package/dist/worker/console/recovery-error-copy.js +4 -4
  34. package/dist/worker/console/server.js +6 -1
  35. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Hfsot6rP.js → abnfDiagram-N423BO3Z-C4kVYweA.js} +1 -1
  36. package/dist/worker/console/static/assets/{arc-YGwEASin.js → arc-Bj2M8iO5.js} +1 -1
  37. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CmOM0F92.js → architectureDiagram-T3A2C74G-CLlXe4-6.js} +1 -1
  38. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DDLuaHC5.js → blockDiagram-VBNYF7ZC-psEp5xH0.js} +1 -1
  39. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B0Um43W4.js → c4Diagram-5PPSVZJV-DeKcOCGJ.js} +1 -1
  40. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +1 -0
  41. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DvBN4JZc.js → chunk-2GRJ4B5K-K0C744p1.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B1UVgW4E.js → chunk-2Q5K7J3B-xbNw4J9-.js} +1 -1
  43. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Cf0GgXzX.js → chunk-5RXB4S5H-DjLaSDUO.js} +1 -1
  44. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DbNA24sj.js → chunk-5VM5RSS4-CtiPlKel.js} +1 -1
  45. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bi_0Be06.js → chunk-6Q2QTUOP-DZtolir7.js} +1 -1
  46. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C8pPGvRd.js → chunk-GF5L2VYU-CPcdNeew.js} +1 -1
  47. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BitQY8Z-.js → chunk-JWPE2WC7-NgHc6V37.js} +1 -1
  48. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-U67GCiEo.js → chunk-KBJHAD2P-DQ_T-hg8.js} +1 -1
  49. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-IoJwXPBA.js → chunk-RYQCIY6F-DgLsxcVP.js} +1 -1
  50. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-YDZ_UHgE.js → chunk-XXDRQBXY-UrVoM9z1.js} +1 -1
  51. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +1 -0
  52. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +1 -0
  53. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CUZgsiRC.js → cose-bilkent-JH36ORCC-CLkYvTb3.js} +1 -1
  54. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-COmCh4pT.js → cynefin-VYW2F7L2-BwT_xKrE.js} +1 -1
  55. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Ta6FSm68.js → cynefinDiagram-MW4NZA55-Bi9tuzif.js} +1 -1
  56. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UdcR-VW9.js → dagre-VZM6K2ZE-BCIqkWBV.js} +1 -1
  57. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DybcO3Et.js → diagram-7IWD3JNH-Dip6l9_Z.js} +1 -1
  58. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-wErDFWLw.js → diagram-B4RE2ZJO-B-xkh_wK.js} +1 -1
  59. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DkVHZWqz.js → diagram-LBJQPF4R-DZO0kTt_.js} +1 -1
  60. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DdqWs7QN.js → diagram-Q27KOJAE-D6ZplNbZ.js} +1 -1
  61. package/dist/worker/console/static/assets/{diagram-UB23O5K3-C9dMVmDo.js → diagram-UB23O5K3-BPekbhoS.js} +1 -1
  62. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B2T2WcBK.js → ebnfDiagram-BXEA7PRR-BtmaJpK5.js} +1 -1
  63. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bt9rjDFq.js → erDiagram-JOGREHBK-6DS0Xc44.js} +1 -1
  64. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-U8ic_tvi.js → flowDiagram-UKHOOZJN-CihnROSm.js} +1 -1
  65. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BfQJdQoO.js → ganttDiagram-PKOTCBZU-CZC4zOE2.js} +1 -1
  66. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CpMftH4O.js → gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js} +1 -1
  67. package/dist/worker/console/static/assets/index-DTZOKgAn.js +404 -0
  68. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +1 -0
  69. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DzqWXNO_.js → infoDiagram-6WML65LV-DlSl4BGx.js} +1 -1
  70. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js → ishikawaDiagram-WSZJBQD7-snnzFl_U.js} +1 -1
  71. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ChPtKFLz.js → journeyDiagram-NVQOT4AX-BDE7qpMx.js} +1 -1
  72. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-PRe7wB5u.js → kanban-definition-27J2QSJJ-CD2Ci04G.js} +1 -1
  73. package/dist/worker/console/static/assets/{linear-DpKX6QNp.js → linear-DdnapKIH.js} +1 -1
  74. package/dist/worker/console/static/assets/{mermaid.core-Dnl76b7Y.js → mermaid.core-BVjAT9b8.js} +5 -5
  75. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-S9PCXc3J.js → mindmap-definition-FAOFIHXS-D0yJk-zA.js} +1 -1
  76. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Du8e-FSX.js → pegDiagram-VL7TDLO6-BVo9tFP-.js} +1 -1
  77. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CY6OmHTc.js → pieDiagram-7S7Q4E2Y-DnOV-mZ_.js} +1 -1
  78. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C65GPhpj.js → quadrantDiagram-CIZ2JOQS-CzUJo58i.js} +1 -1
  79. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-U6IUiTCL.js → railroadDiagram-AXF67PYL-Bvikrolh.js} +1 -1
  80. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-I_9Qhi.js → requirementDiagram-LRYGKXZP-DtaSVCap.js} +1 -1
  81. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B57Fhed0.js → sankeyDiagram-W5VNT64P-C2c9A0wW.js} +1 -1
  82. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CJOtiw21.js → sequenceDiagram-SI44F4Z6-DmXJcU7r.js} +1 -1
  83. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-FyX8zxSh.js → sizeCapture-X5ZJPWSS-B4GUFW92.js} +1 -1
  84. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-eF-pbs9t.js → stateDiagram-OKZ733FA-Dsad2MXf.js} +1 -1
  85. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +1 -0
  86. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CcfkA-Dq.js → swimlanes-SLNWSIFB-DGq48Fbi.js} +2 -2
  87. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +8 -0
  88. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DH8FjnOK.js → timeline-definition-Z64GVDOM-CSSi6OFf.js} +1 -1
  89. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-beoTagJg.js → vennDiagram-T6HMQDX7-BqyzPrWv.js} +1 -1
  90. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CQSfw4cK.js → wardleyDiagram-T6FBY63Y-DlznVb6S.js} +1 -1
  91. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQgzQ6jn.js → xychartDiagram-ELKLHX3M-CfBcgJ_K.js} +1 -1
  92. package/dist/worker/console/static/index.html +2 -2
  93. package/dist/worker/console/static-src/app/console-types.js +0 -12
  94. package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
  95. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  96. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +38 -1
  97. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  98. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  99. package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
  100. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
  101. package/dist/worker/console/static-src/operator-chat/useChatStream.js +204 -11
  102. package/dist/worker/console/static-src/operator-chat/useChatThread.js +23 -1
  103. package/dist/worker/observe/node-input.js +72 -3
  104. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  105. package/dist/worker/observe/static/state.js +2 -2
  106. package/dist/worker/observe/static/views/dag-inspector.js +51 -0
  107. package/dist/worker/observe/static/views/session-timeline.js +18 -5
  108. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +809 -53
  109. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  110. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  111. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  112. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  113. package/dist/workflows/dag/backend-test-layout.js +133 -0
  114. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  115. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  116. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  117. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  118. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  119. package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
  120. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  121. package/dist/workflows/dag/contract-output-registry.js +15 -0
  122. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  123. package/dist/workflows/dag/convergence/controller.js +141 -0
  124. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  125. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  126. package/dist/workflows/dag/failure-category.js +4 -0
  127. package/dist/workflows/dag/frontend-implementation-contract.js +355 -26
  128. package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
  129. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  130. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  131. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  132. package/dist/workflows/dag/frontend-test-result-contract.js +10 -18
  133. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  134. package/dist/workflows/dag/init-hybrid.js +720 -177
  135. package/dist/workflows/dag/node-execution.js +164 -26
  136. package/dist/workflows/dag/prompt.js +60 -1
  137. package/dist/workflows/dag/rerun-feedback.js +212 -0
  138. package/dist/workflows/dag/rerun-plan.js +112 -27
  139. package/dist/workflows/dag/rerun-run.js +3 -0
  140. package/dist/workflows/dag/rerun-task.js +61 -2
  141. package/dist/workflows/dag/retry-policy.js +18 -0
  142. package/dist/workflows/dag/structured-output-repair.js +712 -0
  143. package/dist/workflows/dag/types.js +121 -0
  144. package/dist/workflows/dag/validate.js +41 -1
  145. package/docs/operations/local-development-environment.md +1 -5
  146. package/docs/skills/vetted-skill-registry.md +14 -0
  147. package/docs/templates/README.md +1 -1
  148. package/docs/templates/agent-dag.schema.json +44 -0
  149. package/docs/templates/backend-test-dag.json +60 -35
  150. package/docs/templates/frontend-test-dag.json +8 -10
  151. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  152. package/docs/templates/harness.schema.json +0 -8
  153. package/harness.json +1 -8
  154. package/package.json +8 -4
  155. package/skills/analyze-product-requirements/SKILL.md +8 -5
  156. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  157. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  158. package/skills/analyze-product-requirements/references/example.md +50 -0
  159. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  160. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  161. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  162. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  163. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  164. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  165. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  166. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  167. package/skills/codebase-scout/SKILL.md +1 -1
  168. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  169. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  170. package/skills/improve-codebase-architecture/deepening.md +37 -0
  171. package/skills/improve-codebase-architecture/html-report.md +123 -0
  172. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  173. package/skills/improve-codebase-architecture/language.md +53 -0
  174. package/dist/worker/console/static/assets/channel-Dp_2Aqf1.js +0 -1
  175. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL--CODy37j.js +0 -1
  176. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE--CODy37j.js +0 -1
  177. package/dist/worker/console/static/assets/index-BA5-N5ER.css +0 -1
  178. package/dist/worker/console/static/assets/index-DKPXBrdc.js +0 -325
  179. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B-mvnLEc.js +0 -1
  180. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-JtpBmIv4.js +0 -8
@@ -13,13 +13,45 @@ const CONTROLLER_PROTOCOL_CAPABILITIES = [
13
13
  "worker-attempt-revision-v1",
14
14
  "recovery-decision-v2",
15
15
  ];
16
- function officialModelCallable(coverage, kind) {
16
+ function officialModelCallable(coverage, _kind) {
17
17
  if (coverage === "human-gated-required")
18
18
  return "prepare-only";
19
- if (coverage === "advanced" && kind !== "read")
19
+ // 2026-08-18: advanced actions (read or mutation) stay OUT of the model
20
+ // tool surface — low-frequency/forensic commands with generic args[] params
21
+ // diluted model attention. They remain dispatchable via UI capabilities.
22
+ if (coverage === "advanced")
20
23
  return "never";
21
24
  return "always";
22
25
  }
26
+ const OFFICIAL_ACTION_DESCRIPTIONS = {
27
+ dagDecisionInspect: "Inspect a pending DAG decision envelope (inputs, options, chosen branch). Use before advising the operator on approve/reject; read-only.",
28
+ dagDecisionValidate: "Validate a DAG decision envelope against its schema. Use when a decision payload looks malformed or a writer emitted a suspect envelope; read-only.",
29
+ dagCloseoutDraft: "Draft the closeout handoff document for a finished DAG run. Use when the run is FINISHED and you need the handoff text; read-only.",
30
+ planList: "List execution plans in the repository. Use to discover plan ids before plan create/complete/check.",
31
+ planCreate: "Create an execution plan from a summary. Use to record the next work block before starting it.",
32
+ planComplete: "Mark an execution plan complete with a summary of what was delivered. Use after a work block is done and verified.",
33
+ planCheck: "Check execution-plan health and drift. Use to audit plan state without mutating anything.",
34
+ docsAudit: "Audit docs freshness and links (document catalog policy). Use when asked whether docs are stale or need review.",
35
+ handoffCheck: "Check handoff document completeness against the verification matrix. Use before claiming a task is fully handed off.",
36
+ handoffCoverage: "Show verification coverage across tests/docs/links for the repo. Use when asked what is verified and what is not.",
37
+ coverageReport: "Produce the verification coverage report. Use for a structured view of tests vs requirements.",
38
+ stats: "Repository workflow statistics (tasks, runs, durations). Use for quantitative status questions.",
39
+ promoteRun: "Task advance alias; passes a task id and flags through to task advance to promote a run. Does not pre-verify run state here — confirm with dagReport that the run is FINISHED before calling.",
40
+ closeoutTask: "Task advance alias; passes a task id and flags through to task advance to close out a task. Confirm the task's work is delivered and verified before calling.",
41
+ knowledgeCurate: "Generate knowledge-base curation proposals (may write a proposal file when --output is passed). Write-capable; use via the operator CLI, not as a model tool.",
42
+ knowledgeQuery: "Query the repository knowledge base. Use instead of re-reading files when a curated answer may exist.",
43
+ workerFeatureReview: "Review worker feature status and checkpoints (agent-worker). Use to see where a feature line stands.",
44
+ workerFeatureDoctor: "Diagnose worker feature health (agent-worker). Use when a feature line looks stuck or inconsistent.",
45
+ workerTaskValidate: "Validate a worker task spec (agent-worker). Use before queuing or when a task looks malformed.",
46
+ workerBatchPlanReady: "Show which batch tasks are ready to run (agent-worker). Use for queue triage before workerBatchRunReady.",
47
+ workerReportMorning: "Morning report for overnight worker activity (agent-worker). Use at the start of a session to catch overnight failures.",
48
+ workerReportMetrics: "Worker throughput and failure metrics (agent-worker). Use for quantitative worker questions.",
49
+ workerAdmissionShow: "Show night admission queue state (agent-worker). Use before night-scheduler mutations.",
50
+ workerSchedulerList: "List night-scheduler schedules (agent-worker). Use to discover schedule ids.",
51
+ workerSchedulerStatus: "Night-scheduler status overview (agent-worker). Use to check whether overnight jobs are healthy.",
52
+ workerSchedulerLedger: "Night-scheduler harvest ledger (agent-worker). Use to audit what overnight runs produced.",
53
+ workerSchedulerDoctor: "Diagnose night-scheduler health (agent-worker). Use when overnight jobs fail to fire or harvest.",
54
+ };
23
55
  const officialAction = (action, cli, kind, coverage) => ({
24
56
  action,
25
57
  cli,
@@ -38,7 +70,8 @@ const officialAction = (action, cli, kind, coverage) => ({
38
70
  "TRANSACTION_INCOMPLETE",
39
71
  "NOT_FOUND",
40
72
  ],
41
- description: `Official CLI action coverage for ${cli}.`,
73
+ description: OFFICIAL_ACTION_DESCRIPTIONS[action] ??
74
+ `Official CLI action coverage for ${cli}.`,
42
75
  inputParams: [
43
76
  {
44
77
  name: "args",
@@ -158,8 +191,8 @@ const OFFICIAL_ACTIONS = [
158
191
  [
159
192
  "knowledgeCurate",
160
193
  "loop-agent knowledge curate",
161
- "read",
162
- "model-callable",
194
+ "mutation",
195
+ "advanced",
163
196
  ],
164
197
  ["knowledgeQuery", "loop-agent knowledge query", "read", "model-callable"],
165
198
  [
@@ -334,6 +367,12 @@ const OFFICIAL_ACTIONS = [
334
367
  ],
335
368
  ],
336
369
  ].map(([action, cli, kind, coverage]) => officialAction(action, cli, kind, coverage));
370
+ /**
371
+ * Action ids produced by the officialAction() factory (the generic CLI
372
+ * coverage tail). Derived from the same tuple table so dispatcher / gate
373
+ * checks do not string-match descriptions.
374
+ */
375
+ export const OFFICIAL_TAIL_ACTION_IDS = new Set(OFFICIAL_ACTIONS.map((entry) => entry.action));
337
376
  /** Shared constants so dag + dagSpecVersions cannot diverge. */
338
377
  const DAG_SPEC_SUPPORTED_PARSE = [1, 2, 3, 4];
339
378
  const DAG_SPEC_NEW_WRITER_DEFAULT = 4;
@@ -609,7 +648,7 @@ export function buildOperatorCapabilitiesDocument() {
609
648
  resultSchemaVersion: 1,
610
649
  envelopeSchemaVersion: 1,
611
650
  requiredErrorCodes: [...COMMON_MUTATION_ERRORS],
612
- description: "Create or advance a task via the single lifecycle mutation entry.",
651
+ description: "Create a new task (lifecycle entry). Prefer taskAdvance for the standard flow (PRD import, taskKind, boundaries, DAG generation); newTask only bootstraps a task id + title.",
613
652
  inputParams: [
614
653
  {
615
654
  name: "taskId",
@@ -685,7 +724,7 @@ export function buildOperatorCapabilitiesDocument() {
685
724
  resultSchemaVersion: 1,
686
725
  envelopeSchemaVersion: 1,
687
726
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
688
- description: "Read source instruction contract for a task.",
727
+ description: "Read the task's source instruction contract (需求.md/执行约束.md projection: requirements, constraints, allowed/forbidden paths). Use before drafting or revising a contract to stay aligned with the source.",
689
728
  inputParams: [
690
729
  {
691
730
  name: "taskId",
@@ -835,8 +874,33 @@ export function buildOperatorCapabilitiesDocument() {
835
874
  resultSchemaVersion: 1,
836
875
  envelopeSchemaVersion: 1,
837
876
  requiredErrorCodes: ["INVALID_INPUT"],
838
- description: "Build Observe deep link from stable object identity.",
839
- inputParams: [],
877
+ description: "Build Observe deep link from stable object identity. Pass exactly one of dagRunId (preferred), taskId (resolves to latest run), featureId, or workerRunId; omit all for the Console home link.",
878
+ inputParams: [
879
+ {
880
+ name: "dagRunId",
881
+ type: "string",
882
+ required: false,
883
+ description: "dag run id; deep-links to #/dag/<runId> (preferred when known)",
884
+ },
885
+ {
886
+ name: "taskId",
887
+ type: "string",
888
+ required: false,
889
+ description: "task id; resolves to the latest dag run when one exists, else #/task/<taskId>",
890
+ },
891
+ {
892
+ name: "featureId",
893
+ type: "string",
894
+ required: false,
895
+ description: "feature line id; deep-links to the feature view",
896
+ },
897
+ {
898
+ name: "workerRunId",
899
+ type: "string",
900
+ required: false,
901
+ description: "worker run id; deep-links to the worker run view",
902
+ },
903
+ ],
840
904
  modelCallable: "always",
841
905
  humanConfirmation: "none",
842
906
  },
@@ -848,7 +912,7 @@ export function buildOperatorCapabilitiesDocument() {
848
912
  resultSchemaVersion: 1,
849
913
  envelopeSchemaVersion: 1,
850
914
  requiredErrorCodes: ["INVALID_INPUT"],
851
- description: "Repo preflight (registry entry; envelope migration follow-up).",
915
+ description: "Preflight a repository for loop-agent: checks harness presence, task-pool layout, and reports what the Console can operate on. Use at session start on an unfamiliar repo, before init/upgrade decisions.",
852
916
  inputParams: [],
853
917
  modelCallable: "always",
854
918
  humanConfirmation: "none",
@@ -907,7 +971,7 @@ export function buildOperatorCapabilitiesDocument() {
907
971
  resultSchemaVersion: 1,
908
972
  envelopeSchemaVersion: 1,
909
973
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
910
- description: "Task lifecycle status (read-only).",
974
+ description: "Read one task's lifecycle status: state, blockers, gates, contract revision, run associations. Use for task-level questions; use dagStatus for a specific DAG run's live node/liveness.",
911
975
  inputParams: [
912
976
  {
913
977
  name: "taskId",
@@ -927,7 +991,7 @@ export function buildOperatorCapabilitiesDocument() {
927
991
  resultSchemaVersion: 1,
928
992
  envelopeSchemaVersion: 1,
929
993
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
930
- description: "Spine audit (registry entry).",
994
+ description: "Audit a task's minimal spec spine: source docs, path ownership, requirement ids, final verification coverage. Use before closing out a task to confirm every AC is verified.",
931
995
  inputParams: [
932
996
  {
933
997
  name: "taskId",
@@ -1020,7 +1084,7 @@ export function buildOperatorCapabilitiesDocument() {
1020
1084
  "EXTERNALLY_MODIFIED",
1021
1085
  "INVALID_INPUT",
1022
1086
  ],
1023
- description: "Generate + strict-validate DAG via task advance (stops at writeSet gate).",
1087
+ description: "Generate + strict-validate a task's DAG (via task advance, stops at the writeSet gate). Legacy entry; the standard flow is taskAdvance then prepareDagExecution + runDag.",
1024
1088
  inputParams: [
1025
1089
  {
1026
1090
  name: "taskId",
@@ -1099,7 +1163,7 @@ export function buildOperatorCapabilitiesDocument() {
1099
1163
  resultSchemaVersion: 1,
1100
1164
  envelopeSchemaVersion: 1,
1101
1165
  requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
1102
- description: "Read-only check: can this failed run continue from fromNode? Call after dagReport; pass primaryFailure.nodeId. Does not execute. eligible=true returns planHash for dagRerun. eligible=false: Worker/Task Pool Failed workerTaskRetry; standalone Console run standaloneTaskRerun after any required contract adopt. Do not call dagRerun or invent a new task.",
1166
+ description: "Read-only check for failed-node recovery; it does not execute. Call after dagReport with primaryFailure.nodeId. eligible=true returns the exact planHash required by dagRerun. eligible=false covers writer/decision/fingerprint, unsafe shell, Worker ownership, or workspace/controller drift: use workerTaskRetry for Worker/Task Pool failures, or standaloneTaskRerun after any required contract adopt for standalone runs. Do not force dagRerun or invent a new task.",
1103
1167
  inputParams: [
1104
1168
  {
1105
1169
  name: "runId",
@@ -1129,7 +1193,7 @@ export function buildOperatorCapabilitiesDocument() {
1129
1193
  "BINDING_DRIFT",
1130
1194
  "INVALID_INPUT",
1131
1195
  ],
1132
- description: "Continue the same DAG from fromNode. Requires a fresh dagRerunPlan with eligible=true and that exact planHash. Prefer this for provider flake / safe downstream failures. Not a full task regenerate (that is standaloneTaskRerun). Server rejects stale planHash or binding drift.",
1196
+ description: "Continue the same DAG from the effective failed node by creating a continuation run. Requires a fresh eligible dagRerunPlan and its exact planHash. Prefer this for provider, network, rate-limit, timeout, and other safe downstream failures. Not a full task regenerate: use standaloneTaskRerun for that. Server rejects stale planHash, binding, or fingerprint drift.",
1133
1197
  inputParams: [
1134
1198
  {
1135
1199
  name: "runId",
@@ -1495,7 +1559,7 @@ export function buildOperatorCapabilitiesDocument() {
1495
1559
  "INVALID_INPUT",
1496
1560
  "HUMAN_CONFIRMATION_REQUIRED",
1497
1561
  ],
1498
- description: "Regenerate the whole standalone DAG from the CURRENT contract, then validate and execute a new run (parent lineage kept). This is NOT continue-from-failed-node that is dagRerun. Server rejects the call when dagRerunPlan is eligible, when primaryRecovery.humanRequired=true, or when run facts do not recommend rerun-task. Adopt contract changes before calling or the new DAG repeats the same failure.",
1562
+ description: "Regenerate the whole standalone DAG from the CURRENT contract, then validate and execute a new run with parent lineage and the latest unresolved verify/review feedback when available. This is NOT continue-from-failed-node (use dagRerun for that). Server rejects the call when dagRerunPlan is eligible, primaryRecovery.humanRequired=true, or run facts do not recommend rerun-task. Feedback is recovery context, not a contract rewrite; adopt required contract changes before calling or the new DAG repeats the same failure.",
1499
1563
  inputParams: [
1500
1564
  {
1501
1565
  name: "runId",
@@ -2004,12 +2068,12 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2004
2068
  },
2005
2069
  {
2006
2070
  command: "loop-agent knowledge curate",
2007
- coverage: "model-callable",
2071
+ coverage: "advanced",
2008
2072
  action: "knowledgeCurate",
2009
2073
  source: "loop-agent",
2074
+ note: "Writes knowledge proposals (conditional on --output); not model-callable.",
2010
2075
  },
2011
- {
2012
- command: "loop-agent knowledge query",
2076
+ { command: "loop-agent knowledge query",
2013
2077
  coverage: "model-callable",
2014
2078
  action: "knowledgeQuery",
2015
2079
  source: "loop-agent",
@@ -2584,9 +2648,10 @@ export function assertCoverageManifestConsistent() {
2584
2648
  capability.humanConfirmation !== "required"))
2585
2649
  throw new Error(`invalid required gate classification: ${entry.action}`);
2586
2650
  if (entry.coverage === "advanced") {
2587
- const expected = capability.kind === "read" ? "always" : "never";
2588
- if (capability.modelCallable !== expected) {
2589
- throw new Error(`invalid advanced classification: ${entry.action} (expected modelCallable=${expected} for kind=${capability.kind})`);
2651
+ // 2026-08-18: advanced actions are never model-callable (read or
2652
+ // mutation) — see officialModelCallable. UI dispatch stays available.
2653
+ if (capability.modelCallable !== "never") {
2654
+ throw new Error(`invalid advanced classification: ${entry.action} (expected modelCallable=never)`);
2590
2655
  }
2591
2656
  }
2592
2657
  }
@@ -124,6 +124,26 @@ export const frontendTestConfigSchema = z.object({
124
124
  .strict()
125
125
  .optional(),
126
126
  });
127
+ /** Backend-test artifact layout and pipeline mode configuration (plan A/B/D). */
128
+ export const backendTestConfigSchema = z
129
+ .object({
130
+ /** Repository-relative test root. Default "testcase". */
131
+ testRoot: z.string().min(1).optional(),
132
+ /** Markdown subdirectory relative to testRoot. Default "md". */
133
+ markdownRel: z.string().min(1).optional(),
134
+ /** Pytest script subdirectory relative to testRoot. Default "." (testRoot itself). */
135
+ scriptRel: z.string().min(1).optional(),
136
+ /** User-provided shared setup document (plan B). Read-only bound source. */
137
+ sharedSetupDoc: z.string().min(1).optional(),
138
+ /** Pipeline mode: full 16-node chain (default) or gap-fill incremental DAG (plan D). */
139
+ mode: z.enum(["full", "gap-fill"]).optional(),
140
+ /** Required for mode=gap-fill: user missing-scenario document path. */
141
+ gapDoc: z.string().min(1).optional(),
142
+ /** gap-fill pytest execution scope: only affected items (default) or all eligible. */
143
+ executeScope: z.enum(["affected", "all"]).optional(),
144
+ })
145
+ .strict()
146
+ .optional();
127
147
  export const convergenceConfigSchema = z.object({
128
148
  enabled: z.boolean().optional().default(false),
129
149
  maxPasses: z.number().int().positive().optional().default(3),
@@ -196,6 +216,8 @@ const taskConfigObjectSchema = z.object({
196
216
  frontendTest: frontendTestConfigSchema.optional(),
197
217
  /** Frontend OpenSpec gate policy and contract-declared required read paths. */
198
218
  frontendOpenspec: frontendOpenspecConfigSchema.optional(),
219
+ /** Backend-test layout / shared-setup / gap-fill configuration. */
220
+ backendTest: backendTestConfigSchema,
199
221
  notes: z.string().optional().default(""),
200
222
  });
201
223
  export const taskConfigSchema = z.preprocess((raw) => {
@@ -17,6 +17,7 @@ export const MANAGED_TASK_CONFIG_FIELDS = [
17
17
  "forbiddenPaths",
18
18
  "hardConstraints",
19
19
  "verifyCommands",
20
+ "backendTest",
20
21
  ];
21
22
  export const CONTRACT_FILE_NAME = "contract.json";
22
23
  export const TRANSACTIONS_DIR_NAME = ".transactions";
@@ -1,3 +1,4 @@
1
+ import { canonicalizeJsonValue } from "./canonicalize.js";
1
2
  import { MANAGED_TASK_CONFIG_FIELDS } from "./constants.js";
2
3
  function maxRisk(a, b) {
3
4
  const order = ["routine", "elevated", "blocking"];
@@ -139,6 +140,31 @@ export function diffDraftAgainstCurrent(input) {
139
140
  path: "task.json#featureId",
140
141
  });
141
142
  }
143
+ // backendTest is managed whole-object: an absent draft value means
144
+ // "keep existing" and never diffs; declared values compare canonically.
145
+ if (input.draft.backendTest !== undefined) {
146
+ const before = cfg.backendTest ?? {};
147
+ const after = input.draft.backendTest;
148
+ if (canonicalizeJsonValue(before) !== canonicalizeJsonValue(after)) {
149
+ changedManagedFields.push("backendTest");
150
+ risks.push({
151
+ code: "BACKEND_TEST_CONFIG_CHANGED",
152
+ level: "routine",
153
+ message: "backendTest configuration changed",
154
+ path: "task.json#backendTest",
155
+ });
156
+ const afterKeys = new Set(Object.keys(after));
157
+ const droppedKeys = Object.keys(before).filter((key) => !afterKeys.has(key));
158
+ if (droppedKeys.length > 0) {
159
+ risks.push({
160
+ code: "BACKEND_TEST_CONFIG_WEAKENED",
161
+ level: "elevated",
162
+ message: `drop backendTest keys: ${droppedKeys.sort().join(", ")}`,
163
+ path: "task.json#backendTest",
164
+ });
165
+ }
166
+ }
167
+ }
142
168
  }
143
169
  else {
144
170
  for (const field of MANAGED_TASK_CONFIG_FIELDS) {
@@ -174,6 +174,11 @@ export function mergeManagedTaskConfigFields(existing, draft) {
174
174
  if (draft.featureId !== undefined) {
175
175
  next.featureId = draft.featureId;
176
176
  }
177
+ // backendTest is whole-object replace when the draft declares it (`{}` is a
178
+ // legal explicit clear back to the default layout); absent keeps existing.
179
+ if (draft.backendTest !== undefined) {
180
+ next.backendTest = draft.backendTest;
181
+ }
177
182
  return next;
178
183
  }
179
184
  /**
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { taskKindSchema, taskVerifyCommandSchema } from "../config-types.js";
2
+ import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
3
3
  import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
4
4
  const sha256Schema = z
5
5
  .string()
@@ -62,6 +62,11 @@ export const taskContractDraftSchema = z
62
62
  requirement: taskContractDraftRequirementSchema,
63
63
  constraints: taskContractDraftConstraintsSchema,
64
64
  verification: taskContractDraftVerificationSchema,
65
+ /**
66
+ * Managed backend-test layout / pipeline configuration projected into
67
+ * task.json#backendTest. Optional: absent means "keep existing".
68
+ */
69
+ backendTest: backendTestConfigSchema,
65
70
  references: z.array(taskContractDraftReferenceSchema).optional(),
66
71
  sourceResolutions: z
67
72
  .array(taskContractDraftSourceResolutionSchema)
@@ -22,6 +22,18 @@ function resolveTaskKindExplicit(flags, baseDraft) {
22
22
  return flags.taskKindExplicit;
23
23
  return baseDraft?.taskKindExplicit ?? false;
24
24
  }
25
+ /**
26
+ * Resolve managed backend-test layout config.
27
+ * Precedence: explicit CLI flags > --from-draft base > existing task.json
28
+ * (the last tier self-heals tasks whose backendTest was hand-written).
29
+ */
30
+ function resolveBackendTest(flags, baseDraft, existing) {
31
+ if (flags.backendTest !== undefined)
32
+ return flags.backendTest;
33
+ if (baseDraft?.backendTest !== undefined)
34
+ return baseDraft.backendTest;
35
+ return existing?.backendTest ?? undefined;
36
+ }
25
37
  function resolveTitle(taskId, flags, facts, baseDraft, existing) {
26
38
  return (trimNonEmpty(flags.title) ??
27
39
  trimNonEmpty(baseDraft?.title) ??
@@ -205,6 +217,9 @@ export function buildPrepareDraft(input) {
205
217
  };
206
218
  if (featureId)
207
219
  draft.featureId = featureId;
220
+ const backendTest = resolveBackendTest(flags, baseDraft, existingTaskConfig);
221
+ if (backendTest !== undefined)
222
+ draft.backendTest = backendTest;
208
223
  if (references && references.length > 0) {
209
224
  draft.references = references.map((ref) => ({
210
225
  role: ref.role,
@@ -0,0 +1,91 @@
1
+ import { createHash } from "node:crypto";
2
+ const IMAGE_MIME_TYPES = new Set([
3
+ "image/png",
4
+ "image/jpeg",
5
+ "image/gif",
6
+ "image/webp",
7
+ ]);
8
+ export function hashDeferredTurnPayload(input) {
9
+ return createHash("sha256")
10
+ .update(JSON.stringify({
11
+ text: input.text,
12
+ images: input.images.map(({ type, mimeType, data }) => ({
13
+ type,
14
+ mimeType,
15
+ data,
16
+ })),
17
+ }))
18
+ .digest("hex");
19
+ }
20
+ export function parseDeferredTurn(value) {
21
+ if (!value || typeof value !== "object")
22
+ return undefined;
23
+ const item = value;
24
+ if (item.schemaVersion !== 1 ||
25
+ !Number.isSafeInteger(item.revision) ||
26
+ (item.revision ?? 0) < 1 ||
27
+ typeof item.clientRequestId !== "string" ||
28
+ !item.clientRequestId.trim() ||
29
+ typeof item.payloadHash !== "string" ||
30
+ !/^[a-f0-9]{64}$/.test(item.payloadHash) ||
31
+ typeof item.blockedByTurnId !== "string" ||
32
+ !item.blockedByTurnId.trim() ||
33
+ typeof item.text !== "string" ||
34
+ !Array.isArray(item.images) ||
35
+ !["queued", "dispatching", "accepted", "failed"].includes(item.state) ||
36
+ typeof item.createdAt !== "string" ||
37
+ typeof item.updatedAt !== "string") {
38
+ return undefined;
39
+ }
40
+ const images = [];
41
+ for (const image of item.images) {
42
+ if (!image || typeof image !== "object")
43
+ return undefined;
44
+ const candidate = image;
45
+ if (typeof candidate.name !== "string" ||
46
+ candidate.type !== "image" ||
47
+ typeof candidate.mimeType !== "string" ||
48
+ !IMAGE_MIME_TYPES.has(candidate.mimeType) ||
49
+ typeof candidate.data !== "string") {
50
+ return undefined;
51
+ }
52
+ images.push(candidate);
53
+ }
54
+ return {
55
+ schemaVersion: 1,
56
+ revision: item.revision,
57
+ clientRequestId: item.clientRequestId.trim(),
58
+ payloadHash: item.payloadHash,
59
+ blockedByTurnId: item.blockedByTurnId.trim(),
60
+ text: item.text,
61
+ images,
62
+ state: item.state,
63
+ createdAt: item.createdAt,
64
+ updatedAt: item.updatedAt,
65
+ ...(typeof item.acceptedTurnId === "string" && item.acceptedTurnId
66
+ ? { acceptedTurnId: item.acceptedTurnId }
67
+ : {}),
68
+ ...(item.lastError &&
69
+ typeof item.lastError.code === "string" &&
70
+ typeof item.lastError.message === "string"
71
+ ? { lastError: { code: item.lastError.code, message: item.lastError.message } }
72
+ : {}),
73
+ };
74
+ }
75
+ export function projectDeferredTurnSummary(deferredTurn) {
76
+ if (!deferredTurn)
77
+ return null;
78
+ return {
79
+ revision: deferredTurn.revision,
80
+ clientRequestId: deferredTurn.clientRequestId,
81
+ blockedByTurnId: deferredTurn.blockedByTurnId,
82
+ state: deferredTurn.state,
83
+ updatedAt: deferredTurn.updatedAt,
84
+ textPreview: deferredTurn.text.replace(/\s+/g, " ").trim().slice(0, 120),
85
+ imageCount: deferredTurn.images.length,
86
+ ...(deferredTurn.acceptedTurnId
87
+ ? { acceptedTurnId: deferredTurn.acceptedTurnId }
88
+ : {}),
89
+ ...(deferredTurn.lastError ? { lastError: deferredTurn.lastError } : {}),
90
+ };
91
+ }
@@ -228,19 +228,24 @@ async function buildCustomOperatorTools(options) {
228
228
  // TypeBox schema is built dynamically; cast through unknown to the builder
229
229
  // since Static<TParams> is structural and cannot be statically inferred here.
230
230
  const parameters = Type.Object(schema.inputParams.reduce((acc, param) => {
231
+ // Carry the registry's param description into the tool schema so
232
+ // the model sees parameter semantics, not just names/types.
233
+ const desc = param.description?.trim()
234
+ ? { description: param.description }
235
+ : {};
231
236
  let t;
232
237
  switch (param.type) {
233
238
  case "string":
234
- t = Type.String();
239
+ t = Type.String(desc);
235
240
  break;
236
241
  case "boolean":
237
- t = Type.Boolean();
242
+ t = Type.Boolean(desc);
238
243
  break;
239
244
  case "number":
240
- t = Type.Number();
245
+ t = Type.Number(desc);
241
246
  break;
242
247
  case "array":
243
- t = Type.Array(Type.String());
248
+ t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
244
249
  break;
245
250
  default:
246
251
  t = Type.Object({}, { additionalProperties: true });
@@ -252,7 +257,7 @@ async function buildCustomOperatorTools(options) {
252
257
  name: schema.toolId,
253
258
  label: schema.toolId,
254
259
  description: `${schema.description} (kind=${schema.kind}; cli: ${schema.cli})`,
255
- promptSnippet: `${schema.toolId}: ${schema.description}`,
260
+ promptSnippet: schema.description,
256
261
  parameters: parameters,
257
262
  async execute(toolCallId, params) {
258
263
  const decision = authorizeOperatorChatTool(schema.toolId);