handmux 0.26.0 → 0.27.2
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/README.md +20 -93
- package/README.zh-CN.md +20 -93
- package/dist/package.json +19 -19
- package/dist/public/assets/index-B4pKf3IW.js +356 -0
- package/dist/public/assets/index-Cdrh2CUp.css +32 -0
- package/dist/public/assets/pcm-worklet-NzlBQ6-t.js +1 -0
- package/dist/public/index.html +2 -2
- package/dist/src/agent-runtime/adapter.js +2 -0
- package/dist/src/agent-runtime/builtinRuntime.js +3 -1
- package/dist/src/agent-runtime/conversationActivation.js +136 -4
- package/dist/src/agent-runtime/run.js +23 -2
- package/dist/src/agent-runtime/runtime.js +174 -15
- package/dist/src/agent-runtime/tmuxRuntime.js +3 -7
- package/dist/src/agents/codex.js +78 -10
- package/dist/src/agents/codexActivationReceipt.js +149 -0
- package/dist/src/agents/codexConversation.js +59 -20
- package/dist/src/agents/codexConversationActivation.js +265 -65
- package/dist/src/agents/codexOpenSession.js +164 -0
- package/dist/src/agents/nativeInbox.js +24 -56
- package/dist/src/agents/scanUtils.js +7 -1
- package/dist/src/apiErrors.js +10 -1
- package/dist/src/asr/config.js +29 -0
- package/dist/src/asr/providerRegistry.js +19 -0
- package/dist/src/asr/providers/tencent.js +135 -0
- package/dist/src/asr/providers/xfyun.js +73 -0
- package/dist/src/asr/tencentSentence.js +111 -0
- package/dist/src/asr/tencentSign.js +20 -0
- package/dist/src/asr/verify.js +80 -0
- package/dist/src/cli/codexManaged.js +13 -2
- package/dist/src/cli/i18n/en.js +16 -1
- package/dist/src/cli/i18n/zh.js +16 -1
- package/dist/src/cli/options.js +29 -2
- package/dist/src/cli/setupModel.js +49 -5
- package/dist/src/cli/setupWizard.js +149 -29
- package/dist/src/cli/supervisor.js +24 -8
- package/dist/src/cli/supervisorLaunch.js +26 -0
- package/dist/src/codexAppServer.js +41 -4
- package/dist/src/routes/agents.js +51 -14
- package/dist/src/routes/system.js +109 -10
- package/dist/src/server.js +11 -2
- package/dist/src/terminalStream.js +8 -30
- package/dist/src/tmux/commands.js +14 -0
- package/dist/src/tmux/controlProtocol.js +26 -0
- package/dist/src/tmux/paneOutputCapture.js +180 -0
- package/package.json +5 -5
- package/dist/public/assets/index-BVFKs5dN.css +0 -32
- package/dist/public/assets/index-KDa5PCoi.js +0 -356
- package/dist/public/assets/pcm-worklet-CxcDy7PB.js +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fsyncDirectorySync, PrivateStateStore } from '../privateStateStore.js';
|
|
4
|
+
import { isSessionUuid } from './scanUtils.js';
|
|
5
|
+
const CODEX_AGENT_ID = 'codex';
|
|
6
|
+
const MAX_RECEIPTS = 128;
|
|
7
|
+
const OPERATION_ID_RE = /^[0-9a-f]{64}$/;
|
|
8
|
+
const PANE_ID_RE = /^%\d+$/;
|
|
9
|
+
const WINDOW_ID_RE = /^@\d+$/;
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function validReceipt(value) {
|
|
14
|
+
if (!isRecord(value) || !isRecord(value.pane) || !isRecord(value.process))
|
|
15
|
+
return false;
|
|
16
|
+
const pane = value.pane;
|
|
17
|
+
const process = value.process;
|
|
18
|
+
return typeof value.operationId === 'string' && OPERATION_ID_RE.test(value.operationId)
|
|
19
|
+
&& value.agentId === CODEX_AGENT_ID
|
|
20
|
+
&& typeof pane.paneId === 'string' && PANE_ID_RE.test(pane.paneId)
|
|
21
|
+
&& typeof pane.sessionName === 'string' && pane.sessionName.length > 0 && pane.sessionName.length <= 1_024
|
|
22
|
+
&& typeof pane.windowId === 'string' && WINDOW_ID_RE.test(pane.windowId)
|
|
23
|
+
&& typeof pane.tmuxEpoch === 'string' && pane.tmuxEpoch.length > 0 && pane.tmuxEpoch.length <= 1_024
|
|
24
|
+
&& typeof process.pid === 'number' && Number.isSafeInteger(process.pid) && process.pid > 0
|
|
25
|
+
&& typeof process.startedAt === 'number' && Number.isSafeInteger(process.startedAt) && process.startedAt > 0
|
|
26
|
+
&& typeof process.tty === 'string' && process.tty.length > 0 && process.tty.length <= 1_024
|
|
27
|
+
&& typeof process.executable === 'string' && process.executable.length > 0 && process.executable.length <= 8_192
|
|
28
|
+
&& typeof value.sessionId === 'string' && isSessionUuid(value.sessionId)
|
|
29
|
+
&& value.command === `handmux codex resume ${value.sessionId}`
|
|
30
|
+
&& (value.phase === 'prepared' || value.phase === 'interrupted' || value.phase === 'resuming')
|
|
31
|
+
&& typeof value.createdAt === 'number' && Number.isSafeInteger(value.createdAt) && value.createdAt > 0
|
|
32
|
+
&& typeof value.updatedAt === 'number' && Number.isSafeInteger(value.updatedAt)
|
|
33
|
+
&& value.updatedAt >= value.createdAt;
|
|
34
|
+
}
|
|
35
|
+
function parseState(value) {
|
|
36
|
+
if (value === null)
|
|
37
|
+
return { version: 1, receipts: [] };
|
|
38
|
+
if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.receipts)
|
|
39
|
+
|| value.receipts.length > MAX_RECEIPTS || !value.receipts.every(validReceipt)) {
|
|
40
|
+
throw new Error('Invalid Codex activation recovery state');
|
|
41
|
+
}
|
|
42
|
+
return { version: 1, receipts: structuredClone(value.receipts) };
|
|
43
|
+
}
|
|
44
|
+
function operationId(input) {
|
|
45
|
+
return crypto.createHash('sha256').update(JSON.stringify({
|
|
46
|
+
agentId: CODEX_AGENT_ID,
|
|
47
|
+
...input,
|
|
48
|
+
})).digest('hex');
|
|
49
|
+
}
|
|
50
|
+
function sameProcess(first, second) {
|
|
51
|
+
return first.pid === second.pid && first.startedAt === second.startedAt
|
|
52
|
+
&& first.tty === second.tty && first.executable === second.executable;
|
|
53
|
+
}
|
|
54
|
+
export class CodexActivationReceiptStore {
|
|
55
|
+
#store;
|
|
56
|
+
#now;
|
|
57
|
+
#write;
|
|
58
|
+
#fsyncDirectory;
|
|
59
|
+
#state;
|
|
60
|
+
#available = true;
|
|
61
|
+
constructor(file, { now = Date.now, write, fsyncDirectory = fsyncDirectorySync, } = {}) {
|
|
62
|
+
this.#store = new PrivateStateStore(file);
|
|
63
|
+
try {
|
|
64
|
+
this.#state = parseState(this.#store.readStrict());
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
this.#state = { version: 1, receipts: [] };
|
|
68
|
+
this.#available = false;
|
|
69
|
+
}
|
|
70
|
+
this.#now = now;
|
|
71
|
+
this.#write = write ?? ((state) => this.#store.write(state));
|
|
72
|
+
this.#fsyncDirectory = fsyncDirectory;
|
|
73
|
+
}
|
|
74
|
+
list() {
|
|
75
|
+
this.#assertAvailable();
|
|
76
|
+
return structuredClone(this.#state.receipts);
|
|
77
|
+
}
|
|
78
|
+
latestForPane(paneId) {
|
|
79
|
+
this.#assertAvailable();
|
|
80
|
+
return structuredClone([...this.#state.receipts].reverse().find((receipt) => (receipt.pane.paneId === paneId)) ?? null);
|
|
81
|
+
}
|
|
82
|
+
get(operationId) {
|
|
83
|
+
this.#assertAvailable();
|
|
84
|
+
return structuredClone(this.#state.receipts.find((receipt) => (receipt.operationId === operationId)) ?? null);
|
|
85
|
+
}
|
|
86
|
+
prepare(input) {
|
|
87
|
+
this.#assertAvailable();
|
|
88
|
+
const id = operationId(input);
|
|
89
|
+
const existing = this.#state.receipts.find((receipt) => receipt.operationId === id);
|
|
90
|
+
if (existing)
|
|
91
|
+
return structuredClone(existing);
|
|
92
|
+
const now = Math.trunc(this.#now());
|
|
93
|
+
const receipt = {
|
|
94
|
+
operationId: id,
|
|
95
|
+
agentId: CODEX_AGENT_ID,
|
|
96
|
+
...structuredClone(input),
|
|
97
|
+
phase: 'prepared',
|
|
98
|
+
createdAt: now,
|
|
99
|
+
updatedAt: now,
|
|
100
|
+
};
|
|
101
|
+
if (!validReceipt(receipt))
|
|
102
|
+
throw new Error('Invalid Codex activation recovery receipt');
|
|
103
|
+
const next = {
|
|
104
|
+
version: 1,
|
|
105
|
+
receipts: [...this.#state.receipts, receipt].slice(-MAX_RECEIPTS),
|
|
106
|
+
};
|
|
107
|
+
this.#flush(next);
|
|
108
|
+
this.#state = next;
|
|
109
|
+
return structuredClone(receipt);
|
|
110
|
+
}
|
|
111
|
+
transition(id, expected, phase) {
|
|
112
|
+
this.#assertAvailable();
|
|
113
|
+
const index = this.#state.receipts.findIndex((candidate) => candidate.operationId === id);
|
|
114
|
+
const receipt = this.#state.receipts[index];
|
|
115
|
+
const allowed = Array.isArray(expected) ? expected : [expected];
|
|
116
|
+
if (!receipt || !allowed.includes(receipt.phase)) {
|
|
117
|
+
throw new Error('Codex activation recovery receipt changed');
|
|
118
|
+
}
|
|
119
|
+
if (receipt.phase === phase)
|
|
120
|
+
return structuredClone(receipt);
|
|
121
|
+
const updated = { ...receipt, phase, updatedAt: Math.trunc(this.#now()) };
|
|
122
|
+
const next = {
|
|
123
|
+
version: 1,
|
|
124
|
+
receipts: this.#state.receipts.map((candidate, candidateIndex) => (candidateIndex === index ? updated : candidate)),
|
|
125
|
+
};
|
|
126
|
+
this.#flush(next);
|
|
127
|
+
this.#state = next;
|
|
128
|
+
return structuredClone(updated);
|
|
129
|
+
}
|
|
130
|
+
clearManaged(paneId, sessionId, process) {
|
|
131
|
+
this.#assertAvailable();
|
|
132
|
+
const receipts = this.#state.receipts.filter((receipt) => !(receipt.pane.paneId === paneId && receipt.sessionId === sessionId
|
|
133
|
+
&& (process === undefined || sameProcess(receipt.process, process))));
|
|
134
|
+
if (receipts.length === this.#state.receipts.length)
|
|
135
|
+
return false;
|
|
136
|
+
const next = { version: 1, receipts };
|
|
137
|
+
this.#flush(next);
|
|
138
|
+
this.#state = next;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
#flush(next) {
|
|
142
|
+
this.#write(next);
|
|
143
|
+
this.#fsyncDirectory(path.dirname(this.#store.file));
|
|
144
|
+
}
|
|
145
|
+
#assertAvailable() {
|
|
146
|
+
if (!this.#available)
|
|
147
|
+
throw new Error('Codex activation recovery state is unavailable');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -307,7 +307,16 @@ function itemBase(message, sessionId, id = stableId(message)) {
|
|
|
307
307
|
? { correlationId: message.correlationId } : {}),
|
|
308
308
|
};
|
|
309
309
|
}
|
|
310
|
-
function
|
|
310
|
+
function compactorItemKey(message) {
|
|
311
|
+
return typeof message.turnId === 'string' && message.turnId
|
|
312
|
+
&& typeof message.itemId === 'string' && message.itemId
|
|
313
|
+
? `${message.turnId}\0${message.itemId}` : null;
|
|
314
|
+
}
|
|
315
|
+
function projectDurableMessage(message, sessionId, suppressedCompactorItems) {
|
|
316
|
+
const sourceKey = compactorItemKey(message);
|
|
317
|
+
if (message.type === 'text' && message.role === 'assistant'
|
|
318
|
+
&& sourceKey && suppressedCompactorItems.has(sourceKey))
|
|
319
|
+
return [];
|
|
311
320
|
if (message.type === 'text' && message.role && typeof message.text === 'string') {
|
|
312
321
|
const output = clipConversationText(message.text);
|
|
313
322
|
return [{
|
|
@@ -342,17 +351,15 @@ function projectDurableMessage(message, sessionId) {
|
|
|
342
351
|
const diffProjection = message.tool.diff === undefined
|
|
343
352
|
? undefined : sanitizeToolInputWithMetadata(message.tool.diff);
|
|
344
353
|
const safeDiff = diffProjection?.value;
|
|
345
|
-
const toolProjectionBytes = inputProjection.originalBytes + (diffProjection?.originalBytes ?? 0);
|
|
346
|
-
const toolProjectionTruncated = inputProjection.truncated || diffProjection?.truncated === true;
|
|
347
354
|
const outcomes = ['running', 'success', 'failed', 'declined', 'completed'];
|
|
348
355
|
const outcome = typeof message.tool.outcome === 'string' && outcomes.includes(message.tool.outcome)
|
|
349
356
|
? message.tool.outcome : undefined;
|
|
350
357
|
const items = [{
|
|
351
358
|
...itemBase(message, sessionId, callId), kind: 'tool_call', callId,
|
|
352
359
|
name, ...(input === undefined ? {} : { input }),
|
|
353
|
-
...(
|
|
360
|
+
...(inputProjection.truncated ? {
|
|
354
361
|
status: 'truncated',
|
|
355
|
-
truncation: { reason: 'size_limit', originalBytes:
|
|
362
|
+
truncation: { reason: 'size_limit', originalBytes: inputProjection.originalBytes },
|
|
356
363
|
} : {}),
|
|
357
364
|
extensions: {
|
|
358
365
|
'conversation.tool': {
|
|
@@ -512,6 +519,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
512
519
|
// read at the same byte cutoff as the adapter baseline so later rollout suffixes cannot appear in both
|
|
513
520
|
// the page and the already-buffered native stream.
|
|
514
521
|
const openingSnapshots = new Map();
|
|
522
|
+
const suppressedCompactorItemsBySession = new Map();
|
|
515
523
|
function cloneCheckpoint(checkpoint) {
|
|
516
524
|
return {
|
|
517
525
|
...checkpoint,
|
|
@@ -520,6 +528,19 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
520
528
|
snapshot: { ...checkpoint.snapshot, items: [...checkpoint.snapshot.items] },
|
|
521
529
|
};
|
|
522
530
|
}
|
|
531
|
+
const suppressionKeyOf = (values) => [...values].sort().join('\n');
|
|
532
|
+
async function readCompactorItemKeys(paneId, sessionId) {
|
|
533
|
+
const values = new Set((await app.compactorItemKeys(paneId, sessionId)).filter((value) => (typeof value === 'string' && value.includes('\0'))));
|
|
534
|
+
suppressedCompactorItemsBySession.delete(sessionId);
|
|
535
|
+
suppressedCompactorItemsBySession.set(sessionId, values);
|
|
536
|
+
while (suppressedCompactorItemsBySession.size > MAX_DURABLE_CHECKPOINTS) {
|
|
537
|
+
const oldest = suppressedCompactorItemsBySession.keys().next().value;
|
|
538
|
+
if (typeof oldest !== 'string')
|
|
539
|
+
break;
|
|
540
|
+
suppressedCompactorItemsBySession.delete(oldest);
|
|
541
|
+
}
|
|
542
|
+
return values;
|
|
543
|
+
}
|
|
523
544
|
function rememberCheckpoint(sessionId, checkpoint) {
|
|
524
545
|
durableCheckpoints.delete(sessionId);
|
|
525
546
|
durableCheckpoints.set(sessionId, checkpoint);
|
|
@@ -530,17 +551,20 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
530
551
|
durableCheckpoints.delete(oldest);
|
|
531
552
|
}
|
|
532
553
|
}
|
|
533
|
-
function snapshotFromMessages(session, parsed, file, version, readerGeneration, changedFrom, checkpoint) {
|
|
554
|
+
function snapshotFromMessages(session, parsed, file, version, readerGeneration, changedFrom, checkpoint, suppressedCompactorItems) {
|
|
555
|
+
const suppressionKey = suppressionKeyOf(suppressedCompactorItems);
|
|
534
556
|
const sameReader = readerGeneration === undefined || checkpoint?.readerGeneration === undefined
|
|
535
557
|
|| checkpoint.readerGeneration === readerGeneration;
|
|
536
558
|
const reusable = checkpoint?.file === file && sameReader && changedFrom !== null
|
|
559
|
+
&& checkpoint.suppressionKey === suppressionKey
|
|
537
560
|
&& changedFrom >= 0 && changedFrom <= checkpoint.messageCount
|
|
538
561
|
&& changedFrom <= parsed.length;
|
|
539
562
|
// Build transactionally. A backwards/truncated read must not corrupt the last readable checkpoint
|
|
540
563
|
// before the floor check rejects it.
|
|
541
564
|
const next = reusable && checkpoint ? cloneCheckpoint(checkpoint) : undefined;
|
|
542
565
|
const from = next ? changedFrom : 0;
|
|
543
|
-
const durableFloor = checkpoint?.
|
|
566
|
+
const durableFloor = checkpoint?.suppressionKey === suppressionKey
|
|
567
|
+
? checkpoint.snapshot.items.length : 0;
|
|
544
568
|
const items = next?.snapshot.items ?? [];
|
|
545
569
|
const itemOffsets = next?.itemOffsets ?? [0];
|
|
546
570
|
const historyHashes = next?.historyHashes ?? [];
|
|
@@ -550,14 +574,18 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
550
574
|
historyHashes.length = from;
|
|
551
575
|
let historyHash = historyHashes.at(-1) ?? HISTORY_HASH_SEED;
|
|
552
576
|
for (let index = from; index < parsed.length; index++) {
|
|
553
|
-
const projected = projectDurableMessage(parsed[index], session.sessionId);
|
|
577
|
+
const projected = projectDurableMessage(parsed[index], session.sessionId, suppressedCompactorItems);
|
|
554
578
|
items.push(...projected);
|
|
555
579
|
itemOffsets.push(items.length);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
580
|
+
// A suppressed compactor record is not a visible history revision. Keep the token unchanged until
|
|
581
|
+
// the parser replaces it with the durable compact marker, avoiding an identical-page refresh.
|
|
582
|
+
if (projected.length > 0) {
|
|
583
|
+
historyHash = createHash('sha256')
|
|
584
|
+
.update(historyHash)
|
|
585
|
+
.update('\0')
|
|
586
|
+
.update(JSON.stringify(projected))
|
|
587
|
+
.digest('hex');
|
|
588
|
+
}
|
|
561
589
|
historyHashes.push(historyHash);
|
|
562
590
|
}
|
|
563
591
|
if (items.length < durableFloor) {
|
|
@@ -567,6 +595,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
567
595
|
file,
|
|
568
596
|
...(version === undefined ? {} : { version }),
|
|
569
597
|
...(readerGeneration === undefined ? {} : { readerGeneration }),
|
|
598
|
+
suppressionKey,
|
|
570
599
|
messageCount: parsed.length,
|
|
571
600
|
itemOffsets,
|
|
572
601
|
historyHashes,
|
|
@@ -578,7 +607,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
578
607
|
},
|
|
579
608
|
};
|
|
580
609
|
}
|
|
581
|
-
async function readDurableSnapshot(session) {
|
|
610
|
+
async function readDurableSnapshot(session, suppressionProvider) {
|
|
582
611
|
const file = await findRollout(sessionsRoot, session.sessionId);
|
|
583
612
|
const read = file && reader.readSnapshot
|
|
584
613
|
? await reader.readSnapshot(file, createCodexTranscriptParser)
|
|
@@ -588,8 +617,13 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
588
617
|
changedFrom: 0,
|
|
589
618
|
generation: undefined,
|
|
590
619
|
};
|
|
620
|
+
const suppressedCompactorItems = suppressionProvider
|
|
621
|
+
? await suppressionProvider()
|
|
622
|
+
: suppressedCompactorItemsBySession.get(session.sessionId) ?? new Set();
|
|
623
|
+
const suppressionKey = suppressionKeyOf(suppressedCompactorItems);
|
|
591
624
|
const known = durableCheckpoints.get(session.sessionId);
|
|
592
|
-
if (known && known.file === file &&
|
|
625
|
+
if (known && known.file === file && known.suppressionKey === suppressionKey
|
|
626
|
+
&& read.version !== undefined && known.version === read.version) {
|
|
593
627
|
rememberCheckpoint(session.sessionId, known);
|
|
594
628
|
return known.snapshot;
|
|
595
629
|
}
|
|
@@ -603,7 +637,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
603
637
|
rememberCheckpoint(session.sessionId, known);
|
|
604
638
|
return known.snapshot;
|
|
605
639
|
}
|
|
606
|
-
const checkpoint = snapshotFromMessages(session, read.messages, file, read.version, read.generation, read.changedFrom, known);
|
|
640
|
+
const checkpoint = snapshotFromMessages(session, read.messages, file, read.version, read.generation, read.changedFrom, known, suppressedCompactorItems);
|
|
607
641
|
rememberCheckpoint(session.sessionId, checkpoint);
|
|
608
642
|
return checkpoint.snapshot;
|
|
609
643
|
}
|
|
@@ -616,7 +650,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
616
650
|
? { file, endExclusive }
|
|
617
651
|
: { file: null, endExclusive: 0 };
|
|
618
652
|
}
|
|
619
|
-
async function readDurablePrefix(session, cutoff) {
|
|
653
|
+
async function readDurablePrefix(session, cutoff, suppressionProvider) {
|
|
620
654
|
const known = durableCheckpoints.get(session.sessionId);
|
|
621
655
|
const working = known ? cloneCheckpoint(known) : undefined;
|
|
622
656
|
const read = cutoff.file && reader.readPrefixSnapshot
|
|
@@ -628,10 +662,15 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
628
662
|
changedFrom: 0,
|
|
629
663
|
generation: undefined,
|
|
630
664
|
};
|
|
665
|
+
const suppressedCompactorItems = suppressionProvider
|
|
666
|
+
? await suppressionProvider()
|
|
667
|
+
: suppressedCompactorItemsBySession.get(session.sessionId) ?? new Set();
|
|
668
|
+
const suppressionKey = suppressionKeyOf(suppressedCompactorItems);
|
|
631
669
|
const checkpoint = working && working.file === cutoff.file
|
|
670
|
+
&& working.suppressionKey === suppressionKey
|
|
632
671
|
&& read.version !== undefined && working.version === read.version
|
|
633
672
|
? working
|
|
634
|
-
: snapshotFromMessages(session, read.messages, cutoff.file, read.version, read.generation, read.changedFrom, working);
|
|
673
|
+
: snapshotFromMessages(session, read.messages, cutoff.file, read.version, read.generation, read.changedFrom, working, suppressedCompactorItems);
|
|
635
674
|
return {
|
|
636
675
|
checkpoint,
|
|
637
676
|
snapshot: { ...checkpoint.snapshot, items: [...checkpoint.snapshot.items] },
|
|
@@ -751,7 +790,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
751
790
|
: now - pending.settledAt >= durableSettleTimeoutMs);
|
|
752
791
|
let page;
|
|
753
792
|
try {
|
|
754
|
-
page = await readDurableSnapshot({ agentId: 'codex', sessionId });
|
|
793
|
+
page = await readDurableSnapshot({ agentId: 'codex', sessionId }, () => readCompactorItemKeys(run.ref.paneId, sessionId));
|
|
755
794
|
}
|
|
756
795
|
catch (error) {
|
|
757
796
|
// A writer can transiently rotate or replace the rollout. Periodic reconciliation retries
|
|
@@ -1022,7 +1061,7 @@ export function createCodexConversationAdapter({ app, sessionsRoot = sessionsDir
|
|
|
1022
1061
|
// Capture the append-only byte boundary only after the observer owns the native suffix. Anything
|
|
1023
1062
|
// appended later belongs to that buffered suffix or the first post-open durable reconciliation.
|
|
1024
1063
|
const cutoff = await captureDurableCutoff(sessionId);
|
|
1025
|
-
const openingRead = await readDurablePrefix({ agentId: 'codex', sessionId }, cutoff);
|
|
1064
|
+
const openingRead = await readDurablePrefix({ agentId: 'codex', sessionId }, cutoff, () => readCompactorItemKeys(run.ref.paneId, sessionId));
|
|
1026
1065
|
const baseline = openingRead.snapshot;
|
|
1027
1066
|
live.durableToken = baseline.sourceHistoryToken;
|
|
1028
1067
|
live.durableItems = [...baseline.items];
|