@tea-agent/loop-agent 0.39.0-next.2 → 0.39.0-next.20

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 (121) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/dist/build-stamp.json +2 -2
  3. package/dist/commands/init-upgrade.js +97 -19
  4. package/dist/executors/dag-pi-executor.js +42 -4
  5. package/dist/executors/shell-executor.js +269 -15
  6. package/dist/shared/operator/capabilities.js +149 -27
  7. package/dist/task/config-types.js +22 -0
  8. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  9. package/dist/worker/console/chat/pi-runtime.js +165 -83
  10. package/dist/worker/console/chat/repo-walk.js +10 -1
  11. package/dist/worker/console/chat/routes.js +19 -8
  12. package/dist/worker/console/chat/semantic-activity.js +17 -5
  13. package/dist/worker/console/chat/turn-process.js +17 -30
  14. package/dist/worker/console/harness-pi-model-matrix.js +137 -0
  15. package/dist/worker/console/operator-actions.js +128 -4
  16. package/dist/worker/console/operator-user-error.js +19 -0
  17. package/dist/worker/console/pi-readiness.js +8 -1
  18. package/dist/worker/console/security.js +20 -1
  19. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-BmOEx0gA.js} +1 -1
  20. package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-BGiVVe8b.js} +1 -1
  21. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-nDdu4rgJ.js} +1 -1
  22. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-Bb5u0BWf.js} +1 -1
  23. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-BlhC0j94.js} +1 -1
  24. package/dist/worker/console/static/assets/channel-Tl-FkUqr.js +1 -0
  25. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-Ai8_qQXI.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-B9gyCjqX.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-Bzbj3zGH.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BT415PvT.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-D_A5_5QL.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-srJkaP3f.js} +1 -1
  31. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-UYGvEP4l.js} +1 -1
  32. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-B8luBrrz.js} +1 -1
  33. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-DN7wXZSq.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-DXKyMuKS.js} +1 -1
  35. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BaehtRk4.js +1 -0
  36. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BaehtRk4.js +1 -0
  37. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-dqvoynXj.js} +1 -1
  38. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2--PcyL34A.js} +1 -1
  39. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DvWs_hTY.js} +1 -1
  40. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-DxeIxbyO.js} +1 -1
  41. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-DPCG61Of.js} +1 -1
  42. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CmuU50-U.js} +1 -1
  43. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-B61XLD_v.js} +1 -1
  44. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-BB7HTRRY.js} +1 -1
  45. package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-DvkboAIp.js} +1 -1
  46. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-BdJ_y1fL.js} +1 -1
  47. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-9bUBZTRi.js} +1 -1
  48. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-Dw4L4RBa.js} +1 -1
  49. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-Jr82N_QP.js} +1 -1
  50. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-DAz9wFLr.js} +1 -1
  51. package/dist/worker/console/static/assets/index-B7Y-0g3J.css +1 -0
  52. package/dist/worker/console/static/assets/index-DkCrqaYg.js +325 -0
  53. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-CBB5Np7y.js} +1 -1
  54. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-D8IOp94y.js} +1 -1
  55. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-Dgd3AHGG.js} +1 -1
  56. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-BmYOxLE9.js} +1 -1
  57. package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-DfYTEF6J.js} +1 -1
  58. package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-BjuFPRqa.js} +5 -5
  59. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-CLyUViGd.js} +1 -1
  60. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-BvX2Di4-.js} +1 -1
  61. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-BFj0_OLv.js} +1 -1
  62. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-BIUrD_hx.js} +1 -1
  63. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-CHBB48fj.js} +1 -1
  64. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-sJrYw2Sf.js} +1 -1
  65. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-BzHH6fPR.js} +1 -1
  66. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-7QCnelCt.js} +1 -1
  67. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-sDYSxn72.js} +1 -1
  68. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-XpiUdwzm.js} +1 -1
  69. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-eEhq2A8Y.js +1 -0
  70. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DvqgNPzD.js} +2 -2
  71. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-la8AC7fo.js +8 -0
  72. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-2TyOYNx0.js} +1 -1
  73. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-B-nM6wM2.js} +1 -1
  74. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-B86wPdbr.js} +1 -1
  75. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-Dr1C4AQz.js} +1 -1
  76. package/dist/worker/console/static/index.html +2 -2
  77. package/dist/worker/console/static-src/app/console-types.js +39 -0
  78. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +44 -21
  79. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
  80. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  81. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +15 -0
  82. package/dist/worker/console/static-src/operator-chat/useChatStream.js +17 -4
  83. package/dist/worker/console/static-src/operator-chat/useChatThread.js +28 -22
  84. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  85. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +755 -51
  86. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  87. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  88. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  89. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  90. package/dist/workflows/dag/backend-test-layout.js +133 -0
  91. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  92. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  93. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  94. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  95. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  96. package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
  97. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  98. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  99. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  100. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  101. package/dist/workflows/dag/frontend-implementation-contract.js +386 -0
  102. package/dist/workflows/dag/frontend-plan-render.js +90 -0
  103. package/dist/workflows/dag/frontend-prewrite-gate.js +243 -8
  104. package/dist/workflows/dag/init-hybrid.js +753 -104
  105. package/dist/workflows/dag/node-execution.js +61 -2
  106. package/dist/workflows/dag/retry-policy.js +4 -0
  107. package/dist/workflows/dag/types.js +74 -0
  108. package/docs/templates/backend-test-dag.json +60 -35
  109. package/docs/templates/frontend-implementation-contract.schema.json +9 -2
  110. package/harness.json +3 -1
  111. package/package.json +1 -1
  112. package/skills/frontend-design-review/references/review-checklist.md +2 -0
  113. package/skills/frontend-implementation/references/design-spec.md +16 -19
  114. package/skills/frontend-implementation/references/node-contracts.md +9 -6
  115. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  116. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  117. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  118. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  119. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  120. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  121. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.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",
@@ -63,8 +96,6 @@ const officialAction = (action, cli, kind, coverage) => ({
63
96
  });
64
97
  const OFFICIAL_ACTIONS = [
65
98
  ...[
66
- ["dagDoctor", "loop-agent dag doctor", "read", "model-callable"],
67
- ["dagStatus", "loop-agent dag status", "read", "model-callable"],
68
99
  [
69
100
  "dagDecisionInspect",
70
101
  "loop-agent dag decision inspect",
@@ -160,8 +191,8 @@ const OFFICIAL_ACTIONS = [
160
191
  [
161
192
  "knowledgeCurate",
162
193
  "loop-agent knowledge curate",
163
- "read",
164
- "model-callable",
194
+ "mutation",
195
+ "advanced",
165
196
  ],
166
197
  ["knowledgeQuery", "loop-agent knowledge query", "read", "model-callable"],
167
198
  [
@@ -284,7 +315,6 @@ const OFFICIAL_ACTIONS = [
284
315
  "long-running",
285
316
  "advanced",
286
317
  ],
287
- ["workerPoolDoctor", "agent-worker pool doctor", "read", "model-callable"],
288
318
  [
289
319
  "workerPoolMarkFailed",
290
320
  "agent-worker pool mark-failed",
@@ -337,6 +367,12 @@ const OFFICIAL_ACTIONS = [
337
367
  ],
338
368
  ],
339
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));
340
376
  /** Shared constants so dag + dagSpecVersions cannot diverge. */
341
377
  const DAG_SPEC_SUPPORTED_PARSE = [1, 2, 3, 4];
342
378
  const DAG_SPEC_NEW_WRITER_DEFAULT = 4;
@@ -612,7 +648,7 @@ export function buildOperatorCapabilitiesDocument() {
612
648
  resultSchemaVersion: 1,
613
649
  envelopeSchemaVersion: 1,
614
650
  requiredErrorCodes: [...COMMON_MUTATION_ERRORS],
615
- 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.",
616
652
  inputParams: [
617
653
  {
618
654
  name: "taskId",
@@ -688,7 +724,7 @@ export function buildOperatorCapabilitiesDocument() {
688
724
  resultSchemaVersion: 1,
689
725
  envelopeSchemaVersion: 1,
690
726
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
691
- 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.",
692
728
  inputParams: [
693
729
  {
694
730
  name: "taskId",
@@ -838,8 +874,33 @@ export function buildOperatorCapabilitiesDocument() {
838
874
  resultSchemaVersion: 1,
839
875
  envelopeSchemaVersion: 1,
840
876
  requiredErrorCodes: ["INVALID_INPUT"],
841
- description: "Build Observe deep link from stable object identity.",
842
- 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
+ ],
843
904
  modelCallable: "always",
844
905
  humanConfirmation: "none",
845
906
  },
@@ -851,7 +912,7 @@ export function buildOperatorCapabilitiesDocument() {
851
912
  resultSchemaVersion: 1,
852
913
  envelopeSchemaVersion: 1,
853
914
  requiredErrorCodes: ["INVALID_INPUT"],
854
- 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.",
855
916
  inputParams: [],
856
917
  modelCallable: "always",
857
918
  humanConfirmation: "none",
@@ -910,7 +971,7 @@ export function buildOperatorCapabilitiesDocument() {
910
971
  resultSchemaVersion: 1,
911
972
  envelopeSchemaVersion: 1,
912
973
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
913
- 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.",
914
975
  inputParams: [
915
976
  {
916
977
  name: "taskId",
@@ -930,7 +991,7 @@ export function buildOperatorCapabilitiesDocument() {
930
991
  resultSchemaVersion: 1,
931
992
  envelopeSchemaVersion: 1,
932
993
  requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
933
- 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.",
934
995
  inputParams: [
935
996
  {
936
997
  name: "taskId",
@@ -1023,7 +1084,7 @@ export function buildOperatorCapabilitiesDocument() {
1023
1084
  "EXTERNALLY_MODIFIED",
1024
1085
  "INVALID_INPUT",
1025
1086
  ],
1026
- 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.",
1027
1088
  inputParams: [
1028
1089
  {
1029
1090
  name: "taskId",
@@ -1079,7 +1140,7 @@ export function buildOperatorCapabilitiesDocument() {
1079
1140
  "CONTROLLER_MISMATCH",
1080
1141
  "INVALID_INPUT",
1081
1142
  ],
1082
- description: "Consume a single-use execution receipt and execute the reviewed DAG (prefer task advance --approve-gate when gate token present). accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor/operationWait.",
1143
+ description: "Consume a single-use executionId from prepareDagExecution and execute the reviewed DAG. Do not pass a DAG path and do not bash `task advance --approve-gate`. accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor/operationWait.",
1083
1144
  inputParams: [
1084
1145
  {
1085
1146
  name: "executionId",
@@ -1102,7 +1163,7 @@ export function buildOperatorCapabilitiesDocument() {
1102
1163
  resultSchemaVersion: 1,
1103
1164
  envelopeSchemaVersion: 1,
1104
1165
  requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
1105
- description: "Read-only R1 subgraph plan (no mutation). Call before dagRerun. Prefer primaryFailure.nodeId from dagReport. If eligible=false (writer/decision/fingerprint, unsafe shell, worker-managed, workspace/controller drift), do not force execute — Worker-owned runs use workerTaskRetry; standalone runs use standaloneTaskRerun or same-task advance after fixing the root cause (contract changes must be adopted first). Returns planHash required by execute.",
1166
+ 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.",
1106
1167
  inputParams: [
1107
1168
  {
1108
1169
  name: "runId",
@@ -1132,7 +1193,7 @@ export function buildOperatorCapabilitiesDocument() {
1132
1193
  "BINDING_DRIFT",
1133
1194
  "INVALID_INPUT",
1134
1195
  ],
1135
- description: "Execute R1 continuation from effective node. Requires planHash from a fresh dagRerunPlan (eligible=true). Prefer this over new task / standaloneTaskRerun for provider flake and safe downstream failures. Server enforces planHash/binding/fingerprint freshness.",
1196
+ 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.",
1136
1197
  inputParams: [
1137
1198
  {
1138
1199
  name: "runId",
@@ -1498,7 +1559,7 @@ export function buildOperatorCapabilitiesDocument() {
1498
1559
  "INVALID_INPUT",
1499
1560
  "HUMAN_CONFIRMATION_REQUIRED",
1500
1561
  ],
1501
- description: "Full standalone task regenerate validate execute with parent lineage. Only when node rerun plan is ineligible or primaryRecovery recommends rerun-task; the server checks read-only run facts before accepting. Regeneration reuses the CURRENT contract snapshot: if the failure requires contract changes (e.g. allowing a catalog index path), first update the contract (re-import PRD, or adopt externally-edited sources via `task contract adopt`) BEFORE calling this action, otherwise the regenerated DAG repeats the same failure.",
1562
+ 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.",
1502
1563
  inputParams: [
1503
1564
  {
1504
1565
  name: "runId",
@@ -1542,7 +1603,7 @@ export function buildOperatorCapabilitiesDocument() {
1542
1603
  "INVALID_INPUT",
1543
1604
  "HUMAN_CONFIRMATION_REQUIRED",
1544
1605
  ],
1545
- description: "Requeue failed Task Pool task (Failed → Ready) via in-package pool store. Only when read-only validation confirms the task exists in Failed state; the server checks this before accepting.",
1606
+ description: "Requeue a Worker Task Pool task Failed → Ready. Not a DAG node rerun (use dagRerun) and not a standalone DAG regenerate (use standaloneTaskRerun). Confirm with workerPoolDoctor first; server accepts only Failed pool tasks. Pass featureId when known it must match the pool record.",
1546
1607
  inputParams: [
1547
1608
  {
1548
1609
  name: "taskId",
@@ -1568,6 +1629,66 @@ export function buildOperatorCapabilitiesDocument() {
1568
1629
  modelCallable: "always",
1569
1630
  humanConfirmation: "none",
1570
1631
  },
1632
+ {
1633
+ action: "dagDoctor",
1634
+ cli: "loop-agent dag doctor [--run-id <id>] --json",
1635
+ kind: "read",
1636
+ inputSchemaVersion: 1,
1637
+ resultSchemaVersion: 1,
1638
+ envelopeSchemaVersion: 1,
1639
+ requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
1640
+ description: "Diagnose DAG run health. Omit runId for a repo-wide inventory (active/paused/completed + health issues). Pass runId for one run's liveness, heartbeat, failure node, and recovery hints. Use this to supervise stalls; it does not mutate the run.",
1641
+ inputParams: [
1642
+ {
1643
+ name: "runId",
1644
+ type: "string",
1645
+ required: false,
1646
+ description: "optional dag run id; omit to inventory all runs, pass to diagnose one run",
1647
+ },
1648
+ ],
1649
+ modelCallable: "always",
1650
+ humanConfirmation: "none",
1651
+ },
1652
+ {
1653
+ action: "dagStatus",
1654
+ cli: "loop-agent dag status --run-id <id>",
1655
+ kind: "read",
1656
+ inputSchemaVersion: 1,
1657
+ resultSchemaVersion: 1,
1658
+ envelopeSchemaVersion: 1,
1659
+ requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
1660
+ description: "Read one DAG run's live status, current node, and liveness. Requires runId. Complements dagReport (terminal handoff) and dagDoctor (health diagnosis). Does not mutate the run.",
1661
+ inputParams: [
1662
+ {
1663
+ name: "runId",
1664
+ type: "string",
1665
+ required: true,
1666
+ description: "dag run id",
1667
+ },
1668
+ ],
1669
+ modelCallable: "always",
1670
+ humanConfirmation: "none",
1671
+ },
1672
+ {
1673
+ action: "workerPoolDoctor",
1674
+ cli: "agent-worker pool doctor --repo . --json",
1675
+ kind: "read",
1676
+ inputSchemaVersion: 1,
1677
+ resultSchemaVersion: 1,
1678
+ envelopeSchemaVersion: 1,
1679
+ requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
1680
+ description: "Read-only Task Pool inventory (legacy + v2). Use before workerTaskRetry to confirm the task exists and status is Failed. Optional taskId filters the returned inventory to that task.",
1681
+ inputParams: [
1682
+ {
1683
+ name: "taskId",
1684
+ type: "string",
1685
+ required: false,
1686
+ description: "optional task id to focus the inventory",
1687
+ },
1688
+ ],
1689
+ modelCallable: "always",
1690
+ humanConfirmation: "none",
1691
+ },
1571
1692
  ...OFFICIAL_ACTIONS,
1572
1693
  ];
1573
1694
  return {
@@ -1947,12 +2068,12 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
1947
2068
  },
1948
2069
  {
1949
2070
  command: "loop-agent knowledge curate",
1950
- coverage: "model-callable",
2071
+ coverage: "advanced",
1951
2072
  action: "knowledgeCurate",
1952
2073
  source: "loop-agent",
2074
+ note: "Writes knowledge proposals (conditional on --output); not model-callable.",
1953
2075
  },
1954
- {
1955
- command: "loop-agent knowledge query",
2076
+ { command: "loop-agent knowledge query",
1956
2077
  coverage: "model-callable",
1957
2078
  action: "knowledgeQuery",
1958
2079
  source: "loop-agent",
@@ -2527,9 +2648,10 @@ export function assertCoverageManifestConsistent() {
2527
2648
  capability.humanConfirmation !== "required"))
2528
2649
  throw new Error(`invalid required gate classification: ${entry.action}`);
2529
2650
  if (entry.coverage === "advanced") {
2530
- const expected = capability.kind === "read" ? "always" : "never";
2531
- if (capability.modelCallable !== expected) {
2532
- 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)`);
2533
2655
  }
2534
2656
  }
2535
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) => {
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Read-only MCP inventory for Operator Chat.
3
+ *
4
+ * Pi has no built-in MCP catalog. Console projects two real facts:
5
+ * 1. mcp.json server specs (configured, may or may not have registered tools)
6
+ * 2. tools already on the live session that look like MCP registrations
7
+ *
8
+ * Never fabricate servers or tools. Missing files stay empty.
9
+ */
10
+ import { readFile } from "node:fs/promises";
11
+ import path from "node:path";
12
+ const MCP_CONFIG_NAMES = ["mcp.json"];
13
+ export function isMcpToolName(name) {
14
+ const lower = name.trim().toLowerCase();
15
+ return lower === "mcp" || lower.startsWith("mcp_") || lower.startsWith("mcp-");
16
+ }
17
+ export function inferMcpServerName(toolName, knownServers = []) {
18
+ const name = toolName.trim();
19
+ if (/^mcp$/i.test(name))
20
+ return undefined;
21
+ const rest = name.replace(/^mcp[_-]/i, "");
22
+ if (!rest || rest === name || knownServers.length === 0)
23
+ return undefined;
24
+ const ranked = [...knownServers].sort((left, right) => right.length - left.length);
25
+ const lower = rest.toLowerCase();
26
+ return ranked.find((server) => {
27
+ const key = server.toLowerCase();
28
+ return lower === key || lower.startsWith(`${key}_`) || lower.startsWith(`${key}-`);
29
+ });
30
+ }
31
+ export function looksLikeMcpTool(tool) {
32
+ const name = typeof tool.name === "string" ? tool.name : "";
33
+ if (!name)
34
+ return false;
35
+ if (isMcpToolName(name))
36
+ return true;
37
+ const source = `${tool.sourceInfo?.source ?? ""} ${tool.sourceInfo?.path ?? ""}`.toLowerCase();
38
+ return source.includes("mcp");
39
+ }
40
+ function asRecord(value) {
41
+ return value && typeof value === "object" && !Array.isArray(value)
42
+ ? value
43
+ : undefined;
44
+ }
45
+ function readEnabled(entry) {
46
+ return entry.enabled !== false;
47
+ }
48
+ function readTransport(entry) {
49
+ if (typeof entry.transport === "string" && entry.transport.trim()) {
50
+ return entry.transport.trim();
51
+ }
52
+ if (typeof entry.type === "string" && entry.type.trim()) {
53
+ return entry.type.trim();
54
+ }
55
+ if (typeof entry.url === "string")
56
+ return "http";
57
+ if (Array.isArray(entry.command) || typeof entry.command === "string") {
58
+ return "stdio";
59
+ }
60
+ return undefined;
61
+ }
62
+ function readCommand(entry) {
63
+ if (typeof entry.command === "string" && entry.command.trim()) {
64
+ const args = Array.isArray(entry.args)
65
+ ? entry.args.filter((item) => typeof item === "string").join(" ")
66
+ : "";
67
+ return args ? `${entry.command} ${args}` : entry.command;
68
+ }
69
+ if (Array.isArray(entry.command)) {
70
+ return entry.command
71
+ .filter((item) => typeof item === "string")
72
+ .join(" ")
73
+ .trim();
74
+ }
75
+ return undefined;
76
+ }
77
+ function extractServerMap(parsed) {
78
+ const root = asRecord(parsed);
79
+ if (!root)
80
+ return {};
81
+ const mcpServers = asRecord(root.mcpServers);
82
+ if (mcpServers)
83
+ return mcpServers;
84
+ const mcp = asRecord(root.mcp);
85
+ if (mcp)
86
+ return mcp;
87
+ const servers = asRecord(root.servers);
88
+ if (servers)
89
+ return servers;
90
+ return {};
91
+ }
92
+ export function parseMcpConfig(raw, scope, configPath) {
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(raw);
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ const servers = [];
101
+ for (const [name, value] of Object.entries(extractServerMap(parsed))) {
102
+ if (!name.trim())
103
+ continue;
104
+ const entry = asRecord(value) ?? {};
105
+ const command = readCommand(entry);
106
+ const url = typeof entry.url === "string" ? entry.url.trim() : undefined;
107
+ servers.push({
108
+ name: name.trim(),
109
+ scope,
110
+ enabled: readEnabled(entry),
111
+ ...(readTransport(entry) ? { transport: readTransport(entry) } : {}),
112
+ ...(command ? { command } : {}),
113
+ ...(url ? { url } : {}),
114
+ configPath,
115
+ });
116
+ }
117
+ return servers;
118
+ }
119
+ export function projectSessionMcpTools(tools, activeToolNames = [], knownServers = []) {
120
+ const active = new Set(activeToolNames.map((name) => name.toLowerCase()));
121
+ const seen = new Set();
122
+ const projected = [];
123
+ for (const tool of tools) {
124
+ if (!looksLikeMcpTool(tool) || typeof tool.name !== "string")
125
+ continue;
126
+ const name = tool.name.trim();
127
+ if (!name || seen.has(name))
128
+ continue;
129
+ seen.add(name);
130
+ const server = inferMcpServerName(name, knownServers);
131
+ projected.push({
132
+ name,
133
+ description: typeof tool.description === "string" ? tool.description : "",
134
+ ...(server ? { server } : {}),
135
+ source: tool.sourceInfo?.source ?? "",
136
+ scope: tool.sourceInfo?.scope ?? "session",
137
+ ...(tool.sourceInfo?.path ? { path: tool.sourceInfo.path } : {}),
138
+ active: active.has(name.toLowerCase()),
139
+ });
140
+ }
141
+ return projected;
142
+ }
143
+ export function assembleMcpInventory(servers, tools) {
144
+ const byServer = new Map();
145
+ const unassigned = [];
146
+ for (const tool of tools) {
147
+ const key = tool.server?.toLowerCase();
148
+ if (!key) {
149
+ unassigned.push(tool);
150
+ continue;
151
+ }
152
+ const list = byServer.get(key) ?? [];
153
+ list.push(tool);
154
+ byServer.set(key, list);
155
+ }
156
+ const views = servers.map((server) => {
157
+ const matched = byServer.get(server.name.toLowerCase()) ?? [];
158
+ byServer.delete(server.name.toLowerCase());
159
+ const status = !server.enabled
160
+ ? "disabled"
161
+ : matched.length > 0
162
+ ? "loaded"
163
+ : "configured";
164
+ return {
165
+ name: server.name,
166
+ scope: server.scope,
167
+ status,
168
+ enabled: server.enabled,
169
+ ...(server.transport ? { transport: server.transport } : {}),
170
+ ...(server.command ? { command: server.command } : {}),
171
+ ...(server.url ? { url: server.url } : {}),
172
+ configPath: server.configPath,
173
+ tools: matched,
174
+ };
175
+ });
176
+ for (const [name, matched] of byServer) {
177
+ views.push({
178
+ name,
179
+ scope: "session",
180
+ status: "loaded",
181
+ enabled: true,
182
+ tools: matched,
183
+ });
184
+ }
185
+ return { servers: views, unassignedTools: unassigned };
186
+ }
187
+ async function readConfigFile(filePath) {
188
+ try {
189
+ return await readFile(filePath, "utf8");
190
+ }
191
+ catch {
192
+ return undefined;
193
+ }
194
+ }
195
+ export async function loadMcpServerSpecs(options) {
196
+ const candidates = [
197
+ { path: path.join(options.agentDir, ...MCP_CONFIG_NAMES), scope: "user" },
198
+ { path: path.join(options.cwd, ".pi", ...MCP_CONFIG_NAMES), scope: "project" },
199
+ { path: path.join(options.cwd, ".mcp.json"), scope: "project" },
200
+ ];
201
+ const servers = [];
202
+ const seen = new Set();
203
+ for (const candidate of candidates) {
204
+ const raw = await readConfigFile(candidate.path);
205
+ if (!raw)
206
+ continue;
207
+ for (const server of parseMcpConfig(raw, candidate.scope, candidate.path)) {
208
+ const key = `${server.scope}:${server.name.toLowerCase()}`;
209
+ if (seen.has(key))
210
+ continue;
211
+ seen.add(key);
212
+ servers.push(server);
213
+ }
214
+ }
215
+ return servers;
216
+ }
217
+ export async function readMcpInventory(options) {
218
+ const servers = await loadMcpServerSpecs(options);
219
+ const tools = projectSessionMcpTools(options.tools ?? [], options.activeToolNames ?? [], servers.map((server) => server.name));
220
+ return assembleMcpInventory(servers, tools);
221
+ }