praxis-agent 0.55.0 → 0.55.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 +1 -1
- package/dist/application/context-engine.js +4 -1
- package/dist/application/context-preparation.d.ts +60 -0
- package/dist/application/context-preparation.js +139 -0
- package/dist/application/session-service.d.ts +1 -8
- package/dist/application/session-service.js +362 -844
- package/dist/application/turn-accounting.d.ts +74 -0
- package/dist/application/turn-accounting.js +444 -0
- package/dist/application/turn-lifecycle.d.ts +21 -9
- package/dist/application/turn-lifecycle.js +141 -27
- package/dist/application/turn-persistence.d.ts +62 -0
- package/dist/application/turn-persistence.js +114 -0
- package/dist/cli/protocol.d.ts +16 -4
- package/dist/cli/protocol.js +136 -100
- package/dist/cli-runtime.js +10 -2
- package/package.json +1 -1
|
@@ -8,22 +8,21 @@ 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';
|
|
21
20
|
import { backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
22
21
|
import { usageCostUsd } from '../core/usage.js';
|
|
23
22
|
import { isSessionId } from '../core/session.js';
|
|
24
|
-
import { ActiveTurnInputMailbox, } from '../core/active-turn-input.js';
|
|
25
23
|
import { ContextBudget, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
26
24
|
import { ContextEngine } from './context-engine.js';
|
|
25
|
+
import { ContextPreparation } from './context-preparation.js';
|
|
27
26
|
import { TurnMemoryCoordinator } from './turn-memory-coordinator.js';
|
|
28
27
|
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
29
28
|
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
@@ -34,11 +33,13 @@ 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';
|
|
40
41
|
import { ScheduledPromptManager, } from './scheduled-prompt-manager.js';
|
|
41
|
-
import {
|
|
42
|
+
import { TurnCoordinator } from './turn-lifecycle.js';
|
|
42
43
|
import { ClaudeScheduledToolRegistry } from '../tools/claude-scheduled-tools.js';
|
|
43
44
|
import { ClaudeTaskToolRegistry } from '../tools/claude-task-tools.js';
|
|
44
45
|
import { ClaudeWorkflowToolRegistry } from '../tools/claude-workflow-tools.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();
|
|
@@ -684,12 +627,16 @@ export class ClaudeSessionService {
|
|
|
684
627
|
hookLifecycle;
|
|
685
628
|
leadOperations;
|
|
686
629
|
fileChangeWatcher;
|
|
687
|
-
|
|
630
|
+
turnCoordinator;
|
|
688
631
|
runtimeCwd;
|
|
689
632
|
constructor(options) {
|
|
690
633
|
const dataPlane = options.dataPlane ?? 'native';
|
|
691
634
|
assertNativeDataPlane(dataPlane);
|
|
692
635
|
this.options = { ...options, dataPlane };
|
|
636
|
+
this.turnCoordinator = new TurnCoordinator({
|
|
637
|
+
eventSink: options.eventSink ?? (() => undefined),
|
|
638
|
+
createSteeringId: randomUUID,
|
|
639
|
+
});
|
|
693
640
|
this.assertNativeTranscriptOptions();
|
|
694
641
|
this.leadOperations = options.teamLeadOperations ?? null;
|
|
695
642
|
this.hookLifecycle = new HookLifecycle(options.hooks, options.eventSink);
|
|
@@ -899,18 +846,7 @@ export class ClaudeSessionService {
|
|
|
899
846
|
}
|
|
900
847
|
async close() {
|
|
901
848
|
this.closing = true;
|
|
902
|
-
|
|
903
|
-
if (!mailbox)
|
|
904
|
-
continue;
|
|
905
|
-
for (const item of mailbox.close()) {
|
|
906
|
-
this.options.eventSink?.({
|
|
907
|
-
type: 'user-input-rejected',
|
|
908
|
-
id: item.id,
|
|
909
|
-
content: item.content,
|
|
910
|
-
reason: 'closed',
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
}
|
|
849
|
+
this.turnCoordinator.close();
|
|
914
850
|
await this.fileChangeWatcher?.close(5_000);
|
|
915
851
|
await this.hookLifecycle.close();
|
|
916
852
|
await this.drainDetachedHookRuns(5_000);
|
|
@@ -1330,28 +1266,10 @@ export class ClaudeSessionService {
|
|
|
1330
1266
|
});
|
|
1331
1267
|
}
|
|
1332
1268
|
steer(sessionId, content) {
|
|
1333
|
-
|
|
1334
|
-
if (!active)
|
|
1335
|
-
return { kind: 'no-active-turn' };
|
|
1336
|
-
const mailbox = active.mailbox;
|
|
1337
|
-
if (!mailbox)
|
|
1338
|
-
return { kind: 'not-steerable' };
|
|
1339
|
-
const result = mailbox.enqueue(content);
|
|
1340
|
-
if (result.kind === 'accepted')
|
|
1341
|
-
return result;
|
|
1342
|
-
if (result.kind === 'empty')
|
|
1343
|
-
return result;
|
|
1344
|
-
return { kind: 'turn-completing' };
|
|
1269
|
+
return this.turnCoordinator.steer(sessionId, content);
|
|
1345
1270
|
}
|
|
1346
1271
|
withdrawSteering(sessionId, id) {
|
|
1347
|
-
|
|
1348
|
-
if (!active)
|
|
1349
|
-
return { kind: 'no-active-turn' };
|
|
1350
|
-
const mailbox = active.mailbox;
|
|
1351
|
-
if (!mailbox)
|
|
1352
|
-
return { kind: 'not-steerable' };
|
|
1353
|
-
const result = mailbox.withdraw(id);
|
|
1354
|
-
return result.kind === 'withdrawn' ? result : { kind: 'not-pending' };
|
|
1272
|
+
return this.turnCoordinator.withdrawSteering(sessionId, id);
|
|
1355
1273
|
}
|
|
1356
1274
|
async resumeShell(sessionId, command, signal, name, resumeSessionAt) {
|
|
1357
1275
|
this.worktreeManager?.bindSession(sessionId);
|
|
@@ -2535,29 +2453,8 @@ export class ClaudeSessionService {
|
|
|
2535
2453
|
const documents = submission.kind === 'prompt' ? (submission.documents ?? []) : [];
|
|
2536
2454
|
const shellCommand = submission.kind === 'shell' ? submission.command : undefined;
|
|
2537
2455
|
const skipUserPrompt = submission.kind === 'retry';
|
|
2538
|
-
|
|
2539
|
-
let activeTurnInput;
|
|
2540
|
-
let activeTurnRecord;
|
|
2541
|
-
try {
|
|
2456
|
+
return this.turnCoordinator.run(request, async ({ emit, steering }) => {
|
|
2542
2457
|
this.assertTurnWritable();
|
|
2543
|
-
if (prompt.length === 0 && images.length === 0 && documents.length === 0)
|
|
2544
|
-
throw new Error('Prompt must not be empty');
|
|
2545
|
-
if (name !== undefined && name.length === 0) {
|
|
2546
|
-
throw new Error('Session name must not be empty');
|
|
2547
|
-
}
|
|
2548
|
-
if (shellCommand !== undefined && shellCommand.trim().length === 0) {
|
|
2549
|
-
throw new Error('Shell command must not be empty');
|
|
2550
|
-
}
|
|
2551
|
-
if (this.activeTurnInputs.has(sessionId)) {
|
|
2552
|
-
throw new Error(`conflict: locked (session ${sessionId} already has an active turn)`);
|
|
2553
|
-
}
|
|
2554
|
-
activeTurnRecord =
|
|
2555
|
-
shellCommand === undefined
|
|
2556
|
-
? {
|
|
2557
|
-
mailbox: (activeTurnInput = new ActiveTurnInputMailbox(randomUUID)),
|
|
2558
|
-
}
|
|
2559
|
-
: {};
|
|
2560
|
-
this.activeTurnInputs.set(sessionId, activeTurnRecord);
|
|
2561
2458
|
await this.activateSessionCostTracker(sessionId);
|
|
2562
2459
|
await this.ensureFileResources(sessionId, signal);
|
|
2563
2460
|
this.worktreeManager?.bindSession(sessionId);
|
|
@@ -2575,39 +2472,47 @@ export class ClaudeSessionService {
|
|
|
2575
2472
|
}
|
|
2576
2473
|
const sessionPaths = this.paths(sessionId);
|
|
2577
2474
|
const toolResultDirectory = join(sessionPaths.projectRoot, sessionId, 'tool-results');
|
|
2578
|
-
const runUnderLease = async (
|
|
2579
|
-
const
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
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 });
|
|
2583
2493
|
};
|
|
2584
2494
|
const activeTurnMessages = () => {
|
|
2585
2495
|
// Native events remain authoritative; refresh the derived projection
|
|
2586
2496
|
// after each append so memory/compaction sees the live branch.
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
entries: projectNativeSessionEntries(nativeLease.activeEvents()),
|
|
2590
|
-
};
|
|
2591
|
-
return nativeLease.activeMessages();
|
|
2497
|
+
const view = persistence.refresh();
|
|
2498
|
+
return [...view.activeMessages];
|
|
2592
2499
|
};
|
|
2593
|
-
if (
|
|
2594
|
-
name !== undefined &&
|
|
2595
|
-
this.options.sessionPersistence !== false) {
|
|
2500
|
+
if (name !== undefined && this.options.sessionPersistence !== false) {
|
|
2596
2501
|
const marker = `<praxis-session-name>${name}</praxis-session-name>`;
|
|
2597
|
-
if (!
|
|
2598
|
-
.
|
|
2599
|
-
.some((event) => event.kind === 'messages' &&
|
|
2502
|
+
if (!persistence
|
|
2503
|
+
.view()
|
|
2504
|
+
.activeEvents.some((event) => event.kind === 'messages' &&
|
|
2600
2505
|
event.messages.some((message) => message.role === 'user' && message.content === marker))) {
|
|
2601
|
-
await
|
|
2602
|
-
|
|
2506
|
+
await persistence.commit({
|
|
2507
|
+
kind: 'messages',
|
|
2508
|
+
input: { messages: [{ role: 'user', content: marker }] },
|
|
2603
2509
|
});
|
|
2604
2510
|
}
|
|
2605
2511
|
}
|
|
2606
|
-
const nativeInterruption =
|
|
2512
|
+
const nativeInterruption = persistence.view().interruption;
|
|
2607
2513
|
if (nativeInterruption?.kind === 'indeterminate-tools')
|
|
2608
2514
|
throw new Error(`Native tool execution is indeterminate: ${nativeInterruption.callIds.join(', ')}`);
|
|
2609
|
-
const nativeResumeInterrupted =
|
|
2610
|
-
this.options.resumeInterruptedTurn === true &&
|
|
2515
|
+
const nativeResumeInterrupted = this.options.resumeInterruptedTurn === true &&
|
|
2611
2516
|
(nativeInterruption?.kind === 'interrupted-prompt' ||
|
|
2612
2517
|
nativeInterruption?.kind === 'interrupted-turn');
|
|
2613
2518
|
const shouldSkipUserPrompt = () => skipUserPrompt || nativeResumeInterrupted;
|
|
@@ -2618,48 +2523,35 @@ export class ClaudeSessionService {
|
|
|
2618
2523
|
shellCommand === undefined &&
|
|
2619
2524
|
!shouldSkipUserPrompt() &&
|
|
2620
2525
|
!(this.options.approveRecovery !== undefined &&
|
|
2621
|
-
findUnresolvedClaudeToolCalls(
|
|
2622
|
-
|
|
2526
|
+
findUnresolvedClaudeToolCalls(projectionSnapshot().entries).length >
|
|
2527
|
+
0)) {
|
|
2528
|
+
const interruption = classifyClaudeInterruption(projectionSnapshot().entries);
|
|
2623
2529
|
if ((interruption.kind === 'interrupted-prompt' ||
|
|
2624
2530
|
interruption.kind === 'interrupted-turn') &&
|
|
2625
2531
|
interruption.prompt !== undefined &&
|
|
2626
2532
|
interruption.replayEntries !== undefined) {
|
|
2627
2533
|
automaticReplayPrompt = interruption.prompt;
|
|
2628
|
-
snapshot = {
|
|
2629
|
-
entries: interruption.replayEntries,
|
|
2630
|
-
tail: {
|
|
2631
|
-
...snapshot.tail,
|
|
2632
|
-
branchParentId: interruption.replayParentUuid ?? null,
|
|
2633
|
-
},
|
|
2634
|
-
};
|
|
2635
2534
|
}
|
|
2636
2535
|
}
|
|
2637
2536
|
const initialTransition = this.worktreeManager?.consumeTransition('__initial__');
|
|
2638
|
-
if (initialTransition &&
|
|
2537
|
+
if (initialTransition && projectionSnapshot().entries.length === 0) {
|
|
2639
2538
|
const stateEntry = {
|
|
2640
2539
|
type: 'worktree-state',
|
|
2641
2540
|
worktreeSession: initialTransition.state,
|
|
2642
2541
|
sessionId,
|
|
2643
2542
|
};
|
|
2644
|
-
|
|
2645
|
-
snapshot = {
|
|
2646
|
-
entries: [...snapshot.entries, stateEntry],
|
|
2647
|
-
tail: stateTail,
|
|
2648
|
-
};
|
|
2543
|
+
await commitProjection([stateEntry]);
|
|
2649
2544
|
}
|
|
2650
|
-
this.restoreWorktree(
|
|
2651
|
-
this.options.interactiveTools?.restore(sessionId,
|
|
2545
|
+
this.restoreWorktree(projectionSnapshot().entries);
|
|
2546
|
+
this.options.interactiveTools?.restore(sessionId, projectionSnapshot().entries);
|
|
2652
2547
|
if (requireExisting &&
|
|
2653
2548
|
name !== undefined &&
|
|
2654
|
-
!this.hasSessionName(
|
|
2549
|
+
!this.hasSessionName(projectionSnapshot().entries, name)) {
|
|
2655
2550
|
const entries = this.sessionNameEntries(sessionId, name);
|
|
2656
|
-
|
|
2657
|
-
snapshot = {
|
|
2658
|
-
entries: [...snapshot.entries, ...entries],
|
|
2659
|
-
tail: appendResult.tail,
|
|
2660
|
-
};
|
|
2551
|
+
await commitProjection(entries);
|
|
2661
2552
|
}
|
|
2662
|
-
const agentName = this.options.agent ??
|
|
2553
|
+
const agentName = this.options.agent ??
|
|
2554
|
+
getClaudeAgentSetting(projectionSnapshot().entries);
|
|
2663
2555
|
const agent = this.resolveAgent(agentName);
|
|
2664
2556
|
const provider = this.providerForAgent(agent);
|
|
2665
2557
|
this.activeProvider = provider;
|
|
@@ -2706,11 +2598,9 @@ export class ClaudeSessionService {
|
|
|
2706
2598
|
...(this.options.fileRewindRoots ?? []),
|
|
2707
2599
|
])
|
|
2708
2600
|
: null;
|
|
2709
|
-
const unresolvedToolCalls =
|
|
2710
|
-
? nativeInterruption
|
|
2711
|
-
|
|
2712
|
-
: []
|
|
2713
|
-
: findUnresolvedClaudeToolCalls(snapshot.entries);
|
|
2601
|
+
const unresolvedToolCalls = nativeInterruption?.kind === 'recoverable-tools'
|
|
2602
|
+
? [...nativeInterruption.calls]
|
|
2603
|
+
: [];
|
|
2714
2604
|
const pendingRecoveryToolCallIds = new Set(unresolvedToolCalls.map((call) => call.id));
|
|
2715
2605
|
const pendingRecoveryHookOutcomes = [];
|
|
2716
2606
|
let refreshRuntimeContext;
|
|
@@ -2724,26 +2614,25 @@ export class ClaudeSessionService {
|
|
|
2724
2614
|
permission_mode: 'default',
|
|
2725
2615
|
};
|
|
2726
2616
|
const appendHookOutcome = async (outcome) => {
|
|
2727
|
-
for (const entry of createClaudeHookAttachmentEntries(outcome, this.translationContext(sessionId,
|
|
2728
|
-
const
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
}
|
|
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]);
|
|
2747
2636
|
}
|
|
2748
2637
|
}
|
|
2749
2638
|
};
|
|
@@ -2756,11 +2645,11 @@ export class ClaudeSessionService {
|
|
|
2756
2645
|
};
|
|
2757
2646
|
const flushRecoveryHookOutcomes = async () => {
|
|
2758
2647
|
const entries = [];
|
|
2759
|
-
let history =
|
|
2760
|
-
let parentUuid = this.logicalTailUuid(
|
|
2648
|
+
let history = projectionSnapshot().entries;
|
|
2649
|
+
let parentUuid = this.logicalTailUuid(projectionSnapshot().tail);
|
|
2761
2650
|
for (const outcome of pendingRecoveryHookOutcomes) {
|
|
2762
2651
|
const outcomeEntries = createClaudeHookAttachmentEntries(outcome, {
|
|
2763
|
-
...this.translationContext(sessionId,
|
|
2652
|
+
...this.translationContext(sessionId, projectionSnapshot()),
|
|
2764
2653
|
parentUuid,
|
|
2765
2654
|
history,
|
|
2766
2655
|
});
|
|
@@ -2774,8 +2663,7 @@ export class ClaudeSessionService {
|
|
|
2774
2663
|
pendingRecoveryHookOutcomes.length = 0;
|
|
2775
2664
|
return;
|
|
2776
2665
|
}
|
|
2777
|
-
|
|
2778
|
-
snapshot = { entries: history, tail: appendResult.tail };
|
|
2666
|
+
await commitProjection(entries);
|
|
2779
2667
|
pendingRecoveryHookOutcomes.length = 0;
|
|
2780
2668
|
};
|
|
2781
2669
|
const capabilities = this.toolCapabilities();
|
|
@@ -2916,7 +2804,7 @@ export class ClaudeSessionService {
|
|
|
2916
2804
|
backgroundTaskNotifications: (waitForRunning) => taskTools.notifications(waitForRunning),
|
|
2917
2805
|
}
|
|
2918
2806
|
: {}),
|
|
2919
|
-
notificationDelivered: (notification) => transcriptContainsBackgroundAgentNotification(
|
|
2807
|
+
notificationDelivered: (notification) => transcriptContainsBackgroundAgentNotification(projectionSnapshot().entries, notification),
|
|
2920
2808
|
stopOwnedBackgroundAgent: (ownerSessionId, agentId) => this.backgroundTasks.stopAgent(ownerSessionId, agentId),
|
|
2921
2809
|
outputOwnedBackgroundAgent: (ownerSessionId, agentId, options) => this.backgroundTasks.outputAgent(ownerSessionId, agentId, options),
|
|
2922
2810
|
sendOwnedBackgroundAgent: (ownerSessionId, agentId, message, summary, toolUseId) => this.sendOwnedBackgroundAgent(ownerSessionId, agentId, message, summary, toolUseId),
|
|
@@ -2929,7 +2817,7 @@ export class ClaudeSessionService {
|
|
|
2929
2817
|
}
|
|
2930
2818
|
const agentTools = subagentExecutor
|
|
2931
2819
|
? subagentExecutor.registry(sessionId, 0, (callId) => currentPromptId ??
|
|
2932
|
-
this.promptIdForToolCall(
|
|
2820
|
+
this.promptIdForToolCall(projectionSnapshot().entries, callId))
|
|
2933
2821
|
: baseTools;
|
|
2934
2822
|
const turnTools = this.workflowManager && subagentExecutor && agentTools
|
|
2935
2823
|
? new ClaudeWorkflowToolRegistry({
|
|
@@ -2941,7 +2829,7 @@ export class ClaudeSessionService {
|
|
|
2941
2829
|
configRoot: this.options.configRoot,
|
|
2942
2830
|
sessionId,
|
|
2943
2831
|
promptIdForCall: (callId) => currentPromptId ??
|
|
2944
|
-
this.promptIdForToolCall(
|
|
2832
|
+
this.promptIdForToolCall(projectionSnapshot().entries, callId),
|
|
2945
2833
|
defaultModel: provider.model ?? 'praxis/provider',
|
|
2946
2834
|
tokenBudget: workflowTokenTarget(effectivePrompt),
|
|
2947
2835
|
enabled: capabilities.has('Workflow'),
|
|
@@ -2994,13 +2882,13 @@ export class ClaudeSessionService {
|
|
|
2994
2882
|
return interactiveMessageTools.execute(call, context);
|
|
2995
2883
|
}
|
|
2996
2884
|
const snapshotMessageId = currentPromptId ??
|
|
2997
|
-
this.promptIdForToolCall(
|
|
2885
|
+
this.promptIdForToolCall(projectionSnapshot().entries, call.id);
|
|
2998
2886
|
const assistantMessageId = lastAssistantUuid ??
|
|
2999
|
-
this.assistantIdForToolCall(
|
|
2887
|
+
this.assistantIdForToolCall(projectionSnapshot().entries, call.id);
|
|
3000
2888
|
if (!snapshotMessageId || !assistantMessageId) {
|
|
3001
2889
|
throw new Error('Claude file history could not link tool call');
|
|
3002
2890
|
}
|
|
3003
|
-
const prepared = await fileHistory.prepareMutation(
|
|
2891
|
+
const prepared = await fileHistory.prepareMutation(projectionSnapshot().entries, snapshotMessageId, path);
|
|
3004
2892
|
let result;
|
|
3005
2893
|
try {
|
|
3006
2894
|
result = await interactiveMessageTools.execute(call, context);
|
|
@@ -3015,18 +2903,18 @@ export class ClaudeSessionService {
|
|
|
3015
2903
|
}
|
|
3016
2904
|
const entry = prepared.commit(assistantMessageId);
|
|
3017
2905
|
if (entry) {
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
await nativeLease.appendMessages({
|
|
2906
|
+
await persistence.commit({
|
|
2907
|
+
kind: 'messages',
|
|
2908
|
+
input: {
|
|
3022
2909
|
messages: [
|
|
3023
2910
|
{
|
|
3024
2911
|
role: 'user',
|
|
3025
2912
|
content: `<praxis-file-history>${JSON.stringify(entry)}</praxis-file-history>`,
|
|
3026
2913
|
},
|
|
3027
2914
|
],
|
|
3028
|
-
}
|
|
3029
|
-
|
|
2915
|
+
},
|
|
2916
|
+
projectionEntries: [entry],
|
|
2917
|
+
});
|
|
3030
2918
|
}
|
|
3031
2919
|
return result;
|
|
3032
2920
|
},
|
|
@@ -3071,7 +2959,7 @@ export class ClaudeSessionService {
|
|
|
3071
2959
|
deferPreToolUseOutcome: (call) => pendingRecoveryToolCallIds.has(call.id),
|
|
3072
2960
|
})
|
|
3073
2961
|
: null;
|
|
3074
|
-
const runtime = new AgentRuntime(provider,
|
|
2962
|
+
const runtime = new AgentRuntime(provider, emit, {
|
|
3075
2963
|
emitInitialContextState: false,
|
|
3076
2964
|
...(this.options.emitToolUseSummaries
|
|
3077
2965
|
? {
|
|
@@ -3104,14 +2992,13 @@ export class ClaudeSessionService {
|
|
|
3104
2992
|
const durableFollowUps = new DurableFollowUpTracker();
|
|
3105
2993
|
const claimedToolCallIds = new Set();
|
|
3106
2994
|
const observer = {
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
: {}),
|
|
2995
|
+
toolExecutionStarted: async (call) => {
|
|
2996
|
+
await persistence.commit({
|
|
2997
|
+
kind: 'tool-execution-started',
|
|
2998
|
+
callId: call.id,
|
|
2999
|
+
});
|
|
3000
|
+
claimedToolCallIds.add(call.id);
|
|
3001
|
+
},
|
|
3115
3002
|
modelRequestCompleted: async (input) => {
|
|
3116
3003
|
observeModelRequestUsage(input);
|
|
3117
3004
|
},
|
|
@@ -3127,13 +3014,12 @@ export class ClaudeSessionService {
|
|
|
3127
3014
|
providerMessageId: `msg_${randomUUID().replaceAll('-', '')}`,
|
|
3128
3015
|
model: provider.model ?? 'praxis/provider',
|
|
3129
3016
|
},
|
|
3130
|
-
], this.translationContext(sessionId,
|
|
3017
|
+
], this.translationContext(sessionId, projectionSnapshot()));
|
|
3131
3018
|
if (!entry)
|
|
3132
3019
|
throw new Error('Could not translate assistant response');
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
? await nativeLease.appendMessages({
|
|
3020
|
+
const nativeReceipt = await persistence.commit({
|
|
3021
|
+
kind: 'messages',
|
|
3022
|
+
input: {
|
|
3137
3023
|
messages: [
|
|
3138
3024
|
{
|
|
3139
3025
|
role: 'assistant',
|
|
@@ -3147,29 +3033,26 @@ export class ClaudeSessionService {
|
|
|
3147
3033
|
},
|
|
3148
3034
|
],
|
|
3149
3035
|
model: provider.model ?? 'praxis/provider',
|
|
3150
|
-
}
|
|
3151
|
-
:
|
|
3152
|
-
|
|
3153
|
-
|
|
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;
|
|
3154
3042
|
},
|
|
3155
3043
|
toolCompleted: async (call, toolResult) => {
|
|
3156
|
-
currentTurnToolCalls += 1;
|
|
3157
3044
|
const transition = this.worktreeManager?.consumeTransition(call.id);
|
|
3158
3045
|
if (transition) {
|
|
3159
|
-
this.options.contextAssembler?.invalidate?.({
|
|
3160
|
-
lifecycleId: sessionId,
|
|
3161
|
-
reason: 'worktree',
|
|
3162
|
-
});
|
|
3163
3046
|
const stateEntry = {
|
|
3164
3047
|
type: 'worktree-state',
|
|
3165
3048
|
worktreeSession: transition.state,
|
|
3166
3049
|
sessionId,
|
|
3167
3050
|
};
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
};
|
|
3051
|
+
await commitProjection([stateEntry]);
|
|
3052
|
+
this.options.contextAssembler?.invalidate?.({
|
|
3053
|
+
lifecycleId: sessionId,
|
|
3054
|
+
reason: 'worktree',
|
|
3055
|
+
});
|
|
3173
3056
|
}
|
|
3174
3057
|
const permissionMode = this.options.interactiveTools?.consumeTransition(call.id);
|
|
3175
3058
|
if (permissionMode) {
|
|
@@ -3178,21 +3061,18 @@ export class ClaudeSessionService {
|
|
|
3178
3061
|
permissionMode,
|
|
3179
3062
|
sessionId,
|
|
3180
3063
|
};
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
tail: modeTail,
|
|
3185
|
-
};
|
|
3186
|
-
if (nativeLease) {
|
|
3187
|
-
await nativeLease.appendMessages({
|
|
3064
|
+
await persistence.commit({
|
|
3065
|
+
kind: 'messages',
|
|
3066
|
+
input: {
|
|
3188
3067
|
messages: [
|
|
3189
3068
|
{
|
|
3190
3069
|
role: 'user',
|
|
3191
3070
|
content: `/permission-mode ${permissionMode}`,
|
|
3192
3071
|
},
|
|
3193
3072
|
],
|
|
3194
|
-
}
|
|
3195
|
-
|
|
3073
|
+
},
|
|
3074
|
+
projectionEntries: [modeEntry],
|
|
3075
|
+
});
|
|
3196
3076
|
// Interactive mode transitions change the runtime system
|
|
3197
3077
|
// context for the very next provider request. Refresh here so
|
|
3198
3078
|
// an EnterPlanMode/ExitPlanMode tool call is immediately
|
|
@@ -3200,43 +3080,21 @@ export class ClaudeSessionService {
|
|
|
3200
3080
|
if (typeof refreshRuntimeContext === 'function')
|
|
3201
3081
|
await refreshRuntimeContext();
|
|
3202
3082
|
}
|
|
3203
|
-
if (
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
claimedToolCallIds.add(call.id);
|
|
3207
|
-
}
|
|
3208
|
-
await nativeLease.appendToolCompletion({
|
|
3083
|
+
if (!claimedToolCallIds.has(call.id) && !signal?.aborted) {
|
|
3084
|
+
await persistence.commit({
|
|
3085
|
+
kind: 'tool-execution-started',
|
|
3209
3086
|
callId: call.id,
|
|
3210
|
-
result: toolResult,
|
|
3211
3087
|
});
|
|
3088
|
+
claimedToolCallIds.add(call.id);
|
|
3212
3089
|
}
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
: {}),
|
|
3222
|
-
...(toolResult.images ? { images: toolResult.images } : {}),
|
|
3223
|
-
...(toolResult.documents
|
|
3224
|
-
? { documents: toolResult.documents }
|
|
3225
|
-
: {}),
|
|
3226
|
-
isError: toolResult.isError,
|
|
3227
|
-
...(toolResult.nativeToolUseResult
|
|
3228
|
-
? { nativeToolUseResult: toolResult.nativeToolUseResult }
|
|
3229
|
-
: {}),
|
|
3230
|
-
...(toolResult.nativeMcpMeta
|
|
3231
|
-
? { nativeMcpMeta: toolResult.nativeMcpMeta }
|
|
3232
|
-
: {}),
|
|
3233
|
-
},
|
|
3234
|
-
], this.translationContext(sessionId, snapshot));
|
|
3235
|
-
if (!entry)
|
|
3236
|
-
throw new Error('Could not translate tool result');
|
|
3237
|
-
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3238
|
-
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3239
|
-
}
|
|
3090
|
+
await persistence.commit({
|
|
3091
|
+
kind: 'tool-completion',
|
|
3092
|
+
input: {
|
|
3093
|
+
callId: call.id,
|
|
3094
|
+
result: toolResult,
|
|
3095
|
+
},
|
|
3096
|
+
});
|
|
3097
|
+
currentTurnToolCalls += 1;
|
|
3240
3098
|
if (!toolResult.isError && this.options.projectMemoryDirectory) {
|
|
3241
3099
|
const pathValue = call.input.file_path;
|
|
3242
3100
|
const path = typeof pathValue === 'string'
|
|
@@ -3266,23 +3124,23 @@ export class ClaudeSessionService {
|
|
|
3266
3124
|
!toolResult.accessedPaths) {
|
|
3267
3125
|
return;
|
|
3268
3126
|
}
|
|
3269
|
-
const attachedRulePaths = this.attachedRulePaths(
|
|
3127
|
+
const attachedRulePaths = this.attachedRulePaths(projectionSnapshot().entries);
|
|
3270
3128
|
for (const filePath of toolResult.accessedPaths) {
|
|
3271
3129
|
const rules = await this.options.conditionalRuleResolver.resolve(filePath, [...attachedRulePaths]);
|
|
3272
3130
|
for (const rule of rules) {
|
|
3273
|
-
const attachment = createClaudeRuleAttachmentEntry(rule, this.displayRulePath(rule.path), this.translationContext(sessionId,
|
|
3274
|
-
const
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
if (nativeLease) {
|
|
3280
|
-
const ruleContent = rule.content.trim();
|
|
3281
|
-
if (ruleContent.length > 0) {
|
|
3282
|
-
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: {
|
|
3283
3137
|
messages: [{ role: 'user', content: ruleContent }],
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3138
|
+
},
|
|
3139
|
+
projectionEntries: [attachment],
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
else {
|
|
3143
|
+
await commitProjection([attachment]);
|
|
3286
3144
|
}
|
|
3287
3145
|
attachedRulePaths.add(rule.path);
|
|
3288
3146
|
await this.instructionLoaded(sessionId, {
|
|
@@ -3303,44 +3161,20 @@ export class ClaudeSessionService {
|
|
|
3303
3161
|
},
|
|
3304
3162
|
followUpUserMessagesCompleted: async (messages) => {
|
|
3305
3163
|
for (const content of messages) {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
currentTurnUserMessages?.push(content);
|
|
3311
|
-
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3312
|
-
continue;
|
|
3313
|
-
}
|
|
3314
|
-
const [followUpEntry] = translateProviderEvents([{ type: 'user-text-block', text: content }], this.translationContext(sessionId, snapshot));
|
|
3315
|
-
if (!followUpEntry) {
|
|
3316
|
-
throw new Error('Could not translate tool follow-up message');
|
|
3317
|
-
}
|
|
3318
|
-
const followUpTail = await this.append(lease, snapshot.tail, followUpEntry);
|
|
3319
|
-
snapshot = {
|
|
3320
|
-
entries: [...snapshot.entries, followUpEntry],
|
|
3321
|
-
tail: followUpTail,
|
|
3322
|
-
};
|
|
3164
|
+
await persistence.commit({
|
|
3165
|
+
kind: 'messages',
|
|
3166
|
+
input: { messages: [{ role: 'user', content }] },
|
|
3167
|
+
});
|
|
3323
3168
|
currentTurnUserMessages?.push(content);
|
|
3324
3169
|
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3325
3170
|
}
|
|
3326
3171
|
await durableFollowUps.followUpUserMessagesCompleted(messages);
|
|
3327
3172
|
},
|
|
3328
3173
|
userInputDelivered: async (item) => {
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
currentTurnUserMessages?.push(item.content);
|
|
3334
|
-
return;
|
|
3335
|
-
}
|
|
3336
|
-
const [steeringEntry] = translateProviderEvents([{ type: 'user-text-block', text: item.content }], this.translationContext(sessionId, snapshot));
|
|
3337
|
-
if (!steeringEntry)
|
|
3338
|
-
throw new Error('Could not translate steering message');
|
|
3339
|
-
const steeringTail = await this.append(lease, snapshot.tail, steeringEntry);
|
|
3340
|
-
snapshot = {
|
|
3341
|
-
entries: [...snapshot.entries, steeringEntry],
|
|
3342
|
-
tail: steeringTail,
|
|
3343
|
-
};
|
|
3174
|
+
await persistence.commit({
|
|
3175
|
+
kind: 'messages',
|
|
3176
|
+
input: { messages: [{ role: 'user', content: item.content }] },
|
|
3177
|
+
});
|
|
3344
3178
|
currentTurnUserMessages?.push(item.content);
|
|
3345
3179
|
},
|
|
3346
3180
|
};
|
|
@@ -3384,48 +3218,51 @@ export class ClaudeSessionService {
|
|
|
3384
3218
|
throw new Error(`Claude session tool call ${unresolvedToolCall.id} requires explicit recovery approval`);
|
|
3385
3219
|
}
|
|
3386
3220
|
const recoveryResults = await runtime.recoverToolCalls(unresolvedToolCalls, recoveryRequest);
|
|
3387
|
-
const recoveryUsage = recoveryResults.reduce((usage, result) => result.usage ? mergeUsage(usage, result.usage) : usage, { inputTokens: 0, outputTokens: 0 });
|
|
3388
|
-
const recoveryModelUsage = mergeSessionRawModelUsage(...recoveryResults.map((result) => result.isError ? undefined : result.modelUsage));
|
|
3389
|
-
const foregroundLineChanges = createLineCountAccumulator();
|
|
3390
|
-
for (const result of recoveryResults) {
|
|
3391
|
-
foregroundLineChanges.add(result);
|
|
3392
|
-
}
|
|
3393
3221
|
if (this.options.agent &&
|
|
3394
3222
|
agent &&
|
|
3395
|
-
getClaudeAgentSetting(
|
|
3223
|
+
getClaudeAgentSetting(projectionSnapshot().entries) !==
|
|
3224
|
+
this.options.agent) {
|
|
3396
3225
|
const agentSetting = createClaudeAgentSettingEntry(sessionId, this.options.agent);
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
tail: settingTail,
|
|
3401
|
-
};
|
|
3402
|
-
if (nativeLease) {
|
|
3403
|
-
await nativeLease.appendMessages({
|
|
3226
|
+
await persistence.commit({
|
|
3227
|
+
kind: 'messages',
|
|
3228
|
+
input: {
|
|
3404
3229
|
messages: [
|
|
3405
3230
|
{
|
|
3406
3231
|
role: 'user',
|
|
3407
3232
|
content: `<praxis-agent-setting>${this.options.agent}</praxis-agent-setting>`,
|
|
3408
3233
|
},
|
|
3409
3234
|
],
|
|
3410
|
-
}
|
|
3411
|
-
|
|
3235
|
+
},
|
|
3236
|
+
projectionEntries: [agentSetting],
|
|
3237
|
+
});
|
|
3412
3238
|
}
|
|
3413
3239
|
let agentSystem = null;
|
|
3414
3240
|
let planModeMessage;
|
|
3415
3241
|
let sessionMemoryMessage = null;
|
|
3416
|
-
let
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3242
|
+
let agentMentionMessages = [];
|
|
3243
|
+
const contextPreparation = new ContextPreparation({
|
|
3244
|
+
...(this.options.contextAssembler
|
|
3245
|
+
? { assembler: this.options.contextAssembler }
|
|
3246
|
+
: {}),
|
|
3247
|
+
sources: {
|
|
3248
|
+
history: activeTurnMessages,
|
|
3249
|
+
memory: () => projectMemoryRecallMessages,
|
|
3250
|
+
activeTools: () => provider.capabilities.tools
|
|
3251
|
+
? (activeTurnTools?.definitions() ?? [])
|
|
3252
|
+
: [],
|
|
3253
|
+
},
|
|
3254
|
+
agentMentions: () => ({
|
|
3255
|
+
prompt: effectivePrompt,
|
|
3256
|
+
messages: agentMentionMessages,
|
|
3257
|
+
}),
|
|
3258
|
+
});
|
|
3422
3259
|
refreshRuntimeContext = async () => {
|
|
3423
3260
|
agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
3424
3261
|
planModeMessage =
|
|
3425
3262
|
this.options.interactiveTools?.contextMessage(sessionId);
|
|
3426
3263
|
// DEBUG_PLAN_CONTEXT
|
|
3427
3264
|
sessionMemoryMessage = this.sessionMemoryMessage(await turnMemory.sessionSummary());
|
|
3428
|
-
|
|
3265
|
+
await contextPreparation.refresh({
|
|
3429
3266
|
cwd: this.activeCwd(),
|
|
3430
3267
|
lifecycleId: sessionId,
|
|
3431
3268
|
...(agentSystem
|
|
@@ -3442,10 +3279,6 @@ export class ClaudeSessionService {
|
|
|
3442
3279
|
: {}),
|
|
3443
3280
|
},
|
|
3444
3281
|
});
|
|
3445
|
-
contextProjection = projectContextSnapshot(assembledContext);
|
|
3446
|
-
stableSystemMessageCount =
|
|
3447
|
-
contextProjection.stableSystemSectionCount;
|
|
3448
|
-
contextMessages = [...contextProjection.systemMessages];
|
|
3449
3282
|
};
|
|
3450
3283
|
await refreshRuntimeContext();
|
|
3451
3284
|
const expansion = shouldSkipUserPrompt()
|
|
@@ -3468,16 +3301,6 @@ export class ClaudeSessionService {
|
|
|
3468
3301
|
type: 'state',
|
|
3469
3302
|
state: 'assembling-context',
|
|
3470
3303
|
});
|
|
3471
|
-
let compactionUsage = {
|
|
3472
|
-
inputTokens: 0,
|
|
3473
|
-
outputTokens: 0,
|
|
3474
|
-
};
|
|
3475
|
-
let compactionDurationMs;
|
|
3476
|
-
let compactionDurationWithoutRetriesMs;
|
|
3477
|
-
let compactionModelUsage;
|
|
3478
|
-
const currentDefinitions = () => provider.capabilities.tools
|
|
3479
|
-
? (activeTurnTools?.definitions() ?? [])
|
|
3480
|
-
: [];
|
|
3481
3304
|
const budget = this.contextBudget(provider);
|
|
3482
3305
|
const contextEngine = new ContextEngine({
|
|
3483
3306
|
...(budget ? { budget } : {}),
|
|
@@ -3509,88 +3332,49 @@ export class ClaudeSessionService {
|
|
|
3509
3332
|
? { documents }
|
|
3510
3333
|
: {}),
|
|
3511
3334
|
}));
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
return [...messages];
|
|
3518
|
-
let insertionIndex = messages.length;
|
|
3519
|
-
let foundPrompt = false;
|
|
3520
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3521
|
-
const message = messages[index];
|
|
3522
|
-
if (message?.role === 'user' &&
|
|
3523
|
-
typeof message.content === 'string' &&
|
|
3524
|
-
message.content.endsWith(effectivePrompt)) {
|
|
3525
|
-
insertionIndex = index;
|
|
3526
|
-
foundPrompt = true;
|
|
3527
|
-
break;
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
if (!foundPrompt)
|
|
3531
|
-
return [...messages];
|
|
3532
|
-
return [
|
|
3533
|
-
...messages.slice(0, insertionIndex),
|
|
3534
|
-
...agentMentionMessages.map((content) => ({
|
|
3535
|
-
role: 'user',
|
|
3536
|
-
content,
|
|
3537
|
-
})),
|
|
3538
|
-
...messages.slice(insertionIndex),
|
|
3539
|
-
];
|
|
3540
|
-
};
|
|
3541
|
-
const injectDynamicContext = (messages) => injectFirstUserMessageContext(messages, contextProjection.firstUserMessageContext);
|
|
3542
|
-
const injectTurnContext = (messages) => injectAgentMentionContext(injectDynamicContext(messages));
|
|
3543
|
-
let compactionAnchorUuid = this.lastMessageUuid(snapshot.entries);
|
|
3335
|
+
agentMentionMessages =
|
|
3336
|
+
shellCommand === undefined && !shouldSkipUserPrompt()
|
|
3337
|
+
? (this.options.extensions?.agentMentionMessages(effectivePrompt) ?? [])
|
|
3338
|
+
: [];
|
|
3339
|
+
let compactionAnchorUuid = this.lastMessageUuid(projectionSnapshot().entries);
|
|
3544
3340
|
const contextTransitionPort = (pendingMessages = [], preservedUserMessages = []) => ({
|
|
3545
3341
|
current: () => {
|
|
3546
|
-
const
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
return {
|
|
3551
|
-
messages: [
|
|
3552
|
-
...contextMessages,
|
|
3553
|
-
...injectTurnContext([
|
|
3554
|
-
...historyMessages,
|
|
3555
|
-
...pendingMessages,
|
|
3556
|
-
]),
|
|
3557
|
-
],
|
|
3558
|
-
tools: currentDefinitions(),
|
|
3559
|
-
};
|
|
3342
|
+
const projection = contextPreparation.project({
|
|
3343
|
+
pendingMessages,
|
|
3344
|
+
});
|
|
3345
|
+
return projection.envelope;
|
|
3560
3346
|
},
|
|
3561
|
-
irreducible: () => ({
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
]),
|
|
3347
|
+
irreducible: () => contextPreparation.project({
|
|
3348
|
+
includeHistory: false,
|
|
3349
|
+
includeMemory: false,
|
|
3350
|
+
pendingMessages: [
|
|
3351
|
+
...pendingMessages,
|
|
3352
|
+
...preservedUserMessages.map((content) => ({
|
|
3353
|
+
role: 'user',
|
|
3354
|
+
content,
|
|
3355
|
+
})),
|
|
3571
3356
|
],
|
|
3572
|
-
|
|
3573
|
-
}),
|
|
3357
|
+
}).envelope,
|
|
3574
3358
|
propose: async () => {
|
|
3575
|
-
const activeNativeLease = nativeLease;
|
|
3576
3359
|
if (!budget)
|
|
3577
3360
|
throw new Error('Context budget is unavailable');
|
|
3578
|
-
const definitions =
|
|
3361
|
+
const definitions = contextPreparation.project().envelope.tools;
|
|
3579
3362
|
const historyMessages = activeTurnMessages();
|
|
3580
3363
|
if (historyMessages.length === 0)
|
|
3581
3364
|
throw new Error('Cannot compact an empty native transcript');
|
|
3582
3365
|
if (unresolvedActiveToolCallIds(historyMessages).length > 0)
|
|
3583
3366
|
throw new Error('Cannot compact a native transcript with unresolved tool calls');
|
|
3584
|
-
const irreducibleMessages =
|
|
3585
|
-
|
|
3586
|
-
|
|
3367
|
+
const irreducibleMessages = contextPreparation.project({
|
|
3368
|
+
includeHistory: false,
|
|
3369
|
+
includeMemory: false,
|
|
3370
|
+
pendingMessages: [
|
|
3587
3371
|
...pendingMessages,
|
|
3588
3372
|
...preservedUserMessages.map((content) => ({
|
|
3589
3373
|
role: 'user',
|
|
3590
3374
|
content,
|
|
3591
3375
|
})),
|
|
3592
|
-
]
|
|
3593
|
-
|
|
3376
|
+
],
|
|
3377
|
+
}).envelope.messages;
|
|
3594
3378
|
let compactableMessages = historyMessages;
|
|
3595
3379
|
let preservedMessages = [];
|
|
3596
3380
|
let compactionLogicalParentId;
|
|
@@ -3627,15 +3411,15 @@ export class ClaudeSessionService {
|
|
|
3627
3411
|
!promptContents.has(message.content)),
|
|
3628
3412
|
];
|
|
3629
3413
|
if (earliest === 0 && preservedUserMessages[0] !== undefined) {
|
|
3630
|
-
compactionLogicalParentId =
|
|
3631
|
-
.
|
|
3632
|
-
.find((event) => event.kind === 'messages' &&
|
|
3414
|
+
compactionLogicalParentId = persistence
|
|
3415
|
+
.view()
|
|
3416
|
+
.activeEvents.find((event) => event.kind === 'messages' &&
|
|
3633
3417
|
event.messages.some((message) => message.role === 'user' &&
|
|
3634
3418
|
message.content === preservedUserMessages[0]))?.id;
|
|
3635
3419
|
}
|
|
3636
3420
|
}
|
|
3637
3421
|
const memorySelection = sessionMemory
|
|
3638
|
-
? await this.selectMemoryPreservedCompact(sessionId,
|
|
3422
|
+
? await this.selectMemoryPreservedCompact(sessionId, persistence.view().projectionEntries)
|
|
3639
3423
|
: null;
|
|
3640
3424
|
if (memorySelection) {
|
|
3641
3425
|
compactableMessages = [
|
|
@@ -3656,9 +3440,9 @@ export class ClaudeSessionService {
|
|
|
3656
3440
|
preservedMessages = historyMessages.filter((message) => message.role === 'user');
|
|
3657
3441
|
const anchor = preservedMessages[0];
|
|
3658
3442
|
if (anchor !== undefined) {
|
|
3659
|
-
compactionLogicalParentId =
|
|
3660
|
-
.
|
|
3661
|
-
.find((event) => event.kind === 'messages' &&
|
|
3443
|
+
compactionLogicalParentId = persistence
|
|
3444
|
+
.view()
|
|
3445
|
+
.activeEvents.find((event) => event.kind === 'messages' &&
|
|
3662
3446
|
event.messages.some((message) => message.role === 'user' &&
|
|
3663
3447
|
message.content === anchor.content))?.id;
|
|
3664
3448
|
}
|
|
@@ -3692,15 +3476,6 @@ export class ClaudeSessionService {
|
|
|
3692
3476
|
const { durationMs, durationWithoutRetriesMs } = requireCompactionDurations(compacted);
|
|
3693
3477
|
if (signal?.aborted)
|
|
3694
3478
|
throw new AgentRunCancelledError();
|
|
3695
|
-
const proposedCompactionDurationMs = (compactionDurationMs ?? 0) + durationMs;
|
|
3696
|
-
if (!Number.isFinite(proposedCompactionDurationMs)) {
|
|
3697
|
-
throw new TypeError('compaction durationMs total overflow');
|
|
3698
|
-
}
|
|
3699
|
-
const proposedCompactionDurationWithoutRetriesMs = (compactionDurationWithoutRetriesMs ?? 0) +
|
|
3700
|
-
durationWithoutRetriesMs;
|
|
3701
|
-
if (!Number.isFinite(proposedCompactionDurationWithoutRetriesMs)) {
|
|
3702
|
-
throw new TypeError('compaction durationWithoutRetriesMs total overflow');
|
|
3703
|
-
}
|
|
3704
3479
|
const postTokens = estimateModelRequestTokens([
|
|
3705
3480
|
{ role: 'user', content: compacted.summary },
|
|
3706
3481
|
...preservedMessages,
|
|
@@ -3708,63 +3483,27 @@ export class ClaudeSessionService {
|
|
|
3708
3483
|
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3709
3484
|
? compacted.model
|
|
3710
3485
|
: provider.model;
|
|
3711
|
-
const
|
|
3712
|
-
|
|
3713
|
-
(compactModel === undefined
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3718
|
-
let meteringTurnInput;
|
|
3719
|
-
if (meaningfulUsage &&
|
|
3720
|
-
compactModel !== undefined &&
|
|
3721
|
-
compactModel.trim() !== '') {
|
|
3722
|
-
const pricing = this.options.pricing?.resolve(compactModel);
|
|
3723
|
-
const costUsd = pricing
|
|
3724
|
-
? usageCostUsd(compacted.usage, pricing)
|
|
3725
|
-
: undefined;
|
|
3726
|
-
meteringTurnInput = {
|
|
3727
|
-
model: compactModel,
|
|
3728
|
-
usage: compacted.usage,
|
|
3729
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
3730
|
-
...(compacted.usage.webSearchRequests === undefined
|
|
3731
|
-
? {}
|
|
3732
|
-
: {
|
|
3733
|
-
webSearchRequests: compacted.usage.webSearchRequests,
|
|
3734
|
-
}),
|
|
3735
|
-
};
|
|
3736
|
-
}
|
|
3737
|
-
let meteringDurationsInput;
|
|
3738
|
-
if (durationMs > 0 || durationWithoutRetriesMs > 0)
|
|
3739
|
-
meteringDurationsInput = {
|
|
3740
|
-
...(durationMs === 0 ? {} : { apiDurationMs: durationMs }),
|
|
3741
|
-
apiDurationWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3742
|
-
};
|
|
3743
|
-
if (meteringTurnInput !== undefined ||
|
|
3744
|
-
meteringDurationsInput !== undefined) {
|
|
3745
|
-
const preflight = new ClaudeSessionCostTracker({
|
|
3746
|
-
sessionId,
|
|
3747
|
-
restored: tracker.snapshot(),
|
|
3748
|
-
});
|
|
3749
|
-
if (meteringTurnInput !== undefined)
|
|
3750
|
-
preflight.recordTurn(meteringTurnInput);
|
|
3751
|
-
if (meteringDurationsInput !== undefined)
|
|
3752
|
-
preflight.recordDurations(meteringDurationsInput);
|
|
3753
|
-
}
|
|
3486
|
+
const preparedCompaction = turnAccounting.prepareCompaction({
|
|
3487
|
+
usage: compacted.usage,
|
|
3488
|
+
...(compactModel === undefined ? {} : { model: compactModel }),
|
|
3489
|
+
durationApiMs: durationMs,
|
|
3490
|
+
durationApiWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3491
|
+
});
|
|
3754
3492
|
const summaryMessage = {
|
|
3755
3493
|
role: 'user',
|
|
3756
3494
|
content: compacted.summary,
|
|
3757
3495
|
};
|
|
3758
3496
|
let replayMessages = preservedMessages;
|
|
3759
3497
|
try {
|
|
3760
|
-
const replayReport = budget.evaluate(
|
|
3761
|
-
|
|
3762
|
-
|
|
3498
|
+
const replayReport = budget.evaluate(contextPreparation.project({
|
|
3499
|
+
includeHistory: false,
|
|
3500
|
+
includeMemory: false,
|
|
3501
|
+
pendingMessages: [
|
|
3763
3502
|
summaryMessage,
|
|
3764
3503
|
...replayMessages,
|
|
3765
3504
|
...pendingMessages,
|
|
3766
|
-
]
|
|
3767
|
-
|
|
3505
|
+
],
|
|
3506
|
+
}).envelope.messages, definitions);
|
|
3768
3507
|
if (replayReport.shouldCompact)
|
|
3769
3508
|
throw new Error('replay overflow');
|
|
3770
3509
|
}
|
|
@@ -3775,40 +3514,39 @@ export class ClaudeSessionService {
|
|
|
3775
3514
|
// contains its contents and user prompts remain available.
|
|
3776
3515
|
replayMessages = replayMessages.filter((message) => message.role === 'user' && !Array.isArray(message.content));
|
|
3777
3516
|
}
|
|
3778
|
-
const
|
|
3779
|
-
...
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
...replayMessages,
|
|
3783
|
-
...pendingMessages,
|
|
3784
|
-
]),
|
|
3785
|
-
];
|
|
3517
|
+
const replacement = contextPreparation.proposeHistoryReplacement({
|
|
3518
|
+
historyMessages: [summaryMessage, ...replayMessages],
|
|
3519
|
+
pendingMessages,
|
|
3520
|
+
});
|
|
3786
3521
|
return {
|
|
3787
|
-
envelope:
|
|
3788
|
-
messages: proposedMessages,
|
|
3789
|
-
tools: definitions,
|
|
3790
|
-
},
|
|
3522
|
+
envelope: replacement.envelope,
|
|
3791
3523
|
commit: async () => {
|
|
3792
3524
|
if (signal?.aborted)
|
|
3793
3525
|
throw new AgentRunCancelledError();
|
|
3794
|
-
const
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
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
|
+
},
|
|
3546
|
+
}));
|
|
3547
|
+
const ids = committed.value;
|
|
3548
|
+
if (ids.kind !== 'compaction')
|
|
3549
|
+
throw new Error('Turn persistence returned an invalid compaction receipt');
|
|
3812
3550
|
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'auto', compact_summary: compacted.summary }, 'auto', signal);
|
|
3813
3551
|
if (this.options.hooks) {
|
|
3814
3552
|
const outcome = await this.hookLifecycle.refresh(sessionId, hookSession, signal);
|
|
@@ -3826,21 +3564,7 @@ export class ClaudeSessionService {
|
|
|
3826
3564
|
preTokens,
|
|
3827
3565
|
uuid: ids.boundaryId,
|
|
3828
3566
|
});
|
|
3829
|
-
|
|
3830
|
-
compactionDurationMs =
|
|
3831
|
-
(compactionDurationMs ?? 0) + durationMs;
|
|
3832
|
-
compactionDurationWithoutRetriesMs =
|
|
3833
|
-
(compactionDurationWithoutRetriesMs ?? 0) +
|
|
3834
|
-
durationWithoutRetriesMs;
|
|
3835
|
-
if (meteringTurnInput !== undefined) {
|
|
3836
|
-
tracker.recordTurn(meteringTurnInput);
|
|
3837
|
-
}
|
|
3838
|
-
if (compactModel !== undefined &&
|
|
3839
|
-
compactModel.trim() !== '') {
|
|
3840
|
-
compactionModelUsage = mergeSessionRawModelUsage(compactionModelUsage, { [compactModel]: compacted.usage });
|
|
3841
|
-
}
|
|
3842
|
-
if (meteringDurationsInput !== undefined)
|
|
3843
|
-
tracker.recordDurations(meteringDurationsInput);
|
|
3567
|
+
preparedCompaction.commit();
|
|
3844
3568
|
},
|
|
3845
3569
|
};
|
|
3846
3570
|
},
|
|
@@ -3868,7 +3592,7 @@ export class ClaudeSessionService {
|
|
|
3868
3592
|
: index === 0
|
|
3869
3593
|
? { type: 'user-text', text: message.text }
|
|
3870
3594
|
: { type: 'user-text-block', text: message.text };
|
|
3871
|
-
const [userEntry] = translateProviderEvents([persistenceEvent], this.translationContext(sessionId,
|
|
3595
|
+
const [userEntry] = translateProviderEvents([persistenceEvent], this.translationContext(sessionId, projectionSnapshot()));
|
|
3872
3596
|
if (!userEntry)
|
|
3873
3597
|
throw new Error('Could not translate user prompt');
|
|
3874
3598
|
if (currentPromptId === null &&
|
|
@@ -3876,13 +3600,9 @@ export class ClaudeSessionService {
|
|
|
3876
3600
|
currentPromptId = userEntry.uuid;
|
|
3877
3601
|
compactionAnchorUuid ??= userEntry.uuid;
|
|
3878
3602
|
}
|
|
3879
|
-
const
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
tail: userTail,
|
|
3883
|
-
};
|
|
3884
|
-
if (nativeLease) {
|
|
3885
|
-
const nativePromptId = await nativeLease.appendMessages({
|
|
3603
|
+
const nativePrompt = await persistence.commit({
|
|
3604
|
+
kind: 'messages',
|
|
3605
|
+
input: {
|
|
3886
3606
|
messages: [
|
|
3887
3607
|
{
|
|
3888
3608
|
role: 'user',
|
|
@@ -3893,29 +3613,29 @@ export class ClaudeSessionService {
|
|
|
3893
3613
|
: {}),
|
|
3894
3614
|
},
|
|
3895
3615
|
],
|
|
3896
|
-
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
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;
|
|
3901
3624
|
}
|
|
3902
3625
|
if (fileHistory && currentPromptId) {
|
|
3903
|
-
const historySnapshot = await fileHistory.snapshot(
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
tail: historyTail,
|
|
3908
|
-
};
|
|
3909
|
-
if (nativeLease) {
|
|
3910
|
-
await nativeLease.appendMessages({
|
|
3626
|
+
const historySnapshot = await fileHistory.snapshot(projectionSnapshot().entries, currentPromptId);
|
|
3627
|
+
await persistence.commit({
|
|
3628
|
+
kind: 'messages',
|
|
3629
|
+
input: {
|
|
3911
3630
|
messages: [
|
|
3912
3631
|
{
|
|
3913
3632
|
role: 'user',
|
|
3914
3633
|
content: `<praxis-file-history>${JSON.stringify(historySnapshot)}</praxis-file-history>`,
|
|
3915
3634
|
},
|
|
3916
3635
|
],
|
|
3917
|
-
}
|
|
3918
|
-
|
|
3636
|
+
},
|
|
3637
|
+
projectionEntries: [historySnapshot],
|
|
3638
|
+
});
|
|
3919
3639
|
}
|
|
3920
3640
|
if (this.options.hooks && !shouldSkipUserPrompt()) {
|
|
3921
3641
|
const outcome = await this.options.hooks.run({
|
|
@@ -3929,11 +3649,7 @@ export class ClaudeSessionService {
|
|
|
3929
3649
|
throw new Error(`UserPromptSubmit hook error: ${outcome.blockedReason}`);
|
|
3930
3650
|
}
|
|
3931
3651
|
}
|
|
3932
|
-
let
|
|
3933
|
-
let shellModelUsage;
|
|
3934
|
-
let shellDurationApiMs;
|
|
3935
|
-
let shellDurationApiWithoutRetriesMs;
|
|
3936
|
-
let shellToolDurationMs;
|
|
3652
|
+
let shellResult;
|
|
3937
3653
|
if (shellCommand !== undefined) {
|
|
3938
3654
|
const call = {
|
|
3939
3655
|
id: `shell_${randomUUID().replaceAll('-', '')}`,
|
|
@@ -3945,7 +3661,6 @@ export class ClaudeSessionService {
|
|
|
3945
3661
|
callId: call.id,
|
|
3946
3662
|
command: shellCommand,
|
|
3947
3663
|
});
|
|
3948
|
-
let shellResult;
|
|
3949
3664
|
try {
|
|
3950
3665
|
shellResult = await runtime.executeDirectToolCall(call, {
|
|
3951
3666
|
cwd: this.activeCwd(),
|
|
@@ -3972,15 +3687,6 @@ export class ClaudeSessionService {
|
|
|
3972
3687
|
});
|
|
3973
3688
|
throw error;
|
|
3974
3689
|
}
|
|
3975
|
-
shellUsage = shellResult.usage ?? shellUsage;
|
|
3976
|
-
shellDurationApiMs = shellResult.durationApiMs;
|
|
3977
|
-
shellDurationApiWithoutRetriesMs =
|
|
3978
|
-
shellResult.durationApiWithoutRetriesMs;
|
|
3979
|
-
shellToolDurationMs = shellResult.durationToolMs;
|
|
3980
|
-
if (!shellResult.isError) {
|
|
3981
|
-
shellModelUsage = shellResult.modelUsage;
|
|
3982
|
-
foregroundLineChanges.add(shellResult);
|
|
3983
|
-
}
|
|
3984
3690
|
const stdout = shellResult.processOutput?.stdout ??
|
|
3985
3691
|
(shellResult.isError ? '' : shellResult.content);
|
|
3986
3692
|
const stderr = shellResult.processOutput?.stderr ??
|
|
@@ -3990,22 +3696,15 @@ export class ClaudeSessionService {
|
|
|
3990
3696
|
{ type: 'bash-input', command: shellCommand },
|
|
3991
3697
|
{ type: 'bash-output', stdout, stderr },
|
|
3992
3698
|
], {
|
|
3993
|
-
...this.translationContext(sessionId,
|
|
3699
|
+
...this.translationContext(sessionId, projectionSnapshot()),
|
|
3994
3700
|
createUuid: () => shellUuids.shift() ?? randomUUID(),
|
|
3995
3701
|
});
|
|
3996
3702
|
if (!inputEntry || !outputEntry) {
|
|
3997
3703
|
throw new Error('Could not translate shell command result');
|
|
3998
3704
|
}
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
]);
|
|
4003
|
-
snapshot = {
|
|
4004
|
-
entries: [...snapshot.entries, inputEntry, outputEntry],
|
|
4005
|
-
tail: shellAppend.tail,
|
|
4006
|
-
};
|
|
4007
|
-
if (nativeLease) {
|
|
4008
|
-
await nativeLease.appendMessages({
|
|
3705
|
+
await persistence.commit({
|
|
3706
|
+
kind: 'messages',
|
|
3707
|
+
input: {
|
|
4009
3708
|
messages: [
|
|
4010
3709
|
{
|
|
4011
3710
|
role: 'user',
|
|
@@ -4016,8 +3715,9 @@ export class ClaudeSessionService {
|
|
|
4016
3715
|
content: `<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`,
|
|
4017
3716
|
},
|
|
4018
3717
|
],
|
|
4019
|
-
}
|
|
4020
|
-
|
|
3718
|
+
},
|
|
3719
|
+
projectionEntries: [inputEntry, outputEntry],
|
|
3720
|
+
});
|
|
4021
3721
|
currentTurnUserMessages.push(`<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`);
|
|
4022
3722
|
this.options.eventSink?.({
|
|
4023
3723
|
type: 'shell-result',
|
|
@@ -4028,92 +3728,48 @@ export class ClaudeSessionService {
|
|
|
4028
3728
|
});
|
|
4029
3729
|
}
|
|
4030
3730
|
if (shellCommand !== undefined) {
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
let rawCostUsd;
|
|
4038
|
-
if (turnModelUsage) {
|
|
4039
|
-
for (const [model, usage] of Object.entries(turnModelUsage)) {
|
|
4040
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4041
|
-
const costUsd = pricing
|
|
4042
|
-
? usageCostUsd(usage, pricing)
|
|
4043
|
-
: undefined;
|
|
4044
|
-
if (costUsd !== undefined)
|
|
4045
|
-
rawCostUsd = (rawCostUsd ?? 0) + costUsd;
|
|
4046
|
-
tracker.recordTurn({
|
|
4047
|
-
model,
|
|
4048
|
-
usage,
|
|
4049
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
4050
|
-
...(usage.webSearchRequests === undefined
|
|
4051
|
-
? {}
|
|
4052
|
-
: { webSearchRequests: usage.webSearchRequests }),
|
|
4053
|
-
});
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
let combinedToolDurationMs = 0;
|
|
4057
|
-
for (const recoveryResult of recoveryResults) {
|
|
4058
|
-
combinedToolDurationMs = addToolDuration(recoveryResult.durationToolMs, combinedToolDurationMs);
|
|
4059
|
-
}
|
|
4060
|
-
combinedToolDurationMs = addToolDuration(shellToolDurationMs, combinedToolDurationMs);
|
|
4061
|
-
tracker.recordDurations({
|
|
4062
|
-
...(shellDurationApiMs === undefined
|
|
4063
|
-
? {}
|
|
4064
|
-
: { apiDurationMs: shellDurationApiMs }),
|
|
4065
|
-
...(shellDurationApiWithoutRetriesMs === undefined
|
|
4066
|
-
? {}
|
|
4067
|
-
: {
|
|
4068
|
-
apiDurationWithoutRetriesMs: shellDurationApiWithoutRetriesMs,
|
|
4069
|
-
}),
|
|
4070
|
-
...(combinedToolDurationMs === 0
|
|
4071
|
-
? {}
|
|
4072
|
-
: { 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,
|
|
4073
3737
|
});
|
|
4074
|
-
if (foregroundLineChanges.linesAdded !== 0 ||
|
|
4075
|
-
foregroundLineChanges.linesRemoved !== 0) {
|
|
4076
|
-
tracker.recordLineChanges({
|
|
4077
|
-
...(foregroundLineChanges.linesAdded === 0
|
|
4078
|
-
? {}
|
|
4079
|
-
: { linesAdded: foregroundLineChanges.linesAdded }),
|
|
4080
|
-
...(foregroundLineChanges.linesRemoved === 0
|
|
4081
|
-
? {}
|
|
4082
|
-
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
4083
|
-
});
|
|
4084
|
-
}
|
|
4085
3738
|
turnCompleted = true;
|
|
4086
3739
|
return {
|
|
4087
3740
|
sessionId,
|
|
4088
3741
|
text: '',
|
|
4089
|
-
usage:
|
|
4090
|
-
...(
|
|
4091
|
-
|
|
4092
|
-
|
|
3742
|
+
usage: accounting.usage,
|
|
3743
|
+
...(accounting.costUsd === undefined
|
|
3744
|
+
? {}
|
|
3745
|
+
: { costUsd: accounting.costUsd }),
|
|
3746
|
+
...(accounting.modelUsage === undefined
|
|
4093
3747
|
? {}
|
|
4094
|
-
: {
|
|
3748
|
+
: { modelUsage: accounting.modelUsage }),
|
|
3749
|
+
...(accounting.durationApiMs === undefined
|
|
3750
|
+
? {}
|
|
3751
|
+
: { durationApiMs: accounting.durationApiMs }),
|
|
4095
3752
|
};
|
|
4096
3753
|
}
|
|
4097
3754
|
if (shellCommand === undefined && budget) {
|
|
4098
|
-
const definitions = currentDefinitions();
|
|
4099
3755
|
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
3756
|
+
const projection = contextPreparation.project({
|
|
3757
|
+
includeMemory: false,
|
|
3758
|
+
});
|
|
3759
|
+
budget.assertFits(budget.evaluate(projection.envelope.messages, projection.envelope.tools));
|
|
4104
3760
|
}
|
|
4105
3761
|
let stopHookActive = false;
|
|
3762
|
+
const initialProjection = contextPreparation.project({
|
|
3763
|
+
includeMemory: false,
|
|
3764
|
+
});
|
|
4106
3765
|
const runtimeRequest = {
|
|
4107
3766
|
sessionId,
|
|
4108
|
-
messages:
|
|
4109
|
-
|
|
4110
|
-
...injectTurnContext(activeTurnMessages()),
|
|
4111
|
-
],
|
|
4112
|
-
stableSystemMessageCount,
|
|
3767
|
+
messages: initialProjection.envelope.messages,
|
|
3768
|
+
stableSystemMessageCount: initialProjection.stableSystemMessageCount,
|
|
4113
3769
|
cwd: this.activeCwd(),
|
|
4114
3770
|
toolResultDirectory,
|
|
4115
3771
|
observer,
|
|
4116
|
-
...(
|
|
3772
|
+
...(steering ? { steering } : {}),
|
|
4117
3773
|
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
4118
3774
|
...(this.options.maxModelTurns !== undefined
|
|
4119
3775
|
? { maxModelTurns: this.options.maxModelTurns }
|
|
@@ -4138,14 +3794,10 @@ export class ClaudeSessionService {
|
|
|
4138
3794
|
if (shellCommand === undefined) {
|
|
4139
3795
|
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4140
3796
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
...activeTurnMessages(),
|
|
4146
|
-
...projectMemoryRecallMessages,
|
|
4147
|
-
]),
|
|
4148
|
-
];
|
|
3797
|
+
const projection = contextPreparation.project();
|
|
3798
|
+
runtimeRequest.stableSystemMessageCount =
|
|
3799
|
+
projection.stableSystemMessageCount;
|
|
3800
|
+
return projection.envelope.messages;
|
|
4149
3801
|
},
|
|
4150
3802
|
...(this.options.hooks ||
|
|
4151
3803
|
subagentExecutor ||
|
|
@@ -4176,9 +3828,7 @@ export class ClaudeSessionService {
|
|
|
4176
3828
|
}
|
|
4177
3829
|
const claimedAgentIds = new Set(this.sessionSubagentExecutors(sessionId, true).flatMap((executor) => executor.notificationClaimAgentIds()));
|
|
4178
3830
|
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => {
|
|
4179
|
-
const delivered = (notification) => transcriptContainsBackgroundAgentNotification(
|
|
4180
|
-
? nativeLease.activeMessages()
|
|
4181
|
-
: snapshot.entries, notification);
|
|
3831
|
+
const delivered = (notification) => transcriptContainsBackgroundAgentNotification(persistence.view().activeMessages, notification);
|
|
4182
3832
|
return this.hostedSubagents.has(executor)
|
|
4183
3833
|
? executor.reconcileDetachedNotifications(delivered)
|
|
4184
3834
|
: executor.reconcileDeliveredNotifications(delivered);
|
|
@@ -4186,11 +3836,8 @@ export class ClaudeSessionService {
|
|
|
4186
3836
|
await subagentExecutor?.hydratePersistedTasks(sessionId, this.activeCwd(), claimedAgentIds);
|
|
4187
3837
|
const background = await this.collectSubagentNotifications(sessionId, subagentExecutor ?? undefined);
|
|
4188
3838
|
if (background) {
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
await this.appendBackgroundNotification(sessionId, message, nativeLease);
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
3839
|
+
for (const message of background.messages)
|
|
3840
|
+
await this.appendBackgroundNotification(sessionId, message, persistence);
|
|
4194
3841
|
messages.push(...background.messages);
|
|
4195
3842
|
}
|
|
4196
3843
|
const workflow = await this.workflowManager?.notifications(true);
|
|
@@ -4274,14 +3921,10 @@ export class ClaudeSessionService {
|
|
|
4274
3921
|
}
|
|
4275
3922
|
// The single reactive retry must use the compacted transcript, not
|
|
4276
3923
|
// the stale request copy captured before the compact boundary.
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
...projectMemoryRecallMessages,
|
|
4282
|
-
]),
|
|
4283
|
-
];
|
|
4284
|
-
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
3924
|
+
const projection = contextPreparation.project();
|
|
3925
|
+
runtimeRequest.messages = projection.envelope.messages;
|
|
3926
|
+
runtimeRequest.stableSystemMessageCount =
|
|
3927
|
+
projection.stableSystemMessageCount;
|
|
4285
3928
|
runtimeRequest.deferFailureKinds = true;
|
|
4286
3929
|
try {
|
|
4287
3930
|
result = await attemptMainTurn();
|
|
@@ -4320,108 +3963,20 @@ export class ClaudeSessionService {
|
|
|
4320
3963
|
if (!finalLeafUuid) {
|
|
4321
3964
|
throw new Error('Could not locate final assistant response');
|
|
4322
3965
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
leafUuid: finalLeafUuid,
|
|
4328
|
-
});
|
|
4329
|
-
const tail = await this.append(lease, snapshot.tail, lastPrompt);
|
|
4330
|
-
snapshot = {
|
|
4331
|
-
entries: [...snapshot.entries, lastPrompt],
|
|
4332
|
-
tail,
|
|
4333
|
-
};
|
|
4334
|
-
}
|
|
4335
|
-
const totalUsage = mergeUsage(mergeUsage(mergeUsage(recoveryUsage, compactionUsage), shellUsage), result.usage);
|
|
4336
|
-
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
4337
|
-
if (!tracker) {
|
|
4338
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
4339
|
-
}
|
|
4340
|
-
const turnModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, compactionModelUsage, shellModelUsage, result.modelUsage);
|
|
4341
|
-
const combinedDurationMs = compactionDurationMs === undefined &&
|
|
4342
|
-
result.durationApiMs === undefined
|
|
4343
|
-
? undefined
|
|
4344
|
-
: (compactionDurationMs ?? 0) + (result.durationApiMs ?? 0);
|
|
4345
|
-
let rawCostUsd;
|
|
4346
|
-
if (turnModelUsage) {
|
|
4347
|
-
for (const [model, usage] of Object.entries(turnModelUsage)) {
|
|
4348
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4349
|
-
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4350
|
-
if (costUsd !== undefined)
|
|
4351
|
-
rawCostUsd = (rawCostUsd ?? 0) + costUsd;
|
|
4352
|
-
}
|
|
4353
|
-
}
|
|
4354
|
-
// Auto-compaction metering was already recorded atomically with each
|
|
4355
|
-
// committed boundary, and externally metered tool-summary metrics were
|
|
4356
|
-
// committed through the summary callback, so the live tracker receives
|
|
4357
|
-
// only the unrecorded subset here. The inclusive public aggregates
|
|
4358
|
-
// above still contain every row and duration.
|
|
4359
|
-
const trackedModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, shellModelUsage, result.unrecordedModelUsage ?? result.modelUsage);
|
|
4360
|
-
if (trackedModelUsage) {
|
|
4361
|
-
for (const [model, usage] of Object.entries(trackedModelUsage)) {
|
|
4362
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4363
|
-
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4364
|
-
tracker.recordTurn({
|
|
4365
|
-
model,
|
|
4366
|
-
usage,
|
|
4367
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
4368
|
-
...(usage.webSearchRequests === undefined
|
|
4369
|
-
? {}
|
|
4370
|
-
: { webSearchRequests: usage.webSearchRequests }),
|
|
4371
|
-
});
|
|
4372
|
-
}
|
|
4373
|
-
}
|
|
4374
|
-
let combinedToolDurationMs = 0;
|
|
4375
|
-
for (const recoveryResult of recoveryResults) {
|
|
4376
|
-
combinedToolDurationMs = addToolDuration(recoveryResult.durationToolMs, combinedToolDurationMs);
|
|
4377
|
-
}
|
|
4378
|
-
combinedToolDurationMs = addToolDuration(result.durationToolMs, combinedToolDurationMs);
|
|
4379
|
-
const trackedDurationApiMs = result.unrecordedDurationApiMs ?? result.durationApiMs;
|
|
4380
|
-
const trackedDurationApiWithoutRetriesMs = result.unrecordedDurationApiWithoutRetriesMs ??
|
|
4381
|
-
result.durationApiWithoutRetriesMs;
|
|
4382
|
-
tracker.recordDurations({
|
|
4383
|
-
...(trackedDurationApiMs === undefined
|
|
4384
|
-
? {}
|
|
4385
|
-
: { apiDurationMs: trackedDurationApiMs }),
|
|
4386
|
-
...(trackedDurationApiWithoutRetriesMs === undefined
|
|
4387
|
-
? {}
|
|
4388
|
-
: {
|
|
4389
|
-
apiDurationWithoutRetriesMs: trackedDurationApiWithoutRetriesMs,
|
|
4390
|
-
}),
|
|
4391
|
-
...(combinedToolDurationMs === 0
|
|
4392
|
-
? {}
|
|
4393
|
-
: { toolDurationMs: combinedToolDurationMs }),
|
|
4394
|
-
});
|
|
4395
|
-
foregroundLineChanges.add({
|
|
4396
|
-
isError: false,
|
|
4397
|
-
...(result.linesAdded === undefined
|
|
4398
|
-
? {}
|
|
4399
|
-
: { linesAdded: result.linesAdded }),
|
|
4400
|
-
...(result.linesRemoved === undefined
|
|
4401
|
-
? {}
|
|
4402
|
-
: { linesRemoved: result.linesRemoved }),
|
|
3966
|
+
const accounting = turnAccounting.complete({
|
|
3967
|
+
kind: 'runtime',
|
|
3968
|
+
recovery: recoveryResults,
|
|
3969
|
+
result,
|
|
4403
3970
|
});
|
|
4404
|
-
if (foregroundLineChanges.linesAdded !== 0 ||
|
|
4405
|
-
foregroundLineChanges.linesRemoved !== 0) {
|
|
4406
|
-
tracker.recordLineChanges({
|
|
4407
|
-
...(foregroundLineChanges.linesAdded === 0
|
|
4408
|
-
? {}
|
|
4409
|
-
: { linesAdded: foregroundLineChanges.linesAdded }),
|
|
4410
|
-
...(foregroundLineChanges.linesRemoved === 0
|
|
4411
|
-
? {}
|
|
4412
|
-
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
4413
|
-
});
|
|
4414
|
-
}
|
|
4415
3971
|
const memorySnapshot = activeTurnMessages();
|
|
4416
|
-
const nativeMemoryMessageId =
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
const definitions = currentDefinitions();
|
|
3972
|
+
const nativeMemoryMessageId = persistence
|
|
3973
|
+
.view()
|
|
3974
|
+
.projectionEntries.at(-1)?.uuid;
|
|
3975
|
+
const providerProjection = contextPreparation.project({
|
|
3976
|
+
includeMemory: false,
|
|
3977
|
+
});
|
|
3978
|
+
const providerVisibleMessages = providerProjection.envelope.messages;
|
|
3979
|
+
const definitions = providerProjection.envelope.tools;
|
|
4425
3980
|
const currentContextTokens = contextEngine.report({
|
|
4426
3981
|
messages: providerVisibleMessages,
|
|
4427
3982
|
tools: definitions,
|
|
@@ -4438,18 +3993,16 @@ export class ClaudeSessionService {
|
|
|
4438
3993
|
occupancyTokens: currentContextTokens,
|
|
4439
3994
|
toolCalls: currentTurnToolCalls,
|
|
4440
3995
|
messages: memorySnapshot,
|
|
4441
|
-
projectMessages:
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
{
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
: [])
|
|
4452
|
-
: 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
|
+
: []),
|
|
4453
4006
|
directMaintenance: projectMemoryMaintained,
|
|
4454
4007
|
});
|
|
4455
4008
|
turnCompleted = true;
|
|
@@ -4458,12 +4011,16 @@ export class ClaudeSessionService {
|
|
|
4458
4011
|
text: structuredCapture && structuredCapture.calls === 1
|
|
4459
4012
|
? JSON.stringify(structuredCapture.value)
|
|
4460
4013
|
: result.text,
|
|
4461
|
-
usage:
|
|
4462
|
-
...(
|
|
4014
|
+
usage: accounting.usage,
|
|
4015
|
+
...(accounting.durationApiMs === undefined
|
|
4463
4016
|
? {}
|
|
4464
|
-
: { durationApiMs:
|
|
4465
|
-
...(
|
|
4466
|
-
|
|
4017
|
+
: { durationApiMs: accounting.durationApiMs }),
|
|
4018
|
+
...(accounting.costUsd === undefined
|
|
4019
|
+
? {}
|
|
4020
|
+
: { costUsd: accounting.costUsd }),
|
|
4021
|
+
...(accounting.modelUsage === undefined
|
|
4022
|
+
? {}
|
|
4023
|
+
: { modelUsage: accounting.modelUsage }),
|
|
4467
4024
|
...(structuredCapture && structuredCapture.calls === 1
|
|
4468
4025
|
? { structuredOutput: structuredCapture.value }
|
|
4469
4026
|
: {}),
|
|
@@ -4499,35 +4056,12 @@ export class ClaudeSessionService {
|
|
|
4499
4056
|
: { kind: 'resume', atEventId: resumeSessionAt }
|
|
4500
4057
|
: { kind: 'start' };
|
|
4501
4058
|
result = await nativeTranscript.withLease(activationKind, async (nativeLease) => {
|
|
4502
|
-
const
|
|
4503
|
-
|
|
4504
|
-
if (scratchResult.status === 'conflict') {
|
|
4505
|
-
throw new Error(`native scratch transcript conflict: ${scratchResult.reason}`);
|
|
4506
|
-
}
|
|
4507
|
-
return scratchResult.value;
|
|
4059
|
+
const persistence = new TurnPersistence({ native: nativeLease });
|
|
4060
|
+
return runUnderLease(persistence);
|
|
4508
4061
|
});
|
|
4509
4062
|
}
|
|
4510
|
-
controller.complete();
|
|
4511
4063
|
return result;
|
|
4512
|
-
}
|
|
4513
|
-
catch (error) {
|
|
4514
|
-
controller.fail(error, signal);
|
|
4515
|
-
throw error;
|
|
4516
|
-
}
|
|
4517
|
-
finally {
|
|
4518
|
-
if (activeTurnInput !== undefined) {
|
|
4519
|
-
for (const item of activeTurnInput.close()) {
|
|
4520
|
-
this.options.eventSink?.({
|
|
4521
|
-
type: 'user-input-rejected',
|
|
4522
|
-
id: item.id,
|
|
4523
|
-
content: item.content,
|
|
4524
|
-
reason: signal?.aborted ? 'cancelled' : 'failed',
|
|
4525
|
-
});
|
|
4526
|
-
}
|
|
4527
|
-
}
|
|
4528
|
-
if (this.activeTurnInputs.get(sessionId) === activeTurnRecord)
|
|
4529
|
-
this.activeTurnInputs.delete(sessionId);
|
|
4530
|
-
}
|
|
4064
|
+
});
|
|
4531
4065
|
}
|
|
4532
4066
|
async ensureFileResources(sessionId, signal) {
|
|
4533
4067
|
const resources = this.options.fileResources ?? [];
|
|
@@ -4901,6 +4435,16 @@ export class ClaudeSessionService {
|
|
|
4901
4435
|
});
|
|
4902
4436
|
}
|
|
4903
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
|
+
}
|
|
4904
4448
|
if (nativeLease) {
|
|
4905
4449
|
if (nativeLease.activeMessages().length === 0) {
|
|
4906
4450
|
throw new Error(`Native session not found: ${sessionId}`);
|
|
@@ -5290,32 +4834,6 @@ export class ClaudeSessionService {
|
|
|
5290
4834
|
}
|
|
5291
4835
|
return factory(base, operations, sessionId, enabledTools);
|
|
5292
4836
|
}
|
|
5293
|
-
async append(lease, tail, entry) {
|
|
5294
|
-
void lease;
|
|
5295
|
-
return this.appendProjectionMany(tail, [entry]).then((result) => result.tail);
|
|
5296
|
-
}
|
|
5297
|
-
/**
|
|
5298
|
-
* Update the in-memory compatibility projection used by the turn pipeline.
|
|
5299
|
-
* Authoritative native persistence is performed by NativeSessionTranscript;
|
|
5300
|
-
* these entries are never written to the native event store.
|
|
5301
|
-
*/
|
|
5302
|
-
async appendProjectionMany(tail, entries) {
|
|
5303
|
-
if (entries.length === 0)
|
|
5304
|
-
throw new Error('Cannot append an empty projection');
|
|
5305
|
-
const lastUuid = [...entries]
|
|
5306
|
-
.reverse()
|
|
5307
|
-
.find((entry) => typeof entry.uuid === 'string')?.uuid;
|
|
5308
|
-
return {
|
|
5309
|
-
status: 'appended',
|
|
5310
|
-
tail: {
|
|
5311
|
-
...tail,
|
|
5312
|
-
byteLength: tail.byteLength + entries.length,
|
|
5313
|
-
lastLineHash: `projection:${tail.byteLength + entries.length}`,
|
|
5314
|
-
lastEventId: typeof lastUuid === 'string' ? lastUuid : tail.lastEventId,
|
|
5315
|
-
...(tail.branchParentId === undefined ? {} : { branchParentId: null }),
|
|
5316
|
-
},
|
|
5317
|
-
};
|
|
5318
|
-
}
|
|
5319
4837
|
logicalTailUuid(tail) {
|
|
5320
4838
|
return tail.branchParentId ?? tail.lastEventId;
|
|
5321
4839
|
}
|