@co0ontty/wand 2.4.0 → 2.4.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/build-info.json +3 -3
- package/dist/process-manager.d.ts +2 -0
- package/dist/process-manager.js +94 -19
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "2.4.
|
|
2
|
+
"commit": "f3d51073dca38b18ea1104511c7fdebdd125b086",
|
|
3
|
+
"builtAt": "2026-07-04T10:40:24.303Z",
|
|
4
|
+
"version": "2.4.1",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
|
@@ -30,8 +30,10 @@ export interface CodexHistorySession {
|
|
|
30
30
|
claudeSessionId: string;
|
|
31
31
|
cwd: string;
|
|
32
32
|
firstUserMessage: string;
|
|
33
|
+
firstUserAt: string;
|
|
33
34
|
timestamp: string;
|
|
34
35
|
mtimeMs: number;
|
|
36
|
+
hasUser: boolean;
|
|
35
37
|
hasConversation: boolean;
|
|
36
38
|
managedByWand: boolean;
|
|
37
39
|
provider: "codex";
|
package/dist/process-manager.js
CHANGED
|
@@ -331,6 +331,7 @@ function readCodexSessionSummary(filePath) {
|
|
|
331
331
|
let cwd = "";
|
|
332
332
|
let timestamp = "";
|
|
333
333
|
let firstUserMessage = "";
|
|
334
|
+
let firstUserAt = "";
|
|
334
335
|
let hasUser = false;
|
|
335
336
|
let hasAssistant = false;
|
|
336
337
|
for (const line of lines) {
|
|
@@ -363,6 +364,9 @@ function readCodexSessionSummary(filePath) {
|
|
|
363
364
|
: "";
|
|
364
365
|
if (text.trim()) {
|
|
365
366
|
hasUser = true;
|
|
367
|
+
if (!firstUserAt && parsed.timestamp) {
|
|
368
|
+
firstUserAt = parsed.timestamp;
|
|
369
|
+
}
|
|
366
370
|
if (!firstUserMessage && !isCodexSystemInjectedText(text)) {
|
|
367
371
|
firstUserMessage = text.trim().slice(0, 120);
|
|
368
372
|
}
|
|
@@ -378,8 +382,10 @@ function readCodexSessionSummary(filePath) {
|
|
|
378
382
|
claudeSessionId: id,
|
|
379
383
|
cwd,
|
|
380
384
|
firstUserMessage,
|
|
385
|
+
firstUserAt,
|
|
381
386
|
timestamp: timestamp || new Date(stats.mtimeMs).toISOString(),
|
|
382
387
|
mtimeMs: stats.mtimeMs,
|
|
388
|
+
hasUser,
|
|
383
389
|
hasConversation: hasUser && hasAssistant,
|
|
384
390
|
managedByWand: false,
|
|
385
391
|
provider: "codex",
|
|
@@ -433,10 +439,19 @@ function isSameResolvedPath(left, right) {
|
|
|
433
439
|
return false;
|
|
434
440
|
return path.resolve(left) === path.resolve(right);
|
|
435
441
|
}
|
|
442
|
+
function isUsableCodexHistorySession(session) {
|
|
443
|
+
return session.hasUser || session.hasConversation;
|
|
444
|
+
}
|
|
445
|
+
function parseTimeMs(value) {
|
|
446
|
+
if (!value)
|
|
447
|
+
return null;
|
|
448
|
+
const parsed = Date.parse(value);
|
|
449
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
450
|
+
}
|
|
436
451
|
function selectCodexSessionForRecord(record) {
|
|
437
452
|
const knownMtimes = record.knownCodexSessionMtimes ?? new Map();
|
|
438
453
|
const candidates = listAllCodexHistorySessions()
|
|
439
|
-
.filter(
|
|
454
|
+
.filter(isUsableCodexHistorySession)
|
|
440
455
|
.filter((session) => isSameResolvedPath(session.cwd, record.cwd))
|
|
441
456
|
.filter((session) => {
|
|
442
457
|
const previousMtime = knownMtimes.get(session.claudeSessionId);
|
|
@@ -459,6 +474,36 @@ function selectCodexSessionForRecord(record) {
|
|
|
459
474
|
function getLatestCodexSessionId(record) {
|
|
460
475
|
return selectCodexSessionForRecord(record)?.claudeSessionId ?? null;
|
|
461
476
|
}
|
|
477
|
+
function selectCodexSessionForTimeWindow(record) {
|
|
478
|
+
const startedAtMs = parseTimeMs(record.startedAt);
|
|
479
|
+
if (startedAtMs === null)
|
|
480
|
+
return null;
|
|
481
|
+
const endedAtMs = parseTimeMs(record.endedAt) ?? Date.now();
|
|
482
|
+
const windowStart = startedAtMs - START_TIME_SKEW_MS;
|
|
483
|
+
const windowEnd = endedAtMs + START_TIME_SKEW_MS;
|
|
484
|
+
const candidates = listAllCodexHistorySessions()
|
|
485
|
+
.filter(isUsableCodexHistorySession)
|
|
486
|
+
.filter((session) => isSameResolvedPath(session.cwd, record.cwd))
|
|
487
|
+
.filter((session) => {
|
|
488
|
+
const firstUserAtMs = parseTimeMs(session.firstUserAt);
|
|
489
|
+
if (firstUserAtMs !== null) {
|
|
490
|
+
return firstUserAtMs >= windowStart && firstUserAtMs <= windowEnd;
|
|
491
|
+
}
|
|
492
|
+
return session.mtimeMs >= windowStart && session.mtimeMs <= endedAtMs + DISCOVERY_RECENT_WINDOW_MS;
|
|
493
|
+
})
|
|
494
|
+
.sort((a, b) => {
|
|
495
|
+
const aTime = parseTimeMs(a.firstUserAt) ?? a.mtimeMs;
|
|
496
|
+
const bTime = parseTimeMs(b.firstUserAt) ?? b.mtimeMs;
|
|
497
|
+
return Math.abs(aTime - startedAtMs) - Math.abs(bTime - startedAtMs);
|
|
498
|
+
});
|
|
499
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
500
|
+
}
|
|
501
|
+
function recoverCodexSessionIdFromHistory(snapshot) {
|
|
502
|
+
if (snapshot.provider !== "codex" || snapshot.claudeSessionId) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
return getCodexResumeCommandSessionId(snapshot.command) ?? selectCodexSessionForTimeWindow(snapshot)?.claudeSessionId ?? null;
|
|
506
|
+
}
|
|
462
507
|
/** Delete every rollout file belonging to the given codex thread ids. */
|
|
463
508
|
function deleteCodexRolloutFiles(threadIds) {
|
|
464
509
|
if (threadIds.size === 0)
|
|
@@ -597,6 +642,15 @@ export class ProcessManager extends EventEmitter {
|
|
|
597
642
|
: isCodexCmd
|
|
598
643
|
? getCodexResumeCommandSessionId(snapshot.command)
|
|
599
644
|
: null;
|
|
645
|
+
const orphanEndedAt = snapshot.status === "running" ? new Date().toISOString() : null;
|
|
646
|
+
const sessionIdFromHistory = isCodexCmd
|
|
647
|
+
? recoverCodexSessionIdFromHistory({
|
|
648
|
+
...snapshot,
|
|
649
|
+
provider: "codex",
|
|
650
|
+
endedAt: snapshot.endedAt ?? orphanEndedAt,
|
|
651
|
+
})
|
|
652
|
+
: null;
|
|
653
|
+
const restoredSessionId = resumeCommandSessionId ?? snapshot.claudeSessionId ?? sessionIdFromHistory;
|
|
600
654
|
// Sessions restored from storage have ptyProcess: null — the old server's PTY
|
|
601
655
|
// belongs to a dead process. Mark running sessions as exited so the UI
|
|
602
656
|
// reflects reality and users can start fresh sessions.
|
|
@@ -605,10 +659,14 @@ export class ProcessManager extends EventEmitter {
|
|
|
605
659
|
const updated = {
|
|
606
660
|
...snapshot,
|
|
607
661
|
status: "exited",
|
|
608
|
-
endedAt:
|
|
662
|
+
endedAt: orphanEndedAt,
|
|
663
|
+
claudeSessionId: restoredSessionId ?? null,
|
|
609
664
|
messages: recoveredMessages.length > 0 ? recoveredMessages : snapshot.messages,
|
|
610
665
|
};
|
|
611
666
|
this.storage.saveSession(updated);
|
|
667
|
+
if (isCodexCmd && restoredSessionId && restoredSessionId !== snapshot.claudeSessionId) {
|
|
668
|
+
process.stderr.write(`[wand] Recovered Codex thread ID for orphan PTY ${snapshot.id}: ${restoredSessionId}\n`);
|
|
669
|
+
}
|
|
612
670
|
this.sessions.set(snapshot.id, {
|
|
613
671
|
...updated,
|
|
614
672
|
provider,
|
|
@@ -641,7 +699,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
641
699
|
knownClaudeProjectMtimes: isClaudeCmd ? listClaudeProjectSessionMtimes(updated.cwd) : undefined,
|
|
642
700
|
knownCodexSessionMtimes: isCodexCmd ? listCodexSessionMtimes() : undefined,
|
|
643
701
|
codexSessionDiscoveryTimer: null,
|
|
644
|
-
claudeSessionId:
|
|
702
|
+
claudeSessionId: restoredSessionId ?? updated.claudeSessionId,
|
|
645
703
|
approvalStats: { tool: 0, command: 0, file: 0, total: 0 },
|
|
646
704
|
ptyCols: snapshot.ptyCols ?? 120,
|
|
647
705
|
ptyRows: snapshot.ptyRows ?? 36,
|
|
@@ -649,8 +707,17 @@ export class ProcessManager extends EventEmitter {
|
|
|
649
707
|
this.orphanRecoveredCount += 1;
|
|
650
708
|
}
|
|
651
709
|
else {
|
|
710
|
+
const updated = restoredSessionId && restoredSessionId !== snapshot.claudeSessionId
|
|
711
|
+
? { ...snapshot, claudeSessionId: restoredSessionId }
|
|
712
|
+
: snapshot;
|
|
713
|
+
if (updated !== snapshot) {
|
|
714
|
+
this.storage.saveSessionMetadata(updated);
|
|
715
|
+
if (isCodexCmd) {
|
|
716
|
+
process.stderr.write(`[wand] Recovered Codex thread ID for saved PTY ${snapshot.id}: ${restoredSessionId}\n`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
652
719
|
this.sessions.set(snapshot.id, {
|
|
653
|
-
...
|
|
720
|
+
...updated,
|
|
654
721
|
provider,
|
|
655
722
|
processId: null,
|
|
656
723
|
ptyProcess: null,
|
|
@@ -676,10 +743,10 @@ export class ProcessManager extends EventEmitter {
|
|
|
676
743
|
lastEmittedTask: null,
|
|
677
744
|
knownClaudeTaskIds: undefined,
|
|
678
745
|
claudeTaskDiscoveryTimer: null,
|
|
679
|
-
knownClaudeProjectMtimes: isClaudeCmd ? listClaudeProjectSessionMtimes(
|
|
746
|
+
knownClaudeProjectMtimes: isClaudeCmd ? listClaudeProjectSessionMtimes(updated.cwd) : undefined,
|
|
680
747
|
knownCodexSessionMtimes: isCodexCmd ? listCodexSessionMtimes() : undefined,
|
|
681
748
|
codexSessionDiscoveryTimer: null,
|
|
682
|
-
claudeSessionId:
|
|
749
|
+
claudeSessionId: restoredSessionId ?? updated.claudeSessionId,
|
|
683
750
|
approvalStats: { tool: 0, command: 0, file: 0, total: 0 },
|
|
684
751
|
ptyCols: snapshot.ptyCols ?? 120,
|
|
685
752
|
ptyRows: snapshot.ptyRows ?? 36,
|
|
@@ -928,7 +995,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
928
995
|
}
|
|
929
996
|
current.pendingEscalation = null;
|
|
930
997
|
current.ptyPermissionBlocked = false;
|
|
931
|
-
this.captureCodexSessionId(current);
|
|
998
|
+
this.captureCodexSessionId(current, { allowTimeWindowFallback: true });
|
|
932
999
|
current.status = current.stopRequested ? "stopped" : exitCode === 0 ? "exited" : "failed";
|
|
933
1000
|
current.exitCode = current.stopRequested ? null : exitCode;
|
|
934
1001
|
current.endedAt = new Date().toISOString();
|
|
@@ -1175,22 +1242,30 @@ export class ProcessManager extends EventEmitter {
|
|
|
1175
1242
|
}
|
|
1176
1243
|
return deleted;
|
|
1177
1244
|
}
|
|
1178
|
-
captureCodexSessionId(record) {
|
|
1179
|
-
if (record.provider !== "codex" || record.claudeSessionId
|
|
1245
|
+
captureCodexSessionId(record, options) {
|
|
1246
|
+
if (record.provider !== "codex" || record.claudeSessionId) {
|
|
1180
1247
|
return false;
|
|
1181
1248
|
}
|
|
1182
|
-
const discoveredThreadId =
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1249
|
+
const discoveredThreadId = record.knownCodexSessionMtimes
|
|
1250
|
+
? getLatestCodexSessionId({
|
|
1251
|
+
cwd: record.cwd,
|
|
1252
|
+
startedAt: record.startedAt,
|
|
1253
|
+
knownCodexSessionMtimes: record.knownCodexSessionMtimes,
|
|
1254
|
+
})
|
|
1255
|
+
: null;
|
|
1256
|
+
const fallbackThreadId = discoveredThreadId
|
|
1257
|
+
? null
|
|
1258
|
+
: options?.allowTimeWindowFallback
|
|
1259
|
+
? selectCodexSessionForTimeWindow(record)?.claudeSessionId ?? null
|
|
1260
|
+
: null;
|
|
1261
|
+
const threadId = discoveredThreadId ?? fallbackThreadId;
|
|
1262
|
+
if (!threadId) {
|
|
1188
1263
|
return false;
|
|
1189
1264
|
}
|
|
1190
|
-
record.claudeSessionId =
|
|
1191
|
-
record.knownCodexSessionMtimes
|
|
1265
|
+
record.claudeSessionId = threadId;
|
|
1266
|
+
record.knownCodexSessionMtimes?.set(threadId, Date.now());
|
|
1192
1267
|
this.codexHistoryCache = null;
|
|
1193
|
-
process.stderr.write(`[wand] Captured Codex thread ID: ${
|
|
1268
|
+
process.stderr.write(`[wand] Captured Codex thread ID: ${threadId}\n`);
|
|
1194
1269
|
return true;
|
|
1195
1270
|
}
|
|
1196
1271
|
get(id) {
|
|
@@ -1388,7 +1463,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1388
1463
|
record.ptyBridge = null;
|
|
1389
1464
|
}
|
|
1390
1465
|
// Update lifecycle
|
|
1391
|
-
this.captureCodexSessionId(record);
|
|
1466
|
+
this.captureCodexSessionId(record, { allowTimeWindowFallback: true });
|
|
1392
1467
|
this.persist(record);
|
|
1393
1468
|
return this.snapshot(record);
|
|
1394
1469
|
}
|