gsd-pi 2.36.0-dev.f887f4e → 2.37.0-dev.3186675

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.
Files changed (71) hide show
  1. package/dist/resources/extensions/cmux/index.js +321 -0
  2. package/dist/resources/extensions/cmux/package.json +7 -0
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
  4. package/dist/resources/extensions/gsd/auto-loop.js +29 -4
  5. package/dist/resources/extensions/gsd/auto.js +35 -5
  6. package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
  7. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  8. package/dist/resources/extensions/gsd/commands.js +51 -1
  9. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  10. package/dist/resources/extensions/gsd/git-service.js +9 -1
  11. package/dist/resources/extensions/gsd/history.js +2 -1
  12. package/dist/resources/extensions/gsd/index.js +5 -0
  13. package/dist/resources/extensions/gsd/metrics.js +4 -2
  14. package/dist/resources/extensions/gsd/notifications.js +10 -1
  15. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  16. package/dist/resources/extensions/gsd/preferences-validation.js +29 -0
  17. package/dist/resources/extensions/gsd/preferences.js +3 -0
  18. package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
  19. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
  20. package/dist/resources/extensions/gsd/session-lock.js +26 -6
  21. package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
  22. package/dist/resources/extensions/search-the-web/native-search.js +45 -4
  23. package/dist/resources/extensions/shared/format-utils.js +5 -41
  24. package/dist/resources/extensions/shared/layout-utils.js +46 -0
  25. package/dist/resources/extensions/shared/mod.js +2 -1
  26. package/dist/resources/extensions/shared/terminal.js +5 -0
  27. package/dist/resources/extensions/subagent/index.js +180 -60
  28. package/package.json +1 -1
  29. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  30. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
  31. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  32. package/packages/pi-coding-agent/package.json +1 -1
  33. package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
  34. package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
  35. package/packages/pi-tui/dist/terminal-image.js +4 -0
  36. package/packages/pi-tui/dist/terminal-image.js.map +1 -1
  37. package/packages/pi-tui/src/terminal-image.ts +5 -0
  38. package/pkg/package.json +1 -1
  39. package/src/resources/extensions/cmux/index.ts +384 -0
  40. package/src/resources/extensions/cmux/package.json +7 -0
  41. package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
  42. package/src/resources/extensions/gsd/auto-loop.ts +66 -6
  43. package/src/resources/extensions/gsd/auto.ts +45 -5
  44. package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
  45. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  46. package/src/resources/extensions/gsd/commands.ts +54 -1
  47. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  48. package/src/resources/extensions/gsd/git-service.ts +12 -1
  49. package/src/resources/extensions/gsd/history.ts +2 -1
  50. package/src/resources/extensions/gsd/index.ts +8 -0
  51. package/src/resources/extensions/gsd/metrics.ts +4 -2
  52. package/src/resources/extensions/gsd/notifications.ts +10 -1
  53. package/src/resources/extensions/gsd/preferences-types.ts +13 -0
  54. package/src/resources/extensions/gsd/preferences-validation.ts +26 -0
  55. package/src/resources/extensions/gsd/preferences.ts +4 -0
  56. package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
  57. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
  58. package/src/resources/extensions/gsd/session-lock.ts +41 -6
  59. package/src/resources/extensions/gsd/templates/preferences.md +6 -0
  60. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +39 -1
  61. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
  62. package/src/resources/extensions/gsd/tests/cmux.test.ts +122 -0
  63. package/src/resources/extensions/gsd/tests/preferences.test.ts +23 -0
  64. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +45 -0
  65. package/src/resources/extensions/search-the-web/native-search.ts +50 -4
  66. package/src/resources/extensions/shared/format-utils.ts +5 -44
  67. package/src/resources/extensions/shared/layout-utils.ts +49 -0
  68. package/src/resources/extensions/shared/mod.ts +7 -4
  69. package/src/resources/extensions/shared/terminal.ts +5 -0
  70. package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
  71. package/src/resources/extensions/subagent/index.ts +236 -79
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Shared formatting and layout utilities for TUI dashboard components.
2
+ * Shared pure formatting utilities no @gsd/pi-tui dependency.
3
3
  *
4
- * Consolidates helpers that were previously duplicated across
5
- * auto-dashboard.ts, dashboard-overlay.ts, and visualizer-views.ts.
4
+ * ANSI-aware layout helpers (padRight, joinColumns, centerLine, fitColumns)
5
+ * live in layout-utils.ts to avoid pulling @gsd/pi-tui into modules that
6
+ * run outside jiti's alias resolution (e.g. HTML report generation via
7
+ * dynamic import in auto-loop).
6
8
  */
7
9
 
8
- import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
9
-
10
10
  // ─── Duration Formatting ──────────────────────────────────────────────────────
11
11
 
12
12
  /** Format a millisecond duration as a compact human-readable string. */
@@ -31,45 +31,6 @@ export function formatTokenCount(count: number): string {
31
31
  return `${(count / 1_000_000).toFixed(2)}M`;
32
32
  }
33
33
 
34
- // ─── Layout Helpers ───────────────────────────────────────────────────────────
35
-
36
- /** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
37
- export function padRight(content: string, width: number): string {
38
- const vis = visibleWidth(content);
39
- return content + " ".repeat(Math.max(0, width - vis));
40
- }
41
-
42
- /** Build a line with left-aligned and right-aligned content. */
43
- export function joinColumns(left: string, right: string, width: number): string {
44
- const leftW = visibleWidth(left);
45
- const rightW = visibleWidth(right);
46
- if (leftW + rightW + 2 > width) {
47
- return truncateToWidth(`${left} ${right}`, width);
48
- }
49
- return left + " ".repeat(width - leftW - rightW) + right;
50
- }
51
-
52
- /** Center content within `width` (ANSI-aware). */
53
- export function centerLine(content: string, width: number): string {
54
- const vis = visibleWidth(content);
55
- if (vis >= width) return truncateToWidth(content, width);
56
- const leftPad = Math.floor((width - vis) / 2);
57
- return " ".repeat(leftPad) + content;
58
- }
59
-
60
- /** Join as many parts as fit within `width`, separated by `separator`. */
61
- export function fitColumns(parts: string[], width: number, separator = " "): string {
62
- const filtered = parts.filter(Boolean);
63
- if (filtered.length === 0) return "";
64
- let result = filtered[0];
65
- for (let i = 1; i < filtered.length; i++) {
66
- const candidate = `${result}${separator}${filtered[i]}`;
67
- if (visibleWidth(candidate) > width) break;
68
- result = candidate;
69
- }
70
- return truncateToWidth(result, width);
71
- }
72
-
73
34
  // ─── Text Truncation ─────────────────────────────────────────────────────────
74
35
 
75
36
  /** Truncate a string to `maxLength` characters, replacing the last character with an ellipsis if needed. */
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ANSI-aware TUI layout utilities that depend on @gsd/pi-tui.
3
+ *
4
+ * Separated from format-utils.ts so that modules needing only pure
5
+ * formatting (e.g. HTML report generation) can import format-utils
6
+ * without pulling in the @gsd/pi-tui dependency — which fails when
7
+ * loaded outside jiti's alias resolution context.
8
+ */
9
+
10
+ import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
11
+
12
+ // ─── Layout Helpers ───────────────────────────────────────────────────────────
13
+
14
+ /** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
15
+ export function padRight(content: string, width: number): string {
16
+ const vis = visibleWidth(content);
17
+ return content + " ".repeat(Math.max(0, width - vis));
18
+ }
19
+
20
+ /** Build a line with left-aligned and right-aligned content. */
21
+ export function joinColumns(left: string, right: string, width: number): string {
22
+ const leftW = visibleWidth(left);
23
+ const rightW = visibleWidth(right);
24
+ if (leftW + rightW + 2 > width) {
25
+ return truncateToWidth(`${left} ${right}`, width);
26
+ }
27
+ return left + " ".repeat(width - leftW - rightW) + right;
28
+ }
29
+
30
+ /** Center content within `width` (ANSI-aware). */
31
+ export function centerLine(content: string, width: number): string {
32
+ const vis = visibleWidth(content);
33
+ if (vis >= width) return truncateToWidth(content, width);
34
+ const leftPad = Math.floor((width - vis) / 2);
35
+ return " ".repeat(leftPad) + content;
36
+ }
37
+
38
+ /** Join as many parts as fit within `width`, separated by `separator`. */
39
+ export function fitColumns(parts: string[], width: number, separator = " "): string {
40
+ const filtered = parts.filter(Boolean);
41
+ if (filtered.length === 0) return "";
42
+ let result = filtered[0];
43
+ for (let i = 1; i < filtered.length; i++) {
44
+ const candidate = `${result}${separator}${filtered[i]}`;
45
+ if (visibleWidth(candidate) > width) break;
46
+ result = candidate;
47
+ }
48
+ return truncateToWidth(result, width);
49
+ }
@@ -13,15 +13,18 @@ export {
13
13
  stripAnsi,
14
14
  formatTokenCount,
15
15
  formatDuration,
16
- padRight,
17
- joinColumns,
18
- centerLine,
19
- fitColumns,
20
16
  sparkline,
21
17
  normalizeStringArray,
22
18
  fileLink,
23
19
  } from "./format-utils.js";
24
20
 
21
+ export {
22
+ padRight,
23
+ joinColumns,
24
+ centerLine,
25
+ fitColumns,
26
+ } from "./layout-utils.js";
27
+
25
28
  export { shortcutDesc } from "./terminal.js";
26
29
  export { toPosixPath } from "./path-display.js";
27
30
  export { showInterviewRound } from "./interview-ui.js";
@@ -7,9 +7,14 @@
7
7
 
8
8
  const UNSUPPORTED_TERMS = ["apple_terminal", "warpterm"];
9
9
 
10
+ export function isCmuxTerminal(env: NodeJS.ProcessEnv = process.env): boolean {
11
+ return Boolean(env.CMUX_WORKSPACE_ID && env.CMUX_SURFACE_ID);
12
+ }
13
+
10
14
  export function supportsCtrlAltShortcuts(): boolean {
11
15
  const term = (process.env.TERM_PROGRAM || "").toLowerCase();
12
16
  const jetbrains = (process.env.TERMINAL_EMULATOR || "").toLowerCase().includes("jetbrains");
17
+ if (isCmuxTerminal()) return true;
13
18
  return !UNSUPPORTED_TERMS.some((t) => term.includes(t)) && !jetbrains;
14
19
  }
15
20
 
@@ -2,13 +2,15 @@ import { describe, it } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import {
4
4
  formatDuration,
5
+ sparkline,
6
+ stripAnsi,
7
+ } from "../format-utils.js";
8
+ import {
5
9
  padRight,
6
10
  joinColumns,
7
11
  centerLine,
8
12
  fitColumns,
9
- sparkline,
10
- stripAnsi,
11
- } from "../format-utils.js";
13
+ } from "../layout-utils.js";
12
14
 
13
15
  describe("formatDuration", () => {
14
16
  it("formats seconds", () => {
@@ -34,6 +34,8 @@ import {
34
34
  readIsolationMode,
35
35
  } from "./isolation.js";
36
36
  import { registerWorker, updateWorker } from "./worker-registry.js";
37
+ import { loadEffectiveGSDPreferences } from "../gsd/preferences.js";
38
+ import { CmuxClient, shellEscape } from "../cmux/index.js";
37
39
 
38
40
  const MAX_PARALLEL_TASKS = 8;
39
41
  const MAX_CONCURRENCY = 4;
@@ -257,6 +259,70 @@ function writePromptToTempFile(agentName: string, prompt: string): { dir: string
257
259
  return { dir: tmpDir, filePath };
258
260
  }
259
261
 
262
+ function buildSubagentProcessArgs(
263
+ agent: AgentConfig,
264
+ task: string,
265
+ tmpPromptPath: string | null,
266
+ ): string[] {
267
+ const args: string[] = ["--mode", "json", "-p", "--no-session"];
268
+ if (agent.model) args.push("--model", agent.model);
269
+ if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(","));
270
+ if (tmpPromptPath) args.push("--append-system-prompt", tmpPromptPath);
271
+ args.push(`Task: ${task}`);
272
+ return args;
273
+ }
274
+
275
+ function processSubagentEventLine(
276
+ line: string,
277
+ currentResult: SingleResult,
278
+ emitUpdate: () => void,
279
+ ): void {
280
+ if (!line.trim()) return;
281
+ let event: any;
282
+ try {
283
+ event = JSON.parse(line);
284
+ } catch {
285
+ return;
286
+ }
287
+
288
+ if (event.type === "message_end" && event.message) {
289
+ const msg = event.message as Message;
290
+ currentResult.messages.push(msg);
291
+
292
+ if (msg.role === "assistant") {
293
+ currentResult.usage.turns++;
294
+ const usage = msg.usage;
295
+ if (usage) {
296
+ currentResult.usage.input += usage.input || 0;
297
+ currentResult.usage.output += usage.output || 0;
298
+ currentResult.usage.cacheRead += usage.cacheRead || 0;
299
+ currentResult.usage.cacheWrite += usage.cacheWrite || 0;
300
+ currentResult.usage.cost += usage.cost?.total || 0;
301
+ currentResult.usage.contextTokens = usage.totalTokens || 0;
302
+ }
303
+ if (!currentResult.model && msg.model) currentResult.model = msg.model;
304
+ if (msg.stopReason) currentResult.stopReason = msg.stopReason;
305
+ if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
306
+ }
307
+ emitUpdate();
308
+ }
309
+
310
+ if (event.type === "tool_result_end" && event.message) {
311
+ currentResult.messages.push(event.message as Message);
312
+ emitUpdate();
313
+ }
314
+ }
315
+
316
+ async function waitForFile(filePath: string, signal: AbortSignal | undefined, timeoutMs = 30 * 60 * 1000): Promise<boolean> {
317
+ const started = Date.now();
318
+ while (Date.now() - started < timeoutMs) {
319
+ if (signal?.aborted) return false;
320
+ if (fs.existsSync(filePath)) return true;
321
+ await new Promise((resolve) => setTimeout(resolve, 150));
322
+ }
323
+ return false;
324
+ }
325
+
260
326
  type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
261
327
 
262
328
  async function runSingleAgent(
@@ -286,10 +352,6 @@ async function runSingleAgent(
286
352
  };
287
353
  }
288
354
 
289
- const args: string[] = ["--mode", "json", "-p", "--no-session"];
290
- if (agent.model) args.push("--model", agent.model);
291
- if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(","));
292
-
293
355
  let tmpPromptDir: string | null = null;
294
356
  let tmpPromptPath: string | null = null;
295
357
 
@@ -319,10 +381,8 @@ async function runSingleAgent(
319
381
  const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
320
382
  tmpPromptDir = tmp.dir;
321
383
  tmpPromptPath = tmp.filePath;
322
- args.push("--append-system-prompt", tmpPromptPath);
323
384
  }
324
-
325
- args.push(`Task: ${task}`);
385
+ const args = buildSubagentProcessArgs(agent, task, tmpPromptPath);
326
386
  let wasAborted = false;
327
387
 
328
388
  const exitCode = await new Promise<number>((resolve) => {
@@ -336,48 +396,11 @@ async function runSingleAgent(
336
396
  liveSubagentProcesses.add(proc);
337
397
  let buffer = "";
338
398
 
339
- const processLine = (line: string) => {
340
- if (!line.trim()) return;
341
- let event: any;
342
- try {
343
- event = JSON.parse(line);
344
- } catch {
345
- return;
346
- }
347
-
348
- if (event.type === "message_end" && event.message) {
349
- const msg = event.message as Message;
350
- currentResult.messages.push(msg);
351
-
352
- if (msg.role === "assistant") {
353
- currentResult.usage.turns++;
354
- const usage = msg.usage;
355
- if (usage) {
356
- currentResult.usage.input += usage.input || 0;
357
- currentResult.usage.output += usage.output || 0;
358
- currentResult.usage.cacheRead += usage.cacheRead || 0;
359
- currentResult.usage.cacheWrite += usage.cacheWrite || 0;
360
- currentResult.usage.cost += usage.cost?.total || 0;
361
- currentResult.usage.contextTokens = usage.totalTokens || 0;
362
- }
363
- if (!currentResult.model && msg.model) currentResult.model = msg.model;
364
- if (msg.stopReason) currentResult.stopReason = msg.stopReason;
365
- if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
366
- }
367
- emitUpdate();
368
- }
369
-
370
- if (event.type === "tool_result_end" && event.message) {
371
- currentResult.messages.push(event.message as Message);
372
- emitUpdate();
373
- }
374
- };
375
-
376
399
  proc.stdout.on("data", (data) => {
377
400
  buffer += data.toString();
378
401
  const lines = buffer.split("\n");
379
402
  buffer = lines.pop() || "";
380
- for (const line of lines) processLine(line);
403
+ for (const line of lines) processSubagentEventLine(line, currentResult, emitUpdate);
381
404
  });
382
405
 
383
406
  proc.stderr.on("data", (data) => {
@@ -386,7 +409,7 @@ async function runSingleAgent(
386
409
 
387
410
  proc.on("close", (code) => {
388
411
  liveSubagentProcesses.delete(proc);
389
- if (buffer.trim()) processLine(buffer);
412
+ if (buffer.trim()) processSubagentEventLine(buffer, currentResult, emitUpdate);
390
413
  resolve(code ?? 0);
391
414
  });
392
415
 
@@ -427,6 +450,120 @@ async function runSingleAgent(
427
450
  }
428
451
  }
429
452
 
453
+ async function runSingleAgentInCmuxSplit(
454
+ cmuxClient: CmuxClient,
455
+ direction: "right" | "down",
456
+ defaultCwd: string,
457
+ agents: AgentConfig[],
458
+ agentName: string,
459
+ task: string,
460
+ cwd: string | undefined,
461
+ step: number | undefined,
462
+ signal: AbortSignal | undefined,
463
+ onUpdate: OnUpdateCallback | undefined,
464
+ makeDetails: (results: SingleResult[]) => SubagentDetails,
465
+ ): Promise<SingleResult> {
466
+ const agent = agents.find((a) => a.name === agentName);
467
+ if (!agent) {
468
+ return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
469
+ }
470
+
471
+ let tmpPromptDir: string | null = null;
472
+ let tmpPromptPath: string | null = null;
473
+ let tmpOutputDir: string | null = null;
474
+
475
+ const currentResult: SingleResult = {
476
+ agent: agentName,
477
+ agentSource: agent.source,
478
+ task,
479
+ exitCode: 0,
480
+ messages: [],
481
+ stderr: "",
482
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
483
+ model: agent.model,
484
+ step,
485
+ };
486
+
487
+ const emitUpdate = () => {
488
+ if (onUpdate) {
489
+ onUpdate({
490
+ content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
491
+ details: makeDetails([currentResult]),
492
+ });
493
+ }
494
+ };
495
+
496
+ try {
497
+ if (agent.systemPrompt.trim()) {
498
+ const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
499
+ tmpPromptDir = tmp.dir;
500
+ tmpPromptPath = tmp.filePath;
501
+ }
502
+ tmpOutputDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-cmux-"));
503
+ const stdoutPath = path.join(tmpOutputDir, "stdout.jsonl");
504
+ const stderrPath = path.join(tmpOutputDir, "stderr.log");
505
+ const exitPath = path.join(tmpOutputDir, "exit.code");
506
+ const cmuxSurfaceId = await cmuxClient.createSplit(direction);
507
+ if (!cmuxSurfaceId) {
508
+ return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
509
+ }
510
+
511
+ const bundledPaths = (process.env.GSD_BUNDLED_EXTENSION_PATHS ?? "").split(path.delimiter).map((s) => s.trim()).filter(Boolean);
512
+ const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
513
+ const processArgs = [process.env.GSD_BIN_PATH!, ...extensionArgs, ...buildSubagentProcessArgs(agent, task, tmpPromptPath)];
514
+ const innerScript = [
515
+ `cd ${shellEscape(cwd ?? defaultCwd)}`,
516
+ "set -o pipefail",
517
+ `${shellEscape(process.execPath)} ${processArgs.map(shellEscape).join(" ")} 2> >(tee ${shellEscape(stderrPath)} >&2) | tee ${shellEscape(stdoutPath)}`,
518
+ "status=${PIPESTATUS[0]}",
519
+ `printf '%s' "$status" > ${shellEscape(exitPath)}`,
520
+ ].join("; ");
521
+
522
+ const sent = await cmuxClient.sendSurface(cmuxSurfaceId, `bash -lc ${shellEscape(innerScript)}`);
523
+ if (!sent) {
524
+ return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
525
+ }
526
+
527
+ const finished = await waitForFile(exitPath, signal);
528
+ if (!finished) {
529
+ currentResult.exitCode = 1;
530
+ currentResult.stderr = "cmux split execution timed out or was aborted";
531
+ return currentResult;
532
+ }
533
+
534
+ if (fs.existsSync(stdoutPath)) {
535
+ const stdout = fs.readFileSync(stdoutPath, "utf-8");
536
+ for (const line of stdout.split("\n")) {
537
+ processSubagentEventLine(line, currentResult, emitUpdate);
538
+ }
539
+ }
540
+ if (fs.existsSync(stderrPath)) {
541
+ currentResult.stderr = fs.readFileSync(stderrPath, "utf-8");
542
+ }
543
+ currentResult.exitCode = Number.parseInt(fs.readFileSync(exitPath, "utf-8").trim() || "1", 10) || 0;
544
+ return currentResult;
545
+ } finally {
546
+ if (tmpPromptPath)
547
+ try {
548
+ fs.unlinkSync(tmpPromptPath);
549
+ } catch {
550
+ /* ignore */
551
+ }
552
+ if (tmpPromptDir)
553
+ try {
554
+ fs.rmdirSync(tmpPromptDir);
555
+ } catch {
556
+ /* ignore */
557
+ }
558
+ if (tmpOutputDir)
559
+ try {
560
+ fs.rmSync(tmpOutputDir, { recursive: true, force: true });
561
+ } catch {
562
+ /* ignore */
563
+ }
564
+ }
565
+ }
566
+
430
567
  const TaskItem = Type.Object({
431
568
  agent: Type.String({ description: "Name of the agent to invoke" }),
432
569
  task: Type.String({ description: "Task to delegate to the agent" }),
@@ -511,6 +648,8 @@ export default function (pi: ExtensionAPI) {
511
648
  const discovery = discoverAgents(ctx.cwd, agentScope);
512
649
  const agents = discovery.agents;
513
650
  const confirmProjectAgents = params.confirmProjectAgents ?? false;
651
+ const cmuxClient = CmuxClient.fromPreferences(loadEffectiveGSDPreferences()?.preferences);
652
+ const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
514
653
 
515
654
  // Resolve isolation mode
516
655
  const isolationMode = readIsolationMode();
@@ -669,28 +808,26 @@ export default function (pi: ExtensionAPI) {
669
808
  const batchSize = params.tasks.length;
670
809
  const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
671
810
  const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
672
- let result = await runSingleAgent(
673
- ctx.cwd,
674
- agents,
675
- t.agent,
676
- t.task,
677
- t.cwd,
678
- undefined,
679
- signal,
680
- // Per-task update callback
681
- (partial) => {
682
- if (partial.details?.results[0]) {
683
- allResults[index] = partial.details.results[0];
684
- emitParallelUpdate();
685
- }
686
- },
687
- makeDetails("parallel"),
688
- );
689
-
690
- // Auto-retry failed tasks (likely API rate limit or transient error)
691
- const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
692
- if (isFailed && MAX_RETRIES > 0 && !signal?.aborted) {
693
- result = await runSingleAgent(
811
+ const runTask = () => cmuxSplitsEnabled
812
+ ? runSingleAgentInCmuxSplit(
813
+ cmuxClient,
814
+ index % 2 === 0 ? "right" : "down",
815
+ ctx.cwd,
816
+ agents,
817
+ t.agent,
818
+ t.task,
819
+ t.cwd,
820
+ undefined,
821
+ signal,
822
+ (partial) => {
823
+ if (partial.details?.results[0]) {
824
+ allResults[index] = partial.details.results[0];
825
+ emitParallelUpdate();
826
+ }
827
+ },
828
+ makeDetails("parallel"),
829
+ )
830
+ : runSingleAgent(
694
831
  ctx.cwd,
695
832
  agents,
696
833
  t.agent,
@@ -706,6 +843,12 @@ export default function (pi: ExtensionAPI) {
706
843
  },
707
844
  makeDetails("parallel"),
708
845
  );
846
+ let result = await runTask();
847
+
848
+ // Auto-retry failed tasks (likely API rate limit or transient error)
849
+ const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
850
+ if (isFailed && MAX_RETRIES > 0 && !signal?.aborted) {
851
+ result = await runTask();
709
852
  }
710
853
 
711
854
  updateWorker(workerId, result.exitCode === 0 ? "completed" : "failed");
@@ -744,17 +887,31 @@ export default function (pi: ExtensionAPI) {
744
887
  isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
745
888
  }
746
889
 
747
- const result = await runSingleAgent(
748
- ctx.cwd,
749
- agents,
750
- params.agent,
751
- params.task,
752
- isolation ? isolation.workDir : params.cwd,
753
- undefined,
754
- signal,
755
- onUpdate,
756
- makeDetails("single"),
757
- );
890
+ const result = cmuxSplitsEnabled
891
+ ? await runSingleAgentInCmuxSplit(
892
+ cmuxClient,
893
+ "right",
894
+ ctx.cwd,
895
+ agents,
896
+ params.agent,
897
+ params.task,
898
+ isolation ? isolation.workDir : params.cwd,
899
+ undefined,
900
+ signal,
901
+ onUpdate,
902
+ makeDetails("single"),
903
+ )
904
+ : await runSingleAgent(
905
+ ctx.cwd,
906
+ agents,
907
+ params.agent,
908
+ params.task,
909
+ isolation ? isolation.workDir : params.cwd,
910
+ undefined,
911
+ signal,
912
+ onUpdate,
913
+ makeDetails("single"),
914
+ );
758
915
 
759
916
  // Capture and merge delta if isolated
760
917
  if (isolation) {