stashes 0.1.52 → 0.1.54

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/cli.js CHANGED
@@ -548,7 +548,6 @@ import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync a
548
548
  import { join as join4 } from "path";
549
549
  import { tmpdir } from "os";
550
550
  var CLAUDE_BIN = "/opt/homebrew/bin/claude";
551
- var DEFAULT_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
552
551
  function getPlaywrightMcpConfigPath() {
553
552
  const configDir = join4(tmpdir(), "stashes-mcp");
554
553
  const configPath = join4(configDir, "playwright.json");
@@ -562,11 +561,29 @@ function getPlaywrightMcpConfigPath() {
562
561
  }
563
562
  return configPath;
564
563
  }
564
+ var OVERHEAD_TOOLS = [
565
+ "Agent",
566
+ "TodoWrite",
567
+ "TaskCreate",
568
+ "TaskUpdate",
569
+ "TaskList",
570
+ "TaskGet",
571
+ "Skill",
572
+ "ToolSearch",
573
+ "EnterPlanMode",
574
+ "ExitPlanMode",
575
+ "WebSearch",
576
+ "WebFetch",
577
+ "NotebookEdit",
578
+ "mcp__UseAI__*",
579
+ "mcp__stashes__*",
580
+ "mcp__plugin_drills*",
581
+ "mcp__plugin_coverit*"
582
+ ];
565
583
  var processes = new Map;
566
584
  function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
567
585
  const opts = typeof idOrOpts === "string" ? { id: idOrOpts, prompt, cwd, resumeSessionId, model } : idOrOpts;
568
- const bare = opts.bare ?? true;
569
- const tools = opts.tools ?? DEFAULT_TOOLS;
586
+ const restricted = opts.tools !== undefined;
570
587
  killAiProcess(opts.id);
571
588
  logger.info("claude", `spawning process: ${opts.id}`, {
572
589
  cwd: opts.cwd,
@@ -574,15 +591,14 @@ function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
574
591
  promptPreview: opts.prompt.substring(0, 100),
575
592
  resumeSessionId: opts.resumeSessionId,
576
593
  model: opts.model,
577
- bare,
578
- tools: bare ? tools.join(",") : "all"
594
+ restricted,
595
+ tools: restricted ? opts.tools.join(",") || "none" : "all"
579
596
  });
580
597
  const cmd = [CLAUDE_BIN, "-p", opts.prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
581
- if (bare) {
582
- cmd.push("--bare");
583
- if (tools.length > 0) {
584
- cmd.push("--tools", tools.join(","));
585
- }
598
+ if (restricted) {
599
+ cmd.push("--tools", opts.tools.length > 0 ? opts.tools.join(",") : '""');
600
+ cmd.push("--disallowedTools", OVERHEAD_TOOLS.join(","));
601
+ cmd.push("--strict-mcp-config");
586
602
  if (opts.mcpConfigPath) {
587
603
  cmd.push("--mcp-config", opts.mcpConfigPath);
588
604
  }
@@ -788,33 +804,49 @@ ${truncatedDiff}`;
788
804
  }
789
805
  }
790
806
  function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
807
+ const outputPath = join6(screenshotDir, `${stashId}.png`);
791
808
  return [
792
- "You are a screenshot assistant. Take screenshots of a running web app using Playwright MCP tools.",
809
+ "You are a screenshot assistant. Take a screenshot of a running web app.",
793
810
  "Be fast \u2014 you have a strict time limit.",
794
811
  "",
795
- `## The app is running at: http://localhost:${port}`,
812
+ `The app is running at: http://localhost:${port}`,
796
813
  "",
797
- "## Git diff of changes:",
814
+ "## Git diff of changes (tells you what was modified):",
798
815
  "```",
799
816
  diff,
800
817
  "```",
801
818
  "",
802
- "## Steps:",
803
- `1. Call mcp__playwright__browser_navigate with url "http://localhost:${port}" to open the app`,
804
- "2. From the diff, determine which page/route shows the changed components",
805
- "3. If the change is on a different route, navigate to it",
806
- "4. If the change is behind an interaction (tab click, modal open), perform it with mcp__playwright__browser_click",
807
- `5. Call mcp__playwright__browser_take_screenshot to capture the page`,
808
- "6. Respond with the JSON below",
819
+ "## Instructions",
820
+ "Use mcp__playwright__browser_run_code to navigate AND screenshot in a single call.",
821
+ "From the diff, determine the route/section that shows the changes.",
822
+ "",
823
+ "**Call this tool:**",
824
+ "```",
825
+ "mcp__playwright__browser_run_code",
826
+ "```",
809
827
  "",
810
- `## Screenshot save directory: ${screenshotDir}`,
811
- `Use filename "${stashId}.png" for the primary screenshot.`,
828
+ "**With this code parameter** (adjust the URL path and scroll if needed):",
829
+ "```javascript",
830
+ `async (page) => {`,
831
+ ` await page.setViewportSize({ width: 1280, height: 720 });`,
832
+ ` await page.goto('http://localhost:${port}', { waitUntil: 'networkidle', timeout: 15000 });`,
833
+ ` // If the changed component is further down, scroll to it:`,
834
+ ` // await page.evaluate(() => window.scrollTo(0, 500));`,
835
+ ` // await page.waitForTimeout(500);`,
836
+ ` await page.waitForTimeout(2000);`,
837
+ ` await page.screenshot({ path: '${outputPath.replace(/'/g, "\\'")}', type: 'png' });`,
838
+ ` return 'saved';`,
839
+ `}`,
840
+ "```",
812
841
  "",
813
- "## After taking screenshots, respond with ONLY this JSON:",
842
+ "If the diff shows changes to a component that requires scrolling or navigation,",
843
+ "adjust the code: change the URL path, add scrollTo, or click a tab first.",
844
+ "",
845
+ `After the tool call succeeds, respond with ONLY this JSON:`,
814
846
  "{",
815
847
  ' "screenshots": [',
816
848
  " {",
817
- ` "path": "${join6(screenshotDir, `${stashId}.png`)}",`,
849
+ ` "path": "${outputPath}",`,
818
850
  ' "label": "Short description of what is shown",',
819
851
  ' "route": "/the-url-path",',
820
852
  ' "isPrimary": true',
@@ -872,7 +904,6 @@ async function captureSmartScreenshots(opts) {
872
904
  prompt,
873
905
  cwd: worktreePath,
874
906
  model: modelFlag,
875
- bare: true,
876
907
  tools: [],
877
908
  mcpConfigPath: getPlaywrightMcpConfigPath()
878
909
  });
@@ -1041,8 +1072,7 @@ async function generate(opts) {
1041
1072
  const aiProcess = startAiProcess({
1042
1073
  id: stashId,
1043
1074
  prompt: stashPrompt,
1044
- cwd: worktree.path,
1045
- bare: false
1075
+ cwd: worktree.path
1046
1076
  });
1047
1077
  try {
1048
1078
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
@@ -1110,8 +1140,53 @@ async function generate(opts) {
1110
1140
  return;
1111
1141
  }
1112
1142
  const generatedStash = { ...stash, status: "screenshotting" };
1113
- completedStashes.push(generatedStash);
1114
1143
  persistence.saveStash(generatedStash);
1144
+ emit(onProgress, { type: "screenshotting", stashId });
1145
+ try {
1146
+ const port = await allocatePort();
1147
+ const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
1148
+ const screenshotGit = simpleGit3(screenshotWorktree.path);
1149
+ await screenshotGit.checkout(["--detach", stash.branch]);
1150
+ const devServer = spawn3({
1151
+ cmd: ["npm", "run", "dev"],
1152
+ cwd: screenshotWorktree.path,
1153
+ stdin: "ignore",
1154
+ stdout: "pipe",
1155
+ stderr: "pipe",
1156
+ env: { ...process.env, PORT: String(port), BROWSER: "none" }
1157
+ });
1158
+ try {
1159
+ await waitForPort(port, 60000);
1160
+ await new Promise((r) => setTimeout(r, 2000));
1161
+ const mainGit = simpleGit3(projectPath);
1162
+ const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1163
+ const { primary, screenshots } = await captureSmartScreenshots({
1164
+ projectPath,
1165
+ stashId,
1166
+ stashBranch: stash.branch,
1167
+ parentBranch,
1168
+ worktreePath: screenshotWorktree.path,
1169
+ port,
1170
+ model: opts.screenshotModel,
1171
+ timeout: opts.screenshotTimeout
1172
+ });
1173
+ const readyStash = { ...generatedStash, status: "ready", screenshotUrl: primary, screenshots };
1174
+ completedStashes.push(readyStash);
1175
+ persistence.saveStash(readyStash);
1176
+ emit(onProgress, { type: "ready", stashId, screenshotPath: primary });
1177
+ } finally {
1178
+ devServer.kill();
1179
+ }
1180
+ await worktreeManager.removeGeneration(`screenshot-${stashId}`);
1181
+ } catch (err) {
1182
+ logger.error("generation", `screenshot failed for ${stashId}`, {
1183
+ error: err instanceof Error ? err.message : String(err)
1184
+ });
1185
+ const readyStash = { ...generatedStash, status: "ready" };
1186
+ completedStashes.push(readyStash);
1187
+ persistence.saveStash(readyStash);
1188
+ emit(onProgress, { type: "ready", stashId, screenshotPath: "" });
1189
+ }
1115
1190
  } catch (error) {
1116
1191
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
1117
1192
  persistence.saveStash({ ...stash, status: "error", error: errorMsg });
@@ -1121,53 +1196,6 @@ async function generate(opts) {
1121
1196
  }
1122
1197
  });
1123
1198
  await Promise.all(stashPromises);
1124
- for (const stash of completedStashes) {
1125
- emit(onProgress, { type: "screenshotting", stashId: stash.id });
1126
- try {
1127
- const port = await allocatePort();
1128
- const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
1129
- const screenshotGit = simpleGit3(worktree.path);
1130
- await screenshotGit.checkout(["--detach", stash.branch]);
1131
- const devServer = spawn3({
1132
- cmd: ["npm", "run", "dev"],
1133
- cwd: worktree.path,
1134
- stdin: "ignore",
1135
- stdout: "pipe",
1136
- stderr: "pipe",
1137
- env: { ...process.env, PORT: String(port), BROWSER: "none" }
1138
- });
1139
- try {
1140
- await waitForPort(port, 60000);
1141
- await new Promise((r) => setTimeout(r, 2000));
1142
- const mainGit = simpleGit3(projectPath);
1143
- const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1144
- const { primary, screenshots } = await captureSmartScreenshots({
1145
- projectPath,
1146
- stashId: stash.id,
1147
- stashBranch: stash.branch,
1148
- parentBranch,
1149
- worktreePath: worktree.path,
1150
- port,
1151
- model: opts.screenshotModel,
1152
- timeout: opts.screenshotTimeout
1153
- });
1154
- const updatedStash = { ...stash, status: "ready", screenshotUrl: primary, screenshots };
1155
- persistence.saveStash(updatedStash);
1156
- const idx = completedStashes.indexOf(stash);
1157
- completedStashes[idx] = updatedStash;
1158
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
1159
- } finally {
1160
- devServer.kill();
1161
- }
1162
- await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
1163
- } catch (err) {
1164
- logger.error("generation", `screenshot failed for ${stash.id}`, {
1165
- error: err instanceof Error ? err.message : String(err)
1166
- });
1167
- persistence.saveStash({ ...stash, status: "ready" });
1168
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: "" });
1169
- }
1170
- }
1171
1199
  return completedStashes;
1172
1200
  }
1173
1201
  // ../core/dist/vary.js
@@ -1242,8 +1270,7 @@ async function vary(opts) {
1242
1270
  const aiProcess = startAiProcess({
1243
1271
  id: stashId,
1244
1272
  prompt: varyPrompt,
1245
- cwd: worktree.path,
1246
- bare: false
1273
+ cwd: worktree.path
1247
1274
  });
1248
1275
  try {
1249
1276
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
@@ -1914,7 +1941,6 @@ class StashService {
1914
1941
  prompt,
1915
1942
  cwd: this.projectPath,
1916
1943
  model: "claude-haiku-4-5-20251001",
1917
- bare: true,
1918
1944
  tools: ["Read", "Grep", "Glob", "Bash"]
1919
1945
  });
1920
1946
  let resolvedPath = "";
@@ -2030,8 +2056,7 @@ ${sourceCode.substring(0, 3000)}
2030
2056
  id: "chat",
2031
2057
  prompt: chatPrompt,
2032
2058
  cwd: this.projectPath,
2033
- resumeSessionId: existingSessionId,
2034
- bare: false
2059
+ resumeSessionId: existingSessionId
2035
2060
  });
2036
2061
  let thinkingBuf = "";
2037
2062
  let textBuf = "";
@@ -2130,11 +2155,13 @@ ${sourceCode.substring(0, 3000)}
2130
2155
  toolResult = parsed.result ?? chunk.content;
2131
2156
  isError = !!parsed.is_error;
2132
2157
  } catch {}
2158
+ const endToolName = chunk.toolName ?? "unknown";
2133
2159
  save({
2134
2160
  id: crypto.randomUUID(),
2135
2161
  role: "assistant",
2136
2162
  content: chunk.content,
2137
2163
  type: "tool_end",
2164
+ toolName: endToolName,
2138
2165
  toolStatus: isError ? "error" : "completed",
2139
2166
  toolResult: toolResult.substring(0, 300),
2140
2167
  stashActivity,
@@ -2145,6 +2172,7 @@ ${sourceCode.substring(0, 3000)}
2145
2172
  content: chunk.content,
2146
2173
  streamType: "tool_end",
2147
2174
  source: "chat",
2175
+ toolName: endToolName,
2148
2176
  toolStatus: isError ? "error" : "completed",
2149
2177
  toolResult: toolResult.substring(0, 300)
2150
2178
  });
package/dist/mcp.js CHANGED
@@ -533,7 +533,6 @@ import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync a
533
533
  import { join as join4 } from "path";
534
534
  import { tmpdir } from "os";
535
535
  var CLAUDE_BIN = "/opt/homebrew/bin/claude";
536
- var DEFAULT_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
537
536
  function getPlaywrightMcpConfigPath() {
538
537
  const configDir = join4(tmpdir(), "stashes-mcp");
539
538
  const configPath = join4(configDir, "playwright.json");
@@ -547,11 +546,29 @@ function getPlaywrightMcpConfigPath() {
547
546
  }
548
547
  return configPath;
549
548
  }
549
+ var OVERHEAD_TOOLS = [
550
+ "Agent",
551
+ "TodoWrite",
552
+ "TaskCreate",
553
+ "TaskUpdate",
554
+ "TaskList",
555
+ "TaskGet",
556
+ "Skill",
557
+ "ToolSearch",
558
+ "EnterPlanMode",
559
+ "ExitPlanMode",
560
+ "WebSearch",
561
+ "WebFetch",
562
+ "NotebookEdit",
563
+ "mcp__UseAI__*",
564
+ "mcp__stashes__*",
565
+ "mcp__plugin_drills*",
566
+ "mcp__plugin_coverit*"
567
+ ];
550
568
  var processes = new Map;
551
569
  function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
552
570
  const opts = typeof idOrOpts === "string" ? { id: idOrOpts, prompt, cwd, resumeSessionId, model } : idOrOpts;
553
- const bare = opts.bare ?? true;
554
- const tools = opts.tools ?? DEFAULT_TOOLS;
571
+ const restricted = opts.tools !== undefined;
555
572
  killAiProcess(opts.id);
556
573
  logger.info("claude", `spawning process: ${opts.id}`, {
557
574
  cwd: opts.cwd,
@@ -559,15 +576,14 @@ function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
559
576
  promptPreview: opts.prompt.substring(0, 100),
560
577
  resumeSessionId: opts.resumeSessionId,
561
578
  model: opts.model,
562
- bare,
563
- tools: bare ? tools.join(",") : "all"
579
+ restricted,
580
+ tools: restricted ? opts.tools.join(",") || "none" : "all"
564
581
  });
565
582
  const cmd = [CLAUDE_BIN, "-p", opts.prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
566
- if (bare) {
567
- cmd.push("--bare");
568
- if (tools.length > 0) {
569
- cmd.push("--tools", tools.join(","));
570
- }
583
+ if (restricted) {
584
+ cmd.push("--tools", opts.tools.length > 0 ? opts.tools.join(",") : '""');
585
+ cmd.push("--disallowedTools", OVERHEAD_TOOLS.join(","));
586
+ cmd.push("--strict-mcp-config");
571
587
  if (opts.mcpConfigPath) {
572
588
  cmd.push("--mcp-config", opts.mcpConfigPath);
573
589
  }
@@ -773,33 +789,49 @@ ${truncatedDiff}`;
773
789
  }
774
790
  }
775
791
  function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
792
+ const outputPath = join6(screenshotDir, `${stashId}.png`);
776
793
  return [
777
- "You are a screenshot assistant. Take screenshots of a running web app using Playwright MCP tools.",
794
+ "You are a screenshot assistant. Take a screenshot of a running web app.",
778
795
  "Be fast \u2014 you have a strict time limit.",
779
796
  "",
780
- `## The app is running at: http://localhost:${port}`,
797
+ `The app is running at: http://localhost:${port}`,
781
798
  "",
782
- "## Git diff of changes:",
799
+ "## Git diff of changes (tells you what was modified):",
783
800
  "```",
784
801
  diff,
785
802
  "```",
786
803
  "",
787
- "## Steps:",
788
- `1. Call mcp__playwright__browser_navigate with url "http://localhost:${port}" to open the app`,
789
- "2. From the diff, determine which page/route shows the changed components",
790
- "3. If the change is on a different route, navigate to it",
791
- "4. If the change is behind an interaction (tab click, modal open), perform it with mcp__playwright__browser_click",
792
- `5. Call mcp__playwright__browser_take_screenshot to capture the page`,
793
- "6. Respond with the JSON below",
804
+ "## Instructions",
805
+ "Use mcp__playwright__browser_run_code to navigate AND screenshot in a single call.",
806
+ "From the diff, determine the route/section that shows the changes.",
807
+ "",
808
+ "**Call this tool:**",
809
+ "```",
810
+ "mcp__playwright__browser_run_code",
811
+ "```",
794
812
  "",
795
- `## Screenshot save directory: ${screenshotDir}`,
796
- `Use filename "${stashId}.png" for the primary screenshot.`,
813
+ "**With this code parameter** (adjust the URL path and scroll if needed):",
814
+ "```javascript",
815
+ `async (page) => {`,
816
+ ` await page.setViewportSize({ width: 1280, height: 720 });`,
817
+ ` await page.goto('http://localhost:${port}', { waitUntil: 'networkidle', timeout: 15000 });`,
818
+ ` // If the changed component is further down, scroll to it:`,
819
+ ` // await page.evaluate(() => window.scrollTo(0, 500));`,
820
+ ` // await page.waitForTimeout(500);`,
821
+ ` await page.waitForTimeout(2000);`,
822
+ ` await page.screenshot({ path: '${outputPath.replace(/'/g, "\\'")}', type: 'png' });`,
823
+ ` return 'saved';`,
824
+ `}`,
825
+ "```",
797
826
  "",
798
- "## After taking screenshots, respond with ONLY this JSON:",
827
+ "If the diff shows changes to a component that requires scrolling or navigation,",
828
+ "adjust the code: change the URL path, add scrollTo, or click a tab first.",
829
+ "",
830
+ `After the tool call succeeds, respond with ONLY this JSON:`,
799
831
  "{",
800
832
  ' "screenshots": [',
801
833
  " {",
802
- ` "path": "${join6(screenshotDir, `${stashId}.png`)}",`,
834
+ ` "path": "${outputPath}",`,
803
835
  ' "label": "Short description of what is shown",',
804
836
  ' "route": "/the-url-path",',
805
837
  ' "isPrimary": true',
@@ -857,7 +889,6 @@ async function captureSmartScreenshots(opts) {
857
889
  prompt,
858
890
  cwd: worktreePath,
859
891
  model: modelFlag,
860
- bare: true,
861
892
  tools: [],
862
893
  mcpConfigPath: getPlaywrightMcpConfigPath()
863
894
  });
@@ -1026,8 +1057,7 @@ async function generate(opts) {
1026
1057
  const aiProcess = startAiProcess({
1027
1058
  id: stashId,
1028
1059
  prompt: stashPrompt,
1029
- cwd: worktree.path,
1030
- bare: false
1060
+ cwd: worktree.path
1031
1061
  });
1032
1062
  try {
1033
1063
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
@@ -1095,8 +1125,53 @@ async function generate(opts) {
1095
1125
  return;
1096
1126
  }
1097
1127
  const generatedStash = { ...stash, status: "screenshotting" };
1098
- completedStashes.push(generatedStash);
1099
1128
  persistence.saveStash(generatedStash);
1129
+ emit(onProgress, { type: "screenshotting", stashId });
1130
+ try {
1131
+ const port = await allocatePort();
1132
+ const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
1133
+ const screenshotGit = simpleGit3(screenshotWorktree.path);
1134
+ await screenshotGit.checkout(["--detach", stash.branch]);
1135
+ const devServer = spawn3({
1136
+ cmd: ["npm", "run", "dev"],
1137
+ cwd: screenshotWorktree.path,
1138
+ stdin: "ignore",
1139
+ stdout: "pipe",
1140
+ stderr: "pipe",
1141
+ env: { ...process.env, PORT: String(port), BROWSER: "none" }
1142
+ });
1143
+ try {
1144
+ await waitForPort(port, 60000);
1145
+ await new Promise((r) => setTimeout(r, 2000));
1146
+ const mainGit = simpleGit3(projectPath);
1147
+ const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1148
+ const { primary, screenshots } = await captureSmartScreenshots({
1149
+ projectPath,
1150
+ stashId,
1151
+ stashBranch: stash.branch,
1152
+ parentBranch,
1153
+ worktreePath: screenshotWorktree.path,
1154
+ port,
1155
+ model: opts.screenshotModel,
1156
+ timeout: opts.screenshotTimeout
1157
+ });
1158
+ const readyStash = { ...generatedStash, status: "ready", screenshotUrl: primary, screenshots };
1159
+ completedStashes.push(readyStash);
1160
+ persistence.saveStash(readyStash);
1161
+ emit(onProgress, { type: "ready", stashId, screenshotPath: primary });
1162
+ } finally {
1163
+ devServer.kill();
1164
+ }
1165
+ await worktreeManager.removeGeneration(`screenshot-${stashId}`);
1166
+ } catch (err) {
1167
+ logger.error("generation", `screenshot failed for ${stashId}`, {
1168
+ error: err instanceof Error ? err.message : String(err)
1169
+ });
1170
+ const readyStash = { ...generatedStash, status: "ready" };
1171
+ completedStashes.push(readyStash);
1172
+ persistence.saveStash(readyStash);
1173
+ emit(onProgress, { type: "ready", stashId, screenshotPath: "" });
1174
+ }
1100
1175
  } catch (error) {
1101
1176
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
1102
1177
  persistence.saveStash({ ...stash, status: "error", error: errorMsg });
@@ -1106,53 +1181,6 @@ async function generate(opts) {
1106
1181
  }
1107
1182
  });
1108
1183
  await Promise.all(stashPromises);
1109
- for (const stash of completedStashes) {
1110
- emit(onProgress, { type: "screenshotting", stashId: stash.id });
1111
- try {
1112
- const port = await allocatePort();
1113
- const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
1114
- const screenshotGit = simpleGit3(worktree.path);
1115
- await screenshotGit.checkout(["--detach", stash.branch]);
1116
- const devServer = spawn3({
1117
- cmd: ["npm", "run", "dev"],
1118
- cwd: worktree.path,
1119
- stdin: "ignore",
1120
- stdout: "pipe",
1121
- stderr: "pipe",
1122
- env: { ...process.env, PORT: String(port), BROWSER: "none" }
1123
- });
1124
- try {
1125
- await waitForPort(port, 60000);
1126
- await new Promise((r) => setTimeout(r, 2000));
1127
- const mainGit = simpleGit3(projectPath);
1128
- const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1129
- const { primary, screenshots } = await captureSmartScreenshots({
1130
- projectPath,
1131
- stashId: stash.id,
1132
- stashBranch: stash.branch,
1133
- parentBranch,
1134
- worktreePath: worktree.path,
1135
- port,
1136
- model: opts.screenshotModel,
1137
- timeout: opts.screenshotTimeout
1138
- });
1139
- const updatedStash = { ...stash, status: "ready", screenshotUrl: primary, screenshots };
1140
- persistence.saveStash(updatedStash);
1141
- const idx = completedStashes.indexOf(stash);
1142
- completedStashes[idx] = updatedStash;
1143
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
1144
- } finally {
1145
- devServer.kill();
1146
- }
1147
- await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
1148
- } catch (err) {
1149
- logger.error("generation", `screenshot failed for ${stash.id}`, {
1150
- error: err instanceof Error ? err.message : String(err)
1151
- });
1152
- persistence.saveStash({ ...stash, status: "ready" });
1153
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: "" });
1154
- }
1155
- }
1156
1184
  return completedStashes;
1157
1185
  }
1158
1186
  // ../core/dist/vary.js
@@ -1227,8 +1255,7 @@ async function vary(opts) {
1227
1255
  const aiProcess = startAiProcess({
1228
1256
  id: stashId,
1229
1257
  prompt: varyPrompt,
1230
- cwd: worktree.path,
1231
- bare: false
1258
+ cwd: worktree.path
1232
1259
  });
1233
1260
  try {
1234
1261
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
@@ -2138,7 +2165,6 @@ class StashService {
2138
2165
  prompt,
2139
2166
  cwd: this.projectPath,
2140
2167
  model: "claude-haiku-4-5-20251001",
2141
- bare: true,
2142
2168
  tools: ["Read", "Grep", "Glob", "Bash"]
2143
2169
  });
2144
2170
  let resolvedPath = "";
@@ -2254,8 +2280,7 @@ ${sourceCode.substring(0, 3000)}
2254
2280
  id: "chat",
2255
2281
  prompt: chatPrompt,
2256
2282
  cwd: this.projectPath,
2257
- resumeSessionId: existingSessionId,
2258
- bare: false
2283
+ resumeSessionId: existingSessionId
2259
2284
  });
2260
2285
  let thinkingBuf = "";
2261
2286
  let textBuf = "";
@@ -2354,11 +2379,13 @@ ${sourceCode.substring(0, 3000)}
2354
2379
  toolResult = parsed.result ?? chunk.content;
2355
2380
  isError = !!parsed.is_error;
2356
2381
  } catch {}
2382
+ const endToolName = chunk.toolName ?? "unknown";
2357
2383
  save({
2358
2384
  id: crypto.randomUUID(),
2359
2385
  role: "assistant",
2360
2386
  content: chunk.content,
2361
2387
  type: "tool_end",
2388
+ toolName: endToolName,
2362
2389
  toolStatus: isError ? "error" : "completed",
2363
2390
  toolResult: toolResult.substring(0, 300),
2364
2391
  stashActivity,
@@ -2369,6 +2396,7 @@ ${sourceCode.substring(0, 3000)}
2369
2396
  content: chunk.content,
2370
2397
  streamType: "tool_end",
2371
2398
  source: "chat",
2399
+ toolName: endToolName,
2372
2400
  toolStatus: isError ? "error" : "completed",
2373
2401
  toolResult: toolResult.substring(0, 300)
2374
2402
  });