@tea-agent/loop-agent 0.43.0-next.17 → 0.43.0-next.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 (187) hide show
  1. package/CHANGELOG.md +9 -140
  2. package/dist/application/task-lifecycle/advance.js +4 -17
  3. package/dist/application/task-lifecycle/observe.js +29 -43
  4. package/dist/application/task-lifecycle/plan-transitions.js +4 -5
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/commands/client-recovery.js +55 -20
  7. package/dist/commands/new-task.js +5 -0
  8. package/dist/commands/status.js +57 -0
  9. package/dist/commands/task-contract.js +269 -0
  10. package/dist/executors/dag-pi-executor.js +230 -269
  11. package/dist/executors/pi-executor.js +0 -2
  12. package/dist/executors/pi-sdk-executor.js +39 -77
  13. package/dist/executors/shell-executor.js +2 -3
  14. package/dist/executors/shell-write-guard.js +32 -27
  15. package/dist/shared/pi-context-pressure/extension.js +16 -26
  16. package/dist/shared/pi-retry-settings.js +0 -40
  17. package/dist/shared/prompts.js +26 -0
  18. package/dist/task/config.js +2 -0
  19. package/dist/task/contract/validate-draft.js +106 -0
  20. package/dist/task/goal.js +3 -0
  21. package/dist/task/index.js +12 -0
  22. package/dist/task/lifecycle.js +1 -0
  23. package/dist/task/operator/capabilities.js +6 -0
  24. package/dist/task/operator/envelope.js +2 -0
  25. package/dist/task/operator/index.js +5 -0
  26. package/dist/task/operator/registry.js +2 -0
  27. package/dist/task/operator/types.js +1 -0
  28. package/dist/task/paths.js +1 -0
  29. package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
  30. package/dist/task/source-references.js +23 -48
  31. package/dist/task/source-state.js +1 -0
  32. package/dist/task/subagent-guidance.js +1 -0
  33. package/dist/worker/console/chat/assistant-content.js +2 -23
  34. package/dist/worker/console/chat/explore-tools.js +0 -13
  35. package/dist/worker/console/chat/pi-runtime.js +1182 -221
  36. package/dist/worker/console/chat/routes.js +71 -213
  37. package/dist/worker/console/chat/sdd-data-alignment.js +9 -65
  38. package/dist/worker/console/chat/session-catalog.js +0 -32
  39. package/dist/worker/console/chat/session-store.js +6 -22
  40. package/dist/worker/console/chat/shortcuts.js +0 -6
  41. package/dist/worker/console/chat/sift-bridge.js +1 -0
  42. package/dist/worker/console/chat/tool-preview.js +0 -11
  43. package/dist/worker/console/chat/tools.js +0 -6
  44. package/dist/worker/console/console-update-and-init.js +19 -1
  45. package/dist/worker/console/console-update-runtime.js +20 -5
  46. package/dist/worker/console/index.js +20 -0
  47. package/dist/worker/console/operator-actions.js +1 -13
  48. package/dist/worker/console/routes.js +0 -8
  49. package/dist/worker/console/server.js +29 -11
  50. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-hUmM_GRf.js} +1 -1
  51. package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-Bt-xuieW.js} +1 -1
  52. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DTqkL4DJ.js} +1 -1
  53. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-Cy0MNnzV.js} +1 -1
  54. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DDdlT0Pd.js} +1 -1
  55. package/dist/worker/console/static/assets/channel-BpzRnZmU.js +1 -0
  56. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-3JawwNZ6.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-DoTp7Cri.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-CcirhZaS.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-VrG21gnP.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DDOgIS70.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CBjmgvJ_.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CCQSht8M.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-BpGMkUUV.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DNDT41QL.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-euXnDFbn.js} +1 -1
  66. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +1 -0
  67. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +1 -0
  68. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BJ6NAeql.js} +1 -1
  69. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-F5JKIvIq.js} +1 -1
  70. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-Cwmkl_Gi.js} +1 -1
  71. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-BmCZ8815.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-zWA3hUnX.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-BzA-888F.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-tPNheoch.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-Cdi7Fyqp.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-DFK9VBf1.js} +1 -1
  77. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-xCUr1kpg.js} +1 -1
  78. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-BANQHY_A.js} +1 -1
  79. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-VHJYEMkP.js} +1 -1
  80. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-DiyZQ0C6.js} +1 -1
  81. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-CZtIeBBa.js} +1 -1
  82. package/dist/worker/console/static/assets/index-BlN6ytLr.css +1 -0
  83. package/dist/worker/console/static/assets/index-CA-jfWlE.js +449 -0
  84. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BuSspC0X.js} +1 -1
  85. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DRJZyPFy.js} +1 -1
  86. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-DIfXIVuE.js} +1 -1
  87. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-CT10_7Lt.js} +1 -1
  88. package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-ufemj74H.js} +1 -1
  89. package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DEQe8-_m.js} +5 -5
  90. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-BRLUrbrC.js} +1 -1
  91. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BgibKtiF.js} +1 -1
  92. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-D3eyfdV9.js} +1 -1
  93. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DXeTk5Px.js} +1 -1
  94. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BgXdFEe7.js} +1 -1
  95. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-CseC2A7R.js} +1 -1
  96. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-D80PFzfJ.js} +1 -1
  97. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BWzHW7LS.js} +1 -1
  98. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-xWQYTLAn.js} +1 -1
  99. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-CLxtpZeL.js} +1 -1
  100. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +1 -0
  101. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-DwugtmWV.js} +2 -2
  102. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +8 -0
  103. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-DHEOwHIe.js} +1 -1
  104. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-pmbZKlBK.js} +1 -1
  105. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-CivHoM5q.js} +1 -1
  106. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BfHIE7o6.js} +1 -1
  107. package/dist/worker/console/static/index.html +2 -2
  108. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +10 -122
  109. package/dist/worker/console/static-src/operator-chat/compaction-message.js +0 -7
  110. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +15 -35
  111. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +62 -136
  112. package/dist/worker/console/static-src/operator-chat/useChatStream.js +68 -159
  113. package/dist/worker/console/static-src/operator-chat/useChatThread.js +56 -75
  114. package/dist/worker/console/static-src/operator-chat/useComposer.js +78 -206
  115. package/dist/worker/console/workspace-context.js +1 -21
  116. package/dist/worker/observe/static/router.js +1 -1
  117. package/dist/worker/observe/static/styles.css +4 -7
  118. package/dist/worker/observe/static/views/dag-inspector.js +19 -12
  119. package/dist/worker/observe/static/views/dag.js +2 -2
  120. package/dist/worker/scheduler/index.js +25 -0
  121. package/dist/workflows/dag/facts.js +4 -0
  122. package/dist/workflows/dag/frontend-closeout.js +1 -1
  123. package/dist/workflows/dag/frontend-design-policy.js +1 -2
  124. package/dist/workflows/dag/frontend-implementation-contract.js +4 -8
  125. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  126. package/dist/workflows/dag/frontend-repair.js +76 -55
  127. package/dist/workflows/dag/frontend-review-context.js +1 -2
  128. package/dist/workflows/dag/frontend-review-scopes.js +12 -34
  129. package/dist/workflows/dag/frontend-shape-facts.js +2 -12
  130. package/dist/workflows/dag/frontend-verification-trace.js +42 -8
  131. package/dist/workflows/dag/index.js +6 -0
  132. package/dist/workflows/dag/init-hybrid.js +160 -97
  133. package/dist/workflows/dynamic/index.js +7 -0
  134. package/docs/architecture/runtime-boundaries.md +3 -3
  135. package/docs/templates/frontend-implementation-dag.json +8 -9
  136. package/package.json +3 -7
  137. package/skills/frontend-bounded-implement/SKILL.md +3 -4
  138. package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
  139. package/skills/frontend-contract/references/contract-protocol.md +1 -3
  140. package/skills/frontend-design-review/SKILL.md +7 -12
  141. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  142. package/skills/frontend-plan/SKILL.md +0 -19
  143. package/skills/frontend-plan/references/decision-contract.md +19 -26
  144. package/skills/frontend-review/SKILL.md +18 -12
  145. package/skills/frontend-review/references/review-findings.md +43 -24
  146. package/dist/shared/pi-provider-recovery.js +0 -182
  147. package/dist/worker/console/chat/browser-automation.js +0 -363
  148. package/dist/worker/console/chat/browser-policy.js +0 -113
  149. package/dist/worker/console/chat/browser-routes.js +0 -162
  150. package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +0 -44
  151. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +0 -89
  152. package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +0 -202
  153. package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +0 -79
  154. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +0 -86
  155. package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +0 -38
  156. package/dist/worker/console/chat/pi-runtime/inspection-types.js +0 -28
  157. package/dist/worker/console/chat/pi-runtime/inspection.js +0 -439
  158. package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +0 -153
  159. package/dist/worker/console/chat/pi-runtime/sdk-session.js +0 -5
  160. package/dist/worker/console/chat/scm-routes.js +0 -217
  161. package/dist/worker/console/chat/scm-service.js +0 -283
  162. package/dist/worker/console/chat/scm-tools.js +0 -111
  163. package/dist/worker/console/chat/session-mode-view.js +0 -49
  164. package/dist/worker/console/chat/session-mode.js +0 -220
  165. package/dist/worker/console/chat/terminal-routes.js +0 -216
  166. package/dist/worker/console/chat/terminal-sessions.js +0 -348
  167. package/dist/worker/console/chat/terminal-tools.js +0 -211
  168. package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
  169. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
  170. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
  171. package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
  172. package/dist/worker/console/static/assets/index-wXlQohNs.js +0 -469
  173. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
  174. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
  175. package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +0 -10
  176. package/dist/worker/console/static-src/operator-chat/chat-link.js +0 -94
  177. package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +0 -24
  178. package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +0 -192
  179. package/dist/worker/console/static-src/operator-chat/process-label.js +0 -41
  180. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +0 -13
  181. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +0 -10
  182. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +0 -51
  183. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +0 -21
  184. package/dist/worker/console/workspace-initialization.js +0 -209
  185. package/dist/workflows/dag/frontend-capacity.js +0 -9
  186. package/dist/workflows/dag/frontend-plan-completeness.js +0 -186
  187. package/dist/workflows/dag/frontend-plan-recovery-policy.js +0 -18
@@ -1,217 +0,0 @@
1
- /**
2
- * Operator Chat — source control HTTP API (Console UI side).
3
- *
4
- * Endpoints (all under /api/operator/v1/chat):
5
- * GET /scm/status branch + changes + counts
6
- * GET /scm/diff?path=&staged= unified diff (one path or --stat)
7
- * POST /scm/stage { paths: string[] } | { all: true }
8
- * POST /scm/unstage { paths: string[] }
9
- * POST /scm/discard { paths: string[] } [dangerous]
10
- * POST /scm/commit { message }
11
- * GET /scm/log?limit= recent commits
12
- * GET /scm/branches branch list + current
13
- * POST /scm/branches { name, create? } switch or create
14
- * POST /scm/push push to upstream
15
- * POST /scm/pull pull from upstream
16
- *
17
- * Reads are open (same-origin console); every mutation passes gateMutation.
18
- * The ScmService is workspace-scoped (repoRoot fixed at construction).
19
- */
20
- function sendJson(res, status, body) {
21
- res.writeHead(status, {
22
- "content-type": "application/json; charset=utf-8",
23
- "cache-control": "no-store",
24
- });
25
- res.end(JSON.stringify(body));
26
- }
27
- function mapScmError(res, error) {
28
- const status = error.code === "NO_PATHS" || error.code === "EMPTY_MESSAGE" || error.code === "INVALID_NAME" || error.code === "INVALID_HASH"
29
- ? 400
30
- : error.code === "GIT_TIMEOUT"
31
- ? 504
32
- : 500;
33
- sendJson(res, status, {
34
- ok: false,
35
- error: { code: error.code, message: error.message, ...(error.stderr ? { stderr: error.stderr } : {}) },
36
- });
37
- }
38
- async function readJsonBody(req, maxBytes = 256 * 1024) {
39
- const chunks = [];
40
- let total = 0;
41
- for await (const chunk of req) {
42
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
43
- total += buf.length;
44
- if (total > maxBytes)
45
- throw new Error("request body too large");
46
- chunks.push(buf);
47
- }
48
- if (chunks.length === 0)
49
- return {};
50
- const raw = Buffer.concat(chunks).toString("utf8");
51
- if (!raw.trim())
52
- return {};
53
- return JSON.parse(raw);
54
- }
55
- function parsePaths(body) {
56
- if (!Array.isArray(body.paths))
57
- return [];
58
- return body.paths
59
- .filter((p) => typeof p === "string" && p.length > 0 && !p.includes("\0"))
60
- .slice(0, 500);
61
- }
62
- /** Route dispatcher; returns true when the request was handled. */
63
- export async function handleScmRequest(req, res, deps, pathname) {
64
- if (!pathname.startsWith("/api/operator/v1/chat/scm"))
65
- return false;
66
- const method = (req.method ?? "GET").toUpperCase();
67
- const rest = pathname.slice("/api/operator/v1/chat/scm".length);
68
- const parts = rest.split("/").filter(Boolean);
69
- const sub = parts[0] ?? "";
70
- const url = new URL(req.url ?? "/", "http://localhost");
71
- const gateFailed = (gate) => {
72
- sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
73
- return true;
74
- };
75
- try {
76
- if (method === "GET" && sub === "status" && parts.length === 1) {
77
- const r = await deps.scm.status();
78
- if (!r.ok)
79
- return mapScmError(res, r), true;
80
- sendJson(res, 200, { ok: true, data: r });
81
- return true;
82
- }
83
- if (method === "GET" && sub === "diff" && parts.length === 1) {
84
- const path = url.searchParams.get("path") ?? undefined;
85
- const staged = url.searchParams.get("staged") === "1";
86
- const r = await deps.scm.diff({ path, staged });
87
- if (!r.ok)
88
- return mapScmError(res, r), true;
89
- sendJson(res, 200, { ok: true, data: r });
90
- return true;
91
- }
92
- if (method === "GET" && sub === "commit-diff" && parts.length === 1) {
93
- const hash = url.searchParams.get("hash") ?? "";
94
- if (!hash) {
95
- sendJson(res, 400, { ok: false, error: { code: "INVALID_INPUT", message: "hash is required" } });
96
- return true;
97
- }
98
- const r = await deps.scm.commitDiff(hash);
99
- if (!r.ok)
100
- return mapScmError(res, r), true;
101
- sendJson(res, 200, { ok: true, data: r });
102
- return true;
103
- }
104
- if (method === "GET" && sub === "untracked-diff" && parts.length === 1) {
105
- const path = url.searchParams.get("path") ?? "";
106
- if (!path) {
107
- sendJson(res, 400, { ok: false, error: { code: "NO_PATHS", message: "path is required" } });
108
- return true;
109
- }
110
- const r = await deps.scm.untrackedDiff(path);
111
- if (!r.ok)
112
- return mapScmError(res, r), true;
113
- sendJson(res, 200, { ok: true, data: r });
114
- return true;
115
- }
116
- if (method === "GET" && sub === "log" && parts.length === 1) {
117
- const limit = Number(url.searchParams.get("limit") ?? 30);
118
- const r = await deps.scm.log(Number.isFinite(limit) ? Math.min(Math.max(limit, 1), 200) : 30);
119
- if (!r.ok)
120
- return mapScmError(res, r), true;
121
- sendJson(res, 200, { ok: true, data: r });
122
- return true;
123
- }
124
- if (method === "GET" && sub === "branches" && parts.length === 1) {
125
- const r = await deps.scm.branchList();
126
- if (!r.ok)
127
- return mapScmError(res, r), true;
128
- sendJson(res, 200, { ok: true, data: r });
129
- return true;
130
- }
131
- // Everything below mutates → gate first.
132
- const gate = deps.gateMutation(req);
133
- if (!gate.ok)
134
- return gateFailed(gate);
135
- if (method === "POST" && sub === "stage" && parts.length === 1) {
136
- const body = await readJsonBody(req);
137
- const r = body.all === true
138
- ? await deps.scm.stage([], true)
139
- : await deps.scm.stage(parsePaths(body));
140
- if (!r.ok)
141
- return mapScmError(res, r), true;
142
- sendJson(res, 200, { ok: true });
143
- return true;
144
- }
145
- if (method === "POST" && sub === "unstage" && parts.length === 1) {
146
- const body = await readJsonBody(req);
147
- const r = await deps.scm.unstage(parsePaths(body));
148
- if (!r.ok)
149
- return mapScmError(res, r), true;
150
- sendJson(res, 200, { ok: true });
151
- return true;
152
- }
153
- if (method === "POST" && sub === "discard" && parts.length === 1) {
154
- const body = await readJsonBody(req);
155
- const r = await deps.scm.discard(parsePaths(body));
156
- if (!r.ok)
157
- return mapScmError(res, r), true;
158
- sendJson(res, 200, { ok: true });
159
- return true;
160
- }
161
- if (method === "POST" && sub === "commit" && parts.length === 1) {
162
- const body = await readJsonBody(req);
163
- const message = typeof body.message === "string" ? body.message : "";
164
- const r = await deps.scm.commit(message);
165
- if (!r.ok)
166
- return mapScmError(res, r), true;
167
- sendJson(res, 200, { ok: true, data: r });
168
- return true;
169
- }
170
- if (method === "POST" && sub === "branches" && parts.length === 1) {
171
- const body = await readJsonBody(req);
172
- const name = typeof body.name === "string" ? body.name : "";
173
- if (!name) {
174
- sendJson(res, 400, { ok: false, error: { code: "INVALID_INPUT", message: "name is required" } });
175
- return true;
176
- }
177
- const r = body.create === true
178
- ? await deps.scm.branchCreate(name)
179
- : await deps.scm.branchSwitch(name);
180
- if (!r.ok)
181
- return mapScmError(res, r), true;
182
- sendJson(res, 200, { ok: true });
183
- return true;
184
- }
185
- if (method === "POST" && sub === "push" && parts.length === 1) {
186
- const r = await deps.scm.push();
187
- if (!r.ok)
188
- return mapScmError(res, r), true;
189
- sendJson(res, 200, { ok: true, data: r });
190
- return true;
191
- }
192
- if (method === "POST" && sub === "pull" && parts.length === 1) {
193
- const r = await deps.scm.pull();
194
- if (!r.ok)
195
- return mapScmError(res, r), true;
196
- sendJson(res, 200, { ok: true, data: r });
197
- return true;
198
- }
199
- sendJson(res, 404, { ok: false, error: { code: "NOT_FOUND", message: `unknown scm route: ${method} ${pathname}` } });
200
- return true;
201
- }
202
- catch (error) {
203
- if (!res.headersSent) {
204
- sendJson(res, 500, {
205
- ok: false,
206
- error: {
207
- code: "SCM_INTERNAL",
208
- message: error instanceof Error ? error.message : String(error),
209
- },
210
- });
211
- }
212
- else {
213
- res.end();
214
- }
215
- return true;
216
- }
217
- }
@@ -1,283 +0,0 @@
1
- /**
2
- * Operator Chat — source control service (DSH-style SCM).
3
- *
4
- * Workspace-scoped git operations shared by the Console UI panel and the
5
- * model-facing tools:
6
- * - status: porcelain + branch + ahead/behind, parsed into typed changes
7
- * - diff: working tree / staged, per-path or whole tree (bounded)
8
- * - stage / unstage / discard (path-scoped)
9
- * - commit (message required), log (bounded), branch list/switch/create
10
- * - push / pull (remote ops, explicit user/model intent only)
11
- *
12
- * All git invocations are fixed-argv execFile (no shell), bounded output and
13
- * timeout, scoped to the workspace repoRoot. Failures return typed errors
14
- * instead of throwing raw.
15
- */
16
- import { execFile } from "node:child_process";
17
- import { promisify } from "node:util";
18
- const execFileAsync = promisify(execFile);
19
- const GIT_TIMEOUT_MS = 20_000;
20
- const MAX_OUTPUT_CHARS = 200_000;
21
- function gitEnv() {
22
- return {
23
- ...process.env,
24
- // Deterministic porcelain output regardless of user locale config.
25
- GIT_PAGER: "cat",
26
- LC_ALL: "C",
27
- };
28
- }
29
- export class ScmService {
30
- repoRoot;
31
- constructor(repoRoot) {
32
- this.repoRoot = repoRoot;
33
- }
34
- async git(args, opts) {
35
- try {
36
- const { stdout } = await execFileAsync("git", args, {
37
- cwd: this.repoRoot,
38
- encoding: "utf8",
39
- maxBuffer: 4 * 1024 * 1024,
40
- timeout: opts?.timeoutMs ?? GIT_TIMEOUT_MS,
41
- windowsHide: true,
42
- env: gitEnv(),
43
- });
44
- const cap = opts?.maxOutputChars ?? MAX_OUTPUT_CHARS;
45
- return { ok: true, stdout: stdout.length > cap ? stdout.slice(0, cap) : stdout };
46
- }
47
- catch (e) {
48
- const err = e;
49
- return {
50
- ok: false,
51
- code: err.killed ? "GIT_TIMEOUT" : "GIT_FAILED",
52
- message: err.killed
53
- ? `git ${args[0]} timed out`
54
- : err.message ?? String(e),
55
- stderr: typeof err.stderr === "string" ? err.stderr.slice(0, 2000) : undefined,
56
- // `git diff --no-index` exits 1 when a diff exists; callers that
57
- // expect that carry the partial stdout through.
58
- stdout: typeof err.stdout === "string" ? err.stdout : undefined,
59
- };
60
- }
61
- }
62
- /** `git rev-parse --is-inside-work-tree` — cheap repo probe. */
63
- async isRepo() {
64
- const r = await this.git(["rev-parse", "--is-inside-work-tree"], { timeoutMs: 5_000 });
65
- return r.ok && r.stdout.trim() === "true";
66
- }
67
- async status() {
68
- // -uall: expand untracked directories to individual files (VS Code style);
69
- // renames surface as R rows (with fromPath) only when git's rename
70
- // detection is active, which is the default for status since git 2.9.
71
- const porcelain = await this.git([
72
- "status", "--porcelain=v1", "-z", "--branch", "-uall",
73
- ]);
74
- if (!porcelain.ok)
75
- return porcelain;
76
- const changes = [];
77
- // porcelain v1 -z: NUL-separated fields. Header is
78
- // "## branch...upstream [ahead N, behind M]"; status rows "XY path";
79
- // rename/copy rows "XY newpath" followed by a bare "oldpath" field.
80
- const fields = porcelain.stdout.split("\0").filter((s) => s !== "");
81
- let branch = "";
82
- let upstream;
83
- let ahead;
84
- let behind;
85
- for (const field of fields) {
86
- if (field.startsWith("## ")) {
87
- const header = field.slice(3);
88
- const track = header.split(/\s+/)[0] ?? "";
89
- const dot = track.indexOf("...");
90
- if (dot >= 0) {
91
- branch = track.slice(0, dot);
92
- upstream = track.slice(dot + 3);
93
- }
94
- else {
95
- branch = track;
96
- }
97
- const aheadM = /\[ahead (\d+)/.exec(header);
98
- const behindM = /behind (\d+)\]/.exec(header);
99
- if (aheadM)
100
- ahead = Number(aheadM[1]);
101
- if (behindM)
102
- behind = Number(behindM[1]);
103
- continue;
104
- }
105
- if (field.startsWith("#"))
106
- continue;
107
- // Bare path field: from-path of the previous rename/copy row.
108
- const prev = changes[changes.length - 1];
109
- if (prev && prev.xy.match(/[RC]/) && !prev.fromPath && !/^[ MADRCU?!]{1,2} /.test(field)) {
110
- prev.fromPath = field;
111
- continue;
112
- }
113
- const xy = field.slice(0, 2);
114
- const path = field.slice(3);
115
- changes.push({
116
- path,
117
- xy,
118
- staged: xy[0] !== " " && xy[0] !== "?" && xy[0] !== "!",
119
- unstaged: xy[1] !== " " && xy[1] !== "?",
120
- untracked: xy === "??",
121
- });
122
- }
123
- const head = await this.git(["rev-parse", "--short", "HEAD"], { timeoutMs: 5_000 });
124
- return {
125
- ok: true,
126
- repoRoot: this.repoRoot,
127
- branch,
128
- head: head.ok ? head.stdout.trim() : "",
129
- upstream,
130
- ahead,
131
- behind,
132
- changes,
133
- stagedCount: changes.filter((c) => c.staged).length,
134
- unstagedCount: changes.filter((c) => c.unstaged).length,
135
- untrackedCount: changes.filter((c) => c.untracked).length,
136
- };
137
- }
138
- /** Unified diff for one path (or whole tree when path omitted). */
139
- async diff(options) {
140
- const args = ["diff"];
141
- if (options.staged)
142
- args.push("--cached");
143
- args.push(`-U${options.contextLines ?? 3}`);
144
- if (options.path) {
145
- args.push("--", options.path);
146
- }
147
- else {
148
- args.push("--stat");
149
- }
150
- const r = await this.git(args);
151
- if (!r.ok)
152
- return r;
153
- return { ok: true, diff: r.stdout };
154
- }
155
- /** Full patch of one commit (`git show` without the commit header). */
156
- async commitDiff(hash) {
157
- if (!/^[0-9a-f]{4,40}$/i.test(hash)) {
158
- return { ok: false, code: "INVALID_HASH", message: "invalid commit hash" };
159
- }
160
- const r = await this.git(["show", "--format=", "--no-color", hash]);
161
- if (!r.ok)
162
- return r;
163
- return { ok: true, diff: r.stdout };
164
- }
165
- /**
166
- * Untracked-file content as a full-addition diff (`git diff --no-index
167
- * /dev/null <path>`): git diff never lists untracked files, and this
168
- * renders them like any other change in the diff view.
169
- */
170
- async untrackedDiff(path) {
171
- if (path.includes("\0") || path.includes("..")) {
172
- return { ok: false, code: "NO_PATHS", message: "invalid path" };
173
- }
174
- const r = await this.git(["diff", "--no-index", "--no-color", "-U3", "--", "/dev/null", path]);
175
- // `git diff --no-index` exits 1 when there IS a diff — carry stdout.
176
- if (!r.ok && r.code === "GIT_FAILED")
177
- return { ok: true, diff: r.stdout ?? "" };
178
- if (!r.ok)
179
- return r;
180
- return { ok: true, diff: r.stdout };
181
- }
182
- /** Stage paths (`git add --` / `git add -A --` for "."). */
183
- async stage(paths, all = false) {
184
- if (!all && paths.length === 0) {
185
- return { ok: false, code: "NO_PATHS", message: "stage requires paths or all=true" };
186
- }
187
- const args = ["add"];
188
- if (all) {
189
- args.push("-A");
190
- }
191
- else {
192
- args.push("--");
193
- args.push(...paths);
194
- }
195
- return this.git(args);
196
- }
197
- /** Unstage paths (`git reset HEAD --`). */
198
- async unstage(paths) {
199
- if (paths.length === 0) {
200
- return { ok: false, code: "NO_PATHS", message: "unstage requires paths" };
201
- }
202
- return this.git(["reset", "HEAD", "--", ...paths]);
203
- }
204
- /** Discard worktree changes for paths (`git checkout --`). DANGEROUS. */
205
- async discard(paths) {
206
- if (paths.length === 0) {
207
- return { ok: false, code: "NO_PATHS", message: "discard requires paths" };
208
- }
209
- return this.git(["checkout", "--", ...paths]);
210
- }
211
- async commit(message) {
212
- const trimmed = message.trim();
213
- if (!trimmed) {
214
- return { ok: false, code: "EMPTY_MESSAGE", message: "commit message required" };
215
- }
216
- const r = await this.git(["commit", "-m", trimmed]);
217
- if (!r.ok)
218
- return r;
219
- const sha = await this.git(["rev-parse", "--short", "HEAD"], { timeoutMs: 5_000 });
220
- return { ok: true, commit: sha.ok ? sha.stdout.trim() : "" };
221
- }
222
- async log(limit = 30) {
223
- const r = await this.git([
224
- "log",
225
- `--max-count=${limit}`,
226
- "--pretty=format:%H\u0001%h\u0001%s\u0001%an\u0001%aI\u0001%D",
227
- ]);
228
- if (!r.ok)
229
- return r;
230
- const commits = [];
231
- for (const line of r.stdout.split("\n")) {
232
- if (!line.trim())
233
- continue;
234
- const [sha, short, subject, author, date, refs] = line.split("\u0001");
235
- if (!sha)
236
- continue;
237
- commits.push({
238
- sha: short ?? sha,
239
- subject: subject ?? "",
240
- author: author ?? "",
241
- date: date ?? "",
242
- refs: refs?.trim() || undefined,
243
- });
244
- }
245
- return { ok: true, commits };
246
- }
247
- async branchList() {
248
- const r = await this.git(["branch", "--list", "--format=%(HEAD)%02%(refname:short)"]);
249
- if (!r.ok)
250
- return r;
251
- const branches = [];
252
- for (const line of r.stdout.split("\n")) {
253
- if (!line.trim())
254
- continue;
255
- const [headMark, name] = line.split("\x02");
256
- if (!name)
257
- continue;
258
- branches.push({ name: name.trim(), current: headMark?.trim() === "*" });
259
- }
260
- const current = branches.find((b) => b.current)?.name ?? "";
261
- return { ok: true, current, branches };
262
- }
263
- async branchCreate(name) {
264
- if (!/^[\w.\-/]+$/.test(name)) {
265
- return { ok: false, code: "INVALID_NAME", message: `invalid branch name: ${name}` };
266
- }
267
- return this.git(["checkout", "-b", name]);
268
- }
269
- async branchSwitch(name) {
270
- if (!/^[\w.\-/]+$/.test(name)) {
271
- return { ok: false, code: "INVALID_NAME", message: `invalid branch name: ${name}` };
272
- }
273
- return this.git(["checkout", name]);
274
- }
275
- async push() {
276
- const r = await this.git(["push"], { timeoutMs: 120_000 });
277
- return r.ok ? { ok: true, output: r.stdout } : r;
278
- }
279
- async pull() {
280
- const r = await this.git(["pull", "--no-edit"], { timeoutMs: 120_000 });
281
- return r.ok ? { ok: true, output: r.stdout } : r;
282
- }
283
- }
@@ -1,111 +0,0 @@
1
- /**
2
- * Operator Chat — model-facing source control tool (DSH-style SCM).
3
- *
4
- * Registers `scm` (single tool, multi-action) so the model can inspect and
5
- * drive git in the workspace repo: status / diff / stage / unstage / commit /
6
- * log / branch / push / pull. Read actions are cheap and safe; write actions
7
- * (commit/push) are the model's explicit intent, mirroring DSH's SCM tool.
8
- *
9
- * All operations go through ScmService (fixed-argv git, no shell, bounded),
10
- * scoped to the workspace repoRoot.
11
- */
12
- const TOOL_DESCRIPTION = "Inspect and drive git source control in the workspace repo. " +
13
- "Actions: 'status' (branch + working-tree changes), 'diff' (one path or whole tree; staged=1 for the index), " +
14
- "'stage'/'unstage' (paths array; all=true stages everything), 'discard' (DESTRUCTIVE: revert worktree changes of paths), " +
15
- "'commit' (message required), 'log' (recent commits), 'branch' (list; name+create to create/switch), 'push', 'pull'. " +
16
- "Use status/diff before proposing commits; prefer committing focused changesets. push/pull contact the remote — call them only when the user asks.";
17
- export async function buildScmTools(deps) {
18
- const [{ Type }, { defineTool }] = await Promise.all([
19
- import("typebox"),
20
- import("@earendil-works/pi-coding-agent"),
21
- ]);
22
- function result(body) {
23
- return { content: [{ type: "text", text: JSON.stringify(body) }], details: body };
24
- }
25
- function errText(e) {
26
- return JSON.stringify({ ok: false, code: e.code, message: e.message, ...(e.stderr ? { stderr: e.stderr } : {}) });
27
- }
28
- const tool = defineTool({
29
- name: "scm",
30
- label: "SCM",
31
- description: TOOL_DESCRIPTION,
32
- promptSnippet: "scm: git source control (status/diff/stage/unstage/discard/commit/log/branch/push/pull) for the workspace repo.",
33
- parameters: Type.Object({
34
- action: Type.Union([
35
- Type.Literal("status"),
36
- Type.Literal("diff"),
37
- Type.Literal("stage"),
38
- Type.Literal("unstage"),
39
- Type.Literal("discard"),
40
- Type.Literal("commit"),
41
- Type.Literal("log"),
42
- Type.Literal("branch"),
43
- Type.Literal("push"),
44
- Type.Literal("pull"),
45
- ], { description: "Which git operation to run." }),
46
- path: Type.Optional(Type.String({ description: "For diff: repo-relative path (default whole tree --stat)." })),
47
- staged: Type.Optional(Type.Boolean({ description: "For diff: diff the staging area (index) instead of the worktree." })),
48
- paths: Type.Optional(Type.Array(Type.String(), { description: "For stage/unstage/discard: repo-relative paths." })),
49
- all: Type.Optional(Type.Boolean({ description: "For stage: stage all changes (git add -A)." })),
50
- message: Type.Optional(Type.String({ description: "For commit: commit message (required)." })),
51
- limit: Type.Optional(Type.Number({ description: "For log: max commits (default 30)." })),
52
- name: Type.Optional(Type.String({ description: "For branch: branch name to create or switch to." })),
53
- create: Type.Optional(Type.Boolean({ description: "For branch: create the branch instead of switching." })),
54
- }, { additionalProperties: false }),
55
- async execute(_toolCallId, params) {
56
- const paths = Array.isArray(params.paths) ? params.paths.slice(0, 500) : [];
57
- switch (params.action) {
58
- case "status":
59
- return result(await deps.scm.status());
60
- case "diff": {
61
- const r = await deps.scm.diff({
62
- ...(params.path ? { path: params.path } : {}),
63
- ...(params.staged ? { staged: true } : {}),
64
- });
65
- return r.ok ? result(r) : result({ error: errText(r) });
66
- }
67
- case "stage": {
68
- const r = params.all === true ? await deps.scm.stage([], true) : await deps.scm.stage(paths);
69
- return r.ok ? result({ ok: true, staged: params.all === true ? "all" : paths }) : result({ error: errText(r) });
70
- }
71
- case "unstage": {
72
- const r = await deps.scm.unstage(paths);
73
- return r.ok ? result({ ok: true, unstaged: paths }) : result({ error: errText(r) });
74
- }
75
- case "discard": {
76
- const r = await deps.scm.discard(paths);
77
- return r.ok ? result({ ok: true, discarded: paths }) : result({ error: errText(r) });
78
- }
79
- case "commit": {
80
- const r = await deps.scm.commit(params.message ?? "");
81
- return r.ok ? result(r) : result({ error: errText(r) });
82
- }
83
- case "log": {
84
- const r = await deps.scm.log(params.limit ?? 30);
85
- return r.ok ? result(r) : result({ error: errText(r) });
86
- }
87
- case "branch": {
88
- if (params.name) {
89
- const r = params.create === true
90
- ? await deps.scm.branchCreate(params.name)
91
- : await deps.scm.branchSwitch(params.name);
92
- return r.ok ? result({ ok: true, ...(params.create === true ? { created: params.name } : { switched: params.name }) }) : result({ error: errText(r) });
93
- }
94
- const r = await deps.scm.branchList();
95
- return r.ok ? result(r) : result({ error: errText(r) });
96
- }
97
- case "push": {
98
- const r = await deps.scm.push();
99
- return r.ok ? result(r) : result({ error: errText(r) });
100
- }
101
- case "pull": {
102
- const r = await deps.scm.pull();
103
- return r.ok ? result(r) : result({ error: errText(r) });
104
- }
105
- default:
106
- return result({ error: `unknown action: ${params.action}` });
107
- }
108
- },
109
- });
110
- return [tool];
111
- }
@@ -1,49 +0,0 @@
1
- /**
2
- * Browser-safe session-mode types and copy.
3
- *
4
- * Keep this module free of Node/runtime tool policy so the Console UI
5
- * bundle can import it without pulling operator registries.
6
- */
7
- export const CONSOLE_CHAT_SESSION_MODES = ["workflow", "pi"];
8
- export const DEFAULT_CONSOLE_CHAT_SESSION_MODE = "workflow";
9
- export const SESSION_MODE_COPY = {
10
- workflow: {
11
- id: "workflow",
12
- label: "工作流",
13
- shortLabel: "工作流",
14
- description: "规划并推进工作流",
15
- createLabel: "新建工作流会话",
16
- bannerTitle: "工作流",
17
- bannerBody: "用自然语言编排 loop-agent / agent-worker 的全部能力。这是 Operator 面:默认具备完整 Pi 工具与受策略约束的 operator action。软约束:大改与高风险变更优先走受治 DAG。",
18
- landingTitle: "开始一段新的对话",
19
- landingBody: "用自然语言编排 loop-agent:查询状态、诊断问题、创建与恢复任务。可先点选下方建议,或在输入框描述目标。工作流或 Pi 在输入框上方选择。",
20
- emptyTitle: "今天想推进什么?",
21
- emptyBody: "会话已就绪。输入框上方可切换工作区或工作流 / Pi。点选建议快速开场,或直接描述目标。业务写入仍经受治理 DAG。",
22
- },
23
- pi: {
24
- id: "pi",
25
- label: "Pi",
26
- shortLabel: "Pi",
27
- description: "保留压缩与 Explore,直接协作编码",
28
- createLabel: "新建 Pi 会话",
29
- bannerTitle: "Pi",
30
- bannerBody: "保留 Console 压缩、Explore 与持续目标,直接协作编码。不注入工作流业务指令与业务工具。",
31
- landingTitle: "开始一段 Pi 协作",
32
- landingBody: "Pi 模式保留压缩、Explore、持续目标、认证与 SDD 接入,取消工作流业务注入。适合有人监督的直接编码与调试。",
33
- emptyTitle: "直接协作编码",
34
- emptyBody: "会话已就绪。输入框上方可切换工作区或模式。使用 Pi 工具读写仓库;Explore 负责只读深挖。本会话不加载任务 / DAG 工作流工具。",
35
- },
36
- };
37
- /** Slash actions that only exist in workflow sessions. Browser-safe: pure
38
- * string constants, no runtime/tool policy imports. `/goal` was moved to
39
- * shared on 2026-09-10 (exec-plan 2026-09-10-console-chat-goal-for-pi-mode);
40
- * interview/panel navigation remain workflow-only. */
41
- export const WORKFLOW_EXCLUSIVE_SLASH_ACTIONS = Object.freeze([
42
- "focus-panel:task",
43
- "focus-panel:contract",
44
- "focus-panel:dag",
45
- "focus:interview",
46
- ]);
47
- export function isWorkflowExclusiveSlashAction(action) {
48
- return WORKFLOW_EXCLUSIVE_SLASH_ACTIONS.includes(action);
49
- }