@gajae-code/coding-agent 0.12.6 → 0.12.7
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 +3 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +1 -3
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -4
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-storage.d.ts +2 -5
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +4 -5
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +8 -8
- package/src/async/job-manager.ts +430 -46
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/bus/chat-daemon-control.ts +3 -5
- package/src/sdk/bus/index.ts +8 -324
- package/src/sdk/bus/telegram-daemon-contract.ts +2 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/session/agent-session.ts +15 -178
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-storage.ts +5 -104
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +21 -12
- package/src/session/streaming-output.ts +40 -198
- package/src/task/index.ts +230 -64
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
|
@@ -4,28 +4,18 @@
|
|
|
4
4
|
* Uses brush-core via native bindings for shell execution.
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs/promises";
|
|
7
|
-
import {
|
|
8
|
-
executeShell,
|
|
9
|
-
type MinimizerOptions,
|
|
10
|
-
type ShellRunResult as NativeShellRunResult,
|
|
11
|
-
Shell,
|
|
12
|
-
} from "@gajae-code/natives";
|
|
7
|
+
import { executeShell, type MinimizerOptions, Shell } from "@gajae-code/natives";
|
|
13
8
|
import { postmortem } from "@gajae-code/utils";
|
|
14
9
|
import { Settings, type ShellMinimizerSettings } from "../config/settings";
|
|
15
10
|
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
16
11
|
import {
|
|
12
|
+
DEFAULT_ARTIFACT_MAX_BYTES,
|
|
17
13
|
DEFAULT_MAX_BYTES,
|
|
18
14
|
OutputSink,
|
|
19
|
-
type OutputSummary,
|
|
20
15
|
type TerminalArtifactPublisher,
|
|
21
16
|
truncateHeadBytes,
|
|
22
17
|
} from "../session/streaming-output";
|
|
23
|
-
import {
|
|
24
|
-
formatArtifactEvidenceNotice,
|
|
25
|
-
isValidArtifactId,
|
|
26
|
-
resolveOutputMaxColumns,
|
|
27
|
-
resolveOutputSinkHeadBytes,
|
|
28
|
-
} from "../tools/output-meta";
|
|
18
|
+
import { formatArtifactReference, resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
29
19
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
30
20
|
import { NON_INTERACTIVE_ENV } from "./non-interactive-env";
|
|
31
21
|
|
|
@@ -35,19 +25,7 @@ export interface BashArtifactSaveSummary {
|
|
|
35
25
|
omittedBytes?: number;
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
export type BashMinimizedSaveReturn = BashArtifactSaveResult | BashArtifactSaveSummary | undefined;
|
|
39
|
-
|
|
40
|
-
interface ValidatedNativeMinimized {
|
|
41
|
-
filter: string;
|
|
42
|
-
text: string;
|
|
43
|
-
originalText: string;
|
|
44
|
-
inputBytes: number;
|
|
45
|
-
outputBytes: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const MINIMIZED_SAVE_WAIT_MS = 500;
|
|
49
|
-
const MINIMIZED_SAVE_PENDING_LIMIT = 64;
|
|
50
|
-
const pendingMinimizedSaves = new Set<Promise<unknown>>();
|
|
28
|
+
export type BashMinimizedSaveReturn = BashArtifactSaveResult | BashArtifactSaveSummary | string | undefined;
|
|
51
29
|
|
|
52
30
|
export type BashArtifactSaveResult =
|
|
53
31
|
| { status: "saved"; artifactId: string; complete: true; omittedBytes?: undefined }
|
|
@@ -55,16 +33,18 @@ export type BashArtifactSaveResult =
|
|
|
55
33
|
| { status: "unavailable" }
|
|
56
34
|
| { status: "failed"; diagnostic: string };
|
|
57
35
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
36
|
+
function summarizeLegacyArtifactSave(artifactId: string, originalText: string): BashArtifactSaveResult {
|
|
37
|
+
const inputBytes = Buffer.byteLength(originalText, "utf-8");
|
|
38
|
+
if (inputBytes <= DEFAULT_ARTIFACT_MAX_BYTES) {
|
|
39
|
+
return { status: "saved", artifactId, complete: true };
|
|
40
|
+
}
|
|
41
|
+
const retainedBytes = truncateHeadBytes(originalText, DEFAULT_ARTIFACT_MAX_BYTES).bytes;
|
|
42
|
+
return {
|
|
43
|
+
status: "saved",
|
|
44
|
+
artifactId,
|
|
45
|
+
complete: false,
|
|
46
|
+
omittedBytes: inputBytes - retainedBytes,
|
|
47
|
+
};
|
|
68
48
|
}
|
|
69
49
|
|
|
70
50
|
function normalizeExplicitSavedArtifact(
|
|
@@ -72,12 +52,6 @@ function normalizeExplicitSavedArtifact(
|
|
|
72
52
|
complete: boolean,
|
|
73
53
|
omittedBytes: number | undefined,
|
|
74
54
|
): BashArtifactSaveResult {
|
|
75
|
-
if (!isValidArtifactId(artifactId)) {
|
|
76
|
-
return { status: "failed", diagnostic: "artifact save reported an invalid artifact id" };
|
|
77
|
-
}
|
|
78
|
-
if (omittedBytes !== undefined && !isValidOmittedBytes(omittedBytes)) {
|
|
79
|
-
return { status: "failed", diagnostic: "artifact save reported invalid omitted bytes" };
|
|
80
|
-
}
|
|
81
55
|
if (complete) {
|
|
82
56
|
return (omittedBytes ?? 0) > 0
|
|
83
57
|
? { status: "failed", diagnostic: "artifact save reported complete output with omitted bytes" }
|
|
@@ -88,143 +62,35 @@ function normalizeExplicitSavedArtifact(
|
|
|
88
62
|
: { status: "failed", diagnostic: "artifact save reported incomplete output without omitted bytes" };
|
|
89
63
|
}
|
|
90
64
|
|
|
91
|
-
function normalizeMinimizedSaveResult(value:
|
|
92
|
-
if (value ===
|
|
93
|
-
if (
|
|
94
|
-
if (!
|
|
95
|
-
|
|
96
|
-
const status = value.status;
|
|
97
|
-
if (status === "unavailable") return { status: "unavailable" };
|
|
98
|
-
if (status === "failed") {
|
|
99
|
-
const diagnostic = value.diagnostic;
|
|
100
|
-
return typeof diagnostic === "string" && diagnostic.trim().length > 0
|
|
101
|
-
? { status: "failed", diagnostic: diagnostic.slice(0, 512) }
|
|
102
|
-
: invalidArtifactSaveShape();
|
|
103
|
-
}
|
|
104
|
-
if (status !== undefined && status !== "saved") return invalidArtifactSaveShape();
|
|
105
|
-
if (typeof value.artifactId !== "string" || typeof value.complete !== "boolean") {
|
|
106
|
-
return invalidArtifactSaveShape();
|
|
107
|
-
}
|
|
108
|
-
if (value.omittedBytes !== undefined && typeof value.omittedBytes !== "number") {
|
|
109
|
-
return invalidArtifactSaveShape();
|
|
65
|
+
function normalizeMinimizedSaveResult(value: BashMinimizedSaveReturn, originalText: string): BashArtifactSaveResult {
|
|
66
|
+
if (typeof value === "string") return summarizeLegacyArtifactSave(value, originalText);
|
|
67
|
+
if (!value) return { status: "unavailable" };
|
|
68
|
+
if (!("status" in value)) {
|
|
69
|
+
return normalizeExplicitSavedArtifact(value.artifactId, value.complete, value.omittedBytes);
|
|
110
70
|
}
|
|
71
|
+
if (value.status !== "saved") return value;
|
|
111
72
|
return normalizeExplicitSavedArtifact(value.artifactId, value.complete, value.omittedBytes);
|
|
112
73
|
}
|
|
113
74
|
|
|
114
|
-
export function normalizeMinimizedSaveResultForTests(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (value === undefined) return { malformed: false };
|
|
120
|
-
if (!isRecord(value)) return { malformed: true };
|
|
121
|
-
const { filter, text, originalText, inputBytes, outputBytes } = value;
|
|
122
|
-
if (
|
|
123
|
-
typeof filter !== "string" ||
|
|
124
|
-
typeof text !== "string" ||
|
|
125
|
-
typeof originalText !== "string" ||
|
|
126
|
-
!Number.isSafeInteger(inputBytes) ||
|
|
127
|
-
!Number.isSafeInteger(outputBytes) ||
|
|
128
|
-
(inputBytes as number) < 0 ||
|
|
129
|
-
(outputBytes as number) < 0 ||
|
|
130
|
-
Buffer.byteLength(originalText, "utf-8") !== inputBytes ||
|
|
131
|
-
Buffer.byteLength(text, "utf-8") !== outputBytes
|
|
132
|
-
) {
|
|
133
|
-
return { malformed: true };
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
minimized: { filter, text, originalText, inputBytes: inputBytes as number, outputBytes: outputBytes as number },
|
|
137
|
-
malformed: false,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function nativeCaptureStatusIncomplete(result: NativeShellRunResult | undefined): boolean {
|
|
142
|
-
if (!result) return true;
|
|
143
|
-
const capture = result.outputCaptureIncomplete as unknown;
|
|
144
|
-
const saturated = result.outputLossCountSaturated as unknown;
|
|
145
|
-
return (
|
|
146
|
-
(capture !== undefined && typeof capture !== "boolean") ||
|
|
147
|
-
(saturated !== undefined && typeof saturated !== "boolean") ||
|
|
148
|
-
capture === true ||
|
|
149
|
-
saturated === true
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function saveMinimizedArtifactBounded(
|
|
154
|
-
operation: () => Promise<BashMinimizedSaveReturn>,
|
|
155
|
-
): Promise<BashArtifactSaveResult> {
|
|
156
|
-
if (pendingMinimizedSaves.size >= MINIMIZED_SAVE_PENDING_LIMIT) {
|
|
157
|
-
return { status: "failed", diagnostic: "pending minimized artifact save limit reached" };
|
|
158
|
-
}
|
|
159
|
-
const promise = Promise.resolve().then(operation);
|
|
160
|
-
pendingMinimizedSaves.add(promise);
|
|
161
|
-
void promise.then(
|
|
162
|
-
() => pendingMinimizedSaves.delete(promise),
|
|
163
|
-
() => pendingMinimizedSaves.delete(promise),
|
|
164
|
-
);
|
|
165
|
-
const outcome = await Promise.race([
|
|
166
|
-
promise.then(
|
|
167
|
-
value => ({ status: "ok" as const, value }),
|
|
168
|
-
error => ({ status: "failed" as const, diagnostic: error instanceof Error ? error.message : String(error) }),
|
|
169
|
-
),
|
|
170
|
-
Bun.sleep(MINIMIZED_SAVE_WAIT_MS).then(() => ({
|
|
171
|
-
status: "failed" as const,
|
|
172
|
-
diagnostic: `did not settle within ${MINIMIZED_SAVE_WAIT_MS}ms`,
|
|
173
|
-
})),
|
|
174
|
-
]);
|
|
175
|
-
if (outcome.status === "failed") {
|
|
176
|
-
return {
|
|
177
|
-
status: "failed",
|
|
178
|
-
diagnostic: truncateHeadBytes(outcome.diagnostic.replace(/\s+/gu, " ").trim(), 512).text,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
return normalizeMinimizedSaveResult(outcome.value);
|
|
75
|
+
export function normalizeMinimizedSaveResultForTests(
|
|
76
|
+
value: BashMinimizedSaveReturn,
|
|
77
|
+
originalText: string,
|
|
78
|
+
): BashArtifactSaveResult {
|
|
79
|
+
return normalizeMinimizedSaveResult(value, originalText);
|
|
182
80
|
}
|
|
183
81
|
|
|
184
82
|
function completeRawArtifactAvailable(summary: {
|
|
185
83
|
artifactId?: string;
|
|
186
84
|
artifactTruncatedBytes?: number;
|
|
187
|
-
sourceTruncatedBytes?: number;
|
|
188
|
-
sourceCaptureIncomplete?: boolean;
|
|
189
85
|
artifactFailureDiagnostic?: string;
|
|
190
86
|
}): boolean {
|
|
191
87
|
return (
|
|
192
88
|
summary.artifactId !== undefined &&
|
|
193
89
|
(summary.artifactTruncatedBytes ?? 0) <= 0 &&
|
|
194
|
-
(summary.sourceTruncatedBytes ?? 0) <= 0 &&
|
|
195
|
-
!summary.sourceCaptureIncomplete &&
|
|
196
90
|
summary.artifactFailureDiagnostic === undefined
|
|
197
91
|
);
|
|
198
92
|
}
|
|
199
93
|
|
|
200
|
-
function applyShellCallbackLoss(
|
|
201
|
-
summary: OutputSummary,
|
|
202
|
-
droppedOutputBytes: number | undefined,
|
|
203
|
-
droppedOutputChunks: number | undefined,
|
|
204
|
-
sourceCaptureIncomplete = false,
|
|
205
|
-
): OutputSummary {
|
|
206
|
-
const bytesValid =
|
|
207
|
-
droppedOutputBytes === undefined || (Number.isSafeInteger(droppedOutputBytes) && droppedOutputBytes >= 0);
|
|
208
|
-
const chunksValid =
|
|
209
|
-
droppedOutputChunks === undefined || (Number.isSafeInteger(droppedOutputChunks) && droppedOutputChunks >= 0);
|
|
210
|
-
const counterPairValid = (droppedOutputBytes === undefined) === (droppedOutputChunks === undefined);
|
|
211
|
-
const knownDroppedBytes = bytesValid && droppedOutputBytes !== undefined && droppedOutputBytes > 0;
|
|
212
|
-
const knownDroppedChunks = chunksValid && droppedOutputChunks !== undefined && droppedOutputChunks > 0;
|
|
213
|
-
const captureIncomplete =
|
|
214
|
-
sourceCaptureIncomplete ||
|
|
215
|
-
!bytesValid ||
|
|
216
|
-
!chunksValid ||
|
|
217
|
-
!counterPairValid ||
|
|
218
|
-
knownDroppedBytes !== knownDroppedChunks;
|
|
219
|
-
if (!knownDroppedBytes && !captureIncomplete) return summary;
|
|
220
|
-
return {
|
|
221
|
-
...summary,
|
|
222
|
-
truncated: true,
|
|
223
|
-
...(knownDroppedBytes ? { sourceTruncatedBytes: droppedOutputBytes } : {}),
|
|
224
|
-
...(captureIncomplete ? { sourceCaptureIncomplete: true } : {}),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
94
|
function appendModelNotice(output: string, notice: string): string {
|
|
229
95
|
const separator = output.length > 0 && !output.endsWith("\n") ? "\n" : "";
|
|
230
96
|
return `${output}${separator}${notice}\n`;
|
|
@@ -232,13 +98,7 @@ function appendModelNotice(output: string, notice: string): string {
|
|
|
232
98
|
|
|
233
99
|
function minimizedSaveNotice(
|
|
234
100
|
result: BashArtifactSaveResult,
|
|
235
|
-
summary: {
|
|
236
|
-
artifactId?: string;
|
|
237
|
-
artifactTruncatedBytes?: number;
|
|
238
|
-
sourceTruncatedBytes?: number;
|
|
239
|
-
sourceCaptureIncomplete?: boolean;
|
|
240
|
-
artifactFailureDiagnostic?: string;
|
|
241
|
-
},
|
|
101
|
+
summary: { artifactId?: string; artifactTruncatedBytes?: number; artifactFailureDiagnostic?: string },
|
|
242
102
|
): string | undefined {
|
|
243
103
|
if (result.status === "failed") return `Bash output artifact save failed: ${result.diagnostic}`;
|
|
244
104
|
if (result.status === "unavailable" && !completeRawArtifactAvailable(summary)) {
|
|
@@ -247,6 +107,13 @@ function minimizedSaveNotice(
|
|
|
247
107
|
return undefined;
|
|
248
108
|
}
|
|
249
109
|
|
|
110
|
+
function minimizedArtifactFooter(result: Extract<BashArtifactSaveResult, { status: "saved" }>): string {
|
|
111
|
+
const reference = result.complete
|
|
112
|
+
? `artifact://${result.artifactId}`
|
|
113
|
+
: formatArtifactReference(result.artifactId, result.omittedBytes);
|
|
114
|
+
return `[raw output: ${reference}]`;
|
|
115
|
+
}
|
|
116
|
+
|
|
250
117
|
export interface BashExecutorOptions {
|
|
251
118
|
/**
|
|
252
119
|
* Invoked when the native minimizer rewrote the command's output, giving
|
|
@@ -303,122 +170,40 @@ export interface BashResult {
|
|
|
303
170
|
outputBytes: number;
|
|
304
171
|
artifactId?: string;
|
|
305
172
|
artifactTruncatedBytes?: number;
|
|
306
|
-
/** Bytes dropped before the Bash executor received the native output stream. */
|
|
307
|
-
sourceTruncatedBytes?: number;
|
|
308
|
-
/** Exact source capture completeness could not be proven. */
|
|
309
|
-
sourceCaptureIncomplete?: boolean;
|
|
310
173
|
artifactFailureDiagnostic?: string;
|
|
311
174
|
}
|
|
312
175
|
|
|
313
176
|
const shellSessions = new Map<string, Shell>();
|
|
314
|
-
const brokenShellSessions = new
|
|
177
|
+
const brokenShellSessions = new Set<string>();
|
|
315
178
|
const retiringShellSessions = new Set<Shell>();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const MAX_OWNED_SHELL_SESSIONS = 64;
|
|
319
|
-
let shellSessionAdmissionTail = Promise.resolve();
|
|
320
|
-
|
|
321
|
-
async function withShellSessionAdmission<T>(operation: () => Promise<T>): Promise<T> {
|
|
322
|
-
const previous = shellSessionAdmissionTail;
|
|
323
|
-
const release = Promise.withResolvers<void>();
|
|
324
|
-
shellSessionAdmissionTail = previous.then(() => release.promise);
|
|
325
|
-
await previous;
|
|
326
|
-
try {
|
|
327
|
-
return await operation();
|
|
328
|
-
} finally {
|
|
329
|
-
release.resolve();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function touchShellSession(sessionKey: string, shellSession: Shell): void {
|
|
334
|
-
if (shellSessions.get(sessionKey) !== shellSession) return;
|
|
335
|
-
shellSessions.delete(sessionKey);
|
|
336
|
-
shellSessions.set(sessionKey, shellSession);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async function evictLeastRecentlyUsedIdleShell(): Promise<boolean> {
|
|
340
|
-
for (const [sessionKey, shellSession] of shellSessions) {
|
|
341
|
-
if ((shellInFlightRuns.get(shellSession) ?? 0) > 0) continue;
|
|
342
|
-
quarantineShellSession(sessionKey, shellSession);
|
|
343
|
-
const abortPromise = Promise.resolve()
|
|
344
|
-
.then(() => shellSession.abort())
|
|
345
|
-
.catch(() => undefined);
|
|
346
|
-
void abortPromise.finally(() => releaseQuarantinedShellIfIdle(sessionKey, shellSession));
|
|
347
|
-
return Promise.race([abortPromise.then(() => true), Bun.sleep(CANCEL_CLEANUP_WAIT_MS).then(() => false)]);
|
|
348
|
-
}
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function beginShellRun(shellSession: Shell): void {
|
|
353
|
-
shellInFlightRuns.set(shellSession, (shellInFlightRuns.get(shellSession) ?? 0) + 1);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function releaseQuarantinedShellIfIdle(sessionKey: string, shellSession: Shell): void {
|
|
357
|
-
if ((shellInFlightRuns.get(shellSession) ?? 0) > 0) return;
|
|
358
|
-
clearBrokenShellSession(sessionKey, shellSession);
|
|
359
|
-
retiringShellSessions.delete(shellSession);
|
|
360
|
-
shellSessionKeys.delete(shellSession);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function endShellRun(sessionKey: string, shellSession: Shell): void {
|
|
364
|
-
const remaining = Math.max(0, (shellInFlightRuns.get(shellSession) ?? 1) - 1);
|
|
365
|
-
if (remaining > 0) {
|
|
366
|
-
shellInFlightRuns.set(shellSession, remaining);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const quarantined = brokenShellSessions.get(sessionKey) === shellSession;
|
|
370
|
-
shellInFlightRuns.delete(shellSession);
|
|
371
|
-
if (quarantined) releaseQuarantinedShellIfIdle(sessionKey, shellSession);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function quarantineShellSession(sessionKey: string, shellSession: Shell): void {
|
|
375
|
-
retiringShellSessions.add(shellSession);
|
|
376
|
-
brokenShellSessions.set(sessionKey, shellSession);
|
|
377
|
-
shellSessionKeys.set(shellSession, sessionKey);
|
|
378
|
-
if (shellSessions.get(sessionKey) === shellSession) shellSessions.delete(sessionKey);
|
|
379
|
-
}
|
|
380
|
-
// Give ordinary native cancellation time to settle without turning a stalled
|
|
381
|
-
// cleanup into a multi-second JavaScript tool delay.
|
|
179
|
+
// Cover pi-shell's normal cancellation kill waves without turning a stalled
|
|
180
|
+
// native cleanup into a multi-second JavaScript tool stall.
|
|
382
181
|
const CANCEL_CLEANUP_WAIT_MS = 400;
|
|
383
|
-
function clearBrokenShellSession(sessionKey: string, shellSession: Shell): void {
|
|
384
|
-
if (brokenShellSessions.get(sessionKey) === shellSession) brokenShellSessions.delete(sessionKey);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
interface AbortCleanupOutcome {
|
|
388
|
-
settled: boolean;
|
|
389
|
-
result?: NativeShellRunResult;
|
|
390
|
-
}
|
|
391
182
|
|
|
392
|
-
/** Number of persistent
|
|
183
|
+
/** Number of persistent shell sessions currently retained (owner gauge). */
|
|
393
184
|
export function getShellSessionCount(): number {
|
|
394
|
-
return
|
|
185
|
+
return shellSessions.size;
|
|
395
186
|
}
|
|
396
187
|
|
|
397
188
|
/**
|
|
398
|
-
* Dispose all persistent shell sessions
|
|
399
|
-
*
|
|
400
|
-
*
|
|
189
|
+
* Dispose all persistent shell sessions: abort in-flight work and drop the
|
|
190
|
+
* strong references so the native shells can be finalized. Healthy persistent
|
|
191
|
+
* sessions are otherwise retained for the whole process lifetime (MEM-7). This
|
|
192
|
+
* is registered as a postmortem cleanup so shutdown/signals release native
|
|
193
|
+
* shell resources, and is also callable directly (e.g. on owner teardown).
|
|
401
194
|
*/
|
|
402
195
|
export async function disposeAllShellSessions(): Promise<void> {
|
|
196
|
+
// Snapshot and drop strong references up front so concurrent callers cannot
|
|
197
|
+
// reuse a session that is being torn down, then await every native abort so
|
|
198
|
+
// shutdown/signal cleanup does not return before resources are released.
|
|
199
|
+
// Include retiring shells whose JS call returned after bounded abort cleanup
|
|
200
|
+
// while the native run is still unwinding; they are no longer reusable but
|
|
201
|
+
// remain owned until their run promise settles.
|
|
403
202
|
const sessions = new Set([...shellSessions.values(), ...retiringShellSessions]);
|
|
404
|
-
for (const session of sessions) {
|
|
405
|
-
const sessionKey = shellSessionKeys.get(session);
|
|
406
|
-
if (sessionKey) quarantineShellSession(sessionKey, session);
|
|
407
|
-
else retiringShellSessions.add(session);
|
|
408
|
-
}
|
|
409
203
|
shellSessions.clear();
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const abortPromise = Promise.resolve()
|
|
414
|
-
.then(() => session.abort())
|
|
415
|
-
.catch(() => undefined);
|
|
416
|
-
if (sessionKey) {
|
|
417
|
-
void abortPromise.finally(() => releaseQuarantinedShellIfIdle(sessionKey, session));
|
|
418
|
-
}
|
|
419
|
-
await Promise.race([abortPromise, Bun.sleep(CANCEL_CLEANUP_WAIT_MS)]);
|
|
420
|
-
}),
|
|
421
|
-
);
|
|
204
|
+
retiringShellSessions.clear();
|
|
205
|
+
brokenShellSessions.clear();
|
|
206
|
+
await Promise.allSettled([...sessions].map(session => session.abort()));
|
|
422
207
|
}
|
|
423
208
|
|
|
424
209
|
postmortem.register("bash-executor:shell-sessions", () => disposeAllShellSessions());
|
|
@@ -448,9 +233,6 @@ export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerO
|
|
|
448
233
|
}
|
|
449
234
|
|
|
450
235
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
451
|
-
if (options?.artifactId !== undefined && !isValidArtifactId(options.artifactId)) {
|
|
452
|
-
throw new Error("Invalid Bash artifact id");
|
|
453
|
-
}
|
|
454
236
|
const settings = await Settings.init();
|
|
455
237
|
const { shell, env: shellEnv, prefix } = settings.getShellConfig();
|
|
456
238
|
const configuredPrefix = options?.ignoreShellPrefix ? undefined : prefix;
|
|
@@ -497,71 +279,51 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
497
279
|
};
|
|
498
280
|
}
|
|
499
281
|
|
|
282
|
+
const usePersistentShell = options?.oneShot !== true;
|
|
283
|
+
const sessionKey = buildSessionKey(shell, configuredPrefix, snapshotPath, shellEnv, options?.sessionKey, minimizer);
|
|
284
|
+
const persistentSessionBroken = usePersistentShell && brokenShellSessions.has(sessionKey);
|
|
285
|
+
|
|
286
|
+
let shellSession = persistentSessionBroken || !usePersistentShell ? undefined : shellSessions.get(sessionKey);
|
|
287
|
+
if (!shellSession && !persistentSessionBroken && usePersistentShell) {
|
|
288
|
+
shellSession = new Shell({
|
|
289
|
+
sessionEnv: shellEnv,
|
|
290
|
+
snapshotPath: snapshotPath ?? undefined,
|
|
291
|
+
minimizer,
|
|
292
|
+
});
|
|
293
|
+
shellSessions.set(sessionKey, shellSession);
|
|
294
|
+
}
|
|
500
295
|
const userSignal = options?.signal;
|
|
501
296
|
const runAbortController = new AbortController();
|
|
502
297
|
const abortCurrentExecution = () => {
|
|
503
|
-
if (!runAbortController.signal.aborted)
|
|
298
|
+
if (!runAbortController.signal.aborted) {
|
|
299
|
+
runAbortController.abort();
|
|
300
|
+
}
|
|
301
|
+
if (shellSession && !abortPromise) {
|
|
302
|
+
abortPromise = shellSession.abort();
|
|
303
|
+
}
|
|
504
304
|
};
|
|
505
305
|
const abortDeferred = Promise.withResolvers<"abort">();
|
|
306
|
+
let abortPromise: Promise<unknown> | undefined;
|
|
506
307
|
const abortHandler = () => {
|
|
507
308
|
abortCurrentExecution();
|
|
508
309
|
abortDeferred.resolve("abort");
|
|
509
310
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const usePersistentShell = options?.oneShot !== true;
|
|
514
|
-
const sessionKey = buildSessionKey(shell, configuredPrefix, snapshotPath, shellEnv, options?.sessionKey, minimizer);
|
|
515
|
-
let shellSession: Shell | undefined;
|
|
516
|
-
try {
|
|
517
|
-
shellSession = usePersistentShell
|
|
518
|
-
? await withShellSessionAdmission(async () => {
|
|
519
|
-
if (brokenShellSessions.has(sessionKey)) return undefined;
|
|
520
|
-
const existing = shellSessions.get(sessionKey);
|
|
521
|
-
if (existing) {
|
|
522
|
-
touchShellSession(sessionKey, existing);
|
|
523
|
-
beginShellRun(existing);
|
|
524
|
-
return existing;
|
|
525
|
-
}
|
|
526
|
-
if (getShellSessionCount() >= MAX_OWNED_SHELL_SESSIONS) {
|
|
527
|
-
const evicted = await evictLeastRecentlyUsedIdleShell();
|
|
528
|
-
if (!evicted || getShellSessionCount() >= MAX_OWNED_SHELL_SESSIONS) {
|
|
529
|
-
throw new Error(`Bash shell session limit reached (${MAX_OWNED_SHELL_SESSIONS})`);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
const created = new Shell({
|
|
533
|
-
sessionEnv: shellEnv,
|
|
534
|
-
snapshotPath: snapshotPath ?? undefined,
|
|
535
|
-
minimizer,
|
|
536
|
-
});
|
|
537
|
-
shellSessions.set(sessionKey, created);
|
|
538
|
-
shellSessionKeys.set(created, sessionKey);
|
|
539
|
-
beginShellRun(created);
|
|
540
|
-
return created;
|
|
541
|
-
})
|
|
542
|
-
: undefined;
|
|
543
|
-
} catch (error) {
|
|
544
|
-
userSignal?.removeEventListener("abort", abortHandler);
|
|
545
|
-
throw error;
|
|
546
|
-
}
|
|
547
|
-
if (userSignal?.aborted) {
|
|
548
|
-
if (shellSession) endShellRun(sessionKey, shellSession);
|
|
549
|
-
userSignal.removeEventListener("abort", abortHandler);
|
|
550
|
-
return {
|
|
551
|
-
exitCode: undefined,
|
|
552
|
-
cancelled: true,
|
|
553
|
-
...(await sink.dump("Command cancelled")),
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const awaitAbortCleanup = (runPromise: Promise<NativeShellRunResult>): Promise<AbortCleanupOutcome> =>
|
|
558
|
-
Promise.race([
|
|
311
|
+
const awaitAbortCleanup = async (runPromise: Promise<unknown>): Promise<boolean> => {
|
|
312
|
+
const settled = await Promise.race([
|
|
559
313
|
runPromise.then(
|
|
560
|
-
|
|
561
|
-
() =>
|
|
314
|
+
() => true,
|
|
315
|
+
() => true,
|
|
562
316
|
),
|
|
563
|
-
Bun.sleep(CANCEL_CLEANUP_WAIT_MS).then(() =>
|
|
317
|
+
Bun.sleep(CANCEL_CLEANUP_WAIT_MS).then(() => false),
|
|
564
318
|
]);
|
|
319
|
+
if (abortPromise) {
|
|
320
|
+
await Promise.race([abortPromise.catch(() => undefined), Bun.sleep(CANCEL_CLEANUP_WAIT_MS)]);
|
|
321
|
+
}
|
|
322
|
+
return settled;
|
|
323
|
+
};
|
|
324
|
+
if (userSignal) {
|
|
325
|
+
userSignal.addEventListener("abort", abortHandler, { once: true });
|
|
326
|
+
}
|
|
565
327
|
|
|
566
328
|
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
567
329
|
const timeoutDeferred = Promise.withResolvers<"timeout">();
|
|
@@ -575,12 +337,11 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
575
337
|
}
|
|
576
338
|
|
|
577
339
|
let resetSession = false;
|
|
340
|
+
let runSettled = false;
|
|
578
341
|
|
|
579
342
|
try {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
runPromise = shellSession.run(
|
|
343
|
+
const runPromise = shellSession
|
|
344
|
+
? shellSession.run(
|
|
584
345
|
{
|
|
585
346
|
command: finalCommand,
|
|
586
347
|
cwd: commandCwd,
|
|
@@ -589,31 +350,28 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
589
350
|
signal: runAbortController.signal,
|
|
590
351
|
},
|
|
591
352
|
(err, chunk) => {
|
|
592
|
-
if (!err)
|
|
353
|
+
if (!err) {
|
|
354
|
+
enqueueChunk(chunk);
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
)
|
|
358
|
+
: executeShell(
|
|
359
|
+
{
|
|
360
|
+
command: finalCommand,
|
|
361
|
+
cwd: commandCwd,
|
|
362
|
+
env: commandEnv,
|
|
363
|
+
sessionEnv: shellEnv,
|
|
364
|
+
snapshotPath: snapshotPath ?? undefined,
|
|
365
|
+
minimizer,
|
|
366
|
+
timeoutMs: executionTimeoutMs,
|
|
367
|
+
signal: runAbortController.signal,
|
|
368
|
+
},
|
|
369
|
+
(err, chunk) => {
|
|
370
|
+
if (!err) {
|
|
371
|
+
enqueueChunk(chunk);
|
|
372
|
+
}
|
|
593
373
|
},
|
|
594
374
|
);
|
|
595
|
-
} catch (error) {
|
|
596
|
-
endShellRun(sessionKey, shellSession);
|
|
597
|
-
throw error;
|
|
598
|
-
}
|
|
599
|
-
void runPromise.finally(() => endShellRun(sessionKey, shellSession)).catch(() => undefined);
|
|
600
|
-
} else {
|
|
601
|
-
runPromise = executeShell(
|
|
602
|
-
{
|
|
603
|
-
command: finalCommand,
|
|
604
|
-
cwd: commandCwd,
|
|
605
|
-
env: commandEnv,
|
|
606
|
-
sessionEnv: shellEnv,
|
|
607
|
-
snapshotPath: snapshotPath ?? undefined,
|
|
608
|
-
minimizer,
|
|
609
|
-
timeoutMs: executionTimeoutMs,
|
|
610
|
-
signal: runAbortController.signal,
|
|
611
|
-
},
|
|
612
|
-
(err, chunk) => {
|
|
613
|
-
if (!err) enqueueChunk(chunk);
|
|
614
|
-
},
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
375
|
|
|
618
376
|
const winner = await Promise.race([
|
|
619
377
|
runPromise.then(result => ({ kind: "result" as const, result })),
|
|
@@ -622,37 +380,44 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
622
380
|
]);
|
|
623
381
|
|
|
624
382
|
if (winner.kind === "timeout" || winner.kind === "abort") {
|
|
625
|
-
const abortOutcome = await awaitAbortCleanup(runPromise);
|
|
626
383
|
acceptingChunks = false;
|
|
627
384
|
if (shellSession) {
|
|
628
385
|
resetSession = true;
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
386
|
+
retiringShellSessions.add(shellSession);
|
|
387
|
+
brokenShellSessions.add(sessionKey);
|
|
388
|
+
shellSessions.delete(sessionKey);
|
|
389
|
+
runSettled = await awaitAbortCleanup(runPromise);
|
|
390
|
+
if (runSettled) {
|
|
391
|
+
brokenShellSessions.delete(sessionKey);
|
|
392
|
+
retiringShellSessions.delete(shellSession);
|
|
393
|
+
} else {
|
|
394
|
+
void runPromise
|
|
395
|
+
.finally(() => {
|
|
396
|
+
brokenShellSessions.delete(sessionKey);
|
|
397
|
+
retiringShellSessions.delete(shellSession);
|
|
398
|
+
if (shellSessions.get(sessionKey) === shellSession) {
|
|
399
|
+
shellSessions.delete(sessionKey);
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
.catch(() => undefined);
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
632
405
|
void runPromise.catch(() => undefined);
|
|
633
406
|
}
|
|
634
|
-
const summary = applyShellCallbackLoss(
|
|
635
|
-
await sink.dump(
|
|
636
|
-
winner.kind === "timeout" && baseTimeoutMs !== undefined
|
|
637
|
-
? `Command timed out after ${Math.round(baseTimeoutMs / 1000)} seconds`
|
|
638
|
-
: "Command cancelled",
|
|
639
|
-
),
|
|
640
|
-
abortOutcome.result?.droppedOutputBytes,
|
|
641
|
-
abortOutcome.result?.droppedOutputChunks,
|
|
642
|
-
nativeCaptureStatusIncomplete(abortOutcome.result),
|
|
643
|
-
);
|
|
644
407
|
return {
|
|
645
408
|
exitCode: undefined,
|
|
646
409
|
cancelled: true,
|
|
647
|
-
...
|
|
410
|
+
...(await sink.dump(
|
|
411
|
+
winner.kind === "timeout" && baseTimeoutMs !== undefined
|
|
412
|
+
? `Command timed out after ${Math.round(baseTimeoutMs / 1000)} seconds`
|
|
413
|
+
: "Command cancelled",
|
|
414
|
+
)),
|
|
648
415
|
};
|
|
649
416
|
}
|
|
650
417
|
if (timeoutTimer) {
|
|
651
418
|
clearTimeout(timeoutTimer);
|
|
652
419
|
timeoutTimer = undefined;
|
|
653
420
|
}
|
|
654
|
-
const minimizedEvidence = normalizeNativeMinimized(winner.result.minimized);
|
|
655
|
-
const nativeEvidenceIncomplete = nativeCaptureStatusIncomplete(winner.result) || minimizedEvidence.malformed;
|
|
656
421
|
|
|
657
422
|
// Handle timeout
|
|
658
423
|
if (winner.result.timedOut) {
|
|
@@ -663,12 +428,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
663
428
|
return {
|
|
664
429
|
exitCode: undefined,
|
|
665
430
|
cancelled: true,
|
|
666
|
-
...
|
|
667
|
-
await sink.dump(annotation),
|
|
668
|
-
winner.result.droppedOutputBytes,
|
|
669
|
-
winner.result.droppedOutputChunks,
|
|
670
|
-
nativeEvidenceIncomplete,
|
|
671
|
-
),
|
|
431
|
+
...(await sink.dump(annotation)),
|
|
672
432
|
};
|
|
673
433
|
}
|
|
674
434
|
|
|
@@ -678,12 +438,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
678
438
|
return {
|
|
679
439
|
exitCode: undefined,
|
|
680
440
|
cancelled: true,
|
|
681
|
-
...
|
|
682
|
-
await sink.dump("Command cancelled"),
|
|
683
|
-
winner.result.droppedOutputBytes,
|
|
684
|
-
winner.result.droppedOutputChunks,
|
|
685
|
-
nativeEvidenceIncomplete,
|
|
686
|
-
),
|
|
441
|
+
...(await sink.dump("Command cancelled")),
|
|
687
442
|
};
|
|
688
443
|
}
|
|
689
444
|
|
|
@@ -691,24 +446,21 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
691
446
|
// raw stream for the minimized text, persist the original as a session
|
|
692
447
|
// artifact, and splice an artifact footer into the visible text so the agent
|
|
693
448
|
// can retrieve retained raw bytes without a false completeness claim.
|
|
694
|
-
const minimized =
|
|
449
|
+
const minimized = winner.result.minimized;
|
|
695
450
|
let minimizedSaveResult: BashArtifactSaveResult | undefined;
|
|
696
451
|
if (minimized && minimized.text !== minimized.originalText) {
|
|
697
452
|
sink.replace(minimized.text);
|
|
698
|
-
|
|
699
|
-
? await
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
: { status: "unavailable" };
|
|
453
|
+
const saved = options?.onMinimizedSave
|
|
454
|
+
? await options.onMinimizedSave(minimized.originalText, {
|
|
455
|
+
filter: minimized.filter,
|
|
456
|
+
inputBytes: minimized.inputBytes,
|
|
457
|
+
outputBytes: minimized.outputBytes,
|
|
458
|
+
})
|
|
459
|
+
: undefined;
|
|
460
|
+
minimizedSaveResult = normalizeMinimizedSaveResult(saved, minimized.originalText);
|
|
707
461
|
if (minimizedSaveResult.status === "saved") {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
diagnostic: "artifact save could not be verified in the current session",
|
|
711
|
-
};
|
|
462
|
+
const sep = minimized.text.endsWith("\n") ? "" : "\n";
|
|
463
|
+
sink.push(`${sep}${minimizedArtifactFooter(minimizedSaveResult)}\n`);
|
|
712
464
|
}
|
|
713
465
|
}
|
|
714
466
|
|
|
@@ -728,12 +480,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
728
480
|
}
|
|
729
481
|
|
|
730
482
|
// Normal completion
|
|
731
|
-
const summary =
|
|
732
|
-
await sink.dump(),
|
|
733
|
-
winner.result.droppedOutputBytes,
|
|
734
|
-
winner.result.droppedOutputChunks,
|
|
735
|
-
nativeEvidenceIncomplete,
|
|
736
|
-
);
|
|
483
|
+
const summary = await sink.dump();
|
|
737
484
|
const saveNotice = minimizedSaveResult ? minimizedSaveNotice(minimizedSaveResult, summary) : undefined;
|
|
738
485
|
return {
|
|
739
486
|
exitCode: winner.result.exitCode,
|
|
@@ -743,23 +490,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
743
490
|
};
|
|
744
491
|
} catch (err) {
|
|
745
492
|
resetSession = true;
|
|
746
|
-
|
|
747
|
-
const captured = await sink.dump();
|
|
748
|
-
const rawErrorMessage = err instanceof Error ? err.message : String(err);
|
|
749
|
-
const boundedErrorMessage = truncateHeadBytes(rawErrorMessage, 1024);
|
|
750
|
-
const errorMessage = `${boundedErrorMessage.text}${
|
|
751
|
-
boundedErrorMessage.bytes < Buffer.byteLength(rawErrorMessage, "utf-8") ? "\n[native error truncated]" : ""
|
|
752
|
-
}`;
|
|
753
|
-
if (captured.totalBytes <= 0) {
|
|
754
|
-
throw new Error(
|
|
755
|
-
`Source capture completeness could not be proven before any output was retained\n\nNative shell execution failed: ${errorMessage}`,
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
const summary = applyShellCallbackLoss(captured, undefined, undefined, true);
|
|
759
|
-
const evidence = formatArtifactEvidenceNotice(summary);
|
|
760
|
-
const suffix = [evidence, `Native shell execution failed: ${errorMessage}`].filter(Boolean).join("\n\n");
|
|
761
|
-
const separator = summary.output.length > 0 ? (summary.output.endsWith("\n") ? "\n" : "\n\n") : "";
|
|
762
|
-
throw new Error(`${summary.output}${separator}${suffix}`);
|
|
493
|
+
throw err;
|
|
763
494
|
} finally {
|
|
764
495
|
if (timeoutTimer) {
|
|
765
496
|
clearTimeout(timeoutTimer);
|
|
@@ -767,13 +498,8 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
767
498
|
if (userSignal) {
|
|
768
499
|
userSignal.removeEventListener("abort", abortHandler);
|
|
769
500
|
}
|
|
770
|
-
if (resetSession && shellSession) {
|
|
771
|
-
|
|
772
|
-
if ((shellInFlightRuns.get(shellSession) ?? 0) > 0) {
|
|
773
|
-
quarantineShellSession(sessionKey, shellSession);
|
|
774
|
-
} else {
|
|
775
|
-
releaseQuarantinedShellIfIdle(sessionKey, shellSession);
|
|
776
|
-
}
|
|
501
|
+
if (resetSession && runSettled && shellSessions.get(sessionKey) === shellSession) {
|
|
502
|
+
shellSessions.delete(sessionKey);
|
|
777
503
|
}
|
|
778
504
|
}
|
|
779
505
|
}
|