remote-codex 0.11.39 → 0.11.41
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/apps/supervisor-api/dist/index.js +148 -10
- package/apps/supervisor-web/dist/assets/{index-CUhMNGXI.js → index-B-qj7e8G.js} +4 -4
- package/apps/supervisor-web/dist/index.html +1 -1
- package/package.json +1 -1
- package/packages/claude/src/runtimeAdapter.test.ts +235 -1
- package/packages/claude/src/runtimeAdapter.ts +214 -12
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest" />
|
|
12
12
|
<title>Remote Codex</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-B-qj7e8G.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-Dfg_6BLf.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-CuR8GHb0.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVQUpZ8C.js">
|
package/package.json
CHANGED
|
@@ -22,6 +22,11 @@ function wait(ms = 0) {
|
|
|
22
22
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function uuidV7At(timestamp = Date.now()) {
|
|
26
|
+
const timestampHex = Math.trunc(timestamp).toString(16).padStart(12, '0');
|
|
27
|
+
return `${timestampHex.slice(0, 8)}-${timestampHex.slice(8)}-7000-8000-000000000001`;
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
class FakeQuery implements Query {
|
|
26
31
|
interrupted = false;
|
|
27
32
|
closed = false;
|
|
@@ -541,6 +546,7 @@ describe('ClaudeRuntimeAdapter', () => {
|
|
|
541
546
|
|
|
542
547
|
it('reconciles active multimodal transcript turns back to the live runtime turn id', async () => {
|
|
543
548
|
let queryMessages: SDKMessage[] = [systemInit()];
|
|
549
|
+
const currentMessageUuid = uuidV7At();
|
|
544
550
|
const adapter = new ClaudeRuntimeAdapter({
|
|
545
551
|
home: '/tmp/claude-home',
|
|
546
552
|
command: 'claude',
|
|
@@ -556,7 +562,7 @@ describe('ClaudeRuntimeAdapter', () => {
|
|
|
556
562
|
getSessionMessages: (async () => [
|
|
557
563
|
{
|
|
558
564
|
type: 'user',
|
|
559
|
-
uuid:
|
|
565
|
+
uuid: currentMessageUuid,
|
|
560
566
|
session_id: 'claude-session-1',
|
|
561
567
|
message: {
|
|
562
568
|
role: 'user',
|
|
@@ -609,6 +615,99 @@ describe('ClaudeRuntimeAdapter', () => {
|
|
|
609
615
|
expect(reloadedSession.turns[0]?.providerTurnId).toBe(started.providerTurnId);
|
|
610
616
|
});
|
|
611
617
|
|
|
618
|
+
it('does not reconcile an old matching user-only turn as the active turn', async () => {
|
|
619
|
+
const historicalMessageUuid = uuidV7At(Date.now() - 10 * 60_000);
|
|
620
|
+
const adapter = new ClaudeRuntimeAdapter({
|
|
621
|
+
home: '/tmp/claude-home',
|
|
622
|
+
command: 'claude',
|
|
623
|
+
query: (() => new FakeQuery([systemInit()], { holdOpen: true })) as any,
|
|
624
|
+
listSessions: (async () => [] satisfies SDKSessionInfo[]) as any,
|
|
625
|
+
getSessionInfo: (async () => ({
|
|
626
|
+
sessionId: 'claude-session-1',
|
|
627
|
+
summary: 'Existing session',
|
|
628
|
+
cwd: '/tmp/workspace',
|
|
629
|
+
})) as any,
|
|
630
|
+
getSessionMessages: (async () => [{
|
|
631
|
+
type: 'user',
|
|
632
|
+
uuid: historicalMessageUuid,
|
|
633
|
+
session_id: 'claude-session-1',
|
|
634
|
+
message: { role: 'user', content: 'Repeat this prompt' },
|
|
635
|
+
parent_tool_use_id: null,
|
|
636
|
+
}] satisfies SessionMessage[]) as any,
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
const started = await adapter.startTurn({
|
|
640
|
+
providerSessionId: 'claude-session-1',
|
|
641
|
+
prompt: 'Repeat this prompt',
|
|
642
|
+
model: 'sonnet',
|
|
643
|
+
workspacePath: '/tmp/workspace',
|
|
644
|
+
});
|
|
645
|
+
const session = await adapter.readSession('claude-session-1');
|
|
646
|
+
|
|
647
|
+
expect(session.turns).toHaveLength(1);
|
|
648
|
+
expect(session.turns[0]).toMatchObject({
|
|
649
|
+
providerTurnId: `claude-turn-${historicalMessageUuid}`,
|
|
650
|
+
status: 'completed',
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
await adapter.interruptTurn({
|
|
654
|
+
providerSessionId: 'claude-session-1',
|
|
655
|
+
providerTurnId: started.providerTurnId,
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('reconciles only the current prompt when an older user-only turn exists', async () => {
|
|
660
|
+
const historicalMessageUuid = uuidV7At(Date.now() - 10 * 60_000);
|
|
661
|
+
const currentMessageUuid = uuidV7At();
|
|
662
|
+
const adapter = new ClaudeRuntimeAdapter({
|
|
663
|
+
home: '/tmp/claude-home',
|
|
664
|
+
command: 'claude',
|
|
665
|
+
query: (() => new FakeQuery([systemInit()], { holdOpen: true })) as any,
|
|
666
|
+
listSessions: (async () => [] satisfies SDKSessionInfo[]) as any,
|
|
667
|
+
getSessionInfo: (async () => ({
|
|
668
|
+
sessionId: 'claude-session-1',
|
|
669
|
+
summary: 'Existing session',
|
|
670
|
+
cwd: '/tmp/workspace',
|
|
671
|
+
})) as any,
|
|
672
|
+
getSessionMessages: (async () => [
|
|
673
|
+
{
|
|
674
|
+
type: 'user',
|
|
675
|
+
uuid: historicalMessageUuid,
|
|
676
|
+
session_id: 'claude-session-1',
|
|
677
|
+
message: { role: 'user', content: 'An older prompt' },
|
|
678
|
+
parent_tool_use_id: null,
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
type: 'user',
|
|
682
|
+
uuid: currentMessageUuid,
|
|
683
|
+
session_id: 'claude-session-1',
|
|
684
|
+
message: { role: 'user', content: 'The current prompt' },
|
|
685
|
+
parent_tool_use_id: null,
|
|
686
|
+
},
|
|
687
|
+
] satisfies SessionMessage[]) as any,
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const started = await adapter.startTurn({
|
|
691
|
+
providerSessionId: 'claude-session-1',
|
|
692
|
+
prompt: 'The current prompt',
|
|
693
|
+
model: 'sonnet',
|
|
694
|
+
workspacePath: '/tmp/workspace',
|
|
695
|
+
});
|
|
696
|
+
const session = await adapter.readSession('claude-session-1');
|
|
697
|
+
|
|
698
|
+
expect(session.turns.map((turn) => turn.providerTurnId)).toEqual([
|
|
699
|
+
`claude-turn-${historicalMessageUuid}`,
|
|
700
|
+
started.providerTurnId,
|
|
701
|
+
]);
|
|
702
|
+
expect(session.turns[0]?.items[0]).toMatchObject({ text: 'An older prompt' });
|
|
703
|
+
expect(session.turns[1]).toMatchObject({ status: 'inProgress' });
|
|
704
|
+
|
|
705
|
+
await adapter.interruptTurn({
|
|
706
|
+
providerSessionId: 'claude-session-1',
|
|
707
|
+
providerTurnId: started.providerTurnId,
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
612
711
|
it('keeps image blocks visible when reading Claude session history', async () => {
|
|
613
712
|
const workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-history-image-'));
|
|
614
713
|
const adapter = new ClaudeRuntimeAdapter({
|
|
@@ -2884,4 +2983,139 @@ describe('ClaudeRuntimeAdapter', () => {
|
|
|
2884
2983
|
]),
|
|
2885
2984
|
);
|
|
2886
2985
|
});
|
|
2986
|
+
|
|
2987
|
+
it('keeps live Claude compaction in the active turn and hides its summary', async () => {
|
|
2988
|
+
const adapter = makeAdapter((prompt) => prompt === hiddenInitPrompt()
|
|
2989
|
+
? [systemInit(), result()]
|
|
2990
|
+
: [
|
|
2991
|
+
systemInit(),
|
|
2992
|
+
{
|
|
2993
|
+
type: 'assistant',
|
|
2994
|
+
uuid: 'assistant-before-compact',
|
|
2995
|
+
session_id: 'claude-session-1',
|
|
2996
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'Before.' }] },
|
|
2997
|
+
parent_tool_use_id: null,
|
|
2998
|
+
},
|
|
2999
|
+
{
|
|
3000
|
+
type: 'system',
|
|
3001
|
+
subtype: 'status',
|
|
3002
|
+
status: 'compacting',
|
|
3003
|
+
uuid: 'compact-status',
|
|
3004
|
+
session_id: 'claude-session-1',
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
type: 'user',
|
|
3008
|
+
uuid: 'compact-summary',
|
|
3009
|
+
session_id: 'claude-session-1',
|
|
3010
|
+
isCompactSummary: true,
|
|
3011
|
+
message: { role: 'user', content: 'HIDDEN LIVE SUMMARY' },
|
|
3012
|
+
parent_tool_use_id: null,
|
|
3013
|
+
},
|
|
3014
|
+
{
|
|
3015
|
+
type: 'system',
|
|
3016
|
+
subtype: 'compact_boundary',
|
|
3017
|
+
compact_metadata: { trigger: 'auto', pre_tokens: 180_000 },
|
|
3018
|
+
uuid: 'compact-boundary',
|
|
3019
|
+
session_id: 'claude-session-1',
|
|
3020
|
+
},
|
|
3021
|
+
{
|
|
3022
|
+
type: 'assistant',
|
|
3023
|
+
uuid: 'assistant-after-compact',
|
|
3024
|
+
session_id: 'claude-session-1',
|
|
3025
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'After.' }] },
|
|
3026
|
+
parent_tool_use_id: null,
|
|
3027
|
+
},
|
|
3028
|
+
result(),
|
|
3029
|
+
]);
|
|
3030
|
+
const events: AgentRuntimeEvent[] = [];
|
|
3031
|
+
adapter.on('event', (event) => events.push(event));
|
|
3032
|
+
|
|
3033
|
+
await adapter.startTurn({
|
|
3034
|
+
providerSessionId: 'claude-session-1',
|
|
3035
|
+
displayTurnId: 'turn-compact',
|
|
3036
|
+
prompt: 'Continue working',
|
|
3037
|
+
} as any);
|
|
3038
|
+
await wait();
|
|
3039
|
+
|
|
3040
|
+
const completed = events.findLast((event) => event.type === 'turn.completed');
|
|
3041
|
+
const items = completed?.type === 'turn.completed' ? completed.turn.items : [];
|
|
3042
|
+
expect(completed).toMatchObject({
|
|
3043
|
+
type: 'turn.completed',
|
|
3044
|
+
turn: { providerTurnId: 'turn-compact', status: 'completed' },
|
|
3045
|
+
});
|
|
3046
|
+
expect(items.filter((item) => item.kind === 'contextCompaction')).toEqual([
|
|
3047
|
+
expect.objectContaining({
|
|
3048
|
+
text: 'Context compacted',
|
|
3049
|
+
detailText: null,
|
|
3050
|
+
status: 'completed',
|
|
3051
|
+
}),
|
|
3052
|
+
]);
|
|
3053
|
+
expect(items).toEqual(expect.arrayContaining([
|
|
3054
|
+
expect.objectContaining({ kind: 'agentMessage', text: 'Before.' }),
|
|
3055
|
+
expect.objectContaining({ kind: 'agentMessage', text: 'After.' }),
|
|
3056
|
+
]));
|
|
3057
|
+
expect(JSON.stringify(items)).not.toContain('HIDDEN LIVE SUMMARY');
|
|
3058
|
+
});
|
|
3059
|
+
|
|
3060
|
+
it('does not create a historical turn for Claude compact summaries', async () => {
|
|
3061
|
+
const adapter = new ClaudeRuntimeAdapter({
|
|
3062
|
+
home: '/tmp/claude-home',
|
|
3063
|
+
query: (() => new FakeQuery([])) as any,
|
|
3064
|
+
getSessionInfo: (async () => ({
|
|
3065
|
+
sessionId: 'claude-session-1',
|
|
3066
|
+
summary: 'Existing session',
|
|
3067
|
+
lastModified: 1_772_000_000_000,
|
|
3068
|
+
createdAt: 1_771_000_000_000,
|
|
3069
|
+
cwd: '/tmp/workspace',
|
|
3070
|
+
firstPrompt: 'Check training',
|
|
3071
|
+
})) as any,
|
|
3072
|
+
getSessionMessages: (async () => [
|
|
3073
|
+
{
|
|
3074
|
+
type: 'user',
|
|
3075
|
+
uuid: uuidV7At(1_771_000_000_000),
|
|
3076
|
+
session_id: 'claude-session-1',
|
|
3077
|
+
message: { role: 'user', content: 'Check training' },
|
|
3078
|
+
parent_tool_use_id: null,
|
|
3079
|
+
},
|
|
3080
|
+
{
|
|
3081
|
+
type: 'assistant',
|
|
3082
|
+
uuid: 'assistant-before-compact',
|
|
3083
|
+
session_id: 'claude-session-1',
|
|
3084
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'Before.' }] },
|
|
3085
|
+
parent_tool_use_id: null,
|
|
3086
|
+
},
|
|
3087
|
+
{
|
|
3088
|
+
type: 'user',
|
|
3089
|
+
uuid: 'compact-summary',
|
|
3090
|
+
session_id: 'claude-session-1',
|
|
3091
|
+
message: {
|
|
3092
|
+
role: 'user',
|
|
3093
|
+
content: 'This session is being continued from a previous conversation that ran out of context. HIDDEN HISTORICAL SUMMARY',
|
|
3094
|
+
},
|
|
3095
|
+
parent_tool_use_id: null,
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
type: 'assistant',
|
|
3099
|
+
uuid: 'assistant-after-compact',
|
|
3100
|
+
session_id: 'claude-session-1',
|
|
3101
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'After.' }] },
|
|
3102
|
+
parent_tool_use_id: null,
|
|
3103
|
+
},
|
|
3104
|
+
] satisfies SessionMessage[]) as any,
|
|
3105
|
+
});
|
|
3106
|
+
|
|
3107
|
+
const session = await adapter.readSession('claude-session-1');
|
|
3108
|
+
expect(session.turns).toHaveLength(1);
|
|
3109
|
+
expect(session.turns[0]?.items).toEqual(expect.arrayContaining([
|
|
3110
|
+
expect.objectContaining({ kind: 'userMessage', text: 'Check training' }),
|
|
3111
|
+
expect.objectContaining({ kind: 'agentMessage', text: 'Before.' }),
|
|
3112
|
+
expect.objectContaining({
|
|
3113
|
+
kind: 'contextCompaction',
|
|
3114
|
+
text: 'Context compacted',
|
|
3115
|
+
detailText: null,
|
|
3116
|
+
}),
|
|
3117
|
+
expect.objectContaining({ kind: 'agentMessage', text: 'After.' }),
|
|
3118
|
+
]));
|
|
3119
|
+
expect(JSON.stringify(session.turns)).not.toContain('HIDDEN HISTORICAL SUMMARY');
|
|
3120
|
+
});
|
|
2887
3121
|
});
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
isHiddenContinuationMessage,
|
|
47
47
|
isHiddenInitMessage,
|
|
48
48
|
limitErrorFromHistoryItems,
|
|
49
|
+
messageContentText,
|
|
49
50
|
partialReasoningDelta,
|
|
50
51
|
partialTextDelta,
|
|
51
52
|
resultForToolUse,
|
|
@@ -133,6 +134,16 @@ interface SDKMessage {
|
|
|
133
134
|
errors?: string[];
|
|
134
135
|
stop_reason?: string;
|
|
135
136
|
rate_limit_info?: SDKRateLimitInfo;
|
|
137
|
+
compact_metadata?: {
|
|
138
|
+
trigger?: 'manual' | 'auto';
|
|
139
|
+
pre_tokens?: number;
|
|
140
|
+
post_tokens?: number;
|
|
141
|
+
};
|
|
142
|
+
compact_result?: 'success' | 'failed';
|
|
143
|
+
compact_error?: string;
|
|
144
|
+
status?: 'compacting' | 'requesting' | null;
|
|
145
|
+
isCompactSummary?: boolean;
|
|
146
|
+
is_compact_summary?: boolean;
|
|
136
147
|
}
|
|
137
148
|
interface SDKRateLimitInfo {
|
|
138
149
|
status: 'allowed' | 'allowed_warning' | 'rejected';
|
|
@@ -244,9 +255,88 @@ interface ActiveClaudeTurn {
|
|
|
244
255
|
assistantUsage: ClaudeTokenUsageBreakdown | null;
|
|
245
256
|
resultUsage: ClaudeTokenUsageBreakdown | null;
|
|
246
257
|
modelContextWindow: number | null;
|
|
258
|
+
currentCompactionItemId: string | null;
|
|
247
259
|
}
|
|
248
260
|
|
|
249
261
|
const promptPhotoTokenPattern = /\[PHOTO\s+([^\]]+)\]/g;
|
|
262
|
+
const claudeCompactSummaryPrefix =
|
|
263
|
+
'This session is being continued from a previous conversation that ran out of context.';
|
|
264
|
+
const activeTranscriptMatchWindowMs = 120_000;
|
|
265
|
+
|
|
266
|
+
function isClaudeCompactBoundaryMessage(message: SDKMessage | SessionMessage) {
|
|
267
|
+
if (message.type !== 'system') {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
if (message.subtype === 'compact_boundary' || message.compact_metadata) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return isRecord(message.message)
|
|
274
|
+
&& (message.message.subtype === 'compact_boundary' || isRecord(message.message.compact_metadata));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function isClaudeCompactSummaryMessage(message: SDKMessage | SessionMessage) {
|
|
278
|
+
if (message.type !== 'user' || message.parent_tool_use_id) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
const payload = isRecord(message.message) ? message.message : null;
|
|
282
|
+
if (
|
|
283
|
+
message.isCompactSummary
|
|
284
|
+
|| message.is_compact_summary
|
|
285
|
+
|| payload?.isCompactSummary === true
|
|
286
|
+
|| payload?.is_compact_summary === true
|
|
287
|
+
) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return messageContentText(message.message).trim().startsWith(claudeCompactSummaryPrefix);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function claudeContextCompactionItem(
|
|
294
|
+
id: string,
|
|
295
|
+
status: 'running' | 'completed' | 'failed',
|
|
296
|
+
error: string | null = null,
|
|
297
|
+
): AgentHistoryItem {
|
|
298
|
+
const completed = status === 'completed';
|
|
299
|
+
const failed = status === 'failed';
|
|
300
|
+
const text = failed
|
|
301
|
+
? 'Context compaction failed'
|
|
302
|
+
: completed
|
|
303
|
+
? 'Context compacted'
|
|
304
|
+
: 'Compacting context';
|
|
305
|
+
return {
|
|
306
|
+
id,
|
|
307
|
+
kind: 'contextCompaction',
|
|
308
|
+
text,
|
|
309
|
+
previewText: text,
|
|
310
|
+
detailText: failed ? error : null,
|
|
311
|
+
status,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function normalizePromptForTurnReconciliation(value: string) {
|
|
316
|
+
return value
|
|
317
|
+
// Claude history can rewrite or omit an image marker. The surrounding
|
|
318
|
+
// text and UUIDv7 timestamp remain stable reconciliation identifiers.
|
|
319
|
+
.replace(/\[PHOTO\s+[^\]]+\]/g, ' ')
|
|
320
|
+
.replace(/\s+/g, ' ')
|
|
321
|
+
.trim();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function userPromptForTurn(turn: AgentTurn) {
|
|
325
|
+
return turn.items.find((item) => item.kind === 'userMessage')?.text ?? null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function isRecentActiveTranscriptTurn(turn: AgentTurn, activeStartedAt: string) {
|
|
329
|
+
if (!turn.startedAt) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
const transcriptStartedAt = Date.parse(turn.startedAt);
|
|
333
|
+
const activeStartedAtMs = Date.parse(activeStartedAt);
|
|
334
|
+
return (
|
|
335
|
+
!Number.isFinite(transcriptStartedAt)
|
|
336
|
+
|| !Number.isFinite(activeStartedAtMs)
|
|
337
|
+
|| Math.abs(transcriptStartedAt - activeStartedAtMs) <= activeTranscriptMatchWindowMs
|
|
338
|
+
);
|
|
339
|
+
}
|
|
250
340
|
|
|
251
341
|
function mimeTypeForImagePath(filePath: string) {
|
|
252
342
|
const extension = path.extname(filePath).toLowerCase();
|
|
@@ -1663,6 +1753,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1663
1753
|
assistantUsage: null,
|
|
1664
1754
|
resultUsage: null,
|
|
1665
1755
|
modelContextWindow: null,
|
|
1756
|
+
currentCompactionItemId: null,
|
|
1666
1757
|
};
|
|
1667
1758
|
this.knownSessionIds.add(input.providerSessionId);
|
|
1668
1759
|
let sessionPrompts = this.liveUserPrompts.get(input.providerSessionId);
|
|
@@ -1766,12 +1857,17 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1766
1857
|
const activeItems = [...activeTurn.itemOrder]
|
|
1767
1858
|
.map((itemId) => activeTurn.items.get(itemId))
|
|
1768
1859
|
.filter((item): item is AgentHistoryItem => Boolean(item));
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1860
|
+
const normalizedPrompt = normalizePromptForTurnReconciliation(prompt);
|
|
1861
|
+
const transcriptTurnIndex = turns.findLastIndex((turn) => {
|
|
1862
|
+
const transcriptPrompt = userPromptForTurn(turn);
|
|
1863
|
+
return (
|
|
1864
|
+
turn.status === 'completed'
|
|
1865
|
+
&& transcriptPrompt !== null
|
|
1866
|
+
&& normalizePromptForTurnReconciliation(transcriptPrompt) === normalizedPrompt
|
|
1867
|
+
&& !turn.items.some((item) => item.kind === 'agentMessage')
|
|
1868
|
+
&& isRecentActiveTranscriptTurn(turn, activeTurn.startedAt)
|
|
1869
|
+
);
|
|
1870
|
+
});
|
|
1775
1871
|
|
|
1776
1872
|
if (transcriptTurnIndex < 0) {
|
|
1777
1873
|
return turns;
|
|
@@ -1784,6 +1880,14 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1784
1880
|
aliases = new Map();
|
|
1785
1881
|
this.historicalTurnIdAliases.set(providerSessionId, aliases);
|
|
1786
1882
|
}
|
|
1883
|
+
for (const [historicalTurnId, displayTurnId] of aliases) {
|
|
1884
|
+
if (
|
|
1885
|
+
displayTurnId === activeTurn.providerTurnId
|
|
1886
|
+
&& historicalTurnId !== transcriptTurn.providerTurnId
|
|
1887
|
+
) {
|
|
1888
|
+
aliases.delete(historicalTurnId);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1787
1891
|
aliases.set(transcriptTurn.providerTurnId, activeTurn.providerTurnId);
|
|
1788
1892
|
}
|
|
1789
1893
|
|
|
@@ -1812,9 +1916,20 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1812
1916
|
if (!aliases || aliases.size === 0) {
|
|
1813
1917
|
return turns;
|
|
1814
1918
|
}
|
|
1919
|
+
const rawTurnIds = new Set(turns.map((turn) => turn.providerTurnId));
|
|
1920
|
+
const usedTurnIds = new Set<string>();
|
|
1815
1921
|
return turns.map((turn) => {
|
|
1816
1922
|
const providerTurnId = aliases.get(turn.providerTurnId);
|
|
1817
|
-
|
|
1923
|
+
if (
|
|
1924
|
+
!providerTurnId
|
|
1925
|
+
|| usedTurnIds.has(providerTurnId)
|
|
1926
|
+
|| (providerTurnId !== turn.providerTurnId && rawTurnIds.has(providerTurnId))
|
|
1927
|
+
) {
|
|
1928
|
+
usedTurnIds.add(turn.providerTurnId);
|
|
1929
|
+
return turn;
|
|
1930
|
+
}
|
|
1931
|
+
usedTurnIds.add(providerTurnId);
|
|
1932
|
+
return { ...turn, providerTurnId };
|
|
1818
1933
|
});
|
|
1819
1934
|
}
|
|
1820
1935
|
|
|
@@ -1925,6 +2040,62 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1925
2040
|
return;
|
|
1926
2041
|
}
|
|
1927
2042
|
|
|
2043
|
+
if (message.type === 'system' && message.subtype === 'status') {
|
|
2044
|
+
if (message.status === 'compacting') {
|
|
2045
|
+
const existing = state.currentCompactionItemId
|
|
2046
|
+
? state.items.get(state.currentCompactionItemId)
|
|
2047
|
+
: null;
|
|
2048
|
+
const itemId = existing?.status === 'running'
|
|
2049
|
+
? existing.id
|
|
2050
|
+
: `claude-compaction-${messageUuid(message, randomUUID())}`;
|
|
2051
|
+
state.currentCompactionItemId = itemId;
|
|
2052
|
+
const item = withHistoryItemCreatedAt(
|
|
2053
|
+
claudeContextCompactionItem(itemId, 'running'),
|
|
2054
|
+
messageCreatedAt,
|
|
2055
|
+
);
|
|
2056
|
+
addOrUpdateItem(state, item);
|
|
2057
|
+
this.emitItem(state, item, 'item.started', { force: Boolean(existing) });
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (message.compact_result) {
|
|
2061
|
+
const itemId = state.currentCompactionItemId
|
|
2062
|
+
?? `claude-compaction-${messageUuid(message, randomUUID())}`;
|
|
2063
|
+
const status = message.compact_result === 'success' ? 'completed' : 'failed';
|
|
2064
|
+
const item = withHistoryItemCreatedAt(
|
|
2065
|
+
claudeContextCompactionItem(itemId, status, message.compact_error ?? null),
|
|
2066
|
+
messageCreatedAt,
|
|
2067
|
+
);
|
|
2068
|
+
addOrUpdateItem(state, item);
|
|
2069
|
+
this.emitItem(state, item, 'item.completed');
|
|
2070
|
+
state.currentCompactionItemId = status === 'failed' ? null : itemId;
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
if (isClaudeCompactBoundaryMessage(message)) {
|
|
2077
|
+
const itemId = state.currentCompactionItemId
|
|
2078
|
+
?? `claude-compaction-${messageUuid(message, randomUUID())}`;
|
|
2079
|
+
const previous = state.items.get(itemId);
|
|
2080
|
+
const item = withHistoryItemCreatedAt(
|
|
2081
|
+
claudeContextCompactionItem(itemId, 'completed'),
|
|
2082
|
+
previous?.createdAt ?? messageCreatedAt,
|
|
2083
|
+
);
|
|
2084
|
+
addOrUpdateItem(state, item);
|
|
2085
|
+
if (!previous) {
|
|
2086
|
+
this.emitItem(state, item, 'item.started');
|
|
2087
|
+
}
|
|
2088
|
+
if (previous?.status !== 'completed') {
|
|
2089
|
+
this.emitItem(state, item, 'item.completed');
|
|
2090
|
+
}
|
|
2091
|
+
state.currentCompactionItemId = null;
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
if (isClaudeCompactSummaryMessage(message)) {
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
1928
2099
|
if (message.type === 'stream_event') {
|
|
1929
2100
|
const nextStreamMessageId = streamMessageId(message.event);
|
|
1930
2101
|
if (nextStreamMessageId) {
|
|
@@ -2357,6 +2528,8 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
2357
2528
|
itemsById: Map<string, AgentHistoryItem>;
|
|
2358
2529
|
} | null = null;
|
|
2359
2530
|
let skippingHiddenInit = false;
|
|
2531
|
+
let pendingCompactSummaryItemId: string | null = null;
|
|
2532
|
+
let pendingCompactBoundaryItemId: string | null = null;
|
|
2360
2533
|
const suppressedToolUseIds = new Set<string>();
|
|
2361
2534
|
|
|
2362
2535
|
const upsertCurrentItem = (item: AgentHistoryItem) => {
|
|
@@ -2390,6 +2563,20 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
2390
2563
|
};
|
|
2391
2564
|
|
|
2392
2565
|
for (const message of messages) {
|
|
2566
|
+
if (isClaudeCompactBoundaryMessage(message)) {
|
|
2567
|
+
const itemId: string = pendingCompactSummaryItemId
|
|
2568
|
+
?? `claude-compaction-${messageUuid(message, randomUUID())}`;
|
|
2569
|
+
pendingCompactSummaryItemId = null;
|
|
2570
|
+
pendingCompactBoundaryItemId = itemId;
|
|
2571
|
+
upsertCurrentItem(
|
|
2572
|
+
withHistoryItemCreatedAt(
|
|
2573
|
+
claudeContextCompactionItem(itemId, 'completed'),
|
|
2574
|
+
sessionMessageTimestamp(message) ?? current?.startedAt,
|
|
2575
|
+
),
|
|
2576
|
+
);
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2393
2580
|
if (message.type === 'user') {
|
|
2394
2581
|
const taskNotification = taskNotificationToolResult(message.message);
|
|
2395
2582
|
if (taskNotification) {
|
|
@@ -2436,6 +2623,21 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
2436
2623
|
}
|
|
2437
2624
|
|
|
2438
2625
|
if (message.type === 'user' && !message.parent_tool_use_id) {
|
|
2626
|
+
if (isClaudeCompactSummaryMessage(message)) {
|
|
2627
|
+
const itemId: string = pendingCompactBoundaryItemId
|
|
2628
|
+
?? `claude-compaction-${messageUuid(message, randomUUID())}`;
|
|
2629
|
+
if (!pendingCompactBoundaryItemId) {
|
|
2630
|
+
pendingCompactSummaryItemId = itemId;
|
|
2631
|
+
}
|
|
2632
|
+
pendingCompactBoundaryItemId = null;
|
|
2633
|
+
upsertCurrentItem(
|
|
2634
|
+
withHistoryItemCreatedAt(
|
|
2635
|
+
claudeContextCompactionItem(itemId, 'completed'),
|
|
2636
|
+
sessionMessageTimestamp(message) ?? current?.startedAt,
|
|
2637
|
+
),
|
|
2638
|
+
);
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2439
2641
|
if (isHiddenInitMessage(message.message)) {
|
|
2440
2642
|
skippingHiddenInit = true;
|
|
2441
2643
|
current = null;
|
|
@@ -2455,19 +2657,19 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
2455
2657
|
items: current.items,
|
|
2456
2658
|
}));
|
|
2457
2659
|
}
|
|
2458
|
-
const
|
|
2459
|
-
const userStartedAt = isoFromUuidV7(
|
|
2660
|
+
const userMessageUuid = message.uuid ?? randomUUID();
|
|
2661
|
+
const userStartedAt = isoFromUuidV7(userMessageUuid);
|
|
2460
2662
|
const userItem = await this.userMessageToHistoryItem(
|
|
2461
|
-
|
|
2663
|
+
userMessageUuid,
|
|
2462
2664
|
message.message,
|
|
2463
2665
|
context,
|
|
2464
2666
|
);
|
|
2465
2667
|
const stampedUserItem = withHistoryItemCreatedAt(userItem, userStartedAt);
|
|
2466
2668
|
current = {
|
|
2467
|
-
providerTurnId: `claude-turn-${
|
|
2669
|
+
providerTurnId: `claude-turn-${userMessageUuid}`,
|
|
2468
2670
|
startedAt: userStartedAt,
|
|
2469
2671
|
items: [stampedUserItem],
|
|
2470
|
-
itemsById: new Map([[
|
|
2672
|
+
itemsById: new Map([[userMessageUuid, stampedUserItem]]),
|
|
2471
2673
|
};
|
|
2472
2674
|
continue;
|
|
2473
2675
|
}
|