praxis-agent 0.34.0 → 0.36.0
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 +9 -1
- package/dist/application/background-agent-manager.d.ts +16 -6
- package/dist/application/background-agent-manager.js +139 -97
- package/dist/application/context-engine.d.ts +56 -0
- package/dist/application/context-engine.js +129 -0
- package/dist/application/session-service.d.ts +8 -5
- package/dist/application/session-service.js +1824 -1728
- package/dist/application/subagent-service.js +385 -193
- package/dist/application/top-level-agent-manager.d.ts +1 -0
- package/dist/application/top-level-agent-manager.js +1025 -403
- package/dist/application/transcript-projection.d.ts +33 -0
- package/dist/application/transcript-projection.js +110 -0
- package/dist/application/turn-lifecycle.d.ts +40 -0
- package/dist/application/turn-lifecycle.js +36 -0
- package/dist/application/turn-memory-coordinator.d.ts +43 -0
- package/dist/application/turn-memory-coordinator.js +98 -0
- package/dist/cli/agents-dashboard.js +10 -5
- package/dist/cli/interactive.js +279 -209
- package/dist/cli/protocol.js +5 -2
- package/dist/cli/tui/claude-style.d.ts +5 -48
- package/dist/cli/tui/claude-style.js +167 -197
- package/dist/cli/tui/task-panel.js +10 -1
- package/dist/cli/tui/theme.d.ts +46 -3
- package/dist/cli/tui/theme.js +414 -56
- package/dist/cli/tui/tool-permission.js +9 -5
- package/dist/cli/tui/transcript-presentation.d.ts +89 -0
- package/dist/cli/tui/transcript-presentation.js +169 -0
- package/dist/cli/tui/transcript-viewport.d.ts +5 -34
- package/dist/cli/tui/transcript-viewport.js +639 -158
- package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli/tui/tui-view-model.d.ts +6 -2
- package/dist/cli/tui/tui-view-model.js +23 -6
- package/dist/cli-runtime.d.ts +2 -2
- package/dist/compatibility/claude/context.d.ts +2 -2
- package/dist/compatibility/claude/context.js +2 -14
- package/dist/compatibility/claude/data-plane-adapter.d.ts +9 -0
- package/dist/compatibility/claude/data-plane-adapter.js +44 -0
- package/dist/compatibility/claude/paths.js +2 -2
- package/dist/compatibility/claude/shared-resources.d.ts +7 -38
- package/dist/compatibility/claude/transcript-codec.d.ts +21 -0
- package/dist/compatibility/claude/transcript-codec.js +512 -0
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -0
- package/dist/core/context.d.ts +25 -7
- package/dist/core/context.js +46 -0
- package/dist/core/prompt-composer.d.ts +8 -16
- package/dist/core/prompt-composer.js +65 -25
- package/dist/core/resources.d.ts +39 -0
- package/dist/core/resources.js +2 -0
- package/dist/core/runtime.d.ts +6 -1
- package/dist/core/runtime.js +15 -1
- package/dist/core/session.d.ts +2 -0
- package/dist/core/session.js +5 -0
- package/dist/core/transcript-codec.d.ts +70 -0
- package/dist/core/transcript-codec.js +60 -0
- package/dist/core/transcript-event.d.ts +33 -0
- package/dist/core/transcript-event.js +198 -0
- package/dist/persistence/claude-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- package/dist/persistence/data-plane-adapter.d.ts +32 -0
- package/dist/persistence/data-plane-adapter.js +2 -0
- package/dist/persistence/data-plane.d.ts +4 -20
- package/dist/persistence/data-plane.js +10 -47
- package/dist/persistence/native-data-plane-adapter.d.ts +9 -0
- package/dist/persistence/native-data-plane-adapter.js +39 -0
- package/dist/persistence/native-resources.d.ts +4 -4
- package/dist/persistence/native-transcript-codec.d.ts +13 -0
- package/dist/persistence/native-transcript-codec.js +102 -0
- package/dist/persistence/native-transcript-reader.d.ts +53 -0
- package/dist/persistence/native-transcript-reader.js +283 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
- package/dist/persistence/subagent-lifecycle-store.js +373 -146
- package/dist/platform/exclusive-file-lease.d.ts +3 -0
- package/dist/platform/exclusive-file-lease.js +3 -0
- package/dist/tools/claude-user-message.d.ts +0 -1
- package/dist/tools/claude-user-message.js +0 -1
- package/package.json +6 -2
|
@@ -5,7 +5,7 @@ import { basename, dirname, extname, isAbsolute, join, relative, resolve, } from
|
|
|
5
5
|
import { isPathWithin } from '../platform/path-containment.js';
|
|
6
6
|
import { AGENT_COLOR_DEFAULT, agentColorMessage, getClaudeEffectiveAgentColor, } from '../compatibility/claude/agent-color.js';
|
|
7
7
|
import { createClaudeCompactEntries, formatClaudeCompactSummary, getCumulativeDroppedTokens, } from '../compatibility/claude/compaction.js';
|
|
8
|
-
import { discoverClaudeProjectRoot,
|
|
8
|
+
import { discoverClaudeProjectRoot, resolveClaudePaths, resolveClaudeScheduledTaskFile, } from '../compatibility/claude/paths.js';
|
|
9
9
|
import { isClaudeAgentId } from '../compatibility/claude/sidechain.js';
|
|
10
10
|
import { resolveDataPlanePaths, resolveScheduledTaskFile, } from '../persistence/data-plane.js';
|
|
11
11
|
import { downloadClaudeFileResources, } from '../compatibility/claude/file-resources.js';
|
|
@@ -23,17 +23,24 @@ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
|
23
23
|
import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
24
24
|
import { backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
25
25
|
import { usageCostUsd } from '../core/usage.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { isSessionId } from '../core/session.js';
|
|
27
|
+
import { ContextBudget, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
28
|
+
import { ContextEngine } from './context-engine.js';
|
|
29
|
+
import { TurnMemoryCoordinator } from './turn-memory-coordinator.js';
|
|
30
|
+
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
31
|
+
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
28
32
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
29
33
|
import { ClaudeFileChangeWatcher } from '../hooks/claude-file-change-watcher.js';
|
|
30
34
|
import { ClaudeTranscriptStore, } from '../persistence/claude-transcript-store.js';
|
|
31
35
|
import { ClaudeSessionIndexCandidateError, readClaudeSessionIndexes, } from '../persistence/claude-session-index.js';
|
|
36
|
+
import { readNativeTranscript, readNativeTranscriptIndexes, exportNativeTranscript, NativeTranscriptIndexCandidateError, } from '../persistence/native-transcript-reader.js';
|
|
37
|
+
import { lastUserPrompt, projectTranscriptDisplay, } from './transcript-projection.js';
|
|
32
38
|
import { InMemoryTranscriptStore } from '../persistence/in-memory-transcript-store.js';
|
|
33
39
|
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
34
40
|
import { ModelCompactor } from './model-compactor.js';
|
|
35
41
|
import { agentMemoryPrompt, ClaudeSubagentExecutor, StructuredOutputRegistry, } from './subagent-service.js';
|
|
36
42
|
import { ScheduledPromptManager, } from './scheduled-prompt-manager.js';
|
|
43
|
+
import { TurnTerminalController } from './turn-lifecycle.js';
|
|
37
44
|
import { ClaudeScheduledToolRegistry } from '../tools/claude-scheduled-tools.js';
|
|
38
45
|
import { ClaudeTaskToolRegistry } from '../tools/claude-task-tools.js';
|
|
39
46
|
import { ClaudeWorkflowToolRegistry } from '../tools/claude-workflow-tools.js';
|
|
@@ -46,7 +53,7 @@ import { SessionMemoryController, SessionMemoryStateError, SessionMemoryStore, }
|
|
|
46
53
|
import { FilteredToolRegistry } from '../tools/filtered-tool-registry.js';
|
|
47
54
|
import { ClaudeCapabilityToolRegistry, resolveClaudeToolCapabilities, } from '../tools/claude-capabilities.js';
|
|
48
55
|
import { generateToolUseSummary } from './tool-use-summary.js';
|
|
49
|
-
import { ClaudeUserMessageToolRegistry,
|
|
56
|
+
import { ClaudeUserMessageToolRegistry, } from '../tools/claude-user-message.js';
|
|
50
57
|
function agentPermissionMode(mode) {
|
|
51
58
|
return mode === undefined || mode === 'manual' ? 'default' : mode;
|
|
52
59
|
}
|
|
@@ -76,6 +83,42 @@ function mainAgentToolNames(tools, agent) {
|
|
|
76
83
|
.map(({ name }) => name)
|
|
77
84
|
.filter((name) => (!requested || requested.has(name)) && !disallowed.has(name));
|
|
78
85
|
}
|
|
86
|
+
function sessionMetadataFields(metadata, options) {
|
|
87
|
+
const name = metadata.title ??
|
|
88
|
+
(options.agentNameFallback ? metadata.agentName : undefined);
|
|
89
|
+
const prLink = metadata.prLink;
|
|
90
|
+
return {
|
|
91
|
+
...(name === undefined ? {} : { name }),
|
|
92
|
+
...(metadata.tag === undefined ? {} : { tag: metadata.tag }),
|
|
93
|
+
...(metadata.agentName === undefined
|
|
94
|
+
? {}
|
|
95
|
+
: { agentName: metadata.agentName }),
|
|
96
|
+
...(metadata.agentColor === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { agentColor: metadata.agentColor }),
|
|
99
|
+
...(metadata.agentSetting === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { agentSetting: metadata.agentSetting }),
|
|
102
|
+
...(metadata.permissionMode === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { permissionMode: metadata.permissionMode }),
|
|
105
|
+
...(metadata.mode === undefined ? {} : { mode: metadata.mode }),
|
|
106
|
+
...(prLink === undefined
|
|
107
|
+
? {}
|
|
108
|
+
: {
|
|
109
|
+
prNumber: prLink.prNumber,
|
|
110
|
+
...(prLink.prUrl === undefined ? {} : { prUrl: prLink.prUrl }),
|
|
111
|
+
...(prLink.prRepository === undefined
|
|
112
|
+
? {}
|
|
113
|
+
: { prRepository: prLink.prRepository }),
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function isSessionCandidateError(error) {
|
|
118
|
+
return (error instanceof ClaudeSessionIndexCandidateError ||
|
|
119
|
+
error instanceof NativeTranscriptIndexCandidateError ||
|
|
120
|
+
['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code ?? ''));
|
|
121
|
+
}
|
|
79
122
|
const emptyToolRegistry = {
|
|
80
123
|
definitions: () => [],
|
|
81
124
|
schedulingPolicy: () => ({ concurrency: 'exclusive' }),
|
|
@@ -1232,19 +1275,67 @@ export class ClaudeSessionService {
|
|
|
1232
1275
|
}
|
|
1233
1276
|
async run(prompt, signal, sessionId = randomUUID(), name, images, documents) {
|
|
1234
1277
|
this.worktreeManager?.bindSession(sessionId);
|
|
1235
|
-
return this.executeTurn(
|
|
1278
|
+
return this.executeTurn({
|
|
1279
|
+
activation: {
|
|
1280
|
+
kind: 'start',
|
|
1281
|
+
sessionId,
|
|
1282
|
+
...(name !== undefined ? { name } : {}),
|
|
1283
|
+
},
|
|
1284
|
+
submission: {
|
|
1285
|
+
kind: 'prompt',
|
|
1286
|
+
text: prompt,
|
|
1287
|
+
...(images ? { images } : {}),
|
|
1288
|
+
...(documents ? { documents } : {}),
|
|
1289
|
+
},
|
|
1290
|
+
...(signal ? { signal } : {}),
|
|
1291
|
+
});
|
|
1236
1292
|
}
|
|
1237
1293
|
async runShell(command, signal, sessionId = randomUUID(), name) {
|
|
1238
1294
|
this.worktreeManager?.bindSession(sessionId);
|
|
1239
|
-
return this.executeTurn(
|
|
1295
|
+
return this.executeTurn({
|
|
1296
|
+
activation: {
|
|
1297
|
+
kind: 'start',
|
|
1298
|
+
sessionId,
|
|
1299
|
+
...(name !== undefined ? { name } : {}),
|
|
1300
|
+
},
|
|
1301
|
+
submission: { kind: 'shell', command },
|
|
1302
|
+
...(signal ? { signal } : {}),
|
|
1303
|
+
});
|
|
1240
1304
|
}
|
|
1241
1305
|
async resume(sessionId, prompt, signal, name, images, documents, resumeSessionAt) {
|
|
1242
1306
|
this.worktreeManager?.bindSession(sessionId);
|
|
1243
|
-
return this.executeTurn(
|
|
1307
|
+
return this.executeTurn({
|
|
1308
|
+
activation: {
|
|
1309
|
+
kind: 'resume',
|
|
1310
|
+
sessionId,
|
|
1311
|
+
...(name !== undefined ? { name } : {}),
|
|
1312
|
+
...(resumeSessionAt !== undefined
|
|
1313
|
+
? { atMessageId: resumeSessionAt }
|
|
1314
|
+
: {}),
|
|
1315
|
+
},
|
|
1316
|
+
submission: {
|
|
1317
|
+
kind: 'prompt',
|
|
1318
|
+
text: prompt,
|
|
1319
|
+
...(images ? { images } : {}),
|
|
1320
|
+
...(documents ? { documents } : {}),
|
|
1321
|
+
},
|
|
1322
|
+
...(signal ? { signal } : {}),
|
|
1323
|
+
});
|
|
1244
1324
|
}
|
|
1245
1325
|
async resumeShell(sessionId, command, signal, name, resumeSessionAt) {
|
|
1246
1326
|
this.worktreeManager?.bindSession(sessionId);
|
|
1247
|
-
return this.executeTurn(
|
|
1327
|
+
return this.executeTurn({
|
|
1328
|
+
activation: {
|
|
1329
|
+
kind: 'resume',
|
|
1330
|
+
sessionId,
|
|
1331
|
+
...(name !== undefined ? { name } : {}),
|
|
1332
|
+
...(resumeSessionAt !== undefined
|
|
1333
|
+
? { atMessageId: resumeSessionAt }
|
|
1334
|
+
: {}),
|
|
1335
|
+
},
|
|
1336
|
+
submission: { kind: 'shell', command },
|
|
1337
|
+
...(signal ? { signal } : {}),
|
|
1338
|
+
});
|
|
1248
1339
|
}
|
|
1249
1340
|
async answerSideQuestion(sessionId, question, signal, onDelta, permissionMode = 'default') {
|
|
1250
1341
|
const prompt = question.trim();
|
|
@@ -1270,16 +1361,17 @@ export class ClaudeSessionService {
|
|
|
1270
1361
|
entries = loaded.value.entries;
|
|
1271
1362
|
this.restoreWorktree(entries);
|
|
1272
1363
|
}
|
|
1273
|
-
const assembledContext = await this.options.contextAssembler
|
|
1364
|
+
const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
|
|
1274
1365
|
cwd: this.activeCwd(),
|
|
1275
1366
|
lifecycleId: activeSessionId,
|
|
1276
1367
|
});
|
|
1368
|
+
const contextProjection = projectContextSnapshot(assembledContext);
|
|
1277
1369
|
const messages = [
|
|
1278
|
-
...
|
|
1370
|
+
...contextProjection.systemMessages,
|
|
1279
1371
|
...injectFirstUserMessageContext([
|
|
1280
1372
|
...projectClaudeModelMessages(entries),
|
|
1281
1373
|
{ role: 'user', content: prompt },
|
|
1282
|
-
],
|
|
1374
|
+
], contextProjection.firstUserMessageContext),
|
|
1283
1375
|
];
|
|
1284
1376
|
const budget = this.contextBudget(provider);
|
|
1285
1377
|
if (budget)
|
|
@@ -1290,6 +1382,7 @@ export class ClaudeSessionService {
|
|
|
1290
1382
|
: 'praxis/provider';
|
|
1291
1383
|
const metrics = await completeMeteredModelRequest(provider, {
|
|
1292
1384
|
messages,
|
|
1385
|
+
stableSystemMessageCount: contextProjection.stableSystemSectionCount,
|
|
1293
1386
|
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
1294
1387
|
...(signal ? { signal } : {}),
|
|
1295
1388
|
}, {
|
|
@@ -1430,27 +1523,21 @@ export class ClaudeSessionService {
|
|
|
1430
1523
|
const provider = this.providerForAgent(agent);
|
|
1431
1524
|
this.activeProvider = provider;
|
|
1432
1525
|
const agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
1433
|
-
const assembledContext = await this.options.contextAssembler
|
|
1526
|
+
const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
|
|
1434
1527
|
cwd: this.activeCwd(),
|
|
1435
1528
|
lifecycleId: sessionId,
|
|
1436
|
-
...(agentSystem
|
|
1529
|
+
...(agentSystem
|
|
1530
|
+
? { mode: 'agent', baseSystemPrompt: agentSystem }
|
|
1531
|
+
: {}),
|
|
1437
1532
|
});
|
|
1438
|
-
const
|
|
1439
|
-
const
|
|
1440
|
-
? (assembledContext?.systemMessages ?? [])
|
|
1441
|
-
: this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
|
|
1442
|
-
const contextMessages = [
|
|
1443
|
-
...(!hasPromptManifest && agentSystem
|
|
1444
|
-
? [{ role: 'system', content: agentSystem }]
|
|
1445
|
-
: []),
|
|
1446
|
-
...assembledSystemMessages,
|
|
1447
|
-
];
|
|
1533
|
+
const contextProjection = projectContextSnapshot(assembledContext);
|
|
1534
|
+
const contextMessages = [...contextProjection.systemMessages];
|
|
1448
1535
|
const messages = [
|
|
1449
1536
|
...contextMessages,
|
|
1450
1537
|
...injectFirstUserMessageContext([
|
|
1451
1538
|
...projectClaudeModelMessages(entries.entries),
|
|
1452
1539
|
{ role: 'user', content: PROMPT_SUGGESTION_INSTRUCTION },
|
|
1453
|
-
],
|
|
1540
|
+
], contextProjection.firstUserMessageContext),
|
|
1454
1541
|
];
|
|
1455
1542
|
const suggestionTools = agent && this.options.tools
|
|
1456
1543
|
? new FilteredToolRegistry(this.options.tools, {
|
|
@@ -1460,6 +1547,7 @@ export class ClaudeSessionService {
|
|
|
1460
1547
|
await this.activateSessionCostTracker(sessionId);
|
|
1461
1548
|
const metrics = await completeMeteredModelRequest(provider, {
|
|
1462
1549
|
messages,
|
|
1550
|
+
stableSystemMessageCount: contextProjection.stableSystemSectionCount,
|
|
1463
1551
|
...(provider.capabilities.tools
|
|
1464
1552
|
? { tools: suggestionTools?.definitions() ?? [] }
|
|
1465
1553
|
: {}),
|
|
@@ -1507,19 +1595,62 @@ export class ClaudeSessionService {
|
|
|
1507
1595
|
}
|
|
1508
1596
|
catch (error) {
|
|
1509
1597
|
if (error.code === 'ENOENT')
|
|
1510
|
-
|
|
1511
|
-
|
|
1598
|
+
names = [];
|
|
1599
|
+
else
|
|
1600
|
+
throw error;
|
|
1512
1601
|
}
|
|
1513
1602
|
const discoveredSessionIds = names
|
|
1514
1603
|
.filter((name) => extname(name) === '.jsonl')
|
|
1515
1604
|
.map((name) => basename(name, '.jsonl'))
|
|
1516
|
-
.filter((sessionId) =>
|
|
1605
|
+
.filter((sessionId) => isSessionId(sessionId));
|
|
1517
1606
|
const sessionIds = [
|
|
1518
1607
|
...new Set([
|
|
1519
1608
|
...discoveredSessionIds,
|
|
1520
1609
|
...this.explicitSessionFiles.keys(),
|
|
1521
1610
|
]),
|
|
1522
1611
|
];
|
|
1612
|
+
if (this.options.dataPlane === 'native') {
|
|
1613
|
+
const nativeResults = await readNativeTranscriptIndexes(sessionIds.map((sessionId) => ({
|
|
1614
|
+
sessionId,
|
|
1615
|
+
path: this.explicitSessionFiles.get(sessionId) ??
|
|
1616
|
+
join(projectRoot, `${sessionId}.jsonl`),
|
|
1617
|
+
})));
|
|
1618
|
+
const legacyResults = await readClaudeSessionIndexes(nativeResults.flatMap((result) => 'result' in result && result.result.format === 'legacy'
|
|
1619
|
+
? [{ sessionId: result.sessionId, path: result.path }]
|
|
1620
|
+
: []), this.schema);
|
|
1621
|
+
const legacyBySessionId = new Map(legacyResults.map((result) => [result.sessionId, result]));
|
|
1622
|
+
const nativeSummaries = nativeResults.map((result) => {
|
|
1623
|
+
try {
|
|
1624
|
+
if ('error' in result)
|
|
1625
|
+
throw result.error;
|
|
1626
|
+
const file = result.result;
|
|
1627
|
+
if (file.format === 'legacy') {
|
|
1628
|
+
const legacy = legacyBySessionId.get(result.sessionId);
|
|
1629
|
+
if (!legacy)
|
|
1630
|
+
throw new Error(`Missing legacy transcript index for ${result.sessionId}`);
|
|
1631
|
+
if ('error' in legacy)
|
|
1632
|
+
throw legacy.error;
|
|
1633
|
+
return this.claudeSessionSummary(result.sessionId, legacy.index);
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
sessionId: result.sessionId,
|
|
1637
|
+
lastPrompt: lastUserPrompt(file.records.map((record) => record.event)),
|
|
1638
|
+
updatedAt: file.updatedAt,
|
|
1639
|
+
status: this.nativeSessionStatus(file.issue, file.records.length),
|
|
1640
|
+
issue: file.issue,
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
catch (error) {
|
|
1644
|
+
if (isSessionCandidateError(error))
|
|
1645
|
+
return null;
|
|
1646
|
+
throw error;
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
return nativeSummaries
|
|
1650
|
+
.filter((summary) => summary !== null)
|
|
1651
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt) ||
|
|
1652
|
+
left.sessionId.localeCompare(right.sessionId));
|
|
1653
|
+
}
|
|
1523
1654
|
if (discoveredRoot !== undefined) {
|
|
1524
1655
|
for (const sessionId of discoveredSessionIds) {
|
|
1525
1656
|
this.discoveredProjectRoots.set(sessionId, projectRoot);
|
|
@@ -1534,46 +1665,11 @@ export class ClaudeSessionService {
|
|
|
1534
1665
|
try {
|
|
1535
1666
|
if ('error' in result)
|
|
1536
1667
|
throw result.error;
|
|
1537
|
-
|
|
1538
|
-
const metadata = reduceClaudeSessionMetadata(index.entries, sessionId);
|
|
1539
|
-
const name = metadata.title ?? metadata.agentName;
|
|
1540
|
-
const prLink = metadata.prLink;
|
|
1541
|
-
return {
|
|
1542
|
-
sessionId,
|
|
1543
|
-
...(name === undefined ? {} : { name }),
|
|
1544
|
-
...(metadata.tag === undefined ? {} : { tag: metadata.tag }),
|
|
1545
|
-
...(metadata.agentName === undefined
|
|
1546
|
-
? {}
|
|
1547
|
-
: { agentName: metadata.agentName }),
|
|
1548
|
-
...(metadata.agentColor === undefined
|
|
1549
|
-
? {}
|
|
1550
|
-
: { agentColor: metadata.agentColor }),
|
|
1551
|
-
...(metadata.agentSetting === undefined
|
|
1552
|
-
? {}
|
|
1553
|
-
: { agentSetting: metadata.agentSetting }),
|
|
1554
|
-
...(metadata.permissionMode === undefined
|
|
1555
|
-
? {}
|
|
1556
|
-
: { permissionMode: metadata.permissionMode }),
|
|
1557
|
-
...(metadata.mode === undefined ? {} : { mode: metadata.mode }),
|
|
1558
|
-
lastPrompt: metadata.lastPrompt ?? null,
|
|
1559
|
-
updatedAt: index.updatedAt,
|
|
1560
|
-
status: this.sessionStatus(index.issue, index.entries.length),
|
|
1561
|
-
issue: index.issue,
|
|
1562
|
-
...(prLink
|
|
1563
|
-
? {
|
|
1564
|
-
prNumber: prLink.prNumber,
|
|
1565
|
-
prUrl: prLink.prUrl,
|
|
1566
|
-
prRepository: prLink.prRepository,
|
|
1567
|
-
}
|
|
1568
|
-
: {}),
|
|
1569
|
-
};
|
|
1668
|
+
return this.claudeSessionSummary(result.sessionId, result.index);
|
|
1570
1669
|
}
|
|
1571
1670
|
catch (error) {
|
|
1572
|
-
if (error
|
|
1671
|
+
if (isSessionCandidateError(error))
|
|
1573
1672
|
return null;
|
|
1574
|
-
if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code ?? '')) {
|
|
1575
|
-
return null;
|
|
1576
|
-
}
|
|
1577
1673
|
throw error;
|
|
1578
1674
|
}
|
|
1579
1675
|
});
|
|
@@ -1584,6 +1680,37 @@ export class ClaudeSessionService {
|
|
|
1584
1680
|
}
|
|
1585
1681
|
async inspect(sessionId) {
|
|
1586
1682
|
this.assertSessionPersistence();
|
|
1683
|
+
if (this.options.dataPlane === 'native') {
|
|
1684
|
+
const paths = this.paths(sessionId);
|
|
1685
|
+
const file = await readNativeTranscript(paths.sessionFile);
|
|
1686
|
+
if (file.format === 'legacy') {
|
|
1687
|
+
const recovery = await this.store(sessionId).loadReadOnly();
|
|
1688
|
+
const metadata = reduceClaudeSessionMetadata(recovery.entries, sessionId);
|
|
1689
|
+
return {
|
|
1690
|
+
sessionId,
|
|
1691
|
+
...sessionMetadataFields(metadata, { agentNameFallback: false }),
|
|
1692
|
+
lastPrompt: metadata.lastPrompt ?? null,
|
|
1693
|
+
updatedAt: file.updatedAt,
|
|
1694
|
+
status: this.sessionStatus(recovery.issue, recovery.entries.length),
|
|
1695
|
+
issue: recovery.issue,
|
|
1696
|
+
writeMode: this.schema.writeMode,
|
|
1697
|
+
entryCount: recovery.entries.length,
|
|
1698
|
+
byteLength: file.byteLength,
|
|
1699
|
+
newlineTerminated: file.newlineTerminated,
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
return {
|
|
1703
|
+
sessionId,
|
|
1704
|
+
lastPrompt: lastUserPrompt(file.records.map((record) => record.event)),
|
|
1705
|
+
updatedAt: file.updatedAt,
|
|
1706
|
+
status: this.nativeSessionStatus(file.issue, file.records.length),
|
|
1707
|
+
issue: file.issue,
|
|
1708
|
+
writeMode: file.writeMode,
|
|
1709
|
+
entryCount: file.records.length,
|
|
1710
|
+
byteLength: file.byteLength,
|
|
1711
|
+
newlineTerminated: file.newlineTerminated,
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1587
1714
|
await this.discoverProjectRoot(sessionId);
|
|
1588
1715
|
const paths = this.paths(sessionId);
|
|
1589
1716
|
let metadata;
|
|
@@ -1598,30 +1725,9 @@ export class ClaudeSessionService {
|
|
|
1598
1725
|
}
|
|
1599
1726
|
const recovery = await this.store(sessionId).loadReadOnly();
|
|
1600
1727
|
const sessionMetadata = reduceClaudeSessionMetadata(recovery.entries, sessionId);
|
|
1601
|
-
const prLink = sessionMetadata.prLink;
|
|
1602
1728
|
return {
|
|
1603
1729
|
sessionId,
|
|
1604
|
-
...(sessionMetadata
|
|
1605
|
-
? {}
|
|
1606
|
-
: { name: sessionMetadata.title }),
|
|
1607
|
-
...(sessionMetadata.tag === undefined
|
|
1608
|
-
? {}
|
|
1609
|
-
: { tag: sessionMetadata.tag }),
|
|
1610
|
-
...(sessionMetadata.agentName === undefined
|
|
1611
|
-
? {}
|
|
1612
|
-
: { agentName: sessionMetadata.agentName }),
|
|
1613
|
-
...(sessionMetadata.agentColor === undefined
|
|
1614
|
-
? {}
|
|
1615
|
-
: { agentColor: sessionMetadata.agentColor }),
|
|
1616
|
-
...(sessionMetadata.agentSetting === undefined
|
|
1617
|
-
? {}
|
|
1618
|
-
: { agentSetting: sessionMetadata.agentSetting }),
|
|
1619
|
-
...(sessionMetadata.permissionMode === undefined
|
|
1620
|
-
? {}
|
|
1621
|
-
: { permissionMode: sessionMetadata.permissionMode }),
|
|
1622
|
-
...(sessionMetadata.mode === undefined
|
|
1623
|
-
? {}
|
|
1624
|
-
: { mode: sessionMetadata.mode }),
|
|
1730
|
+
...sessionMetadataFields(sessionMetadata, { agentNameFallback: false }),
|
|
1625
1731
|
lastPrompt: sessionMetadata.lastPrompt ?? null,
|
|
1626
1732
|
updatedAt: metadata.mtime.toISOString(),
|
|
1627
1733
|
status: this.sessionStatus(recovery.issue, recovery.entries.length),
|
|
@@ -1631,13 +1737,6 @@ export class ClaudeSessionService {
|
|
|
1631
1737
|
entryCount: recovery.entries.length,
|
|
1632
1738
|
byteLength: recovery.tail.byteLength,
|
|
1633
1739
|
newlineTerminated: recovery.tail.newlineTerminated,
|
|
1634
|
-
...(prLink
|
|
1635
|
-
? {
|
|
1636
|
-
prNumber: prLink.prNumber,
|
|
1637
|
-
prUrl: prLink.prUrl,
|
|
1638
|
-
prRepository: prLink.prRepository,
|
|
1639
|
-
}
|
|
1640
|
-
: {}),
|
|
1641
1740
|
};
|
|
1642
1741
|
}
|
|
1643
1742
|
async readEffectiveAgentColor(sessionId) {
|
|
@@ -1647,6 +1746,8 @@ export class ClaudeSessionService {
|
|
|
1647
1746
|
}
|
|
1648
1747
|
async export(sessionId) {
|
|
1649
1748
|
this.assertSessionPersistence();
|
|
1749
|
+
if (this.options.dataPlane === 'native')
|
|
1750
|
+
return exportNativeTranscript(this.paths(sessionId).sessionFile);
|
|
1650
1751
|
await this.discoverProjectRoot(sessionId);
|
|
1651
1752
|
try {
|
|
1652
1753
|
return await this.store(sessionId).exportReadOnly();
|
|
@@ -1662,17 +1763,40 @@ export class ClaudeSessionService {
|
|
|
1662
1763
|
this.assertSessionPersistence();
|
|
1663
1764
|
const path = resolve(requestedPath);
|
|
1664
1765
|
const pathMetadata = await lstat(path);
|
|
1766
|
+
const dataPlaneName = this.options.dataPlane === 'native' ? 'Native' : 'Claude';
|
|
1665
1767
|
if (!pathMetadata.isFile()) {
|
|
1666
|
-
throw new Error(
|
|
1768
|
+
throw new Error(`${dataPlaneName} resume path must be a regular JSONL file: ${path}`);
|
|
1667
1769
|
}
|
|
1668
1770
|
if (extname(path).toLowerCase() !== '.jsonl') {
|
|
1669
|
-
throw new Error(
|
|
1771
|
+
throw new Error(`${dataPlaneName} resume path must end in .jsonl: ${path}`);
|
|
1670
1772
|
}
|
|
1671
1773
|
const sessionId = basename(path, '.jsonl');
|
|
1672
|
-
if (!
|
|
1673
|
-
throw new Error(
|
|
1774
|
+
if (!isSessionId(sessionId)) {
|
|
1775
|
+
throw new Error(`${dataPlaneName} resume path filename must be a session UUID: ${path}`);
|
|
1674
1776
|
}
|
|
1675
1777
|
const canonicalPath = await realpath(path);
|
|
1778
|
+
if (this.options.dataPlane === 'native') {
|
|
1779
|
+
const detected = await readNativeTranscript(canonicalPath);
|
|
1780
|
+
if (detected.format === 'native') {
|
|
1781
|
+
if (detected.issue?.kind === 'unsupported-version')
|
|
1782
|
+
throw new Error('Native resume transcript version is unsupported');
|
|
1783
|
+
if (detected.issue ||
|
|
1784
|
+
!detected.newlineTerminated ||
|
|
1785
|
+
detected.records.length === 0)
|
|
1786
|
+
throw new Error('Native resume transcript must be a complete non-empty newline-terminated file');
|
|
1787
|
+
if (detected.records.some((record) => record.event.sessionId !== sessionId))
|
|
1788
|
+
throw new Error('Native resume transcript contains a different sessionId');
|
|
1789
|
+
this.explicitSessionFiles.set(sessionId, canonicalPath);
|
|
1790
|
+
this.discoveredProjectRoots.set(sessionId, dirname(canonicalPath));
|
|
1791
|
+
return {
|
|
1792
|
+
sessionId,
|
|
1793
|
+
lastPrompt: lastUserPrompt(detected.records.map((record) => record.event)),
|
|
1794
|
+
updatedAt: detected.updatedAt,
|
|
1795
|
+
status: 'read-only',
|
|
1796
|
+
issue: detected.issue,
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1676
1800
|
const exact = this.pathsForCwd(sessionId, this.activeCwd());
|
|
1677
1801
|
const candidate = new ClaudeTranscriptStore({
|
|
1678
1802
|
sessionFile: canonicalPath,
|
|
@@ -1686,6 +1810,10 @@ export class ClaudeSessionService {
|
|
|
1686
1810
|
if (snapshot.entries.length === 0) {
|
|
1687
1811
|
throw new Error('Claude resume transcript must not be empty');
|
|
1688
1812
|
}
|
|
1813
|
+
if (this.options.dataPlane === 'native' &&
|
|
1814
|
+
this.schema.writeMode !== 'read-write') {
|
|
1815
|
+
throw new Error('Native legacy resume transcript is read-only');
|
|
1816
|
+
}
|
|
1689
1817
|
let matchingSessionIdentity = false;
|
|
1690
1818
|
for (const entry of snapshot.entries) {
|
|
1691
1819
|
if (entry.sessionId === sessionId)
|
|
@@ -1703,40 +1831,21 @@ export class ClaudeSessionService {
|
|
|
1703
1831
|
this.explicitResumeLeafUuids.set(sessionId, selected.leafUuid);
|
|
1704
1832
|
this.discoveredProjectRoots.set(sessionId, dirname(canonicalPath));
|
|
1705
1833
|
const metadata = reduceClaudeSessionMetadata(snapshot.entries, sessionId);
|
|
1706
|
-
const prLink = metadata.prLink;
|
|
1707
1834
|
return {
|
|
1708
1835
|
sessionId,
|
|
1709
|
-
...(metadata
|
|
1710
|
-
...(metadata.tag === undefined ? {} : { tag: metadata.tag }),
|
|
1711
|
-
...(metadata.agentName === undefined
|
|
1712
|
-
? {}
|
|
1713
|
-
: { agentName: metadata.agentName }),
|
|
1714
|
-
...(metadata.agentColor === undefined
|
|
1715
|
-
? {}
|
|
1716
|
-
: { agentColor: metadata.agentColor }),
|
|
1717
|
-
...(metadata.agentSetting === undefined
|
|
1718
|
-
? {}
|
|
1719
|
-
: { agentSetting: metadata.agentSetting }),
|
|
1720
|
-
...(metadata.permissionMode === undefined
|
|
1721
|
-
? {}
|
|
1722
|
-
: { permissionMode: metadata.permissionMode }),
|
|
1723
|
-
...(metadata.mode === undefined ? {} : { mode: metadata.mode }),
|
|
1836
|
+
...sessionMetadataFields(metadata, { agentNameFallback: false }),
|
|
1724
1837
|
lastPrompt: metadata.lastPrompt ?? null,
|
|
1725
1838
|
updatedAt: pathMetadata.mtime.toISOString(),
|
|
1726
1839
|
status: this.sessionStatus(null, snapshot.entries.length),
|
|
1727
1840
|
issue: null,
|
|
1728
|
-
...(prLink === undefined
|
|
1729
|
-
? {}
|
|
1730
|
-
: {
|
|
1731
|
-
prNumber: prLink.prNumber,
|
|
1732
|
-
...(prLink.prUrl === undefined ? {} : { prUrl: prLink.prUrl }),
|
|
1733
|
-
...(prLink.prRepository === undefined
|
|
1734
|
-
? {}
|
|
1735
|
-
: { prRepository: prLink.prRepository }),
|
|
1736
|
-
}),
|
|
1737
1841
|
};
|
|
1738
1842
|
}
|
|
1739
1843
|
async transcript(sessionId, resumeSessionAt) {
|
|
1844
|
+
if (this.options.dataPlane === 'native') {
|
|
1845
|
+
const file = await readNativeTranscript(this.paths(sessionId).sessionFile);
|
|
1846
|
+
if (file.format === 'native')
|
|
1847
|
+
return projectTranscriptDisplay(file.records.map((record) => record.event), resumeSessionAt);
|
|
1848
|
+
}
|
|
1740
1849
|
await this.discoverProjectRoot(sessionId);
|
|
1741
1850
|
try {
|
|
1742
1851
|
const recovery = await this.store(sessionId).loadReadOnly();
|
|
@@ -1987,7 +2096,11 @@ export class ClaudeSessionService {
|
|
|
1987
2096
|
}
|
|
1988
2097
|
async retryRecentlyDenied(sessionId, display, signal) {
|
|
1989
2098
|
await this.appendPermissionGrant(sessionId, display, true);
|
|
1990
|
-
return this.executeTurn(
|
|
2099
|
+
return this.executeTurn({
|
|
2100
|
+
activation: { kind: 'resume', sessionId },
|
|
2101
|
+
submission: { kind: 'retry', prompt: '/permissions' },
|
|
2102
|
+
...(signal ? { signal } : {}),
|
|
2103
|
+
});
|
|
1991
2104
|
}
|
|
1992
2105
|
async recordBtwUsage(sessionId, permissionMode = 'default') {
|
|
1993
2106
|
const activeSessionId = await this.ensureLocalSession(sessionId, permissionMode);
|
|
@@ -2320,6 +2433,7 @@ export class ClaudeSessionService {
|
|
|
2320
2433
|
}
|
|
2321
2434
|
async fork(parentSessionId, sessionId = randomUUID(), resumeSessionAt) {
|
|
2322
2435
|
this.assertSessionPersistence();
|
|
2436
|
+
await this.assertNativeWriteTargetIsLegacy(parentSessionId);
|
|
2323
2437
|
this.assertWritable();
|
|
2324
2438
|
const entries = await this.nativeForkEntries(parentSessionId, sessionId, resumeSessionAt);
|
|
2325
2439
|
const result = await this.store(sessionId).create(entries);
|
|
@@ -2633,1695 +2747,1658 @@ export class ClaudeSessionService {
|
|
|
2633
2747
|
}
|
|
2634
2748
|
return tracker.snapshot();
|
|
2635
2749
|
}
|
|
2636
|
-
async executeTurn(
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
this.
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
this.runtimeCwd = pinnedCwd;
|
|
2661
|
-
if (requireExisting) {
|
|
2662
|
-
await this.discoverProjectRoot(sessionId);
|
|
2663
|
-
}
|
|
2664
|
-
const sessionPaths = this.paths(sessionId);
|
|
2665
|
-
const toolResultDirectory = join(sessionPaths.projectRoot, sessionId, 'tool-results');
|
|
2666
|
-
const store = this.turnStore(sessionId);
|
|
2667
|
-
const leaseResult = await store.withLease(async (lease) => {
|
|
2668
|
-
if (!requireExisting) {
|
|
2669
|
-
const initialization = name === undefined
|
|
2670
|
-
? await store.reserve()
|
|
2671
|
-
: await store.create(this.sessionNameEntries(sessionId, name));
|
|
2672
|
-
if (initialization.status === 'conflict') {
|
|
2673
|
-
throw new Error(`Session ID ${sessionId} is already in use`);
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
let snapshot = await lease.load();
|
|
2677
|
-
let automaticReplayPrompt;
|
|
2678
|
-
if (requireExisting &&
|
|
2679
|
-
this.options.sessionPersistence === false &&
|
|
2680
|
-
snapshot.entries.length === 0) {
|
|
2681
|
-
const persisted = await this.store(sessionId).load();
|
|
2682
|
-
if (persisted.entries.length > 0) {
|
|
2683
|
-
const imported = await store.create(persisted.entries);
|
|
2684
|
-
if (imported.status === 'created')
|
|
2685
|
-
snapshot = await lease.load();
|
|
2686
|
-
}
|
|
2750
|
+
async executeTurn(request) {
|
|
2751
|
+
const { activation, submission, signal } = request;
|
|
2752
|
+
const sessionId = activation.sessionId;
|
|
2753
|
+
const requireExisting = activation.kind === 'resume';
|
|
2754
|
+
const name = activation.name;
|
|
2755
|
+
const resumeSessionAt = activation.kind === 'resume' ? activation.atMessageId : undefined;
|
|
2756
|
+
const prompt = submission.kind === 'shell'
|
|
2757
|
+
? `! ${submission.command}`
|
|
2758
|
+
: submission.kind === 'retry'
|
|
2759
|
+
? submission.prompt
|
|
2760
|
+
: submission.text;
|
|
2761
|
+
const images = submission.kind === 'prompt' ? (submission.images ?? []) : [];
|
|
2762
|
+
const documents = submission.kind === 'prompt' ? (submission.documents ?? []) : [];
|
|
2763
|
+
const shellCommand = submission.kind === 'shell' ? submission.command : undefined;
|
|
2764
|
+
const skipUserPrompt = submission.kind === 'retry';
|
|
2765
|
+
const controller = new TurnTerminalController(this.options.eventSink ?? (() => undefined));
|
|
2766
|
+
try {
|
|
2767
|
+
if (requireExisting)
|
|
2768
|
+
await this.assertNativeWriteTargetIsLegacy(sessionId);
|
|
2769
|
+
this.assertWritable();
|
|
2770
|
+
if (prompt.length === 0 && images.length === 0 && documents.length === 0)
|
|
2771
|
+
throw new Error('Prompt must not be empty');
|
|
2772
|
+
if (name !== undefined && name.length === 0) {
|
|
2773
|
+
throw new Error('Session name must not be empty');
|
|
2687
2774
|
}
|
|
2688
|
-
if (
|
|
2689
|
-
throw new Error(
|
|
2775
|
+
if (shellCommand !== undefined && shellCommand.trim().length === 0) {
|
|
2776
|
+
throw new Error('Shell command must not be empty');
|
|
2690
2777
|
}
|
|
2691
|
-
this.
|
|
2692
|
-
if (
|
|
2693
|
-
|
|
2694
|
-
entries: selectClaudeTranscriptAtMessage(snapshot.entries, resumeSessionAt),
|
|
2695
|
-
tail: { ...snapshot.tail, branchParentUuid: resumeSessionAt },
|
|
2696
|
-
};
|
|
2778
|
+
await this.activateSessionCostTracker(sessionId);
|
|
2779
|
+
if (this.options.sessionPersistence !== false) {
|
|
2780
|
+
this.durableMetadataSessions.add(sessionId);
|
|
2697
2781
|
}
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
entries: selected.entries,
|
|
2703
|
-
tail: { ...snapshot.tail, branchParentUuid: selected.leafUuid },
|
|
2704
|
-
};
|
|
2782
|
+
await this.ensureFileResources(sessionId, signal);
|
|
2783
|
+
this.worktreeManager?.bindSession(sessionId);
|
|
2784
|
+
if (this.options.initialWorktree) {
|
|
2785
|
+
await this.worktreeManager?.ensureInitial(this.options.initialWorktreeName);
|
|
2705
2786
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
!skipUserPrompt &&
|
|
2711
|
-
!(this.options.approveRecovery !== undefined &&
|
|
2712
|
-
findUnresolvedClaudeToolCalls(snapshot.entries).length > 0)) {
|
|
2713
|
-
const interruption = classifyClaudeInterruption(snapshot.entries);
|
|
2714
|
-
if ((interruption.kind === 'interrupted-prompt' ||
|
|
2715
|
-
interruption.kind === 'interrupted-turn') &&
|
|
2716
|
-
interruption.prompt !== undefined &&
|
|
2717
|
-
interruption.replayEntries !== undefined) {
|
|
2718
|
-
automaticReplayPrompt = interruption.prompt;
|
|
2719
|
-
snapshot = {
|
|
2720
|
-
entries: interruption.replayEntries,
|
|
2721
|
-
tail: {
|
|
2722
|
-
...snapshot.tail,
|
|
2723
|
-
branchParentUuid: interruption.replayParentUuid ?? null,
|
|
2724
|
-
},
|
|
2725
|
-
};
|
|
2726
|
-
}
|
|
2787
|
+
const pinnedCwd = this.sessionCwds.get(sessionId) ?? this.activeCwd();
|
|
2788
|
+
this.sessionCwds.set(sessionId, pinnedCwd);
|
|
2789
|
+
if (this.options.workspace?.cwd() !== pinnedCwd) {
|
|
2790
|
+
this.options.workspace?.setCwd(pinnedCwd);
|
|
2727
2791
|
}
|
|
2728
|
-
|
|
2729
|
-
if (
|
|
2730
|
-
|
|
2731
|
-
type: 'worktree-state',
|
|
2732
|
-
worktreeSession: initialTransition.state,
|
|
2733
|
-
sessionId,
|
|
2734
|
-
};
|
|
2735
|
-
const stateTail = await this.append(lease, snapshot.tail, stateEntry);
|
|
2736
|
-
snapshot = {
|
|
2737
|
-
entries: [...snapshot.entries, stateEntry],
|
|
2738
|
-
tail: stateTail,
|
|
2739
|
-
};
|
|
2740
|
-
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
2792
|
+
this.runtimeCwd = pinnedCwd;
|
|
2793
|
+
if (requireExisting) {
|
|
2794
|
+
await this.discoverProjectRoot(sessionId);
|
|
2741
2795
|
}
|
|
2742
|
-
this.
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
!
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2796
|
+
const sessionPaths = this.paths(sessionId);
|
|
2797
|
+
const toolResultDirectory = join(sessionPaths.projectRoot, sessionId, 'tool-results');
|
|
2798
|
+
const store = this.turnStore(sessionId);
|
|
2799
|
+
const leaseResult = await store.withLease(async (lease) => {
|
|
2800
|
+
if (!requireExisting) {
|
|
2801
|
+
const initialization = name === undefined
|
|
2802
|
+
? await store.reserve()
|
|
2803
|
+
: await store.create(this.sessionNameEntries(sessionId, name));
|
|
2804
|
+
if (initialization.status === 'conflict') {
|
|
2805
|
+
throw new Error(`Session ID ${sessionId} is already in use`);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
let snapshot = await lease.load();
|
|
2809
|
+
let automaticReplayPrompt;
|
|
2810
|
+
if (requireExisting &&
|
|
2811
|
+
this.options.sessionPersistence === false &&
|
|
2812
|
+
snapshot.entries.length === 0) {
|
|
2813
|
+
const persisted = await this.store(sessionId).load();
|
|
2814
|
+
if (persisted.entries.length > 0) {
|
|
2815
|
+
const imported = await store.create(persisted.entries);
|
|
2816
|
+
if (imported.status === 'created')
|
|
2817
|
+
snapshot = await lease.load();
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (requireExisting && snapshot.entries.length === 0) {
|
|
2821
|
+
throw new Error(`Claude session not found: ${sessionId}`);
|
|
2751
2822
|
}
|
|
2752
|
-
snapshot = {
|
|
2753
|
-
entries: [...snapshot.entries, ...entries],
|
|
2754
|
-
tail: appendResult.tail,
|
|
2755
|
-
};
|
|
2756
2823
|
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
const effectivePrompt = automaticReplayPrompt ??
|
|
2763
|
-
(!requireExisting &&
|
|
2764
|
-
!skipUserPrompt &&
|
|
2765
|
-
!this.options.agentInitialPromptHandledExternally &&
|
|
2766
|
-
shellCommand === undefined &&
|
|
2767
|
-
agent?.initialPrompt
|
|
2768
|
-
? `${agent.initialPrompt}\n\n${prompt}`
|
|
2769
|
-
: prompt);
|
|
2770
|
-
const projectMemoryRecallTurn = !skipUserPrompt && shellCommand === undefined
|
|
2771
|
-
? this.options.projectMemoryRecall?.prefetch({
|
|
2772
|
-
sessionId,
|
|
2773
|
-
turnId: randomUUID(),
|
|
2774
|
-
prompt: effectivePrompt,
|
|
2775
|
-
...(signal ? { signal } : {}),
|
|
2776
|
-
})
|
|
2777
|
-
: undefined;
|
|
2778
|
-
const initialPricing = this.options.pricing?.resolve(provider.model ?? 'praxis/provider');
|
|
2779
|
-
if (this.options.maxBudgetUsd !== undefined && !initialPricing) {
|
|
2780
|
-
throw new Error(`Cannot enforce --max-budget-usd: no pricing is configured for model ${provider.model ?? 'praxis/provider'}`);
|
|
2781
|
-
}
|
|
2782
|
-
const shellInputUuid = shellCommand === undefined ? null : randomUUID();
|
|
2783
|
-
let currentPromptId = shellInputUuid;
|
|
2784
|
-
let lastAssistantUuid = null;
|
|
2785
|
-
const fileHistory = this.options.fileCheckpointing &&
|
|
2786
|
-
this.options.sessionPersistence !== false
|
|
2787
|
-
? new ClaudeFileHistory(this.options.configRoot, sessionId, [
|
|
2788
|
-
this.activeCwd(),
|
|
2789
|
-
...(this.options.fileRewindRoots ?? []),
|
|
2790
|
-
])
|
|
2791
|
-
: null;
|
|
2792
|
-
const unresolvedToolCalls = findUnresolvedClaudeToolCalls(snapshot.entries);
|
|
2793
|
-
const pendingRecoveryToolCallIds = new Set(unresolvedToolCalls.map((call) => call.id));
|
|
2794
|
-
const pendingRecoveryHookOutcomes = [];
|
|
2795
|
-
const currentHookCwd = () => this.activeCwd();
|
|
2796
|
-
const hookSession = {
|
|
2797
|
-
session_id: sessionId,
|
|
2798
|
-
transcript_path: sessionPaths.sessionFile,
|
|
2799
|
-
get cwd() {
|
|
2800
|
-
return currentHookCwd();
|
|
2801
|
-
},
|
|
2802
|
-
permission_mode: 'default',
|
|
2803
|
-
};
|
|
2804
|
-
const appendHookOutcome = async (outcome) => {
|
|
2805
|
-
for (const entry of createClaudeHookAttachmentEntries(outcome, this.translationContext(sessionId, snapshot))) {
|
|
2806
|
-
const tail = await this.append(lease, snapshot.tail, entry);
|
|
2807
|
-
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
2824
|
+
if (resumeSessionAt !== undefined) {
|
|
2825
|
+
snapshot = {
|
|
2826
|
+
entries: selectClaudeTranscriptAtMessage(snapshot.entries, resumeSessionAt),
|
|
2827
|
+
tail: { ...snapshot.tail, branchParentUuid: resumeSessionAt },
|
|
2828
|
+
};
|
|
2808
2829
|
}
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2830
|
+
else if (this.explicitResumeLeafUuids.has(sessionId)) {
|
|
2831
|
+
const selected = selectClaudeTranscriptFromNewestLeaf(snapshot.entries);
|
|
2832
|
+
this.explicitResumeLeafUuids.set(sessionId, selected.leafUuid);
|
|
2833
|
+
snapshot = {
|
|
2834
|
+
entries: selected.entries,
|
|
2835
|
+
tail: { ...snapshot.tail, branchParentUuid: selected.leafUuid },
|
|
2836
|
+
};
|
|
2814
2837
|
}
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
const
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2838
|
+
if (requireExisting &&
|
|
2839
|
+
resumeSessionAt === undefined &&
|
|
2840
|
+
this.options.resumeInterruptedTurn === true &&
|
|
2841
|
+
shellCommand === undefined &&
|
|
2842
|
+
!skipUserPrompt &&
|
|
2843
|
+
!(this.options.approveRecovery !== undefined &&
|
|
2844
|
+
findUnresolvedClaudeToolCalls(snapshot.entries).length > 0)) {
|
|
2845
|
+
const interruption = classifyClaudeInterruption(snapshot.entries);
|
|
2846
|
+
if ((interruption.kind === 'interrupted-prompt' ||
|
|
2847
|
+
interruption.kind === 'interrupted-turn') &&
|
|
2848
|
+
interruption.prompt !== undefined &&
|
|
2849
|
+
interruption.replayEntries !== undefined) {
|
|
2850
|
+
automaticReplayPrompt = interruption.prompt;
|
|
2851
|
+
snapshot = {
|
|
2852
|
+
entries: interruption.replayEntries,
|
|
2853
|
+
tail: {
|
|
2854
|
+
...snapshot.tail,
|
|
2855
|
+
branchParentUuid: interruption.replayParentUuid ?? null,
|
|
2856
|
+
},
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2832
2859
|
}
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2860
|
+
const initialTransition = this.worktreeManager?.consumeTransition('__initial__');
|
|
2861
|
+
if (initialTransition && snapshot.entries.length === 0) {
|
|
2862
|
+
const stateEntry = {
|
|
2863
|
+
type: 'worktree-state',
|
|
2864
|
+
worktreeSession: initialTransition.state,
|
|
2865
|
+
sessionId,
|
|
2866
|
+
};
|
|
2867
|
+
const stateTail = await this.append(lease, snapshot.tail, stateEntry);
|
|
2868
|
+
snapshot = {
|
|
2869
|
+
entries: [...snapshot.entries, stateEntry],
|
|
2870
|
+
tail: stateTail,
|
|
2871
|
+
};
|
|
2872
|
+
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
2836
2873
|
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2874
|
+
this.restoreWorktree(snapshot.entries);
|
|
2875
|
+
this.options.interactiveTools?.restore(sessionId, snapshot.entries);
|
|
2876
|
+
if (requireExisting &&
|
|
2877
|
+
name !== undefined &&
|
|
2878
|
+
!this.hasSessionName(snapshot.entries, name)) {
|
|
2879
|
+
const entries = this.sessionNameEntries(sessionId, name);
|
|
2880
|
+
const appendResult = await lease.appendMany(snapshot.tail, entries);
|
|
2881
|
+
if (appendResult.status === 'conflict') {
|
|
2882
|
+
throw new Error(`Claude transcript append conflict: ${appendResult.reason}`);
|
|
2883
|
+
}
|
|
2884
|
+
snapshot = {
|
|
2885
|
+
entries: [...snapshot.entries, ...entries],
|
|
2886
|
+
tail: appendResult.tail,
|
|
2887
|
+
};
|
|
2888
|
+
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
2840
2889
|
}
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
...(this.options.eventSink
|
|
2856
|
-
? { eventSink: this.options.eventSink }
|
|
2857
|
-
: {}),
|
|
2858
|
-
enabledTools: taskToolNames,
|
|
2859
|
-
...(this.options.hooks
|
|
2860
|
-
? {
|
|
2861
|
-
taskHooks: {
|
|
2862
|
-
created: async (task, taskSignal) => {
|
|
2863
|
-
const outcome = await this.options.hooks?.run({
|
|
2864
|
-
...hookSession,
|
|
2865
|
-
hook_event_name: 'TaskCreated',
|
|
2866
|
-
task_id: task.id,
|
|
2867
|
-
task_subject: task.subject,
|
|
2868
|
-
task_description: task.description,
|
|
2869
|
-
}, undefined, taskSignal);
|
|
2870
|
-
if (!outcome)
|
|
2871
|
-
return;
|
|
2872
|
-
await recordHookOutcome(outcome);
|
|
2873
|
-
if (outcome.blockedReason) {
|
|
2874
|
-
throw new Error(`TaskCreated hook error: ${outcome.blockedReason}`);
|
|
2875
|
-
}
|
|
2876
|
-
},
|
|
2877
|
-
completed: async (task, taskSignal) => {
|
|
2878
|
-
const outcome = await this.options.hooks?.run({
|
|
2879
|
-
...hookSession,
|
|
2880
|
-
hook_event_name: 'TaskCompleted',
|
|
2881
|
-
task_id: task.id,
|
|
2882
|
-
task_subject: task.subject,
|
|
2883
|
-
task_description: task.description,
|
|
2884
|
-
}, undefined, taskSignal);
|
|
2885
|
-
if (!outcome)
|
|
2886
|
-
return;
|
|
2887
|
-
await recordHookOutcome(outcome);
|
|
2888
|
-
if (outcome.blockedReason) {
|
|
2889
|
-
throw new Error(`TaskCompleted hook error: ${outcome.blockedReason}`);
|
|
2890
|
-
}
|
|
2891
|
-
},
|
|
2892
|
-
},
|
|
2893
|
-
}
|
|
2894
|
-
: {}),
|
|
2895
|
-
})
|
|
2896
|
-
: null;
|
|
2897
|
-
const scheduledTools = this.scheduledPrompts &&
|
|
2898
|
-
this.options.tools &&
|
|
2899
|
-
scheduledToolNames.length > 0
|
|
2900
|
-
? new ClaudeScheduledToolRegistry({
|
|
2901
|
-
base: taskTools ?? this.options.tools,
|
|
2902
|
-
manager: this.scheduledPrompts,
|
|
2890
|
+
const agentName = this.options.agent ?? getClaudeAgentSetting(snapshot.entries);
|
|
2891
|
+
const agent = this.resolveAgent(agentName);
|
|
2892
|
+
const provider = this.providerForAgent(agent);
|
|
2893
|
+
this.activeProvider = provider;
|
|
2894
|
+
const effectivePrompt = automaticReplayPrompt ??
|
|
2895
|
+
(!requireExisting &&
|
|
2896
|
+
!skipUserPrompt &&
|
|
2897
|
+
!this.options.agentInitialPromptHandledExternally &&
|
|
2898
|
+
shellCommand === undefined &&
|
|
2899
|
+
agent?.initialPrompt
|
|
2900
|
+
? `${agent.initialPrompt}\n\n${prompt}`
|
|
2901
|
+
: prompt);
|
|
2902
|
+
const sessionMemory = this.sessionMemoryController(sessionId);
|
|
2903
|
+
const turnMemory = new TurnMemoryCoordinator({
|
|
2903
2904
|
sessionId,
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
: null;
|
|
2908
|
-
const baseTools = scheduledTools ?? taskTools ?? this.options.tools;
|
|
2909
|
-
const turnPermissions = this.options.interactiveTools?.permissions(sessionId) ??
|
|
2910
|
-
this.options.permissions;
|
|
2911
|
-
const subagentExecutor = (this.options.enableSubagents || this.options.enableWorkflows) &&
|
|
2912
|
-
baseTools &&
|
|
2913
|
-
turnPermissions
|
|
2914
|
-
? new ClaudeSubagentExecutor({
|
|
2915
|
-
configRoot: this.options.configRoot,
|
|
2916
|
-
dataPlane: this.options.dataPlane ?? 'claude',
|
|
2917
|
-
cwd: this.activeCwd(),
|
|
2918
|
-
cwdProvider: () => this.activeCwd(),
|
|
2919
|
-
claudeVersion: this.options.claudeVersion,
|
|
2920
|
-
provider,
|
|
2921
|
-
persistence: this.options.sessionPersistence === false ? 'memory' : 'disk',
|
|
2922
|
-
...(this.options.providerForModel
|
|
2923
|
-
? { providerForModel: this.options.providerForModel }
|
|
2924
|
-
: {}),
|
|
2925
|
-
baseTools,
|
|
2926
|
-
permissions: turnPermissions,
|
|
2927
|
-
...(this.options.permissionResolverForMode
|
|
2928
|
-
? {
|
|
2929
|
-
permissionResolverForMode: this.options.permissionResolverForMode,
|
|
2930
|
-
}
|
|
2931
|
-
: {}),
|
|
2932
|
-
parentPermissionMode: () => agentPermissionMode(this.options.interactiveTools?.mode(sessionId) ??
|
|
2933
|
-
this.options.permissionMode),
|
|
2934
|
-
...(this.options.subagentToolNames
|
|
2935
|
-
? {
|
|
2936
|
-
toolNames: this.capabilityToolNames(this.options.subagentToolNames, capabilities),
|
|
2937
|
-
}
|
|
2938
|
-
: {}),
|
|
2939
|
-
...(this.options.extensions
|
|
2940
|
-
? { extensions: this.options.extensions }
|
|
2941
|
-
: {}),
|
|
2942
|
-
...(this.options.mcp ? { mcp: this.options.mcp } : {}),
|
|
2943
|
-
...(this.options.hooks ? { hooks: this.options.hooks } : {}),
|
|
2944
|
-
...(this.options.contextAssembler
|
|
2945
|
-
? { contextAssembler: this.options.contextAssembler }
|
|
2946
|
-
: {}),
|
|
2947
|
-
...((this.options.contextBudget?.reserveTokens ??
|
|
2948
|
-
this.options.contextReserveTokens) === undefined
|
|
2949
|
-
? {}
|
|
2950
|
-
: {
|
|
2951
|
-
contextReserveTokens: this.options.contextBudget?.reserveTokens ??
|
|
2952
|
-
this.options.contextReserveTokens,
|
|
2953
|
-
}),
|
|
2954
|
-
...(this.options.approveTool
|
|
2955
|
-
? { approveTool: this.options.approveTool }
|
|
2956
|
-
: {}),
|
|
2957
|
-
permissionUpdates: () => this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
2958
|
-
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
2959
|
-
onLineChanges: (changes) => {
|
|
2960
|
-
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
2961
|
-
if (!tracker) {
|
|
2962
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
2963
|
-
}
|
|
2964
|
-
tracker.recordLineChanges(changes);
|
|
2965
|
-
},
|
|
2966
|
-
...(this.options.eventSink
|
|
2967
|
-
? { eventSink: this.options.eventSink }
|
|
2968
|
-
: {}),
|
|
2969
|
-
...(taskTools
|
|
2970
|
-
? {
|
|
2971
|
-
backgroundTaskNotifications: (waitForRunning) => taskTools.notifications(waitForRunning),
|
|
2972
|
-
}
|
|
2905
|
+
...(sessionMemory ? { session: sessionMemory } : {}),
|
|
2906
|
+
...(this.options.projectMemoryRecall
|
|
2907
|
+
? { projectRecall: this.options.projectMemoryRecall }
|
|
2973
2908
|
: {}),
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
outputOwnedBackgroundAgent: (ownerSessionId, agentId, options) => this.backgroundTasks.outputAgent(ownerSessionId, agentId, options),
|
|
2977
|
-
sendOwnedBackgroundAgent: (ownerSessionId, agentId, message, summary, toolUseId) => this.sendOwnedBackgroundAgent(ownerSessionId, agentId, message, summary, toolUseId),
|
|
2978
|
-
})
|
|
2979
|
-
: null;
|
|
2980
|
-
if (subagentExecutor) {
|
|
2981
|
-
this.subagentExecutors.add(subagentExecutor);
|
|
2982
|
-
this.subagentExecutorSessions.set(subagentExecutor, sessionId);
|
|
2983
|
-
this.backgroundTasks.registerAgents(sessionId, subagentExecutor);
|
|
2984
|
-
}
|
|
2985
|
-
const agentTools = subagentExecutor
|
|
2986
|
-
? subagentExecutor.registry(sessionId, 0, (callId) => currentPromptId ??
|
|
2987
|
-
this.promptIdForToolCall(snapshot.entries, callId))
|
|
2988
|
-
: baseTools;
|
|
2989
|
-
const turnTools = this.workflowManager && subagentExecutor && agentTools
|
|
2990
|
-
? new ClaudeWorkflowToolRegistry({
|
|
2991
|
-
base: agentTools,
|
|
2992
|
-
manager: this.workflowManager,
|
|
2993
|
-
executor: subagentExecutor,
|
|
2994
|
-
cwd: this.activeCwd(),
|
|
2995
|
-
cwdProvider: () => this.activeCwd(),
|
|
2996
|
-
configRoot: this.options.configRoot,
|
|
2997
|
-
sessionId,
|
|
2998
|
-
promptIdForCall: (callId) => currentPromptId ??
|
|
2999
|
-
this.promptIdForToolCall(snapshot.entries, callId),
|
|
3000
|
-
defaultModel: provider.model ?? 'praxis/provider',
|
|
3001
|
-
tokenBudget: workflowTokenTarget(effectivePrompt),
|
|
3002
|
-
enabled: capabilities.has('Workflow'),
|
|
3003
|
-
dataPlane: this.options.dataPlane ?? 'claude',
|
|
3004
|
-
})
|
|
3005
|
-
: agentTools;
|
|
3006
|
-
const workspaceTools = this.worktreeManager &&
|
|
3007
|
-
turnTools &&
|
|
3008
|
-
this.options.workspace &&
|
|
3009
|
-
(this.options.worktreeToolNames?.length ?? 0) > 0
|
|
3010
|
-
? new ClaudeWorktreeToolRegistry({
|
|
3011
|
-
base: turnTools,
|
|
3012
|
-
manager: this.worktreeManager,
|
|
3013
|
-
workspace: this.options.workspace,
|
|
3014
|
-
enabledTools: this.options.worktreeToolNames ?? [],
|
|
3015
|
-
dataPlane: this.options.dataPlane ?? 'claude',
|
|
3016
|
-
})
|
|
3017
|
-
: turnTools;
|
|
3018
|
-
const messageTools = this.options.brief && workspaceTools
|
|
3019
|
-
? new ClaudeUserMessageToolRegistry(workspaceTools, (message) => this.options.eventSink?.({
|
|
3020
|
-
type: 'user-message',
|
|
3021
|
-
message: message.message,
|
|
3022
|
-
status: message.status,
|
|
3023
|
-
...(message.attachments.length
|
|
3024
|
-
? { attachments: message.attachments }
|
|
2909
|
+
...(this.options.projectMemoryExtraction
|
|
2910
|
+
? { projectExtraction: this.options.projectMemoryExtraction }
|
|
3025
2911
|
: {}),
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
schedulingPolicy: (call) => ({
|
|
3035
|
-
...resolveToolSchedulingPolicy(interactiveMessageTools, call),
|
|
3036
|
-
startAfterAssistant: true,
|
|
3037
|
-
}),
|
|
3038
|
-
prepare: (call, context) => interactiveMessageTools.prepare(call, context),
|
|
3039
|
-
execute: async (call, context) => {
|
|
3040
|
-
const path = call.name === 'Write' || call.name === 'Edit'
|
|
3041
|
-
? call.input.file_path
|
|
3042
|
-
: call.name === 'NotebookEdit'
|
|
3043
|
-
? call.input.notebook_path
|
|
3044
|
-
: undefined;
|
|
3045
|
-
if (typeof path !== 'string') {
|
|
3046
|
-
return interactiveMessageTools.execute(call, context);
|
|
3047
|
-
}
|
|
3048
|
-
if ((call.name === 'Write' || call.name === 'Edit') &&
|
|
3049
|
-
(await this.options.interactiveTools?.isPlanFile(sessionId, path))) {
|
|
3050
|
-
return interactiveMessageTools.execute(call, context);
|
|
3051
|
-
}
|
|
3052
|
-
const snapshotMessageId = currentPromptId ??
|
|
3053
|
-
this.promptIdForToolCall(snapshot.entries, call.id);
|
|
3054
|
-
const assistantMessageId = lastAssistantUuid ??
|
|
3055
|
-
this.assistantIdForToolCall(snapshot.entries, call.id);
|
|
3056
|
-
if (!snapshotMessageId || !assistantMessageId) {
|
|
3057
|
-
throw new Error('Claude file history could not link tool call');
|
|
3058
|
-
}
|
|
3059
|
-
const prepared = await fileHistory.prepareMutation(snapshot.entries, snapshotMessageId, path);
|
|
3060
|
-
let result;
|
|
3061
|
-
try {
|
|
3062
|
-
result = await interactiveMessageTools.execute(call, context);
|
|
3063
|
-
}
|
|
3064
|
-
catch (error) {
|
|
3065
|
-
await prepared.rollback();
|
|
3066
|
-
throw error;
|
|
3067
|
-
}
|
|
3068
|
-
if (result.isError) {
|
|
3069
|
-
await prepared.rollback();
|
|
3070
|
-
return result;
|
|
3071
|
-
}
|
|
3072
|
-
const entry = prepared.commit(assistantMessageId);
|
|
3073
|
-
if (entry) {
|
|
3074
|
-
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3075
|
-
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3076
|
-
}
|
|
3077
|
-
return result;
|
|
3078
|
-
},
|
|
2912
|
+
warn: (message) => this.options.eventSink?.({ type: 'warning', message }),
|
|
2913
|
+
});
|
|
2914
|
+
if (!skipUserPrompt && shellCommand === undefined) {
|
|
2915
|
+
turnMemory.prefetch({
|
|
2916
|
+
turnId: randomUUID(),
|
|
2917
|
+
prompt: effectivePrompt,
|
|
2918
|
+
...(signal ? { signal } : {}),
|
|
2919
|
+
});
|
|
3079
2920
|
}
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
const runtime = new AgentRuntime(provider, this.options.eventSink, {
|
|
3111
|
-
emitInitialContextState: false,
|
|
3112
|
-
...(this.options.emitToolUseSummaries
|
|
3113
|
-
? {
|
|
3114
|
-
generateToolUseSummary: ({ tools, lastAssistantText, signal }) => generateToolUseSummary(provider, tools, signal, lastAssistantText, (metrics) => this.recordAuxiliaryMetrics(sessionId, metrics)),
|
|
2921
|
+
const initialPricing = this.options.pricing?.resolve(provider.model ?? 'praxis/provider');
|
|
2922
|
+
if (this.options.maxBudgetUsd !== undefined && !initialPricing) {
|
|
2923
|
+
throw new Error(`Cannot enforce --max-budget-usd: no pricing is configured for model ${provider.model ?? 'praxis/provider'}`);
|
|
2924
|
+
}
|
|
2925
|
+
const shellInputUuid = shellCommand === undefined ? null : randomUUID();
|
|
2926
|
+
let currentPromptId = shellInputUuid;
|
|
2927
|
+
let lastAssistantUuid = null;
|
|
2928
|
+
const fileHistory = this.options.fileCheckpointing &&
|
|
2929
|
+
this.options.sessionPersistence !== false
|
|
2930
|
+
? new ClaudeFileHistory(this.options.configRoot, sessionId, [
|
|
2931
|
+
this.activeCwd(),
|
|
2932
|
+
...(this.options.fileRewindRoots ?? []),
|
|
2933
|
+
])
|
|
2934
|
+
: null;
|
|
2935
|
+
const unresolvedToolCalls = findUnresolvedClaudeToolCalls(snapshot.entries);
|
|
2936
|
+
const pendingRecoveryToolCallIds = new Set(unresolvedToolCalls.map((call) => call.id));
|
|
2937
|
+
const pendingRecoveryHookOutcomes = [];
|
|
2938
|
+
const currentHookCwd = () => this.activeCwd();
|
|
2939
|
+
const hookSession = {
|
|
2940
|
+
session_id: sessionId,
|
|
2941
|
+
transcript_path: sessionPaths.sessionFile,
|
|
2942
|
+
get cwd() {
|
|
2943
|
+
return currentHookCwd();
|
|
2944
|
+
},
|
|
2945
|
+
permission_mode: 'default',
|
|
2946
|
+
};
|
|
2947
|
+
const appendHookOutcome = async (outcome) => {
|
|
2948
|
+
for (const entry of createClaudeHookAttachmentEntries(outcome, this.translationContext(sessionId, snapshot))) {
|
|
2949
|
+
const tail = await this.append(lease, snapshot.tail, entry);
|
|
2950
|
+
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3115
2951
|
}
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
return pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
3122
|
-
},
|
|
2952
|
+
};
|
|
2953
|
+
const recordHookOutcome = async (outcome, deferUntilApproval = false) => {
|
|
2954
|
+
if (deferUntilApproval) {
|
|
2955
|
+
pendingRecoveryHookOutcomes.push(outcome);
|
|
2956
|
+
return;
|
|
3123
2957
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
let currentTurnUserMessages = null;
|
|
3136
|
-
let currentTurnToolCalls = 0;
|
|
3137
|
-
let projectMemoryMaintained = false;
|
|
3138
|
-
let projectMemoryRecallMessages = [];
|
|
3139
|
-
const observer = {
|
|
3140
|
-
assistantCompleted: async (message) => {
|
|
3141
|
-
const [entry] = translateProviderEvents([
|
|
3142
|
-
{
|
|
3143
|
-
type: 'assistant-message',
|
|
3144
|
-
text: message.content,
|
|
3145
|
-
...(message.thinkingBlocks
|
|
3146
|
-
? { thinkingBlocks: message.thinkingBlocks }
|
|
3147
|
-
: {}),
|
|
3148
|
-
toolCalls: message.toolCalls ?? [],
|
|
3149
|
-
providerMessageId: `msg_${randomUUID().replaceAll('-', '')}`,
|
|
3150
|
-
model: provider.model ?? 'praxis/provider',
|
|
3151
|
-
},
|
|
3152
|
-
], this.translationContext(sessionId, snapshot));
|
|
3153
|
-
if (!entry)
|
|
3154
|
-
throw new Error('Could not translate assistant response');
|
|
3155
|
-
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3156
|
-
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3157
|
-
lastAssistantUuid =
|
|
3158
|
-
typeof entry.uuid === 'string' ? entry.uuid : lastAssistantUuid;
|
|
3159
|
-
},
|
|
3160
|
-
toolCompleted: async (call, toolResult) => {
|
|
3161
|
-
currentTurnToolCalls += 1;
|
|
3162
|
-
const transition = this.worktreeManager?.consumeTransition(call.id);
|
|
3163
|
-
if (transition) {
|
|
3164
|
-
this.options.contextAssembler?.invalidate?.({
|
|
3165
|
-
lifecycleId: sessionId,
|
|
3166
|
-
reason: 'worktree',
|
|
2958
|
+
await appendHookOutcome(outcome);
|
|
2959
|
+
};
|
|
2960
|
+
const flushRecoveryHookOutcomes = async () => {
|
|
2961
|
+
const entries = [];
|
|
2962
|
+
let history = snapshot.entries;
|
|
2963
|
+
let parentUuid = this.logicalTailUuid(snapshot.tail);
|
|
2964
|
+
for (const outcome of pendingRecoveryHookOutcomes) {
|
|
2965
|
+
const outcomeEntries = createClaudeHookAttachmentEntries(outcome, {
|
|
2966
|
+
...this.translationContext(sessionId, snapshot),
|
|
2967
|
+
parentUuid,
|
|
2968
|
+
history,
|
|
3167
2969
|
});
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
const stateTail = await this.append(lease, snapshot.tail, stateEntry);
|
|
3174
|
-
snapshot = {
|
|
3175
|
-
entries: [...snapshot.entries, stateEntry],
|
|
3176
|
-
tail: stateTail,
|
|
3177
|
-
};
|
|
2970
|
+
entries.push(...outcomeEntries);
|
|
2971
|
+
history = [...history, ...outcomeEntries];
|
|
2972
|
+
const lastEntry = outcomeEntries.at(-1);
|
|
2973
|
+
if (typeof lastEntry?.uuid === 'string')
|
|
2974
|
+
parentUuid = lastEntry.uuid;
|
|
3178
2975
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
type: 'permission-mode',
|
|
3183
|
-
permissionMode,
|
|
3184
|
-
sessionId,
|
|
3185
|
-
};
|
|
3186
|
-
const modeTail = await this.append(lease, snapshot.tail, modeEntry);
|
|
3187
|
-
snapshot = {
|
|
3188
|
-
entries: [...snapshot.entries, modeEntry],
|
|
3189
|
-
tail: modeTail,
|
|
3190
|
-
};
|
|
2976
|
+
if (entries.length === 0) {
|
|
2977
|
+
pendingRecoveryHookOutcomes.length = 0;
|
|
2978
|
+
return;
|
|
3191
2979
|
}
|
|
3192
|
-
const
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
2980
|
+
const appendResult = await lease.appendMany(snapshot.tail, entries);
|
|
2981
|
+
if (appendResult.status === 'conflict') {
|
|
2982
|
+
throw new Error(`Claude transcript append conflict: ${appendResult.reason}`);
|
|
2983
|
+
}
|
|
2984
|
+
snapshot = { entries: history, tail: appendResult.tail };
|
|
2985
|
+
pendingRecoveryHookOutcomes.length = 0;
|
|
2986
|
+
};
|
|
2987
|
+
const capabilities = this.toolCapabilities();
|
|
2988
|
+
const taskToolNames = this.capabilityToolNames(this.options.taskToolNames, capabilities);
|
|
2989
|
+
const scheduledToolNames = this.capabilityToolNames(this.options.scheduledToolNames, capabilities);
|
|
2990
|
+
const taskTools = this.options.tools && taskToolNames.length > 0
|
|
2991
|
+
? new ClaudeTaskToolRegistry({
|
|
2992
|
+
base: this.options.tools,
|
|
2993
|
+
cwd: this.activeCwd(),
|
|
2994
|
+
cwdProvider: () => this.activeCwd(),
|
|
2995
|
+
praxisRoot: sessionPaths.praxisRoot,
|
|
2996
|
+
sessionId,
|
|
2997
|
+
taskRoot: sessionPaths.taskRoot,
|
|
2998
|
+
...(this.options.eventSink
|
|
2999
|
+
? { eventSink: this.options.eventSink }
|
|
3000
|
+
: {}),
|
|
3001
|
+
enabledTools: taskToolNames,
|
|
3002
|
+
...(this.options.hooks
|
|
3003
|
+
? {
|
|
3004
|
+
taskHooks: {
|
|
3005
|
+
created: async (task, taskSignal) => {
|
|
3006
|
+
const outcome = await this.options.hooks?.run({
|
|
3007
|
+
...hookSession,
|
|
3008
|
+
hook_event_name: 'TaskCreated',
|
|
3009
|
+
task_id: task.id,
|
|
3010
|
+
task_subject: task.subject,
|
|
3011
|
+
task_description: task.description,
|
|
3012
|
+
}, undefined, taskSignal);
|
|
3013
|
+
if (!outcome)
|
|
3014
|
+
return;
|
|
3015
|
+
await recordHookOutcome(outcome);
|
|
3016
|
+
if (outcome.blockedReason) {
|
|
3017
|
+
throw new Error(`TaskCreated hook error: ${outcome.blockedReason}`);
|
|
3018
|
+
}
|
|
3019
|
+
},
|
|
3020
|
+
completed: async (task, taskSignal) => {
|
|
3021
|
+
const outcome = await this.options.hooks?.run({
|
|
3022
|
+
...hookSession,
|
|
3023
|
+
hook_event_name: 'TaskCompleted',
|
|
3024
|
+
task_id: task.id,
|
|
3025
|
+
task_subject: task.subject,
|
|
3026
|
+
task_description: task.description,
|
|
3027
|
+
}, undefined, taskSignal);
|
|
3028
|
+
if (!outcome)
|
|
3029
|
+
return;
|
|
3030
|
+
await recordHookOutcome(outcome);
|
|
3031
|
+
if (outcome.blockedReason) {
|
|
3032
|
+
throw new Error(`TaskCompleted hook error: ${outcome.blockedReason}`);
|
|
3033
|
+
}
|
|
3034
|
+
},
|
|
3035
|
+
},
|
|
3036
|
+
}
|
|
3037
|
+
: {}),
|
|
3038
|
+
})
|
|
3039
|
+
: null;
|
|
3040
|
+
const scheduledTools = this.scheduledPrompts &&
|
|
3041
|
+
this.options.tools &&
|
|
3042
|
+
scheduledToolNames.length > 0
|
|
3043
|
+
? new ClaudeScheduledToolRegistry({
|
|
3044
|
+
base: taskTools ?? this.options.tools,
|
|
3045
|
+
manager: this.scheduledPrompts,
|
|
3046
|
+
sessionId,
|
|
3047
|
+
enabledTools: scheduledToolNames,
|
|
3048
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
3049
|
+
})
|
|
3050
|
+
: null;
|
|
3051
|
+
const baseTools = scheduledTools ?? taskTools ?? this.options.tools;
|
|
3052
|
+
const turnPermissions = this.options.interactiveTools?.permissions(sessionId) ??
|
|
3053
|
+
this.options.permissions;
|
|
3054
|
+
const subagentExecutor = (this.options.enableSubagents || this.options.enableWorkflows) &&
|
|
3055
|
+
baseTools &&
|
|
3056
|
+
turnPermissions
|
|
3057
|
+
? new ClaudeSubagentExecutor({
|
|
3058
|
+
configRoot: this.options.configRoot,
|
|
3059
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
3060
|
+
cwd: this.activeCwd(),
|
|
3061
|
+
cwdProvider: () => this.activeCwd(),
|
|
3062
|
+
claudeVersion: this.options.claudeVersion,
|
|
3063
|
+
provider,
|
|
3064
|
+
persistence: this.options.sessionPersistence === false ? 'memory' : 'disk',
|
|
3065
|
+
...(this.options.providerForModel
|
|
3066
|
+
? { providerForModel: this.options.providerForModel }
|
|
3067
|
+
: {}),
|
|
3068
|
+
baseTools,
|
|
3069
|
+
permissions: turnPermissions,
|
|
3070
|
+
...(this.options.permissionResolverForMode
|
|
3071
|
+
? {
|
|
3072
|
+
permissionResolverForMode: this.options.permissionResolverForMode,
|
|
3073
|
+
}
|
|
3074
|
+
: {}),
|
|
3075
|
+
parentPermissionMode: () => agentPermissionMode(this.options.interactiveTools?.mode(sessionId) ??
|
|
3076
|
+
this.options.permissionMode),
|
|
3077
|
+
...(this.options.subagentToolNames
|
|
3078
|
+
? {
|
|
3079
|
+
toolNames: this.capabilityToolNames(this.options.subagentToolNames, capabilities),
|
|
3080
|
+
}
|
|
3081
|
+
: {}),
|
|
3082
|
+
...(this.options.extensions
|
|
3083
|
+
? { extensions: this.options.extensions }
|
|
3084
|
+
: {}),
|
|
3085
|
+
...(this.options.mcp ? { mcp: this.options.mcp } : {}),
|
|
3086
|
+
...(this.options.hooks ? { hooks: this.options.hooks } : {}),
|
|
3087
|
+
...(this.options.contextAssembler
|
|
3088
|
+
? { contextAssembler: this.options.contextAssembler }
|
|
3089
|
+
: {}),
|
|
3090
|
+
...((this.options.contextBudget?.reserveTokens ??
|
|
3091
|
+
this.options.contextReserveTokens) === undefined
|
|
3092
|
+
? {}
|
|
3093
|
+
: {
|
|
3094
|
+
contextReserveTokens: this.options.contextBudget?.reserveTokens ??
|
|
3095
|
+
this.options.contextReserveTokens,
|
|
3096
|
+
}),
|
|
3097
|
+
...(this.options.approveTool
|
|
3098
|
+
? { approveTool: this.options.approveTool }
|
|
3099
|
+
: {}),
|
|
3100
|
+
permissionUpdates: () => this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
3101
|
+
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
3102
|
+
onLineChanges: (changes) => {
|
|
3103
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
3104
|
+
if (!tracker) {
|
|
3105
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3106
|
+
}
|
|
3107
|
+
tracker.recordLineChanges(changes);
|
|
3211
3108
|
},
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
const pathValue = call.input.file_path;
|
|
3219
|
-
const path = typeof pathValue === 'string'
|
|
3220
|
-
? resolve(this.activeCwd(), pathValue)
|
|
3221
|
-
: null;
|
|
3222
|
-
if (path &&
|
|
3223
|
-
isPathWithin(this.options.projectMemoryDirectory, path)) {
|
|
3224
|
-
if (call.name === 'Read') {
|
|
3225
|
-
this.options.projectMemoryRecall?.recordRead(sessionId, path);
|
|
3109
|
+
...(this.options.eventSink
|
|
3110
|
+
? { eventSink: this.options.eventSink }
|
|
3111
|
+
: {}),
|
|
3112
|
+
...(taskTools
|
|
3113
|
+
? {
|
|
3114
|
+
backgroundTaskNotifications: (waitForRunning) => taskTools.notifications(waitForRunning),
|
|
3226
3115
|
}
|
|
3227
|
-
|
|
3228
|
-
|
|
3116
|
+
: {}),
|
|
3117
|
+
notificationDelivered: (notification) => transcriptContainsBackgroundAgentNotification(snapshot.entries, notification),
|
|
3118
|
+
stopOwnedBackgroundAgent: (ownerSessionId, agentId) => this.backgroundTasks.stopAgent(ownerSessionId, agentId),
|
|
3119
|
+
outputOwnedBackgroundAgent: (ownerSessionId, agentId, options) => this.backgroundTasks.outputAgent(ownerSessionId, agentId, options),
|
|
3120
|
+
sendOwnedBackgroundAgent: (ownerSessionId, agentId, message, summary, toolUseId) => this.sendOwnedBackgroundAgent(ownerSessionId, agentId, message, summary, toolUseId),
|
|
3121
|
+
})
|
|
3122
|
+
: null;
|
|
3123
|
+
if (subagentExecutor) {
|
|
3124
|
+
this.subagentExecutors.add(subagentExecutor);
|
|
3125
|
+
this.subagentExecutorSessions.set(subagentExecutor, sessionId);
|
|
3126
|
+
this.backgroundTasks.registerAgents(sessionId, subagentExecutor);
|
|
3127
|
+
}
|
|
3128
|
+
const agentTools = subagentExecutor
|
|
3129
|
+
? subagentExecutor.registry(sessionId, 0, (callId) => currentPromptId ??
|
|
3130
|
+
this.promptIdForToolCall(snapshot.entries, callId))
|
|
3131
|
+
: baseTools;
|
|
3132
|
+
const turnTools = this.workflowManager && subagentExecutor && agentTools
|
|
3133
|
+
? new ClaudeWorkflowToolRegistry({
|
|
3134
|
+
base: agentTools,
|
|
3135
|
+
manager: this.workflowManager,
|
|
3136
|
+
executor: subagentExecutor,
|
|
3137
|
+
cwd: this.activeCwd(),
|
|
3138
|
+
cwdProvider: () => this.activeCwd(),
|
|
3139
|
+
configRoot: this.options.configRoot,
|
|
3140
|
+
sessionId,
|
|
3141
|
+
promptIdForCall: (callId) => currentPromptId ??
|
|
3142
|
+
this.promptIdForToolCall(snapshot.entries, callId),
|
|
3143
|
+
defaultModel: provider.model ?? 'praxis/provider',
|
|
3144
|
+
tokenBudget: workflowTokenTarget(effectivePrompt),
|
|
3145
|
+
enabled: capabilities.has('Workflow'),
|
|
3146
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
3147
|
+
})
|
|
3148
|
+
: agentTools;
|
|
3149
|
+
const workspaceTools = this.worktreeManager &&
|
|
3150
|
+
turnTools &&
|
|
3151
|
+
this.options.workspace &&
|
|
3152
|
+
(this.options.worktreeToolNames?.length ?? 0) > 0
|
|
3153
|
+
? new ClaudeWorktreeToolRegistry({
|
|
3154
|
+
base: turnTools,
|
|
3155
|
+
manager: this.worktreeManager,
|
|
3156
|
+
workspace: this.options.workspace,
|
|
3157
|
+
enabledTools: this.options.worktreeToolNames ?? [],
|
|
3158
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
3159
|
+
})
|
|
3160
|
+
: turnTools;
|
|
3161
|
+
const messageTools = this.options.brief && workspaceTools
|
|
3162
|
+
? new ClaudeUserMessageToolRegistry(workspaceTools, (message) => this.options.eventSink?.({
|
|
3163
|
+
type: 'user-message',
|
|
3164
|
+
message: message.message,
|
|
3165
|
+
status: message.status,
|
|
3166
|
+
...(message.attachments.length
|
|
3167
|
+
? { attachments: message.attachments }
|
|
3168
|
+
: {}),
|
|
3169
|
+
}))
|
|
3170
|
+
: workspaceTools;
|
|
3171
|
+
const interactiveMessageTools = this.options.interactiveTools && messageTools
|
|
3172
|
+
? this.options.interactiveTools.registry(messageTools, sessionId)
|
|
3173
|
+
: messageTools;
|
|
3174
|
+
const fileHistoryTools = fileHistory && interactiveMessageTools
|
|
3175
|
+
? {
|
|
3176
|
+
definitions: () => interactiveMessageTools.definitions(),
|
|
3177
|
+
schedulingPolicy: (call) => ({
|
|
3178
|
+
...resolveToolSchedulingPolicy(interactiveMessageTools, call),
|
|
3179
|
+
startAfterAssistant: true,
|
|
3180
|
+
}),
|
|
3181
|
+
prepare: (call, context) => interactiveMessageTools.prepare(call, context),
|
|
3182
|
+
execute: async (call, context) => {
|
|
3183
|
+
const path = call.name === 'Write' || call.name === 'Edit'
|
|
3184
|
+
? call.input.file_path
|
|
3185
|
+
: call.name === 'NotebookEdit'
|
|
3186
|
+
? call.input.notebook_path
|
|
3187
|
+
: undefined;
|
|
3188
|
+
if (typeof path !== 'string') {
|
|
3189
|
+
return interactiveMessageTools.execute(call, context);
|
|
3229
3190
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
const resolvedAccessedPath = resolve(this.activeCwd(), accessedPath);
|
|
3234
|
-
if (isPathWithin(this.options.projectMemoryDirectory, resolvedAccessedPath)) {
|
|
3235
|
-
this.options.projectMemoryRecall?.recordRead(sessionId, resolvedAccessedPath);
|
|
3236
|
-
}
|
|
3191
|
+
if ((call.name === 'Write' || call.name === 'Edit') &&
|
|
3192
|
+
(await this.options.interactiveTools?.isPlanFile(sessionId, path))) {
|
|
3193
|
+
return interactiveMessageTools.execute(call, context);
|
|
3237
3194
|
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
triggerFilePath: filePath,
|
|
3266
|
-
...(rule.importedFrom === undefined
|
|
3267
|
-
? {}
|
|
3268
|
-
: { parentFilePath: rule.importedFrom }),
|
|
3269
|
-
}, 'path_glob_match');
|
|
3270
|
-
}
|
|
3195
|
+
const snapshotMessageId = currentPromptId ??
|
|
3196
|
+
this.promptIdForToolCall(snapshot.entries, call.id);
|
|
3197
|
+
const assistantMessageId = lastAssistantUuid ??
|
|
3198
|
+
this.assistantIdForToolCall(snapshot.entries, call.id);
|
|
3199
|
+
if (!snapshotMessageId || !assistantMessageId) {
|
|
3200
|
+
throw new Error('Claude file history could not link tool call');
|
|
3201
|
+
}
|
|
3202
|
+
const prepared = await fileHistory.prepareMutation(snapshot.entries, snapshotMessageId, path);
|
|
3203
|
+
let result;
|
|
3204
|
+
try {
|
|
3205
|
+
result = await interactiveMessageTools.execute(call, context);
|
|
3206
|
+
}
|
|
3207
|
+
catch (error) {
|
|
3208
|
+
await prepared.rollback();
|
|
3209
|
+
throw error;
|
|
3210
|
+
}
|
|
3211
|
+
if (result.isError) {
|
|
3212
|
+
await prepared.rollback();
|
|
3213
|
+
return result;
|
|
3214
|
+
}
|
|
3215
|
+
const entry = prepared.commit(assistantMessageId);
|
|
3216
|
+
if (entry) {
|
|
3217
|
+
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3218
|
+
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3219
|
+
}
|
|
3220
|
+
return result;
|
|
3221
|
+
},
|
|
3271
3222
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3223
|
+
: interactiveMessageTools;
|
|
3224
|
+
const capabilityTools = fileHistoryTools
|
|
3225
|
+
? new ClaudeCapabilityToolRegistry(fileHistoryTools, capabilities)
|
|
3226
|
+
: undefined;
|
|
3227
|
+
const structuredCapture = this.options.structuredOutputSchema
|
|
3228
|
+
? { calls: 0, value: undefined }
|
|
3229
|
+
: undefined;
|
|
3230
|
+
const agentScopedTools = agent && capabilityTools
|
|
3231
|
+
? new FilteredToolRegistry(capabilityTools, {
|
|
3232
|
+
tools: mainAgentToolNames(capabilityTools, agent),
|
|
3233
|
+
})
|
|
3234
|
+
: capabilityTools;
|
|
3235
|
+
const structuredTools = this.options.structuredOutputSchema && structuredCapture
|
|
3236
|
+
? new StructuredOutputRegistry(agentScopedTools ?? this.options.tools ?? emptyToolRegistry, this.options.structuredOutputSchema, structuredCapture)
|
|
3237
|
+
: agentScopedTools;
|
|
3238
|
+
const hookTools = this.options.hooks && structuredTools && turnPermissions
|
|
3239
|
+
? new ClaudeHookToolCoordinator({
|
|
3240
|
+
tools: structuredTools,
|
|
3241
|
+
permissions: turnPermissions,
|
|
3242
|
+
hooks: this.options.hooks,
|
|
3243
|
+
session: hookSession,
|
|
3244
|
+
recordOutcome: recordHookOutcome,
|
|
3245
|
+
...(this.options.eventSink
|
|
3246
|
+
? {
|
|
3247
|
+
warn: (message) => this.options.eventSink?.({
|
|
3248
|
+
type: 'warning',
|
|
3249
|
+
message,
|
|
3250
|
+
}),
|
|
3251
|
+
}
|
|
3252
|
+
: {}),
|
|
3253
|
+
deferPreToolUseOutcome: (call) => pendingRecoveryToolCallIds.has(call.id),
|
|
3254
|
+
})
|
|
3255
|
+
: null;
|
|
3256
|
+
const runtime = new AgentRuntime(provider, controller.emit, {
|
|
3257
|
+
emitInitialContextState: false,
|
|
3258
|
+
...(this.options.emitToolUseSummaries
|
|
3259
|
+
? {
|
|
3260
|
+
generateToolUseSummary: ({ tools, lastAssistantText, signal: summarySignal, }) => generateToolUseSummary(provider, tools, summarySignal, lastAssistantText, (metrics) => this.recordAuxiliaryMetrics(sessionId, metrics)),
|
|
3278
3261
|
}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
entries: [...snapshot.entries, followUpEntry],
|
|
3282
|
-
tail: followUpTail,
|
|
3283
|
-
};
|
|
3284
|
-
currentTurnUserMessages?.push(content);
|
|
3285
|
-
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3286
|
-
}
|
|
3287
|
-
},
|
|
3288
|
-
};
|
|
3289
|
-
let turnCompleted = false;
|
|
3290
|
-
try {
|
|
3291
|
-
const outcome = await this.hookLifecycle.start(sessionId, hookSession, requireExisting ? 'resume' : 'startup', signal);
|
|
3292
|
-
if (outcome) {
|
|
3293
|
-
await recordHookOutcome(outcome, pendingRecoveryToolCallIds.size > 0);
|
|
3294
|
-
}
|
|
3295
|
-
const approveRecovery = this.options.approveRecovery;
|
|
3296
|
-
const recoveryRequest = {
|
|
3297
|
-
cwd: this.activeCwd(),
|
|
3298
|
-
toolResultDirectory,
|
|
3299
|
-
messages: projectClaudeModelMessages(snapshot.entries),
|
|
3300
|
-
observer,
|
|
3301
|
-
permissionUpdates: this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
3302
|
-
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
3303
|
-
...(signal ? { signal } : {}),
|
|
3304
|
-
...(approveRecovery
|
|
3262
|
+
: {}),
|
|
3263
|
+
...(this.options.pricing
|
|
3305
3264
|
? {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
}
|
|
3310
|
-
if (signal?.aborted)
|
|
3311
|
-
throw new AgentRunCancelledError();
|
|
3312
|
-
await flushRecoveryHookOutcomes();
|
|
3313
|
-
pendingRecoveryToolCallIds.delete(call.id);
|
|
3314
|
-
return true;
|
|
3265
|
+
costUsd: (usage) => {
|
|
3266
|
+
const pricing = this.options.pricing?.resolve(provider.model ?? 'praxis/provider');
|
|
3267
|
+
return pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
3315
3268
|
},
|
|
3316
|
-
approveTool: () => true,
|
|
3317
3269
|
}
|
|
3318
3270
|
: {}),
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
const recoveryResults = await runtime.recoverToolCalls(unresolvedToolCalls, recoveryRequest);
|
|
3325
|
-
const recoveryUsage = recoveryResults.reduce((usage, result) => result.usage ? mergeUsage(usage, result.usage) : usage, { inputTokens: 0, outputTokens: 0 });
|
|
3326
|
-
const recoveryModelUsage = mergeSessionRawModelUsage(...recoveryResults.map((result) => result.isError ? undefined : result.modelUsage));
|
|
3327
|
-
const foregroundLineChanges = createLineCountAccumulator();
|
|
3328
|
-
for (const result of recoveryResults) {
|
|
3329
|
-
foregroundLineChanges.add(result);
|
|
3330
|
-
}
|
|
3331
|
-
if (this.options.agent &&
|
|
3332
|
-
agent &&
|
|
3333
|
-
getClaudeAgentSetting(snapshot.entries) !== this.options.agent) {
|
|
3334
|
-
const agentSetting = createClaudeAgentSettingEntry(sessionId, this.options.agent);
|
|
3335
|
-
const settingTail = await this.append(lease, snapshot.tail, agentSetting);
|
|
3336
|
-
snapshot = {
|
|
3337
|
-
entries: [...snapshot.entries, agentSetting],
|
|
3338
|
-
tail: settingTail,
|
|
3339
|
-
};
|
|
3340
|
-
}
|
|
3341
|
-
const sessionMemory = this.sessionMemoryController(sessionId);
|
|
3342
|
-
let assembledContext;
|
|
3343
|
-
let agentSystem = null;
|
|
3344
|
-
let planModeMessage;
|
|
3345
|
-
let sessionMemoryMessage = null;
|
|
3346
|
-
let contextMessages = [];
|
|
3347
|
-
let stableSystemMessageCount;
|
|
3348
|
-
const refreshRuntimeContext = async () => {
|
|
3349
|
-
agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
3350
|
-
planModeMessage =
|
|
3351
|
-
this.options.interactiveTools?.contextMessage(sessionId);
|
|
3352
|
-
sessionMemoryMessage = sessionMemory
|
|
3353
|
-
? this.sessionMemoryMessage(await sessionMemory.summary())
|
|
3354
|
-
: null;
|
|
3355
|
-
const additionalSections = [
|
|
3356
|
-
...(planModeMessage
|
|
3357
|
-
? [
|
|
3358
|
-
{
|
|
3359
|
-
id: 'plan-mode',
|
|
3360
|
-
placement: 'system',
|
|
3361
|
-
stability: 'volatile',
|
|
3362
|
-
content: planModeMessage,
|
|
3363
|
-
},
|
|
3364
|
-
]
|
|
3365
|
-
: []),
|
|
3366
|
-
...(sessionMemoryMessage
|
|
3367
|
-
? [
|
|
3368
|
-
{
|
|
3369
|
-
id: 'session-memory',
|
|
3370
|
-
placement: 'system',
|
|
3371
|
-
stability: 'volatile',
|
|
3372
|
-
content: sessionMemoryMessage,
|
|
3373
|
-
},
|
|
3374
|
-
]
|
|
3375
|
-
: []),
|
|
3376
|
-
...(this.options.brief
|
|
3377
|
-
? [
|
|
3378
|
-
{
|
|
3379
|
-
id: 'brief-output',
|
|
3380
|
-
placement: 'system',
|
|
3381
|
-
stability: 'volatile',
|
|
3382
|
-
content: CLAUDE_USER_MESSAGE_PROMPT,
|
|
3383
|
-
},
|
|
3384
|
-
]
|
|
3385
|
-
: []),
|
|
3386
|
-
...(this.options.structuredOutputSchema
|
|
3387
|
-
? [
|
|
3388
|
-
{
|
|
3389
|
-
id: 'structured-output',
|
|
3390
|
-
placement: 'system',
|
|
3391
|
-
stability: 'volatile',
|
|
3392
|
-
content: 'You MUST call StructuredOutput exactly once at the end with a value matching the requested JSON Schema.',
|
|
3393
|
-
},
|
|
3394
|
-
]
|
|
3395
|
-
: []),
|
|
3396
|
-
];
|
|
3397
|
-
assembledContext = await this.options.contextAssembler?.assemble({
|
|
3398
|
-
cwd: this.activeCwd(),
|
|
3399
|
-
lifecycleId: sessionId,
|
|
3400
|
-
...(agentSystem
|
|
3401
|
-
? { mode: 'agent', baseSystemPrompt: agentSystem }
|
|
3402
|
-
: {}),
|
|
3403
|
-
additionalSections,
|
|
3404
|
-
});
|
|
3405
|
-
const hasPromptManifest = assembledContext?.promptSections !== undefined;
|
|
3406
|
-
stableSystemMessageCount = hasPromptManifest
|
|
3407
|
-
? assembledContext?.stableSystemSectionCount
|
|
3408
|
-
: undefined;
|
|
3409
|
-
const assembledSystemMessages = hasPromptManifest
|
|
3410
|
-
? (assembledContext?.systemMessages ?? [])
|
|
3411
|
-
: this.assembledSystemMessages(agent, assembledContext?.systemMessages ?? []);
|
|
3412
|
-
contextMessages = [
|
|
3413
|
-
...(!hasPromptManifest && agentSystem
|
|
3414
|
-
? [{ role: 'system', content: agentSystem }]
|
|
3415
|
-
: []),
|
|
3416
|
-
...assembledSystemMessages,
|
|
3417
|
-
...(!hasPromptManifest && planModeMessage
|
|
3418
|
-
? [{ role: 'system', content: planModeMessage }]
|
|
3419
|
-
: []),
|
|
3420
|
-
...(!hasPromptManifest && sessionMemoryMessage
|
|
3421
|
-
? [{ role: 'system', content: sessionMemoryMessage }]
|
|
3422
|
-
: []),
|
|
3423
|
-
...(!hasPromptManifest && this.options.brief
|
|
3424
|
-
? [
|
|
3425
|
-
{
|
|
3426
|
-
role: 'system',
|
|
3427
|
-
content: CLAUDE_USER_MESSAGE_PROMPT,
|
|
3428
|
-
},
|
|
3429
|
-
]
|
|
3430
|
-
: []),
|
|
3431
|
-
...(!hasPromptManifest && this.options.structuredOutputSchema
|
|
3432
|
-
? [
|
|
3433
|
-
{
|
|
3434
|
-
role: 'system',
|
|
3435
|
-
content: 'You MUST call StructuredOutput exactly once at the end with a value matching the requested JSON Schema.',
|
|
3436
|
-
},
|
|
3437
|
-
]
|
|
3438
|
-
: []),
|
|
3439
|
-
];
|
|
3440
|
-
};
|
|
3441
|
-
await refreshRuntimeContext();
|
|
3442
|
-
const expansion = skipUserPrompt
|
|
3443
|
-
? { userMessages: [] }
|
|
3444
|
-
: shellCommand === undefined
|
|
3445
|
-
? this.options.extensions
|
|
3446
|
-
? await this.options.extensions.expandPromptAsync(effectivePrompt, signal, toolResultDirectory)
|
|
3447
|
-
: { userMessages: [effectivePrompt] }
|
|
3271
|
+
...(this.options.maxBudgetUsd === undefined
|
|
3272
|
+
? {}
|
|
3273
|
+
: { maxBudgetUsd: this.options.maxBudgetUsd }),
|
|
3274
|
+
...(hookTools
|
|
3275
|
+
? { tools: hookTools, permissions: hookTools }
|
|
3448
3276
|
: {
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
const attachmentIndex = expansion.messages
|
|
3453
|
-
? expandedMessages.length - 1
|
|
3454
|
-
: 0;
|
|
3455
|
-
currentTurnUserMessages = [...expansion.userMessages];
|
|
3456
|
-
this.options.eventSink?.({
|
|
3457
|
-
type: 'state',
|
|
3458
|
-
state: 'assembling-context',
|
|
3277
|
+
...(structuredTools ? { tools: structuredTools } : {}),
|
|
3278
|
+
...(turnPermissions ? { permissions: turnPermissions } : {}),
|
|
3279
|
+
}),
|
|
3459
3280
|
});
|
|
3460
|
-
let
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
let
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
:
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3281
|
+
let currentTurnUserMessages = null;
|
|
3282
|
+
let currentTurnToolCalls = 0;
|
|
3283
|
+
let projectMemoryMaintained = false;
|
|
3284
|
+
let projectMemoryRecallMessages = [];
|
|
3285
|
+
let observeModelRequestUsage = () => undefined;
|
|
3286
|
+
const observer = {
|
|
3287
|
+
modelRequestCompleted: async (input) => {
|
|
3288
|
+
observeModelRequestUsage(input);
|
|
3289
|
+
},
|
|
3290
|
+
assistantCompleted: async (message) => {
|
|
3291
|
+
const [entry] = translateProviderEvents([
|
|
3292
|
+
{
|
|
3293
|
+
type: 'assistant-message',
|
|
3294
|
+
text: message.content,
|
|
3295
|
+
...(message.thinkingBlocks
|
|
3296
|
+
? { thinkingBlocks: message.thinkingBlocks }
|
|
3297
|
+
: {}),
|
|
3298
|
+
toolCalls: message.toolCalls ?? [],
|
|
3299
|
+
providerMessageId: `msg_${randomUUID().replaceAll('-', '')}`,
|
|
3300
|
+
model: provider.model ?? 'praxis/provider',
|
|
3301
|
+
},
|
|
3302
|
+
], this.translationContext(sessionId, snapshot));
|
|
3303
|
+
if (!entry)
|
|
3304
|
+
throw new Error('Could not translate assistant response');
|
|
3305
|
+
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3306
|
+
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3307
|
+
lastAssistantUuid =
|
|
3308
|
+
typeof entry.uuid === 'string' ? entry.uuid : lastAssistantUuid;
|
|
3309
|
+
},
|
|
3310
|
+
toolCompleted: async (call, toolResult) => {
|
|
3311
|
+
currentTurnToolCalls += 1;
|
|
3312
|
+
const transition = this.worktreeManager?.consumeTransition(call.id);
|
|
3313
|
+
if (transition) {
|
|
3314
|
+
this.options.contextAssembler?.invalidate?.({
|
|
3315
|
+
lifecycleId: sessionId,
|
|
3316
|
+
reason: 'worktree',
|
|
3317
|
+
});
|
|
3318
|
+
const stateEntry = {
|
|
3319
|
+
type: 'worktree-state',
|
|
3320
|
+
worktreeSession: transition.state,
|
|
3321
|
+
sessionId,
|
|
3322
|
+
};
|
|
3323
|
+
const stateTail = await this.append(lease, snapshot.tail, stateEntry);
|
|
3324
|
+
snapshot = {
|
|
3325
|
+
entries: [...snapshot.entries, stateEntry],
|
|
3326
|
+
tail: stateTail,
|
|
3327
|
+
};
|
|
3485
3328
|
}
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
let insertionIndex = messages.length;
|
|
3499
|
-
let foundPrompt = false;
|
|
3500
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3501
|
-
const message = messages[index];
|
|
3502
|
-
if (message?.role === 'user' &&
|
|
3503
|
-
typeof message.content === 'string' &&
|
|
3504
|
-
message.content.endsWith(effectivePrompt)) {
|
|
3505
|
-
insertionIndex = index;
|
|
3506
|
-
foundPrompt = true;
|
|
3507
|
-
break;
|
|
3329
|
+
const permissionMode = this.options.interactiveTools?.consumeTransition(call.id);
|
|
3330
|
+
if (permissionMode) {
|
|
3331
|
+
const modeEntry = {
|
|
3332
|
+
type: 'permission-mode',
|
|
3333
|
+
permissionMode,
|
|
3334
|
+
sessionId,
|
|
3335
|
+
};
|
|
3336
|
+
const modeTail = await this.append(lease, snapshot.tail, modeEntry);
|
|
3337
|
+
snapshot = {
|
|
3338
|
+
entries: [...snapshot.entries, modeEntry],
|
|
3339
|
+
tail: modeTail,
|
|
3340
|
+
};
|
|
3508
3341
|
}
|
|
3509
|
-
|
|
3510
|
-
if (!foundPrompt)
|
|
3511
|
-
return [...messages];
|
|
3512
|
-
return [
|
|
3513
|
-
...messages.slice(0, insertionIndex),
|
|
3514
|
-
...agentMentionMessages.map((content) => ({
|
|
3515
|
-
role: 'user',
|
|
3516
|
-
content,
|
|
3517
|
-
})),
|
|
3518
|
-
...messages.slice(insertionIndex),
|
|
3519
|
-
];
|
|
3520
|
-
};
|
|
3521
|
-
const injectDynamicContext = (messages) => injectFirstUserMessageContext(messages, assembledContext?.firstUserMessageContext);
|
|
3522
|
-
const injectTurnContext = (messages) => injectAgentMentionContext(injectDynamicContext(messages));
|
|
3523
|
-
let compactionAnchorUuid = this.lastMessageUuid(snapshot.entries);
|
|
3524
|
-
const compactIfNeeded = async (pendingMessages = [], preservedUserMessages = [], options = {}) => {
|
|
3525
|
-
if (!budget || this.options.autoCompact === false)
|
|
3526
|
-
return;
|
|
3527
|
-
const historyMessages = [
|
|
3528
|
-
...projectClaudeModelMessages(snapshot.entries),
|
|
3529
|
-
...projectMemoryRecallMessages,
|
|
3530
|
-
];
|
|
3531
|
-
const predicted = budget.evaluate([
|
|
3532
|
-
...contextMessages,
|
|
3533
|
-
...injectTurnContext([...historyMessages, ...pendingMessages]),
|
|
3534
|
-
], definitions, options);
|
|
3535
|
-
if (!predicted.shouldCompact)
|
|
3536
|
-
return;
|
|
3537
|
-
const irreducibleMessages = [
|
|
3538
|
-
...contextMessages,
|
|
3539
|
-
...injectTurnContext([
|
|
3540
|
-
...pendingMessages,
|
|
3541
|
-
...preservedUserMessages.map((content) => ({
|
|
3542
|
-
role: 'user',
|
|
3543
|
-
content,
|
|
3544
|
-
})),
|
|
3545
|
-
]),
|
|
3546
|
-
];
|
|
3547
|
-
const irreducible = budget.evaluate(irreducibleMessages, definitions);
|
|
3548
|
-
budget.assertFits(irreducible);
|
|
3549
|
-
let logicalParentUuid = compactionAnchorUuid;
|
|
3550
|
-
if (!logicalParentUuid || historyMessages.length === 0) {
|
|
3551
|
-
budget.assertFits(predicted);
|
|
3552
|
-
throw new Error('Cannot compact an empty Claude transcript');
|
|
3553
|
-
}
|
|
3554
|
-
if (findUnresolvedClaudeToolCalls(snapshot.entries).length > 0) {
|
|
3555
|
-
throw new Error('Cannot compact a Claude session with unresolved tool calls');
|
|
3556
|
-
}
|
|
3557
|
-
const preCompact = await this.runAdvisoryHook(sessionId, 'PreCompact', { trigger: 'auto', custom_instructions: null }, 'auto', signal);
|
|
3558
|
-
const memorySelection = sessionMemory
|
|
3559
|
-
? await this.selectMemoryPreservedCompact(sessionId, selectClaudeActiveTranscript(snapshot.entries))
|
|
3560
|
-
: null;
|
|
3561
|
-
const compactorMessages = memorySelection
|
|
3562
|
-
? [
|
|
3563
|
-
memorySelection.memoryMessage,
|
|
3564
|
-
...projectClaudeModelMessages(memorySelection.compactedEntries),
|
|
3565
|
-
]
|
|
3566
|
-
: historyMessages;
|
|
3567
|
-
compactorMessages.push(...successfulHookOutput(preCompact).map((content) => ({
|
|
3568
|
-
role: 'user',
|
|
3569
|
-
content: `Additional summarization context: ${content}`,
|
|
3570
|
-
})));
|
|
3571
|
-
if (memorySelection) {
|
|
3572
|
-
logicalParentUuid = memorySelection.logicalParentUuid;
|
|
3573
|
-
}
|
|
3574
|
-
this.options.eventSink?.({ type: 'state', state: 'compacting' });
|
|
3575
|
-
const compactEnvelope = budget.evaluate([
|
|
3576
|
-
...irreducibleMessages,
|
|
3577
|
-
{
|
|
3578
|
-
role: 'user',
|
|
3579
|
-
content: formatClaudeCompactSummary(''),
|
|
3580
|
-
},
|
|
3581
|
-
], definitions);
|
|
3582
|
-
let targetTokens = Math.min(8192, compactEnvelope.availableTokens - compactEnvelope.estimatedTokens);
|
|
3583
|
-
if (targetTokens < 1) {
|
|
3584
|
-
budget.assertFits(budget.evaluate([
|
|
3585
|
-
...irreducibleMessages,
|
|
3342
|
+
const [entry] = translateProviderEvents([
|
|
3586
3343
|
{
|
|
3587
|
-
|
|
3588
|
-
|
|
3344
|
+
type: 'tool-result',
|
|
3345
|
+
toolCallId: call.id,
|
|
3346
|
+
content: toolResult.content,
|
|
3347
|
+
...(toolResult.contentBlocks
|
|
3348
|
+
? { contentBlocks: toolResult.contentBlocks }
|
|
3349
|
+
: {}),
|
|
3350
|
+
...(toolResult.images ? { images: toolResult.images } : {}),
|
|
3351
|
+
...(toolResult.documents
|
|
3352
|
+
? { documents: toolResult.documents }
|
|
3353
|
+
: {}),
|
|
3354
|
+
isError: toolResult.isError,
|
|
3355
|
+
...(toolResult.nativeToolUseResult
|
|
3356
|
+
? { nativeToolUseResult: toolResult.nativeToolUseResult }
|
|
3357
|
+
: {}),
|
|
3358
|
+
...(toolResult.nativeMcpMeta
|
|
3359
|
+
? { nativeMcpMeta: toolResult.nativeMcpMeta }
|
|
3360
|
+
: {}),
|
|
3589
3361
|
},
|
|
3590
|
-
],
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
const proposedCompactionDurationWithoutRetriesMs = (compactionDurationWithoutRetriesMs ?? 0) +
|
|
3605
|
-
compactedDurationWithoutRetriesMs;
|
|
3606
|
-
if (!Number.isFinite(proposedCompactionDurationWithoutRetriesMs)) {
|
|
3607
|
-
throw new TypeError('compaction durationWithoutRetriesMs total overflow');
|
|
3608
|
-
}
|
|
3609
|
-
const boundaryUuid = randomUUID();
|
|
3610
|
-
const summaryUuid = randomUUID();
|
|
3611
|
-
const timestamp = new Date().toISOString();
|
|
3612
|
-
const preTokens = budget.evaluate([...contextMessages, ...injectTurnContext(historyMessages)], definitions).estimatedTokens;
|
|
3613
|
-
const compactEntries = (postTokens) => {
|
|
3614
|
-
const uuids = [boundaryUuid, summaryUuid];
|
|
3615
|
-
return createClaudeCompactEntries({
|
|
3616
|
-
sessionId,
|
|
3617
|
-
logicalParentUuid,
|
|
3618
|
-
summary: compacted.summary,
|
|
3619
|
-
preTokens,
|
|
3620
|
-
postTokens,
|
|
3621
|
-
previousCumulativeDroppedTokens: getCumulativeDroppedTokens(snapshot.entries),
|
|
3622
|
-
durationMs: compactedDurationMs,
|
|
3623
|
-
cwd: this.activeCwd(),
|
|
3624
|
-
claudeVersion: this.options.claudeVersion,
|
|
3625
|
-
gitBranch: null,
|
|
3626
|
-
...(memorySelection
|
|
3627
|
-
? {
|
|
3628
|
-
preservedUuids: memorySelection.preservedEntries.flatMap((entry) => typeof entry.uuid === 'string' ? [entry.uuid] : []),
|
|
3362
|
+
], this.translationContext(sessionId, snapshot));
|
|
3363
|
+
if (!entry)
|
|
3364
|
+
throw new Error('Could not translate tool result');
|
|
3365
|
+
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3366
|
+
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3367
|
+
if (!toolResult.isError && this.options.projectMemoryDirectory) {
|
|
3368
|
+
const pathValue = call.input.file_path;
|
|
3369
|
+
const path = typeof pathValue === 'string'
|
|
3370
|
+
? resolve(this.activeCwd(), pathValue)
|
|
3371
|
+
: null;
|
|
3372
|
+
if (path &&
|
|
3373
|
+
isPathWithin(this.options.projectMemoryDirectory, path)) {
|
|
3374
|
+
if (call.name === 'Read') {
|
|
3375
|
+
turnMemory.recordRead(path);
|
|
3629
3376
|
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
? { type: 'user-text', text }
|
|
3643
|
-
: { type: 'user-text-block', text }), {
|
|
3644
|
-
sessionId,
|
|
3645
|
-
parentUuid: compactSummaryUuid,
|
|
3646
|
-
cwd: this.activeCwd(),
|
|
3647
|
-
claudeVersion: this.options.claudeVersion,
|
|
3648
|
-
gitBranch: null,
|
|
3649
|
-
history: [...snapshot.entries, ...provisionalEntries],
|
|
3650
|
-
createUuid: () => replayUuids.shift() ?? randomUUID(),
|
|
3651
|
-
now: () => timestamp,
|
|
3652
|
-
});
|
|
3653
|
-
const compactedHistory = [
|
|
3654
|
-
...projectClaudeModelMessages([
|
|
3655
|
-
...snapshot.entries,
|
|
3656
|
-
...provisionalEntries,
|
|
3657
|
-
...replayEntries,
|
|
3658
|
-
]),
|
|
3659
|
-
...projectMemoryRecallMessages,
|
|
3660
|
-
];
|
|
3661
|
-
const afterHistory = budget.evaluate([...contextMessages, ...injectTurnContext(compactedHistory)], definitions);
|
|
3662
|
-
const afterPending = budget.evaluate([
|
|
3663
|
-
...contextMessages,
|
|
3664
|
-
...injectTurnContext([...compactedHistory, ...pendingMessages]),
|
|
3665
|
-
], definitions);
|
|
3666
|
-
budget.assertFits(afterPending);
|
|
3667
|
-
if (options.promptTooLong === true &&
|
|
3668
|
-
!contextRecoveryMadeProgress({
|
|
3669
|
-
beforeOccupancyTokens: predicted.occupancyTokens,
|
|
3670
|
-
afterOccupancyTokens: afterPending.occupancyTokens,
|
|
3671
|
-
})) {
|
|
3672
|
-
throw new Error('Reactive context compaction made no occupancy progress');
|
|
3673
|
-
}
|
|
3674
|
-
const entries = [
|
|
3675
|
-
...compactEntries(afterHistory.estimatedTokens),
|
|
3676
|
-
...replayEntries,
|
|
3677
|
-
];
|
|
3678
|
-
if (signal?.aborted)
|
|
3679
|
-
throw new AgentRunCancelledError();
|
|
3680
|
-
// Construct the exact tracker mutations for this single committed
|
|
3681
|
-
// boundary and preflight them against a clone of the live tracker so
|
|
3682
|
-
// invalid input or cumulative overflow rejects before the compact
|
|
3683
|
-
// boundary is appended rather than after a half-commit.
|
|
3684
|
-
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
3685
|
-
if (!tracker) {
|
|
3686
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3687
|
-
}
|
|
3688
|
-
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3689
|
-
? compacted.model
|
|
3690
|
-
: provider.model;
|
|
3691
|
-
const compactModelNonBlank = compactModel !== undefined && compactModel.trim() !== '';
|
|
3692
|
-
if (hasNonZeroUsage(compacted.usage) && !compactModelNonBlank) {
|
|
3693
|
-
throw new Error('Auto compact usage requires a nonblank model identity');
|
|
3694
|
-
}
|
|
3695
|
-
let meteringTurnInput;
|
|
3696
|
-
if (compactModelNonBlank && hasNonZeroUsage(compacted.usage)) {
|
|
3697
|
-
const pricing = this.options.pricing?.resolve(compactModel);
|
|
3698
|
-
const costUsd = pricing
|
|
3699
|
-
? usageCostUsd(compacted.usage, pricing)
|
|
3700
|
-
: undefined;
|
|
3701
|
-
meteringTurnInput = {
|
|
3702
|
-
model: compactModel,
|
|
3703
|
-
usage: compacted.usage,
|
|
3704
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
3705
|
-
...(compacted.usage.webSearchRequests === undefined
|
|
3706
|
-
? {}
|
|
3707
|
-
: { webSearchRequests: compacted.usage.webSearchRequests }),
|
|
3708
|
-
};
|
|
3709
|
-
}
|
|
3710
|
-
let meteringDurationsInput;
|
|
3711
|
-
if (compactedDurationMs > 0 ||
|
|
3712
|
-
compactedDurationWithoutRetriesMs > 0) {
|
|
3713
|
-
meteringDurationsInput = {
|
|
3714
|
-
...(compactedDurationMs === 0
|
|
3715
|
-
? {}
|
|
3716
|
-
: { apiDurationMs: compactedDurationMs }),
|
|
3717
|
-
apiDurationWithoutRetriesMs: compactedDurationWithoutRetriesMs,
|
|
3718
|
-
};
|
|
3719
|
-
}
|
|
3720
|
-
if (meteringTurnInput !== undefined ||
|
|
3721
|
-
meteringDurationsInput !== undefined) {
|
|
3722
|
-
const preflight = new ClaudeSessionCostTracker({
|
|
3723
|
-
sessionId,
|
|
3724
|
-
restored: tracker.snapshot(),
|
|
3725
|
-
});
|
|
3726
|
-
if (meteringTurnInput !== undefined) {
|
|
3727
|
-
preflight.recordTurn(meteringTurnInput);
|
|
3377
|
+
else if (call.name === 'Write' || call.name === 'Edit') {
|
|
3378
|
+
projectMemoryMaintained = true;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
if (call.name === 'Read') {
|
|
3382
|
+
for (const accessedPath of toolResult.accessedPaths ?? []) {
|
|
3383
|
+
const resolvedAccessedPath = resolve(this.activeCwd(), accessedPath);
|
|
3384
|
+
if (isPathWithin(this.options.projectMemoryDirectory, resolvedAccessedPath)) {
|
|
3385
|
+
turnMemory.recordRead(resolvedAccessedPath);
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3728
3389
|
}
|
|
3729
|
-
if (
|
|
3730
|
-
|
|
3390
|
+
if (toolResult.isError ||
|
|
3391
|
+
call.name !== 'Read' ||
|
|
3392
|
+
!this.options.conditionalRuleResolver ||
|
|
3393
|
+
!toolResult.accessedPaths) {
|
|
3394
|
+
return;
|
|
3731
3395
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3396
|
+
const attachedRulePaths = this.attachedRulePaths(snapshot.entries);
|
|
3397
|
+
for (const filePath of toolResult.accessedPaths) {
|
|
3398
|
+
const rules = await this.options.conditionalRuleResolver.resolve(filePath, [...attachedRulePaths]);
|
|
3399
|
+
for (const rule of rules) {
|
|
3400
|
+
const attachment = createClaudeRuleAttachmentEntry(rule, this.displayRulePath(rule.path), this.translationContext(sessionId, snapshot));
|
|
3401
|
+
const attachmentTail = await this.append(lease, snapshot.tail, attachment);
|
|
3402
|
+
snapshot = {
|
|
3403
|
+
entries: [...snapshot.entries, attachment],
|
|
3404
|
+
tail: attachmentTail,
|
|
3405
|
+
};
|
|
3406
|
+
attachedRulePaths.add(rule.path);
|
|
3407
|
+
await this.instructionLoaded(sessionId, {
|
|
3408
|
+
path: rule.path,
|
|
3409
|
+
memoryType: rule.scope === 'user'
|
|
3410
|
+
? 'User'
|
|
3411
|
+
: rule.scope === 'local'
|
|
3412
|
+
? 'Local'
|
|
3413
|
+
: 'Project',
|
|
3414
|
+
globs: rule.globs,
|
|
3415
|
+
triggerFilePath: filePath,
|
|
3416
|
+
...(rule.importedFrom === undefined
|
|
3417
|
+
? {}
|
|
3418
|
+
: { parentFilePath: rule.importedFrom }),
|
|
3419
|
+
}, 'path_glob_match');
|
|
3420
|
+
}
|
|
3746
3421
|
}
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
await recordHookOutcome(outcome);
|
|
3762
|
-
}
|
|
3763
|
-
this.options.contextAssembler?.invalidate?.({
|
|
3764
|
-
lifecycleId: sessionId,
|
|
3765
|
-
reason: 'compact',
|
|
3766
|
-
});
|
|
3767
|
-
this.options.projectMemoryRecall?.recordCompact(sessionId);
|
|
3768
|
-
await refreshRuntimeContext();
|
|
3769
|
-
this.options.eventSink?.({
|
|
3770
|
-
type: 'compact-boundary',
|
|
3771
|
-
trigger: 'auto',
|
|
3772
|
-
preTokens,
|
|
3773
|
-
uuid: boundaryUuid,
|
|
3774
|
-
});
|
|
3775
|
-
compactionAnchorUuid = compactSummaryUuid;
|
|
3776
|
-
compactionUsage = mergeUsage(compactionUsage, compacted.usage);
|
|
3777
|
-
compactionDurationMs = proposedCompactionDurationMs;
|
|
3778
|
-
compactionDurationWithoutRetriesMs =
|
|
3779
|
-
proposedCompactionDurationWithoutRetriesMs;
|
|
3780
|
-
if (meteringTurnInput !== undefined) {
|
|
3781
|
-
compactionModelUsage = mergeSessionRawModelUsage(compactionModelUsage, { [meteringTurnInput.model]: compacted.usage });
|
|
3782
|
-
}
|
|
3783
|
-
// Commit the preflighted mutations once the boundary is durable so a
|
|
3784
|
-
// later main-provider failure or cancellation cannot lose the
|
|
3785
|
-
// compactor's usage/cost/API durations.
|
|
3786
|
-
if (meteringTurnInput !== undefined) {
|
|
3787
|
-
tracker.recordTurn(meteringTurnInput);
|
|
3788
|
-
}
|
|
3789
|
-
if (meteringDurationsInput !== undefined) {
|
|
3790
|
-
tracker.recordDurations(meteringDurationsInput);
|
|
3791
|
-
}
|
|
3792
|
-
};
|
|
3793
|
-
await compactIfNeeded(pendingUserMessages);
|
|
3794
|
-
for (const [index, message] of expandedMessages.entries()) {
|
|
3795
|
-
if (shellCommand !== undefined)
|
|
3796
|
-
break;
|
|
3797
|
-
const messageImages = [
|
|
3798
|
-
...(index === attachmentIndex ? images : []),
|
|
3799
|
-
...(message.images ?? []),
|
|
3800
|
-
];
|
|
3801
|
-
const persistenceEvent = messageImages.length > 0 ||
|
|
3802
|
-
(index === attachmentIndex && documents.length > 0)
|
|
3803
|
-
? {
|
|
3804
|
-
type: 'user-message',
|
|
3805
|
-
text: message.text,
|
|
3806
|
-
images: messageImages,
|
|
3807
|
-
...(index === attachmentIndex && documents.length > 0
|
|
3808
|
-
? { documents }
|
|
3809
|
-
: {}),
|
|
3422
|
+
},
|
|
3423
|
+
followUpUserMessagesCompleted: async (messages) => {
|
|
3424
|
+
for (const content of messages) {
|
|
3425
|
+
const [followUpEntry] = translateProviderEvents([{ type: 'user-text-block', text: content }], this.translationContext(sessionId, snapshot));
|
|
3426
|
+
if (!followUpEntry) {
|
|
3427
|
+
throw new Error('Could not translate tool follow-up message');
|
|
3428
|
+
}
|
|
3429
|
+
const followUpTail = await this.append(lease, snapshot.tail, followUpEntry);
|
|
3430
|
+
snapshot = {
|
|
3431
|
+
entries: [...snapshot.entries, followUpEntry],
|
|
3432
|
+
tail: followUpTail,
|
|
3433
|
+
};
|
|
3434
|
+
currentTurnUserMessages?.push(content);
|
|
3435
|
+
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3810
3436
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
currentPromptId = userEntry.uuid;
|
|
3819
|
-
compactionAnchorUuid ??= userEntry.uuid;
|
|
3437
|
+
},
|
|
3438
|
+
};
|
|
3439
|
+
let turnCompleted = false;
|
|
3440
|
+
try {
|
|
3441
|
+
const outcome = await this.hookLifecycle.start(sessionId, hookSession, requireExisting ? 'resume' : 'startup', signal);
|
|
3442
|
+
if (outcome) {
|
|
3443
|
+
await recordHookOutcome(outcome, pendingRecoveryToolCallIds.size > 0);
|
|
3820
3444
|
}
|
|
3821
|
-
const
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3445
|
+
const approveRecovery = this.options.approveRecovery;
|
|
3446
|
+
const recoveryRequest = {
|
|
3447
|
+
cwd: this.activeCwd(),
|
|
3448
|
+
toolResultDirectory,
|
|
3449
|
+
messages: projectClaudeModelMessages(snapshot.entries),
|
|
3450
|
+
observer,
|
|
3451
|
+
permissionUpdates: this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
3452
|
+
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
3453
|
+
...(signal ? { signal } : {}),
|
|
3454
|
+
...(approveRecovery
|
|
3455
|
+
? {
|
|
3456
|
+
approveRecovery: async (call) => {
|
|
3457
|
+
if (!(await approveRecovery(call))) {
|
|
3458
|
+
throw new Error(`Claude session tool call ${call.id} recovery was declined`);
|
|
3459
|
+
}
|
|
3460
|
+
if (signal?.aborted)
|
|
3461
|
+
throw new AgentRunCancelledError();
|
|
3462
|
+
await flushRecoveryHookOutcomes();
|
|
3463
|
+
pendingRecoveryToolCallIds.delete(call.id);
|
|
3464
|
+
return true;
|
|
3465
|
+
},
|
|
3466
|
+
approveTool: () => true,
|
|
3467
|
+
}
|
|
3468
|
+
: {}),
|
|
3833
3469
|
};
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
...hookSession,
|
|
3838
|
-
hook_event_name: 'UserPromptSubmit',
|
|
3839
|
-
prompt_id: currentPromptId ?? randomUUID(),
|
|
3840
|
-
prompt: effectivePrompt,
|
|
3841
|
-
}, undefined, signal);
|
|
3842
|
-
await recordHookOutcome(outcome);
|
|
3843
|
-
if (outcome.blockedReason) {
|
|
3844
|
-
throw new Error(`UserPromptSubmit hook error: ${outcome.blockedReason}`);
|
|
3470
|
+
const unresolvedToolCall = unresolvedToolCalls[0];
|
|
3471
|
+
if (unresolvedToolCall && !approveRecovery) {
|
|
3472
|
+
throw new Error(`Claude session tool call ${unresolvedToolCall.id} requires explicit recovery approval`);
|
|
3845
3473
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
const
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3474
|
+
const recoveryResults = await runtime.recoverToolCalls(unresolvedToolCalls, recoveryRequest);
|
|
3475
|
+
const recoveryUsage = recoveryResults.reduce((usage, result) => result.usage ? mergeUsage(usage, result.usage) : usage, { inputTokens: 0, outputTokens: 0 });
|
|
3476
|
+
const recoveryModelUsage = mergeSessionRawModelUsage(...recoveryResults.map((result) => result.isError ? undefined : result.modelUsage));
|
|
3477
|
+
const foregroundLineChanges = createLineCountAccumulator();
|
|
3478
|
+
for (const result of recoveryResults) {
|
|
3479
|
+
foregroundLineChanges.add(result);
|
|
3480
|
+
}
|
|
3481
|
+
if (this.options.agent &&
|
|
3482
|
+
agent &&
|
|
3483
|
+
getClaudeAgentSetting(snapshot.entries) !== this.options.agent) {
|
|
3484
|
+
const agentSetting = createClaudeAgentSettingEntry(sessionId, this.options.agent);
|
|
3485
|
+
const settingTail = await this.append(lease, snapshot.tail, agentSetting);
|
|
3486
|
+
snapshot = {
|
|
3487
|
+
entries: [...snapshot.entries, agentSetting],
|
|
3488
|
+
tail: settingTail,
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
let agentSystem = null;
|
|
3492
|
+
let planModeMessage;
|
|
3493
|
+
let sessionMemoryMessage = null;
|
|
3494
|
+
let contextMessages = [];
|
|
3495
|
+
let contextProjection = {
|
|
3496
|
+
systemMessages: [],
|
|
3497
|
+
stableSystemSectionCount: 0,
|
|
3854
3498
|
};
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
shellResult = await runtime.executeDirectToolCall(call, {
|
|
3499
|
+
let stableSystemMessageCount = 0;
|
|
3500
|
+
const refreshRuntimeContext = async () => {
|
|
3501
|
+
agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
3502
|
+
planModeMessage =
|
|
3503
|
+
this.options.interactiveTools?.contextMessage(sessionId);
|
|
3504
|
+
sessionMemoryMessage = this.sessionMemoryMessage(await turnMemory.sessionSummary());
|
|
3505
|
+
const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
|
|
3863
3506
|
cwd: this.activeCwd(),
|
|
3864
|
-
sessionId,
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
observer: {
|
|
3868
|
-
assistantCompleted: async () => undefined,
|
|
3869
|
-
toolCompleted: async () => undefined,
|
|
3870
|
-
followUpUserMessagesCompleted: observer.followUpUserMessagesCompleted,
|
|
3871
|
-
},
|
|
3872
|
-
...(this.options.approveTool
|
|
3873
|
-
? { approveTool: this.options.approveTool }
|
|
3507
|
+
lifecycleId: sessionId,
|
|
3508
|
+
...(agentSystem
|
|
3509
|
+
? { mode: 'agent', baseSystemPrompt: agentSystem }
|
|
3874
3510
|
: {}),
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3511
|
+
turn: {
|
|
3512
|
+
...(planModeMessage ? { planMode: planModeMessage } : {}),
|
|
3513
|
+
...(sessionMemoryMessage
|
|
3514
|
+
? { sessionMemory: sessionMemoryMessage }
|
|
3515
|
+
: {}),
|
|
3516
|
+
...(this.options.brief ? { briefOutput: true } : {}),
|
|
3517
|
+
...(this.options.structuredOutputSchema
|
|
3518
|
+
? { structuredOutput: true }
|
|
3519
|
+
: {}),
|
|
3520
|
+
},
|
|
3884
3521
|
});
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
shellModelUsage = shellResult.modelUsage;
|
|
3890
|
-
foregroundLineChanges.add(shellResult);
|
|
3891
|
-
}
|
|
3892
|
-
const stdout = shellResult.processOutput?.stdout ??
|
|
3893
|
-
(shellResult.isError ? '' : shellResult.content);
|
|
3894
|
-
const stderr = shellResult.processOutput?.stderr ??
|
|
3895
|
-
(shellResult.isError ? shellResult.content : '');
|
|
3896
|
-
const shellUuids = [shellInputUuid ?? randomUUID(), randomUUID()];
|
|
3897
|
-
const [inputEntry, outputEntry] = translateProviderEvents([
|
|
3898
|
-
{ type: 'bash-input', command: shellCommand },
|
|
3899
|
-
{ type: 'bash-output', stdout, stderr },
|
|
3900
|
-
], {
|
|
3901
|
-
...this.translationContext(sessionId, snapshot),
|
|
3902
|
-
createUuid: () => shellUuids.shift() ?? randomUUID(),
|
|
3903
|
-
});
|
|
3904
|
-
if (!inputEntry || !outputEntry) {
|
|
3905
|
-
throw new Error('Could not translate shell command result');
|
|
3906
|
-
}
|
|
3907
|
-
const shellAppend = await lease.appendMany(snapshot.tail, [
|
|
3908
|
-
inputEntry,
|
|
3909
|
-
outputEntry,
|
|
3910
|
-
]);
|
|
3911
|
-
if (shellAppend.status === 'conflict') {
|
|
3912
|
-
throw new Error(`Claude transcript append conflict: ${shellAppend.reason}`);
|
|
3913
|
-
}
|
|
3914
|
-
snapshot = {
|
|
3915
|
-
entries: [...snapshot.entries, inputEntry, outputEntry],
|
|
3916
|
-
tail: shellAppend.tail,
|
|
3522
|
+
contextProjection = projectContextSnapshot(assembledContext);
|
|
3523
|
+
stableSystemMessageCount =
|
|
3524
|
+
contextProjection.stableSystemSectionCount;
|
|
3525
|
+
contextMessages = [...contextProjection.systemMessages];
|
|
3917
3526
|
};
|
|
3918
|
-
|
|
3527
|
+
await refreshRuntimeContext();
|
|
3528
|
+
const expansion = skipUserPrompt
|
|
3529
|
+
? { userMessages: [] }
|
|
3530
|
+
: shellCommand === undefined
|
|
3531
|
+
? this.options.extensions
|
|
3532
|
+
? await this.options.extensions.expandPromptAsync(effectivePrompt, signal, toolResultDirectory)
|
|
3533
|
+
: { userMessages: [effectivePrompt] }
|
|
3534
|
+
: {
|
|
3535
|
+
userMessages: [`<bash-input>${shellCommand}</bash-input>`],
|
|
3536
|
+
};
|
|
3537
|
+
const expansionMessages = 'messages' in expansion ? expansion.messages : undefined;
|
|
3538
|
+
const userMessages = 'userMessages' in expansion ? expansion.userMessages : [];
|
|
3539
|
+
const expandedMessages = expansionMessages ?? userMessages.map((text) => ({ text }));
|
|
3540
|
+
const attachmentIndex = expansionMessages
|
|
3541
|
+
? expandedMessages.length - 1
|
|
3542
|
+
: 0;
|
|
3543
|
+
currentTurnUserMessages = [...expansion.userMessages];
|
|
3919
3544
|
this.options.eventSink?.({
|
|
3920
|
-
type: '
|
|
3921
|
-
|
|
3922
|
-
stdout,
|
|
3923
|
-
stderr,
|
|
3924
|
-
isError: shellResult.isError,
|
|
3545
|
+
type: 'state',
|
|
3546
|
+
state: 'assembling-context',
|
|
3925
3547
|
});
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
...
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
:
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3548
|
+
let compactionUsage = {
|
|
3549
|
+
inputTokens: 0,
|
|
3550
|
+
outputTokens: 0,
|
|
3551
|
+
};
|
|
3552
|
+
let compactionDurationMs;
|
|
3553
|
+
let compactionDurationWithoutRetriesMs;
|
|
3554
|
+
let compactionModelUsage;
|
|
3555
|
+
const definitions = provider.capabilities.tools
|
|
3556
|
+
? (structuredTools?.definitions() ?? [])
|
|
3557
|
+
: [];
|
|
3558
|
+
const budget = this.contextBudget(provider);
|
|
3559
|
+
const contextEngine = new ContextEngine({
|
|
3560
|
+
...(budget ? { budget } : {}),
|
|
3561
|
+
autoCompact: this.options.autoCompact !== false,
|
|
3562
|
+
memory: {
|
|
3563
|
+
beforeCompact: () => turnMemory.beforeCompact(),
|
|
3564
|
+
afterCompact: () => turnMemory.afterCompact(),
|
|
3565
|
+
},
|
|
3566
|
+
});
|
|
3567
|
+
observeModelRequestUsage = ({ usage, messages, tools }) => {
|
|
3568
|
+
contextEngine.observeUsage(usage, messages, tools);
|
|
3569
|
+
};
|
|
3570
|
+
const pendingUserMessages = expandedMessages.map((message, index) => ({
|
|
3571
|
+
role: 'user',
|
|
3572
|
+
content: message.text,
|
|
3573
|
+
...(message.contentBlocks?.length
|
|
3574
|
+
? { contentBlocks: message.contentBlocks }
|
|
3575
|
+
: {}),
|
|
3576
|
+
...((index === attachmentIndex && images.length > 0) ||
|
|
3577
|
+
message.images?.length
|
|
3578
|
+
? {
|
|
3579
|
+
images: [
|
|
3580
|
+
...(index === attachmentIndex ? images : []),
|
|
3581
|
+
...(message.images ?? []),
|
|
3582
|
+
],
|
|
3583
|
+
}
|
|
3584
|
+
: {}),
|
|
3585
|
+
...(index === attachmentIndex && documents.length > 0
|
|
3586
|
+
? { documents }
|
|
3587
|
+
: {}),
|
|
3588
|
+
}));
|
|
3589
|
+
const agentMentionMessages = shellCommand === undefined && !skipUserPrompt
|
|
3590
|
+
? (this.options.extensions?.agentMentionMessages(effectivePrompt) ?? [])
|
|
3591
|
+
: [];
|
|
3592
|
+
const injectAgentMentionContext = (messages) => {
|
|
3593
|
+
if (agentMentionMessages.length === 0)
|
|
3594
|
+
return [...messages];
|
|
3595
|
+
let insertionIndex = messages.length;
|
|
3596
|
+
let foundPrompt = false;
|
|
3597
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3598
|
+
const message = messages[index];
|
|
3599
|
+
if (message?.role === 'user' &&
|
|
3600
|
+
typeof message.content === 'string' &&
|
|
3601
|
+
message.content.endsWith(effectivePrompt)) {
|
|
3602
|
+
insertionIndex = index;
|
|
3603
|
+
foundPrompt = true;
|
|
3604
|
+
break;
|
|
3605
|
+
}
|
|
3971
3606
|
}
|
|
3607
|
+
if (!foundPrompt)
|
|
3608
|
+
return [...messages];
|
|
3972
3609
|
return [
|
|
3973
|
-
...
|
|
3974
|
-
...
|
|
3610
|
+
...messages.slice(0, insertionIndex),
|
|
3611
|
+
...agentMentionMessages.map((content) => ({
|
|
3612
|
+
role: 'user',
|
|
3613
|
+
content,
|
|
3614
|
+
})),
|
|
3615
|
+
...messages.slice(insertionIndex),
|
|
3616
|
+
];
|
|
3617
|
+
};
|
|
3618
|
+
const injectDynamicContext = (messages) => injectFirstUserMessageContext(messages, contextProjection.firstUserMessageContext);
|
|
3619
|
+
const injectTurnContext = (messages) => injectAgentMentionContext(injectDynamicContext(messages));
|
|
3620
|
+
let compactionAnchorUuid = this.lastMessageUuid(snapshot.entries);
|
|
3621
|
+
const contextTransitionPort = (pendingMessages = [], preservedUserMessages = []) => ({
|
|
3622
|
+
current: () => {
|
|
3623
|
+
const historyMessages = [
|
|
3975
3624
|
...projectClaudeModelMessages(snapshot.entries),
|
|
3976
3625
|
...projectMemoryRecallMessages,
|
|
3977
|
-
]
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
3626
|
+
];
|
|
3627
|
+
return {
|
|
3628
|
+
messages: [
|
|
3629
|
+
...contextMessages,
|
|
3630
|
+
...injectTurnContext([
|
|
3631
|
+
...historyMessages,
|
|
3632
|
+
...pendingMessages,
|
|
3633
|
+
]),
|
|
3634
|
+
],
|
|
3635
|
+
tools: definitions,
|
|
3636
|
+
};
|
|
3637
|
+
},
|
|
3638
|
+
irreducible: () => ({
|
|
3639
|
+
messages: [
|
|
3640
|
+
...contextMessages,
|
|
3641
|
+
...injectTurnContext([
|
|
3642
|
+
...pendingMessages,
|
|
3643
|
+
...preservedUserMessages.map((content) => ({
|
|
3644
|
+
role: 'user',
|
|
3645
|
+
content,
|
|
3646
|
+
})),
|
|
3647
|
+
]),
|
|
3648
|
+
],
|
|
3649
|
+
tools: definitions,
|
|
3650
|
+
}),
|
|
3651
|
+
propose: async () => {
|
|
3652
|
+
if (!budget) {
|
|
3653
|
+
throw new Error('Context budget is unavailable');
|
|
3654
|
+
}
|
|
3655
|
+
const historyMessages = [
|
|
3656
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
3657
|
+
...projectMemoryRecallMessages,
|
|
3658
|
+
];
|
|
3659
|
+
const irreducibleMessages = [
|
|
3660
|
+
...contextMessages,
|
|
3661
|
+
...injectTurnContext([
|
|
3662
|
+
...pendingMessages,
|
|
3663
|
+
...preservedUserMessages.map((content) => ({
|
|
3664
|
+
role: 'user',
|
|
3665
|
+
content,
|
|
3666
|
+
})),
|
|
3667
|
+
]),
|
|
3668
|
+
];
|
|
3669
|
+
let logicalParentUuid = compactionAnchorUuid;
|
|
3670
|
+
if (!logicalParentUuid || historyMessages.length === 0) {
|
|
3671
|
+
throw new Error('Cannot compact an empty Claude transcript');
|
|
3672
|
+
}
|
|
3673
|
+
if (findUnresolvedClaudeToolCalls(snapshot.entries).length > 0) {
|
|
3674
|
+
throw new Error('Cannot compact a Claude session with unresolved tool calls');
|
|
3675
|
+
}
|
|
3676
|
+
const preCompact = await this.runAdvisoryHook(sessionId, 'PreCompact', { trigger: 'auto', custom_instructions: null }, 'auto', signal);
|
|
3677
|
+
const memorySelection = sessionMemory
|
|
3678
|
+
? await this.selectMemoryPreservedCompact(sessionId, selectClaudeActiveTranscript(snapshot.entries), { sessionMemoryReady: true })
|
|
3679
|
+
: null;
|
|
3680
|
+
const compactorMessages = memorySelection
|
|
3681
|
+
? [
|
|
3682
|
+
memorySelection.memoryMessage,
|
|
3683
|
+
...projectClaudeModelMessages(memorySelection.compactedEntries),
|
|
3684
|
+
]
|
|
3685
|
+
: historyMessages;
|
|
3686
|
+
compactorMessages.push(...successfulHookOutput(preCompact).map((content) => ({
|
|
3687
|
+
role: 'user',
|
|
3688
|
+
content: `Additional summarization context: ${content}`,
|
|
3689
|
+
})));
|
|
3690
|
+
if (memorySelection) {
|
|
3691
|
+
logicalParentUuid = memorySelection.logicalParentUuid;
|
|
3692
|
+
}
|
|
3693
|
+
this.options.eventSink?.({ type: 'state', state: 'compacting' });
|
|
3694
|
+
const compactEnvelope = budget.evaluate([
|
|
3695
|
+
...irreducibleMessages,
|
|
3696
|
+
{
|
|
3697
|
+
role: 'user',
|
|
3698
|
+
content: formatClaudeCompactSummary(''),
|
|
3699
|
+
},
|
|
3700
|
+
], definitions);
|
|
3701
|
+
let targetTokens = Math.min(8192, compactEnvelope.availableTokens -
|
|
3702
|
+
compactEnvelope.estimatedTokens);
|
|
3703
|
+
if (targetTokens < 1) {
|
|
3704
|
+
budget.assertFits(budget.evaluate([
|
|
3705
|
+
...irreducibleMessages,
|
|
3706
|
+
{
|
|
3707
|
+
role: 'user',
|
|
3708
|
+
content: formatClaudeCompactSummary('a'),
|
|
3709
|
+
},
|
|
3710
|
+
], definitions));
|
|
3711
|
+
targetTokens = 1;
|
|
3712
|
+
}
|
|
3713
|
+
const compacted = await (this.options.compactor ?? new ModelCompactor(provider)).compact({
|
|
3714
|
+
messages: compactorMessages,
|
|
3715
|
+
targetTokens,
|
|
3716
|
+
contextWindowTokens: budget.contextWindowTokens,
|
|
3717
|
+
...(signal ? { signal } : {}),
|
|
3718
|
+
});
|
|
3719
|
+
const { durationMs: compactedDurationMs, durationWithoutRetriesMs: compactedDurationWithoutRetriesMs, } = requireCompactionDurations(compacted);
|
|
3720
|
+
const proposedCompactionDurationMs = (compactionDurationMs ?? 0) + compactedDurationMs;
|
|
3721
|
+
if (!Number.isFinite(proposedCompactionDurationMs)) {
|
|
3722
|
+
throw new TypeError('compaction durationMs total overflow');
|
|
3723
|
+
}
|
|
3724
|
+
const proposedCompactionDurationWithoutRetriesMs = (compactionDurationWithoutRetriesMs ?? 0) +
|
|
3725
|
+
compactedDurationWithoutRetriesMs;
|
|
3726
|
+
if (!Number.isFinite(proposedCompactionDurationWithoutRetriesMs)) {
|
|
3727
|
+
throw new TypeError('compaction durationWithoutRetriesMs total overflow');
|
|
3728
|
+
}
|
|
3729
|
+
const boundaryUuid = randomUUID();
|
|
3730
|
+
const summaryUuid = randomUUID();
|
|
3731
|
+
const timestamp = new Date().toISOString();
|
|
3732
|
+
const preTokens = budget.evaluate([...contextMessages, ...injectTurnContext(historyMessages)], definitions).estimatedTokens;
|
|
3733
|
+
const compactEntries = (postTokens) => {
|
|
3734
|
+
const uuids = [boundaryUuid, summaryUuid];
|
|
3735
|
+
return createClaudeCompactEntries({
|
|
3736
|
+
sessionId,
|
|
3737
|
+
logicalParentUuid,
|
|
3738
|
+
summary: compacted.summary,
|
|
3739
|
+
preTokens,
|
|
3740
|
+
postTokens,
|
|
3741
|
+
previousCumulativeDroppedTokens: getCumulativeDroppedTokens(snapshot.entries),
|
|
3742
|
+
durationMs: compactedDurationMs,
|
|
3743
|
+
cwd: this.activeCwd(),
|
|
3744
|
+
claudeVersion: this.options.claudeVersion,
|
|
3745
|
+
gitBranch: null,
|
|
3746
|
+
...(memorySelection
|
|
4053
3747
|
? {
|
|
4054
|
-
|
|
4055
|
-
durationApiWithoutRetriesMs: batchDurationApiWithoutRetriesMs,
|
|
3748
|
+
preservedUuids: memorySelection.preservedEntries.flatMap((entry) => typeof entry.uuid === 'string' ? [entry.uuid] : []),
|
|
4056
3749
|
}
|
|
4057
3750
|
: {}),
|
|
3751
|
+
createUuid: () => uuids.shift() ?? randomUUID(),
|
|
3752
|
+
now: () => timestamp,
|
|
3753
|
+
});
|
|
3754
|
+
};
|
|
3755
|
+
const provisionalEntries = compactEntries(0);
|
|
3756
|
+
const compactSummaryUuid = provisionalEntries.at(-1)?.uuid;
|
|
3757
|
+
if (typeof compactSummaryUuid !== 'string') {
|
|
3758
|
+
throw new Error('Could not create Claude compact summary');
|
|
3759
|
+
}
|
|
3760
|
+
const replayUuids = preservedUserMessages.map(() => randomUUID());
|
|
3761
|
+
const replayEntries = translateProviderEvents(preservedUserMessages.map((text, index) => index === 0
|
|
3762
|
+
? { type: 'user-text', text }
|
|
3763
|
+
: { type: 'user-text-block', text }), {
|
|
3764
|
+
sessionId,
|
|
3765
|
+
parentUuid: compactSummaryUuid,
|
|
3766
|
+
cwd: this.activeCwd(),
|
|
3767
|
+
claudeVersion: this.options.claudeVersion,
|
|
3768
|
+
gitBranch: null,
|
|
3769
|
+
history: [...snapshot.entries, ...provisionalEntries],
|
|
3770
|
+
createUuid: () => replayUuids.shift() ?? randomUUID(),
|
|
3771
|
+
now: () => timestamp,
|
|
3772
|
+
});
|
|
3773
|
+
const compactedHistory = [
|
|
3774
|
+
...projectClaudeModelMessages([
|
|
3775
|
+
...snapshot.entries,
|
|
3776
|
+
...provisionalEntries,
|
|
3777
|
+
...replayEntries,
|
|
3778
|
+
]),
|
|
3779
|
+
...projectMemoryRecallMessages,
|
|
3780
|
+
];
|
|
3781
|
+
const afterHistory = budget.evaluate([...contextMessages, ...injectTurnContext(compactedHistory)], definitions);
|
|
3782
|
+
const proposedMessages = [
|
|
3783
|
+
...contextMessages,
|
|
3784
|
+
...injectTurnContext([...compactedHistory, ...pendingMessages]),
|
|
3785
|
+
];
|
|
3786
|
+
const entries = [
|
|
3787
|
+
...compactEntries(afterHistory.estimatedTokens),
|
|
3788
|
+
...replayEntries,
|
|
3789
|
+
];
|
|
3790
|
+
if (signal?.aborted)
|
|
3791
|
+
throw new AgentRunCancelledError();
|
|
3792
|
+
// Construct the exact tracker mutations for this single committed
|
|
3793
|
+
// boundary and preflight them against a clone of the live tracker so
|
|
3794
|
+
// invalid input or cumulative overflow rejects before the compact
|
|
3795
|
+
// boundary is appended rather than after a half-commit.
|
|
3796
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
3797
|
+
if (!tracker) {
|
|
3798
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3799
|
+
}
|
|
3800
|
+
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3801
|
+
? compacted.model
|
|
3802
|
+
: provider.model;
|
|
3803
|
+
const compactModelNonBlank = compactModel !== undefined && compactModel.trim() !== '';
|
|
3804
|
+
if (hasNonZeroUsage(compacted.usage) && !compactModelNonBlank) {
|
|
3805
|
+
throw new Error('Auto compact usage requires a nonblank model identity');
|
|
3806
|
+
}
|
|
3807
|
+
let meteringTurnInput;
|
|
3808
|
+
if (compactModelNonBlank && hasNonZeroUsage(compacted.usage)) {
|
|
3809
|
+
const pricing = this.options.pricing?.resolve(compactModel);
|
|
3810
|
+
const costUsd = pricing
|
|
3811
|
+
? usageCostUsd(compacted.usage, pricing)
|
|
3812
|
+
: undefined;
|
|
3813
|
+
meteringTurnInput = {
|
|
3814
|
+
model: compactModel,
|
|
3815
|
+
usage: compacted.usage,
|
|
3816
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
3817
|
+
...(compacted.usage.webSearchRequests === undefined
|
|
3818
|
+
? {}
|
|
3819
|
+
: { webSearchRequests: compacted.usage.webSearchRequests }),
|
|
4058
3820
|
};
|
|
4059
|
-
}
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
3821
|
+
}
|
|
3822
|
+
let meteringDurationsInput;
|
|
3823
|
+
if (compactedDurationMs > 0 ||
|
|
3824
|
+
compactedDurationWithoutRetriesMs > 0) {
|
|
3825
|
+
meteringDurationsInput = {
|
|
3826
|
+
...(compactedDurationMs === 0
|
|
3827
|
+
? {}
|
|
3828
|
+
: { apiDurationMs: compactedDurationMs }),
|
|
3829
|
+
apiDurationWithoutRetriesMs: compactedDurationWithoutRetriesMs,
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
if (meteringTurnInput !== undefined ||
|
|
3833
|
+
meteringDurationsInput !== undefined) {
|
|
3834
|
+
const preflight = new ClaudeSessionCostTracker({
|
|
3835
|
+
sessionId,
|
|
3836
|
+
restored: tracker.snapshot(),
|
|
3837
|
+
});
|
|
3838
|
+
if (meteringTurnInput !== undefined) {
|
|
3839
|
+
preflight.recordTurn(meteringTurnInput);
|
|
3840
|
+
}
|
|
3841
|
+
if (meteringDurationsInput !== undefined) {
|
|
3842
|
+
preflight.recordDurations(meteringDurationsInput);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
return {
|
|
3846
|
+
envelope: {
|
|
3847
|
+
messages: proposedMessages,
|
|
3848
|
+
tools: definitions,
|
|
3849
|
+
},
|
|
3850
|
+
commit: async () => {
|
|
3851
|
+
if (signal?.aborted)
|
|
3852
|
+
throw new AgentRunCancelledError();
|
|
3853
|
+
const appendResult = await lease.appendMany(snapshot.tail, entries);
|
|
3854
|
+
if (appendResult.status === 'conflict') {
|
|
3855
|
+
throw new Error(`Claude transcript append conflict: ${appendResult.reason}`);
|
|
3856
|
+
}
|
|
3857
|
+
snapshot = {
|
|
3858
|
+
entries: [...snapshot.entries, ...entries],
|
|
3859
|
+
tail: appendResult.tail,
|
|
3860
|
+
};
|
|
3861
|
+
const metadataEntries = createClaudeDurableMetadataSnapshot(snapshot.entries, sessionId);
|
|
3862
|
+
if (metadataEntries.length > 0) {
|
|
3863
|
+
const metadataAppend = await lease.appendMany(snapshot.tail, metadataEntries);
|
|
3864
|
+
if (metadataAppend.status === 'conflict') {
|
|
3865
|
+
throw new Error(`Claude metadata snapshot conflict: ${metadataAppend.reason}`);
|
|
3866
|
+
}
|
|
3867
|
+
snapshot = {
|
|
3868
|
+
entries: [...snapshot.entries, ...metadataEntries],
|
|
3869
|
+
tail: metadataAppend.tail,
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
3873
|
+
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'auto', compact_summary: compacted.summary }, 'auto', signal);
|
|
3874
|
+
// The boundary is durable: mirror Claude's full-compact behavior by
|
|
3875
|
+
// rerunning SessionStart with source compact and refreshing the
|
|
3876
|
+
// runtime-only context so the next request retains current
|
|
3877
|
+
// instructions, plan state, session memory, and hook context.
|
|
3878
|
+
if (this.options.hooks) {
|
|
3879
|
+
const outcome = await this.hookLifecycle.refresh(sessionId, hookSession, signal);
|
|
3880
|
+
if (outcome)
|
|
3881
|
+
await recordHookOutcome(outcome);
|
|
3882
|
+
}
|
|
3883
|
+
this.options.contextAssembler?.invalidate?.({
|
|
3884
|
+
lifecycleId: sessionId,
|
|
3885
|
+
reason: 'compact',
|
|
3886
|
+
});
|
|
3887
|
+
await refreshRuntimeContext();
|
|
3888
|
+
this.options.eventSink?.({
|
|
3889
|
+
type: 'compact-boundary',
|
|
3890
|
+
trigger: 'auto',
|
|
3891
|
+
preTokens,
|
|
3892
|
+
uuid: boundaryUuid,
|
|
3893
|
+
});
|
|
3894
|
+
compactionAnchorUuid = compactSummaryUuid;
|
|
3895
|
+
compactionUsage = mergeUsage(compactionUsage, compacted.usage);
|
|
3896
|
+
compactionDurationMs = proposedCompactionDurationMs;
|
|
3897
|
+
compactionDurationWithoutRetriesMs =
|
|
3898
|
+
proposedCompactionDurationWithoutRetriesMs;
|
|
3899
|
+
if (meteringTurnInput !== undefined) {
|
|
3900
|
+
compactionModelUsage = mergeSessionRawModelUsage(compactionModelUsage, { [meteringTurnInput.model]: compacted.usage });
|
|
3901
|
+
}
|
|
3902
|
+
// Commit the preflighted mutations once the boundary is durable so a
|
|
3903
|
+
// later main-provider failure or cancellation cannot lose the
|
|
3904
|
+
// compactor's usage/cost/API durations.
|
|
3905
|
+
if (meteringTurnInput !== undefined) {
|
|
3906
|
+
tracker.recordTurn(meteringTurnInput);
|
|
3907
|
+
}
|
|
3908
|
+
if (meteringDurationsInput !== undefined) {
|
|
3909
|
+
tracker.recordDurations(meteringDurationsInput);
|
|
3910
|
+
}
|
|
3911
|
+
},
|
|
3912
|
+
};
|
|
3913
|
+
},
|
|
4076
3914
|
});
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
3915
|
+
await contextEngine.prepare(contextTransitionPort(pendingUserMessages), signal);
|
|
3916
|
+
for (const [index, message] of expandedMessages.entries()) {
|
|
3917
|
+
if (shellCommand !== undefined)
|
|
3918
|
+
break;
|
|
3919
|
+
const messageImages = [
|
|
3920
|
+
...(index === attachmentIndex ? images : []),
|
|
3921
|
+
...(message.images ?? []),
|
|
3922
|
+
];
|
|
3923
|
+
const persistenceEvent = messageImages.length > 0 ||
|
|
3924
|
+
(index === attachmentIndex && documents.length > 0)
|
|
3925
|
+
? {
|
|
3926
|
+
type: 'user-message',
|
|
3927
|
+
text: message.text,
|
|
3928
|
+
images: messageImages,
|
|
3929
|
+
...(index === attachmentIndex && documents.length > 0
|
|
3930
|
+
? { documents }
|
|
3931
|
+
: {}),
|
|
3932
|
+
}
|
|
3933
|
+
: index === 0
|
|
3934
|
+
? { type: 'user-text', text: message.text }
|
|
3935
|
+
: { type: 'user-text-block', text: message.text };
|
|
3936
|
+
const [userEntry] = translateProviderEvents([persistenceEvent], this.translationContext(sessionId, snapshot));
|
|
3937
|
+
if (!userEntry)
|
|
3938
|
+
throw new Error('Could not translate user prompt');
|
|
3939
|
+
if (currentPromptId === null &&
|
|
3940
|
+
typeof userEntry.uuid === 'string') {
|
|
3941
|
+
currentPromptId = userEntry.uuid;
|
|
3942
|
+
compactionAnchorUuid ??= userEntry.uuid;
|
|
3943
|
+
}
|
|
3944
|
+
const userTail = await this.append(lease, snapshot.tail, userEntry);
|
|
3945
|
+
snapshot = {
|
|
3946
|
+
entries: [...snapshot.entries, userEntry],
|
|
3947
|
+
tail: userTail,
|
|
3948
|
+
};
|
|
4082
3949
|
}
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
3950
|
+
if (fileHistory && currentPromptId) {
|
|
3951
|
+
const historySnapshot = await fileHistory.snapshot(snapshot.entries, currentPromptId);
|
|
3952
|
+
const historyTail = await this.append(lease, snapshot.tail, historySnapshot);
|
|
3953
|
+
snapshot = {
|
|
3954
|
+
entries: [...snapshot.entries, historySnapshot],
|
|
3955
|
+
tail: historyTail,
|
|
3956
|
+
};
|
|
3957
|
+
}
|
|
3958
|
+
if (this.options.hooks && !skipUserPrompt) {
|
|
3959
|
+
const outcome = await this.options.hooks.run({
|
|
3960
|
+
...hookSession,
|
|
3961
|
+
hook_event_name: 'UserPromptSubmit',
|
|
3962
|
+
prompt_id: currentPromptId ?? randomUUID(),
|
|
3963
|
+
prompt: effectivePrompt,
|
|
3964
|
+
}, undefined, signal);
|
|
3965
|
+
await recordHookOutcome(outcome);
|
|
3966
|
+
if (outcome.blockedReason) {
|
|
3967
|
+
throw new Error(`UserPromptSubmit hook error: ${outcome.blockedReason}`);
|
|
4089
3968
|
}
|
|
4090
|
-
|
|
3969
|
+
}
|
|
3970
|
+
let shellUsage = { inputTokens: 0, outputTokens: 0 };
|
|
3971
|
+
let shellModelUsage;
|
|
3972
|
+
if (shellCommand !== undefined) {
|
|
3973
|
+
const call = {
|
|
3974
|
+
id: `shell_${randomUUID().replaceAll('-', '')}`,
|
|
3975
|
+
name: 'Bash',
|
|
3976
|
+
input: { command: shellCommand },
|
|
3977
|
+
};
|
|
3978
|
+
this.options.eventSink?.({
|
|
3979
|
+
type: 'shell-command',
|
|
3980
|
+
callId: call.id,
|
|
3981
|
+
command: shellCommand,
|
|
3982
|
+
});
|
|
3983
|
+
let shellResult;
|
|
4091
3984
|
try {
|
|
4092
|
-
await
|
|
4093
|
-
|
|
3985
|
+
shellResult = await runtime.executeDirectToolCall(call, {
|
|
3986
|
+
cwd: this.activeCwd(),
|
|
3987
|
+
sessionId,
|
|
3988
|
+
toolResultDirectory,
|
|
3989
|
+
messages: projectClaudeModelMessages(snapshot.entries),
|
|
3990
|
+
observer: {
|
|
3991
|
+
assistantCompleted: async () => undefined,
|
|
3992
|
+
toolCompleted: async () => undefined,
|
|
3993
|
+
followUpUserMessagesCompleted: observer.followUpUserMessagesCompleted,
|
|
3994
|
+
},
|
|
3995
|
+
...(this.options.approveTool
|
|
3996
|
+
? { approveTool: this.options.approveTool }
|
|
3997
|
+
: {}),
|
|
3998
|
+
permissionUpdates: this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
3999
|
+
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
4000
|
+
...(signal ? { signal } : {}),
|
|
4094
4001
|
});
|
|
4095
4002
|
}
|
|
4096
|
-
catch (
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
}
|
|
4101
|
-
// Compaction could not free the provider-bounded context; surface
|
|
4102
|
-
// the original prompt-too-long error rather than the compaction
|
|
4103
|
-
// failure.
|
|
4104
|
-
surfaceExhaustedRecovery(error);
|
|
4003
|
+
catch (error) {
|
|
4004
|
+
this.options.eventSink?.({
|
|
4005
|
+
type: 'shell-cancelled',
|
|
4006
|
+
callId: call.id,
|
|
4007
|
+
});
|
|
4105
4008
|
throw error;
|
|
4106
4009
|
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
...injectTurnContext([
|
|
4112
|
-
...projectClaudeModelMessages(snapshot.entries),
|
|
4113
|
-
...projectMemoryRecallMessages,
|
|
4114
|
-
]),
|
|
4115
|
-
];
|
|
4116
|
-
if (stableSystemMessageCount === undefined) {
|
|
4117
|
-
delete runtimeRequest.stableSystemMessageCount;
|
|
4010
|
+
shellUsage = shellResult.usage ?? shellUsage;
|
|
4011
|
+
if (!shellResult.isError) {
|
|
4012
|
+
shellModelUsage = shellResult.modelUsage;
|
|
4013
|
+
foregroundLineChanges.add(shellResult);
|
|
4118
4014
|
}
|
|
4119
|
-
|
|
4120
|
-
|
|
4015
|
+
const stdout = shellResult.processOutput?.stdout ??
|
|
4016
|
+
(shellResult.isError ? '' : shellResult.content);
|
|
4017
|
+
const stderr = shellResult.processOutput?.stderr ??
|
|
4018
|
+
(shellResult.isError ? shellResult.content : '');
|
|
4019
|
+
const shellUuids = [shellInputUuid ?? randomUUID(), randomUUID()];
|
|
4020
|
+
const [inputEntry, outputEntry] = translateProviderEvents([
|
|
4021
|
+
{ type: 'bash-input', command: shellCommand },
|
|
4022
|
+
{ type: 'bash-output', stdout, stderr },
|
|
4023
|
+
], {
|
|
4024
|
+
...this.translationContext(sessionId, snapshot),
|
|
4025
|
+
createUuid: () => shellUuids.shift() ?? randomUUID(),
|
|
4026
|
+
});
|
|
4027
|
+
if (!inputEntry || !outputEntry) {
|
|
4028
|
+
throw new Error('Could not translate shell command result');
|
|
4121
4029
|
}
|
|
4122
|
-
const
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
throw error;
|
|
4030
|
+
const shellAppend = await lease.appendMany(snapshot.tail, [
|
|
4031
|
+
inputEntry,
|
|
4032
|
+
outputEntry,
|
|
4033
|
+
]);
|
|
4034
|
+
if (shellAppend.status === 'conflict') {
|
|
4035
|
+
throw new Error(`Claude transcript append conflict: ${shellAppend.reason}`);
|
|
4129
4036
|
}
|
|
4130
|
-
|
|
4037
|
+
snapshot = {
|
|
4038
|
+
entries: [...snapshot.entries, inputEntry, outputEntry],
|
|
4039
|
+
tail: shellAppend.tail,
|
|
4040
|
+
};
|
|
4041
|
+
currentTurnUserMessages.push(`<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`);
|
|
4042
|
+
this.options.eventSink?.({
|
|
4043
|
+
type: 'shell-result',
|
|
4044
|
+
callId: call.id,
|
|
4045
|
+
stdout,
|
|
4046
|
+
stderr,
|
|
4047
|
+
isError: shellResult.isError,
|
|
4048
|
+
});
|
|
4049
|
+
}
|
|
4050
|
+
if (budget) {
|
|
4051
|
+
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4052
|
+
budget.assertFits(budget.evaluate([
|
|
4053
|
+
...contextMessages,
|
|
4054
|
+
...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
|
|
4055
|
+
], definitions));
|
|
4056
|
+
}
|
|
4057
|
+
let stopHookActive = false;
|
|
4058
|
+
const runtimeRequest = {
|
|
4059
|
+
sessionId,
|
|
4060
|
+
messages: [
|
|
4061
|
+
...contextMessages,
|
|
4062
|
+
...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
|
|
4063
|
+
],
|
|
4064
|
+
stableSystemMessageCount,
|
|
4065
|
+
cwd: this.activeCwd(),
|
|
4066
|
+
toolResultDirectory,
|
|
4067
|
+
observer,
|
|
4068
|
+
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
4069
|
+
...(this.options.maxModelTurns !== undefined
|
|
4070
|
+
? { maxModelTurns: this.options.maxModelTurns }
|
|
4071
|
+
: {}),
|
|
4072
|
+
...(this.options.betas?.length
|
|
4073
|
+
? { betas: this.options.betas }
|
|
4074
|
+
: {}),
|
|
4075
|
+
...(this.options.collectMetrics || this.options.costStateStore
|
|
4076
|
+
? { collectMetrics: true }
|
|
4077
|
+
: {}),
|
|
4078
|
+
...(budget
|
|
4079
|
+
? { deferFailureKinds: ['prompt_too_long'] }
|
|
4080
|
+
: {}),
|
|
4081
|
+
reloadMessages: async () => {
|
|
4082
|
+
const recalled = turnMemory.consumeRecall();
|
|
4083
|
+
if (recalled) {
|
|
4084
|
+
projectMemoryRecallMessages = [
|
|
4085
|
+
{ role: 'user', content: recalled.content },
|
|
4086
|
+
];
|
|
4087
|
+
}
|
|
4088
|
+
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4089
|
+
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
4090
|
+
return [
|
|
4091
|
+
...contextMessages,
|
|
4092
|
+
...injectTurnContext([
|
|
4093
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
4094
|
+
...projectMemoryRecallMessages,
|
|
4095
|
+
]),
|
|
4096
|
+
];
|
|
4097
|
+
},
|
|
4098
|
+
...(this.options.hooks ||
|
|
4099
|
+
subagentExecutor ||
|
|
4100
|
+
taskTools ||
|
|
4101
|
+
this.scheduledPrompts ||
|
|
4102
|
+
this.workflowManager
|
|
4103
|
+
? {
|
|
4104
|
+
onStop: async (text) => {
|
|
4105
|
+
const messages = [];
|
|
4106
|
+
const outcome = await this.options.hooks?.run({
|
|
4107
|
+
...hookSession,
|
|
4108
|
+
hook_event_name: 'Stop',
|
|
4109
|
+
stop_hook_active: stopHookActive,
|
|
4110
|
+
last_assistant_message: text,
|
|
4111
|
+
}, undefined, signal);
|
|
4112
|
+
if (outcome) {
|
|
4113
|
+
await recordHookOutcome(outcome);
|
|
4114
|
+
if (outcome.blockedReason) {
|
|
4115
|
+
stopHookActive = true;
|
|
4116
|
+
messages.push(`Stop hook error: ${outcome.blockedReason}`);
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
const claimedAgentIds = new Set(this.sessionSubagentExecutors(sessionId, true).flatMap((executor) => executor.notificationClaimAgentIds()));
|
|
4120
|
+
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => {
|
|
4121
|
+
const delivered = (notification) => transcriptContainsBackgroundAgentNotification(snapshot.entries, notification);
|
|
4122
|
+
return this.hostedSubagents.has(executor)
|
|
4123
|
+
? executor.reconcileDetachedNotifications(delivered)
|
|
4124
|
+
: executor.reconcileDeliveredNotifications(delivered);
|
|
4125
|
+
}));
|
|
4126
|
+
await subagentExecutor?.hydratePersistedTasks(sessionId, this.activeCwd(), claimedAgentIds);
|
|
4127
|
+
const background = await this.collectSubagentNotifications(sessionId, subagentExecutor ?? undefined);
|
|
4128
|
+
if (background)
|
|
4129
|
+
messages.push(...background.messages);
|
|
4130
|
+
const workflow = await this.workflowManager?.notifications(true);
|
|
4131
|
+
if (workflow)
|
|
4132
|
+
messages.push(...workflow.messages);
|
|
4133
|
+
const bashMessages = await taskTools?.notifications(true);
|
|
4134
|
+
if (bashMessages)
|
|
4135
|
+
messages.push(...bashMessages);
|
|
4136
|
+
const scheduled = await this.scheduledPrompts?.drainDue();
|
|
4137
|
+
if (scheduled) {
|
|
4138
|
+
messages.push(...scheduled.map(({ prompt }) => prompt));
|
|
4139
|
+
}
|
|
4140
|
+
const batchUsage = background || workflow
|
|
4141
|
+
? mergeUsage(background?.usage ?? {
|
|
4142
|
+
inputTokens: 0,
|
|
4143
|
+
outputTokens: 0,
|
|
4144
|
+
}, workflow?.usage ?? {
|
|
4145
|
+
inputTokens: 0,
|
|
4146
|
+
outputTokens: 0,
|
|
4147
|
+
})
|
|
4148
|
+
: undefined;
|
|
4149
|
+
const batchModelUsage = mergeSessionRawModelUsage(background?.modelUsage, workflow?.modelUsage);
|
|
4150
|
+
const batchDurationApiMs = addApiDuration(workflow?.durationApiMs, addApiDuration(background?.durationApiMs, 0, 'durationApiMs'), 'durationApiMs');
|
|
4151
|
+
const backgroundDurationWithoutRetries = background?.durationApiWithoutRetriesMs ??
|
|
4152
|
+
background?.durationApiMs;
|
|
4153
|
+
const workflowDurationWithoutRetries = workflow?.durationApiWithoutRetriesMs ??
|
|
4154
|
+
workflow?.durationApiMs;
|
|
4155
|
+
const batchDurationApiWithoutRetriesMs = addApiDuration(workflowDurationWithoutRetries, addApiDuration(backgroundDurationWithoutRetries, 0, 'durationApiWithoutRetriesMs'), 'durationApiWithoutRetriesMs');
|
|
4156
|
+
const hasBatchDuration = background?.durationApiMs !== undefined ||
|
|
4157
|
+
background?.durationApiWithoutRetriesMs !== undefined ||
|
|
4158
|
+
workflow?.durationApiMs !== undefined ||
|
|
4159
|
+
workflow?.durationApiWithoutRetriesMs !== undefined;
|
|
4160
|
+
return {
|
|
4161
|
+
messages,
|
|
4162
|
+
...(batchUsage
|
|
4163
|
+
? {
|
|
4164
|
+
usage: batchUsage,
|
|
4165
|
+
...(batchModelUsage
|
|
4166
|
+
? { modelUsage: batchModelUsage }
|
|
4167
|
+
: {}),
|
|
4168
|
+
}
|
|
4169
|
+
: {}),
|
|
4170
|
+
...(hasBatchDuration
|
|
4171
|
+
? {
|
|
4172
|
+
durationApiMs: batchDurationApiMs,
|
|
4173
|
+
durationApiWithoutRetriesMs: batchDurationApiWithoutRetriesMs,
|
|
4174
|
+
}
|
|
4175
|
+
: {}),
|
|
4176
|
+
};
|
|
4177
|
+
},
|
|
4178
|
+
}
|
|
4179
|
+
: {}),
|
|
4180
|
+
...(this.options.approveTool
|
|
4181
|
+
? { approveTool: this.options.approveTool }
|
|
4182
|
+
: {}),
|
|
4183
|
+
permissionUpdates: this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
4184
|
+
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
4185
|
+
};
|
|
4186
|
+
const attemptMainTurn = () => signal
|
|
4187
|
+
? runtime.run({ ...runtimeRequest, signal })
|
|
4188
|
+
: runtime.run(runtimeRequest);
|
|
4189
|
+
const surfaceExhaustedRecovery = (error) => {
|
|
4190
|
+
this.options.eventSink?.({
|
|
4191
|
+
type: 'failed',
|
|
4192
|
+
message: error.message,
|
|
4193
|
+
retryable: false,
|
|
4194
|
+
});
|
|
4195
|
+
};
|
|
4196
|
+
let result;
|
|
4197
|
+
try {
|
|
4131
4198
|
try {
|
|
4132
4199
|
result = await attemptMainTurn();
|
|
4133
4200
|
}
|
|
4134
|
-
catch (
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
if (
|
|
4139
|
-
|
|
4140
|
-
throw
|
|
4201
|
+
catch (error) {
|
|
4202
|
+
if (!budget || !isPromptTooLongError(error))
|
|
4203
|
+
throw error;
|
|
4204
|
+
const recovery = await contextEngine.recover(error, contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4205
|
+
if (recovery.kind !== 'retry') {
|
|
4206
|
+
surfaceExhaustedRecovery(error);
|
|
4207
|
+
throw error;
|
|
4208
|
+
}
|
|
4209
|
+
// The single reactive retry must use the compacted transcript, not
|
|
4210
|
+
// the stale request copy captured before the compact boundary.
|
|
4211
|
+
runtimeRequest.messages = [
|
|
4212
|
+
...contextMessages,
|
|
4213
|
+
...injectTurnContext([
|
|
4214
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
4215
|
+
...projectMemoryRecallMessages,
|
|
4216
|
+
]),
|
|
4217
|
+
];
|
|
4218
|
+
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
4219
|
+
runtimeRequest.deferFailureKinds = true;
|
|
4220
|
+
try {
|
|
4221
|
+
result = await attemptMainTurn();
|
|
4222
|
+
}
|
|
4223
|
+
catch (retryError) {
|
|
4224
|
+
// Exactly one reactive retry is consumed; fail deterministically
|
|
4225
|
+
// and surface the original prompt-too-long error.
|
|
4226
|
+
if (signal?.aborted ||
|
|
4227
|
+
retryError instanceof AgentRunCancelledError) {
|
|
4228
|
+
throw new AgentRunCancelledError();
|
|
4229
|
+
}
|
|
4230
|
+
surfaceExhaustedRecovery(error);
|
|
4231
|
+
throw error;
|
|
4141
4232
|
}
|
|
4142
|
-
surfaceExhaustedRecovery(error);
|
|
4143
|
-
throw error;
|
|
4144
4233
|
}
|
|
4145
4234
|
}
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4235
|
+
catch (error) {
|
|
4236
|
+
if (!signal?.aborted &&
|
|
4237
|
+
!(error instanceof AgentRunCancelledError) &&
|
|
4238
|
+
!(error instanceof ModelProviderError &&
|
|
4239
|
+
error.kind === 'cancelled')) {
|
|
4240
|
+
const failureKind = error instanceof ModelProviderError
|
|
4241
|
+
? claudeStopFailureError(error.kind)
|
|
4242
|
+
: 'unknown';
|
|
4243
|
+
await this.runAdvisoryHook(sessionId, 'StopFailure', {
|
|
4244
|
+
error: failureKind,
|
|
4245
|
+
error_details: error instanceof Error ? error.message : String(error),
|
|
4246
|
+
}, failureKind, signal);
|
|
4247
|
+
}
|
|
4248
|
+
throw error;
|
|
4158
4249
|
}
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
recoveryPlanner.recordSuccess();
|
|
4162
|
-
const mainModel = provider.model !== undefined && provider.model.trim() !== ''
|
|
4163
|
-
? provider.model
|
|
4164
|
-
: undefined;
|
|
4165
|
-
const observedUsage = (mainModel !== undefined
|
|
4166
|
-
? result.modelUsage?.[mainModel]
|
|
4167
|
-
: undefined) ??
|
|
4168
|
-
Object.values(result.modelUsage ?? {})[0] ??
|
|
4169
|
-
result.usage;
|
|
4170
|
-
budget?.observeUsage(observedUsage, runtimeRequest.messages, definitions);
|
|
4171
|
-
if (structuredCapture && structuredCapture.calls !== 1) {
|
|
4172
|
-
throw new Error(`StructuredOutput must be called exactly once (received ${structuredCapture.calls})`);
|
|
4173
|
-
}
|
|
4174
|
-
const finalLeafUuid = lastAssistantUuid;
|
|
4175
|
-
if (!finalLeafUuid) {
|
|
4176
|
-
throw new Error('Could not locate final assistant response');
|
|
4177
|
-
}
|
|
4178
|
-
if (!skipUserPrompt) {
|
|
4179
|
-
const lastPrompt = createClaudeLastPromptEntry({
|
|
4180
|
-
sessionId,
|
|
4181
|
-
lastPrompt: effectivePrompt,
|
|
4182
|
-
leafUuid: finalLeafUuid,
|
|
4183
|
-
});
|
|
4184
|
-
const tail = await this.append(lease, snapshot.tail, lastPrompt);
|
|
4185
|
-
snapshot = {
|
|
4186
|
-
entries: [...snapshot.entries, lastPrompt],
|
|
4187
|
-
tail,
|
|
4188
|
-
};
|
|
4189
|
-
}
|
|
4190
|
-
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
4191
|
-
const totalUsage = mergeUsage(mergeUsage(mergeUsage(recoveryUsage, compactionUsage), shellUsage), result.usage);
|
|
4192
|
-
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
4193
|
-
if (!tracker) {
|
|
4194
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
4195
|
-
}
|
|
4196
|
-
const turnModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, compactionModelUsage, shellModelUsage, result.modelUsage);
|
|
4197
|
-
const combinedDurationMs = compactionDurationMs === undefined &&
|
|
4198
|
-
result.durationApiMs === undefined
|
|
4199
|
-
? undefined
|
|
4200
|
-
: (compactionDurationMs ?? 0) + (result.durationApiMs ?? 0);
|
|
4201
|
-
let rawCostUsd;
|
|
4202
|
-
if (turnModelUsage) {
|
|
4203
|
-
for (const [model, usage] of Object.entries(turnModelUsage)) {
|
|
4204
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4205
|
-
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4206
|
-
if (costUsd !== undefined)
|
|
4207
|
-
rawCostUsd = (rawCostUsd ?? 0) + costUsd;
|
|
4250
|
+
if (structuredCapture && structuredCapture.calls !== 1) {
|
|
4251
|
+
throw new Error(`StructuredOutput must be called exactly once (received ${structuredCapture.calls})`);
|
|
4208
4252
|
}
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
const pricing = this.options.pricing?.resolve(model);
|
|
4219
|
-
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4220
|
-
tracker.recordTurn({
|
|
4221
|
-
model,
|
|
4222
|
-
usage,
|
|
4223
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
4224
|
-
...(usage.webSearchRequests === undefined
|
|
4225
|
-
? {}
|
|
4226
|
-
: { webSearchRequests: usage.webSearchRequests }),
|
|
4253
|
+
const finalLeafUuid = lastAssistantUuid;
|
|
4254
|
+
if (!finalLeafUuid) {
|
|
4255
|
+
throw new Error('Could not locate final assistant response');
|
|
4256
|
+
}
|
|
4257
|
+
if (!skipUserPrompt) {
|
|
4258
|
+
const lastPrompt = createClaudeLastPromptEntry({
|
|
4259
|
+
sessionId,
|
|
4260
|
+
lastPrompt: effectivePrompt,
|
|
4261
|
+
leafUuid: finalLeafUuid,
|
|
4227
4262
|
});
|
|
4263
|
+
const tail = await this.append(lease, snapshot.tail, lastPrompt);
|
|
4264
|
+
snapshot = {
|
|
4265
|
+
entries: [...snapshot.entries, lastPrompt],
|
|
4266
|
+
tail,
|
|
4267
|
+
};
|
|
4228
4268
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4269
|
+
this.rememberDurableMetadata(sessionId, snapshot.entries);
|
|
4270
|
+
const totalUsage = mergeUsage(mergeUsage(mergeUsage(recoveryUsage, compactionUsage), shellUsage), result.usage);
|
|
4271
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
4272
|
+
if (!tracker) {
|
|
4273
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
4274
|
+
}
|
|
4275
|
+
const turnModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, compactionModelUsage, shellModelUsage, result.modelUsage);
|
|
4276
|
+
const combinedDurationMs = compactionDurationMs === undefined &&
|
|
4277
|
+
result.durationApiMs === undefined
|
|
4278
|
+
? undefined
|
|
4279
|
+
: (compactionDurationMs ?? 0) + (result.durationApiMs ?? 0);
|
|
4280
|
+
let rawCostUsd;
|
|
4281
|
+
if (turnModelUsage) {
|
|
4282
|
+
for (const [model, usage] of Object.entries(turnModelUsage)) {
|
|
4283
|
+
const pricing = this.options.pricing?.resolve(model);
|
|
4284
|
+
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4285
|
+
if (costUsd !== undefined)
|
|
4286
|
+
rawCostUsd = (rawCostUsd ?? 0) + costUsd;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
// Auto-compaction metering was already recorded atomically with each
|
|
4290
|
+
// committed boundary, and externally metered tool-summary metrics were
|
|
4291
|
+
// committed through the summary callback, so the live tracker receives
|
|
4292
|
+
// only the unrecorded subset here. The inclusive public aggregates
|
|
4293
|
+
// above still contain every row and duration.
|
|
4294
|
+
const trackedModelUsage = mergeSessionRawModelUsage(recoveryModelUsage, shellModelUsage, result.unrecordedModelUsage ?? result.modelUsage);
|
|
4295
|
+
if (trackedModelUsage) {
|
|
4296
|
+
for (const [model, usage] of Object.entries(trackedModelUsage)) {
|
|
4297
|
+
const pricing = this.options.pricing?.resolve(model);
|
|
4298
|
+
const costUsd = pricing ? usageCostUsd(usage, pricing) : undefined;
|
|
4299
|
+
tracker.recordTurn({
|
|
4300
|
+
model,
|
|
4301
|
+
usage,
|
|
4302
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
4303
|
+
...(usage.webSearchRequests === undefined
|
|
4304
|
+
? {}
|
|
4305
|
+
: { webSearchRequests: usage.webSearchRequests }),
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
let combinedToolDurationMs = 0;
|
|
4310
|
+
for (const recoveryResult of recoveryResults) {
|
|
4311
|
+
combinedToolDurationMs = addToolDuration(recoveryResult.durationToolMs, combinedToolDurationMs);
|
|
4312
|
+
}
|
|
4313
|
+
combinedToolDurationMs = addToolDuration(result.durationToolMs, combinedToolDurationMs);
|
|
4314
|
+
const trackedDurationApiMs = result.unrecordedDurationApiMs ?? result.durationApiMs;
|
|
4315
|
+
const trackedDurationApiWithoutRetriesMs = result.unrecordedDurationApiWithoutRetriesMs ??
|
|
4316
|
+
result.durationApiWithoutRetriesMs;
|
|
4317
|
+
tracker.recordDurations({
|
|
4318
|
+
...(trackedDurationApiMs === undefined
|
|
4264
4319
|
? {}
|
|
4265
|
-
: {
|
|
4266
|
-
...(
|
|
4320
|
+
: { apiDurationMs: trackedDurationApiMs }),
|
|
4321
|
+
...(trackedDurationApiWithoutRetriesMs === undefined
|
|
4267
4322
|
? {}
|
|
4268
|
-
: {
|
|
4323
|
+
: {
|
|
4324
|
+
apiDurationWithoutRetriesMs: trackedDurationApiWithoutRetriesMs,
|
|
4325
|
+
}),
|
|
4326
|
+
...(combinedToolDurationMs === 0
|
|
4327
|
+
? {}
|
|
4328
|
+
: { toolDurationMs: combinedToolDurationMs }),
|
|
4269
4329
|
});
|
|
4270
|
-
|
|
4271
|
-
|
|
4330
|
+
foregroundLineChanges.add({
|
|
4331
|
+
isError: false,
|
|
4332
|
+
...(result.linesAdded === undefined
|
|
4333
|
+
? {}
|
|
4334
|
+
: { linesAdded: result.linesAdded }),
|
|
4335
|
+
...(result.linesRemoved === undefined
|
|
4336
|
+
? {}
|
|
4337
|
+
: { linesRemoved: result.linesRemoved }),
|
|
4338
|
+
});
|
|
4339
|
+
if (foregroundLineChanges.linesAdded !== 0 ||
|
|
4340
|
+
foregroundLineChanges.linesRemoved !== 0) {
|
|
4341
|
+
tracker.recordLineChanges({
|
|
4342
|
+
...(foregroundLineChanges.linesAdded === 0
|
|
4343
|
+
? {}
|
|
4344
|
+
: { linesAdded: foregroundLineChanges.linesAdded }),
|
|
4345
|
+
...(foregroundLineChanges.linesRemoved === 0
|
|
4346
|
+
? {}
|
|
4347
|
+
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4272
4350
|
const memorySnapshot = projectClaudeModelMessages(snapshot.entries);
|
|
4273
4351
|
const providerVisibleMessages = [
|
|
4274
4352
|
...contextMessages,
|
|
4275
4353
|
...injectTurnContext(memorySnapshot),
|
|
4276
4354
|
];
|
|
4277
|
-
const currentContextTokens =
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4355
|
+
const currentContextTokens = contextEngine.report({
|
|
4356
|
+
messages: providerVisibleMessages,
|
|
4357
|
+
tools: definitions,
|
|
4358
|
+
})?.occupancyTokens ??
|
|
4359
|
+
estimateModelRequestTokens(providerVisibleMessages, definitions);
|
|
4360
|
+
await turnMemory.observeSuccess({
|
|
4361
|
+
...(finalLeafUuid ? { messageId: finalLeafUuid } : {}),
|
|
4362
|
+
occupancyTokens: currentContextTokens,
|
|
4363
|
+
toolCalls: currentTurnToolCalls,
|
|
4364
|
+
messages: memorySnapshot,
|
|
4365
|
+
projectMessages: projectMemoryMessages(snapshot.entries),
|
|
4366
|
+
directMaintenance: projectMemoryMaintained,
|
|
4367
|
+
});
|
|
4368
|
+
turnCompleted = true;
|
|
4369
|
+
return {
|
|
4370
|
+
sessionId,
|
|
4371
|
+
text: structuredCapture && structuredCapture.calls === 1
|
|
4372
|
+
? JSON.stringify(structuredCapture.value)
|
|
4373
|
+
: result.text,
|
|
4374
|
+
usage: totalUsage,
|
|
4375
|
+
...(combinedDurationMs === undefined
|
|
4376
|
+
? {}
|
|
4377
|
+
: { durationApiMs: combinedDurationMs }),
|
|
4378
|
+
...(rawCostUsd === undefined ? {} : { costUsd: rawCostUsd }),
|
|
4379
|
+
...(turnModelUsage ? { modelUsage: { ...turnModelUsage } } : {}),
|
|
4380
|
+
...(structuredCapture && structuredCapture.calls === 1
|
|
4381
|
+
? { structuredOutput: structuredCapture.value }
|
|
4382
|
+
: {}),
|
|
4383
|
+
};
|
|
4292
4384
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
});
|
|
4298
|
-
turnCompleted = true;
|
|
4299
|
-
return {
|
|
4300
|
-
sessionId,
|
|
4301
|
-
text: structuredCapture && structuredCapture.calls === 1
|
|
4302
|
-
? JSON.stringify(structuredCapture.value)
|
|
4303
|
-
: result.text,
|
|
4304
|
-
usage: totalUsage,
|
|
4305
|
-
...(combinedDurationMs === undefined
|
|
4306
|
-
? {}
|
|
4307
|
-
: { durationApiMs: combinedDurationMs }),
|
|
4308
|
-
...(rawCostUsd === undefined ? {} : { costUsd: rawCostUsd }),
|
|
4309
|
-
...(turnModelUsage ? { modelUsage: { ...turnModelUsage } } : {}),
|
|
4310
|
-
...(structuredCapture && structuredCapture.calls === 1
|
|
4311
|
-
? { structuredOutput: structuredCapture.value }
|
|
4312
|
-
: {}),
|
|
4313
|
-
};
|
|
4314
|
-
}
|
|
4315
|
-
finally {
|
|
4316
|
-
if (!turnCompleted) {
|
|
4317
|
-
await this.hookLifecycle.end(sessionId, 'other');
|
|
4385
|
+
finally {
|
|
4386
|
+
if (!turnCompleted) {
|
|
4387
|
+
await this.hookLifecycle.end(sessionId, 'other');
|
|
4388
|
+
}
|
|
4318
4389
|
}
|
|
4390
|
+
});
|
|
4391
|
+
if (leaseResult.status === 'conflict') {
|
|
4392
|
+
throw new Error(`Claude transcript append conflict: ${leaseResult.reason}`);
|
|
4319
4393
|
}
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4394
|
+
const result = leaseResult.value;
|
|
4395
|
+
controller.complete();
|
|
4396
|
+
return result;
|
|
4397
|
+
}
|
|
4398
|
+
catch (error) {
|
|
4399
|
+
controller.fail(error, signal);
|
|
4400
|
+
throw error;
|
|
4323
4401
|
}
|
|
4324
|
-
return leaseResult.value;
|
|
4325
4402
|
}
|
|
4326
4403
|
async ensureFileResources(sessionId, signal) {
|
|
4327
4404
|
const resources = this.options.fileResources ?? [];
|
|
@@ -4416,11 +4493,12 @@ export class ClaudeSessionService {
|
|
|
4416
4493
|
* recent suffix. Returns null to fall back to the existing full-compaction
|
|
4417
4494
|
* behavior (missing/invalid watermark, empty projection, or oversized
|
|
4418
4495
|
* projection). */
|
|
4419
|
-
async selectMemoryPreservedCompact(sessionId, activeEntries) {
|
|
4496
|
+
async selectMemoryPreservedCompact(sessionId, activeEntries, options = {}) {
|
|
4420
4497
|
const controller = this.sessionMemoryController(sessionId);
|
|
4421
4498
|
if (controller === null)
|
|
4422
4499
|
return null;
|
|
4423
|
-
|
|
4500
|
+
if (options.sessionMemoryReady !== true)
|
|
4501
|
+
await controller.waitForCompact();
|
|
4424
4502
|
let watermark = null;
|
|
4425
4503
|
let memorySummary = '';
|
|
4426
4504
|
try {
|
|
@@ -5052,12 +5130,37 @@ export class ClaudeSessionService {
|
|
|
5052
5130
|
return 'corrupt';
|
|
5053
5131
|
return this.schema.writeMode === 'read-write' ? 'ready' : 'read-only';
|
|
5054
5132
|
}
|
|
5133
|
+
nativeSessionStatus(issue, entryCount) {
|
|
5134
|
+
if (issue?.kind === 'unsupported-version')
|
|
5135
|
+
return 'read-only';
|
|
5136
|
+
if (entryCount === 0 || issue)
|
|
5137
|
+
return 'corrupt';
|
|
5138
|
+
return 'read-only';
|
|
5139
|
+
}
|
|
5140
|
+
claudeSessionSummary(sessionId, index) {
|
|
5141
|
+
const metadata = reduceClaudeSessionMetadata(index.entries, sessionId);
|
|
5142
|
+
return {
|
|
5143
|
+
sessionId,
|
|
5144
|
+
...sessionMetadataFields(metadata, { agentNameFallback: true }),
|
|
5145
|
+
lastPrompt: metadata.lastPrompt ?? null,
|
|
5146
|
+
updatedAt: index.updatedAt,
|
|
5147
|
+
status: this.sessionStatus(index.issue, index.entries.length),
|
|
5148
|
+
issue: index.issue,
|
|
5149
|
+
};
|
|
5150
|
+
}
|
|
5055
5151
|
provider() {
|
|
5056
5152
|
if (!this.options.provider) {
|
|
5057
5153
|
throw new Error('A model provider is required for run and resume');
|
|
5058
5154
|
}
|
|
5059
5155
|
return this.options.provider;
|
|
5060
5156
|
}
|
|
5157
|
+
async assertNativeWriteTargetIsLegacy(sessionId) {
|
|
5158
|
+
if (this.options.dataPlane !== 'native')
|
|
5159
|
+
return;
|
|
5160
|
+
const existing = await readNativeTranscript(this.paths(sessionId).sessionFile);
|
|
5161
|
+
if (existing.format === 'native')
|
|
5162
|
+
throw new Error('native transcript is read-only until native writes are enabled');
|
|
5163
|
+
}
|
|
5061
5164
|
model() {
|
|
5062
5165
|
return this.activeProvider?.model ?? this.options.provider?.model;
|
|
5063
5166
|
}
|
|
@@ -5087,13 +5190,6 @@ export class ClaudeSessionService {
|
|
|
5087
5190
|
const system = memory ? `${agent.body}\n\n${memory}` : agent.body;
|
|
5088
5191
|
return system.trim() ? system : null;
|
|
5089
5192
|
}
|
|
5090
|
-
assembledSystemMessages(agent, messages) {
|
|
5091
|
-
return agent &&
|
|
5092
|
-
this.options.explicitSystemPrompt &&
|
|
5093
|
-
this.options.agentSystemPromptOverridesExplicit
|
|
5094
|
-
? messages.slice(1)
|
|
5095
|
-
: messages;
|
|
5096
|
-
}
|
|
5097
5193
|
contextBudget(provider) {
|
|
5098
5194
|
if (this.options.contextBudget)
|
|
5099
5195
|
return this.options.contextBudget;
|
|
@@ -5122,7 +5218,7 @@ export class ClaudeSessionService {
|
|
|
5122
5218
|
return this.sessionMemoryProviderFactory() !== null;
|
|
5123
5219
|
}
|
|
5124
5220
|
sessionMemoryController(sessionId) {
|
|
5125
|
-
if (!this.sessionMemoryEnabled() || !
|
|
5221
|
+
if (!this.sessionMemoryEnabled() || !isSessionId(sessionId)) {
|
|
5126
5222
|
return null;
|
|
5127
5223
|
}
|
|
5128
5224
|
let controller = this.sessionMemoryControllers.get(sessionId);
|