@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.
@@ -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. Sets `resumedToSessionId` on the original session and
108
- * `autoRecovered: true` on the new session.
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;
@@ -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. Sets `resumedToSessionId` on the original session and
1361
- * `autoRecovered: true` on the new session.
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
- resumedFromSessionId: original.id,
1450
+ reuseId: original.id,
1400
1451
  autoRecovered: true
1401
1452
  });
1402
- newRecord = this.sessions.get(snapshot.id) ?? null;
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, { resumedFromSessionId: sessionId });
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, { resumedFromSessionId: existingSession.id });
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 {