@tea-agent/loop-agent 0.43.0-next.3 → 0.43.0-next.4

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 (87) hide show
  1. package/CHANGELOG.md +15 -14
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/worker/console/chat/assistant-content.js +23 -2
  4. package/dist/worker/console/chat/explore-tools.js +13 -0
  5. package/dist/worker/console/chat/pi-runtime.js +18 -0
  6. package/dist/worker/console/chat/routes.js +11 -0
  7. package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
  8. package/dist/worker/console/chat/terminal-routes.js +216 -0
  9. package/dist/worker/console/chat/terminal-sessions.js +284 -0
  10. package/dist/worker/console/chat/terminal-tools.js +199 -0
  11. package/dist/worker/console/chat/tool-preview.js +11 -0
  12. package/dist/worker/console/chat/tools.js +1 -0
  13. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-hUmM_GRf.js → abnfDiagram-N423BO3Z-CCj36aaC.js} +1 -1
  14. package/dist/worker/console/static/assets/{arc-Bt-xuieW.js → arc-Cl3MIVFh.js} +1 -1
  15. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DTqkL4DJ.js → architectureDiagram-T3A2C74G-C_n4kMJS.js} +1 -1
  16. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Cy0MNnzV.js → blockDiagram-VBNYF7ZC-DlWYJ7cK.js} +1 -1
  17. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DDdlT0Pd.js → c4Diagram-5PPSVZJV-Ud5pS7vS.js} +1 -1
  18. package/dist/worker/console/static/assets/channel-CEMRpNm-.js +1 -0
  19. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-3JawwNZ6.js → chunk-2GRJ4B5K-CQPwOv_j.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DoTp7Cri.js → chunk-2Q5K7J3B-CMzjxrWI.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CcirhZaS.js → chunk-5RXB4S5H-pcnXnD6t.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-VrG21gnP.js → chunk-5VM5RSS4-CEB3AvU2.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DDOgIS70.js → chunk-6Q2QTUOP-DSKnbXbp.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CBjmgvJ_.js → chunk-GF5L2VYU-CS6-PjCZ.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CCQSht8M.js → chunk-JWPE2WC7-f6q8Tnzz.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BpGMkUUV.js → chunk-KBJHAD2P-DkB0QX3q.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DNDT41QL.js → chunk-RYQCIY6F-BdB4jAv6.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-euXnDFbn.js → chunk-XXDRQBXY-BVm1bnX6.js} +1 -1
  29. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +1 -0
  30. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +1 -0
  31. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BJ6NAeql.js → cose-bilkent-JH36ORCC-FV_P-IEp.js} +1 -1
  32. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-F5JKIvIq.js → cynefin-VYW2F7L2-BM7RtkA2.js} +1 -1
  33. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Cwmkl_Gi.js → cynefinDiagram-MW4NZA55-C1BZwTG5.js} +1 -1
  34. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BmCZ8815.js → dagre-VZM6K2ZE-C6McNKmY.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zWA3hUnX.js → diagram-7IWD3JNH-C0aWRUaE.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BzA-888F.js → diagram-B4RE2ZJO-Bfk_SKE8.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-tPNheoch.js → diagram-LBJQPF4R-prx1T4qf.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cdi7Fyqp.js → diagram-Q27KOJAE-DBvty0eU.js} +1 -1
  39. package/dist/worker/console/static/assets/{diagram-UB23O5K3-DFK9VBf1.js → diagram-UB23O5K3-CF0em8e9.js} +1 -1
  40. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-xCUr1kpg.js → ebnfDiagram-BXEA7PRR-CF90b4Ws.js} +1 -1
  41. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BANQHY_A.js → erDiagram-JOGREHBK-hfunxzof.js} +1 -1
  42. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-VHJYEMkP.js → flowDiagram-UKHOOZJN-D-QSy4Nl.js} +1 -1
  43. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiyZQ0C6.js → ganttDiagram-PKOTCBZU-DaBGNcvT.js} +1 -1
  44. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CZtIeBBa.js → gitGraphDiagram-DS77QQ5N-BChcubGL.js} +1 -1
  45. package/dist/worker/console/static/assets/index-CJkCY1XR.js +464 -0
  46. package/dist/worker/console/static/assets/index-DUBCbiqv.css +1 -0
  47. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BuSspC0X.js → infoDiagram-6WML65LV-BkC67fIa.js} +1 -1
  48. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DRJZyPFy.js → ishikawaDiagram-WSZJBQD7-ibhLczCD.js} +1 -1
  49. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DIfXIVuE.js → journeyDiagram-NVQOT4AX-DCRMitay.js} +1 -1
  50. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CT10_7Lt.js → kanban-definition-27J2QSJJ-DJ_hKaYI.js} +1 -1
  51. package/dist/worker/console/static/assets/{linear-ufemj74H.js → linear-CIlLsYsA.js} +1 -1
  52. package/dist/worker/console/static/assets/{mermaid.core-DEQe8-_m.js → mermaid.core-C-5IQMP_.js} +5 -5
  53. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BRLUrbrC.js → mindmap-definition-FAOFIHXS-TCzV5TQT.js} +1 -1
  54. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BgibKtiF.js → pegDiagram-VL7TDLO6-bfhd2BQH.js} +1 -1
  55. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-D3eyfdV9.js → pieDiagram-7S7Q4E2Y-BxFRAm5N.js} +1 -1
  56. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DXeTk5Px.js → quadrantDiagram-CIZ2JOQS-C-m12lxv.js} +1 -1
  57. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BgXdFEe7.js → railroadDiagram-AXF67PYL-CwmLiGgx.js} +1 -1
  58. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CseC2A7R.js → requirementDiagram-LRYGKXZP-oc0Q1jiD.js} +1 -1
  59. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-D80PFzfJ.js → sankeyDiagram-W5VNT64P-9Ght6Yur.js} +1 -1
  60. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BWzHW7LS.js → sequenceDiagram-SI44F4Z6-ndm3lyz9.js} +1 -1
  61. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-xWQYTLAn.js → sizeCapture-X5ZJPWSS-7sPlKVcw.js} +1 -1
  62. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CLxtpZeL.js → stateDiagram-OKZ733FA-Izl21-oS.js} +1 -1
  63. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +1 -0
  64. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DwugtmWV.js → swimlanes-SLNWSIFB-CtO3D65o.js} +2 -2
  65. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +8 -0
  66. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DHEOwHIe.js → timeline-definition-Z64GVDOM-Cyg5cY7z.js} +1 -1
  67. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-pmbZKlBK.js → vennDiagram-T6HMQDX7-CwtE_TFK.js} +1 -1
  68. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CivHoM5q.js → wardleyDiagram-T6FBY63Y-Dtt9v8OO.js} +1 -1
  69. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BfHIE7o6.js → xychartDiagram-ELKLHX3M-B2Y3ryb6.js} +1 -1
  70. package/dist/worker/console/static/index.html +2 -2
  71. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
  72. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
  73. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  74. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  75. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  76. package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
  77. package/dist/worker/console/static-src/operator-chat/useChatThread.js +39 -4
  78. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  79. package/dist/worker/console/workspace-context.js +9 -1
  80. package/package.json +6 -3
  81. package/dist/worker/console/static/assets/channel-BpzRnZmU.js +0 -1
  82. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +0 -1
  83. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +0 -1
  84. package/dist/worker/console/static/assets/index-BlN6ytLr.css +0 -1
  85. package/dist/worker/console/static/assets/index-CA-jfWlE.js +0 -449
  86. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +0 -1
  87. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +0 -8
@@ -0,0 +1,284 @@
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
+ /** Bounded ring buffer of raw output chunks (bytes preserved as utf8 text). */
20
+ const MAX_BUFFER_CHARS = 200_000;
21
+ export class TerminalSessionError extends Error {
22
+ code;
23
+ constructor(code, message) {
24
+ super(message);
25
+ this.code = code;
26
+ this.name = "TerminalSessionError";
27
+ }
28
+ }
29
+ const IS_WINDOWS = process.platform === "win32";
30
+ function firstExisting(paths) {
31
+ return paths.find((p) => existsSync(p));
32
+ }
33
+ /** Windows PowerShell 5+ or pwsh 7; prefer pwsh 7 when installed. */
34
+ function resolvePowershell() {
35
+ if (IS_WINDOWS) {
36
+ const pwsh7 = firstExisting([
37
+ "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
38
+ "C:\\Program Files (x86)\\PowerShell\\7\\pwsh.exe",
39
+ ]);
40
+ if (pwsh7) {
41
+ return {
42
+ file: pwsh7,
43
+ args: [
44
+ "-NoLogo",
45
+ "-NoProfile",
46
+ "-ExecutionPolicy",
47
+ "Bypass",
48
+ "-NoExit",
49
+ "-Command",
50
+ "$PSDefaultParameterValues['Out-File:Encoding']='utf8'; [Console]::OutputEncoding=[Text.Encoding]::UTF8",
51
+ ],
52
+ };
53
+ }
54
+ const ps5 = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
55
+ if (existsSync(ps5)) {
56
+ return {
57
+ file: ps5,
58
+ args: [
59
+ "-NoProfile",
60
+ "-ExecutionPolicy",
61
+ "Bypass",
62
+ "-NoExit",
63
+ "-Command",
64
+ "$PSDefaultParameterValues['Out-File:Encoding']='utf8'; [Console]::OutputEncoding=[Text.Encoding]::UTF8",
65
+ ],
66
+ };
67
+ }
68
+ return undefined;
69
+ }
70
+ // macOS / Linux: only pwsh 7 if explicitly installed.
71
+ const pwsh = firstExisting([
72
+ "/usr/local/bin/pwsh",
73
+ "/opt/homebrew/bin/pwsh",
74
+ "/usr/bin/pwsh",
75
+ ]);
76
+ return pwsh
77
+ ? { file: pwsh, args: ["-NoLogo", "-NoProfile"] }
78
+ : undefined;
79
+ }
80
+ /** bash: Git Bash on Windows, /bin/bash (or Homebrew) on unix-likes. */
81
+ function resolveBash() {
82
+ if (IS_WINDOWS) {
83
+ const file = firstExisting([
84
+ "C:\\Program Files\\Git\\bin\\bash.exe",
85
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
86
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
87
+ ]);
88
+ return file ? { file, args: ["-i", "-l"] } : undefined;
89
+ }
90
+ const file = firstExisting(["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash", "/opt/homebrew/bin/bash"]);
91
+ return file ? { file, args: ["-i"] } : undefined;
92
+ }
93
+ /** zsh: macOS default shell; on Linux only when installed. */
94
+ function resolveZsh() {
95
+ if (IS_WINDOWS)
96
+ return undefined;
97
+ const file = firstExisting(["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh", "/opt/homebrew/bin/zsh"]);
98
+ return file ? { file, args: ["-i"] } : undefined;
99
+ }
100
+ let cachedCatalog;
101
+ /** Platform shell catalog, computed once per process. Drives both the UI
102
+ * button set and the model tool's shell parameter enum. */
103
+ export function availableShellKinds() {
104
+ if (cachedCatalog)
105
+ return cachedCatalog;
106
+ const resolved = new Map();
107
+ const pwsh = resolvePowershell();
108
+ if (pwsh)
109
+ resolved.set("powershell", pwsh);
110
+ const bash = resolveBash();
111
+ if (bash)
112
+ resolved.set("bash", bash);
113
+ const zsh = resolveZsh();
114
+ if (zsh)
115
+ resolved.set("zsh", zsh);
116
+ cachedCatalog = { kinds: [...resolved.keys()], resolved };
117
+ return cachedCatalog;
118
+ }
119
+ export function resolveShellCommand(shell) {
120
+ const resolved = availableShellKinds().resolved.get(shell);
121
+ if (!resolved) {
122
+ throw new TerminalSessionError("TERMINAL_SPAWN_FAILED", `shell "${shell}" is not available on this platform (${process.platform})`);
123
+ }
124
+ return resolved;
125
+ }
126
+ export class TerminalSessionRegistry {
127
+ entries = new Map();
128
+ nextSeq = 0;
129
+ spawn(options) {
130
+ let file;
131
+ let args;
132
+ try {
133
+ ({ file, args } = resolveShellCommand(options.shell));
134
+ }
135
+ catch (error) {
136
+ throw new TerminalSessionError("TERMINAL_SPAWN_FAILED", `failed to spawn ${options.shell}: ${error instanceof Error ? error.message : String(error)}`);
137
+ }
138
+ const sessionId = `term-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
139
+ let pty;
140
+ try {
141
+ pty = ptySpawn(file, args, {
142
+ name: "xterm-256color",
143
+ cwd: options.cwd,
144
+ cols: options.cols ?? 120,
145
+ rows: options.rows ?? 30,
146
+ env: process.env ?? {},
147
+ encoding: "utf8",
148
+ });
149
+ }
150
+ catch (error) {
151
+ throw new TerminalSessionError("TERMINAL_SPAWN_FAILED", `failed to spawn ${options.shell}: ${error instanceof Error ? error.message : String(error)}`);
152
+ }
153
+ const entry = {
154
+ info: {
155
+ sessionId,
156
+ ownerSessionId: options.ownerSessionId,
157
+ name: options.name,
158
+ shell: options.shell,
159
+ createdAt: new Date().toISOString(),
160
+ totalOutputChars: 0,
161
+ exited: false,
162
+ },
163
+ pty,
164
+ buffer: [],
165
+ bufferChars: 0,
166
+ subscribers: new Set(),
167
+ disposing: undefined,
168
+ };
169
+ this.entries.set(sessionId, entry);
170
+ pty.onData((chunk) => {
171
+ entry.info.totalOutputChars += chunk.length;
172
+ entry.buffer.push(chunk);
173
+ entry.bufferChars += chunk.length;
174
+ while (entry.bufferChars > MAX_BUFFER_CHARS && entry.buffer.length > 1) {
175
+ const dropped = entry.buffer.shift();
176
+ entry.bufferChars -= dropped?.length ?? 0;
177
+ }
178
+ for (const notify of entry.subscribers) {
179
+ try {
180
+ notify(chunk);
181
+ }
182
+ catch {
183
+ // subscriber callbacks must never break the pty stream
184
+ }
185
+ }
186
+ });
187
+ pty.onExit(({ exitCode }) => {
188
+ entry.info.exited = true;
189
+ entry.info.exitCode = exitCode;
190
+ for (const notify of entry.subscribers) {
191
+ try {
192
+ notify(`\r\n\u001b[90m[terminal exited: code ${exitCode}]\u001b[0m\r\n`);
193
+ }
194
+ catch {
195
+ // ignore
196
+ }
197
+ }
198
+ });
199
+ return { ...entry.info };
200
+ }
201
+ list(ownerSessionId) {
202
+ const all = [...this.entries.values()].map((e) => ({ ...e.info }));
203
+ return ownerSessionId
204
+ ? all.filter((i) => i.ownerSessionId === ownerSessionId)
205
+ : all;
206
+ }
207
+ get(sessionId) {
208
+ const entry = this.entries.get(sessionId);
209
+ if (!entry)
210
+ throw new TerminalSessionError("TERMINAL_NOT_FOUND", sessionId);
211
+ return { ...entry.info };
212
+ }
213
+ mustGet(sessionId) {
214
+ const entry = this.entries.get(sessionId);
215
+ if (!entry)
216
+ throw new TerminalSessionError("TERMINAL_NOT_FOUND", sessionId);
217
+ return entry;
218
+ }
219
+ /** Full buffered output (post-trim prefix may be incomplete). */
220
+ readAll(sessionId) {
221
+ const entry = this.mustGet(sessionId);
222
+ return {
223
+ text: entry.buffer.join(""),
224
+ truncated: entry.info.totalOutputChars > entry.bufferChars,
225
+ totalOutputChars: entry.info.totalOutputChars,
226
+ };
227
+ }
228
+ /** Subscribe to live output chunks; returns unsubscribe. */
229
+ subscribe(sessionId, onChunk) {
230
+ const entry = this.mustGet(sessionId);
231
+ entry.subscribers.add(onChunk);
232
+ return () => entry.subscribers.delete(onChunk);
233
+ }
234
+ send(sessionId, data) {
235
+ const entry = this.mustGet(sessionId);
236
+ if (entry.info.exited)
237
+ throw new TerminalSessionError("TERMINAL_EXITED", "terminal already exited");
238
+ entry.pty.write(data);
239
+ }
240
+ /** Resize the PTY (console UI attach). */
241
+ resize(sessionId, cols, rows) {
242
+ const entry = this.mustGet(sessionId);
243
+ if (entry.info.exited)
244
+ return;
245
+ entry.pty.resize(cols, rows);
246
+ }
247
+ /** Send an interrupt character (Ctrl+C) to the foreground process. */
248
+ interrupt(sessionId) {
249
+ const entry = this.mustGet(sessionId);
250
+ if (entry.info.exited)
251
+ return;
252
+ entry.pty.write("\x03");
253
+ }
254
+ /** Kill and wait for full stop, then remove the registry entry so closed
255
+ * terminals disappear from the list (explicit close = forget). Idempotent. */
256
+ async kill(sessionId) {
257
+ const entry = this.entries.get(sessionId);
258
+ if (!entry)
259
+ throw new TerminalSessionError("TERMINAL_NOT_FOUND", sessionId);
260
+ if (!entry.info.exited) {
261
+ entry.disposing ??= new Promise((resolve) => {
262
+ const timer = setTimeout(() => resolve(), 5_000);
263
+ entry.pty.onExit(() => {
264
+ clearTimeout(timer);
265
+ resolve();
266
+ });
267
+ try {
268
+ entry.pty.kill();
269
+ }
270
+ catch {
271
+ clearTimeout(timer);
272
+ resolve();
273
+ }
274
+ });
275
+ await entry.disposing;
276
+ }
277
+ this.entries.delete(sessionId);
278
+ }
279
+ /** Kill every session owned by one chat session; awaited. */
280
+ async disposeOwner(ownerSessionId) {
281
+ const owned = this.list(ownerSessionId).map((i) => i.sessionId);
282
+ await Promise.all(owned.map((id) => this.kill(id).catch(() => undefined)));
283
+ }
284
+ }
@@ -0,0 +1,199 @@
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: PowerShell uses $LASTEXITCODE; bash/zsh use $?.
52
+ const exitEcho = kind === "powershell"
53
+ ? `Write-Host "${marker}:$LASTEXITCODE"`
54
+ : `echo "${marker}:$?"`;
55
+ const wrapped = `${command}\n${exitEcho}\n`;
56
+ // Capture output from the moment of send until the marker line shows up.
57
+ const pending = [];
58
+ let settled = false;
59
+ const unsubscribe = deps.registry.subscribe(shellInfo.sessionId, (chunk) => {
60
+ pending.push(chunk);
61
+ });
62
+ // Drain anything already in the ring buffer boundary: we only want output
63
+ // produced after this send, so snapshot totalOutputChars first.
64
+ const startTotal = deps.registry.get(shellInfo.sessionId).totalOutputChars;
65
+ // Re-read after subscribing; chunks between snapshot and send are benign
66
+ // (they precede the marker) and get filtered by the marker search below.
67
+ let captured = "";
68
+ const collect = () => {
69
+ captured = pending.join("");
70
+ };
71
+ try {
72
+ deps.registry.send(shellInfo.sessionId, wrapped);
73
+ }
74
+ catch (error) {
75
+ unsubscribe();
76
+ return `error: ${error instanceof TerminalSessionError
77
+ ? `${error.code} ${error.message}`
78
+ : error instanceof Error
79
+ ? error.message
80
+ : String(error)}`;
81
+ }
82
+ const deadline = Date.now() + timeoutMs;
83
+ const markerRe = new RegExp(`${marker}:(\\d+)\\s*(${"\\r"}|\\n|$)`);
84
+ let exitCode;
85
+ return await new Promise((resolve) => {
86
+ const poll = () => {
87
+ collect();
88
+ const text = pending.join("");
89
+ const match = markerRe.exec(text);
90
+ if (match) {
91
+ settled = true;
92
+ exitCode = Number(match[1]);
93
+ // Output up to the start of the marker line.
94
+ const output = text.slice(0, match.index);
95
+ finish(output);
96
+ return;
97
+ }
98
+ if (Date.now() > deadline) {
99
+ settled = true;
100
+ finish(text, true);
101
+ return;
102
+ }
103
+ setTimeout(poll, 120);
104
+ };
105
+ const finish = (output, timedOut = false) => {
106
+ unsubscribe();
107
+ let result = stripAnsi(output).trimEnd();
108
+ if (result.length > MAX_OUTPUT_CHARS) {
109
+ result =
110
+ result.slice(0, MAX_OUTPUT_CHARS) +
111
+ `\n…[truncated ${result.length - MAX_OUTPUT_CHARS} chars]`;
112
+ }
113
+ if (exitCode !== undefined && exitCode !== 0) {
114
+ result += `\n[exit code: ${exitCode}]`;
115
+ }
116
+ if (timedOut) {
117
+ result += `\n[timeout after ${timeoutMs}ms — shell still running; output may be partial]`;
118
+ }
119
+ resolve(result || "(no output)");
120
+ };
121
+ poll();
122
+ });
123
+ };
124
+ const kinds = availableShellKinds().kinds;
125
+ const defaultKind = kinds.includes("bash")
126
+ ? "bash"
127
+ : kinds.includes("zsh")
128
+ ? "zsh"
129
+ : "powershell";
130
+ const kindLabels = {
131
+ powershell: "PowerShell",
132
+ bash: "bash",
133
+ zsh: "zsh",
134
+ };
135
+ const tool = defineTool({
136
+ name: "terminal",
137
+ label: "Terminal",
138
+ description: TOOL_DESCRIPTION,
139
+ promptSnippet: `terminal: persistent PTY shells (${kinds.join(" + ")}). State (cwd/env/jobs) survives across calls. Actions: run / list / kill.`,
140
+ parameters: Type.Object({
141
+ action: Type.Union([Type.Literal("run"), Type.Literal("list"), Type.Literal("kill")], {
142
+ description: "run = execute command; list = active shells; kill = stop a shell",
143
+ }),
144
+ command: Type.Optional(Type.String({ description: "Command for action=run (no trailing newline)." })),
145
+ shell: Type.Optional(Type.Union(kinds.map((k) => Type.Literal(k)), {
146
+ description: `Shell kind for action=run (available on this platform: ${kinds.map((k) => kindLabels[k]).join(", ")}). Default ${kindLabels[defaultKind]}.`,
147
+ })),
148
+ sessionId: Type.Optional(Type.String({ description: "Terminal session id for action=kill." })),
149
+ timeoutMs: Type.Optional(Type.Number({ description: "Wall-clock limit for action=run. Default 120000." })),
150
+ }, { additionalProperties: false }),
151
+ async execute(_toolCallId, params, signal) {
152
+ const action = params.action;
153
+ try {
154
+ if (action === "list") {
155
+ const shells = deps.registry.list(deps.ownerSessionId);
156
+ return text(JSON.stringify(shells.map(({ sessionId, shell, name, exited, exitCode, createdAt }) => ({
157
+ sessionId,
158
+ shell,
159
+ name,
160
+ exited,
161
+ ...(exitCode !== undefined ? { exitCode } : {}),
162
+ createdAt,
163
+ })), null, 2));
164
+ }
165
+ if (action === "kill") {
166
+ if (!params.sessionId)
167
+ return text(`error: sessionId required for kill`);
168
+ const info = deps.registry.list(deps.ownerSessionId).find((s) => s.sessionId === params.sessionId);
169
+ if (!info)
170
+ return text(`error: no terminal ${params.sessionId} owned by this chat session`);
171
+ await deps.registry.kill(params.sessionId);
172
+ return text(`terminal ${params.sessionId} stopped`);
173
+ }
174
+ // action === "run"
175
+ const command = (params.command ?? "").trim();
176
+ if (!command)
177
+ return text("error: command required for action=run");
178
+ const kind = kinds.includes(params.shell)
179
+ ? params.shell
180
+ : defaultKind;
181
+ const timeoutMs = Math.min(Math.max(params.timeoutMs ?? DEFAULT_TIMEOUT_MS, 1_000), 600_000);
182
+ const output = await runInShell(kind, command, timeoutMs);
183
+ return text(output);
184
+ }
185
+ catch (error) {
186
+ return text(`error: ${error instanceof Error ? error.message : String(error)}`);
187
+ }
188
+ },
189
+ });
190
+ return [tool];
191
+ }
192
+ function text(body) {
193
+ return { content: [{ type: "text", text: body }], details: { output: body } };
194
+ }
195
+ /** Minimal ANSI escape stripper for tool-result readability. */
196
+ export function stripAnsi(input) {
197
+ // eslint-disable-next-line no-control-regex
198
+ return input.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
199
+ }
@@ -193,6 +193,17 @@ export function getToolPresentation(toolName) {
193
193
  }
194
194
  return { kind: "tool", label: raw };
195
195
  }
196
+ /** A tool call that failed on its own. A call interrupted by Stop carries
197
+ * `interrupted: true` and must never render (or count) as a failure — every
198
+ * error affordance goes through this single predicate (2026-09-08). */
199
+ export function isToolFailure(entry) {
200
+ return entry.status === "error" && entry.interrupted !== true;
201
+ }
202
+ /** Built-in subagent delegations surface as the `Agent` tool. DSH counts them
203
+ * in a separate「N 个 subagent」bucket instead of the tool-call bucket. */
204
+ export function isSubagentTool(toolName) {
205
+ return (toolName ?? "").trim().toLowerCase() === "agent";
206
+ }
196
207
  /** Section labels for ToolCard expand body. */
197
208
  export function getToolSectionLabels(toolName) {
198
209
  const name = (toolName ?? "").toLowerCase();
@@ -23,6 +23,7 @@ const EXECUTABLE_GATE_ONLY = new Set(EXECUTABLE_GATE_ONLY_ACTIONS);
23
23
  /** Built-in Chat custom tools that are not operator registry actions. */
24
24
  export const OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS = Object.freeze([
25
25
  OPERATOR_CHAT_AGENT_TOOL_NAME,
26
+ "terminal",
26
27
  ]);
27
28
  /**
28
29
  * Actions the model MAY invoke. Derived from the registry but filtered by the
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-DDOgIS70.js";import{p as f}from"./chunk-JWPE2WC7-CCQSht8M.js";import{_ as n,l as o}from"./mermaid.core-DEQe8-_m.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-F5JKIvIq.js";import"./index-CA-jfWlE.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-DSKnbXbp.js";import{p as f}from"./chunk-JWPE2WC7-f6q8Tnzz.js";import{_ as n,l as o}from"./mermaid.core-C-5IQMP_.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-BM7RtkA2.js";import"./index-CJkCY1XR.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-DEQe8-_m.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-C-5IQMP_.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};