@oh-my-pi/pi-coding-agent 15.5.1 → 15.5.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 +26 -0
- package/dist/types/config/settings-schema.d.ts +3 -3
- package/dist/types/hashline/constants.d.ts +31 -0
- package/dist/types/hashline/executor.d.ts +7 -6
- package/dist/types/hashline/hash.d.ts +9 -7
- package/dist/types/hashline/tokenizer.d.ts +3 -0
- package/dist/types/tools/approval.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +5 -4
- package/package.json +7 -7
- package/src/config/settings-schema.ts +4 -4
- package/src/edit/streaming.ts +3 -4
- package/src/extensibility/extensions/wrapper.ts +2 -3
- package/src/hashline/anchors.ts +1 -1
- package/src/hashline/apply.ts +66 -56
- package/src/hashline/constants.ts +38 -0
- package/src/hashline/execute.ts +5 -3
- package/src/hashline/executor.ts +105 -19
- package/src/hashline/grammar.lark +4 -5
- package/src/hashline/hash.ts +9 -6
- package/src/hashline/recovery.ts +35 -1
- package/src/hashline/tokenizer.ts +10 -4
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/prompts/tools/hashline.md +39 -15
- package/src/task/executor.ts +2 -2
- package/src/tools/approval.ts +6 -2
- package/src/tools/bash.ts +78 -14
package/src/tools/bash.ts
CHANGED
|
@@ -42,11 +42,11 @@ const BASH_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
42
42
|
const DEFAULT_AUTO_BACKGROUND_THRESHOLD_MS = 60_000;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Bash patterns
|
|
45
|
+
* Bash patterns flagged as safety critical for approval policy.
|
|
46
46
|
*
|
|
47
|
-
* Kept intentionally tight — the cost of a false
|
|
48
|
-
*
|
|
49
|
-
* should target shapes that are virtually never legitimate in automation.
|
|
47
|
+
* Kept intentionally tight — the cost of a false negative is data loss or a compromised host,
|
|
48
|
+
* while false positives remain actionable through user policy control.
|
|
49
|
+
* New patterns should target shapes that are virtually never legitimate in automation.
|
|
50
50
|
*/
|
|
51
51
|
export const CRITICAL_BASH_PATTERNS = [
|
|
52
52
|
// Recursive destruction.
|
|
@@ -128,6 +128,7 @@ export interface BashToolDetails {
|
|
|
128
128
|
meta?: OutputMeta;
|
|
129
129
|
timeoutSeconds?: number;
|
|
130
130
|
requestedTimeoutSeconds?: number;
|
|
131
|
+
wallTimeMs?: number;
|
|
131
132
|
terminalId?: string;
|
|
132
133
|
async?: {
|
|
133
134
|
state: "running" | "completed" | "failed";
|
|
@@ -272,6 +273,34 @@ function formatTimeoutClampNotice(requestedTimeoutSec: number, effectiveTimeoutS
|
|
|
272
273
|
: undefined;
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
function formatWallTimeSeconds(wallTimeMs: number): string {
|
|
277
|
+
return (wallTimeMs / 1000).toFixed(2);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function formatWallTimeNotice(wallTimeMs: number): string {
|
|
281
|
+
return `Wall time: ${formatWallTimeSeconds(wallTimeMs)} seconds`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Strip the trailing `Wall time: <secs> seconds` notice from text so the TUI
|
|
286
|
+
* can render the wall time via its styled `[Wall: …]` label without echoing
|
|
287
|
+
* the same value verbatim in the output pane.
|
|
288
|
+
*/
|
|
289
|
+
function stripWallTimeNotice(text: string, wallTimeMs: number | undefined): string {
|
|
290
|
+
if (wallTimeMs === undefined) return text;
|
|
291
|
+
// Reconstruct the notice from the same value the result was tagged with so
|
|
292
|
+
// a literal sub-string match never strips a coincidental in-output token —
|
|
293
|
+
// only the exact line we appended in #buildCompletedResult.
|
|
294
|
+
const notice = formatWallTimeNotice(wallTimeMs);
|
|
295
|
+
const idx = text.lastIndexOf(notice);
|
|
296
|
+
if (idx === -1) return text;
|
|
297
|
+
let start = idx;
|
|
298
|
+
let end = idx + notice.length;
|
|
299
|
+
if (text[start - 1] === "\n") start -= 1;
|
|
300
|
+
if (text[end] === "\n") end += 1;
|
|
301
|
+
return (text.slice(0, start) + text.slice(end)).trimEnd();
|
|
302
|
+
}
|
|
303
|
+
|
|
275
304
|
/**
|
|
276
305
|
* Bash tool implementation.
|
|
277
306
|
*
|
|
@@ -347,10 +376,23 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
347
376
|
#buildCompletedResult(
|
|
348
377
|
result: BashResult | BashInteractiveResult,
|
|
349
378
|
timeoutSec: number,
|
|
350
|
-
options: {
|
|
379
|
+
options: {
|
|
380
|
+
requestedTimeoutSec?: number;
|
|
381
|
+
notices?: readonly string[];
|
|
382
|
+
terminalId?: string;
|
|
383
|
+
wallTimeMs?: number;
|
|
384
|
+
} = {},
|
|
351
385
|
): AgentToolResult<BashToolDetails> {
|
|
352
386
|
const outputLines = [this.#formatResultOutput(result)];
|
|
353
|
-
const notices =
|
|
387
|
+
const notices: string[] = [];
|
|
388
|
+
if (options.wallTimeMs !== undefined) {
|
|
389
|
+
notices.push(formatWallTimeNotice(options.wallTimeMs));
|
|
390
|
+
}
|
|
391
|
+
if (options.notices) {
|
|
392
|
+
for (const notice of options.notices) {
|
|
393
|
+
if (notice) notices.push(notice);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
354
396
|
if (notices.length > 0) outputLines.push("", ...notices);
|
|
355
397
|
const outputText = outputLines.join("\n");
|
|
356
398
|
const details: BashToolDetails = { timeoutSeconds: timeoutSec };
|
|
@@ -360,6 +402,9 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
360
402
|
if (options.terminalId !== undefined) {
|
|
361
403
|
details.terminalId = options.terminalId;
|
|
362
404
|
}
|
|
405
|
+
if (options.wallTimeMs !== undefined) {
|
|
406
|
+
details.wallTimeMs = options.wallTimeMs;
|
|
407
|
+
}
|
|
363
408
|
const resultBuilder = toolResult(details).text(outputText).truncationFromSummary(result, { direction: "tail" });
|
|
364
409
|
this.#buildResultText(result, timeoutSec, outputText);
|
|
365
410
|
return resultBuilder.done();
|
|
@@ -430,6 +475,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
430
475
|
async ({ jobId, signal: runSignal, reportProgress }) => {
|
|
431
476
|
const { path: artifactPath, id: artifactId } = (await this.session.allocateOutputArtifact?.("bash")) ?? {};
|
|
432
477
|
const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES);
|
|
478
|
+
const wallTimeStart = performance.now();
|
|
433
479
|
try {
|
|
434
480
|
const result = await executeBash(options.command, {
|
|
435
481
|
cwd: options.commandCwd,
|
|
@@ -446,9 +492,11 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
446
492
|
},
|
|
447
493
|
onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
|
|
448
494
|
});
|
|
495
|
+
const wallTimeMs = performance.now() - wallTimeStart;
|
|
449
496
|
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
450
497
|
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
451
498
|
notices: options.notices ?? [],
|
|
499
|
+
wallTimeMs,
|
|
452
500
|
});
|
|
453
501
|
const finalText = this.#extractTextResult(finalResult);
|
|
454
502
|
latestText = finalText;
|
|
@@ -697,6 +745,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
697
745
|
// Skip when pty=true (PTY needs the local terminal UI).
|
|
698
746
|
const clientBridge = this.session.getClientBridge?.();
|
|
699
747
|
if (clientBridge?.capabilities.terminal && clientBridge.createTerminal && !pty) {
|
|
748
|
+
const bridgeWallTimeStart = performance.now();
|
|
700
749
|
const handle = await clientBridge.createTerminal({
|
|
701
750
|
command,
|
|
702
751
|
cwd: commandCwd,
|
|
@@ -792,6 +841,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
792
841
|
requestedTimeoutSec,
|
|
793
842
|
notices: pendingNotices,
|
|
794
843
|
terminalId: handle.terminalId,
|
|
844
|
+
wallTimeMs: performance.now() - bridgeWallTimeStart,
|
|
795
845
|
});
|
|
796
846
|
}
|
|
797
847
|
|
|
@@ -852,6 +902,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
852
902
|
requestedTimeoutSec,
|
|
853
903
|
notices: bridgeNotices,
|
|
854
904
|
terminalId: handle.terminalId,
|
|
905
|
+
wallTimeMs: performance.now() - bridgeWallTimeStart,
|
|
855
906
|
});
|
|
856
907
|
} finally {
|
|
857
908
|
try {
|
|
@@ -869,6 +920,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
869
920
|
const { path: artifactPath, id: artifactId } = (await this.session.allocateOutputArtifact?.("bash")) ?? {};
|
|
870
921
|
|
|
871
922
|
const interactiveUi = canUseInteractiveBashPty(pty, ctx) ? ctx?.ui : undefined;
|
|
923
|
+
const wallTimeStart = performance.now();
|
|
872
924
|
const result: BashResult | BashInteractiveResult = interactiveUi
|
|
873
925
|
? await runInteractiveBashPty(interactiveUi, {
|
|
874
926
|
command,
|
|
@@ -890,6 +942,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
890
942
|
onChunk: streamTailUpdates(tailBuffer, onUpdate),
|
|
891
943
|
onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
|
|
892
944
|
});
|
|
945
|
+
const wallTimeMs = performance.now() - wallTimeStart;
|
|
893
946
|
if (result.cancelled) {
|
|
894
947
|
if (signal?.aborted) {
|
|
895
948
|
throw new ToolAbortError(normalizeResultOutput(result) || "Command aborted");
|
|
@@ -902,6 +955,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
902
955
|
return this.#buildCompletedResult(result, timeoutSec, {
|
|
903
956
|
requestedTimeoutSec,
|
|
904
957
|
notices: pendingNotices,
|
|
958
|
+
wallTimeMs,
|
|
905
959
|
});
|
|
906
960
|
}
|
|
907
961
|
}
|
|
@@ -1032,22 +1086,32 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1032
1086
|
// Strip the LLM-facing notice appended by wrappedExecute so we don't
|
|
1033
1087
|
// double-print it alongside the styled warning line below.
|
|
1034
1088
|
const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1035
|
-
const
|
|
1089
|
+
const strippedOutput = stripOutputNotice(rawOutput, details?.meta);
|
|
1090
|
+
const output = stripWallTimeNotice(strippedOutput, details?.wallTimeMs);
|
|
1036
1091
|
const displayOutput = output.trimEnd();
|
|
1037
1092
|
const showingFullOutput = expanded && renderContext?.isFullOutput === true;
|
|
1038
1093
|
|
|
1039
1094
|
// Build truncation warning
|
|
1040
1095
|
const timeoutSeconds = details?.timeoutSeconds ?? renderContext?.timeout;
|
|
1041
1096
|
const requestedTimeoutSeconds = details?.requestedTimeoutSeconds;
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1097
|
+
const wallTimeMs = details?.wallTimeMs;
|
|
1098
|
+
const statsParts: string[] = [];
|
|
1099
|
+
if (wallTimeMs !== undefined) {
|
|
1100
|
+
statsParts.push(`Wall: ${formatWallTimeSeconds(wallTimeMs)}s`);
|
|
1101
|
+
}
|
|
1102
|
+
if (typeof timeoutSeconds === "number") {
|
|
1103
|
+
statsParts.push(
|
|
1104
|
+
requestedTimeoutSeconds !== undefined && requestedTimeoutSeconds !== timeoutSeconds
|
|
1045
1105
|
? `Timeout: ${timeoutSeconds}s (requested ${requestedTimeoutSeconds}s clamped)`
|
|
1046
|
-
: `Timeout: ${timeoutSeconds}s
|
|
1047
|
-
|
|
1106
|
+
: `Timeout: ${timeoutSeconds}s`,
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1048
1109
|
const timeoutLine =
|
|
1049
|
-
|
|
1050
|
-
? uiTheme.fg(
|
|
1110
|
+
statsParts.length > 0
|
|
1111
|
+
? uiTheme.fg(
|
|
1112
|
+
"dim",
|
|
1113
|
+
`${uiTheme.format.bracketLeft}${statsParts.join(" | ")}${uiTheme.format.bracketRight}`,
|
|
1114
|
+
)
|
|
1051
1115
|
: undefined;
|
|
1052
1116
|
let warningLine: string | undefined;
|
|
1053
1117
|
if (details?.meta?.truncation && !showingFullOutput) {
|