@wrongstack/tools 0.313.0 → 0.316.1
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/dist/_edit-match.d.ts +3 -2
- package/dist/_output-spool.d.ts +4 -3
- package/dist/_shell-pick.d.ts +2 -1
- package/dist/_spawn-stream.d.ts +2 -1
- package/dist/_syntax-check.d.ts +2 -1
- package/dist/audit.js +10 -4055
- package/dist/bash-kill-guard.d.ts +3 -6
- package/dist/bash.js +9 -2308
- package/dist/batch-tool-use.js +2 -187
- package/dist/browser/index.js +25 -1339
- package/dist/browser/network-guard-proxy.d.ts +2 -1
- package/dist/builtin.js +69 -33949
- package/dist/chunk-22B7GE5T.js +260 -0
- package/dist/chunk-2VSZC6QM.js +113 -0
- package/dist/chunk-3PVYQFRQ.js +472 -0
- package/dist/chunk-42BHI7TN.js +337 -0
- package/dist/chunk-4BHUSMNL.js +104 -0
- package/dist/chunk-4I4LWFOO.js +431 -0
- package/dist/chunk-4UESMQDP.js +122 -0
- package/dist/chunk-553IQUMO.js +478 -0
- package/dist/chunk-5H4GSJAI.js +223 -0
- package/dist/chunk-6RNIFLG4.js +305 -0
- package/dist/chunk-7EY5DCRO.js +434 -0
- package/dist/chunk-A6BRWIXN.js +317 -0
- package/dist/chunk-AHHDWNXC.js +200 -0
- package/dist/chunk-BSLVR56S.js +199 -0
- package/dist/chunk-BTSFTGIL.js +7 -0
- package/dist/chunk-D33S4RQG.js +222 -0
- package/dist/chunk-DA4KNE2H.js +4412 -0
- package/dist/chunk-DR7JOQTC.js +520 -0
- package/dist/chunk-EEOFQDT6.js +898 -0
- package/dist/chunk-EJWSCQ6D.js +137 -0
- package/dist/chunk-EMRGN4KM.js +170 -0
- package/dist/chunk-FQZOHLG3.js +89 -0
- package/dist/chunk-GOIZNNOA.js +221 -0
- package/dist/chunk-HFXSNKCV.js +196 -0
- package/dist/chunk-HYX2TZ7O.js +1272 -0
- package/dist/chunk-I64BS3VC.js +15 -0
- package/dist/chunk-IVWV2UM6.js +955 -0
- package/dist/chunk-IXMNTNW5.js +238 -0
- package/dist/chunk-IYK7XGDZ.js +892 -0
- package/dist/chunk-J5SKD6LN.js +235 -0
- package/dist/chunk-JNNVRHMV.js +2111 -0
- package/dist/chunk-JOHXDF3D.js +295 -0
- package/dist/chunk-JRJJX3L5.js +424 -0
- package/dist/chunk-K3WNSHUF.js +1371 -0
- package/dist/chunk-L25CVUVR.js +2830 -0
- package/dist/chunk-LIJQMYYX.js +59 -0
- package/dist/chunk-LWKUTMD5.js +197 -0
- package/dist/chunk-MQX2KXVW.js +54 -0
- package/dist/chunk-MZZN7VCD.js +359 -0
- package/dist/chunk-NBDZMMG2.js +169 -0
- package/dist/chunk-NKJW5GFO.js +366 -0
- package/dist/chunk-NKNOBUBU.js +522 -0
- package/dist/chunk-O4MBV4AX.js +134 -0
- package/dist/chunk-OUBDL3WH.js +3515 -0
- package/dist/chunk-PQKK5JH7.js +101 -0
- package/dist/chunk-PY2IOS7C.js +1000 -0
- package/dist/chunk-Q5JPPCZC.js +12 -0
- package/dist/chunk-QBV725YG.js +128 -0
- package/dist/chunk-QZQSPUBF.js +315 -0
- package/dist/chunk-RGISBZBD.js +231 -0
- package/dist/chunk-RJZQWEMS.js +84 -0
- package/dist/chunk-RXISBFR5.js +315 -0
- package/dist/chunk-S5CCLZL3.js +99 -0
- package/dist/chunk-SDLVH5RZ.js +178 -0
- package/dist/chunk-T2F4S7U4.js +1723 -0
- package/dist/chunk-TEBQ4VPZ.js +163 -0
- package/dist/chunk-U4D5S3VQ.js +21 -0
- package/dist/chunk-U4VAATKP.js +193 -0
- package/dist/chunk-UCYQTZ7F.js +113 -0
- package/dist/chunk-UKU2ZX4C.js +203 -0
- package/dist/chunk-UNSKG6DC.js +159 -0
- package/dist/chunk-V43EQ6VR.js +68 -0
- package/dist/chunk-VB3MJGCV.js +610 -0
- package/dist/chunk-VFKPWSOX.js +154 -0
- package/dist/chunk-VJNHFZ7J.js +190 -0
- package/dist/chunk-VXECAJJ7.js +1091 -0
- package/dist/chunk-WHIIIREL.js +223 -0
- package/dist/chunk-Z6DAKQ3U.js +1 -0
- package/dist/chunk-ZJQ4IGXK.js +474 -0
- package/dist/chunk-ZLTFLC25.js +385 -0
- package/dist/circuit-breaker.js +3 -191
- package/dist/codebase-index/ast-invariant-engine.d.ts +5 -4
- package/dist/codebase-index/binary-frame.d.ts +0 -2
- package/dist/codebase-index/bm25.d.ts +1 -1
- package/dist/codebase-index/circuit-breaker.d.ts +2 -1
- package/dist/codebase-index/codebase-ast-replace-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +4 -3
- package/dist/codebase-index/codebase-invariant-check-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-repo-map-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-skeleton-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-targeted-test-tool.d.ts +3 -2
- package/dist/codebase-index/content-hash.d.ts +0 -2
- package/dist/codebase-index/import-extractor.d.ts +0 -13
- package/dist/codebase-index/index-service.d.ts +1 -1
- package/dist/codebase-index/index.js +84 -13688
- package/dist/codebase-index/languages.d.ts +2 -1
- package/dist/codebase-index/module-roots.d.ts +3 -2
- package/dist/codebase-index/parser-batch.d.ts +0 -5
- package/dist/codebase-index/parser-dispatch.d.ts +2 -1
- package/dist/codebase-index/parser-output.d.ts +4 -3
- package/dist/codebase-index/parser-worker-pool.d.ts +0 -2
- package/dist/codebase-index/parser-worker-script.d.ts +1 -16
- package/dist/codebase-index/parser-worker-script.js +8 -3995
- package/dist/codebase-index/project-server-client-state.d.ts +2 -2
- package/dist/codebase-index/project-server-endpoint.d.ts +0 -15
- package/dist/codebase-index/project-server.js +60 -8548
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/tree-sitter-parser.d.ts +0 -2
- package/dist/codebase-index/ts-parser.d.ts +1 -1
- package/dist/codebase-index/vector-search.d.ts +2 -1
- package/dist/codebase-index/wal-maintenance.d.ts +3 -2
- package/dist/codebase-index/worker.js +20 -8416
- package/dist/codebase-index/writer-admin.d.ts +0 -1
- package/dist/codebase-index/writer-search.d.ts +0 -9
- package/dist/design.js +2 -299
- package/dist/diff.js +4 -256
- package/dist/document.js +4 -222
- package/dist/e2e.js +5 -476
- package/dist/edit.js +14 -10200
- package/dist/exec.js +16 -3131
- package/dist/fetch.js +8 -398
- package/dist/format.js +10 -3750
- package/dist/generic-parser-VUXDIAOZ.js +13 -0
- package/dist/git.js +4 -380
- package/dist/glob.js +6 -280
- package/dist/go-parser-JXYKJHRC.js +469 -0
- package/dist/grep.js +7 -604
- package/dist/index.js +318 -35387
- package/dist/install.js +10 -4091
- package/dist/json-parser-IBHZ4BZC.js +213 -0
- package/dist/json.js +4 -665
- package/dist/kanban-contract-actions.d.ts +1 -1
- package/dist/kanban-detail-actions.d.ts +2 -1
- package/dist/kanban-presence.d.ts +2 -1
- package/dist/kanban-serializer.d.ts +0 -8
- package/dist/kanban-split-task-handler.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +2 -1
- package/dist/kanban.js +3 -2096
- package/dist/languages/diagnostics.d.ts +2 -1
- package/dist/languages/index.js +23 -4664
- package/dist/languages/legacy-bridge.d.ts +3 -2
- package/dist/languages/types.d.ts +2 -1
- package/dist/legacy-bridge-7M6QLUQN.js +18 -0
- package/dist/lint.js +10 -3706
- package/dist/logs.js +4 -287
- package/dist/memory.js +6 -215
- package/dist/mode.js +3 -99
- package/dist/outdated.js +4 -5170
- package/dist/pack.js +65 -33897
- package/dist/parser-dispatch-QH4XPGG6.js +14 -0
- package/dist/patch.js +13 -9935
- package/dist/plan.js +5 -3286
- package/dist/process-registry.js +8 -651
- package/dist/ps-slash.js +12 -1439
- package/dist/pwsh.d.ts +0 -1
- package/dist/py-parser-IS4HKYMF.js +16 -0
- package/dist/read.js +13 -9816
- package/dist/replace.js +15 -10014
- package/dist/rs-parser-XHUUO2KZ.js +81 -0
- package/dist/scaffold.js +4 -253
- package/dist/search.js +5 -585
- package/dist/session-kanban-sync.d.ts +0 -6
- package/dist/session-kanban.js +30 -972
- package/dist/skill.js +2 -215
- package/dist/spawn-background.d.ts +1 -1
- package/dist/task.js +5 -3410
- package/dist/test.js +10 -3773
- package/dist/todo.js +4 -2891
- package/dist/tool-help.js +3 -123
- package/dist/tool-search.js +3 -79
- package/dist/tool-tier.js +68 -33984
- package/dist/tool-use.js +2 -93
- package/dist/tree-sitter-parser-AIERW3YI.js +15 -0
- package/dist/tree.js +4 -288
- package/dist/ts-parser-RCTYKSVI.js +232 -0
- package/dist/typecheck.js +10 -3753
- package/dist/win32.js +6 -60
- package/dist/write.js +14 -9882
- package/dist/yaml-parser-4SAXV3PL.js +157 -0
- package/package.json +5 -5
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildChildEnv
|
|
3
|
+
} from "./chunk-BTSFTGIL.js";
|
|
4
|
+
import {
|
|
5
|
+
getPersistentProcessRegistry
|
|
6
|
+
} from "./chunk-NKNOBUBU.js";
|
|
7
|
+
import {
|
|
8
|
+
createOutputSpool,
|
|
9
|
+
spoolNote
|
|
10
|
+
} from "./chunk-2VSZC6QM.js";
|
|
11
|
+
import {
|
|
12
|
+
getProcessRegistry,
|
|
13
|
+
redactCommand
|
|
14
|
+
} from "./chunk-3PVYQFRQ.js";
|
|
15
|
+
import {
|
|
16
|
+
resolvePowerShell
|
|
17
|
+
} from "./chunk-V43EQ6VR.js";
|
|
18
|
+
import {
|
|
19
|
+
normalizeCommandOutput
|
|
20
|
+
} from "./chunk-D33S4RQG.js";
|
|
21
|
+
|
|
22
|
+
// src/bash.ts
|
|
23
|
+
import { spawn } from "node:child_process";
|
|
24
|
+
import * as os2 from "node:os";
|
|
25
|
+
import { StringDecoder } from "node:string_decoder";
|
|
26
|
+
import {
|
|
27
|
+
emitProcessCompleted,
|
|
28
|
+
emitProcessOutput,
|
|
29
|
+
emitProcessStarted
|
|
30
|
+
} from "@wrongstack/core/observability";
|
|
31
|
+
|
|
32
|
+
// src/_shell-pick.ts
|
|
33
|
+
var POSIX_DEFAULT = "cmd";
|
|
34
|
+
function pickShell(platform3, command, env) {
|
|
35
|
+
if (platform3 !== "win32") return POSIX_DEFAULT;
|
|
36
|
+
const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
|
|
37
|
+
if (override === "cmd" || override === "cmd.exe") return "cmd";
|
|
38
|
+
if (override === "powershell" || override === "powershell.exe") return "powershell";
|
|
39
|
+
if (override === "pwsh" || override === "pwsh.exe") return "pwsh";
|
|
40
|
+
if (looksLikePowerShell(command)) return "pwsh";
|
|
41
|
+
return "cmd";
|
|
42
|
+
}
|
|
43
|
+
function looksLikePowerShell(command) {
|
|
44
|
+
if (!command) return false;
|
|
45
|
+
const trimmed = command.trimStart();
|
|
46
|
+
if (/\.ps1\b/i.test(trimmed)) return true;
|
|
47
|
+
if (/^\s*#requires\s/i.test(trimmed)) return true;
|
|
48
|
+
if (/^\s*param\s*\(/i.test(trimmed)) return true;
|
|
49
|
+
if (/\$[\w:{]/i.test(trimmed)) return true;
|
|
50
|
+
if (/\$\(/.test(trimmed)) return true;
|
|
51
|
+
if (/@\s*['"]/.test(trimmed)) return true;
|
|
52
|
+
if (/&\s+\$/.test(trimmed)) return true;
|
|
53
|
+
if (/(^|\s)@\s*\(/.test(trimmed)) return true;
|
|
54
|
+
if (/(^|\s)@\{/.test(trimmed)) return true;
|
|
55
|
+
if (/(?:^|[\s[({,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\])},;])/i.test(
|
|
56
|
+
trimmed
|
|
57
|
+
)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (PS_VERB_RE.test(trimmed)) return true;
|
|
61
|
+
if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps)\b/i.test(trimmed)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (looksLikePowerShellExtended(command)) return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function looksLikePowerShellExtended(command) {
|
|
68
|
+
if (!command) return false;
|
|
69
|
+
const trimmed = command.trimStart();
|
|
70
|
+
if (/(?:^|\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\s]+|\s|=|$)/i.test(trimmed)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (/(?:^|[\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\s|$)/i.test(
|
|
74
|
+
trimmed
|
|
75
|
+
)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (/\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\s|$)/i.test(trimmed)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\/i.test(trimmed)) return true;
|
|
82
|
+
if (/\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\.)/i.test(trimmed)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
|
|
86
|
+
if (/(?:^|\s)[-//](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(
|
|
87
|
+
trimmed
|
|
88
|
+
)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function wrapPowerShellScript(command) {
|
|
94
|
+
const bootstrap = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$ConfirmPreference='None';$WhatIfPreference=$false";
|
|
95
|
+
return bootstrap + "\n$ErrorActionPreference='Stop'\n" + command + "\nif ($LASTEXITCODE -is [int]) { exit $LASTEXITCODE }";
|
|
96
|
+
}
|
|
97
|
+
var PS_VERB_RE = new RegExp(
|
|
98
|
+
// Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.
|
|
99
|
+
"(?:^|[\\s;&|\\(\\{,])(?:Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send)-[A-Za-z][A-Za-z0-9]+(?:[\\-\\+][A-Za-z][A-Za-z0-9]+)*(?:$|[\\s\\-\\;\\&\\|\\(\\)\\{\\},])",
|
|
100
|
+
"i"
|
|
101
|
+
);
|
|
102
|
+
function shellArgs(shell) {
|
|
103
|
+
if (shell === "powershell" || shell === "pwsh") {
|
|
104
|
+
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand"];
|
|
105
|
+
}
|
|
106
|
+
return ["/c"];
|
|
107
|
+
}
|
|
108
|
+
function diagnoseBashism(command, shell) {
|
|
109
|
+
if (!command) return void 0;
|
|
110
|
+
const isCmd = shell === "cmd";
|
|
111
|
+
const hints = [];
|
|
112
|
+
const add = (h) => {
|
|
113
|
+
if (!hints.includes(h)) hints.push(h);
|
|
114
|
+
};
|
|
115
|
+
if (/\/dev\/null/.test(command)) {
|
|
116
|
+
add(
|
|
117
|
+
isCmd ? "use `nul` instead of `/dev/null` (e.g. `2>nul`)" : "use `$null` instead of `/dev/null` (e.g. `2>$null`)"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (/(^|[;&|]\s*)export\s+[A-Za-z_]\w*=/.test(command)) {
|
|
121
|
+
add(
|
|
122
|
+
isCmd ? "set env vars with `set NAME=value`, not `export`" : "set env vars with `$env:NAME = 'value'`, not `export`"
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (/<<-?\s*['"]?[A-Za-z_]\w*/.test(command)) {
|
|
126
|
+
add(
|
|
127
|
+
isCmd ? "cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines" : "PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (shell === "powershell" && /(&&|\|\|)/.test(command)) {
|
|
131
|
+
add(
|
|
132
|
+
"Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (/\brm\s+-[A-Za-z]*[rf]/.test(command)) {
|
|
136
|
+
add(
|
|
137
|
+
isCmd ? "`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)" : "use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (/\bwhich\s+\S/.test(command)) {
|
|
141
|
+
add(
|
|
142
|
+
isCmd ? "use `where <cmd>` instead of `which`" : "use `Get-Command <cmd>` instead of `which`"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (hints.length === 0) return void 0;
|
|
146
|
+
const label = isCmd ? "cmd.exe" : shell === "pwsh" ? "PowerShell 7" : "Windows PowerShell";
|
|
147
|
+
return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join("; ")}. Rewrite it in ${isCmd ? "cmd" : "PowerShell"} syntax and retry.`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/bash-kill-guard.ts
|
|
151
|
+
import * as os from "node:os";
|
|
152
|
+
var isWin = os.platform() === "win32";
|
|
153
|
+
var SCRIPT_KILL_RE = /^(?:\.\\|\.\/)?(?:kill|terminate|stop)\S*\.(?:ps1|bat|cmd|sh)(?:\s|$)/i;
|
|
154
|
+
var SCRIPT_KILL_RE_POSIX = /^(?:\.\/)?(?:kill|terminate|stop)\S*\.sh(?:\s|$)/i;
|
|
155
|
+
var SCRIPT_KILL_FALLBACK_RE = /^\S*(?:kill|terminate|stop)\S*\.(?:ps1|bat|cmd|sh)\b/i;
|
|
156
|
+
function extractKillCommand(command) {
|
|
157
|
+
const normalized = command.replace(/\s+/g, " ").trim();
|
|
158
|
+
const shellCMatch = normalized.match(/^.+?\s+-c\s+(['"])([\s\S]+)\1$/);
|
|
159
|
+
if (shellCMatch?.[2]) {
|
|
160
|
+
const inner = shellCMatch[2].trim();
|
|
161
|
+
return isKillRelatedCommand(inner) ? inner : null;
|
|
162
|
+
}
|
|
163
|
+
const shellCUnquoted = normalized.match(
|
|
164
|
+
/^.+?\s+-c\s+(kill(?:\s+-s\s+[a-zA-Z0-9]+|\s+-[a-zA-Z0-9]+)?\s+\d+)$/
|
|
165
|
+
);
|
|
166
|
+
if (shellCUnquoted?.[1]) {
|
|
167
|
+
return shellCUnquoted[1];
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
function isKillRelatedCommand(cmd) {
|
|
172
|
+
const normalized = cmd.toLowerCase().replace(/\s+/g, " ").trim();
|
|
173
|
+
if (isWin) {
|
|
174
|
+
if (/^taskkill\s/i.test(normalized)) return true;
|
|
175
|
+
if (/^tskill\s/i.test(normalized)) return true;
|
|
176
|
+
if (/^(stop-process|kill|stop)\s/i.test(normalized)) return true;
|
|
177
|
+
if (/^wmic\s+process\s/i.test(normalized) && /\bdelete\b/i.test(normalized)) return true;
|
|
178
|
+
if (SCRIPT_KILL_RE.test(normalized)) return true;
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (/^kill(\s|$)/.test(normalized)) return true;
|
|
182
|
+
if (/^(pkill|killall|pgrep|skill)\s/.test(normalized)) return true;
|
|
183
|
+
if (/^\/proc\/\d+\/(?:kill|fd)/.test(normalized)) return true;
|
|
184
|
+
if (SCRIPT_KILL_RE_POSIX.test(normalized)) return true;
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
function parseKillCommand(command) {
|
|
188
|
+
const normalized = command.replace(/\s+/g, " ").trim();
|
|
189
|
+
if (isWin) {
|
|
190
|
+
const hasTaskkillForce = /(?:^|\s)\/F(?=\s|$)/i.test(normalized);
|
|
191
|
+
const isSimpleTaskkill = /^taskkill\s+/i.test(normalized) && !/[|&<>]/.test(normalized);
|
|
192
|
+
const taskkillPidMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/PID\s+(\d+)(?=\s|$)/i) : null;
|
|
193
|
+
if (taskkillPidMatch?.[1]) {
|
|
194
|
+
return {
|
|
195
|
+
pid: parseInt(taskkillPidMatch[1], 10),
|
|
196
|
+
signal: hasTaskkillForce ? "FORCE" : "TERM",
|
|
197
|
+
isGroupKill: false,
|
|
198
|
+
isAllKill: false,
|
|
199
|
+
originalCommand: command
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const taskkillImMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/IM\s+([^\s/]+)(?=\s|$)/i) : null;
|
|
203
|
+
if (taskkillImMatch?.[1]) {
|
|
204
|
+
return {
|
|
205
|
+
name: taskkillImMatch[1],
|
|
206
|
+
signal: hasTaskkillForce ? "FORCE" : "TERM",
|
|
207
|
+
isGroupKill: false,
|
|
208
|
+
isAllKill: false,
|
|
209
|
+
originalCommand: command
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const taskkillFiMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/FI\s+"IMAGENAME\s+eq\s+([^"]+)"(?=\s|$)/i) : null;
|
|
213
|
+
if (taskkillFiMatch?.[1]) {
|
|
214
|
+
return {
|
|
215
|
+
name: taskkillFiMatch[1],
|
|
216
|
+
signal: hasTaskkillForce ? "FORCE" : "TERM",
|
|
217
|
+
isGroupKill: false,
|
|
218
|
+
isAllKill: false,
|
|
219
|
+
originalCommand: command
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const tskillMatch = normalized.match(/^tskill\s+(\d+)/i);
|
|
223
|
+
if (tskillMatch?.[1]) {
|
|
224
|
+
return {
|
|
225
|
+
pid: parseInt(tskillMatch[1], 10),
|
|
226
|
+
signal: "TERM",
|
|
227
|
+
isGroupKill: false,
|
|
228
|
+
isAllKill: false,
|
|
229
|
+
originalCommand: command
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const isStopProcIdCommand = /^(?:stop-process|kill)(?:\s+-(?:id|pid)\s+\d+|\s+-[a-zA-Z]+)+$/i.test(normalized);
|
|
233
|
+
const stopProcIdMatch = normalized.match(/(?:^|\s)-(?:id|pid)\s+(\d+)(?=\s|$)/i);
|
|
234
|
+
if (isStopProcIdCommand && stopProcIdMatch?.[1]) {
|
|
235
|
+
return {
|
|
236
|
+
pid: parseInt(stopProcIdMatch[1], 10),
|
|
237
|
+
signal: "FORCE",
|
|
238
|
+
isGroupKill: false,
|
|
239
|
+
isAllKill: false,
|
|
240
|
+
originalCommand: command
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const killSignalOptionMatch = normalized.match(/^kill\s+-s\s+([a-zA-Z0-9]+)\s+(\d+)$/i);
|
|
244
|
+
if (killSignalOptionMatch?.[1] && killSignalOptionMatch[2]) {
|
|
245
|
+
return {
|
|
246
|
+
pid: parseInt(killSignalOptionMatch[2], 10),
|
|
247
|
+
signal: killSignalOptionMatch[1].toUpperCase(),
|
|
248
|
+
isGroupKill: false,
|
|
249
|
+
isAllKill: false,
|
|
250
|
+
originalCommand: command
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const killPosixMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z0-9]+)\s+)?(\d+)$/);
|
|
254
|
+
if (killPosixMatch?.[2]) {
|
|
255
|
+
const sig = killPosixMatch[1] ? killPosixMatch[1].slice(1).toUpperCase() : "TERM";
|
|
256
|
+
return {
|
|
257
|
+
pid: parseInt(killPosixMatch[2], 10),
|
|
258
|
+
signal: sig,
|
|
259
|
+
isGroupKill: false,
|
|
260
|
+
isAllKill: false,
|
|
261
|
+
originalCommand: command
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const stopProcNameMatch = normalized.match(
|
|
265
|
+
/^(?:stop-process|kill)\s+-(?:name|n)\s+(?:['"]([a-zA-Z0-9_.-]+)['"]|([a-zA-Z0-9_.-]+))(?:\s|$)/i
|
|
266
|
+
);
|
|
267
|
+
const stopProcName = stopProcNameMatch?.[1] ?? stopProcNameMatch?.[2];
|
|
268
|
+
if (stopProcName) {
|
|
269
|
+
return {
|
|
270
|
+
name: stopProcName,
|
|
271
|
+
signal: "FORCE",
|
|
272
|
+
isGroupKill: false,
|
|
273
|
+
isAllKill: false,
|
|
274
|
+
originalCommand: command
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const stopProcStandalone = normalized.match(
|
|
278
|
+
/^(?:stop-process|kill)\s+['"]?([a-zA-Z][a-zA-Z0-9_.-]+)['"]?$/i
|
|
279
|
+
);
|
|
280
|
+
if (stopProcStandalone?.[1]) {
|
|
281
|
+
return {
|
|
282
|
+
name: stopProcStandalone[1],
|
|
283
|
+
signal: "FORCE",
|
|
284
|
+
isGroupKill: false,
|
|
285
|
+
isAllKill: false,
|
|
286
|
+
originalCommand: command
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const wmicMatch = normalized.match(
|
|
290
|
+
/^wmic\s+process\s+where\s+['"]?(?:name\s*=\s*['"]?)([a-zA-Z0-9_.-]+)/i
|
|
291
|
+
);
|
|
292
|
+
if (wmicMatch?.[1]) {
|
|
293
|
+
return {
|
|
294
|
+
name: wmicMatch[1],
|
|
295
|
+
signal: "FORCE",
|
|
296
|
+
isGroupKill: false,
|
|
297
|
+
isAllKill: false,
|
|
298
|
+
originalCommand: command
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const wmicPidMatch = normalized.match(
|
|
302
|
+
/^wmic\s+process\s+where\s+['"]?processid\s*=\s*['"]?(\d+)/i
|
|
303
|
+
);
|
|
304
|
+
if (wmicPidMatch?.[1]) {
|
|
305
|
+
return {
|
|
306
|
+
pid: parseInt(wmicPidMatch[1], 10),
|
|
307
|
+
signal: "FORCE",
|
|
308
|
+
isGroupKill: false,
|
|
309
|
+
isAllKill: false,
|
|
310
|
+
originalCommand: command
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const killScriptMatch = normalized.match(SCRIPT_KILL_RE);
|
|
314
|
+
if (killScriptMatch) {
|
|
315
|
+
return {
|
|
316
|
+
name: "kill-script",
|
|
317
|
+
// sentinel — isKillProtected always blocks "kill-script"
|
|
318
|
+
signal: "FORCE",
|
|
319
|
+
isGroupKill: false,
|
|
320
|
+
isAllKill: false,
|
|
321
|
+
originalCommand: command
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
const signalOptionMatch = normalized.match(/^kill\s+-s\s+([a-zA-Z0-9]+)\s+(\d+|-?\d+)$/i);
|
|
327
|
+
if (signalOptionMatch?.[1] && signalOptionMatch[2]) {
|
|
328
|
+
const pidOrGroup = signalOptionMatch[2];
|
|
329
|
+
const isGroupKill = pidOrGroup.startsWith("-");
|
|
330
|
+
return {
|
|
331
|
+
pid: parseInt(isGroupKill ? pidOrGroup.slice(1) : pidOrGroup, 10),
|
|
332
|
+
signal: signalOptionMatch[1].toUpperCase(),
|
|
333
|
+
isGroupKill,
|
|
334
|
+
isAllKill: false,
|
|
335
|
+
originalCommand: command
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const simpleMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z0-9]+)\s+)?(\d+|-?\d+)$/);
|
|
339
|
+
if (simpleMatch) {
|
|
340
|
+
const signal = simpleMatch[1] ?? "-TERM";
|
|
341
|
+
const pidOrGroup = simpleMatch[2];
|
|
342
|
+
if (!pidOrGroup) return null;
|
|
343
|
+
const isGroupKill = pidOrGroup.startsWith("-");
|
|
344
|
+
const pid = isGroupKill ? parseInt(pidOrGroup.slice(1), 10) : parseInt(pidOrGroup, 10);
|
|
345
|
+
return {
|
|
346
|
+
pid,
|
|
347
|
+
signal: signal.slice(1),
|
|
348
|
+
isGroupKill,
|
|
349
|
+
isAllKill: false,
|
|
350
|
+
originalCommand: command
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const pkillMatch = normalized.match(/^pkill\s+(?:(-[a-zA-Z]+)\s+)?(.+)$/);
|
|
354
|
+
if (pkillMatch?.[2]) {
|
|
355
|
+
const name = pkillMatch[2];
|
|
356
|
+
const signalMatch = pkillMatch[1];
|
|
357
|
+
return {
|
|
358
|
+
name,
|
|
359
|
+
signal: signalMatch ? signalMatch.slice(1) : "TERM",
|
|
360
|
+
isGroupKill: false,
|
|
361
|
+
isAllKill: false,
|
|
362
|
+
originalCommand: command
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const killallMatch = normalized.match(/^killall\s+(?:(-[a-zA-Z]+)\s+)?(.+)$/);
|
|
366
|
+
if (killallMatch?.[2]) {
|
|
367
|
+
const name = killallMatch[2];
|
|
368
|
+
const signalMatch = killallMatch[1];
|
|
369
|
+
return {
|
|
370
|
+
name,
|
|
371
|
+
signal: signalMatch ? signalMatch.slice(1) : "TERM",
|
|
372
|
+
isGroupKill: false,
|
|
373
|
+
isAllKill: false,
|
|
374
|
+
originalCommand: command
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const pgrepMatch = normalized.match(/^pgrep\s+(.+)$/);
|
|
378
|
+
if (pgrepMatch) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const posixScriptMatch = normalized.match(SCRIPT_KILL_RE_POSIX);
|
|
382
|
+
if (posixScriptMatch) {
|
|
383
|
+
return {
|
|
384
|
+
name: "kill-script",
|
|
385
|
+
signal: "FORCE",
|
|
386
|
+
isGroupKill: false,
|
|
387
|
+
isAllKill: false,
|
|
388
|
+
originalCommand: command
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
async function getProtectedEntries() {
|
|
394
|
+
const registry = getPersistentProcessRegistry();
|
|
395
|
+
const status = await registry.getGlobalStatus();
|
|
396
|
+
const entries = [];
|
|
397
|
+
for (const instanceEntries of status.instances.values()) {
|
|
398
|
+
for (const entry of instanceEntries) {
|
|
399
|
+
if (entry.protected && Date.now() - entry.lastHeartbeat < 3e4) {
|
|
400
|
+
entries.push(entry);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return entries;
|
|
405
|
+
}
|
|
406
|
+
async function isKillProtected(kill) {
|
|
407
|
+
const registry = getPersistentProcessRegistry();
|
|
408
|
+
if (kill.name === "kill-script") {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
if (kill.name) {
|
|
412
|
+
const entries = await getProtectedEntries();
|
|
413
|
+
const killNameLower = kill.name.toLowerCase();
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
if (entry.name?.toLowerCase().includes(killNameLower)) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (killNameLower.includes("wrongstack")) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
if (killNameLower.includes("node") && entries.length > 0) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
if (kill.isGroupKill) {
|
|
428
|
+
const protectedPids = await registry.getAllProtectedPids();
|
|
429
|
+
return protectedPids.length > 0;
|
|
430
|
+
}
|
|
431
|
+
if (kill.pid !== void 0) {
|
|
432
|
+
if (await registry.shouldBlockKill(kill.pid)) return true;
|
|
433
|
+
if (kill.pid === process.pid || kill.pid === process.ppid) return true;
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
async function checkAndBlockKillCommand(command) {
|
|
439
|
+
const normalized = command.replace(/\s+/g, " ").trim();
|
|
440
|
+
const killCmd = extractKillCommand(normalized) || (isKillRelatedCommand(normalized) ? normalized : null);
|
|
441
|
+
if (!killCmd) {
|
|
442
|
+
return { blocked: false };
|
|
443
|
+
}
|
|
444
|
+
const parsed = parseKillCommand(killCmd);
|
|
445
|
+
if (!parsed) {
|
|
446
|
+
if (killCmd.includes("kill") && /kill\s+.*\|/.test(killCmd)) {
|
|
447
|
+
return {
|
|
448
|
+
blocked: true,
|
|
449
|
+
reason: `Blocked: complex kill pipeline detected \u2014 "${killCmd.slice(0, 50)}..."`
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (SCRIPT_KILL_FALLBACK_RE.test(killCmd)) {
|
|
453
|
+
return {
|
|
454
|
+
blocked: true,
|
|
455
|
+
reason: `Blocked: script-based kill detected \u2014 "${killCmd.slice(0, 80)}" may target protected WrongStack processes (cannot inspect script body).`
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return { blocked: false };
|
|
459
|
+
}
|
|
460
|
+
if (await isKillProtected(parsed)) {
|
|
461
|
+
let target;
|
|
462
|
+
if (parsed.name) {
|
|
463
|
+
target = `process name "${parsed.name}"`;
|
|
464
|
+
} else if (parsed.pid !== void 0) {
|
|
465
|
+
target = `PID ${parsed.pid}`;
|
|
466
|
+
} else {
|
|
467
|
+
target = "(unknown target)";
|
|
468
|
+
}
|
|
469
|
+
const signal = parsed.signal ? ` (${parsed.signal})` : "";
|
|
470
|
+
const groupNote = parsed.isGroupKill ? " (process group)" : "";
|
|
471
|
+
return {
|
|
472
|
+
blocked: true,
|
|
473
|
+
reason: `Blocked: kill${signal} ${target}${groupNote} targets a protected WrongStack process.`
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return { blocked: false };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/bash.ts
|
|
480
|
+
var MAX_OUTPUT = 32768;
|
|
481
|
+
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
482
|
+
var STREAM_FLUSH_INTERVAL_MS = 200;
|
|
483
|
+
var STREAM_FLUSH_BYTES = 4 * 1024;
|
|
484
|
+
var MAX_QUEUE_CHUNKS = 500;
|
|
485
|
+
var bashTool = {
|
|
486
|
+
name: "bash",
|
|
487
|
+
category: "Shell",
|
|
488
|
+
description: "Execute an arbitrary command in the user's default shell (bash/zsh/pwsh/cmd). stdout and stderr are merged into one stream. This is the most powerful and dangerous tool \u2014 it gives the model full access to the developer's machine. Prefer specialized tools whenever possible.",
|
|
489
|
+
usageHint: "SECURITY WARNING: This tool runs with the full privileges of the current user.\n\nBest practices for the model:\n- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\n- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\n- Prefer single focused commands over huge `&&` chains.\n- Use `background: true` only for long-running processes (dev servers, watchers).\n- The working directory is the session working dir (changed via `set_working_dir`), defaulting to the project root.\n- Output may be truncated in the middle for very large results.",
|
|
490
|
+
selection: {
|
|
491
|
+
doNotUseWhen: "the command is allowlisted and does not require pipes, redirection, or shell expansion.",
|
|
492
|
+
useInstead: ["exec"]
|
|
493
|
+
},
|
|
494
|
+
permission: "confirm",
|
|
495
|
+
mutating: true,
|
|
496
|
+
riskTier: "destructive",
|
|
497
|
+
icon: "terminal",
|
|
498
|
+
// Trust rules match on the literal `command` string. Without subjectKey
|
|
499
|
+
// the policy heuristic would have done the same here, but declaring it
|
|
500
|
+
// explicitly removes the implicit cross-tool aliasing.
|
|
501
|
+
subjectKey: "command",
|
|
502
|
+
capabilities: ["shell.arbitrary"],
|
|
503
|
+
// Executor-level abort ceiling. Must sit ABOVE the per-call `timeout_ms`
|
|
504
|
+
// ceiling (600_000): the tool's own timer tree-kills and returns a
|
|
505
|
+
// structured `timed_out: true` result, while the executor's
|
|
506
|
+
// AbortSignal.timeout is a blunt abort. The old value (300_000) meant any
|
|
507
|
+
// timeout_ms > 5min was silently cut short by the executor. The 10s margin
|
|
508
|
+
// covers the kill/teardown window. (The executor additionally clamps to
|
|
509
|
+
// config `tools.maxToolTimeoutMs`.)
|
|
510
|
+
timeoutMs: 61e4,
|
|
511
|
+
maxOutputBytes: MAX_OUTPUT,
|
|
512
|
+
estimatedDurationMs: 3e4,
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: "object",
|
|
515
|
+
properties: {
|
|
516
|
+
command: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "The exact shell command to run. Prefer simple, focused commands."
|
|
519
|
+
},
|
|
520
|
+
timeout_ms: {
|
|
521
|
+
type: "integer",
|
|
522
|
+
description: "Optional timeout for this specific command in milliseconds (default 300000, max 600000)."
|
|
523
|
+
},
|
|
524
|
+
background: {
|
|
525
|
+
type: "boolean",
|
|
526
|
+
description: "If true, launch the process in the background and return the PID immediately."
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
required: ["command"]
|
|
530
|
+
},
|
|
531
|
+
async execute(input, ctx, opts) {
|
|
532
|
+
let final;
|
|
533
|
+
const executeStream = bashTool.executeStream;
|
|
534
|
+
if (!executeStream) throw new Error("bashTool: stream execution unavailable");
|
|
535
|
+
for await (const ev of executeStream(input, ctx, opts)) {
|
|
536
|
+
if (ev.type === "final") final = ev.output;
|
|
537
|
+
}
|
|
538
|
+
if (!final) throw new Error("bash: stream ended without final event");
|
|
539
|
+
return final;
|
|
540
|
+
},
|
|
541
|
+
async *executeStream(input, ctx, opts) {
|
|
542
|
+
if (!input?.command) throw new Error("bash: command is required");
|
|
543
|
+
const registry = getProcessRegistry();
|
|
544
|
+
const bypassBreaker = !!input.background;
|
|
545
|
+
if (!registry.beforeCall(bypassBreaker)) {
|
|
546
|
+
yield {
|
|
547
|
+
type: "final",
|
|
548
|
+
output: {
|
|
549
|
+
output: "",
|
|
550
|
+
exit_code: 1,
|
|
551
|
+
timed_out: false,
|
|
552
|
+
pid: null,
|
|
553
|
+
error: "bash: circuit breaker open \u2014 too many consecutive failures or slow calls. Use /kill to inspect or /kill reset to recover."
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const killCheck = await checkAndBlockKillCommand(input.command);
|
|
559
|
+
if (killCheck.blocked) {
|
|
560
|
+
yield {
|
|
561
|
+
type: "final",
|
|
562
|
+
output: {
|
|
563
|
+
output: "",
|
|
564
|
+
exit_code: 1,
|
|
565
|
+
timed_out: false,
|
|
566
|
+
pid: null,
|
|
567
|
+
error: killCheck.reason || "Kill command blocked: targets a protected WrongStack process."
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const PIPE_TO_SHELL_PATTERN = /\|\s*(sh|bash|ksh|zsh|fish|cmd|powershell|pwsh)/i;
|
|
573
|
+
const pipeToShellNote = PIPE_TO_SHELL_PATTERN.test(input.command) ? "\n\n[wrongstack] Caution: this command pipes output into a shell interpreter (pipe-to-shell). Piped content executes as arbitrary code \u2014 review the source before trusting the result, and prefer downloading to a file and inspecting it first." : "";
|
|
574
|
+
const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
|
|
575
|
+
const isWin2 = os2.platform() === "win32";
|
|
576
|
+
let plan;
|
|
577
|
+
let winShellKind;
|
|
578
|
+
if (isWin2) {
|
|
579
|
+
const shell2 = pickShell("win32", input.command, {
|
|
580
|
+
get: (k) => process.env[k]
|
|
581
|
+
});
|
|
582
|
+
winShellKind = shell2;
|
|
583
|
+
const bin = shell2 === "powershell" ? resolvePowerShell("powershell.exe") : shell2 === "pwsh" ? resolvePowerShell("pwsh.exe") : process.env["COMSPEC"] ?? "cmd.exe";
|
|
584
|
+
plan = {
|
|
585
|
+
bin,
|
|
586
|
+
argv: shellArgs(shell2),
|
|
587
|
+
commandArg: shell2 === "powershell" || shell2 === "pwsh" ? Buffer.from(wrapPowerShellScript(input.command), "utf16le").toString("base64") : input.command
|
|
588
|
+
};
|
|
589
|
+
} else {
|
|
590
|
+
const explicit = process.env["WRONGSTACK_SHELL"];
|
|
591
|
+
let bin;
|
|
592
|
+
if (explicit) bin = explicit;
|
|
593
|
+
else {
|
|
594
|
+
const fromEnv = process.env["SHELL"];
|
|
595
|
+
if (fromEnv) {
|
|
596
|
+
const name = fromEnv.split("/").pop() ?? "";
|
|
597
|
+
if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) bin = fromEnv;
|
|
598
|
+
else bin = "/bin/bash";
|
|
599
|
+
} else bin = "/bin/bash";
|
|
600
|
+
}
|
|
601
|
+
plan = { bin, argv: ["-c"], commandArg: input.command };
|
|
602
|
+
}
|
|
603
|
+
const shell = plan.bin;
|
|
604
|
+
const args = [...plan.argv, plan.commandArg];
|
|
605
|
+
const env = buildChildEnv(ctx.session?.id);
|
|
606
|
+
const spawnCwd = ctx.workingDir ?? ctx.projectRoot;
|
|
607
|
+
const detached = !isWin2;
|
|
608
|
+
const startedAt = Date.now();
|
|
609
|
+
if (input.background) {
|
|
610
|
+
const child2 = spawn(shell, args, {
|
|
611
|
+
cwd: spawnCwd,
|
|
612
|
+
env,
|
|
613
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
614
|
+
detached: !isWin2,
|
|
615
|
+
windowsHide: true
|
|
616
|
+
});
|
|
617
|
+
const pid2 = child2.pid;
|
|
618
|
+
const stdoutBytes2 = 0;
|
|
619
|
+
const stderrBytes2 = 0;
|
|
620
|
+
let telemetryCompleted2 = false;
|
|
621
|
+
emitProcessStarted({
|
|
622
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
623
|
+
parentPid: process.pid,
|
|
624
|
+
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
625
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
626
|
+
cwd: spawnCwd,
|
|
627
|
+
background: true,
|
|
628
|
+
startedAt: new Date(startedAt).toISOString()
|
|
629
|
+
});
|
|
630
|
+
const completeBackground = (exitCode, signal) => {
|
|
631
|
+
if (telemetryCompleted2) return;
|
|
632
|
+
telemetryCompleted2 = true;
|
|
633
|
+
emitProcessCompleted({
|
|
634
|
+
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
635
|
+
exitCode,
|
|
636
|
+
...signal ? { signal } : {},
|
|
637
|
+
durationMs: Date.now() - startedAt,
|
|
638
|
+
stdoutBytes: stdoutBytes2,
|
|
639
|
+
stderrBytes: stderrBytes2,
|
|
640
|
+
timedOut: false,
|
|
641
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
642
|
+
});
|
|
643
|
+
};
|
|
644
|
+
if (typeof pid2 === "number") {
|
|
645
|
+
registry.register({
|
|
646
|
+
pid: pid2,
|
|
647
|
+
name: "bash",
|
|
648
|
+
command: redactCommand(input.command),
|
|
649
|
+
startedAt: Date.now(),
|
|
650
|
+
sessionId: ctx.session?.id,
|
|
651
|
+
child: child2,
|
|
652
|
+
processGroupLeader: detached && child2.pid === pid2,
|
|
653
|
+
background: true
|
|
654
|
+
});
|
|
655
|
+
child2.on("close", () => registry.unregister(pid2));
|
|
656
|
+
}
|
|
657
|
+
child2.on("error", () => {
|
|
658
|
+
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
659
|
+
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
660
|
+
completeBackground(1);
|
|
661
|
+
});
|
|
662
|
+
child2.on("close", (code, signal) => {
|
|
663
|
+
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
664
|
+
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
665
|
+
});
|
|
666
|
+
if (typeof pid2 === "number") child2.unref();
|
|
667
|
+
yield {
|
|
668
|
+
type: "final",
|
|
669
|
+
output: {
|
|
670
|
+
// Background runs have no captured output; the pipe-to-shell caution
|
|
671
|
+
// (when present) is the only thing worth surfacing.
|
|
672
|
+
output: pipeToShellNote.trim(),
|
|
673
|
+
exit_code: null,
|
|
674
|
+
timed_out: false,
|
|
675
|
+
pid: pid2
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
ctx.recordSideEffect?.({
|
|
679
|
+
toolUseId: `bash-bg-${Date.now()}`,
|
|
680
|
+
toolName: "bash",
|
|
681
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
682
|
+
input: { command: redactCommand(input.command), background: true },
|
|
683
|
+
outcome: `launched (pid ${pid2 ?? "unknown"})`,
|
|
684
|
+
risk: "shell"
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const child = spawn(shell, args, {
|
|
689
|
+
cwd: spawnCwd,
|
|
690
|
+
env,
|
|
691
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
692
|
+
detached,
|
|
693
|
+
windowsHide: true,
|
|
694
|
+
...isWin2 ? {} : { signal: opts.signal }
|
|
695
|
+
});
|
|
696
|
+
const pid = child.pid;
|
|
697
|
+
let stdoutBytes = 0;
|
|
698
|
+
let stderrBytes = 0;
|
|
699
|
+
let telemetryCompleted = false;
|
|
700
|
+
emitProcessStarted({
|
|
701
|
+
...pid !== void 0 ? { pid } : {},
|
|
702
|
+
parentPid: process.pid,
|
|
703
|
+
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
704
|
+
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
705
|
+
cwd: spawnCwd,
|
|
706
|
+
background: false,
|
|
707
|
+
startedAt: new Date(startedAt).toISOString()
|
|
708
|
+
});
|
|
709
|
+
const completeForeground = (exitCode, signal) => {
|
|
710
|
+
if (telemetryCompleted) return;
|
|
711
|
+
telemetryCompleted = true;
|
|
712
|
+
emitProcessCompleted({
|
|
713
|
+
...pid !== void 0 ? { pid } : {},
|
|
714
|
+
exitCode,
|
|
715
|
+
...signal ? { signal } : {},
|
|
716
|
+
durationMs: Date.now() - startedAt,
|
|
717
|
+
stdoutBytes,
|
|
718
|
+
stderrBytes,
|
|
719
|
+
timedOut,
|
|
720
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
721
|
+
});
|
|
722
|
+
};
|
|
723
|
+
if (typeof pid === "number") {
|
|
724
|
+
registry.register({
|
|
725
|
+
pid,
|
|
726
|
+
name: "bash",
|
|
727
|
+
command: redactCommand(input.command),
|
|
728
|
+
startedAt: Date.now(),
|
|
729
|
+
sessionId: ctx.session?.id,
|
|
730
|
+
child,
|
|
731
|
+
processGroupLeader: detached && child.pid === pid
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
let buf = "";
|
|
735
|
+
let pending = "";
|
|
736
|
+
let timedOut = false;
|
|
737
|
+
const timers = [];
|
|
738
|
+
const spool = createOutputSpool({ tool: "bash", thresholdBytes: MAX_OUTPUT });
|
|
739
|
+
function killWithTimeout(child2, timeoutMs2) {
|
|
740
|
+
if (isWin2) {
|
|
741
|
+
if (typeof child2.pid === "number" && child2.exitCode === null) {
|
|
742
|
+
const attempted = registry.kill(child2.pid, { force: true, graceMs: timeoutMs2 });
|
|
743
|
+
if (!attempted) {
|
|
744
|
+
try {
|
|
745
|
+
child2.kill();
|
|
746
|
+
} catch {
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
try {
|
|
751
|
+
child2.kill();
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (typeof child2.pid === "number") {
|
|
758
|
+
registry.kill(child2.pid, { graceMs: timeoutMs2 });
|
|
759
|
+
} else {
|
|
760
|
+
try {
|
|
761
|
+
child2.kill("SIGTERM");
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
const killTimer = setTimeout(() => {
|
|
765
|
+
try {
|
|
766
|
+
child2.kill("SIGKILL");
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}, timeoutMs2);
|
|
770
|
+
timers.push(killTimer);
|
|
771
|
+
killTimer.unref?.();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const timer = setTimeout(() => {
|
|
775
|
+
timedOut = true;
|
|
776
|
+
killWithTimeout(child, 2e3);
|
|
777
|
+
}, timeoutMs);
|
|
778
|
+
timers.push(timer);
|
|
779
|
+
timer.unref?.();
|
|
780
|
+
const onAbort = () => killWithTimeout(child, 2e3);
|
|
781
|
+
if (isWin2) {
|
|
782
|
+
if (opts.signal.aborted) onAbort();
|
|
783
|
+
else opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
784
|
+
}
|
|
785
|
+
const queue = [];
|
|
786
|
+
let resolveNext = null;
|
|
787
|
+
const push = (c) => {
|
|
788
|
+
if (resolveNext) {
|
|
789
|
+
const r = resolveNext;
|
|
790
|
+
resolveNext = null;
|
|
791
|
+
r(c);
|
|
792
|
+
} else {
|
|
793
|
+
queue.push(c);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
const next = () => new Promise((resolve) => {
|
|
797
|
+
const c = queue.shift();
|
|
798
|
+
if (c) resolve(c);
|
|
799
|
+
else resolveNext = resolve;
|
|
800
|
+
});
|
|
801
|
+
let lastFlush = Date.now();
|
|
802
|
+
const flush = () => {
|
|
803
|
+
if (pending.length === 0) return null;
|
|
804
|
+
const text = pending;
|
|
805
|
+
pending = "";
|
|
806
|
+
lastFlush = Date.now();
|
|
807
|
+
return text;
|
|
808
|
+
};
|
|
809
|
+
let paused = false;
|
|
810
|
+
const pauseIfFlooded = () => {
|
|
811
|
+
if (!paused && queue.length >= MAX_QUEUE_CHUNKS) {
|
|
812
|
+
paused = true;
|
|
813
|
+
child.stdout?.pause();
|
|
814
|
+
child.stderr?.pause();
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
const resumeIfDrained = () => {
|
|
818
|
+
if (paused && queue.length < MAX_QUEUE_CHUNKS) {
|
|
819
|
+
paused = false;
|
|
820
|
+
child.stdout?.resume();
|
|
821
|
+
child.stderr?.resume();
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
825
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
826
|
+
const onData = (chunk, stream) => {
|
|
827
|
+
const text = (stream === "stdout" ? stdoutDecoder : stderrDecoder).write(chunk);
|
|
828
|
+
if (stream === "stdout") stdoutBytes += chunk.byteLength;
|
|
829
|
+
else stderrBytes += chunk.byteLength;
|
|
830
|
+
emitProcessOutput({ pid, stream, chunk });
|
|
831
|
+
if (buf.length < MAX_OUTPUT) {
|
|
832
|
+
buf += text.slice(0, MAX_OUTPUT - buf.length);
|
|
833
|
+
}
|
|
834
|
+
spool.write(text);
|
|
835
|
+
pending += text;
|
|
836
|
+
push({ kind: "data", text });
|
|
837
|
+
pauseIfFlooded();
|
|
838
|
+
};
|
|
839
|
+
const onStdoutData = (chunk) => onData(chunk, "stdout");
|
|
840
|
+
const onStderrData = (chunk) => onData(chunk, "stderr");
|
|
841
|
+
child.stdout?.on("data", onStdoutData);
|
|
842
|
+
child.stderr?.on("data", onStderrData);
|
|
843
|
+
child.on("error", (err) => {
|
|
844
|
+
for (const t of timers) clearTimeout(t);
|
|
845
|
+
registry.afterCall(Date.now() - startedAt, true);
|
|
846
|
+
completeForeground(1);
|
|
847
|
+
push({ kind: "error", err });
|
|
848
|
+
});
|
|
849
|
+
child.on("close", (code, signal) => {
|
|
850
|
+
for (const t of timers) clearTimeout(t);
|
|
851
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
852
|
+
registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);
|
|
853
|
+
completeForeground(timedOut ? 124 : code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
854
|
+
const tail = stdoutDecoder.end() + stderrDecoder.end();
|
|
855
|
+
if (tail) {
|
|
856
|
+
if (buf.length < MAX_OUTPUT) buf += tail.slice(0, MAX_OUTPUT - buf.length);
|
|
857
|
+
spool.write(tail);
|
|
858
|
+
pending += tail;
|
|
859
|
+
}
|
|
860
|
+
push({ kind: "end", code });
|
|
861
|
+
});
|
|
862
|
+
try {
|
|
863
|
+
while (true) {
|
|
864
|
+
const c = await next();
|
|
865
|
+
resumeIfDrained();
|
|
866
|
+
if (c.kind === "error") throw c.err;
|
|
867
|
+
if (c.kind === "end") {
|
|
868
|
+
const remainder = flush();
|
|
869
|
+
if (remainder !== null) {
|
|
870
|
+
yield { type: "partial_output", text: remainder };
|
|
871
|
+
}
|
|
872
|
+
const spooled = spool.finalize();
|
|
873
|
+
const hint = !timedOut && typeof c.code === "number" && c.code !== 0 && winShellKind ? diagnoseBashism(input.command, winShellKind) : void 0;
|
|
874
|
+
yield {
|
|
875
|
+
type: "final",
|
|
876
|
+
output: {
|
|
877
|
+
output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "") + (hint ? `
|
|
878
|
+
|
|
879
|
+
${hint}` : "") + pipeToShellNote,
|
|
880
|
+
exit_code: c.code,
|
|
881
|
+
timed_out: timedOut
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
ctx.recordSideEffect?.({
|
|
885
|
+
toolUseId: `bash-${Date.now()}`,
|
|
886
|
+
toolName: "bash",
|
|
887
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
888
|
+
input: { command: redactCommand(input.command) },
|
|
889
|
+
outcome: timedOut ? `timed out (exit ${c.code})` : `exit ${c.code}`,
|
|
890
|
+
risk: "shell"
|
|
891
|
+
});
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
const now = Date.now();
|
|
895
|
+
if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {
|
|
896
|
+
const text = flush();
|
|
897
|
+
if (text) yield { type: "partial_output", text };
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
} finally {
|
|
901
|
+
for (const t of timers) clearTimeout(t);
|
|
902
|
+
spool.finalize();
|
|
903
|
+
if (isWin2) opts.signal.removeEventListener("abort", onAbort);
|
|
904
|
+
child.stdout?.off("data", onStdoutData);
|
|
905
|
+
child.stderr?.off("data", onStderrData);
|
|
906
|
+
child.stdout?.destroy();
|
|
907
|
+
child.stderr?.destroy();
|
|
908
|
+
if (child.exitCode === null && !child.killed) {
|
|
909
|
+
if (typeof pid === "number") registry.kill(pid, { force: true });
|
|
910
|
+
else killWithTimeout(child, 2e3);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
/**
|
|
915
|
+
* Tool-level teardown fired by `ToolExecutor.runToolCleanup()` when the
|
|
916
|
+
* tool's run is aborted/timeout'd. The generator's `finally` block above
|
|
917
|
+
* already force-kills the direct child, but that only runs if the
|
|
918
|
+
* executor closes the async iterator (via `iter.return()`). When the
|
|
919
|
+
* executor tears down without iterating — or a re-entrant abort races
|
|
920
|
+
* with the generator — a bash-spawned process tree can survive in the
|
|
921
|
+
* ProcessRegistry with `killed === false`, continuing to write files,
|
|
922
|
+
* consume CPU, or hold inherited stdio pipes open for the rest of the
|
|
923
|
+
* session.
|
|
924
|
+
*
|
|
925
|
+
* This is the defensive layer the executor calls via `tool.cleanup()`
|
|
926
|
+
* (see `types/tool.ts`): kill every bash-owned process still tracked
|
|
927
|
+
* for this session that hasn't exited yet. `registry.kill()` already
|
|
928
|
+
* handles process-group / taskkill tree-kill and the SIGTERM→SIGKILL
|
|
929
|
+
* grace window, so this just scopes the registry's existing kill path
|
|
930
|
+
* to "this session's runaway bash children". Idempotent — a process
|
|
931
|
+
* that already exited is skipped by `kill()` (it returns false), and a
|
|
932
|
+
* `protected` infrastructure process (dev server the user intentionally
|
|
933
|
+
* backgrounded) is left alone by design.
|
|
934
|
+
*/
|
|
935
|
+
async cleanup(_input, ctx) {
|
|
936
|
+
const registry = getProcessRegistry();
|
|
937
|
+
const sessionId = ctx.session?.id;
|
|
938
|
+
if (!sessionId) return;
|
|
939
|
+
for (const entry of registry.bySession(sessionId)) {
|
|
940
|
+
if (entry.name !== "bash") continue;
|
|
941
|
+
if (entry.child && entry.child.exitCode !== null) continue;
|
|
942
|
+
if (entry.background) continue;
|
|
943
|
+
if (entry.protected) continue;
|
|
944
|
+
registry.kill(entry.pid, { force: true });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
export {
|
|
950
|
+
shellArgs,
|
|
951
|
+
diagnoseBashism,
|
|
952
|
+
checkAndBlockKillCommand,
|
|
953
|
+
bashTool
|
|
954
|
+
};
|
|
955
|
+
//# sourceMappingURL=chunk-IVWV2UM6.js.map
|