@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,348 +0,0 @@
1
- /**
2
- * Operator Chat — persistent PTY terminal sessions (DSH-style terminal seam).
3
- *
4
- * Process-local registry of owner-scoped persistent terminal sessions:
5
- * - spawn: create a named PTY (shell) owned by a chat session
6
- * - send: write bytes into the PTY (model tool or console UI input)
7
- * - read: ring-buffered output history + live subscriber fan-out
8
- * - signal/kill: interrupt or fully stop the process tree, awaited
9
- * - dispose: stop everything owned by one chat session
10
- *
11
- * Design mirrors the DSH terminal seam contract (process-local, no sharing
12
- * across agents, single active send, kill waits for full stop) but stays
13
- * deliberately small: no backend abstraction (node-pty is the only backend),
14
- * no cross-process persistence (sessions die with the Console process).
15
- */
16
- import { spawn as ptySpawn } from "node-pty";
17
- import { existsSync } from "node:fs";
18
- import { randomBytes } from "node:crypto";
19
- import { release as osRelease } from "node:os";
20
- /** Bounded ring buffer of raw output chunks (bytes preserved as utf8 text). */
21
- const MAX_BUFFER_CHARS = 200_000;
22
- export class TerminalSessionError extends Error {
23
- code;
24
- constructor(code, message) {
25
- super(message);
26
- this.code = code;
27
- this.name = "TerminalSessionError";
28
- }
29
- }
30
- const IS_WINDOWS = process.platform === "win32";
31
- function firstExisting(paths) {
32
- return paths.find((p) => existsSync(p));
33
- }
34
- /** Windows PowerShell 5+ or pwsh 7; prefer pwsh 7 when installed. */
35
- function resolvePowershell() {
36
- if (IS_WINDOWS) {
37
- const pwsh7 = firstExisting([
38
- "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
39
- "C:\\Program Files (x86)\\PowerShell\\7\\pwsh.exe",
40
- ]);
41
- if (pwsh7) {
42
- return {
43
- file: pwsh7,
44
- args: [
45
- "-NoLogo",
46
- "-NoProfile",
47
- "-ExecutionPolicy",
48
- "Bypass",
49
- "-NoExit",
50
- "-Command",
51
- "$PSDefaultParameterValues['Out-File:Encoding']='utf8'; [Console]::OutputEncoding=[Text.Encoding]::UTF8",
52
- ],
53
- };
54
- }
55
- const ps5 = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
56
- if (existsSync(ps5)) {
57
- return {
58
- file: ps5,
59
- args: [
60
- "-NoProfile",
61
- "-ExecutionPolicy",
62
- "Bypass",
63
- "-NoExit",
64
- "-Command",
65
- "$PSDefaultParameterValues['Out-File:Encoding']='utf8'; [Console]::OutputEncoding=[Text.Encoding]::UTF8",
66
- ],
67
- };
68
- }
69
- return undefined;
70
- }
71
- // macOS / Linux: only pwsh 7 if explicitly installed.
72
- const pwsh = firstExisting([
73
- "/usr/local/bin/pwsh",
74
- "/opt/homebrew/bin/pwsh",
75
- "/usr/bin/pwsh",
76
- ]);
77
- return pwsh
78
- ? { file: pwsh, args: ["-NoLogo", "-NoProfile"] }
79
- : undefined;
80
- }
81
- /** bash: Git Bash on Windows, /bin/bash (or Homebrew) on unix-likes. */
82
- function resolveBash() {
83
- if (IS_WINDOWS) {
84
- const file = firstExisting([
85
- "C:\\Program Files\\Git\\bin\\bash.exe",
86
- "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
87
- "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
88
- ]);
89
- return file ? { file, args: ["-i", "-l"] } : undefined;
90
- }
91
- const file = firstExisting(["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash", "/opt/homebrew/bin/bash"]);
92
- return file ? { file, args: ["-i"] } : undefined;
93
- }
94
- /** zsh: macOS default shell; on Linux only when installed. */
95
- function resolveZsh() {
96
- if (IS_WINDOWS)
97
- return undefined;
98
- const file = firstExisting(["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh", "/opt/homebrew/bin/zsh"]);
99
- return file ? { file, args: ["-i"] } : undefined;
100
- }
101
- /** Windows `cmd.exe`. Always present on Windows; offered alongside
102
- * PowerShell so users can use native Windows tooling (`dir`, `where`,
103
- * `mklink`, `net user`, etc.) without leaving the terminal panel.
104
- * - `/Q` turns off command echo (otherwise cmd echoes every input line,
105
- * doubling every prompt in PTY output).
106
- * - `/D` disables AutoRun execution from the registry (avoid running the
107
- * user's HKCU\...\RunOnce / autorun scripts on every fresh shell).
108
- * - `/K "prompt $P$G"` keeps the shell alive across tool calls (cwd/env
109
- * survive) and uses the classic cmd prompt `C:\path>` instead of the
110
- * bare `>`, so the user can see the working directory at a glance —
111
- * matching the default `cmd.exe` window. */
112
- function resolveCmd() {
113
- if (!IS_WINDOWS)
114
- return undefined;
115
- const file = firstExisting([
116
- "C:\\Windows\\System32\\cmd.exe",
117
- // Wine / uncommon layouts
118
- "C:\\Windows\\Sysnative\\cmd.exe",
119
- ]);
120
- if (!file)
121
- return undefined;
122
- return {
123
- file,
124
- args: ["/Q", "/D", "/K", "prompt $P$G"],
125
- };
126
- }
127
- /** Synthesize the cmd.exe startup banner that node-pty drops. cmd prints
128
- * `Microsoft Windows [版本 10.0.26200]\n(c) Microsoft Corporation。保留所有权利。`
129
- * via `WriteConsole` BEFORE ConPTY attaches to its stdio handles, so
130
- * node-pty / ConPTY never sees those writes. We re-emit an equivalent
131
- * banner using `os.release()` (kernel build, e.g. "10.0.26200") so the
132
- * console terminal looks like a real cmd window. The leading \r\n avoids
133
- * the banner colliding with whatever ANSI preamble ConPTY already sent.
134
- *
135
- * Only meaningful for the cmd shell — bash and PowerShell on Windows have
136
- * their own startup output that we must NOT paper over with a Windows
137
- * banner they don't print. */
138
- function makeCmdBanner(shell) {
139
- if (!IS_WINDOWS)
140
- return undefined;
141
- if (shell !== "cmd")
142
- return undefined;
143
- const build = osRelease();
144
- return `\r\nMicrosoft Windows [版本 ${build}]\r\n(c) Microsoft Corporation。保留所有权利。\r\n\r\n`;
145
- }
146
- let cachedCatalog;
147
- /** Platform shell catalog, computed once per process. Drives both the UI
148
- * button set and the model tool's shell parameter enum. */
149
- export function availableShellKinds() {
150
- if (cachedCatalog)
151
- return cachedCatalog;
152
- const resolved = new Map();
153
- const pwsh = resolvePowershell();
154
- if (pwsh)
155
- resolved.set("powershell", pwsh);
156
- const bash = resolveBash();
157
- if (bash)
158
- resolved.set("bash", bash);
159
- const zsh = resolveZsh();
160
- if (zsh)
161
- resolved.set("zsh", zsh);
162
- const cmd = resolveCmd();
163
- if (cmd)
164
- resolved.set("cmd", cmd);
165
- cachedCatalog = { kinds: [...resolved.keys()], resolved };
166
- return cachedCatalog;
167
- }
168
- export function resolveShellCommand(shell) {
169
- const resolved = availableShellKinds().resolved.get(shell);
170
- if (!resolved) {
171
- throw new TerminalSessionError("TERMINAL_SPAWN_FAILED", `shell "${shell}" is not available on this platform (${process.platform})`);
172
- }
173
- return resolved;
174
- }
175
- export class TerminalSessionRegistry {
176
- entries = new Map();
177
- nextSeq = 0;
178
- spawn(options) {
179
- let file;
180
- let args;
181
- try {
182
- ({ file, args } = resolveShellCommand(options.shell));
183
- }
184
- catch (error) {
185
- throw new TerminalSessionError("TERMINAL_SPAWN_FAILED", `failed to spawn ${options.shell}: ${error instanceof Error ? error.message : String(error)}`);
186
- }
187
- const sessionId = `term-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
188
- let pty;
189
- try {
190
- pty = ptySpawn(file, args, {
191
- name: "xterm-256color",
192
- cwd: options.cwd,
193
- cols: options.cols ?? 120,
194
- rows: options.rows ?? 30,
195
- env: process.env ?? {},
196
- encoding: "utf8",
197
- });
198
- }
199
- catch (error) {
200
- throw new TerminalSessionError("TERMINAL_SPAWN_FAILED", `failed to spawn ${options.shell}: ${error instanceof Error ? error.message : String(error)}`);
201
- }
202
- const entry = {
203
- info: {
204
- sessionId,
205
- ownerSessionId: options.ownerSessionId,
206
- name: options.name,
207
- shell: options.shell,
208
- createdAt: new Date().toISOString(),
209
- totalOutputChars: 0,
210
- exited: false,
211
- },
212
- pty,
213
- buffer: [],
214
- bufferChars: 0,
215
- subscribers: new Set(),
216
- disposing: undefined,
217
- pendingBanner: makeCmdBanner(options.shell) ?? undefined,
218
- };
219
- this.entries.set(sessionId, entry);
220
- pty.onData((chunk) => {
221
- entry.info.totalOutputChars += chunk.length;
222
- // Inject the synthesized cmd banner into the FIRST chunk the
223
- // subscribers see, so SSE/streaming attaches still see it even
224
- // though node-pty already missed cmd's real banner.
225
- if (entry.pendingBanner !== undefined) {
226
- chunk = entry.pendingBanner + chunk;
227
- entry.pendingBanner = undefined;
228
- }
229
- entry.buffer.push(chunk);
230
- entry.bufferChars += chunk.length;
231
- while (entry.bufferChars > MAX_BUFFER_CHARS && entry.buffer.length > 1) {
232
- const dropped = entry.buffer.shift();
233
- entry.bufferChars -= dropped?.length ?? 0;
234
- }
235
- for (const notify of entry.subscribers) {
236
- try {
237
- notify(chunk);
238
- }
239
- catch {
240
- // subscriber callbacks must never break the pty stream
241
- }
242
- }
243
- });
244
- pty.onExit(({ exitCode }) => {
245
- entry.info.exited = true;
246
- entry.info.exitCode = exitCode;
247
- for (const notify of entry.subscribers) {
248
- try {
249
- notify(`\r\n\u001b[90m[terminal exited: code ${exitCode}]\u001b[0m\r\n`);
250
- }
251
- catch {
252
- // ignore
253
- }
254
- }
255
- });
256
- return { ...entry.info };
257
- }
258
- list(ownerSessionId) {
259
- const all = [...this.entries.values()].map((e) => ({ ...e.info }));
260
- return ownerSessionId
261
- ? all.filter((i) => i.ownerSessionId === ownerSessionId)
262
- : all;
263
- }
264
- get(sessionId) {
265
- const entry = this.entries.get(sessionId);
266
- if (!entry)
267
- throw new TerminalSessionError("TERMINAL_NOT_FOUND", sessionId);
268
- return { ...entry.info };
269
- }
270
- mustGet(sessionId) {
271
- const entry = this.entries.get(sessionId);
272
- if (!entry)
273
- throw new TerminalSessionError("TERMINAL_NOT_FOUND", sessionId);
274
- return entry;
275
- }
276
- /** Full buffered output (post-trim prefix may be incomplete). */
277
- readAll(sessionId) {
278
- const entry = this.mustGet(sessionId);
279
- let text = entry.buffer.join("");
280
- // If the caller reads before any real PTY output has arrived, the
281
- // banner injection in onData hasn't fired yet — fold it in here.
282
- if (entry.pendingBanner !== undefined) {
283
- text = entry.pendingBanner + text;
284
- entry.pendingBanner = undefined;
285
- }
286
- return {
287
- text,
288
- truncated: entry.info.totalOutputChars > entry.bufferChars,
289
- totalOutputChars: entry.info.totalOutputChars,
290
- };
291
- }
292
- /** Subscribe to live output chunks; returns unsubscribe. */
293
- subscribe(sessionId, onChunk) {
294
- const entry = this.mustGet(sessionId);
295
- entry.subscribers.add(onChunk);
296
- return () => entry.subscribers.delete(onChunk);
297
- }
298
- send(sessionId, data) {
299
- const entry = this.mustGet(sessionId);
300
- if (entry.info.exited)
301
- throw new TerminalSessionError("TERMINAL_EXITED", "terminal already exited");
302
- entry.pty.write(data);
303
- }
304
- /** Resize the PTY (console UI attach). */
305
- resize(sessionId, cols, rows) {
306
- const entry = this.mustGet(sessionId);
307
- if (entry.info.exited)
308
- return;
309
- entry.pty.resize(cols, rows);
310
- }
311
- /** Send an interrupt character (Ctrl+C) to the foreground process. */
312
- interrupt(sessionId) {
313
- const entry = this.mustGet(sessionId);
314
- if (entry.info.exited)
315
- return;
316
- entry.pty.write("\x03");
317
- }
318
- /** Kill and wait for full stop, then remove the registry entry so closed
319
- * terminals disappear from the list (explicit close = forget). Idempotent. */
320
- async kill(sessionId) {
321
- const entry = this.entries.get(sessionId);
322
- if (!entry)
323
- throw new TerminalSessionError("TERMINAL_NOT_FOUND", sessionId);
324
- if (!entry.info.exited) {
325
- entry.disposing ??= new Promise((resolve) => {
326
- const timer = setTimeout(() => resolve(), 5_000);
327
- entry.pty.onExit(() => {
328
- clearTimeout(timer);
329
- resolve();
330
- });
331
- try {
332
- entry.pty.kill();
333
- }
334
- catch {
335
- clearTimeout(timer);
336
- resolve();
337
- }
338
- });
339
- await entry.disposing;
340
- }
341
- this.entries.delete(sessionId);
342
- }
343
- /** Kill every session owned by one chat session; awaited. */
344
- async disposeOwner(ownerSessionId) {
345
- const owned = this.list(ownerSessionId).map((i) => i.sessionId);
346
- await Promise.all(owned.map((id) => this.kill(id).catch(() => undefined)));
347
- }
348
- }
@@ -1,211 +0,0 @@
1
- /**
2
- * Operator Chat — model-facing persistent terminal tools (DSH-style).
3
- *
4
- * Registers `terminal` (single tool managing named persistent shells) so the
5
- * model can run commands in a REAL persistent PTY: cwd, env vars, activated
6
- * virtualenvs and background jobs survive across calls, exactly like the DSH
7
- * persistent bash/pwsh tools.
8
- *
9
- * Protocol (completion-marker, same idea as DSH):
10
- * 1. tool writes `command + unique echo marker + newline`
11
- * 2. waits until the marker line appears in PTY output
12
- * 3. returns output captured since the send (bounded), plus exit-code echo
13
- *
14
- * Lifecycle: shells are owned by the chat session id; `terminal_list`,
15
- * `terminal_kill` manage them. Console UI can attach to the same PTYs via the
16
- * terminal HTTP API (same registry).
17
- */
18
- import { TerminalSessionError, availableShellKinds, } from "./terminal-sessions.js";
19
- const MAX_OUTPUT_CHARS = 16_000;
20
- const DEFAULT_TIMEOUT_MS = 120_000;
21
- const MARKER_PREFIX = "__DSH_TERM_DONE_";
22
- const TOOL_DESCRIPTION = "Run commands in a persistent terminal (real PTY). State survives across calls: cwd, env vars, activated environments, functions, and background jobs. " +
23
- "Actions: 'run' executes a command in the owner's shell of the given kind (auto-creates on first use, reuses afterwards; avoid interactive commands that wait for stdin); " +
24
- "'list' shows active shells; 'kill' stops one. Prefer this over one-shot bash for multi-step work needing shared state (build → test → inspect) or long-running dev servers. " +
25
- "Output is bounded; long output keeps the earliest readable prefix. On timeout the command output so far is returned (the shell keeps running — use 'kill' or run an interrupt to stop it). " +
26
- "The shell parameter accepts only kinds available on the host platform.";
27
- export async function buildPersistentTerminalTools(deps) {
28
- const [{ Type }, { defineTool }] = await Promise.all([
29
- import("typebox"),
30
- import("@earendil-works/pi-coding-agent"),
31
- ]);
32
- /** Resolve the owner's shell for a kind, spawning on first use. */
33
- const ensureShell = (kind, cwd) => {
34
- const existing = deps.registry
35
- .list(deps.ownerSessionId)
36
- .find((s) => s.shell === kind && !s.exited);
37
- if (existing)
38
- return existing;
39
- return deps.registry.spawn({
40
- ownerSessionId: deps.ownerSessionId,
41
- name: kind,
42
- shell: kind,
43
- cwd: cwd ?? deps.defaultCwd ?? process.cwd(),
44
- });
45
- };
46
- const runInShell = async (kind, command, timeoutMs) => {
47
- const shellInfo = ensureShell(kind);
48
- const marker = `${MARKER_PREFIX}${Date.now().toString(36)}${Math.random()
49
- .toString(36)
50
- .slice(2, 8)}`;
51
- // Exit-code echo per shell:
52
- // - PowerShell: $LASTEXITCODE
53
- // - bash / zsh: $?
54
- // - cmd.exe: %ERRORLEVEL% (interpolation happens before echo runs,
55
- // so we use `prompt` substitution style `echo MARKER:%ERRORLEVEL%`).
56
- const exitEcho = kind === "powershell"
57
- ? `Write-Host "${marker}:$LASTEXITCODE"`
58
- : kind === "cmd"
59
- ? `echo ${marker}:%ERRORLEVEL%`
60
- : `echo "${marker}:$?"`;
61
- const wrapped = `${command}\n${exitEcho}\n`;
62
- // Capture output from the moment of send until the marker line shows up.
63
- const pending = [];
64
- let settled = false;
65
- const unsubscribe = deps.registry.subscribe(shellInfo.sessionId, (chunk) => {
66
- pending.push(chunk);
67
- });
68
- // Drain anything already in the ring buffer boundary: we only want output
69
- // produced after this send, so snapshot totalOutputChars first.
70
- const startTotal = deps.registry.get(shellInfo.sessionId).totalOutputChars;
71
- // Re-read after subscribing; chunks between snapshot and send are benign
72
- // (they precede the marker) and get filtered by the marker search below.
73
- let captured = "";
74
- const collect = () => {
75
- captured = pending.join("");
76
- };
77
- try {
78
- deps.registry.send(shellInfo.sessionId, wrapped);
79
- }
80
- catch (error) {
81
- unsubscribe();
82
- return `error: ${error instanceof TerminalSessionError
83
- ? `${error.code} ${error.message}`
84
- : error instanceof Error
85
- ? error.message
86
- : String(error)}`;
87
- }
88
- const deadline = Date.now() + timeoutMs;
89
- const markerRe = new RegExp(`${marker}:(\\d+)\\s*(${"\\r"}|\\n|$)`);
90
- let exitCode;
91
- return await new Promise((resolve) => {
92
- const poll = () => {
93
- collect();
94
- const text = pending.join("");
95
- const match = markerRe.exec(text);
96
- if (match) {
97
- settled = true;
98
- exitCode = Number(match[1]);
99
- // Output up to the start of the marker line.
100
- const output = text.slice(0, match.index);
101
- finish(output);
102
- return;
103
- }
104
- if (Date.now() > deadline) {
105
- settled = true;
106
- finish(text, true);
107
- return;
108
- }
109
- setTimeout(poll, 120);
110
- };
111
- const finish = (output, timedOut = false) => {
112
- unsubscribe();
113
- let result = stripAnsi(output).trimEnd();
114
- if (result.length > MAX_OUTPUT_CHARS) {
115
- result =
116
- result.slice(0, MAX_OUTPUT_CHARS) +
117
- `\n…[truncated ${result.length - MAX_OUTPUT_CHARS} chars]`;
118
- }
119
- if (exitCode !== undefined && exitCode !== 0) {
120
- result += `\n[exit code: ${exitCode}]`;
121
- }
122
- if (timedOut) {
123
- result += `\n[timeout after ${timeoutMs}ms — shell still running; output may be partial]`;
124
- }
125
- resolve(result || "(no output)");
126
- };
127
- poll();
128
- });
129
- };
130
- const kinds = availableShellKinds().kinds;
131
- // Default precedence: bash (most posix-feel for cross-platform models) →
132
- // cmd (native Windows shell, offered when bash is absent) → zsh (macOS
133
- // default on some dev boxes) → powershell (universal Windows fallback).
134
- const defaultKind = kinds.includes("bash")
135
- ? "bash"
136
- : kinds.includes("cmd")
137
- ? "cmd"
138
- : kinds.includes("zsh")
139
- ? "zsh"
140
- : "powershell";
141
- const kindLabels = {
142
- powershell: "PowerShell",
143
- bash: "bash",
144
- zsh: "zsh",
145
- cmd: "cmd",
146
- };
147
- const tool = defineTool({
148
- name: "terminal",
149
- label: "Terminal",
150
- description: TOOL_DESCRIPTION,
151
- promptSnippet: `terminal: persistent PTY shells (${kinds.join(" + ")}). State (cwd/env/jobs) survives across calls. Actions: run / list / kill.`,
152
- parameters: Type.Object({
153
- action: Type.Union([Type.Literal("run"), Type.Literal("list"), Type.Literal("kill")], {
154
- description: "run = execute command; list = active shells; kill = stop a shell",
155
- }),
156
- command: Type.Optional(Type.String({ description: "Command for action=run (no trailing newline)." })),
157
- shell: Type.Optional(Type.Union(kinds.map((k) => Type.Literal(k)), {
158
- description: `Shell kind for action=run (available on this platform: ${kinds.map((k) => kindLabels[k]).join(", ")}). Default ${kindLabels[defaultKind]}.`,
159
- })),
160
- sessionId: Type.Optional(Type.String({ description: "Terminal session id for action=kill." })),
161
- timeoutMs: Type.Optional(Type.Number({ description: "Wall-clock limit for action=run. Default 120000." })),
162
- }, { additionalProperties: false }),
163
- async execute(_toolCallId, params, signal) {
164
- const action = params.action;
165
- try {
166
- if (action === "list") {
167
- const shells = deps.registry.list(deps.ownerSessionId);
168
- return text(JSON.stringify(shells.map(({ sessionId, shell, name, exited, exitCode, createdAt }) => ({
169
- sessionId,
170
- shell,
171
- name,
172
- exited,
173
- ...(exitCode !== undefined ? { exitCode } : {}),
174
- createdAt,
175
- })), null, 2));
176
- }
177
- if (action === "kill") {
178
- if (!params.sessionId)
179
- return text(`error: sessionId required for kill`);
180
- const info = deps.registry.list(deps.ownerSessionId).find((s) => s.sessionId === params.sessionId);
181
- if (!info)
182
- return text(`error: no terminal ${params.sessionId} owned by this chat session`);
183
- await deps.registry.kill(params.sessionId);
184
- return text(`terminal ${params.sessionId} stopped`);
185
- }
186
- // action === "run"
187
- const command = (params.command ?? "").trim();
188
- if (!command)
189
- return text("error: command required for action=run");
190
- const kind = kinds.includes(params.shell)
191
- ? params.shell
192
- : defaultKind;
193
- const timeoutMs = Math.min(Math.max(params.timeoutMs ?? DEFAULT_TIMEOUT_MS, 1_000), 600_000);
194
- const output = await runInShell(kind, command, timeoutMs);
195
- return text(output);
196
- }
197
- catch (error) {
198
- return text(`error: ${error instanceof Error ? error.message : String(error)}`);
199
- }
200
- },
201
- });
202
- return [tool];
203
- }
204
- function text(body) {
205
- return { content: [{ type: "text", text: body }], details: { output: body } };
206
- }
207
- /** Minimal ANSI escape stripper for tool-result readability. */
208
- export function stripAnsi(input) {
209
- // eslint-disable-next-line no-control-regex
210
- return input.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
211
- }
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-CQA4Nzmo.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D9UA7hUb.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D9UA7hUb.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};