@cydm/pie 2.7.6 → 2.7.8
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/builtin/extensions/ask-user/index.js +3 -3
- package/dist/builtin/extensions/plan-mode/index.js +6 -4
- package/dist/builtin/extensions/subagent/index.js +11 -5
- package/dist/builtin/extensions/todo/index.js +3 -3
- package/dist/builtin/skills/chrome-devtools-axi/SKILL.md +2 -2
- package/dist/builtin/skills/computer-use-axi/SKILL.md +1 -1
- package/dist/builtin/skills/tui-use/SKILL.md +1 -1
- package/dist/chunks/{chunk-ZF5FHTES.js → chunk-2FEDB5GR.js} +22 -0
- package/dist/chunks/{chunk-GAUIKMBA.js → chunk-E6F4BTNN.js} +325 -56
- package/dist/chunks/{chunk-YWWLRZSG.js → chunk-P22SYVGO.js} +291 -44
- package/dist/chunks/{chunk-5YWEUFLM.js → chunk-W5B3OXHQ.js} +1 -1
- package/dist/chunks/{src-6WK5KYWQ.js → src-C2CBPEUY.js} +2 -2
- package/dist/chunks/{test-stream-7EH7BGY6.js → test-stream-LDVUVSUP.js} +10 -1
- package/dist/cli.js +494 -325
- package/package.json +6 -5
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
createSharedWebSearchTool,
|
|
7
7
|
interpretShellExit,
|
|
8
8
|
requestInteraction
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-P22SYVGO.js";
|
|
10
10
|
import {
|
|
11
11
|
getLogger
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2FEDB5GR.js";
|
|
13
13
|
import {
|
|
14
14
|
Type
|
|
15
15
|
} from "./chunk-XXGYUYXI.js";
|
|
@@ -188,7 +188,7 @@ function createTraceTransportSink(noteRuntimeEvent) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// src/capabilities/bash/index.ts
|
|
191
|
-
import { spawn } from "child_process";
|
|
191
|
+
import { spawn, spawnSync } from "child_process";
|
|
192
192
|
import { closeSync, createWriteStream, existsSync as existsSync2, openSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
193
193
|
import { tmpdir } from "os";
|
|
194
194
|
import { join as join2, resolve as resolve2 } from "path";
|
|
@@ -316,6 +316,30 @@ function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
|
316
316
|
return buf.slice(start).toString("utf-8");
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
// src/capabilities/bash/contract.ts
|
|
320
|
+
var WINDOWS_DIALECT_BLOCK = `On this host shell:auto runs Windows PowerShell 5.1 (powershell.exe). Write PowerShell syntax: separate statements with ';' (PS 5.1 has no && or || \u2014 chain with "if ($?) { ... }" or check $LASTEXITCODE after native commands). Nested quotes in inline "node -e" or "-Command" strings are fragile; write a temp script file with write_file and execute it instead. Heredocs do not exist; multi-line commands work, but long scripts run more reliably from a file. Judge outcomes by the exit code; stderr may carry PowerShell-generated records such as CLIXML. shell:"bash" runs the first real bash on PATH (Git Bash when installed) and fails with an actionable error when only the WSL stub exists; shell:"cmd" runs cmd.exe.`;
|
|
321
|
+
var POSIX_DIALECT_BLOCK = 'On this host shell:auto runs bash (bash -c). POSIX syntax applies: && and || chaining, heredocs, and standard quoting rules are available. shell:"powershell" uses pwsh when installed; shell:"cmd" is Windows-only.';
|
|
322
|
+
function buildShellToolContract(platform = process.platform) {
|
|
323
|
+
if (platform === "win32") {
|
|
324
|
+
return {
|
|
325
|
+
dialect: "powershell51",
|
|
326
|
+
defaultShell: "powershell",
|
|
327
|
+
name: "shell",
|
|
328
|
+
label: "powershell",
|
|
329
|
+
dialectBlock: WINDOWS_DIALECT_BLOCK,
|
|
330
|
+
commandParamDescription: "The PowerShell command to execute by default (Windows PowerShell 5.1). If shell is overridden, use that shell's syntax."
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
dialect: "bash",
|
|
335
|
+
defaultShell: "bash",
|
|
336
|
+
name: "shell",
|
|
337
|
+
label: "bash",
|
|
338
|
+
dialectBlock: POSIX_DIALECT_BLOCK,
|
|
339
|
+
commandParamDescription: "The bash command to execute by default. If shell is overridden, use that shell's syntax."
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
319
343
|
// src/capabilities/bash/artifact-sink.ts
|
|
320
344
|
function createArtifactSinkGate(sink, isActive) {
|
|
321
345
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -353,40 +377,198 @@ function createArtifactSinkGate(sink, isActive) {
|
|
|
353
377
|
};
|
|
354
378
|
}
|
|
355
379
|
|
|
380
|
+
// src/capabilities/bash/powershell-display.ts
|
|
381
|
+
import { StringDecoder } from "node:string_decoder";
|
|
382
|
+
var MARKER = "#< CLIXML";
|
|
383
|
+
var MAX_CLIXML_CHARS = 64 * 1024;
|
|
384
|
+
function xmlText(text) {
|
|
385
|
+
if (/[<>]|\]\]>/.test(text)) return void 0;
|
|
386
|
+
let valid = true;
|
|
387
|
+
const decoded = text.replace(/&([^;]{0,10});|&/g, (whole, entity) => {
|
|
388
|
+
const named = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
389
|
+
if (entity && Object.hasOwn(named, entity)) return named[entity];
|
|
390
|
+
const n = entity && /^#(?:[0-9]+|x[0-9a-fA-F]+)$/.test(entity) ? Number.parseInt(entity.slice(entity[1] === "x" ? 2 : 1), entity[1] === "x" ? 16 : 10) : NaN;
|
|
391
|
+
if (!Number.isInteger(n) || !(n === 9 || n === 10 || n === 13 || n >= 32 && n <= 55295 || n >= 57344 && n <= 65533 || n >= 65536 && n <= 1114111)) {
|
|
392
|
+
valid = false;
|
|
393
|
+
return whole;
|
|
394
|
+
}
|
|
395
|
+
return String.fromCodePoint(n);
|
|
396
|
+
});
|
|
397
|
+
if (!valid || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\ufffe\uffff]/.test(decoded)) return void 0;
|
|
398
|
+
return decoded.replace(/_x([0-9a-fA-F]{4})_/g, (whole, hex) => {
|
|
399
|
+
const n = Number.parseInt(hex, 16);
|
|
400
|
+
return n === 9 || n === 10 || n === 13 || n >= 32 && n < 127 || n >= 160 && n < 55296 || n >= 57344 && n < 65534 ? String.fromCharCode(n) : whole;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function unpack(xml) {
|
|
404
|
+
let pos = 0;
|
|
405
|
+
const take = (pattern) => {
|
|
406
|
+
pattern.lastIndex = pos;
|
|
407
|
+
const match = pattern.exec(xml);
|
|
408
|
+
if (match) pos = pattern.lastIndex;
|
|
409
|
+
return match;
|
|
410
|
+
};
|
|
411
|
+
if (!take(/\s*<Objs/y)) return void 0;
|
|
412
|
+
const attrs = /* @__PURE__ */ new Set();
|
|
413
|
+
while (true) {
|
|
414
|
+
const attr = take(/\s+(Version|xmlns)=(?:"([^"<>]*)"|'([^'<>]*)')/y);
|
|
415
|
+
if (!attr) break;
|
|
416
|
+
if (attrs.has(attr[1])) return void 0;
|
|
417
|
+
attrs.add(attr[1]);
|
|
418
|
+
const value = attr[2] ?? attr[3];
|
|
419
|
+
if (attr[1] === "Version" ? value !== "1.1.0.1" : value !== "http://schemas.microsoft.com/powershell/2004/04") return void 0;
|
|
420
|
+
}
|
|
421
|
+
if (!take(/\s*>/y)) return void 0;
|
|
422
|
+
const records = [];
|
|
423
|
+
while (true) {
|
|
424
|
+
if (take(/\s*<\/Objs>/y)) return pos === xml.length && records.length > 0 ? records.join("") : void 0;
|
|
425
|
+
const record = take(/\s*<S\s+S=(?:"(Error|Warning|Verbose|Debug|Information|Output|Progress)"|'(Error|Warning|Verbose|Debug|Information|Output|Progress)')\s*>([^<]*)<\/S>/y);
|
|
426
|
+
if (!record) return void 0;
|
|
427
|
+
const text = xmlText(record[3]);
|
|
428
|
+
if (text === void 0) return void 0;
|
|
429
|
+
records.push(text);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
var PowerShellStderrNormalizer = class {
|
|
433
|
+
constructor(emit) {
|
|
434
|
+
this.emit = emit;
|
|
435
|
+
}
|
|
436
|
+
emit;
|
|
437
|
+
decoder = new StringDecoder("utf8");
|
|
438
|
+
pending = "";
|
|
439
|
+
block = false;
|
|
440
|
+
endTagTail = "";
|
|
441
|
+
lineStart = true;
|
|
442
|
+
disabled = false;
|
|
443
|
+
normalizedBlocks = 0;
|
|
444
|
+
write(data) {
|
|
445
|
+
this.accept(this.decoder.write(data));
|
|
446
|
+
}
|
|
447
|
+
end() {
|
|
448
|
+
this.accept(this.decoder.end());
|
|
449
|
+
this.emit(this.pending);
|
|
450
|
+
this.pending = "";
|
|
451
|
+
}
|
|
452
|
+
accept(text) {
|
|
453
|
+
let plain = "";
|
|
454
|
+
const flush = () => {
|
|
455
|
+
if (plain) this.emit(plain);
|
|
456
|
+
plain = "";
|
|
457
|
+
};
|
|
458
|
+
for (const char of text) {
|
|
459
|
+
if (this.disabled) {
|
|
460
|
+
plain += char;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (this.block) {
|
|
464
|
+
this.pending += char;
|
|
465
|
+
this.endTagTail = (this.endTagTail + char).slice(-7);
|
|
466
|
+
if (this.pending.length > MAX_CLIXML_CHARS) {
|
|
467
|
+
plain += this.pending;
|
|
468
|
+
this.pending = "";
|
|
469
|
+
this.block = false;
|
|
470
|
+
this.disabled = true;
|
|
471
|
+
} else if (this.endTagTail === "</Objs>") {
|
|
472
|
+
const newline = this.pending.indexOf("\n");
|
|
473
|
+
const decoded = unpack(this.pending.slice(newline + 1));
|
|
474
|
+
flush();
|
|
475
|
+
if (decoded !== void 0) {
|
|
476
|
+
this.normalizedBlocks++;
|
|
477
|
+
this.emit(decoded);
|
|
478
|
+
} else this.emit(this.pending);
|
|
479
|
+
this.pending = "";
|
|
480
|
+
this.block = false;
|
|
481
|
+
this.endTagTail = "";
|
|
482
|
+
this.lineStart = false;
|
|
483
|
+
}
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (this.lineStart) {
|
|
487
|
+
this.pending += char;
|
|
488
|
+
if (this.pending === MARKER + "\n" || this.pending === MARKER + "\r\n") {
|
|
489
|
+
flush();
|
|
490
|
+
this.block = true;
|
|
491
|
+
this.endTagTail = "";
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if ((MARKER + "\n").startsWith(this.pending) || (MARKER + "\r\n").startsWith(this.pending)) continue;
|
|
495
|
+
plain += this.pending;
|
|
496
|
+
this.pending = "";
|
|
497
|
+
this.lineStart = char === "\n";
|
|
498
|
+
} else {
|
|
499
|
+
plain += char;
|
|
500
|
+
this.lineStart = char === "\n";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
flush();
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
var PowerShellDisplay = class {
|
|
507
|
+
head = "";
|
|
508
|
+
tail = "";
|
|
509
|
+
total = 0;
|
|
510
|
+
totalBytes = 0;
|
|
511
|
+
newlines = 0;
|
|
512
|
+
stdout = new StringDecoder("utf8");
|
|
513
|
+
stderr = new PowerShellStderrNormalizer((text) => this.append(text));
|
|
514
|
+
append(text) {
|
|
515
|
+
const cap = DEFAULT_MAX_BYTES;
|
|
516
|
+
this.total += text.length;
|
|
517
|
+
this.totalBytes += Buffer.byteLength(text, "utf8");
|
|
518
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) this.newlines++;
|
|
519
|
+
if (this.head.length < cap) this.head += text.slice(0, cap - this.head.length);
|
|
520
|
+
this.tail = (this.tail + text).slice(-cap);
|
|
521
|
+
}
|
|
522
|
+
writeStdout(data) {
|
|
523
|
+
this.append(this.stdout.write(data));
|
|
524
|
+
}
|
|
525
|
+
end() {
|
|
526
|
+
this.append(this.stdout.end());
|
|
527
|
+
this.stderr.end();
|
|
528
|
+
}
|
|
529
|
+
preview() {
|
|
530
|
+
const result = truncateHeadTail(this.total <= this.head.length ? this.head : this.total <= this.head.length + this.tail.length ? this.head + this.tail.slice(this.head.length + this.tail.length - this.total) : this.head + "\n... [stream middle omitted] ...\n" + this.tail);
|
|
531
|
+
if (this.totalBytes > result.totalBytes) {
|
|
532
|
+
result.truncated = true;
|
|
533
|
+
result.totalBytes = this.totalBytes;
|
|
534
|
+
result.totalLines = this.newlines + 1;
|
|
535
|
+
result.truncatedBy = result.totalLines > DEFAULT_MAX_LINES ? "lines" : "bytes";
|
|
536
|
+
}
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
356
541
|
// src/capabilities/bash/index.ts
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
Type.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
Type.
|
|
368
|
-
Type.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
542
|
+
function buildBashSchema(commandDescription) {
|
|
543
|
+
return Type.Object({
|
|
544
|
+
command: Type.String({ description: commandDescription }),
|
|
545
|
+
timeout: Type.Optional(
|
|
546
|
+
Type.Number({
|
|
547
|
+
exclusiveMinimum: 0,
|
|
548
|
+
maximum: 2147483,
|
|
549
|
+
description: "Timeout in seconds. Default: 120s (2 minutes). The requested value is honored as-is for every command; set it to what the task needs (installs, builds, and tests often need more)."
|
|
550
|
+
})
|
|
551
|
+
),
|
|
552
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for the command" })),
|
|
553
|
+
shell: Type.Optional(Type.Union([
|
|
554
|
+
Type.Literal("auto"),
|
|
555
|
+
Type.Literal("bash"),
|
|
556
|
+
Type.Literal("powershell"),
|
|
557
|
+
Type.Literal("cmd")
|
|
558
|
+
], { description: "Shell to use. Defaults to bash on macOS/Linux and PowerShell on Windows. Use this only for platform-specific commands." }))
|
|
559
|
+
});
|
|
560
|
+
}
|
|
372
561
|
var DEFAULT_TIMEOUT_SECONDS = 120;
|
|
373
|
-
var MAX_GENERIC_TIMEOUT_SECONDS = 120;
|
|
374
562
|
var OUTPUT_DRAIN_TIMEOUT_MS = 1e3;
|
|
375
563
|
var MAX_SHELL_EXIT_DRAIN_MS = 1e4;
|
|
376
|
-
function resolveTimeoutPolicy(requestedTimeout
|
|
564
|
+
function resolveTimeoutPolicy(requestedTimeout) {
|
|
377
565
|
const requestedSeconds = requestedTimeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
378
|
-
if (requestedSeconds
|
|
379
|
-
|
|
380
|
-
requestedSeconds,
|
|
381
|
-
effectiveSeconds: MAX_GENERIC_TIMEOUT_SECONDS,
|
|
382
|
-
capped: true,
|
|
383
|
-
reason: `long timeouts are capped at ${MAX_GENERIC_TIMEOUT_SECONDS}s for non-build commands`
|
|
384
|
-
};
|
|
566
|
+
if (!Number.isFinite(requestedSeconds) || requestedSeconds <= 0 || requestedSeconds > 2147483) {
|
|
567
|
+
throw new Error("Invalid shell timeout: use a finite positive number of seconds within the supported timer range (2147483s).");
|
|
385
568
|
}
|
|
386
569
|
return {
|
|
387
570
|
requestedSeconds,
|
|
388
|
-
effectiveSeconds: requestedSeconds
|
|
389
|
-
capped: false
|
|
571
|
+
effectiveSeconds: requestedSeconds
|
|
390
572
|
};
|
|
391
573
|
}
|
|
392
574
|
function validateBashCommand(command) {
|
|
@@ -396,10 +578,10 @@ function validateBashCommand(command) {
|
|
|
396
578
|
throw new Error("Command cannot be empty. Pass a real shell command in the command argument.");
|
|
397
579
|
}
|
|
398
580
|
if (/^:\s/.test(trimmed) || /^:\s*(?:\d+[\.)]?)?\s*$/.test(trimmed) || /^:\s*\d+[\.)]/.test(trimmed)) {
|
|
399
|
-
throw new Error(`Invalid
|
|
581
|
+
throw new Error(`Invalid shell command "${value}". Do not pass a no-op, numbered plan item, or plan fragment; pass the actual shell command to run.`);
|
|
400
582
|
}
|
|
401
583
|
if (trimmed.includes("<|tool")) {
|
|
402
|
-
throw new Error("Invalid
|
|
584
|
+
throw new Error("Invalid shell command. The command argument must be only a real shell command, not tool-call markup.");
|
|
403
585
|
}
|
|
404
586
|
}
|
|
405
587
|
function buildEffectiveCommand(command, options, shell) {
|
|
@@ -413,21 +595,80 @@ function buildEffectiveCommand(command, options, shell) {
|
|
|
413
595
|
}
|
|
414
596
|
return `${prefix} && ${command}`;
|
|
415
597
|
}
|
|
416
|
-
function resolveShell(shell) {
|
|
598
|
+
function resolveShell(shell, contract) {
|
|
417
599
|
if (!shell || shell === "auto") {
|
|
418
|
-
return
|
|
600
|
+
return contract.defaultShell;
|
|
419
601
|
}
|
|
420
602
|
return shell;
|
|
421
603
|
}
|
|
604
|
+
function buildBashDescription(contract) {
|
|
605
|
+
return [
|
|
606
|
+
"Execute a shell command in the current working directory.",
|
|
607
|
+
contract.dialectBlock,
|
|
608
|
+
`Returns stdout and stderr as a bounded head/tail preview of at most ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB. If truncated, the result includes a private full-output artifact path. Default timeout: 120s. The timeout parameter is the budget: pass a larger value when a task legitimately needs it, and keep long waits focused rather than polling loops.`
|
|
609
|
+
].join(" ");
|
|
610
|
+
}
|
|
422
611
|
function buildSpawnCommand(shell, command) {
|
|
423
612
|
if (shell === "powershell") {
|
|
424
|
-
const
|
|
425
|
-
|
|
613
|
+
const childScript = [
|
|
614
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
615
|
+
"[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding; $OutputEncoding = [Console]::OutputEncoding",
|
|
616
|
+
command,
|
|
617
|
+
"if (($null -ne $LASTEXITCODE) -and ($LASTEXITCODE -ne 0)) { exit $LASTEXITCODE }"
|
|
618
|
+
].join("\n");
|
|
619
|
+
const script = [
|
|
620
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
621
|
+
`powershell.exe -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(childScript, "utf16le").toString("base64")}`,
|
|
622
|
+
"$__pieCode = $LASTEXITCODE; $__pieOk = $?",
|
|
623
|
+
"if (-not $__pieOk -and (($null -eq $__pieCode) -or ($__pieCode -eq 0))) { exit 1 }",
|
|
624
|
+
"if (($null -ne $__pieCode) -and ($__pieCode -ne 0)) { exit $__pieCode }",
|
|
625
|
+
"exit 0"
|
|
626
|
+
].join("\n");
|
|
627
|
+
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
628
|
+
return { file: process.platform === "win32" ? "powershell.exe" : "pwsh", args: ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded], shell: false };
|
|
426
629
|
}
|
|
427
630
|
if (shell === "cmd") {
|
|
428
631
|
return { file: "cmd.exe", args: ["/d", "/s", "/c", command], shell: false };
|
|
429
632
|
}
|
|
430
|
-
|
|
633
|
+
if (process.platform === "win32") {
|
|
634
|
+
const bashPath = resolveWindowsBashPath();
|
|
635
|
+
if (!bashPath) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
"No usable bash found on Windows: PATH only offers the WSL store stub (WindowsApps\\bash.exe). Install Git Bash (https://git-scm.com) or WSL, or use shell:auto (PowerShell)."
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return { file: bashPath, args: ["-c", command], shell: false };
|
|
641
|
+
}
|
|
642
|
+
return { file: "bash", args: ["-c", command], shell: false };
|
|
643
|
+
}
|
|
644
|
+
var windowsBashPathCache;
|
|
645
|
+
function resolveWindowsBashPath() {
|
|
646
|
+
if (windowsBashPathCache !== void 0) return windowsBashPathCache;
|
|
647
|
+
let resolved = null;
|
|
648
|
+
try {
|
|
649
|
+
const result = spawnSync("cmd.exe", ["/d", "/c", "where", "bash"], { windowsHide: true, encoding: "utf8", timeout: 5e3 });
|
|
650
|
+
const lines = (result.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
651
|
+
const real = lines.find((line) => !isWindowsAppsPath(line));
|
|
652
|
+
resolved = real ?? null;
|
|
653
|
+
} catch {
|
|
654
|
+
resolved = null;
|
|
655
|
+
}
|
|
656
|
+
if (!resolved) {
|
|
657
|
+
const programFiles = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]].filter((value) => Boolean(value));
|
|
658
|
+
for (const root of programFiles) {
|
|
659
|
+
const candidate = join2(root, "Git", "bin", "bash.exe");
|
|
660
|
+
if (existsSync2(candidate)) {
|
|
661
|
+
resolved = candidate;
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
windowsBashPathCache = resolved;
|
|
667
|
+
return resolved;
|
|
668
|
+
}
|
|
669
|
+
function isWindowsAppsPath(p) {
|
|
670
|
+
const normalized = p.replace(/\\/g, "/").toLowerCase();
|
|
671
|
+
return normalized.includes("/windowsapps/");
|
|
431
672
|
}
|
|
432
673
|
async function requestHighRiskConfirmation(request) {
|
|
433
674
|
const response = await requestInteraction({
|
|
@@ -633,6 +874,7 @@ function killProcessTree(pid, leaderExited) {
|
|
|
633
874
|
}
|
|
634
875
|
}
|
|
635
876
|
function createBashTool(defaultCwd, options) {
|
|
877
|
+
const contract = buildShellToolContract(options?.platform);
|
|
636
878
|
const recentCommands = /* @__PURE__ */ new Map();
|
|
637
879
|
const rememberCommandResult = (key, fingerprint) => {
|
|
638
880
|
const previous = recentCommands.get(key);
|
|
@@ -646,13 +888,28 @@ function createBashTool(defaultCwd, options) {
|
|
|
646
888
|
return { count, sameResult };
|
|
647
889
|
};
|
|
648
890
|
return {
|
|
649
|
-
name:
|
|
650
|
-
label:
|
|
651
|
-
description:
|
|
652
|
-
parameters:
|
|
891
|
+
name: contract.name,
|
|
892
|
+
label: contract.label,
|
|
893
|
+
description: buildBashDescription(contract),
|
|
894
|
+
parameters: buildBashSchema(contract.commandParamDescription),
|
|
895
|
+
// Policy identity travels with the tool (single source of truth, so a
|
|
896
|
+
// host may rename it without touching this layer's registries); the
|
|
897
|
+
// shared BUILTIN_TOOL_CAPABILITY_METADATA["bash"] entry stays as the
|
|
898
|
+
// name-keyed fallback for legacy sessions and name-only test doubles.
|
|
899
|
+
capabilityMetadata: {
|
|
900
|
+
riskClass: "shell",
|
|
901
|
+
concurrencySafe: false,
|
|
902
|
+
requiresPermission: true,
|
|
903
|
+
permissionScope: "shell",
|
|
904
|
+
executesShell: true,
|
|
905
|
+
availableInPlanMode: true,
|
|
906
|
+
allowedForSubagentByDefault: true,
|
|
907
|
+
highRisk: true,
|
|
908
|
+
maxOutputChars: 1e5
|
|
909
|
+
},
|
|
653
910
|
execute: async (_toolCallId, { command, timeout, cwd, shell }, signal, onUpdate) => {
|
|
654
911
|
validateBashCommand(command);
|
|
655
|
-
const resolvedShell = resolveShell(shell);
|
|
912
|
+
const resolvedShell = resolveShell(shell, contract);
|
|
656
913
|
const effectiveCommand = buildEffectiveCommand(command, options, resolvedShell);
|
|
657
914
|
const workingDir = normalizeWorkingDirectory(cwd, defaultCwd);
|
|
658
915
|
validateWorkingDirectory(workingDir);
|
|
@@ -717,7 +974,7 @@ function createBashTool(defaultCwd, options) {
|
|
|
717
974
|
}
|
|
718
975
|
});
|
|
719
976
|
}
|
|
720
|
-
const timeoutPolicy = resolveTimeoutPolicy(timeout
|
|
977
|
+
const timeoutPolicy = resolveTimeoutPolicy(timeout);
|
|
721
978
|
const timeoutMs = timeoutPolicy.effectiveSeconds * 1e3;
|
|
722
979
|
return new Promise((resolve3, reject) => {
|
|
723
980
|
if (signal?.aborted) {
|
|
@@ -729,6 +986,7 @@ function createBashTool(defaultCwd, options) {
|
|
|
729
986
|
let tempFileSink;
|
|
730
987
|
let settled = false;
|
|
731
988
|
let totalBytes = 0;
|
|
989
|
+
const display = resolvedShell === "powershell" ? new PowerShellDisplay() : void 0;
|
|
732
990
|
const chunks = [];
|
|
733
991
|
let chunksBytes = 0;
|
|
734
992
|
const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
|
|
@@ -854,10 +1112,9 @@ function createBashTool(defaultCwd, options) {
|
|
|
854
1112
|
}
|
|
855
1113
|
}
|
|
856
1114
|
appendRollingChunk(data);
|
|
1115
|
+
display?.writeStdout(data);
|
|
857
1116
|
if (onUpdate) {
|
|
858
|
-
const
|
|
859
|
-
const fullText = fullBuffer.toString("utf-8");
|
|
860
|
-
const truncation = truncateHeadTail(fullText);
|
|
1117
|
+
const truncation = display?.preview() ?? truncateHeadTail(Buffer.concat(chunks).toString("utf-8"));
|
|
861
1118
|
onUpdate({
|
|
862
1119
|
content: [{ type: "text", text: truncation.content || "" }],
|
|
863
1120
|
details: {
|
|
@@ -908,6 +1165,15 @@ function createBashTool(defaultCwd, options) {
|
|
|
908
1165
|
}
|
|
909
1166
|
}
|
|
910
1167
|
appendRollingChunk(data);
|
|
1168
|
+
display?.stderr.write(data);
|
|
1169
|
+
if (display?.stderr.normalizedBlocks && !tempFilePath) {
|
|
1170
|
+
try {
|
|
1171
|
+
const sink = ensureOutputArtifact();
|
|
1172
|
+
for (const chunk of chunks) sink.write(chunk);
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
failExecution(error instanceof Error ? error : new Error(String(error)));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
911
1177
|
});
|
|
912
1178
|
const finishExecution = async (code, sig, stdioIncomplete = false) => {
|
|
913
1179
|
if (settled) return;
|
|
@@ -924,8 +1190,9 @@ function createBashTool(defaultCwd, options) {
|
|
|
924
1190
|
... [stream middle omitted] ...
|
|
925
1191
|
${fullOutput}`;
|
|
926
1192
|
}
|
|
927
|
-
|
|
928
|
-
|
|
1193
|
+
display?.end();
|
|
1194
|
+
const truncation = display?.preview() ?? truncateHeadTail(fullOutput);
|
|
1195
|
+
if (!display && totalBytes > truncation.totalBytes) {
|
|
929
1196
|
truncation.truncated = true;
|
|
930
1197
|
truncation.totalBytes = totalBytes;
|
|
931
1198
|
truncation.totalLines = totalNewlines + 1;
|
|
@@ -938,7 +1205,7 @@ ${fullOutput}`;
|
|
|
938
1205
|
tempFilePath = path3;
|
|
939
1206
|
retainedOutputArtifacts.add(path3);
|
|
940
1207
|
try {
|
|
941
|
-
writeFileSync(fd,
|
|
1208
|
+
writeFileSync(fd, fullBuffer);
|
|
942
1209
|
} finally {
|
|
943
1210
|
closeSync(fd);
|
|
944
1211
|
}
|
|
@@ -998,20 +1265,22 @@ ${commandNonzeroHint}`;
|
|
|
998
1265
|
[Showing a bounded head/tail preview of ${truncation.totalLines} lines (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;
|
|
999
1266
|
}
|
|
1000
1267
|
}
|
|
1268
|
+
if (display?.stderr.normalizedBlocks) {
|
|
1269
|
+
details.outputNormalization = { kind: "powershell-clixml-text", blocks: display.stderr.normalizedBlocks, rawEvidence: true };
|
|
1270
|
+
details.fullOutputPath = tempFilePath;
|
|
1271
|
+
outputText += `
|
|
1272
|
+
|
|
1273
|
+
[PowerShell CLIXML text records unpacked for display (limited support: complex/unknown records remain raw). Raw stdout/stderr evidence in observed capture order: ${tempFilePath}]`;
|
|
1274
|
+
}
|
|
1001
1275
|
if (timedOut) {
|
|
1002
1276
|
outputText += `
|
|
1003
1277
|
|
|
1004
1278
|
[Command timed out after ${timeoutPolicy.effectiveSeconds} seconds]`;
|
|
1005
|
-
outputText += "\n[Timeout hint:
|
|
1279
|
+
outputText += "\n[Timeout hint: the command ran longer than its requested budget. Inspect partial results/logs, then either raise the timeout to what the task needs or split long waits into smaller focused steps. Avoid repeating identical long waits.]";
|
|
1006
1280
|
}
|
|
1007
1281
|
if (stdioIncomplete && !timedOut) {
|
|
1008
1282
|
outputText += "\n\n[Output capture ended after the command exited because a descendant kept stdio open or the output artifact was still draining (bounded wait). Captured output may be incomplete.]";
|
|
1009
1283
|
}
|
|
1010
|
-
if (timeoutPolicy.capped) {
|
|
1011
|
-
outputText += `
|
|
1012
|
-
|
|
1013
|
-
[Timeout policy: requested ${timeoutPolicy.requestedSeconds} seconds, capped to ${timeoutPolicy.effectiveSeconds} seconds because ${timeoutPolicy.reason}.]`;
|
|
1014
|
-
}
|
|
1015
1284
|
const recoveryHint = buildBashRecoveryHint(command, outputText);
|
|
1016
1285
|
if (recoveryHint) {
|
|
1017
1286
|
outputText += `
|
|
@@ -1046,7 +1315,7 @@ ${commandNonzeroHint}`;
|
|
|
1046
1315
|
void finishExecution(code, sig);
|
|
1047
1316
|
});
|
|
1048
1317
|
child.on("error", (err) => {
|
|
1049
|
-
failExecution(new Error(`Failed to start
|
|
1318
|
+
failExecution(new Error(`Failed to start shell command in ${workingDir}: ${err.message}. Check that the command is executable and cwd exists.`));
|
|
1050
1319
|
});
|
|
1051
1320
|
});
|
|
1052
1321
|
}
|
|
@@ -1292,7 +1561,7 @@ function createCliHostCapabilities(cwd, options = {}) {
|
|
|
1292
1561
|
});
|
|
1293
1562
|
const capabilities = [
|
|
1294
1563
|
{
|
|
1295
|
-
id: "
|
|
1564
|
+
id: "shell",
|
|
1296
1565
|
description: "Node/TUI host capability for executing shell commands in the local workspace.",
|
|
1297
1566
|
tools: [bashTool]
|
|
1298
1567
|
},
|