@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.
- package/CHANGELOG.md +9 -140
- package/dist/application/task-lifecycle/advance.js +4 -17
- package/dist/application/task-lifecycle/observe.js +29 -43
- package/dist/application/task-lifecycle/plan-transitions.js +4 -5
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +55 -20
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/executors/dag-pi-executor.js +230 -269
- package/dist/executors/pi-executor.js +0 -2
- package/dist/executors/pi-sdk-executor.js +39 -77
- package/dist/executors/shell-executor.js +2 -3
- package/dist/executors/shell-write-guard.js +32 -27
- package/dist/shared/pi-context-pressure/extension.js +16 -26
- package/dist/shared/pi-retry-settings.js +0 -40
- package/dist/shared/prompts.js +26 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +23 -48
- package/dist/task/source-state.js +1 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/worker/console/chat/assistant-content.js +2 -23
- package/dist/worker/console/chat/explore-tools.js +0 -13
- package/dist/worker/console/chat/pi-runtime.js +1182 -221
- package/dist/worker/console/chat/routes.js +71 -213
- package/dist/worker/console/chat/sdd-data-alignment.js +9 -65
- package/dist/worker/console/chat/session-catalog.js +0 -32
- package/dist/worker/console/chat/session-store.js +6 -22
- package/dist/worker/console/chat/shortcuts.js +0 -6
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/tool-preview.js +0 -11
- package/dist/worker/console/chat/tools.js +0 -6
- package/dist/worker/console/console-update-and-init.js +19 -1
- package/dist/worker/console/console-update-runtime.js +20 -5
- package/dist/worker/console/index.js +20 -0
- package/dist/worker/console/operator-actions.js +1 -13
- package/dist/worker/console/routes.js +0 -8
- package/dist/worker/console/server.js +29 -11
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-hUmM_GRf.js} +1 -1
- package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-Bt-xuieW.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DTqkL4DJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-Cy0MNnzV.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DDdlT0Pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-BpzRnZmU.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-3JawwNZ6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-DoTp7Cri.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-CcirhZaS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-VrG21gnP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DDOgIS70.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CBjmgvJ_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CCQSht8M.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-BpGMkUUV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DNDT41QL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-euXnDFbn.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BJ6NAeql.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-F5JKIvIq.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-Cwmkl_Gi.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-BmCZ8815.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-zWA3hUnX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-BzA-888F.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-tPNheoch.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-Cdi7Fyqp.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-DFK9VBf1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-xCUr1kpg.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-BANQHY_A.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-VHJYEMkP.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-DiyZQ0C6.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-CZtIeBBa.js} +1 -1
- package/dist/worker/console/static/assets/index-BlN6ytLr.css +1 -0
- package/dist/worker/console/static/assets/index-CA-jfWlE.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BuSspC0X.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DRJZyPFy.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-DIfXIVuE.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-CT10_7Lt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-ufemj74H.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DEQe8-_m.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-BRLUrbrC.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BgibKtiF.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-D3eyfdV9.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DXeTk5Px.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BgXdFEe7.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-CseC2A7R.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-D80PFzfJ.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BWzHW7LS.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-xWQYTLAn.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-CLxtpZeL.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-DwugtmWV.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-DHEOwHIe.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-pmbZKlBK.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-CivHoM5q.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BfHIE7o6.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +10 -122
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +0 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +15 -35
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +62 -136
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +68 -159
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +56 -75
- package/dist/worker/console/static-src/operator-chat/useComposer.js +78 -206
- package/dist/worker/console/workspace-context.js +1 -21
- package/dist/worker/observe/static/router.js +1 -1
- package/dist/worker/observe/static/styles.css +4 -7
- package/dist/worker/observe/static/views/dag-inspector.js +19 -12
- package/dist/worker/observe/static/views/dag.js +2 -2
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-design-policy.js +1 -2
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -8
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-repair.js +76 -55
- package/dist/workflows/dag/frontend-review-context.js +1 -2
- package/dist/workflows/dag/frontend-review-scopes.js +12 -34
- package/dist/workflows/dag/frontend-shape-facts.js +2 -12
- package/dist/workflows/dag/frontend-verification-trace.js +42 -8
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +160 -97
- package/dist/workflows/dynamic/index.js +7 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +8 -9
- package/package.json +3 -7
- package/skills/frontend-bounded-implement/SKILL.md +3 -4
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +1 -3
- package/skills/frontend-design-review/SKILL.md +7 -12
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +0 -19
- package/skills/frontend-plan/references/decision-contract.md +19 -26
- package/skills/frontend-review/SKILL.md +18 -12
- package/skills/frontend-review/references/review-findings.md +43 -24
- package/dist/shared/pi-provider-recovery.js +0 -182
- package/dist/worker/console/chat/browser-automation.js +0 -363
- package/dist/worker/console/chat/browser-policy.js +0 -113
- package/dist/worker/console/chat/browser-routes.js +0 -162
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +0 -44
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +0 -89
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +0 -202
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +0 -79
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +0 -86
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +0 -38
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +0 -28
- package/dist/worker/console/chat/pi-runtime/inspection.js +0 -439
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +0 -153
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +0 -5
- package/dist/worker/console/chat/scm-routes.js +0 -217
- package/dist/worker/console/chat/scm-service.js +0 -283
- package/dist/worker/console/chat/scm-tools.js +0 -111
- package/dist/worker/console/chat/session-mode-view.js +0 -49
- package/dist/worker/console/chat/session-mode.js +0 -220
- package/dist/worker/console/chat/terminal-routes.js +0 -216
- package/dist/worker/console/chat/terminal-sessions.js +0 -348
- package/dist/worker/console/chat/terminal-tools.js +0 -211
- package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
- package/dist/worker/console/static/assets/index-wXlQohNs.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +0 -10
- package/dist/worker/console/static-src/operator-chat/chat-link.js +0 -94
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +0 -24
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +0 -192
- package/dist/worker/console/static-src/operator-chat/process-label.js +0 -41
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +0 -13
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +0 -10
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +0 -51
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +0 -21
- package/dist/worker/console/workspace-initialization.js +0 -209
- package/dist/workflows/dag/frontend-capacity.js +0 -9
- package/dist/workflows/dag/frontend-plan-completeness.js +0 -186
- 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};
|