praxis-agent 0.55.1 → 0.55.3
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/application/session-service.d.ts +0 -7
- package/dist/application/session-service.js +270 -639
- package/dist/application/turn-accounting.d.ts +74 -0
- package/dist/application/turn-accounting.js +444 -0
- package/dist/application/turn-persistence.d.ts +62 -0
- package/dist/application/turn-persistence.js +114 -0
- package/dist/cli/protocol.d.ts +45 -10
- package/dist/cli/protocol.js +283 -139
- package/dist/cli-runtime.js +56 -11
- package/package.json +1 -1
|
@@ -8,13 +8,12 @@ import { formatClaudeCompactSummary } from '../native/compaction.js';
|
|
|
8
8
|
import { isClaudeAgentId } from '../native/sidechain.js';
|
|
9
9
|
import { assertNativeDataPlane, resolveDataPlanePaths, resolveScheduledTaskFile, } from '../persistence/data-plane.js';
|
|
10
10
|
import { downloadClaudeFileResources, } from '../native/file-resources.js';
|
|
11
|
-
import { selectClaudeActiveTranscript } from '../native/history.js';
|
|
12
11
|
import { ClaudeFileHistory } from '../native/file-history.js';
|
|
13
12
|
import { getClaudeAgentSetting, projectClaudeModelMessages, } from '../native/projection.js';
|
|
14
13
|
import { projectNativeSessionEntries } from './native-session-projection.js';
|
|
15
14
|
import { classifyClaudeInterruption, } from '../native/interruption.js';
|
|
16
15
|
import { findUnresolvedClaudeToolCalls, getClaudeContentBlocks, } from '../native/tool-links.js';
|
|
17
|
-
import { createClaudeAgentSettingEntry, createClaudeHookAttachmentEntries,
|
|
16
|
+
import { createClaudeAgentSettingEntry, createClaudeHookAttachmentEntries, createClaudeRuleAttachmentEntry, translateProviderEvents, } from '../native/translation.js';
|
|
18
17
|
import { AgentRunCancelledError, AgentRuntime, MALFORMED_TOOL_INPUT_MESSAGE, ModelProviderError, } from '../core/runtime.js';
|
|
19
18
|
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
20
19
|
import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
@@ -34,6 +33,8 @@ import { lastUserPrompt, projectTranscriptDisplay, unresolvedActiveToolCallIds,
|
|
|
34
33
|
import { InMemoryTranscriptStore } from '../persistence/in-memory-transcript-store.js';
|
|
35
34
|
import { NativeTranscriptStore, } from '../persistence/native-transcript-store.js';
|
|
36
35
|
import { NativeSessionTranscript, } from './native-session-transcript.js';
|
|
36
|
+
import { TurnPersistence } from './turn-persistence.js';
|
|
37
|
+
import { TurnAccounting } from './turn-accounting.js';
|
|
37
38
|
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
38
39
|
import { ModelCompactor } from './model-compactor.js';
|
|
39
40
|
import { agentMemoryPrompt, ClaudeSubagentExecutor, StructuredOutputRegistry, } from './subagent-service.js';
|
|
@@ -249,18 +250,6 @@ function requireCompactionDurations(result) {
|
|
|
249
250
|
}
|
|
250
251
|
return { durationMs, durationWithoutRetriesMs };
|
|
251
252
|
}
|
|
252
|
-
function addToolDuration(value, total) {
|
|
253
|
-
if (value === undefined)
|
|
254
|
-
return total;
|
|
255
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
256
|
-
throw new TypeError('durationToolMs must be a finite nonnegative number');
|
|
257
|
-
}
|
|
258
|
-
const next = total + value;
|
|
259
|
-
if (!Number.isFinite(next) || next < 0) {
|
|
260
|
-
throw new TypeError('durationToolMs total overflow');
|
|
261
|
-
}
|
|
262
|
-
return next;
|
|
263
|
-
}
|
|
264
253
|
function addApiDuration(value, total, field) {
|
|
265
254
|
if (value === undefined)
|
|
266
255
|
return total;
|
|
@@ -273,36 +262,6 @@ function addApiDuration(value, total, field) {
|
|
|
273
262
|
}
|
|
274
263
|
return next;
|
|
275
264
|
}
|
|
276
|
-
function createLineCountAccumulator() {
|
|
277
|
-
let linesAdded = 0;
|
|
278
|
-
let linesRemoved = 0;
|
|
279
|
-
const addCounter = (current, value, field) => {
|
|
280
|
-
if (value === undefined)
|
|
281
|
-
return current;
|
|
282
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
283
|
-
throw new TypeError(`${field} must be a nonnegative safe integer`);
|
|
284
|
-
}
|
|
285
|
-
const next = current + value;
|
|
286
|
-
if (!Number.isSafeInteger(next) || next < 0) {
|
|
287
|
-
throw new TypeError(`${field} total must be a nonnegative safe integer`);
|
|
288
|
-
}
|
|
289
|
-
return next;
|
|
290
|
-
};
|
|
291
|
-
return {
|
|
292
|
-
get linesAdded() {
|
|
293
|
-
return linesAdded;
|
|
294
|
-
},
|
|
295
|
-
get linesRemoved() {
|
|
296
|
-
return linesRemoved;
|
|
297
|
-
},
|
|
298
|
-
add(input) {
|
|
299
|
-
if (input.isError)
|
|
300
|
-
return;
|
|
301
|
-
linesAdded = addCounter(linesAdded, input.linesAdded, 'linesAdded');
|
|
302
|
-
linesRemoved = addCounter(linesRemoved, input.linesRemoved, 'linesRemoved');
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
265
|
export function workflowTokenTarget(prompt) {
|
|
307
266
|
const match = /(?:^|\s)\+(\d+(?:\.\d+)?)([km])\b/iu.exec(prompt);
|
|
308
267
|
if (!match)
|
|
@@ -495,22 +454,6 @@ function validSessionName(value) {
|
|
|
495
454
|
? name.toLocaleLowerCase()
|
|
496
455
|
: null;
|
|
497
456
|
}
|
|
498
|
-
function projectMemoryMessages(entries) {
|
|
499
|
-
return selectClaudeActiveTranscript(entries).flatMap((entry) => {
|
|
500
|
-
if (typeof entry.uuid !== 'string')
|
|
501
|
-
return [];
|
|
502
|
-
return projectClaudeModelMessages([entry]).flatMap((message) => (message.role === 'user' || message.role === 'assistant') &&
|
|
503
|
-
message.content.trim().length > 0
|
|
504
|
-
? [
|
|
505
|
-
{
|
|
506
|
-
id: entry.uuid,
|
|
507
|
-
role: message.role,
|
|
508
|
-
content: message.content,
|
|
509
|
-
},
|
|
510
|
-
]
|
|
511
|
-
: []);
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
457
|
function successfulHookOutput(outcome) {
|
|
515
458
|
return (outcome?.executions ?? []).flatMap((execution) => {
|
|
516
459
|
const output = execution.stdout.trim();
|
|
@@ -2529,39 +2472,47 @@ export class ClaudeSessionService {
|
|
|
2529
2472
|
}
|
|
2530
2473
|
const sessionPaths = this.paths(sessionId);
|
|
2531
2474
|
const toolResultDirectory = join(sessionPaths.projectRoot, sessionId, 'tool-results');
|
|
2532
|
-
const runUnderLease = async (
|
|
2533
|
-
const
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2475
|
+
const runUnderLease = async (persistence) => {
|
|
2476
|
+
const activeTracker = this.sessionCostTrackers.get(sessionId);
|
|
2477
|
+
if (!activeTracker) {
|
|
2478
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
2479
|
+
}
|
|
2480
|
+
const turnAccounting = new TurnAccounting({
|
|
2481
|
+
tracker: activeTracker,
|
|
2482
|
+
...(this.options.pricing ? { pricing: this.options.pricing } : {}),
|
|
2483
|
+
});
|
|
2484
|
+
const projectionSnapshot = () => {
|
|
2485
|
+
const view = persistence.view();
|
|
2486
|
+
return {
|
|
2487
|
+
entries: [...view.projectionEntries],
|
|
2488
|
+
tail: view.projectionTail,
|
|
2489
|
+
};
|
|
2490
|
+
};
|
|
2491
|
+
const commitProjection = async (entries) => {
|
|
2492
|
+
await persistence.commit({ kind: 'projection', entries });
|
|
2537
2493
|
};
|
|
2538
2494
|
const activeTurnMessages = () => {
|
|
2539
2495
|
// Native events remain authoritative; refresh the derived projection
|
|
2540
2496
|
// after each append so memory/compaction sees the live branch.
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
entries: projectNativeSessionEntries(nativeLease.activeEvents()),
|
|
2544
|
-
};
|
|
2545
|
-
return nativeLease.activeMessages();
|
|
2497
|
+
const view = persistence.refresh();
|
|
2498
|
+
return [...view.activeMessages];
|
|
2546
2499
|
};
|
|
2547
|
-
if (
|
|
2548
|
-
name !== undefined &&
|
|
2549
|
-
this.options.sessionPersistence !== false) {
|
|
2500
|
+
if (name !== undefined && this.options.sessionPersistence !== false) {
|
|
2550
2501
|
const marker = `<praxis-session-name>${name}</praxis-session-name>`;
|
|
2551
|
-
if (!
|
|
2552
|
-
.
|
|
2553
|
-
.some((event) => event.kind === 'messages' &&
|
|
2502
|
+
if (!persistence
|
|
2503
|
+
.view()
|
|
2504
|
+
.activeEvents.some((event) => event.kind === 'messages' &&
|
|
2554
2505
|
event.messages.some((message) => message.role === 'user' && message.content === marker))) {
|
|
2555
|
-
await
|
|
2556
|
-
|
|
2506
|
+
await persistence.commit({
|
|
2507
|
+
kind: 'messages',
|
|
2508
|
+
input: { messages: [{ role: 'user', content: marker }] },
|
|
2557
2509
|
});
|
|
2558
2510
|
}
|
|
2559
2511
|
}
|
|
2560
|
-
const nativeInterruption =
|
|
2512
|
+
const nativeInterruption = persistence.view().interruption;
|
|
2561
2513
|
if (nativeInterruption?.kind === 'indeterminate-tools')
|
|
2562
2514
|
throw new Error(`Native tool execution is indeterminate: ${nativeInterruption.callIds.join(', ')}`);
|
|
2563
|
-
const nativeResumeInterrupted =
|
|
2564
|
-
this.options.resumeInterruptedTurn === true &&
|
|
2515
|
+
const nativeResumeInterrupted = this.options.resumeInterruptedTurn === true &&
|
|
2565
2516
|
(nativeInterruption?.kind === 'interrupted-prompt' ||
|
|
2566
2517
|
nativeInterruption?.kind === 'interrupted-turn');
|
|
2567
2518
|
const shouldSkipUserPrompt = () => skipUserPrompt || nativeResumeInterrupted;
|
|
@@ -2572,48 +2523,35 @@ export class ClaudeSessionService {
|
|
|
2572
2523
|
shellCommand === undefined &&
|
|
2573
2524
|
!shouldSkipUserPrompt() &&
|
|
2574
2525
|
!(this.options.approveRecovery !== undefined &&
|
|
2575
|
-
findUnresolvedClaudeToolCalls(
|
|
2576
|
-
|
|
2526
|
+
findUnresolvedClaudeToolCalls(projectionSnapshot().entries).length >
|
|
2527
|
+
0)) {
|
|
2528
|
+
const interruption = classifyClaudeInterruption(projectionSnapshot().entries);
|
|
2577
2529
|
if ((interruption.kind === 'interrupted-prompt' ||
|
|
2578
2530
|
interruption.kind === 'interrupted-turn') &&
|
|
2579
2531
|
interruption.prompt !== undefined &&
|
|
2580
2532
|
interruption.replayEntries !== undefined) {
|
|
2581
2533
|
automaticReplayPrompt = interruption.prompt;
|
|
2582
|
-
snapshot = {
|
|
2583
|
-
entries: interruption.replayEntries,
|
|
2584
|
-
tail: {
|
|
2585
|
-
...snapshot.tail,
|
|
2586
|
-
branchParentId: interruption.replayParentUuid ?? null,
|
|
2587
|
-
},
|
|
2588
|
-
};
|
|
2589
2534
|
}
|
|
2590
2535
|
}
|
|
2591
2536
|
const initialTransition = this.worktreeManager?.consumeTransition('__initial__');
|
|
2592
|
-
if (initialTransition &&
|
|
2537
|
+
if (initialTransition && projectionSnapshot().entries.length === 0) {
|
|
2593
2538
|
const stateEntry = {
|
|
2594
2539
|
type: 'worktree-state',
|
|
2595
2540
|
worktreeSession: initialTransition.state,
|
|
2596
2541
|
sessionId,
|
|
2597
2542
|
};
|
|
2598
|
-
|
|
2599
|
-
snapshot = {
|
|
2600
|
-
entries: [...snapshot.entries, stateEntry],
|
|
2601
|
-
tail: stateTail,
|
|
2602
|
-
};
|
|
2543
|
+
await commitProjection([stateEntry]);
|
|
2603
2544
|
}
|
|
2604
|
-
this.restoreWorktree(
|
|
2605
|
-
this.options.interactiveTools?.restore(sessionId,
|
|
2545
|
+
this.restoreWorktree(projectionSnapshot().entries);
|
|
2546
|
+
this.options.interactiveTools?.restore(sessionId, projectionSnapshot().entries);
|
|
2606
2547
|
if (requireExisting &&
|
|
2607
2548
|
name !== undefined &&
|
|
2608
|
-
!this.hasSessionName(
|
|
2549
|
+
!this.hasSessionName(projectionSnapshot().entries, name)) {
|
|
2609
2550
|
const entries = this.sessionNameEntries(sessionId, name);
|
|
2610
|
-
|
|
2611
|
-
snapshot = {
|
|
2612
|
-
entries: [...snapshot.entries, ...entries],
|
|
2613
|
-
tail: appendResult.tail,
|
|
2614
|
-
};
|
|
2551
|
+
await commitProjection(entries);
|
|
2615
2552
|
}
|
|
2616
|
-
const agentName = this.options.agent ??
|
|
2553
|
+
const agentName = this.options.agent ??
|
|
2554
|
+
getClaudeAgentSetting(projectionSnapshot().entries);
|
|
2617
2555
|
const agent = this.resolveAgent(agentName);
|
|
2618
2556
|
const provider = this.providerForAgent(agent);
|
|
2619
2557
|
this.activeProvider = provider;
|
|
@@ -2660,11 +2598,9 @@ export class ClaudeSessionService {
|
|
|
2660
2598
|
...(this.options.fileRewindRoots ?? []),
|
|
2661
2599
|
])
|
|
2662
2600
|
: null;
|
|
2663
|
-
const unresolvedToolCalls =
|
|
2664
|
-
? nativeInterruption
|
|
2665
|
-
|
|
2666
|
-
: []
|
|
2667
|
-
: findUnresolvedClaudeToolCalls(snapshot.entries);
|
|
2601
|
+
const unresolvedToolCalls = nativeInterruption?.kind === 'recoverable-tools'
|
|
2602
|
+
? [...nativeInterruption.calls]
|
|
2603
|
+
: [];
|
|
2668
2604
|
const pendingRecoveryToolCallIds = new Set(unresolvedToolCalls.map((call) => call.id));
|
|
2669
2605
|
const pendingRecoveryHookOutcomes = [];
|
|
2670
2606
|
let refreshRuntimeContext;
|
|
@@ -2678,26 +2614,25 @@ export class ClaudeSessionService {
|
|
|
2678
2614
|
permission_mode: 'default',
|
|
2679
2615
|
};
|
|
2680
2616
|
const appendHookOutcome = async (outcome) => {
|
|
2681
|
-
for (const entry of createClaudeHookAttachmentEntries(outcome, this.translationContext(sessionId,
|
|
2682
|
-
const
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2617
|
+
for (const entry of createClaudeHookAttachmentEntries(outcome, this.translationContext(sessionId, projectionSnapshot()))) {
|
|
2618
|
+
const attachment = entry.attachment;
|
|
2619
|
+
const raw = attachment?.content;
|
|
2620
|
+
const content = typeof raw === 'string'
|
|
2621
|
+
? raw
|
|
2622
|
+
: Array.isArray(raw)
|
|
2623
|
+
? raw.filter((value) => typeof value === 'string').join('\n')
|
|
2624
|
+
: raw && typeof raw === 'object' && 'content' in raw
|
|
2625
|
+
? String(raw.content)
|
|
2626
|
+
: '';
|
|
2627
|
+
if (content) {
|
|
2628
|
+
await persistence.commit({
|
|
2629
|
+
kind: 'messages',
|
|
2630
|
+
input: { messages: [{ role: 'user', content }] },
|
|
2631
|
+
projectionEntries: [entry],
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
else {
|
|
2635
|
+
await commitProjection([entry]);
|
|
2701
2636
|
}
|
|
2702
2637
|
}
|
|
2703
2638
|
};
|
|
@@ -2710,11 +2645,11 @@ export class ClaudeSessionService {
|
|
|
2710
2645
|
};
|
|
2711
2646
|
const flushRecoveryHookOutcomes = async () => {
|
|
2712
2647
|
const entries = [];
|
|
2713
|
-
let history =
|
|
2714
|
-
let parentUuid = this.logicalTailUuid(
|
|
2648
|
+
let history = projectionSnapshot().entries;
|
|
2649
|
+
let parentUuid = this.logicalTailUuid(projectionSnapshot().tail);
|
|
2715
2650
|
for (const outcome of pendingRecoveryHookOutcomes) {
|
|
2716
2651
|
const outcomeEntries = createClaudeHookAttachmentEntries(outcome, {
|
|
2717
|
-
...this.translationContext(sessionId,
|
|
2652
|
+
...this.translationContext(sessionId, projectionSnapshot()),
|
|
2718
2653
|
parentUuid,
|
|
2719
2654
|
history,
|
|
2720
2655
|
});
|
|
@@ -2728,8 +2663,7 @@ export class ClaudeSessionService {
|
|
|
2728
2663
|
pendingRecoveryHookOutcomes.length = 0;
|
|
2729
2664
|
return;
|
|
2730
2665
|
}
|
|
2731
|
-
|
|
2732
|
-
snapshot = { entries: history, tail: appendResult.tail };
|
|
2666
|
+
await commitProjection(entries);
|
|
2733
2667
|
pendingRecoveryHookOutcomes.length = 0;
|
|
2734
2668
|
};
|
|
2735
2669
|
const capabilities = this.toolCapabilities();
|
|
@@ -2870,7 +2804,7 @@ export class ClaudeSessionService {
|
|
|
2870
2804
|
backgroundTaskNotifications: (waitForRunning) => taskTools.notifications(waitForRunning),
|
|
2871
2805
|
}
|
|
2872
2806
|
: {}),
|
|
2873
|
-
notificationDelivered: (notification) => transcriptContainsBackgroundAgentNotification(
|
|
2807
|
+
notificationDelivered: (notification) => transcriptContainsBackgroundAgentNotification(projectionSnapshot().entries, notification),
|
|
2874
2808
|
stopOwnedBackgroundAgent: (ownerSessionId, agentId) => this.backgroundTasks.stopAgent(ownerSessionId, agentId),
|
|
2875
2809
|
outputOwnedBackgroundAgent: (ownerSessionId, agentId, options) => this.backgroundTasks.outputAgent(ownerSessionId, agentId, options),
|
|
2876
2810
|
sendOwnedBackgroundAgent: (ownerSessionId, agentId, message, summary, toolUseId) => this.sendOwnedBackgroundAgent(ownerSessionId, agentId, message, summary, toolUseId),
|
|
@@ -2883,7 +2817,7 @@ export class ClaudeSessionService {
|
|
|
2883
2817
|
}
|
|
2884
2818
|
const agentTools = subagentExecutor
|
|
2885
2819
|
? subagentExecutor.registry(sessionId, 0, (callId) => currentPromptId ??
|
|
2886
|
-
this.promptIdForToolCall(
|
|
2820
|
+
this.promptIdForToolCall(projectionSnapshot().entries, callId))
|
|
2887
2821
|
: baseTools;
|
|
2888
2822
|
const turnTools = this.workflowManager && subagentExecutor && agentTools
|
|
2889
2823
|
? new ClaudeWorkflowToolRegistry({
|
|
@@ -2895,7 +2829,7 @@ export class ClaudeSessionService {
|
|
|
2895
2829
|
configRoot: this.options.configRoot,
|
|
2896
2830
|
sessionId,
|
|
2897
2831
|
promptIdForCall: (callId) => currentPromptId ??
|
|
2898
|
-
this.promptIdForToolCall(
|
|
2832
|
+
this.promptIdForToolCall(projectionSnapshot().entries, callId),
|
|
2899
2833
|
defaultModel: provider.model ?? 'praxis/provider',
|
|
2900
2834
|
tokenBudget: workflowTokenTarget(effectivePrompt),
|
|
2901
2835
|
enabled: capabilities.has('Workflow'),
|
|
@@ -2948,13 +2882,13 @@ export class ClaudeSessionService {
|
|
|
2948
2882
|
return interactiveMessageTools.execute(call, context);
|
|
2949
2883
|
}
|
|
2950
2884
|
const snapshotMessageId = currentPromptId ??
|
|
2951
|
-
this.promptIdForToolCall(
|
|
2885
|
+
this.promptIdForToolCall(projectionSnapshot().entries, call.id);
|
|
2952
2886
|
const assistantMessageId = lastAssistantUuid ??
|
|
2953
|
-
this.assistantIdForToolCall(
|
|
2887
|
+
this.assistantIdForToolCall(projectionSnapshot().entries, call.id);
|
|
2954
2888
|
if (!snapshotMessageId || !assistantMessageId) {
|
|
2955
2889
|
throw new Error('Claude file history could not link tool call');
|
|
2956
2890
|
}
|
|
2957
|
-
const prepared = await fileHistory.prepareMutation(
|
|
2891
|
+
const prepared = await fileHistory.prepareMutation(projectionSnapshot().entries, snapshotMessageId, path);
|
|
2958
2892
|
let result;
|
|
2959
2893
|
try {
|
|
2960
2894
|
result = await interactiveMessageTools.execute(call, context);
|
|
@@ -2969,18 +2903,18 @@ export class ClaudeSessionService {
|
|
|
2969
2903
|
}
|
|
2970
2904
|
const entry = prepared.commit(assistantMessageId);
|
|
2971
2905
|
if (entry) {
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
await nativeLease.appendMessages({
|
|
2906
|
+
await persistence.commit({
|
|
2907
|
+
kind: 'messages',
|
|
2908
|
+
input: {
|
|
2976
2909
|
messages: [
|
|
2977
2910
|
{
|
|
2978
2911
|
role: 'user',
|
|
2979
2912
|
content: `<praxis-file-history>${JSON.stringify(entry)}</praxis-file-history>`,
|
|
2980
2913
|
},
|
|
2981
2914
|
],
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2915
|
+
},
|
|
2916
|
+
projectionEntries: [entry],
|
|
2917
|
+
});
|
|
2984
2918
|
}
|
|
2985
2919
|
return result;
|
|
2986
2920
|
},
|
|
@@ -3058,14 +2992,13 @@ export class ClaudeSessionService {
|
|
|
3058
2992
|
const durableFollowUps = new DurableFollowUpTracker();
|
|
3059
2993
|
const claimedToolCallIds = new Set();
|
|
3060
2994
|
const observer = {
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
: {}),
|
|
2995
|
+
toolExecutionStarted: async (call) => {
|
|
2996
|
+
await persistence.commit({
|
|
2997
|
+
kind: 'tool-execution-started',
|
|
2998
|
+
callId: call.id,
|
|
2999
|
+
});
|
|
3000
|
+
claimedToolCallIds.add(call.id);
|
|
3001
|
+
},
|
|
3069
3002
|
modelRequestCompleted: async (input) => {
|
|
3070
3003
|
observeModelRequestUsage(input);
|
|
3071
3004
|
},
|
|
@@ -3081,13 +3014,12 @@ export class ClaudeSessionService {
|
|
|
3081
3014
|
providerMessageId: `msg_${randomUUID().replaceAll('-', '')}`,
|
|
3082
3015
|
model: provider.model ?? 'praxis/provider',
|
|
3083
3016
|
},
|
|
3084
|
-
], this.translationContext(sessionId,
|
|
3017
|
+
], this.translationContext(sessionId, projectionSnapshot()));
|
|
3085
3018
|
if (!entry)
|
|
3086
3019
|
throw new Error('Could not translate assistant response');
|
|
3087
|
-
const
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
? await nativeLease.appendMessages({
|
|
3020
|
+
const nativeReceipt = await persistence.commit({
|
|
3021
|
+
kind: 'messages',
|
|
3022
|
+
input: {
|
|
3091
3023
|
messages: [
|
|
3092
3024
|
{
|
|
3093
3025
|
role: 'assistant',
|
|
@@ -3101,29 +3033,26 @@ export class ClaudeSessionService {
|
|
|
3101
3033
|
},
|
|
3102
3034
|
],
|
|
3103
3035
|
model: provider.model ?? 'praxis/provider',
|
|
3104
|
-
}
|
|
3105
|
-
:
|
|
3106
|
-
|
|
3107
|
-
|
|
3036
|
+
},
|
|
3037
|
+
projectionEntries: [entry],
|
|
3038
|
+
});
|
|
3039
|
+
if (nativeReceipt.kind !== 'messages')
|
|
3040
|
+
throw new Error('Turn persistence returned an invalid message receipt');
|
|
3041
|
+
lastAssistantUuid = nativeReceipt.eventId;
|
|
3108
3042
|
},
|
|
3109
3043
|
toolCompleted: async (call, toolResult) => {
|
|
3110
|
-
currentTurnToolCalls += 1;
|
|
3111
3044
|
const transition = this.worktreeManager?.consumeTransition(call.id);
|
|
3112
3045
|
if (transition) {
|
|
3113
|
-
this.options.contextAssembler?.invalidate?.({
|
|
3114
|
-
lifecycleId: sessionId,
|
|
3115
|
-
reason: 'worktree',
|
|
3116
|
-
});
|
|
3117
3046
|
const stateEntry = {
|
|
3118
3047
|
type: 'worktree-state',
|
|
3119
3048
|
worktreeSession: transition.state,
|
|
3120
3049
|
sessionId,
|
|
3121
3050
|
};
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
};
|
|
3051
|
+
await commitProjection([stateEntry]);
|
|
3052
|
+
this.options.contextAssembler?.invalidate?.({
|
|
3053
|
+
lifecycleId: sessionId,
|
|
3054
|
+
reason: 'worktree',
|
|
3055
|
+
});
|
|
3127
3056
|
}
|
|
3128
3057
|
const permissionMode = this.options.interactiveTools?.consumeTransition(call.id);
|
|
3129
3058
|
if (permissionMode) {
|
|
@@ -3132,21 +3061,18 @@ export class ClaudeSessionService {
|
|
|
3132
3061
|
permissionMode,
|
|
3133
3062
|
sessionId,
|
|
3134
3063
|
};
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
tail: modeTail,
|
|
3139
|
-
};
|
|
3140
|
-
if (nativeLease) {
|
|
3141
|
-
await nativeLease.appendMessages({
|
|
3064
|
+
await persistence.commit({
|
|
3065
|
+
kind: 'messages',
|
|
3066
|
+
input: {
|
|
3142
3067
|
messages: [
|
|
3143
3068
|
{
|
|
3144
3069
|
role: 'user',
|
|
3145
3070
|
content: `/permission-mode ${permissionMode}`,
|
|
3146
3071
|
},
|
|
3147
3072
|
],
|
|
3148
|
-
}
|
|
3149
|
-
|
|
3073
|
+
},
|
|
3074
|
+
projectionEntries: [modeEntry],
|
|
3075
|
+
});
|
|
3150
3076
|
// Interactive mode transitions change the runtime system
|
|
3151
3077
|
// context for the very next provider request. Refresh here so
|
|
3152
3078
|
// an EnterPlanMode/ExitPlanMode tool call is immediately
|
|
@@ -3154,43 +3080,21 @@ export class ClaudeSessionService {
|
|
|
3154
3080
|
if (typeof refreshRuntimeContext === 'function')
|
|
3155
3081
|
await refreshRuntimeContext();
|
|
3156
3082
|
}
|
|
3157
|
-
if (
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
claimedToolCallIds.add(call.id);
|
|
3161
|
-
}
|
|
3162
|
-
await nativeLease.appendToolCompletion({
|
|
3083
|
+
if (!claimedToolCallIds.has(call.id) && !signal?.aborted) {
|
|
3084
|
+
await persistence.commit({
|
|
3085
|
+
kind: 'tool-execution-started',
|
|
3163
3086
|
callId: call.id,
|
|
3164
|
-
result: toolResult,
|
|
3165
3087
|
});
|
|
3088
|
+
claimedToolCallIds.add(call.id);
|
|
3166
3089
|
}
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
: {}),
|
|
3176
|
-
...(toolResult.images ? { images: toolResult.images } : {}),
|
|
3177
|
-
...(toolResult.documents
|
|
3178
|
-
? { documents: toolResult.documents }
|
|
3179
|
-
: {}),
|
|
3180
|
-
isError: toolResult.isError,
|
|
3181
|
-
...(toolResult.nativeToolUseResult
|
|
3182
|
-
? { nativeToolUseResult: toolResult.nativeToolUseResult }
|
|
3183
|
-
: {}),
|
|
3184
|
-
...(toolResult.nativeMcpMeta
|
|
3185
|
-
? { nativeMcpMeta: toolResult.nativeMcpMeta }
|
|
3186
|
-
: {}),
|
|
3187
|
-
},
|
|
3188
|
-
], this.translationContext(sessionId, snapshot));
|
|
3189
|
-
if (!entry)
|
|
3190
|
-
throw new Error('Could not translate tool result');
|
|
3191
|
-
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3192
|
-
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3193
|
-
}
|
|
3090
|
+
await persistence.commit({
|
|
3091
|
+
kind: 'tool-completion',
|
|
3092
|
+
input: {
|
|
3093
|
+
callId: call.id,
|
|
3094
|
+
result: toolResult,
|
|
3095
|
+
},
|
|
3096
|
+
});
|
|
3097
|
+
currentTurnToolCalls += 1;
|
|
3194
3098
|
if (!toolResult.isError && this.options.projectMemoryDirectory) {
|
|
3195
3099
|
const pathValue = call.input.file_path;
|
|
3196
3100
|
const path = typeof pathValue === 'string'
|
|
@@ -3220,23 +3124,23 @@ export class ClaudeSessionService {
|
|
|
3220
3124
|
!toolResult.accessedPaths) {
|
|
3221
3125
|
return;
|
|
3222
3126
|
}
|
|
3223
|
-
const attachedRulePaths = this.attachedRulePaths(
|
|
3127
|
+
const attachedRulePaths = this.attachedRulePaths(projectionSnapshot().entries);
|
|
3224
3128
|
for (const filePath of toolResult.accessedPaths) {
|
|
3225
3129
|
const rules = await this.options.conditionalRuleResolver.resolve(filePath, [...attachedRulePaths]);
|
|
3226
3130
|
for (const rule of rules) {
|
|
3227
|
-
const attachment = createClaudeRuleAttachmentEntry(rule, this.displayRulePath(rule.path), this.translationContext(sessionId,
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
if (nativeLease) {
|
|
3234
|
-
const ruleContent = rule.content.trim();
|
|
3235
|
-
if (ruleContent.length > 0) {
|
|
3236
|
-
await nativeLease.appendMessages({
|
|
3131
|
+
const attachment = createClaudeRuleAttachmentEntry(rule, this.displayRulePath(rule.path), this.translationContext(sessionId, projectionSnapshot()));
|
|
3132
|
+
const ruleContent = rule.content.trim();
|
|
3133
|
+
if (ruleContent.length > 0) {
|
|
3134
|
+
await persistence.commit({
|
|
3135
|
+
kind: 'messages',
|
|
3136
|
+
input: {
|
|
3237
3137
|
messages: [{ role: 'user', content: ruleContent }],
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3138
|
+
},
|
|
3139
|
+
projectionEntries: [attachment],
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
else {
|
|
3143
|
+
await commitProjection([attachment]);
|
|
3240
3144
|
}
|
|
3241
3145
|
attachedRulePaths.add(rule.path);
|
|
3242
3146
|
await this.instructionLoaded(sessionId, {
|
|
@@ -3257,44 +3161,20 @@ export class ClaudeSessionService {
|
|
|
3257
3161
|
},
|
|
3258
3162
|
followUpUserMessagesCompleted: async (messages) => {
|
|
3259
3163
|
for (const content of messages) {
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
currentTurnUserMessages?.push(content);
|
|
3265
|
-
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3266
|
-
continue;
|
|
3267
|
-
}
|
|
3268
|
-
const [followUpEntry] = translateProviderEvents([{ type: 'user-text-block', text: content }], this.translationContext(sessionId, snapshot));
|
|
3269
|
-
if (!followUpEntry) {
|
|
3270
|
-
throw new Error('Could not translate tool follow-up message');
|
|
3271
|
-
}
|
|
3272
|
-
const followUpTail = await this.append(lease, snapshot.tail, followUpEntry);
|
|
3273
|
-
snapshot = {
|
|
3274
|
-
entries: [...snapshot.entries, followUpEntry],
|
|
3275
|
-
tail: followUpTail,
|
|
3276
|
-
};
|
|
3164
|
+
await persistence.commit({
|
|
3165
|
+
kind: 'messages',
|
|
3166
|
+
input: { messages: [{ role: 'user', content }] },
|
|
3167
|
+
});
|
|
3277
3168
|
currentTurnUserMessages?.push(content);
|
|
3278
3169
|
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3279
3170
|
}
|
|
3280
3171
|
await durableFollowUps.followUpUserMessagesCompleted(messages);
|
|
3281
3172
|
},
|
|
3282
3173
|
userInputDelivered: async (item) => {
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
currentTurnUserMessages?.push(item.content);
|
|
3288
|
-
return;
|
|
3289
|
-
}
|
|
3290
|
-
const [steeringEntry] = translateProviderEvents([{ type: 'user-text-block', text: item.content }], this.translationContext(sessionId, snapshot));
|
|
3291
|
-
if (!steeringEntry)
|
|
3292
|
-
throw new Error('Could not translate steering message');
|
|
3293
|
-
const steeringTail = await this.append(lease, snapshot.tail, steeringEntry);
|
|
3294
|
-
snapshot = {
|
|
3295
|
-
entries: [...snapshot.entries, steeringEntry],
|
|
3296
|
-
tail: steeringTail,
|
|
3297
|
-
};
|
|
3174
|
+
await persistence.commit({
|
|
3175
|
+
kind: 'messages',
|
|
3176
|
+
input: { messages: [{ role: 'user', content: item.content }] },
|
|
3177
|
+
});
|
|
3298
3178
|
currentTurnUserMessages?.push(item.content);
|
|
3299
3179
|
},
|
|
3300
3180
|
};
|
|
@@ -3338,31 +3218,23 @@ export class ClaudeSessionService {
|
|
|
3338
3218
|
throw new Error(`Claude session tool call ${unresolvedToolCall.id} requires explicit recovery approval`);
|
|
3339
3219
|
}
|
|
3340
3220
|
const recoveryResults = await runtime.recoverToolCalls(unresolvedToolCalls, recoveryRequest);
|
|
3341
|
-
const recoveryUsage = recoveryResults.reduce((usage, result) => result.usage ? mergeUsage(usage, result.usage) : usage, { inputTokens: 0, outputTokens: 0 });
|
|
3342
|
-
const recoveryModelUsage = mergeSessionRawModelUsage(...recoveryResults.map((result) => result.isError ? undefined : result.modelUsage));
|
|
3343
|
-
const foregroundLineChanges = createLineCountAccumulator();
|
|
3344
|
-
for (const result of recoveryResults) {
|
|
3345
|
-
foregroundLineChanges.add(result);
|
|
3346
|
-
}
|
|
3347
3221
|
if (this.options.agent &&
|
|
3348
3222
|
agent &&
|
|
3349
|
-
getClaudeAgentSetting(
|
|
3223
|
+
getClaudeAgentSetting(projectionSnapshot().entries) !==
|
|
3224
|
+
this.options.agent) {
|
|
3350
3225
|
const agentSetting = createClaudeAgentSettingEntry(sessionId, this.options.agent);
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
tail: settingTail,
|
|
3355
|
-
};
|
|
3356
|
-
if (nativeLease) {
|
|
3357
|
-
await nativeLease.appendMessages({
|
|
3226
|
+
await persistence.commit({
|
|
3227
|
+
kind: 'messages',
|
|
3228
|
+
input: {
|
|
3358
3229
|
messages: [
|
|
3359
3230
|
{
|
|
3360
3231
|
role: 'user',
|
|
3361
3232
|
content: `<praxis-agent-setting>${this.options.agent}</praxis-agent-setting>`,
|
|
3362
3233
|
},
|
|
3363
3234
|
],
|
|
3364
|
-
}
|
|
3365
|
-
|
|
3235
|
+
},
|
|
3236
|
+
projectionEntries: [agentSetting],
|
|
3237
|
+
});
|
|
3366
3238
|
}
|
|
3367
3239
|
let agentSystem = null;
|
|
3368
3240
|
let planModeMessage;
|
|
@@ -3429,13 +3301,6 @@ export class ClaudeSessionService {
|
|
|
3429
3301
|
type: 'state',
|
|
3430
3302
|
state: 'assembling-context',
|
|
3431
3303
|
});
|
|
3432
|
-
let compactionUsage = {
|
|
3433
|
-
inputTokens: 0,
|
|
3434
|
-
outputTokens: 0,
|
|
3435
|
-
};
|
|
3436
|
-
let compactionDurationMs;
|
|
3437
|
-
let compactionDurationWithoutRetriesMs;
|
|
3438
|
-
let compactionModelUsage;
|
|
3439
3304
|
const budget = this.contextBudget(provider);
|
|
3440
3305
|
const contextEngine = new ContextEngine({
|
|
3441
3306
|
...(budget ? { budget } : {}),
|
|
@@ -3471,7 +3336,7 @@ export class ClaudeSessionService {
|
|
|
3471
3336
|
shellCommand === undefined && !shouldSkipUserPrompt()
|
|
3472
3337
|
? (this.options.extensions?.agentMentionMessages(effectivePrompt) ?? [])
|
|
3473
3338
|
: [];
|
|
3474
|
-
let compactionAnchorUuid = this.lastMessageUuid(
|
|
3339
|
+
let compactionAnchorUuid = this.lastMessageUuid(projectionSnapshot().entries);
|
|
3475
3340
|
const contextTransitionPort = (pendingMessages = [], preservedUserMessages = []) => ({
|
|
3476
3341
|
current: () => {
|
|
3477
3342
|
const projection = contextPreparation.project({
|
|
@@ -3491,7 +3356,6 @@ export class ClaudeSessionService {
|
|
|
3491
3356
|
],
|
|
3492
3357
|
}).envelope,
|
|
3493
3358
|
propose: async () => {
|
|
3494
|
-
const activeNativeLease = nativeLease;
|
|
3495
3359
|
if (!budget)
|
|
3496
3360
|
throw new Error('Context budget is unavailable');
|
|
3497
3361
|
const definitions = contextPreparation.project().envelope.tools;
|
|
@@ -3547,15 +3411,15 @@ export class ClaudeSessionService {
|
|
|
3547
3411
|
!promptContents.has(message.content)),
|
|
3548
3412
|
];
|
|
3549
3413
|
if (earliest === 0 && preservedUserMessages[0] !== undefined) {
|
|
3550
|
-
compactionLogicalParentId =
|
|
3551
|
-
.
|
|
3552
|
-
.find((event) => event.kind === 'messages' &&
|
|
3414
|
+
compactionLogicalParentId = persistence
|
|
3415
|
+
.view()
|
|
3416
|
+
.activeEvents.find((event) => event.kind === 'messages' &&
|
|
3553
3417
|
event.messages.some((message) => message.role === 'user' &&
|
|
3554
3418
|
message.content === preservedUserMessages[0]))?.id;
|
|
3555
3419
|
}
|
|
3556
3420
|
}
|
|
3557
3421
|
const memorySelection = sessionMemory
|
|
3558
|
-
? await this.selectMemoryPreservedCompact(sessionId,
|
|
3422
|
+
? await this.selectMemoryPreservedCompact(sessionId, persistence.view().projectionEntries)
|
|
3559
3423
|
: null;
|
|
3560
3424
|
if (memorySelection) {
|
|
3561
3425
|
compactableMessages = [
|
|
@@ -3576,9 +3440,9 @@ export class ClaudeSessionService {
|
|
|
3576
3440
|
preservedMessages = historyMessages.filter((message) => message.role === 'user');
|
|
3577
3441
|
const anchor = preservedMessages[0];
|
|
3578
3442
|
if (anchor !== undefined) {
|
|
3579
|
-
compactionLogicalParentId =
|
|
3580
|
-
.
|
|
3581
|
-
.find((event) => event.kind === 'messages' &&
|
|
3443
|
+
compactionLogicalParentId = persistence
|
|
3444
|
+
.view()
|
|
3445
|
+
.activeEvents.find((event) => event.kind === 'messages' &&
|
|
3582
3446
|
event.messages.some((message) => message.role === 'user' &&
|
|
3583
3447
|
message.content === anchor.content))?.id;
|
|
3584
3448
|
}
|
|
@@ -3612,15 +3476,6 @@ export class ClaudeSessionService {
|
|
|
3612
3476
|
const { durationMs, durationWithoutRetriesMs } = requireCompactionDurations(compacted);
|
|
3613
3477
|
if (signal?.aborted)
|
|
3614
3478
|
throw new AgentRunCancelledError();
|
|
3615
|
-
const proposedCompactionDurationMs = (compactionDurationMs ?? 0) + durationMs;
|
|
3616
|
-
if (!Number.isFinite(proposedCompactionDurationMs)) {
|
|
3617
|
-
throw new TypeError('compaction durationMs total overflow');
|
|
3618
|
-
}
|
|
3619
|
-
const proposedCompactionDurationWithoutRetriesMs = (compactionDurationWithoutRetriesMs ?? 0) +
|
|
3620
|
-
durationWithoutRetriesMs;
|
|
3621
|
-
if (!Number.isFinite(proposedCompactionDurationWithoutRetriesMs)) {
|
|
3622
|
-
throw new TypeError('compaction durationWithoutRetriesMs total overflow');
|
|
3623
|
-
}
|
|
3624
3479
|
const postTokens = estimateModelRequestTokens([
|
|
3625
3480
|
{ role: 'user', content: compacted.summary },
|
|
3626
3481
|
...preservedMessages,
|
|
@@ -3628,49 +3483,12 @@ export class ClaudeSessionService {
|
|
|
3628
3483
|
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3629
3484
|
? compacted.model
|
|
3630
3485
|
: provider.model;
|
|
3631
|
-
const
|
|
3632
|
-
|
|
3633
|
-
(compactModel === undefined
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3638
|
-
let meteringTurnInput;
|
|
3639
|
-
if (meaningfulUsage &&
|
|
3640
|
-
compactModel !== undefined &&
|
|
3641
|
-
compactModel.trim() !== '') {
|
|
3642
|
-
const pricing = this.options.pricing?.resolve(compactModel);
|
|
3643
|
-
const costUsd = pricing
|
|
3644
|
-
? usageCostUsd(compacted.usage, pricing)
|
|
3645
|
-
: undefined;
|
|
3646
|
-
meteringTurnInput = {
|
|
3647
|
-
model: compactModel,
|
|
3648
|
-
usage: compacted.usage,
|
|
3649
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
3650
|
-
...(compacted.usage.webSearchRequests === undefined
|
|
3651
|
-
? {}
|
|
3652
|
-
: {
|
|
3653
|
-
webSearchRequests: compacted.usage.webSearchRequests,
|
|
3654
|
-
}),
|
|
3655
|
-
};
|
|
3656
|
-
}
|
|
3657
|
-
let meteringDurationsInput;
|
|
3658
|
-
if (durationMs > 0 || durationWithoutRetriesMs > 0)
|
|
3659
|
-
meteringDurationsInput = {
|
|
3660
|
-
...(durationMs === 0 ? {} : { apiDurationMs: durationMs }),
|
|
3661
|
-
apiDurationWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3662
|
-
};
|
|
3663
|
-
if (meteringTurnInput !== undefined ||
|
|
3664
|
-
meteringDurationsInput !== undefined) {
|
|
3665
|
-
const preflight = new ClaudeSessionCostTracker({
|
|
3666
|
-
sessionId,
|
|
3667
|
-
restored: tracker.snapshot(),
|
|
3668
|
-
});
|
|
3669
|
-
if (meteringTurnInput !== undefined)
|
|
3670
|
-
preflight.recordTurn(meteringTurnInput);
|
|
3671
|
-
if (meteringDurationsInput !== undefined)
|
|
3672
|
-
preflight.recordDurations(meteringDurationsInput);
|
|
3673
|
-
}
|
|
3486
|
+
const preparedCompaction = turnAccounting.prepareCompaction({
|
|
3487
|
+
usage: compacted.usage,
|
|
3488
|
+
...(compactModel === undefined ? {} : { model: compactModel }),
|
|
3489
|
+
durationApiMs: durationMs,
|
|
3490
|
+
durationApiWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3491
|
+
});
|
|
3674
3492
|
const summaryMessage = {
|
|
3675
3493
|
role: 'user',
|
|
3676
3494
|
content: compacted.summary,
|
|
@@ -3705,25 +3523,30 @@ export class ClaudeSessionService {
|
|
|
3705
3523
|
commit: async () => {
|
|
3706
3524
|
if (signal?.aborted)
|
|
3707
3525
|
throw new AgentRunCancelledError();
|
|
3708
|
-
const committed = await replacement.commit(() =>
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3526
|
+
const committed = await replacement.commit(() => persistence.commit({
|
|
3527
|
+
kind: 'compaction',
|
|
3528
|
+
input: {
|
|
3529
|
+
summary: compacted.summary,
|
|
3530
|
+
trigger: 'auto',
|
|
3531
|
+
preTokens,
|
|
3532
|
+
postTokens,
|
|
3533
|
+
durationMs,
|
|
3534
|
+
preservedMessages: replayMessages,
|
|
3535
|
+
...(compactionLogicalParentId === undefined
|
|
3536
|
+
? {}
|
|
3537
|
+
: { logicalParentId: compactionLogicalParentId }),
|
|
3538
|
+
...(memorySelection
|
|
3539
|
+
? {
|
|
3540
|
+
direction: 'from',
|
|
3541
|
+
messagesSummarized: compactableMessages.length,
|
|
3542
|
+
preservePrefix: false,
|
|
3543
|
+
}
|
|
3544
|
+
: {}),
|
|
3545
|
+
},
|
|
3725
3546
|
}));
|
|
3726
3547
|
const ids = committed.value;
|
|
3548
|
+
if (ids.kind !== 'compaction')
|
|
3549
|
+
throw new Error('Turn persistence returned an invalid compaction receipt');
|
|
3727
3550
|
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'auto', compact_summary: compacted.summary }, 'auto', signal);
|
|
3728
3551
|
if (this.options.hooks) {
|
|
3729
3552
|
const outcome = await this.hookLifecycle.refresh(sessionId, hookSession, signal);
|
|
@@ -3741,21 +3564,7 @@ export class ClaudeSessionService {
|
|
|
3741
3564
|
preTokens,
|
|
3742
3565
|
uuid: ids.boundaryId,
|
|
3743
3566
|
});
|
|
3744
|
-
|
|
3745
|
-
compactionDurationMs =
|
|
3746
|
-
(compactionDurationMs ?? 0) + durationMs;
|
|
3747
|
-
compactionDurationWithoutRetriesMs =
|
|
3748
|
-
(compactionDurationWithoutRetriesMs ?? 0) +
|
|
3749
|
-
durationWithoutRetriesMs;
|
|
3750
|
-
if (meteringTurnInput !== undefined) {
|
|
3751
|
-
tracker.recordTurn(meteringTurnInput);
|
|
3752
|
-
}
|
|
3753
|
-
if (compactModel !== undefined &&
|
|
3754
|
-
compactModel.trim() !== '') {
|
|
3755
|
-
compactionModelUsage = mergeSessionRawModelUsage(compactionModelUsage, { [compactModel]: compacted.usage });
|
|
3756
|
-
}
|
|
3757
|
-
if (meteringDurationsInput !== undefined)
|
|
3758
|
-
tracker.recordDurations(meteringDurationsInput);
|
|
3567
|
+
preparedCompaction.commit();
|
|
3759
3568
|
},
|
|
3760
3569
|
};
|
|
3761
3570
|
},
|
|
@@ -3783,7 +3592,7 @@ export class ClaudeSessionService {
|
|
|
3783
3592
|
: index === 0
|
|
3784
3593
|
? { type: 'user-text', text: message.text }
|
|
3785
3594
|
: { type: 'user-text-block', text: message.text };
|
|
3786
|
-
const [userEntry] = translateProviderEvents([persistenceEvent], this.translationContext(sessionId,
|
|
3595
|
+
const [userEntry] = translateProviderEvents([persistenceEvent], this.translationContext(sessionId, projectionSnapshot()));
|
|
3787
3596
|
if (!userEntry)
|
|
3788
3597
|
throw new Error('Could not translate user prompt');
|
|
3789
3598
|
if (currentPromptId === null &&
|
|
@@ -3791,13 +3600,9 @@ export class ClaudeSessionService {
|
|
|
3791
3600
|
currentPromptId = userEntry.uuid;
|
|
3792
3601
|
compactionAnchorUuid ??= userEntry.uuid;
|
|
3793
3602
|
}
|
|
3794
|
-
const
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
tail: userTail,
|
|
3798
|
-
};
|
|
3799
|
-
if (nativeLease) {
|
|
3800
|
-
const nativePromptId = await nativeLease.appendMessages({
|
|
3603
|
+
const nativePrompt = await persistence.commit({
|
|
3604
|
+
kind: 'messages',
|
|
3605
|
+
input: {
|
|
3801
3606
|
messages: [
|
|
3802
3607
|
{
|
|
3803
3608
|
role: 'user',
|
|
@@ -3808,29 +3613,29 @@ export class ClaudeSessionService {
|
|
|
3808
3613
|
: {}),
|
|
3809
3614
|
},
|
|
3810
3615
|
],
|
|
3811
|
-
}
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3616
|
+
},
|
|
3617
|
+
projectionEntries: [userEntry],
|
|
3618
|
+
});
|
|
3619
|
+
// File-history snapshots key their metadata to the durable native
|
|
3620
|
+
// event id, not the compatibility projection id.
|
|
3621
|
+
if (nativePrompt.kind !== 'messages')
|
|
3622
|
+
throw new Error('Turn persistence returned an invalid message receipt');
|
|
3623
|
+
currentPromptId = nativePrompt.eventId;
|
|
3816
3624
|
}
|
|
3817
3625
|
if (fileHistory && currentPromptId) {
|
|
3818
|
-
const historySnapshot = await fileHistory.snapshot(
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
tail: historyTail,
|
|
3823
|
-
};
|
|
3824
|
-
if (nativeLease) {
|
|
3825
|
-
await nativeLease.appendMessages({
|
|
3626
|
+
const historySnapshot = await fileHistory.snapshot(projectionSnapshot().entries, currentPromptId);
|
|
3627
|
+
await persistence.commit({
|
|
3628
|
+
kind: 'messages',
|
|
3629
|
+
input: {
|
|
3826
3630
|
messages: [
|
|
3827
3631
|
{
|
|
3828
3632
|
role: 'user',
|
|
3829
3633
|
content: `<praxis-file-history>${JSON.stringify(historySnapshot)}</praxis-file-history>`,
|
|
3830
3634
|
},
|
|
3831
3635
|
],
|
|
3832
|
-
}
|
|
3833
|
-
|
|
3636
|
+
},
|
|
3637
|
+
projectionEntries: [historySnapshot],
|
|
3638
|
+
});
|
|
3834
3639
|
}
|
|
3835
3640
|
if (this.options.hooks && !shouldSkipUserPrompt()) {
|
|
3836
3641
|
const outcome = await this.options.hooks.run({
|
|
@@ -3844,11 +3649,7 @@ export class ClaudeSessionService {
|
|
|
3844
3649
|
throw new Error(`UserPromptSubmit hook error: ${outcome.blockedReason}`);
|
|
3845
3650
|
}
|
|
3846
3651
|
}
|
|
3847
|
-
let
|
|
3848
|
-
let shellModelUsage;
|
|
3849
|
-
let shellDurationApiMs;
|
|
3850
|
-
let shellDurationApiWithoutRetriesMs;
|
|
3851
|
-
let shellToolDurationMs;
|
|
3652
|
+
let shellResult;
|
|
3852
3653
|
if (shellCommand !== undefined) {
|
|
3853
3654
|
const call = {
|
|
3854
3655
|
id: `shell_${randomUUID().replaceAll('-', '')}`,
|
|
@@ -3860,7 +3661,6 @@ export class ClaudeSessionService {
|
|
|
3860
3661
|
callId: call.id,
|
|
3861
3662
|
command: shellCommand,
|
|
3862
3663
|
});
|
|
3863
|
-
let shellResult;
|
|
3864
3664
|
try {
|
|
3865
3665
|
shellResult = await runtime.executeDirectToolCall(call, {
|
|
3866
3666
|
cwd: this.activeCwd(),
|
|
@@ -3887,15 +3687,6 @@ export class ClaudeSessionService {
|
|
|
3887
3687
|
});
|
|
3888
3688
|
throw error;
|
|
3889
3689
|
}
|
|
3890
|
-
shellUsage = shellResult.usage ?? shellUsage;
|
|
3891
|
-
shellDurationApiMs = shellResult.durationApiMs;
|
|
3892
|
-
shellDurationApiWithoutRetriesMs =
|
|
3893
|
-
shellResult.durationApiWithoutRetriesMs;
|
|
3894
|
-
shellToolDurationMs = shellResult.durationToolMs;
|
|
3895
|
-
if (!shellResult.isError) {
|
|
3896
|
-
shellModelUsage = shellResult.modelUsage;
|
|
3897
|
-
foregroundLineChanges.add(shellResult);
|
|
3898
|
-
}
|
|
3899
3690
|
const stdout = shellResult.processOutput?.stdout ??
|
|
3900
3691
|
(shellResult.isError ? '' : shellResult.content);
|
|
3901
3692
|
const stderr = shellResult.processOutput?.stderr ??
|
|
@@ -3905,22 +3696,15 @@ export class ClaudeSessionService {
|
|
|
3905
3696
|
{ type: 'bash-input', command: shellCommand },
|
|
3906
3697
|
{ type: 'bash-output', stdout, stderr },
|
|
3907
3698
|
], {
|
|
3908
|
-
...this.translationContext(sessionId,
|
|
3699
|
+
...this.translationContext(sessionId, projectionSnapshot()),
|
|
3909
3700
|
createUuid: () => shellUuids.shift() ?? randomUUID(),
|
|
3910
3701
|
});
|
|
3911
3702
|
if (!inputEntry || !outputEntry) {
|
|
3912
3703
|
throw new Error('Could not translate shell command result');
|
|
3913
3704
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
]);
|
|
3918
|
-
snapshot = {
|
|
3919
|
-
entries: [...snapshot.entries, inputEntry, outputEntry],
|
|
3920
|
-
tail: shellAppend.tail,
|
|
3921
|
-
};
|
|
3922
|
-
if (nativeLease) {
|
|
3923
|
-
await nativeLease.appendMessages({
|
|
3705
|
+
await persistence.commit({
|
|
3706
|
+
kind: 'messages',
|
|
3707
|
+
input: {
|
|
3924
3708
|
messages: [
|
|
3925
3709
|
{
|
|
3926
3710
|
role: 'user',
|
|
@@ -3931,8 +3715,9 @@ export class ClaudeSessionService {
|
|
|
3931
3715
|
content: `<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`,
|
|
3932
3716
|
},
|
|
3933
3717
|
],
|
|
3934
|
-
}
|
|
3935
|
-
|
|
3718
|
+
},
|
|
3719
|
+
projectionEntries: [inputEntry, outputEntry],
|
|
3720
|
+
});
|
|
3936
3721
|
currentTurnUserMessages.push(`<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`);
|
|
3937
3722
|
this.options.eventSink?.({
|
|
3938
3723
|
type: 'shell-result',
|
|
@@ -3943,70 +3728,27 @@ export class ClaudeSessionService {
|
|
|
3943
3728
|
});
|
|
3944
3729
|
}
|
|
3945
3730
|
if (shellCommand !== undefined) {
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
let rawCostUsd;
|
|
3953
|
-
if (turnModelUsage) {
|
|
3954
|
-
for (const [model, usage] of Object.entries(turnModelUsage)) {
|
|
3955
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
3956
|
-
const costUsd = pricing
|
|
3957
|
-
? usageCostUsd(usage, pricing)
|
|
3958
|
-
: undefined;
|
|
3959
|
-
if (costUsd !== undefined)
|
|
3960
|
-
rawCostUsd = (rawCostUsd ?? 0) + costUsd;
|
|
3961
|
-
tracker.recordTurn({
|
|
3962
|
-
model,
|
|
3963
|
-
usage,
|
|
3964
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
3965
|
-
...(usage.webSearchRequests === undefined
|
|
3966
|
-
? {}
|
|
3967
|
-
: { webSearchRequests: usage.webSearchRequests }),
|
|
3968
|
-
});
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
let combinedToolDurationMs = 0;
|
|
3972
|
-
for (const recoveryResult of recoveryResults) {
|
|
3973
|
-
combinedToolDurationMs = addToolDuration(recoveryResult.durationToolMs, combinedToolDurationMs);
|
|
3974
|
-
}
|
|
3975
|
-
combinedToolDurationMs = addToolDuration(shellToolDurationMs, combinedToolDurationMs);
|
|
3976
|
-
tracker.recordDurations({
|
|
3977
|
-
...(shellDurationApiMs === undefined
|
|
3978
|
-
? {}
|
|
3979
|
-
: { apiDurationMs: shellDurationApiMs }),
|
|
3980
|
-
...(shellDurationApiWithoutRetriesMs === undefined
|
|
3981
|
-
? {}
|
|
3982
|
-
: {
|
|
3983
|
-
apiDurationWithoutRetriesMs: shellDurationApiWithoutRetriesMs,
|
|
3984
|
-
}),
|
|
3985
|
-
...(combinedToolDurationMs === 0
|
|
3986
|
-
? {}
|
|
3987
|
-
: { toolDurationMs: combinedToolDurationMs }),
|
|
3731
|
+
if (!shellResult)
|
|
3732
|
+
throw new Error('Shell command produced no result');
|
|
3733
|
+
const accounting = turnAccounting.complete({
|
|
3734
|
+
kind: 'shell',
|
|
3735
|
+
recovery: recoveryResults,
|
|
3736
|
+
result: shellResult,
|
|
3988
3737
|
});
|
|
3989
|
-
if (foregroundLineChanges.linesAdded !== 0 ||
|
|
3990
|
-
foregroundLineChanges.linesRemoved !== 0) {
|
|
3991
|
-
tracker.recordLineChanges({
|
|
3992
|
-
...(foregroundLineChanges.linesAdded === 0
|
|
3993
|
-
? {}
|
|
3994
|
-
: { linesAdded: foregroundLineChanges.linesAdded }),
|
|
3995
|
-
...(foregroundLineChanges.linesRemoved === 0
|
|
3996
|
-
? {}
|
|
3997
|
-
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
3998
|
-
});
|
|
3999
|
-
}
|
|
4000
3738
|
turnCompleted = true;
|
|
4001
3739
|
return {
|
|
4002
3740
|
sessionId,
|
|
4003
3741
|
text: '',
|
|
4004
|
-
usage:
|
|
4005
|
-
...(
|
|
4006
|
-
|
|
4007
|
-
|
|
3742
|
+
usage: accounting.usage,
|
|
3743
|
+
...(accounting.costUsd === undefined
|
|
3744
|
+
? {}
|
|
3745
|
+
: { costUsd: accounting.costUsd }),
|
|
3746
|
+
...(accounting.modelUsage === undefined
|
|
4008
3747
|
? {}
|
|
4009
|
-
: {
|
|
3748
|
+
: { modelUsage: accounting.modelUsage }),
|
|
3749
|
+
...(accounting.durationApiMs === undefined
|
|
3750
|
+
? {}
|
|
3751
|
+
: { durationApiMs: accounting.durationApiMs }),
|
|
4010
3752
|
};
|
|
4011
3753
|
}
|
|
4012
3754
|
if (shellCommand === undefined && budget) {
|
|
@@ -4086,9 +3828,7 @@ export class ClaudeSessionService {
|
|
|
4086
3828
|
}
|
|
4087
3829
|
const claimedAgentIds = new Set(this.sessionSubagentExecutors(sessionId, true).flatMap((executor) => executor.notificationClaimAgentIds()));
|
|
4088
3830
|
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => {
|
|
4089
|
-
const delivered = (notification) => transcriptContainsBackgroundAgentNotification(
|
|
4090
|
-
? nativeLease.activeMessages()
|
|
4091
|
-
: snapshot.entries, notification);
|
|
3831
|
+
const delivered = (notification) => transcriptContainsBackgroundAgentNotification(persistence.view().activeMessages, notification);
|
|
4092
3832
|
return this.hostedSubagents.has(executor)
|
|
4093
3833
|
? executor.reconcileDetachedNotifications(delivered)
|
|
4094
3834
|
: executor.reconcileDeliveredNotifications(delivered);
|
|
@@ -4096,11 +3836,8 @@ export class ClaudeSessionService {
|
|
|
4096
3836
|
await subagentExecutor?.hydratePersistedTasks(sessionId, this.activeCwd(), claimedAgentIds);
|
|
4097
3837
|
const background = await this.collectSubagentNotifications(sessionId, subagentExecutor ?? undefined);
|
|
4098
3838
|
if (background) {
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
await this.appendBackgroundNotification(sessionId, message, nativeLease);
|
|
4102
|
-
}
|
|
4103
|
-
}
|
|
3839
|
+
for (const message of background.messages)
|
|
3840
|
+
await this.appendBackgroundNotification(sessionId, message, persistence);
|
|
4104
3841
|
messages.push(...background.messages);
|
|
4105
3842
|
}
|
|
4106
3843
|
const workflow = await this.workflowManager?.notifications(true);
|
|
@@ -4226,103 +3963,15 @@ export class ClaudeSessionService {
|
|
|
4226
3963
|
if (!finalLeafUuid) {
|
|
4227
3964
|
throw new Error('Could not locate final assistant response');
|
|
4228
3965
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
leafUuid: finalLeafUuid,
|
|
4234
|
-
});
|
|
4235
|
-
const tail = await this.append(lease, snapshot.tail, lastPrompt);
|
|
4236
|
-
snapshot = {
|
|
4237
|
-
entries: [...snapshot.entries, lastPrompt],
|
|
4238
|
-
tail,
|
|
4239
|
-
};
|
|
4240
|
-
}
|
|
4241
|
-
const totalUsage = mergeUsage(mergeUsage(mergeUsage(recoveryUsage, compactionUsage), shellUsage), result.usage);
|
|
4242
|
-
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
4243
|
-
if (!tracker) {
|
|
4244
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
4245
|
-
}
|
|
4246
|
-
const turnModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, compactionModelUsage, shellModelUsage, result.modelUsage);
|
|
4247
|
-
const combinedDurationMs = compactionDurationMs === undefined &&
|
|
4248
|
-
result.durationApiMs === undefined
|
|
4249
|
-
? undefined
|
|
4250
|
-
: (compactionDurationMs ?? 0) + (result.durationApiMs ?? 0);
|
|
4251
|
-
let rawCostUsd;
|
|
4252
|
-
if (turnModelUsage) {
|
|
4253
|
-
for (const [model, usage] of Object.entries(turnModelUsage)) {
|
|
4254
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4255
|
-
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4256
|
-
if (costUsd !== undefined)
|
|
4257
|
-
rawCostUsd = (rawCostUsd ?? 0) + costUsd;
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
// Auto-compaction metering was already recorded atomically with each
|
|
4261
|
-
// committed boundary, and externally metered tool-summary metrics were
|
|
4262
|
-
// committed through the summary callback, so the live tracker receives
|
|
4263
|
-
// only the unrecorded subset here. The inclusive public aggregates
|
|
4264
|
-
// above still contain every row and duration.
|
|
4265
|
-
const trackedModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, shellModelUsage, result.unrecordedModelUsage ?? result.modelUsage);
|
|
4266
|
-
if (trackedModelUsage) {
|
|
4267
|
-
for (const [model, usage] of Object.entries(trackedModelUsage)) {
|
|
4268
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4269
|
-
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4270
|
-
tracker.recordTurn({
|
|
4271
|
-
model,
|
|
4272
|
-
usage,
|
|
4273
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
4274
|
-
...(usage.webSearchRequests === undefined
|
|
4275
|
-
? {}
|
|
4276
|
-
: { webSearchRequests: usage.webSearchRequests }),
|
|
4277
|
-
});
|
|
4278
|
-
}
|
|
4279
|
-
}
|
|
4280
|
-
let combinedToolDurationMs = 0;
|
|
4281
|
-
for (const recoveryResult of recoveryResults) {
|
|
4282
|
-
combinedToolDurationMs = addToolDuration(recoveryResult.durationToolMs, combinedToolDurationMs);
|
|
4283
|
-
}
|
|
4284
|
-
combinedToolDurationMs = addToolDuration(result.durationToolMs, combinedToolDurationMs);
|
|
4285
|
-
const trackedDurationApiMs = result.unrecordedDurationApiMs ?? result.durationApiMs;
|
|
4286
|
-
const trackedDurationApiWithoutRetriesMs = result.unrecordedDurationApiWithoutRetriesMs ??
|
|
4287
|
-
result.durationApiWithoutRetriesMs;
|
|
4288
|
-
tracker.recordDurations({
|
|
4289
|
-
...(trackedDurationApiMs === undefined
|
|
4290
|
-
? {}
|
|
4291
|
-
: { apiDurationMs: trackedDurationApiMs }),
|
|
4292
|
-
...(trackedDurationApiWithoutRetriesMs === undefined
|
|
4293
|
-
? {}
|
|
4294
|
-
: {
|
|
4295
|
-
apiDurationWithoutRetriesMs: trackedDurationApiWithoutRetriesMs,
|
|
4296
|
-
}),
|
|
4297
|
-
...(combinedToolDurationMs === 0
|
|
4298
|
-
? {}
|
|
4299
|
-
: { toolDurationMs: combinedToolDurationMs }),
|
|
4300
|
-
});
|
|
4301
|
-
foregroundLineChanges.add({
|
|
4302
|
-
isError: false,
|
|
4303
|
-
...(result.linesAdded === undefined
|
|
4304
|
-
? {}
|
|
4305
|
-
: { linesAdded: result.linesAdded }),
|
|
4306
|
-
...(result.linesRemoved === undefined
|
|
4307
|
-
? {}
|
|
4308
|
-
: { linesRemoved: result.linesRemoved }),
|
|
3966
|
+
const accounting = turnAccounting.complete({
|
|
3967
|
+
kind: 'runtime',
|
|
3968
|
+
recovery: recoveryResults,
|
|
3969
|
+
result,
|
|
4309
3970
|
});
|
|
4310
|
-
if (foregroundLineChanges.linesAdded !== 0 ||
|
|
4311
|
-
foregroundLineChanges.linesRemoved !== 0) {
|
|
4312
|
-
tracker.recordLineChanges({
|
|
4313
|
-
...(foregroundLineChanges.linesAdded === 0
|
|
4314
|
-
? {}
|
|
4315
|
-
: { linesAdded: foregroundLineChanges.linesAdded }),
|
|
4316
|
-
...(foregroundLineChanges.linesRemoved === 0
|
|
4317
|
-
? {}
|
|
4318
|
-
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
4319
|
-
});
|
|
4320
|
-
}
|
|
4321
3971
|
const memorySnapshot = activeTurnMessages();
|
|
4322
|
-
const nativeMemoryMessageId =
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
: undefined;
|
|
3972
|
+
const nativeMemoryMessageId = persistence
|
|
3973
|
+
.view()
|
|
3974
|
+
.projectionEntries.at(-1)?.uuid;
|
|
4326
3975
|
const providerProjection = contextPreparation.project({
|
|
4327
3976
|
includeMemory: false,
|
|
4328
3977
|
});
|
|
@@ -4344,18 +3993,16 @@ export class ClaudeSessionService {
|
|
|
4344
3993
|
occupancyTokens: currentContextTokens,
|
|
4345
3994
|
toolCalls: currentTurnToolCalls,
|
|
4346
3995
|
messages: memorySnapshot,
|
|
4347
|
-
projectMessages:
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
{
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
: [])
|
|
4358
|
-
: projectMemoryMessages(snapshot.entries),
|
|
3996
|
+
projectMessages: memorySnapshot.flatMap((message, index) => (message.role === 'user' || message.role === 'assistant') &&
|
|
3997
|
+
message.content.trim().length > 0
|
|
3998
|
+
? [
|
|
3999
|
+
{
|
|
4000
|
+
id: `${finalLeafUuid ?? sessionId}:${index}`,
|
|
4001
|
+
role: message.role,
|
|
4002
|
+
content: message.content,
|
|
4003
|
+
},
|
|
4004
|
+
]
|
|
4005
|
+
: []),
|
|
4359
4006
|
directMaintenance: projectMemoryMaintained,
|
|
4360
4007
|
});
|
|
4361
4008
|
turnCompleted = true;
|
|
@@ -4364,12 +4011,16 @@ export class ClaudeSessionService {
|
|
|
4364
4011
|
text: structuredCapture && structuredCapture.calls === 1
|
|
4365
4012
|
? JSON.stringify(structuredCapture.value)
|
|
4366
4013
|
: result.text,
|
|
4367
|
-
usage:
|
|
4368
|
-
...(
|
|
4014
|
+
usage: accounting.usage,
|
|
4015
|
+
...(accounting.durationApiMs === undefined
|
|
4016
|
+
? {}
|
|
4017
|
+
: { durationApiMs: accounting.durationApiMs }),
|
|
4018
|
+
...(accounting.costUsd === undefined
|
|
4369
4019
|
? {}
|
|
4370
|
-
: {
|
|
4371
|
-
...(
|
|
4372
|
-
|
|
4020
|
+
: { costUsd: accounting.costUsd }),
|
|
4021
|
+
...(accounting.modelUsage === undefined
|
|
4022
|
+
? {}
|
|
4023
|
+
: { modelUsage: accounting.modelUsage }),
|
|
4373
4024
|
...(structuredCapture && structuredCapture.calls === 1
|
|
4374
4025
|
? { structuredOutput: structuredCapture.value }
|
|
4375
4026
|
: {}),
|
|
@@ -4405,12 +4056,8 @@ export class ClaudeSessionService {
|
|
|
4405
4056
|
: { kind: 'resume', atEventId: resumeSessionAt }
|
|
4406
4057
|
: { kind: 'start' };
|
|
4407
4058
|
result = await nativeTranscript.withLease(activationKind, async (nativeLease) => {
|
|
4408
|
-
const
|
|
4409
|
-
|
|
4410
|
-
if (scratchResult.status === 'conflict') {
|
|
4411
|
-
throw new Error(`native scratch transcript conflict: ${scratchResult.reason}`);
|
|
4412
|
-
}
|
|
4413
|
-
return scratchResult.value;
|
|
4059
|
+
const persistence = new TurnPersistence({ native: nativeLease });
|
|
4060
|
+
return runUnderLease(persistence);
|
|
4414
4061
|
});
|
|
4415
4062
|
}
|
|
4416
4063
|
return result;
|
|
@@ -4788,6 +4435,16 @@ export class ClaudeSessionService {
|
|
|
4788
4435
|
});
|
|
4789
4436
|
}
|
|
4790
4437
|
async appendBackgroundNotification(sessionId, content, nativeLease) {
|
|
4438
|
+
if (nativeLease instanceof TurnPersistence) {
|
|
4439
|
+
if (nativeLease.view().activeMessages.length === 0) {
|
|
4440
|
+
throw new Error(`Native session not found: ${sessionId}`);
|
|
4441
|
+
}
|
|
4442
|
+
await nativeLease.commit({
|
|
4443
|
+
kind: 'messages',
|
|
4444
|
+
input: { messages: [{ role: 'user', content }] },
|
|
4445
|
+
});
|
|
4446
|
+
return true;
|
|
4447
|
+
}
|
|
4791
4448
|
if (nativeLease) {
|
|
4792
4449
|
if (nativeLease.activeMessages().length === 0) {
|
|
4793
4450
|
throw new Error(`Native session not found: ${sessionId}`);
|
|
@@ -5177,32 +4834,6 @@ export class ClaudeSessionService {
|
|
|
5177
4834
|
}
|
|
5178
4835
|
return factory(base, operations, sessionId, enabledTools);
|
|
5179
4836
|
}
|
|
5180
|
-
async append(lease, tail, entry) {
|
|
5181
|
-
void lease;
|
|
5182
|
-
return this.appendProjectionMany(tail, [entry]).then((result) => result.tail);
|
|
5183
|
-
}
|
|
5184
|
-
/**
|
|
5185
|
-
* Update the in-memory compatibility projection used by the turn pipeline.
|
|
5186
|
-
* Authoritative native persistence is performed by NativeSessionTranscript;
|
|
5187
|
-
* these entries are never written to the native event store.
|
|
5188
|
-
*/
|
|
5189
|
-
async appendProjectionMany(tail, entries) {
|
|
5190
|
-
if (entries.length === 0)
|
|
5191
|
-
throw new Error('Cannot append an empty projection');
|
|
5192
|
-
const lastUuid = [...entries]
|
|
5193
|
-
.reverse()
|
|
5194
|
-
.find((entry) => typeof entry.uuid === 'string')?.uuid;
|
|
5195
|
-
return {
|
|
5196
|
-
status: 'appended',
|
|
5197
|
-
tail: {
|
|
5198
|
-
...tail,
|
|
5199
|
-
byteLength: tail.byteLength + entries.length,
|
|
5200
|
-
lastLineHash: `projection:${tail.byteLength + entries.length}`,
|
|
5201
|
-
lastEventId: typeof lastUuid === 'string' ? lastUuid : tail.lastEventId,
|
|
5202
|
-
...(tail.branchParentId === undefined ? {} : { branchParentId: null }),
|
|
5203
|
-
},
|
|
5204
|
-
};
|
|
5205
|
-
}
|
|
5206
4837
|
logicalTailUuid(tail) {
|
|
5207
4838
|
return tail.branchParentId ?? tail.lastEventId;
|
|
5208
4839
|
}
|