stashes 0.2.4 → 0.2.6
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 +177 -7
- package/dist/mcp.js +177 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -139,7 +139,7 @@ async function prepareWorktreeDeps(worktreePath, projectPath) {
|
|
|
139
139
|
pm = "yarn";
|
|
140
140
|
if (pm === "npm")
|
|
141
141
|
return;
|
|
142
|
-
const cmd = pm === "pnpm" ? ["pnpm", "install", "--
|
|
142
|
+
const cmd = pm === "pnpm" ? ["pnpm", "install", "--force", "--prefer-offline"] : pm === "bun" ? ["bun", "install", "--frozen-lockfile"] : ["yarn", "install", "--frozen-lockfile"];
|
|
143
143
|
logger.info("worktree", `installing deps (${pm})`, { worktreePath: worktreePath.split("/").slice(-2).join("/") });
|
|
144
144
|
const proc = spawn({
|
|
145
145
|
cmd,
|
|
@@ -1090,8 +1090,11 @@ async function generate(opts) {
|
|
|
1090
1090
|
id: stashId,
|
|
1091
1091
|
prompt: stashPrompt,
|
|
1092
1092
|
cwd: worktree.path,
|
|
1093
|
-
persistSession:
|
|
1093
|
+
persistSession: true
|
|
1094
1094
|
})) {
|
|
1095
|
+
if (chunk.type === "session_id" && chunk.sessionId) {
|
|
1096
|
+
persistence.saveStash({ ...stash, sessionId: chunk.sessionId });
|
|
1097
|
+
}
|
|
1095
1098
|
emit(onProgress, {
|
|
1096
1099
|
type: "ai_stream",
|
|
1097
1100
|
stashId,
|
|
@@ -1104,12 +1107,36 @@ async function generate(opts) {
|
|
|
1104
1107
|
const filePath = chunk.toolInput?.file_path ?? chunk.toolInput?.path ?? chunk.toolInput?.command ?? undefined;
|
|
1105
1108
|
const lines = chunk.toolInput?.content ? chunk.toolInput.content.split(`
|
|
1106
1109
|
`).length : undefined;
|
|
1110
|
+
let diff;
|
|
1111
|
+
if (chunk.toolName === "Write") {
|
|
1112
|
+
const writeContent = chunk.toolInput?.content;
|
|
1113
|
+
if (writeContent) {
|
|
1114
|
+
diff = writeContent.substring(0, 5000);
|
|
1115
|
+
}
|
|
1116
|
+
} else if (chunk.toolName === "Edit") {
|
|
1117
|
+
const oldStr = chunk.toolInput?.old_string;
|
|
1118
|
+
const newStr = chunk.toolInput?.new_string;
|
|
1119
|
+
if (oldStr && newStr) {
|
|
1120
|
+
const oldLines = oldStr.split(`
|
|
1121
|
+
`).map((l) => `-${l}`).join(`
|
|
1122
|
+
`);
|
|
1123
|
+
const newLines = newStr.split(`
|
|
1124
|
+
`).map((l) => `+${l}`).join(`
|
|
1125
|
+
`);
|
|
1126
|
+
diff = `--- old
|
|
1127
|
+
+++ new
|
|
1128
|
+
@@ @@
|
|
1129
|
+
${oldLines}
|
|
1130
|
+
${newLines}`.substring(0, 5000);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1107
1133
|
emit(onProgress, {
|
|
1108
1134
|
type: "activity",
|
|
1109
1135
|
stashId,
|
|
1110
1136
|
action: chunk.toolName,
|
|
1111
1137
|
file: filePath,
|
|
1112
1138
|
lines,
|
|
1139
|
+
diff,
|
|
1113
1140
|
timestamp: Date.now()
|
|
1114
1141
|
});
|
|
1115
1142
|
}
|
|
@@ -1164,7 +1191,7 @@ async function generate(opts) {
|
|
|
1164
1191
|
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1165
1192
|
await prepareWorktreeDeps(screenshotWorktree.path, projectPath);
|
|
1166
1193
|
const devServer = spawn3({
|
|
1167
|
-
cmd: ["npm", "run", "dev", "--", "--port", String(port)],
|
|
1194
|
+
cmd: ["npm", "run", "dev", "--", "--", "--port", String(port)],
|
|
1168
1195
|
cwd: screenshotWorktree.path,
|
|
1169
1196
|
stdin: "ignore",
|
|
1170
1197
|
stdout: "pipe",
|
|
@@ -1292,14 +1319,73 @@ ${context}` : `The user wants to vary the current UI. Apply this change: ${promp
|
|
|
1292
1319
|
id: stashId,
|
|
1293
1320
|
prompt: varyPrompt,
|
|
1294
1321
|
cwd: worktree.path,
|
|
1295
|
-
persistSession:
|
|
1322
|
+
persistSession: true
|
|
1296
1323
|
})) {
|
|
1324
|
+
if (chunk.type === "session_id" && chunk.sessionId) {
|
|
1325
|
+
persistence.saveStash({ ...stash, sessionId: chunk.sessionId });
|
|
1326
|
+
}
|
|
1297
1327
|
emit2(onProgress, {
|
|
1298
1328
|
type: "ai_stream",
|
|
1299
1329
|
stashId,
|
|
1300
1330
|
content: chunk.content,
|
|
1301
1331
|
streamType: chunk.type
|
|
1302
1332
|
});
|
|
1333
|
+
if (chunk.type === "tool_use" && chunk.toolName) {
|
|
1334
|
+
const knownTools = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
1335
|
+
if (knownTools.includes(chunk.toolName)) {
|
|
1336
|
+
const filePath = chunk.toolInput?.file_path ?? chunk.toolInput?.path ?? chunk.toolInput?.command ?? undefined;
|
|
1337
|
+
const lines = chunk.toolInput?.content ? chunk.toolInput.content.split(`
|
|
1338
|
+
`).length : undefined;
|
|
1339
|
+
let diff;
|
|
1340
|
+
if (chunk.toolName === "Write") {
|
|
1341
|
+
const writeContent = chunk.toolInput?.content;
|
|
1342
|
+
if (writeContent) {
|
|
1343
|
+
diff = writeContent.substring(0, 5000);
|
|
1344
|
+
}
|
|
1345
|
+
} else if (chunk.toolName === "Edit") {
|
|
1346
|
+
const oldStr = chunk.toolInput?.old_string;
|
|
1347
|
+
const newStr = chunk.toolInput?.new_string;
|
|
1348
|
+
if (oldStr && newStr) {
|
|
1349
|
+
const oldLines = oldStr.split(`
|
|
1350
|
+
`).map((l) => `-${l}`).join(`
|
|
1351
|
+
`);
|
|
1352
|
+
const newLines = newStr.split(`
|
|
1353
|
+
`).map((l) => `+${l}`).join(`
|
|
1354
|
+
`);
|
|
1355
|
+
diff = `--- old
|
|
1356
|
+
+++ new
|
|
1357
|
+
@@ @@
|
|
1358
|
+
${oldLines}
|
|
1359
|
+
${newLines}`.substring(0, 5000);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
emit2(onProgress, {
|
|
1363
|
+
type: "activity",
|
|
1364
|
+
stashId,
|
|
1365
|
+
action: chunk.toolName,
|
|
1366
|
+
file: filePath,
|
|
1367
|
+
lines,
|
|
1368
|
+
diff,
|
|
1369
|
+
timestamp: Date.now()
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
} else if (chunk.type === "thinking") {
|
|
1373
|
+
emit2(onProgress, {
|
|
1374
|
+
type: "activity",
|
|
1375
|
+
stashId,
|
|
1376
|
+
action: "thinking",
|
|
1377
|
+
content: chunk.content.substring(0, 200),
|
|
1378
|
+
timestamp: Date.now()
|
|
1379
|
+
});
|
|
1380
|
+
} else if (chunk.type === "text") {
|
|
1381
|
+
emit2(onProgress, {
|
|
1382
|
+
type: "activity",
|
|
1383
|
+
stashId,
|
|
1384
|
+
action: "text",
|
|
1385
|
+
content: chunk.content.substring(0, 200),
|
|
1386
|
+
timestamp: Date.now()
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1303
1389
|
}
|
|
1304
1390
|
const wtGit = simpleGit4(worktree.path);
|
|
1305
1391
|
let hasChanges = false;
|
|
@@ -1336,7 +1422,7 @@ ${context}` : `The user wants to vary the current UI. Apply this change: ${promp
|
|
|
1336
1422
|
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1337
1423
|
await prepareWorktreeDeps(screenshotWorktree.path, projectPath);
|
|
1338
1424
|
const devServer = spawn4({
|
|
1339
|
-
cmd: ["npm", "run", "dev", "--", "--port", String(port)],
|
|
1425
|
+
cmd: ["npm", "run", "dev", "--", "--", "--port", String(port)],
|
|
1340
1426
|
cwd: screenshotWorktree.path,
|
|
1341
1427
|
stdin: "ignore",
|
|
1342
1428
|
stdout: "pipe",
|
|
@@ -1750,7 +1836,7 @@ class PreviewPool {
|
|
|
1750
1836
|
const worktreePath = await this.worktreeManager.createPreviewForPool(stashId);
|
|
1751
1837
|
await prepareWorktreeDeps(worktreePath, this.worktreeManager.getProjectPath());
|
|
1752
1838
|
const process2 = Bun.spawn({
|
|
1753
|
-
cmd: ["npm", "run", "dev", "--", "--port", String(devPort)],
|
|
1839
|
+
cmd: ["npm", "run", "dev", "--", "--", "--port", String(devPort)],
|
|
1754
1840
|
cwd: worktreePath,
|
|
1755
1841
|
stdin: "ignore",
|
|
1756
1842
|
stdout: "pipe",
|
|
@@ -2401,7 +2487,6 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2401
2487
|
for (const s of allStashes) {
|
|
2402
2488
|
if (this.activityStore.has(s.id)) {
|
|
2403
2489
|
stashActivity[s.id] = this.activityStore.getSnapshot(s.id);
|
|
2404
|
-
this.activityStore.clear(s.id);
|
|
2405
2490
|
}
|
|
2406
2491
|
}
|
|
2407
2492
|
if (Object.keys(stashActivity).length === 0)
|
|
@@ -2559,6 +2644,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2559
2644
|
file: event.file,
|
|
2560
2645
|
lines: event.lines,
|
|
2561
2646
|
content: event.content,
|
|
2647
|
+
diff: event.diff,
|
|
2562
2648
|
timestamp: event.timestamp
|
|
2563
2649
|
};
|
|
2564
2650
|
this.activityStore.append(activityEvent);
|
|
@@ -2630,6 +2716,61 @@ ${refDescriptions.join(`
|
|
|
2630
2716
|
await this.previewPool.stop(stashId);
|
|
2631
2717
|
await remove(this.projectPath, stashId);
|
|
2632
2718
|
}
|
|
2719
|
+
async continueStash(projectId, stashId, message) {
|
|
2720
|
+
const stash = this.persistence.getStash(projectId, stashId);
|
|
2721
|
+
if (!stash?.sessionId) {
|
|
2722
|
+
this.broadcast({ type: "stash:error", stashId, error: "No session to continue" });
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
const worktree = await this.worktreeManager.createPreviewForPool(stashId);
|
|
2726
|
+
try {
|
|
2727
|
+
for await (const chunk of runAgentQuery({
|
|
2728
|
+
id: `continue-${stashId}`,
|
|
2729
|
+
prompt: message,
|
|
2730
|
+
cwd: worktree,
|
|
2731
|
+
resumeSessionId: stash.sessionId,
|
|
2732
|
+
persistSession: true
|
|
2733
|
+
})) {
|
|
2734
|
+
if (chunk.type === "session_id" && chunk.sessionId) {
|
|
2735
|
+
this.persistence.saveStash({ ...stash, sessionId: chunk.sessionId });
|
|
2736
|
+
}
|
|
2737
|
+
if (chunk.type === "tool_use" && chunk.toolName) {
|
|
2738
|
+
const knownTools = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
2739
|
+
if (knownTools.includes(chunk.toolName)) {
|
|
2740
|
+
const filePath = chunk.toolInput?.file_path ?? chunk.toolInput?.path ?? chunk.toolInput?.command ?? undefined;
|
|
2741
|
+
const activityEvent = {
|
|
2742
|
+
stashId,
|
|
2743
|
+
action: chunk.toolName,
|
|
2744
|
+
file: filePath,
|
|
2745
|
+
timestamp: Date.now()
|
|
2746
|
+
};
|
|
2747
|
+
this.activityStore.append(activityEvent);
|
|
2748
|
+
this.broadcast({ type: "stash:activity", stashId, event: activityEvent });
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
if (chunk.type === "text") {
|
|
2752
|
+
this.broadcast({
|
|
2753
|
+
type: "ai_stream",
|
|
2754
|
+
content: chunk.content,
|
|
2755
|
+
streamType: "text",
|
|
2756
|
+
source: `stash-${stashId}`
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
const simpleGit6 = (await import("simple-git")).default;
|
|
2761
|
+
const git = simpleGit6(worktree);
|
|
2762
|
+
await git.add("-A");
|
|
2763
|
+
const status = await git.status();
|
|
2764
|
+
if (status.staged.length > 0) {
|
|
2765
|
+
await git.commit(`stashes: continue ${stashId}`);
|
|
2766
|
+
this.broadcast({ type: "stash:status", stashId, status: "screenshotting" });
|
|
2767
|
+
}
|
|
2768
|
+
} catch (err) {
|
|
2769
|
+
this.broadcast({ type: "stash:error", stashId, error: err instanceof Error ? err.message : String(err) });
|
|
2770
|
+
} finally {
|
|
2771
|
+
killAgentQuery(`continue-${stashId}`);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2633
2774
|
}
|
|
2634
2775
|
|
|
2635
2776
|
// ../server/dist/services/activity-store.js
|
|
@@ -2809,6 +2950,15 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort, stashPor
|
|
|
2809
2950
|
case "delete_stash":
|
|
2810
2951
|
await stashService.deleteStash(event.stashId);
|
|
2811
2952
|
break;
|
|
2953
|
+
case "continue_stash": {
|
|
2954
|
+
const stash = persistence.getStash(event.projectId, event.stashId);
|
|
2955
|
+
if (!stash?.sessionId) {
|
|
2956
|
+
broadcast({ type: "stash:error", stashId: event.stashId, error: "No session to continue \u2014 stash was generated without session persistence" });
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
await stashService.continueStash(event.projectId, event.stashId, event.message);
|
|
2960
|
+
break;
|
|
2961
|
+
}
|
|
2812
2962
|
}
|
|
2813
2963
|
} catch (err) {
|
|
2814
2964
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -2959,6 +3109,26 @@ app.get("/stash-activity/:stashId", (c) => {
|
|
|
2959
3109
|
const events = store.getEvents(stashId);
|
|
2960
3110
|
return c.json({ data: events });
|
|
2961
3111
|
});
|
|
3112
|
+
app.get("/stash-session/:stashId", async (c) => {
|
|
3113
|
+
const stashId = c.req.param("stashId");
|
|
3114
|
+
const persistence2 = getPersistence();
|
|
3115
|
+
let stash = null;
|
|
3116
|
+
for (const project of persistence2.listProjects()) {
|
|
3117
|
+
stash = persistence2.getStash(project.id, stashId);
|
|
3118
|
+
if (stash)
|
|
3119
|
+
break;
|
|
3120
|
+
}
|
|
3121
|
+
if (!stash?.sessionId) {
|
|
3122
|
+
return c.json({ data: null, error: "No session found for this stash" }, 404);
|
|
3123
|
+
}
|
|
3124
|
+
try {
|
|
3125
|
+
const { getSessionMessages } = await import("@anthropic-ai/claude-agent-sdk");
|
|
3126
|
+
const messages = await getSessionMessages(stash.sessionId, { limit: 100 });
|
|
3127
|
+
return c.json({ data: messages });
|
|
3128
|
+
} catch (err) {
|
|
3129
|
+
return c.json({ data: null, error: "Failed to retrieve session messages" }, 500);
|
|
3130
|
+
}
|
|
3131
|
+
});
|
|
2962
3132
|
function ensureProject(persistence2) {
|
|
2963
3133
|
const projects = persistence2.listProjects();
|
|
2964
3134
|
if (projects.length > 0)
|
package/dist/mcp.js
CHANGED
|
@@ -124,7 +124,7 @@ async function prepareWorktreeDeps(worktreePath, projectPath) {
|
|
|
124
124
|
pm = "yarn";
|
|
125
125
|
if (pm === "npm")
|
|
126
126
|
return;
|
|
127
|
-
const cmd = pm === "pnpm" ? ["pnpm", "install", "--
|
|
127
|
+
const cmd = pm === "pnpm" ? ["pnpm", "install", "--force", "--prefer-offline"] : pm === "bun" ? ["bun", "install", "--frozen-lockfile"] : ["yarn", "install", "--frozen-lockfile"];
|
|
128
128
|
logger.info("worktree", `installing deps (${pm})`, { worktreePath: worktreePath.split("/").slice(-2).join("/") });
|
|
129
129
|
const proc = spawn({
|
|
130
130
|
cmd,
|
|
@@ -1075,8 +1075,11 @@ async function generate(opts) {
|
|
|
1075
1075
|
id: stashId,
|
|
1076
1076
|
prompt: stashPrompt,
|
|
1077
1077
|
cwd: worktree.path,
|
|
1078
|
-
persistSession:
|
|
1078
|
+
persistSession: true
|
|
1079
1079
|
})) {
|
|
1080
|
+
if (chunk.type === "session_id" && chunk.sessionId) {
|
|
1081
|
+
persistence.saveStash({ ...stash, sessionId: chunk.sessionId });
|
|
1082
|
+
}
|
|
1080
1083
|
emit(onProgress, {
|
|
1081
1084
|
type: "ai_stream",
|
|
1082
1085
|
stashId,
|
|
@@ -1089,12 +1092,36 @@ async function generate(opts) {
|
|
|
1089
1092
|
const filePath = chunk.toolInput?.file_path ?? chunk.toolInput?.path ?? chunk.toolInput?.command ?? undefined;
|
|
1090
1093
|
const lines = chunk.toolInput?.content ? chunk.toolInput.content.split(`
|
|
1091
1094
|
`).length : undefined;
|
|
1095
|
+
let diff;
|
|
1096
|
+
if (chunk.toolName === "Write") {
|
|
1097
|
+
const writeContent = chunk.toolInput?.content;
|
|
1098
|
+
if (writeContent) {
|
|
1099
|
+
diff = writeContent.substring(0, 5000);
|
|
1100
|
+
}
|
|
1101
|
+
} else if (chunk.toolName === "Edit") {
|
|
1102
|
+
const oldStr = chunk.toolInput?.old_string;
|
|
1103
|
+
const newStr = chunk.toolInput?.new_string;
|
|
1104
|
+
if (oldStr && newStr) {
|
|
1105
|
+
const oldLines = oldStr.split(`
|
|
1106
|
+
`).map((l) => `-${l}`).join(`
|
|
1107
|
+
`);
|
|
1108
|
+
const newLines = newStr.split(`
|
|
1109
|
+
`).map((l) => `+${l}`).join(`
|
|
1110
|
+
`);
|
|
1111
|
+
diff = `--- old
|
|
1112
|
+
+++ new
|
|
1113
|
+
@@ @@
|
|
1114
|
+
${oldLines}
|
|
1115
|
+
${newLines}`.substring(0, 5000);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1092
1118
|
emit(onProgress, {
|
|
1093
1119
|
type: "activity",
|
|
1094
1120
|
stashId,
|
|
1095
1121
|
action: chunk.toolName,
|
|
1096
1122
|
file: filePath,
|
|
1097
1123
|
lines,
|
|
1124
|
+
diff,
|
|
1098
1125
|
timestamp: Date.now()
|
|
1099
1126
|
});
|
|
1100
1127
|
}
|
|
@@ -1149,7 +1176,7 @@ async function generate(opts) {
|
|
|
1149
1176
|
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1150
1177
|
await prepareWorktreeDeps(screenshotWorktree.path, projectPath);
|
|
1151
1178
|
const devServer = spawn3({
|
|
1152
|
-
cmd: ["npm", "run", "dev", "--", "--port", String(port)],
|
|
1179
|
+
cmd: ["npm", "run", "dev", "--", "--", "--port", String(port)],
|
|
1153
1180
|
cwd: screenshotWorktree.path,
|
|
1154
1181
|
stdin: "ignore",
|
|
1155
1182
|
stdout: "pipe",
|
|
@@ -1277,14 +1304,73 @@ ${context}` : `The user wants to vary the current UI. Apply this change: ${promp
|
|
|
1277
1304
|
id: stashId,
|
|
1278
1305
|
prompt: varyPrompt,
|
|
1279
1306
|
cwd: worktree.path,
|
|
1280
|
-
persistSession:
|
|
1307
|
+
persistSession: true
|
|
1281
1308
|
})) {
|
|
1309
|
+
if (chunk.type === "session_id" && chunk.sessionId) {
|
|
1310
|
+
persistence.saveStash({ ...stash, sessionId: chunk.sessionId });
|
|
1311
|
+
}
|
|
1282
1312
|
emit2(onProgress, {
|
|
1283
1313
|
type: "ai_stream",
|
|
1284
1314
|
stashId,
|
|
1285
1315
|
content: chunk.content,
|
|
1286
1316
|
streamType: chunk.type
|
|
1287
1317
|
});
|
|
1318
|
+
if (chunk.type === "tool_use" && chunk.toolName) {
|
|
1319
|
+
const knownTools = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
1320
|
+
if (knownTools.includes(chunk.toolName)) {
|
|
1321
|
+
const filePath = chunk.toolInput?.file_path ?? chunk.toolInput?.path ?? chunk.toolInput?.command ?? undefined;
|
|
1322
|
+
const lines = chunk.toolInput?.content ? chunk.toolInput.content.split(`
|
|
1323
|
+
`).length : undefined;
|
|
1324
|
+
let diff;
|
|
1325
|
+
if (chunk.toolName === "Write") {
|
|
1326
|
+
const writeContent = chunk.toolInput?.content;
|
|
1327
|
+
if (writeContent) {
|
|
1328
|
+
diff = writeContent.substring(0, 5000);
|
|
1329
|
+
}
|
|
1330
|
+
} else if (chunk.toolName === "Edit") {
|
|
1331
|
+
const oldStr = chunk.toolInput?.old_string;
|
|
1332
|
+
const newStr = chunk.toolInput?.new_string;
|
|
1333
|
+
if (oldStr && newStr) {
|
|
1334
|
+
const oldLines = oldStr.split(`
|
|
1335
|
+
`).map((l) => `-${l}`).join(`
|
|
1336
|
+
`);
|
|
1337
|
+
const newLines = newStr.split(`
|
|
1338
|
+
`).map((l) => `+${l}`).join(`
|
|
1339
|
+
`);
|
|
1340
|
+
diff = `--- old
|
|
1341
|
+
+++ new
|
|
1342
|
+
@@ @@
|
|
1343
|
+
${oldLines}
|
|
1344
|
+
${newLines}`.substring(0, 5000);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
emit2(onProgress, {
|
|
1348
|
+
type: "activity",
|
|
1349
|
+
stashId,
|
|
1350
|
+
action: chunk.toolName,
|
|
1351
|
+
file: filePath,
|
|
1352
|
+
lines,
|
|
1353
|
+
diff,
|
|
1354
|
+
timestamp: Date.now()
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
} else if (chunk.type === "thinking") {
|
|
1358
|
+
emit2(onProgress, {
|
|
1359
|
+
type: "activity",
|
|
1360
|
+
stashId,
|
|
1361
|
+
action: "thinking",
|
|
1362
|
+
content: chunk.content.substring(0, 200),
|
|
1363
|
+
timestamp: Date.now()
|
|
1364
|
+
});
|
|
1365
|
+
} else if (chunk.type === "text") {
|
|
1366
|
+
emit2(onProgress, {
|
|
1367
|
+
type: "activity",
|
|
1368
|
+
stashId,
|
|
1369
|
+
action: "text",
|
|
1370
|
+
content: chunk.content.substring(0, 200),
|
|
1371
|
+
timestamp: Date.now()
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1288
1374
|
}
|
|
1289
1375
|
const wtGit = simpleGit4(worktree.path);
|
|
1290
1376
|
let hasChanges = false;
|
|
@@ -1321,7 +1407,7 @@ ${context}` : `The user wants to vary the current UI. Apply this change: ${promp
|
|
|
1321
1407
|
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1322
1408
|
await prepareWorktreeDeps(screenshotWorktree.path, projectPath);
|
|
1323
1409
|
const devServer = spawn4({
|
|
1324
|
-
cmd: ["npm", "run", "dev", "--", "--port", String(port)],
|
|
1410
|
+
cmd: ["npm", "run", "dev", "--", "--", "--port", String(port)],
|
|
1325
1411
|
cwd: screenshotWorktree.path,
|
|
1326
1412
|
stdin: "ignore",
|
|
1327
1413
|
stdout: "pipe",
|
|
@@ -1974,7 +2060,7 @@ class PreviewPool {
|
|
|
1974
2060
|
const worktreePath = await this.worktreeManager.createPreviewForPool(stashId);
|
|
1975
2061
|
await prepareWorktreeDeps(worktreePath, this.worktreeManager.getProjectPath());
|
|
1976
2062
|
const process2 = Bun.spawn({
|
|
1977
|
-
cmd: ["npm", "run", "dev", "--", "--port", String(devPort)],
|
|
2063
|
+
cmd: ["npm", "run", "dev", "--", "--", "--port", String(devPort)],
|
|
1978
2064
|
cwd: worktreePath,
|
|
1979
2065
|
stdin: "ignore",
|
|
1980
2066
|
stdout: "pipe",
|
|
@@ -2625,7 +2711,6 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2625
2711
|
for (const s of allStashes) {
|
|
2626
2712
|
if (this.activityStore.has(s.id)) {
|
|
2627
2713
|
stashActivity[s.id] = this.activityStore.getSnapshot(s.id);
|
|
2628
|
-
this.activityStore.clear(s.id);
|
|
2629
2714
|
}
|
|
2630
2715
|
}
|
|
2631
2716
|
if (Object.keys(stashActivity).length === 0)
|
|
@@ -2783,6 +2868,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2783
2868
|
file: event.file,
|
|
2784
2869
|
lines: event.lines,
|
|
2785
2870
|
content: event.content,
|
|
2871
|
+
diff: event.diff,
|
|
2786
2872
|
timestamp: event.timestamp
|
|
2787
2873
|
};
|
|
2788
2874
|
this.activityStore.append(activityEvent);
|
|
@@ -2854,6 +2940,61 @@ ${refDescriptions.join(`
|
|
|
2854
2940
|
await this.previewPool.stop(stashId);
|
|
2855
2941
|
await remove(this.projectPath, stashId);
|
|
2856
2942
|
}
|
|
2943
|
+
async continueStash(projectId, stashId, message) {
|
|
2944
|
+
const stash = this.persistence.getStash(projectId, stashId);
|
|
2945
|
+
if (!stash?.sessionId) {
|
|
2946
|
+
this.broadcast({ type: "stash:error", stashId, error: "No session to continue" });
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
const worktree = await this.worktreeManager.createPreviewForPool(stashId);
|
|
2950
|
+
try {
|
|
2951
|
+
for await (const chunk of runAgentQuery({
|
|
2952
|
+
id: `continue-${stashId}`,
|
|
2953
|
+
prompt: message,
|
|
2954
|
+
cwd: worktree,
|
|
2955
|
+
resumeSessionId: stash.sessionId,
|
|
2956
|
+
persistSession: true
|
|
2957
|
+
})) {
|
|
2958
|
+
if (chunk.type === "session_id" && chunk.sessionId) {
|
|
2959
|
+
this.persistence.saveStash({ ...stash, sessionId: chunk.sessionId });
|
|
2960
|
+
}
|
|
2961
|
+
if (chunk.type === "tool_use" && chunk.toolName) {
|
|
2962
|
+
const knownTools = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
2963
|
+
if (knownTools.includes(chunk.toolName)) {
|
|
2964
|
+
const filePath = chunk.toolInput?.file_path ?? chunk.toolInput?.path ?? chunk.toolInput?.command ?? undefined;
|
|
2965
|
+
const activityEvent = {
|
|
2966
|
+
stashId,
|
|
2967
|
+
action: chunk.toolName,
|
|
2968
|
+
file: filePath,
|
|
2969
|
+
timestamp: Date.now()
|
|
2970
|
+
};
|
|
2971
|
+
this.activityStore.append(activityEvent);
|
|
2972
|
+
this.broadcast({ type: "stash:activity", stashId, event: activityEvent });
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
if (chunk.type === "text") {
|
|
2976
|
+
this.broadcast({
|
|
2977
|
+
type: "ai_stream",
|
|
2978
|
+
content: chunk.content,
|
|
2979
|
+
streamType: "text",
|
|
2980
|
+
source: `stash-${stashId}`
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
const simpleGit6 = (await import("simple-git")).default;
|
|
2985
|
+
const git = simpleGit6(worktree);
|
|
2986
|
+
await git.add("-A");
|
|
2987
|
+
const status = await git.status();
|
|
2988
|
+
if (status.staged.length > 0) {
|
|
2989
|
+
await git.commit(`stashes: continue ${stashId}`);
|
|
2990
|
+
this.broadcast({ type: "stash:status", stashId, status: "screenshotting" });
|
|
2991
|
+
}
|
|
2992
|
+
} catch (err) {
|
|
2993
|
+
this.broadcast({ type: "stash:error", stashId, error: err instanceof Error ? err.message : String(err) });
|
|
2994
|
+
} finally {
|
|
2995
|
+
killAgentQuery(`continue-${stashId}`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2857
2998
|
}
|
|
2858
2999
|
|
|
2859
3000
|
// ../server/dist/services/activity-store.js
|
|
@@ -3033,6 +3174,15 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort, stashPor
|
|
|
3033
3174
|
case "delete_stash":
|
|
3034
3175
|
await stashService.deleteStash(event.stashId);
|
|
3035
3176
|
break;
|
|
3177
|
+
case "continue_stash": {
|
|
3178
|
+
const stash = persistence.getStash(event.projectId, event.stashId);
|
|
3179
|
+
if (!stash?.sessionId) {
|
|
3180
|
+
broadcast({ type: "stash:error", stashId: event.stashId, error: "No session to continue \u2014 stash was generated without session persistence" });
|
|
3181
|
+
break;
|
|
3182
|
+
}
|
|
3183
|
+
await stashService.continueStash(event.projectId, event.stashId, event.message);
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3036
3186
|
}
|
|
3037
3187
|
} catch (err) {
|
|
3038
3188
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -3183,6 +3333,26 @@ app.get("/stash-activity/:stashId", (c) => {
|
|
|
3183
3333
|
const events = store.getEvents(stashId);
|
|
3184
3334
|
return c.json({ data: events });
|
|
3185
3335
|
});
|
|
3336
|
+
app.get("/stash-session/:stashId", async (c) => {
|
|
3337
|
+
const stashId = c.req.param("stashId");
|
|
3338
|
+
const persistence2 = getPersistence();
|
|
3339
|
+
let stash = null;
|
|
3340
|
+
for (const project of persistence2.listProjects()) {
|
|
3341
|
+
stash = persistence2.getStash(project.id, stashId);
|
|
3342
|
+
if (stash)
|
|
3343
|
+
break;
|
|
3344
|
+
}
|
|
3345
|
+
if (!stash?.sessionId) {
|
|
3346
|
+
return c.json({ data: null, error: "No session found for this stash" }, 404);
|
|
3347
|
+
}
|
|
3348
|
+
try {
|
|
3349
|
+
const { getSessionMessages } = await import("@anthropic-ai/claude-agent-sdk");
|
|
3350
|
+
const messages = await getSessionMessages(stash.sessionId, { limit: 100 });
|
|
3351
|
+
return c.json({ data: messages });
|
|
3352
|
+
} catch (err) {
|
|
3353
|
+
return c.json({ data: null, error: "Failed to retrieve session messages" }, 500);
|
|
3354
|
+
}
|
|
3355
|
+
});
|
|
3186
3356
|
function ensureProject(persistence2) {
|
|
3187
3357
|
const projects = persistence2.listProjects();
|
|
3188
3358
|
if (projects.length > 0)
|