gsd-pi 2.8.0 → 2.8.2
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/loader.js +5 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/config.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js +32 -22
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +3 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js +4 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -5
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts +7 -0
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js +11 -0
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/config.ts +5 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/bash-executor.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash.ts +35 -22
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
- package/node_modules/@gsd/pi-coding-agent/src/index.ts +4 -1
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
- package/node_modules/@gsd/pi-coding-agent/src/utils/shell.ts +11 -0
- package/package.json +6 -1
- package/packages/pi-coding-agent/dist/config.d.ts +2 -0
- package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/config.js +4 -0
- package/packages/pi-coding-agent/dist/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/bash-executor.js +2 -2
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +32 -22
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +3 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -5
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +11 -0
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/src/config.ts +5 -0
- package/packages/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +2 -2
- package/packages/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/packages/pi-coding-agent/src/core/tools/bash.ts +35 -22
- package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
- package/packages/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
- package/packages/pi-coding-agent/src/index.ts +4 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +11 -0
- package/src/resources/extensions/bg-shell/index.ts +2 -1
- package/src/resources/extensions/browser-tools/lifecycle.ts +6 -1
- package/src/resources/extensions/gsd/auto.ts +92 -49
- package/src/resources/extensions/gsd/dispatch-guard.ts +65 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +76 -0
- package/src/resources/extensions/gsd/exit-command.ts +18 -0
- package/src/resources/extensions/gsd/files.ts +9 -40
- package/src/resources/extensions/gsd/git-service.ts +62 -17
- package/src/resources/extensions/gsd/gitignore.ts +28 -0
- package/src/resources/extensions/gsd/guided-flow.ts +49 -11
- package/src/resources/extensions/gsd/index.ts +111 -16
- package/src/resources/extensions/gsd/preferences.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +27 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -3
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/src/resources/extensions/gsd/roadmap-slices.ts +50 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +116 -39
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/src/resources/extensions/ttsr/index.ts +163 -0
- package/src/resources/extensions/ttsr/rule-loader.ts +121 -0
- package/src/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
- package/src/resources/extensions/ttsr/ttsr-manager.ts +344 -0
|
@@ -6,8 +6,9 @@ import { join } from "node:path";
|
|
|
6
6
|
import type { AgentTool } from "@gsd/pi-agent-core";
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import { spawn } from "child_process";
|
|
9
|
-
import { getShellConfig, getShellEnv, killProcessTree } from "../../utils/shell.js";
|
|
9
|
+
import { getShellConfig, getShellEnv, killProcessTree, sanitizeCommand } from "../../utils/shell.js";
|
|
10
10
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from "./truncate.js";
|
|
11
|
+
import type { ArtifactManager } from "../artifact-manager.js";
|
|
11
12
|
|
|
12
13
|
// Cached Win32 FFI handles for restoring VT input after child processes
|
|
13
14
|
let _vtHandles: { GetConsoleMode: any; SetConsoleMode: any; handle: any } | null = null;
|
|
@@ -51,6 +52,7 @@ export type BashToolInput = Static<typeof bashSchema>;
|
|
|
51
52
|
export interface BashToolDetails {
|
|
52
53
|
truncation?: TruncationResult;
|
|
53
54
|
fullOutputPath?: string;
|
|
55
|
+
artifactId?: string;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -187,12 +189,15 @@ export interface BashToolOptions {
|
|
|
187
189
|
commandPrefix?: string;
|
|
188
190
|
/** Hook to adjust command, cwd, or env before execution */
|
|
189
191
|
spawnHook?: BashSpawnHook;
|
|
192
|
+
/** Session-scoped artifact storage. When provided, spills to artifact files instead of temp files. */
|
|
193
|
+
artifactManager?: ArtifactManager;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
|
|
193
197
|
const ops = options?.operations ?? defaultBashOperations;
|
|
194
198
|
const commandPrefix = options?.commandPrefix;
|
|
195
199
|
const spawnHook = options?.spawnHook;
|
|
200
|
+
const artifactManager = options?.artifactManager;
|
|
196
201
|
|
|
197
202
|
return {
|
|
198
203
|
name: "bash",
|
|
@@ -206,13 +211,14 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
206
211
|
onUpdate?,
|
|
207
212
|
) => {
|
|
208
213
|
// Apply command prefix if configured (e.g., "shopt -s expand_aliases" for alias support)
|
|
209
|
-
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
|
|
214
|
+
const resolvedCommand = sanitizeCommand(commandPrefix ? `${commandPrefix}\n${command}` : command);
|
|
210
215
|
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
|
|
211
216
|
|
|
212
217
|
return new Promise((resolve, reject) => {
|
|
213
|
-
// We'll stream to a
|
|
214
|
-
let
|
|
215
|
-
let
|
|
218
|
+
// We'll stream to a file if output gets large
|
|
219
|
+
let spillFilePath: string | undefined;
|
|
220
|
+
let spillArtifactId: string | undefined;
|
|
221
|
+
let spillFileStream: ReturnType<typeof createWriteStream> | undefined;
|
|
216
222
|
let totalBytes = 0;
|
|
217
223
|
|
|
218
224
|
// Keep a rolling buffer of the last chunk for tail truncation
|
|
@@ -224,19 +230,25 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
224
230
|
const handleData = (data: Buffer) => {
|
|
225
231
|
totalBytes += data.length;
|
|
226
232
|
|
|
227
|
-
// Start writing to
|
|
228
|
-
if (totalBytes > DEFAULT_MAX_BYTES && !
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
// Start writing to file once we exceed the threshold
|
|
234
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !spillFilePath) {
|
|
235
|
+
if (artifactManager) {
|
|
236
|
+
const allocated = artifactManager.allocatePath("bash");
|
|
237
|
+
spillFilePath = allocated.path;
|
|
238
|
+
spillArtifactId = allocated.id;
|
|
239
|
+
} else {
|
|
240
|
+
spillFilePath = getTempFilePath();
|
|
241
|
+
}
|
|
242
|
+
spillFileStream = createWriteStream(spillFilePath);
|
|
231
243
|
// Write all buffered chunks to the file
|
|
232
244
|
for (const chunk of chunks) {
|
|
233
|
-
|
|
245
|
+
spillFileStream.write(chunk);
|
|
234
246
|
}
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
// Write to temp file if we have one
|
|
238
|
-
if (
|
|
239
|
-
|
|
250
|
+
if (spillFileStream) {
|
|
251
|
+
spillFileStream.write(data);
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
// Keep rolling buffer of recent data
|
|
@@ -258,7 +270,7 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
258
270
|
content: [{ type: "text", text: truncation.content || "" }],
|
|
259
271
|
details: {
|
|
260
272
|
truncation: truncation.truncated ? truncation : undefined,
|
|
261
|
-
fullOutputPath:
|
|
273
|
+
fullOutputPath: spillFilePath,
|
|
262
274
|
},
|
|
263
275
|
});
|
|
264
276
|
}
|
|
@@ -272,8 +284,8 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
272
284
|
})
|
|
273
285
|
.then(({ exitCode }) => {
|
|
274
286
|
// Close temp file stream
|
|
275
|
-
if (
|
|
276
|
-
|
|
287
|
+
if (spillFileStream) {
|
|
288
|
+
spillFileStream.end();
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
// Combine all buffered chunks
|
|
@@ -290,21 +302,22 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
290
302
|
if (truncation.truncated) {
|
|
291
303
|
details = {
|
|
292
304
|
truncation,
|
|
293
|
-
fullOutputPath:
|
|
305
|
+
fullOutputPath: spillFilePath,
|
|
306
|
+
...(spillArtifactId ? { artifactId: spillArtifactId } : {}),
|
|
294
307
|
};
|
|
295
308
|
|
|
296
309
|
// Build actionable notice
|
|
297
310
|
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
298
311
|
const endLine = truncation.totalLines;
|
|
312
|
+
const outputRef = spillArtifactId ? `artifact://${spillArtifactId}` : spillFilePath;
|
|
299
313
|
|
|
300
314
|
if (truncation.lastLinePartial) {
|
|
301
|
-
// Edge case: last line alone > 30KB
|
|
302
315
|
const lastLineSize = formatSize(Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8"));
|
|
303
|
-
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${
|
|
316
|
+
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${outputRef}]`;
|
|
304
317
|
} else if (truncation.truncatedBy === "lines") {
|
|
305
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${
|
|
318
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${outputRef}]`;
|
|
306
319
|
} else {
|
|
307
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${
|
|
320
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${outputRef}]`;
|
|
308
321
|
}
|
|
309
322
|
}
|
|
310
323
|
|
|
@@ -317,8 +330,8 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
317
330
|
})
|
|
318
331
|
.catch((err: Error) => {
|
|
319
332
|
// Close temp file stream
|
|
320
|
-
if (
|
|
321
|
-
|
|
333
|
+
if (spillFileStream) {
|
|
334
|
+
spillFileStream.end();
|
|
322
335
|
}
|
|
323
336
|
|
|
324
337
|
// Combine all buffered chunks for error output
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, mock, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { resolveToCwd, expandPath } from "./path-utils.js";
|
|
4
|
+
|
|
5
|
+
describe("resolveToCwd", () => {
|
|
6
|
+
it("resolves relative paths against cwd", () => {
|
|
7
|
+
const result = resolveToCwd("foo/bar.txt", "/home/user/project");
|
|
8
|
+
assert.equal(result, "/home/user/project/foo/bar.txt");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("returns absolute paths unchanged", () => {
|
|
12
|
+
const result = resolveToCwd("/absolute/path.txt", "/home/user/project");
|
|
13
|
+
assert.equal(result, "/absolute/path.txt");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("expands ~ to home directory", () => {
|
|
17
|
+
const result = resolveToCwd("~/file.txt", "/home/user/project");
|
|
18
|
+
assert.ok(result.endsWith("/file.txt"));
|
|
19
|
+
assert.ok(!result.includes("~"));
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("normalizeMsysPath (via resolveToCwd on win32)", () => {
|
|
24
|
+
const originalPlatform = process.platform;
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("converts /c/Users/... to C:\\Users\\... on win32", () => {
|
|
31
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
32
|
+
// Re-import to pick up platform change — but since normalizeMsysPath
|
|
33
|
+
// reads process.platform at call time, we can test directly.
|
|
34
|
+
// On non-Windows, resolveToCwd treats /c/Users as absolute, so we
|
|
35
|
+
// test the normalization logic by checking the MSYS regex behavior.
|
|
36
|
+
const msysPath = "/c/Users/test/project";
|
|
37
|
+
const msysRegex = /^\/[a-zA-Z]\//;
|
|
38
|
+
assert.ok(msysRegex.test(msysPath), "MSYS path pattern matches");
|
|
39
|
+
|
|
40
|
+
// Simulate the conversion
|
|
41
|
+
const converted = `${msysPath[1].toUpperCase()}:\\${msysPath.slice(3).replace(/\//g, "\\")}`;
|
|
42
|
+
assert.equal(converted, "C:\\Users\\test\\project");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("converts /f/Projects to F:\\Projects on win32", () => {
|
|
46
|
+
const msysPath = "/f/Projects";
|
|
47
|
+
const converted = `${msysPath[1].toUpperCase()}:\\${msysPath.slice(3).replace(/\//g, "\\")}`;
|
|
48
|
+
assert.equal(converted, "F:\\Projects");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("does not convert regular Unix paths", () => {
|
|
52
|
+
const regularPath = "/usr/local/bin";
|
|
53
|
+
const msysRegex = /^\/[a-zA-Z]\//;
|
|
54
|
+
// /u/local/bin would match, but /usr/local/bin has 3+ chars before /
|
|
55
|
+
// Actually /u/ would match — but /usr/ won't because 'us' is 2 chars.
|
|
56
|
+
// The regex checks single letter after leading slash.
|
|
57
|
+
assert.ok(!msysRegex.test("/usr/local/bin"), "/usr/... is not an MSYS path");
|
|
58
|
+
assert.ok(msysRegex.test("/u/local/bin"), "/u/... would match (single letter)");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("does not convert paths without leading slash", () => {
|
|
62
|
+
const msysRegex = /^\/[a-zA-Z]\//;
|
|
63
|
+
assert.ok(!msysRegex.test("c/Users/test"), "no leading slash — not MSYS");
|
|
64
|
+
assert.ok(!msysRegex.test("relative/path"), "relative path — not MSYS");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -47,12 +47,24 @@ export function expandPath(filePath: string): string {
|
|
|
47
47
|
return normalized;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* On Windows, convert MSYS/MinGW-style paths (/c/Users/...) to native
|
|
52
|
+
* drive letter paths (C:\Users\...). LLMs often produce these when given
|
|
53
|
+
* Windows paths in prompts.
|
|
54
|
+
*/
|
|
55
|
+
function normalizeMsysPath(p: string): string {
|
|
56
|
+
if (process.platform === "win32" && /^\/[a-zA-Z]\//.test(p)) {
|
|
57
|
+
return `${p[1]!.toUpperCase()}:\\${p.slice(3).replace(/\//g, "\\")}`;
|
|
58
|
+
}
|
|
59
|
+
return p;
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
/**
|
|
51
63
|
* Resolve a path relative to the given cwd.
|
|
52
|
-
* Handles ~ expansion and absolute paths.
|
|
64
|
+
* Handles ~ expansion, MSYS-style paths on Windows, and absolute paths.
|
|
53
65
|
*/
|
|
54
66
|
export function resolveToCwd(filePath: string, cwd: string): string {
|
|
55
|
-
const expanded = expandPath(filePath);
|
|
67
|
+
const expanded = normalizeMsysPath(expandPath(filePath));
|
|
56
68
|
if (isAbsolute(expanded)) {
|
|
57
69
|
return expanded;
|
|
58
70
|
}
|
|
@@ -198,6 +198,9 @@ export {
|
|
|
198
198
|
type SessionMessageEntry,
|
|
199
199
|
type ThinkingLevelChangeEntry,
|
|
200
200
|
} from "./core/session-manager.js";
|
|
201
|
+
// Blob and artifact storage
|
|
202
|
+
export { BlobStore, isBlobRef, parseBlobRef, externalizeImageData, resolveImageData } from "./core/blob-store.js";
|
|
203
|
+
export { ArtifactManager } from "./core/artifact-manager.js";
|
|
201
204
|
export {
|
|
202
205
|
type CompactionSettings,
|
|
203
206
|
type ImageSettings,
|
|
@@ -330,4 +333,4 @@ export {
|
|
|
330
333
|
export { copyToClipboard } from "./utils/clipboard.js";
|
|
331
334
|
export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.js";
|
|
332
335
|
// Shell utilities
|
|
333
|
-
export { getShellConfig } from "./utils/shell.js";
|
|
336
|
+
export { getShellConfig, sanitizeCommand } from "./utils/shell.js";
|
|
@@ -824,10 +824,12 @@ export class InteractiveMode {
|
|
|
824
824
|
|
|
825
825
|
// Try parent directories (package manager stores directory paths)
|
|
826
826
|
let current = p;
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const
|
|
830
|
-
if (
|
|
827
|
+
let parent = path.dirname(current);
|
|
828
|
+
while (parent !== current) {
|
|
829
|
+
const meta = metadata.get(parent);
|
|
830
|
+
if (meta) return meta;
|
|
831
|
+
current = parent;
|
|
832
|
+
parent = path.dirname(current);
|
|
831
833
|
}
|
|
832
834
|
|
|
833
835
|
return undefined;
|
|
@@ -118,6 +118,17 @@ export function getShellConfig(): { shell: string; args: string[] } {
|
|
|
118
118
|
return cachedShellConfig;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* On Windows + Git Bash, rewrite Windows-style NUL redirects to /dev/null.
|
|
123
|
+
* Git Bash doesn't recognize NUL as a device name and creates a literal file
|
|
124
|
+
* that is undeletable due to NUL being a reserved Windows device name.
|
|
125
|
+
* No-op on non-Windows platforms.
|
|
126
|
+
*/
|
|
127
|
+
export function sanitizeCommand(command: string): string {
|
|
128
|
+
if (process.platform !== "win32") return command;
|
|
129
|
+
return command.replace(/(\d*>>?) *\bNUL\b(?=\s|;|\||&|\)|$)/gi, "$1 /dev/null");
|
|
130
|
+
}
|
|
131
|
+
|
|
121
132
|
export function getShellEnv(): NodeJS.ProcessEnv {
|
|
122
133
|
const binDir = getBinDir();
|
|
123
134
|
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
DEFAULT_MAX_BYTES,
|
|
35
35
|
DEFAULT_MAX_LINES,
|
|
36
36
|
getShellConfig,
|
|
37
|
+
sanitizeCommand,
|
|
37
38
|
} from "@gsd/pi-coding-agent";
|
|
38
39
|
import {
|
|
39
40
|
Text,
|
|
@@ -582,7 +583,7 @@ function startProcess(opts: StartOptions): BgProcess {
|
|
|
582
583
|
const env = { ...process.env, ...(opts.env || {}) };
|
|
583
584
|
|
|
584
585
|
const { shell, args: shellArgs } = getShellConfig();
|
|
585
|
-
const proc = spawn(shell, [...shellArgs, opts.command], {
|
|
586
|
+
const proc = spawn(shell, [...shellArgs, sanitizeCommand(opts.command)], {
|
|
586
587
|
cwd: opts.cwd,
|
|
587
588
|
stdio: ["pipe", "pipe", "pipe"],
|
|
588
589
|
env,
|
|
@@ -181,7 +181,12 @@ export async function ensureBrowser(): Promise<{ browser: Browser; context: Brow
|
|
|
181
181
|
// Lazy import so playwright is only loaded when actually needed
|
|
182
182
|
const { chromium } = await import("playwright");
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
// Auto-detect headless environments: Linux without $DISPLAY has no GUI.
|
|
185
|
+
// All browser tool operations (navigation, screenshots, DOM) work in headless mode.
|
|
186
|
+
const needsHeadless = process.platform === "linux" && !process.env.DISPLAY;
|
|
187
|
+
const launchOptions: Record<string, unknown> = {
|
|
188
|
+
headless: needsHeadless || process.env.FORCE_HEADLESS === "true",
|
|
189
|
+
};
|
|
185
190
|
const customPath = process.env.BROWSER_PATH;
|
|
186
191
|
if (customPath) launchOptions.executablePath = customPath;
|
|
187
192
|
const browser = await chromium.launch(launchOptions);
|