@co0ontty/wand 1.17.6 → 1.18.1
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/process-manager.d.ts +4 -2
- package/dist/process-manager.js +57 -12
- package/dist/server-session-routes.js +2 -4
- package/dist/web-ui/content/scripts.js +427 -121
- package/dist/web-ui/content/styles.css +546 -67
- package/package.json +1 -1
|
@@ -44,6 +44,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
44
44
|
worktreeEnabled?: boolean;
|
|
45
45
|
provider?: SessionProvider;
|
|
46
46
|
model?: string;
|
|
47
|
+
reuseId?: string;
|
|
47
48
|
}): SessionSnapshot;
|
|
48
49
|
list(): SessionSnapshot[];
|
|
49
50
|
/** Return lightweight snapshots for the session list (no output/messages). */
|
|
@@ -69,6 +70,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
69
70
|
private emitTask;
|
|
70
71
|
resize(id: string, cols: number, rows: number): SessionSnapshot;
|
|
71
72
|
stop(id: string): SessionSnapshot;
|
|
73
|
+
private cleanupRecord;
|
|
72
74
|
delete(id: string): void;
|
|
73
75
|
private deleteClaudeCache;
|
|
74
76
|
runStartupCommands(): SessionSnapshot[];
|
|
@@ -104,8 +106,8 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
104
106
|
/**
|
|
105
107
|
* Auto-recover the most recent exited session that has a Claude session ID.
|
|
106
108
|
* Only resumes one session per server start, using the most recent eligible
|
|
107
|
-
* session.
|
|
108
|
-
* `autoRecovered: true
|
|
109
|
+
* session. Reuses the original session ID (in-place resume) and sets
|
|
110
|
+
* `autoRecovered: true`.
|
|
109
111
|
*/
|
|
110
112
|
private autoRecoverExitedSessions;
|
|
111
113
|
private archiveExpiredSessions;
|
package/dist/process-manager.js
CHANGED
|
@@ -608,13 +608,23 @@ export class ProcessManager extends EventEmitter {
|
|
|
608
608
|
this.lastPersistedMessageState.delete(id);
|
|
609
609
|
this.storage.deleteSession(id);
|
|
610
610
|
}
|
|
611
|
+
if (toRemove.length > 0) {
|
|
612
|
+
this.claudeHistoryCache = null;
|
|
613
|
+
}
|
|
611
614
|
}
|
|
612
615
|
start(command, cwd, mode, initialInput, opts) {
|
|
613
616
|
this.assertCommandAllowed(command);
|
|
614
617
|
const baseCwd = cwd
|
|
615
618
|
? path.resolve(process.cwd(), cwd)
|
|
616
619
|
: path.resolve(process.cwd(), this.config.defaultCwd);
|
|
617
|
-
const id = randomUUID();
|
|
620
|
+
const id = opts?.reuseId || randomUUID();
|
|
621
|
+
if (opts?.reuseId) {
|
|
622
|
+
const oldRecord = this.sessions.get(id);
|
|
623
|
+
if (oldRecord) {
|
|
624
|
+
this.cleanupRecord(oldRecord);
|
|
625
|
+
this.sessions.delete(id);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
618
628
|
const worktreeSetup = opts?.worktreeEnabled
|
|
619
629
|
? prepareSessionWorktree({ cwd: baseCwd, sessionId: id })
|
|
620
630
|
: null;
|
|
@@ -690,6 +700,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
690
700
|
}
|
|
691
701
|
this.sessions.set(id, record);
|
|
692
702
|
this.persist(record);
|
|
703
|
+
if (initialClaudeSessionId) {
|
|
704
|
+
this.claudeHistoryCache = null;
|
|
705
|
+
}
|
|
693
706
|
this.cleanupOldSessions();
|
|
694
707
|
this.lifecycleManager.register(id, "initializing");
|
|
695
708
|
const shellArgs = this.buildShellArgs(processedCommand);
|
|
@@ -800,6 +813,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
800
813
|
const bridgeSessionId = rec.ptyBridge?.getClaudeSessionId();
|
|
801
814
|
if (bridgeSessionId && bridgeSessionId !== rec.claudeSessionId) {
|
|
802
815
|
rec.claudeSessionId = bridgeSessionId;
|
|
816
|
+
this.claudeHistoryCache = null;
|
|
803
817
|
process.stderr.write(`[wand] Captured Claude session ID: ${bridgeSessionId}\n`);
|
|
804
818
|
}
|
|
805
819
|
if (!rec.claudeSessionId && rec.knownClaudeTaskIds) {
|
|
@@ -1083,6 +1097,41 @@ export class ProcessManager extends EventEmitter {
|
|
|
1083
1097
|
this.persist(record);
|
|
1084
1098
|
return this.snapshot(record);
|
|
1085
1099
|
}
|
|
1100
|
+
cleanupRecord(record) {
|
|
1101
|
+
if (record.taskDebounceTimer) {
|
|
1102
|
+
clearTimeout(record.taskDebounceTimer);
|
|
1103
|
+
record.taskDebounceTimer = null;
|
|
1104
|
+
}
|
|
1105
|
+
if (record.claudeTaskDiscoveryTimer) {
|
|
1106
|
+
clearTimeout(record.claudeTaskDiscoveryTimer);
|
|
1107
|
+
record.claudeTaskDiscoveryTimer = null;
|
|
1108
|
+
}
|
|
1109
|
+
if (record.initialInputTimer) {
|
|
1110
|
+
clearTimeout(record.initialInputTimer);
|
|
1111
|
+
record.initialInputTimer = null;
|
|
1112
|
+
}
|
|
1113
|
+
const pendingPersist = this.persistDebounceTimers.get(record.id);
|
|
1114
|
+
if (pendingPersist) {
|
|
1115
|
+
clearTimeout(pendingPersist);
|
|
1116
|
+
this.persistDebounceTimers.delete(record.id);
|
|
1117
|
+
}
|
|
1118
|
+
if (record.status === "running") {
|
|
1119
|
+
record.stopRequested = true;
|
|
1120
|
+
if (record.childProcess) {
|
|
1121
|
+
record.childProcess.kill();
|
|
1122
|
+
record.childProcess = null;
|
|
1123
|
+
}
|
|
1124
|
+
if (record.ptyProcess) {
|
|
1125
|
+
record.ptyProcess.kill();
|
|
1126
|
+
record.ptyProcess = null;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (record.ptyBridge) {
|
|
1130
|
+
record.ptyBridge.removeAllListeners();
|
|
1131
|
+
record.ptyBridge = null;
|
|
1132
|
+
}
|
|
1133
|
+
this.lifecycleManager.unregister(record.id);
|
|
1134
|
+
}
|
|
1086
1135
|
delete(id) {
|
|
1087
1136
|
const record = this.mustGet(id);
|
|
1088
1137
|
// Always clear pending timers
|
|
@@ -1135,6 +1184,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
1135
1184
|
this.sessions.delete(id);
|
|
1136
1185
|
this.lastPersistedMessageState.delete(id);
|
|
1137
1186
|
this.lifecycleManager.unregister(id);
|
|
1187
|
+
if (record.claudeSessionId) {
|
|
1188
|
+
this.claudeHistoryCache = null;
|
|
1189
|
+
}
|
|
1138
1190
|
}
|
|
1139
1191
|
deleteClaudeCache(record) {
|
|
1140
1192
|
if (!record.claudeSessionId)
|
|
@@ -1357,8 +1409,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
1357
1409
|
/**
|
|
1358
1410
|
* Auto-recover the most recent exited session that has a Claude session ID.
|
|
1359
1411
|
* Only resumes one session per server start, using the most recent eligible
|
|
1360
|
-
* session.
|
|
1361
|
-
* `autoRecovered: true
|
|
1412
|
+
* session. Reuses the original session ID (in-place resume) and sets
|
|
1413
|
+
* `autoRecovered: true`.
|
|
1362
1414
|
*/
|
|
1363
1415
|
autoRecoverExitedSessions() {
|
|
1364
1416
|
// Find eligible exited sessions
|
|
@@ -1393,19 +1445,12 @@ export class ProcessManager extends EventEmitter {
|
|
|
1393
1445
|
}
|
|
1394
1446
|
console.error(`[ProcessManager] Auto-recovering session ${original.id} with Claude session ID ${original.claudeSessionId}`);
|
|
1395
1447
|
const resumeCommand = `${original.command.trim()} --resume ${original.claudeSessionId}`;
|
|
1396
|
-
let newRecord = null;
|
|
1397
1448
|
try {
|
|
1398
1449
|
const snapshot = this.start(resumeCommand, original.cwd, original.mode, undefined, {
|
|
1399
|
-
|
|
1450
|
+
reuseId: original.id,
|
|
1400
1451
|
autoRecovered: true
|
|
1401
1452
|
});
|
|
1402
|
-
|
|
1403
|
-
if (!newRecord)
|
|
1404
|
-
return;
|
|
1405
|
-
// Set resumedToSessionId on the original session
|
|
1406
|
-
original.resumedToSessionId = snapshot.id;
|
|
1407
|
-
this.storage.saveSession(this.snapshot(original));
|
|
1408
|
-
console.error(`[ProcessManager] Auto-recovered session ${snapshot.id} from ${original.id}`);
|
|
1453
|
+
console.error(`[ProcessManager] Auto-recovered session ${snapshot.id} (in-place)`);
|
|
1409
1454
|
}
|
|
1410
1455
|
catch (err) {
|
|
1411
1456
|
console.error(`[ProcessManager] Auto-recovery failed: ${String(err)}`);
|
|
@@ -406,8 +406,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
406
406
|
}
|
|
407
407
|
const newMode = body.mode ? normalizeMode(body.mode, defaultMode) : normalizeMode(existingSession.mode, defaultMode);
|
|
408
408
|
const resumeCommand = `${command} --resume ${claudeSessionId}`;
|
|
409
|
-
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, {
|
|
410
|
-
storage.saveSession({ ...existingSession, resumedToSessionId: newSnapshot.id, archived: true });
|
|
409
|
+
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, { reuseId: sessionId });
|
|
411
410
|
res.status(201).json({ resumedFromSessionId: sessionId, ...newSnapshot });
|
|
412
411
|
}
|
|
413
412
|
catch (error) {
|
|
@@ -444,8 +443,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
444
443
|
}
|
|
445
444
|
const newMode = body.mode ? normalizeMode(body.mode, defaultMode) : normalizeMode(existingSession.mode, defaultMode);
|
|
446
445
|
const resumeCommand = `${command} --resume ${claudeSessionId}`;
|
|
447
|
-
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, {
|
|
448
|
-
storage.saveSession({ ...existingSession, resumedToSessionId: newSnapshot.id, archived: true });
|
|
446
|
+
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, { reuseId: existingSession.id });
|
|
449
447
|
res.status(201).json({ resumedFromSessionId: existingSession.id, resumedClaudeSessionId: claudeSessionId, ...newSnapshot });
|
|
450
448
|
}
|
|
451
449
|
else {
|