praxis-agent 0.35.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 +5 -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 -4
- package/dist/application/session-service.js +528 -434
- package/dist/application/subagent-service.js +358 -177
- 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 +2 -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 +264 -204
- package/dist/cli/tui/claude-style.js +154 -130
- 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/tui-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli-runtime.d.ts +2 -2
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -0
- package/dist/core/runtime.d.ts +5 -0
- package/dist/core/runtime.js +15 -1
- package/dist/persistence/claude-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- 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/package.json +1 -1
|
@@ -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,13 +23,18 @@ 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 {
|
|
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';
|
|
27
30
|
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
28
31
|
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
29
32
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
30
33
|
import { ClaudeFileChangeWatcher } from '../hooks/claude-file-change-watcher.js';
|
|
31
34
|
import { ClaudeTranscriptStore, } from '../persistence/claude-transcript-store.js';
|
|
32
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';
|
|
33
38
|
import { InMemoryTranscriptStore } from '../persistence/in-memory-transcript-store.js';
|
|
34
39
|
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
35
40
|
import { ModelCompactor } from './model-compactor.js';
|
|
@@ -78,6 +83,42 @@ function mainAgentToolNames(tools, agent) {
|
|
|
78
83
|
.map(({ name }) => name)
|
|
79
84
|
.filter((name) => (!requested || requested.has(name)) && !disallowed.has(name));
|
|
80
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
|
+
}
|
|
81
122
|
const emptyToolRegistry = {
|
|
82
123
|
definitions: () => [],
|
|
83
124
|
schedulingPolicy: () => ({ concurrency: 'exclusive' }),
|
|
@@ -1554,19 +1595,62 @@ export class ClaudeSessionService {
|
|
|
1554
1595
|
}
|
|
1555
1596
|
catch (error) {
|
|
1556
1597
|
if (error.code === 'ENOENT')
|
|
1557
|
-
|
|
1558
|
-
|
|
1598
|
+
names = [];
|
|
1599
|
+
else
|
|
1600
|
+
throw error;
|
|
1559
1601
|
}
|
|
1560
1602
|
const discoveredSessionIds = names
|
|
1561
1603
|
.filter((name) => extname(name) === '.jsonl')
|
|
1562
1604
|
.map((name) => basename(name, '.jsonl'))
|
|
1563
|
-
.filter((sessionId) =>
|
|
1605
|
+
.filter((sessionId) => isSessionId(sessionId));
|
|
1564
1606
|
const sessionIds = [
|
|
1565
1607
|
...new Set([
|
|
1566
1608
|
...discoveredSessionIds,
|
|
1567
1609
|
...this.explicitSessionFiles.keys(),
|
|
1568
1610
|
]),
|
|
1569
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
|
+
}
|
|
1570
1654
|
if (discoveredRoot !== undefined) {
|
|
1571
1655
|
for (const sessionId of discoveredSessionIds) {
|
|
1572
1656
|
this.discoveredProjectRoots.set(sessionId, projectRoot);
|
|
@@ -1581,46 +1665,11 @@ export class ClaudeSessionService {
|
|
|
1581
1665
|
try {
|
|
1582
1666
|
if ('error' in result)
|
|
1583
1667
|
throw result.error;
|
|
1584
|
-
|
|
1585
|
-
const metadata = reduceClaudeSessionMetadata(index.entries, sessionId);
|
|
1586
|
-
const name = metadata.title ?? metadata.agentName;
|
|
1587
|
-
const prLink = metadata.prLink;
|
|
1588
|
-
return {
|
|
1589
|
-
sessionId,
|
|
1590
|
-
...(name === undefined ? {} : { name }),
|
|
1591
|
-
...(metadata.tag === undefined ? {} : { tag: metadata.tag }),
|
|
1592
|
-
...(metadata.agentName === undefined
|
|
1593
|
-
? {}
|
|
1594
|
-
: { agentName: metadata.agentName }),
|
|
1595
|
-
...(metadata.agentColor === undefined
|
|
1596
|
-
? {}
|
|
1597
|
-
: { agentColor: metadata.agentColor }),
|
|
1598
|
-
...(metadata.agentSetting === undefined
|
|
1599
|
-
? {}
|
|
1600
|
-
: { agentSetting: metadata.agentSetting }),
|
|
1601
|
-
...(metadata.permissionMode === undefined
|
|
1602
|
-
? {}
|
|
1603
|
-
: { permissionMode: metadata.permissionMode }),
|
|
1604
|
-
...(metadata.mode === undefined ? {} : { mode: metadata.mode }),
|
|
1605
|
-
lastPrompt: metadata.lastPrompt ?? null,
|
|
1606
|
-
updatedAt: index.updatedAt,
|
|
1607
|
-
status: this.sessionStatus(index.issue, index.entries.length),
|
|
1608
|
-
issue: index.issue,
|
|
1609
|
-
...(prLink
|
|
1610
|
-
? {
|
|
1611
|
-
prNumber: prLink.prNumber,
|
|
1612
|
-
prUrl: prLink.prUrl,
|
|
1613
|
-
prRepository: prLink.prRepository,
|
|
1614
|
-
}
|
|
1615
|
-
: {}),
|
|
1616
|
-
};
|
|
1668
|
+
return this.claudeSessionSummary(result.sessionId, result.index);
|
|
1617
1669
|
}
|
|
1618
1670
|
catch (error) {
|
|
1619
|
-
if (error
|
|
1620
|
-
return null;
|
|
1621
|
-
if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code ?? '')) {
|
|
1671
|
+
if (isSessionCandidateError(error))
|
|
1622
1672
|
return null;
|
|
1623
|
-
}
|
|
1624
1673
|
throw error;
|
|
1625
1674
|
}
|
|
1626
1675
|
});
|
|
@@ -1631,6 +1680,37 @@ export class ClaudeSessionService {
|
|
|
1631
1680
|
}
|
|
1632
1681
|
async inspect(sessionId) {
|
|
1633
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
|
+
}
|
|
1634
1714
|
await this.discoverProjectRoot(sessionId);
|
|
1635
1715
|
const paths = this.paths(sessionId);
|
|
1636
1716
|
let metadata;
|
|
@@ -1645,30 +1725,9 @@ export class ClaudeSessionService {
|
|
|
1645
1725
|
}
|
|
1646
1726
|
const recovery = await this.store(sessionId).loadReadOnly();
|
|
1647
1727
|
const sessionMetadata = reduceClaudeSessionMetadata(recovery.entries, sessionId);
|
|
1648
|
-
const prLink = sessionMetadata.prLink;
|
|
1649
1728
|
return {
|
|
1650
1729
|
sessionId,
|
|
1651
|
-
...(sessionMetadata
|
|
1652
|
-
? {}
|
|
1653
|
-
: { name: sessionMetadata.title }),
|
|
1654
|
-
...(sessionMetadata.tag === undefined
|
|
1655
|
-
? {}
|
|
1656
|
-
: { tag: sessionMetadata.tag }),
|
|
1657
|
-
...(sessionMetadata.agentName === undefined
|
|
1658
|
-
? {}
|
|
1659
|
-
: { agentName: sessionMetadata.agentName }),
|
|
1660
|
-
...(sessionMetadata.agentColor === undefined
|
|
1661
|
-
? {}
|
|
1662
|
-
: { agentColor: sessionMetadata.agentColor }),
|
|
1663
|
-
...(sessionMetadata.agentSetting === undefined
|
|
1664
|
-
? {}
|
|
1665
|
-
: { agentSetting: sessionMetadata.agentSetting }),
|
|
1666
|
-
...(sessionMetadata.permissionMode === undefined
|
|
1667
|
-
? {}
|
|
1668
|
-
: { permissionMode: sessionMetadata.permissionMode }),
|
|
1669
|
-
...(sessionMetadata.mode === undefined
|
|
1670
|
-
? {}
|
|
1671
|
-
: { mode: sessionMetadata.mode }),
|
|
1730
|
+
...sessionMetadataFields(sessionMetadata, { agentNameFallback: false }),
|
|
1672
1731
|
lastPrompt: sessionMetadata.lastPrompt ?? null,
|
|
1673
1732
|
updatedAt: metadata.mtime.toISOString(),
|
|
1674
1733
|
status: this.sessionStatus(recovery.issue, recovery.entries.length),
|
|
@@ -1678,13 +1737,6 @@ export class ClaudeSessionService {
|
|
|
1678
1737
|
entryCount: recovery.entries.length,
|
|
1679
1738
|
byteLength: recovery.tail.byteLength,
|
|
1680
1739
|
newlineTerminated: recovery.tail.newlineTerminated,
|
|
1681
|
-
...(prLink
|
|
1682
|
-
? {
|
|
1683
|
-
prNumber: prLink.prNumber,
|
|
1684
|
-
prUrl: prLink.prUrl,
|
|
1685
|
-
prRepository: prLink.prRepository,
|
|
1686
|
-
}
|
|
1687
|
-
: {}),
|
|
1688
1740
|
};
|
|
1689
1741
|
}
|
|
1690
1742
|
async readEffectiveAgentColor(sessionId) {
|
|
@@ -1694,6 +1746,8 @@ export class ClaudeSessionService {
|
|
|
1694
1746
|
}
|
|
1695
1747
|
async export(sessionId) {
|
|
1696
1748
|
this.assertSessionPersistence();
|
|
1749
|
+
if (this.options.dataPlane === 'native')
|
|
1750
|
+
return exportNativeTranscript(this.paths(sessionId).sessionFile);
|
|
1697
1751
|
await this.discoverProjectRoot(sessionId);
|
|
1698
1752
|
try {
|
|
1699
1753
|
return await this.store(sessionId).exportReadOnly();
|
|
@@ -1709,17 +1763,40 @@ export class ClaudeSessionService {
|
|
|
1709
1763
|
this.assertSessionPersistence();
|
|
1710
1764
|
const path = resolve(requestedPath);
|
|
1711
1765
|
const pathMetadata = await lstat(path);
|
|
1766
|
+
const dataPlaneName = this.options.dataPlane === 'native' ? 'Native' : 'Claude';
|
|
1712
1767
|
if (!pathMetadata.isFile()) {
|
|
1713
|
-
throw new Error(
|
|
1768
|
+
throw new Error(`${dataPlaneName} resume path must be a regular JSONL file: ${path}`);
|
|
1714
1769
|
}
|
|
1715
1770
|
if (extname(path).toLowerCase() !== '.jsonl') {
|
|
1716
|
-
throw new Error(
|
|
1771
|
+
throw new Error(`${dataPlaneName} resume path must end in .jsonl: ${path}`);
|
|
1717
1772
|
}
|
|
1718
1773
|
const sessionId = basename(path, '.jsonl');
|
|
1719
|
-
if (!
|
|
1720
|
-
throw new Error(
|
|
1774
|
+
if (!isSessionId(sessionId)) {
|
|
1775
|
+
throw new Error(`${dataPlaneName} resume path filename must be a session UUID: ${path}`);
|
|
1721
1776
|
}
|
|
1722
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
|
+
}
|
|
1723
1800
|
const exact = this.pathsForCwd(sessionId, this.activeCwd());
|
|
1724
1801
|
const candidate = new ClaudeTranscriptStore({
|
|
1725
1802
|
sessionFile: canonicalPath,
|
|
@@ -1733,6 +1810,10 @@ export class ClaudeSessionService {
|
|
|
1733
1810
|
if (snapshot.entries.length === 0) {
|
|
1734
1811
|
throw new Error('Claude resume transcript must not be empty');
|
|
1735
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
|
+
}
|
|
1736
1817
|
let matchingSessionIdentity = false;
|
|
1737
1818
|
for (const entry of snapshot.entries) {
|
|
1738
1819
|
if (entry.sessionId === sessionId)
|
|
@@ -1750,40 +1831,21 @@ export class ClaudeSessionService {
|
|
|
1750
1831
|
this.explicitResumeLeafUuids.set(sessionId, selected.leafUuid);
|
|
1751
1832
|
this.discoveredProjectRoots.set(sessionId, dirname(canonicalPath));
|
|
1752
1833
|
const metadata = reduceClaudeSessionMetadata(snapshot.entries, sessionId);
|
|
1753
|
-
const prLink = metadata.prLink;
|
|
1754
1834
|
return {
|
|
1755
1835
|
sessionId,
|
|
1756
|
-
...(metadata
|
|
1757
|
-
...(metadata.tag === undefined ? {} : { tag: metadata.tag }),
|
|
1758
|
-
...(metadata.agentName === undefined
|
|
1759
|
-
? {}
|
|
1760
|
-
: { agentName: metadata.agentName }),
|
|
1761
|
-
...(metadata.agentColor === undefined
|
|
1762
|
-
? {}
|
|
1763
|
-
: { agentColor: metadata.agentColor }),
|
|
1764
|
-
...(metadata.agentSetting === undefined
|
|
1765
|
-
? {}
|
|
1766
|
-
: { agentSetting: metadata.agentSetting }),
|
|
1767
|
-
...(metadata.permissionMode === undefined
|
|
1768
|
-
? {}
|
|
1769
|
-
: { permissionMode: metadata.permissionMode }),
|
|
1770
|
-
...(metadata.mode === undefined ? {} : { mode: metadata.mode }),
|
|
1836
|
+
...sessionMetadataFields(metadata, { agentNameFallback: false }),
|
|
1771
1837
|
lastPrompt: metadata.lastPrompt ?? null,
|
|
1772
1838
|
updatedAt: pathMetadata.mtime.toISOString(),
|
|
1773
1839
|
status: this.sessionStatus(null, snapshot.entries.length),
|
|
1774
1840
|
issue: null,
|
|
1775
|
-
...(prLink === undefined
|
|
1776
|
-
? {}
|
|
1777
|
-
: {
|
|
1778
|
-
prNumber: prLink.prNumber,
|
|
1779
|
-
...(prLink.prUrl === undefined ? {} : { prUrl: prLink.prUrl }),
|
|
1780
|
-
...(prLink.prRepository === undefined
|
|
1781
|
-
? {}
|
|
1782
|
-
: { prRepository: prLink.prRepository }),
|
|
1783
|
-
}),
|
|
1784
1841
|
};
|
|
1785
1842
|
}
|
|
1786
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
|
+
}
|
|
1787
1849
|
await this.discoverProjectRoot(sessionId);
|
|
1788
1850
|
try {
|
|
1789
1851
|
const recovery = await this.store(sessionId).loadReadOnly();
|
|
@@ -2371,6 +2433,7 @@ export class ClaudeSessionService {
|
|
|
2371
2433
|
}
|
|
2372
2434
|
async fork(parentSessionId, sessionId = randomUUID(), resumeSessionAt) {
|
|
2373
2435
|
this.assertSessionPersistence();
|
|
2436
|
+
await this.assertNativeWriteTargetIsLegacy(parentSessionId);
|
|
2374
2437
|
this.assertWritable();
|
|
2375
2438
|
const entries = await this.nativeForkEntries(parentSessionId, sessionId, resumeSessionAt);
|
|
2376
2439
|
const result = await this.store(sessionId).create(entries);
|
|
@@ -2701,6 +2764,8 @@ export class ClaudeSessionService {
|
|
|
2701
2764
|
const skipUserPrompt = submission.kind === 'retry';
|
|
2702
2765
|
const controller = new TurnTerminalController(this.options.eventSink ?? (() => undefined));
|
|
2703
2766
|
try {
|
|
2767
|
+
if (requireExisting)
|
|
2768
|
+
await this.assertNativeWriteTargetIsLegacy(sessionId);
|
|
2704
2769
|
this.assertWritable();
|
|
2705
2770
|
if (prompt.length === 0 && images.length === 0 && documents.length === 0)
|
|
2706
2771
|
throw new Error('Prompt must not be empty');
|
|
@@ -2834,14 +2899,25 @@ export class ClaudeSessionService {
|
|
|
2834
2899
|
agent?.initialPrompt
|
|
2835
2900
|
? `${agent.initialPrompt}\n\n${prompt}`
|
|
2836
2901
|
: prompt);
|
|
2837
|
-
const
|
|
2838
|
-
|
|
2839
|
-
|
|
2902
|
+
const sessionMemory = this.sessionMemoryController(sessionId);
|
|
2903
|
+
const turnMemory = new TurnMemoryCoordinator({
|
|
2904
|
+
sessionId,
|
|
2905
|
+
...(sessionMemory ? { session: sessionMemory } : {}),
|
|
2906
|
+
...(this.options.projectMemoryRecall
|
|
2907
|
+
? { projectRecall: this.options.projectMemoryRecall }
|
|
2908
|
+
: {}),
|
|
2909
|
+
...(this.options.projectMemoryExtraction
|
|
2910
|
+
? { projectExtraction: this.options.projectMemoryExtraction }
|
|
2911
|
+
: {}),
|
|
2912
|
+
warn: (message) => this.options.eventSink?.({ type: 'warning', message }),
|
|
2913
|
+
});
|
|
2914
|
+
if (!skipUserPrompt && shellCommand === undefined) {
|
|
2915
|
+
turnMemory.prefetch({
|
|
2840
2916
|
turnId: randomUUID(),
|
|
2841
2917
|
prompt: effectivePrompt,
|
|
2842
2918
|
...(signal ? { signal } : {}),
|
|
2843
|
-
})
|
|
2844
|
-
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2845
2921
|
const initialPricing = this.options.pricing?.resolve(provider.model ?? 'praxis/provider');
|
|
2846
2922
|
if (this.options.maxBudgetUsd !== undefined && !initialPricing) {
|
|
2847
2923
|
throw new Error(`Cannot enforce --max-budget-usd: no pricing is configured for model ${provider.model ?? 'praxis/provider'}`);
|
|
@@ -3206,7 +3282,11 @@ export class ClaudeSessionService {
|
|
|
3206
3282
|
let currentTurnToolCalls = 0;
|
|
3207
3283
|
let projectMemoryMaintained = false;
|
|
3208
3284
|
let projectMemoryRecallMessages = [];
|
|
3285
|
+
let observeModelRequestUsage = () => undefined;
|
|
3209
3286
|
const observer = {
|
|
3287
|
+
modelRequestCompleted: async (input) => {
|
|
3288
|
+
observeModelRequestUsage(input);
|
|
3289
|
+
},
|
|
3210
3290
|
assistantCompleted: async (message) => {
|
|
3211
3291
|
const [entry] = translateProviderEvents([
|
|
3212
3292
|
{
|
|
@@ -3292,7 +3372,7 @@ export class ClaudeSessionService {
|
|
|
3292
3372
|
if (path &&
|
|
3293
3373
|
isPathWithin(this.options.projectMemoryDirectory, path)) {
|
|
3294
3374
|
if (call.name === 'Read') {
|
|
3295
|
-
|
|
3375
|
+
turnMemory.recordRead(path);
|
|
3296
3376
|
}
|
|
3297
3377
|
else if (call.name === 'Write' || call.name === 'Edit') {
|
|
3298
3378
|
projectMemoryMaintained = true;
|
|
@@ -3302,7 +3382,7 @@ export class ClaudeSessionService {
|
|
|
3302
3382
|
for (const accessedPath of toolResult.accessedPaths ?? []) {
|
|
3303
3383
|
const resolvedAccessedPath = resolve(this.activeCwd(), accessedPath);
|
|
3304
3384
|
if (isPathWithin(this.options.projectMemoryDirectory, resolvedAccessedPath)) {
|
|
3305
|
-
|
|
3385
|
+
turnMemory.recordRead(resolvedAccessedPath);
|
|
3306
3386
|
}
|
|
3307
3387
|
}
|
|
3308
3388
|
}
|
|
@@ -3408,7 +3488,6 @@ export class ClaudeSessionService {
|
|
|
3408
3488
|
tail: settingTail,
|
|
3409
3489
|
};
|
|
3410
3490
|
}
|
|
3411
|
-
const sessionMemory = this.sessionMemoryController(sessionId);
|
|
3412
3491
|
let agentSystem = null;
|
|
3413
3492
|
let planModeMessage;
|
|
3414
3493
|
let sessionMemoryMessage = null;
|
|
@@ -3422,9 +3501,7 @@ export class ClaudeSessionService {
|
|
|
3422
3501
|
agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
3423
3502
|
planModeMessage =
|
|
3424
3503
|
this.options.interactiveTools?.contextMessage(sessionId);
|
|
3425
|
-
sessionMemoryMessage =
|
|
3426
|
-
? this.sessionMemoryMessage(await sessionMemory.summary())
|
|
3427
|
-
: null;
|
|
3504
|
+
sessionMemoryMessage = this.sessionMemoryMessage(await turnMemory.sessionSummary());
|
|
3428
3505
|
const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
|
|
3429
3506
|
cwd: this.activeCwd(),
|
|
3430
3507
|
lifecycleId: sessionId,
|
|
@@ -3479,7 +3556,17 @@ export class ClaudeSessionService {
|
|
|
3479
3556
|
? (structuredTools?.definitions() ?? [])
|
|
3480
3557
|
: [];
|
|
3481
3558
|
const budget = this.contextBudget(provider);
|
|
3482
|
-
const
|
|
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
|
+
};
|
|
3483
3570
|
const pendingUserMessages = expandedMessages.map((message, index) => ({
|
|
3484
3571
|
role: 'user',
|
|
3485
3572
|
content: message.text,
|
|
@@ -3531,276 +3618,301 @@ export class ClaudeSessionService {
|
|
|
3531
3618
|
const injectDynamicContext = (messages) => injectFirstUserMessageContext(messages, contextProjection.firstUserMessageContext);
|
|
3532
3619
|
const injectTurnContext = (messages) => injectAgentMentionContext(injectDynamicContext(messages));
|
|
3533
3620
|
let compactionAnchorUuid = this.lastMessageUuid(snapshot.entries);
|
|
3534
|
-
const
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
...
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
logicalParentUuid =
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3621
|
+
const contextTransitionPort = (pendingMessages = [], preservedUserMessages = []) => ({
|
|
3622
|
+
current: () => {
|
|
3623
|
+
const historyMessages = [
|
|
3624
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
3625
|
+
...projectMemoryRecallMessages,
|
|
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) => ({
|
|
3588
3687
|
role: 'user',
|
|
3589
|
-
content:
|
|
3590
|
-
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
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([
|
|
3595
3695
|
...irreducibleMessages,
|
|
3596
3696
|
{
|
|
3597
3697
|
role: 'user',
|
|
3598
|
-
content: formatClaudeCompactSummary('
|
|
3698
|
+
content: formatClaudeCompactSummary(''),
|
|
3599
3699
|
},
|
|
3600
|
-
], definitions)
|
|
3601
|
-
targetTokens =
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
const
|
|
3625
|
-
|
|
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
|
|
3747
|
+
? {
|
|
3748
|
+
preservedUuids: memorySelection.preservedEntries.flatMap((entry) => typeof entry.uuid === 'string' ? [entry.uuid] : []),
|
|
3749
|
+
}
|
|
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 }), {
|
|
3626
3764
|
sessionId,
|
|
3627
|
-
|
|
3628
|
-
summary: compacted.summary,
|
|
3629
|
-
preTokens,
|
|
3630
|
-
postTokens,
|
|
3631
|
-
previousCumulativeDroppedTokens: getCumulativeDroppedTokens(snapshot.entries),
|
|
3632
|
-
durationMs: compactedDurationMs,
|
|
3765
|
+
parentUuid: compactSummaryUuid,
|
|
3633
3766
|
cwd: this.activeCwd(),
|
|
3634
3767
|
claudeVersion: this.options.claudeVersion,
|
|
3635
3768
|
gitBranch: null,
|
|
3636
|
-
...
|
|
3637
|
-
|
|
3638
|
-
preservedUuids: memorySelection.preservedEntries.flatMap((entry) => typeof entry.uuid === 'string' ? [entry.uuid] : []),
|
|
3639
|
-
}
|
|
3640
|
-
: {}),
|
|
3641
|
-
createUuid: () => uuids.shift() ?? randomUUID(),
|
|
3769
|
+
history: [...snapshot.entries, ...provisionalEntries],
|
|
3770
|
+
createUuid: () => replayUuids.shift() ?? randomUUID(),
|
|
3642
3771
|
now: () => timestamp,
|
|
3643
3772
|
});
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
history: [...snapshot.entries, ...provisionalEntries],
|
|
3660
|
-
createUuid: () => replayUuids.shift() ?? randomUUID(),
|
|
3661
|
-
now: () => timestamp,
|
|
3662
|
-
});
|
|
3663
|
-
const compactedHistory = [
|
|
3664
|
-
...projectClaudeModelMessages([
|
|
3665
|
-
...snapshot.entries,
|
|
3666
|
-
...provisionalEntries,
|
|
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),
|
|
3667
3788
|
...replayEntries,
|
|
3668
|
-
]
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
!contextRecoveryMadeProgress({
|
|
3679
|
-
beforeOccupancyTokens: predicted.occupancyTokens,
|
|
3680
|
-
afterOccupancyTokens: afterPending.occupancyTokens,
|
|
3681
|
-
})) {
|
|
3682
|
-
throw new Error('Reactive context compaction made no occupancy progress');
|
|
3683
|
-
}
|
|
3684
|
-
const entries = [
|
|
3685
|
-
...compactEntries(afterHistory.estimatedTokens),
|
|
3686
|
-
...replayEntries,
|
|
3687
|
-
];
|
|
3688
|
-
if (signal?.aborted)
|
|
3689
|
-
throw new AgentRunCancelledError();
|
|
3690
|
-
// Construct the exact tracker mutations for this single committed
|
|
3691
|
-
// boundary and preflight them against a clone of the live tracker so
|
|
3692
|
-
// invalid input or cumulative overflow rejects before the compact
|
|
3693
|
-
// boundary is appended rather than after a half-commit.
|
|
3694
|
-
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
3695
|
-
if (!tracker) {
|
|
3696
|
-
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3697
|
-
}
|
|
3698
|
-
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3699
|
-
? compacted.model
|
|
3700
|
-
: provider.model;
|
|
3701
|
-
const compactModelNonBlank = compactModel !== undefined && compactModel.trim() !== '';
|
|
3702
|
-
if (hasNonZeroUsage(compacted.usage) && !compactModelNonBlank) {
|
|
3703
|
-
throw new Error('Auto compact usage requires a nonblank model identity');
|
|
3704
|
-
}
|
|
3705
|
-
let meteringTurnInput;
|
|
3706
|
-
if (compactModelNonBlank && hasNonZeroUsage(compacted.usage)) {
|
|
3707
|
-
const pricing = this.options.pricing?.resolve(compactModel);
|
|
3708
|
-
const costUsd = pricing
|
|
3709
|
-
? usageCostUsd(compacted.usage, pricing)
|
|
3710
|
-
: undefined;
|
|
3711
|
-
meteringTurnInput = {
|
|
3712
|
-
model: compactModel,
|
|
3713
|
-
usage: compacted.usage,
|
|
3714
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
3715
|
-
...(compacted.usage.webSearchRequests === undefined
|
|
3716
|
-
? {}
|
|
3717
|
-
: { webSearchRequests: compacted.usage.webSearchRequests }),
|
|
3718
|
-
};
|
|
3719
|
-
}
|
|
3720
|
-
let meteringDurationsInput;
|
|
3721
|
-
if (compactedDurationMs > 0 ||
|
|
3722
|
-
compactedDurationWithoutRetriesMs > 0) {
|
|
3723
|
-
meteringDurationsInput = {
|
|
3724
|
-
...(compactedDurationMs === 0
|
|
3725
|
-
? {}
|
|
3726
|
-
: { apiDurationMs: compactedDurationMs }),
|
|
3727
|
-
apiDurationWithoutRetriesMs: compactedDurationWithoutRetriesMs,
|
|
3728
|
-
};
|
|
3729
|
-
}
|
|
3730
|
-
if (meteringTurnInput !== undefined ||
|
|
3731
|
-
meteringDurationsInput !== undefined) {
|
|
3732
|
-
const preflight = new ClaudeSessionCostTracker({
|
|
3733
|
-
sessionId,
|
|
3734
|
-
restored: tracker.snapshot(),
|
|
3735
|
-
});
|
|
3736
|
-
if (meteringTurnInput !== undefined) {
|
|
3737
|
-
preflight.recordTurn(meteringTurnInput);
|
|
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}`);
|
|
3738
3799
|
}
|
|
3739
|
-
|
|
3740
|
-
|
|
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');
|
|
3741
3806
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
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 }),
|
|
3820
|
+
};
|
|
3756
3821
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
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
|
+
},
|
|
3760
3912
|
};
|
|
3761
|
-
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
// The boundary is durable: mirror Claude's full-compact behavior by
|
|
3765
|
-
// rerunning SessionStart with source compact and refreshing the
|
|
3766
|
-
// runtime-only context so the next request retains current
|
|
3767
|
-
// instructions, plan state, session memory, and hook context.
|
|
3768
|
-
if (this.options.hooks) {
|
|
3769
|
-
const outcome = await this.hookLifecycle.refresh(sessionId, hookSession, signal);
|
|
3770
|
-
if (outcome)
|
|
3771
|
-
await recordHookOutcome(outcome);
|
|
3772
|
-
}
|
|
3773
|
-
this.options.contextAssembler?.invalidate?.({
|
|
3774
|
-
lifecycleId: sessionId,
|
|
3775
|
-
reason: 'compact',
|
|
3776
|
-
});
|
|
3777
|
-
this.options.projectMemoryRecall?.recordCompact(sessionId);
|
|
3778
|
-
await refreshRuntimeContext();
|
|
3779
|
-
this.options.eventSink?.({
|
|
3780
|
-
type: 'compact-boundary',
|
|
3781
|
-
trigger: 'auto',
|
|
3782
|
-
preTokens,
|
|
3783
|
-
uuid: boundaryUuid,
|
|
3784
|
-
});
|
|
3785
|
-
compactionAnchorUuid = compactSummaryUuid;
|
|
3786
|
-
compactionUsage = mergeUsage(compactionUsage, compacted.usage);
|
|
3787
|
-
compactionDurationMs = proposedCompactionDurationMs;
|
|
3788
|
-
compactionDurationWithoutRetriesMs =
|
|
3789
|
-
proposedCompactionDurationWithoutRetriesMs;
|
|
3790
|
-
if (meteringTurnInput !== undefined) {
|
|
3791
|
-
compactionModelUsage = mergeSessionRawModelUsage(compactionModelUsage, { [meteringTurnInput.model]: compacted.usage });
|
|
3792
|
-
}
|
|
3793
|
-
// Commit the preflighted mutations once the boundary is durable so a
|
|
3794
|
-
// later main-provider failure or cancellation cannot lose the
|
|
3795
|
-
// compactor's usage/cost/API durations.
|
|
3796
|
-
if (meteringTurnInput !== undefined) {
|
|
3797
|
-
tracker.recordTurn(meteringTurnInput);
|
|
3798
|
-
}
|
|
3799
|
-
if (meteringDurationsInput !== undefined) {
|
|
3800
|
-
tracker.recordDurations(meteringDurationsInput);
|
|
3801
|
-
}
|
|
3802
|
-
};
|
|
3803
|
-
await compactIfNeeded(pendingUserMessages);
|
|
3913
|
+
},
|
|
3914
|
+
});
|
|
3915
|
+
await contextEngine.prepare(contextTransitionPort(pendingUserMessages), signal);
|
|
3804
3916
|
for (const [index, message] of expandedMessages.entries()) {
|
|
3805
3917
|
if (shellCommand !== undefined)
|
|
3806
3918
|
break;
|
|
@@ -3936,7 +4048,7 @@ export class ClaudeSessionService {
|
|
|
3936
4048
|
});
|
|
3937
4049
|
}
|
|
3938
4050
|
if (budget) {
|
|
3939
|
-
await
|
|
4051
|
+
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
3940
4052
|
budget.assertFits(budget.evaluate([
|
|
3941
4053
|
...contextMessages,
|
|
3942
4054
|
...injectTurnContext(projectClaudeModelMessages(snapshot.entries)),
|
|
@@ -3967,13 +4079,13 @@ export class ClaudeSessionService {
|
|
|
3967
4079
|
? { deferFailureKinds: ['prompt_too_long'] }
|
|
3968
4080
|
: {}),
|
|
3969
4081
|
reloadMessages: async () => {
|
|
3970
|
-
const recalled =
|
|
4082
|
+
const recalled = turnMemory.consumeRecall();
|
|
3971
4083
|
if (recalled) {
|
|
3972
4084
|
projectMemoryRecallMessages = [
|
|
3973
4085
|
{ role: 'user', content: recalled.content },
|
|
3974
4086
|
];
|
|
3975
4087
|
}
|
|
3976
|
-
await
|
|
4088
|
+
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
3977
4089
|
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
3978
4090
|
return [
|
|
3979
4091
|
...contextMessages,
|
|
@@ -4089,24 +4201,8 @@ export class ClaudeSessionService {
|
|
|
4089
4201
|
catch (error) {
|
|
4090
4202
|
if (!budget || !isPromptTooLongError(error))
|
|
4091
4203
|
throw error;
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
throw error;
|
|
4095
|
-
}
|
|
4096
|
-
const beforeRecovery = budget.evaluate(runtimeRequest.messages, definitions);
|
|
4097
|
-
try {
|
|
4098
|
-
await compactIfNeeded([], currentTurnUserMessages ?? [], {
|
|
4099
|
-
promptTooLong: true,
|
|
4100
|
-
});
|
|
4101
|
-
}
|
|
4102
|
-
catch (compactionError) {
|
|
4103
|
-
if (signal?.aborted ||
|
|
4104
|
-
compactionError instanceof AgentRunCancelledError) {
|
|
4105
|
-
throw new AgentRunCancelledError();
|
|
4106
|
-
}
|
|
4107
|
-
// Compaction could not free the provider-bounded context; surface
|
|
4108
|
-
// the original prompt-too-long error rather than the compaction
|
|
4109
|
-
// failure.
|
|
4204
|
+
const recovery = await contextEngine.recover(error, contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4205
|
+
if (recovery.kind !== 'retry') {
|
|
4110
4206
|
surfaceExhaustedRecovery(error);
|
|
4111
4207
|
throw error;
|
|
4112
4208
|
}
|
|
@@ -4120,14 +4216,6 @@ export class ClaudeSessionService {
|
|
|
4120
4216
|
]),
|
|
4121
4217
|
];
|
|
4122
4218
|
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
4123
|
-
const afterRecovery = budget.evaluate(runtimeRequest.messages, definitions);
|
|
4124
|
-
if (recoveryPlanner.consumeReactiveRetry({
|
|
4125
|
-
beforeOccupancyTokens: beforeRecovery.occupancyTokens,
|
|
4126
|
-
afterOccupancyTokens: afterRecovery.occupancyTokens,
|
|
4127
|
-
}) !== 'reactive-retry') {
|
|
4128
|
-
surfaceExhaustedRecovery(error);
|
|
4129
|
-
throw error;
|
|
4130
|
-
}
|
|
4131
4219
|
runtimeRequest.deferFailureKinds = true;
|
|
4132
4220
|
try {
|
|
4133
4221
|
result = await attemptMainTurn();
|
|
@@ -4135,7 +4223,6 @@ export class ClaudeSessionService {
|
|
|
4135
4223
|
catch (retryError) {
|
|
4136
4224
|
// Exactly one reactive retry is consumed; fail deterministically
|
|
4137
4225
|
// and surface the original prompt-too-long error.
|
|
4138
|
-
recoveryPlanner.recordFailure();
|
|
4139
4226
|
if (signal?.aborted ||
|
|
4140
4227
|
retryError instanceof AgentRunCancelledError) {
|
|
4141
4228
|
throw new AgentRunCancelledError();
|
|
@@ -4160,16 +4247,6 @@ export class ClaudeSessionService {
|
|
|
4160
4247
|
}
|
|
4161
4248
|
throw error;
|
|
4162
4249
|
}
|
|
4163
|
-
recoveryPlanner.recordSuccess();
|
|
4164
|
-
const mainModel = provider.model !== undefined && provider.model.trim() !== ''
|
|
4165
|
-
? provider.model
|
|
4166
|
-
: undefined;
|
|
4167
|
-
const observedUsage = (mainModel !== undefined
|
|
4168
|
-
? result.modelUsage?.[mainModel]
|
|
4169
|
-
: undefined) ??
|
|
4170
|
-
Object.values(result.modelUsage ?? {})[0] ??
|
|
4171
|
-
result.usage;
|
|
4172
|
-
budget?.observeUsage(observedUsage, runtimeRequest.messages, definitions);
|
|
4173
4250
|
if (structuredCapture && structuredCapture.calls !== 1) {
|
|
4174
4251
|
throw new Error(`StructuredOutput must be called exactly once (received ${structuredCapture.calls})`);
|
|
4175
4252
|
}
|
|
@@ -4270,32 +4347,23 @@ export class ClaudeSessionService {
|
|
|
4270
4347
|
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
4271
4348
|
});
|
|
4272
4349
|
}
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
this.options.eventSink?.({
|
|
4290
|
-
type: 'warning',
|
|
4291
|
-
message: `Session memory observation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
4292
|
-
});
|
|
4293
|
-
}
|
|
4294
|
-
}
|
|
4295
|
-
this.options.projectMemoryExtraction?.observe({
|
|
4296
|
-
sessionId,
|
|
4350
|
+
const memorySnapshot = projectClaudeModelMessages(snapshot.entries);
|
|
4351
|
+
const providerVisibleMessages = [
|
|
4352
|
+
...contextMessages,
|
|
4353
|
+
...injectTurnContext(memorySnapshot),
|
|
4354
|
+
];
|
|
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),
|
|
4297
4366
|
directMaintenance: projectMemoryMaintained,
|
|
4298
|
-
messages: projectMemoryMessages(snapshot.entries),
|
|
4299
4367
|
});
|
|
4300
4368
|
turnCompleted = true;
|
|
4301
4369
|
return {
|
|
@@ -4425,11 +4493,12 @@ export class ClaudeSessionService {
|
|
|
4425
4493
|
* recent suffix. Returns null to fall back to the existing full-compaction
|
|
4426
4494
|
* behavior (missing/invalid watermark, empty projection, or oversized
|
|
4427
4495
|
* projection). */
|
|
4428
|
-
async selectMemoryPreservedCompact(sessionId, activeEntries) {
|
|
4496
|
+
async selectMemoryPreservedCompact(sessionId, activeEntries, options = {}) {
|
|
4429
4497
|
const controller = this.sessionMemoryController(sessionId);
|
|
4430
4498
|
if (controller === null)
|
|
4431
4499
|
return null;
|
|
4432
|
-
|
|
4500
|
+
if (options.sessionMemoryReady !== true)
|
|
4501
|
+
await controller.waitForCompact();
|
|
4433
4502
|
let watermark = null;
|
|
4434
4503
|
let memorySummary = '';
|
|
4435
4504
|
try {
|
|
@@ -5061,12 +5130,37 @@ export class ClaudeSessionService {
|
|
|
5061
5130
|
return 'corrupt';
|
|
5062
5131
|
return this.schema.writeMode === 'read-write' ? 'ready' : 'read-only';
|
|
5063
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
|
+
}
|
|
5064
5151
|
provider() {
|
|
5065
5152
|
if (!this.options.provider) {
|
|
5066
5153
|
throw new Error('A model provider is required for run and resume');
|
|
5067
5154
|
}
|
|
5068
5155
|
return this.options.provider;
|
|
5069
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
|
+
}
|
|
5070
5164
|
model() {
|
|
5071
5165
|
return this.activeProvider?.model ?? this.options.provider?.model;
|
|
5072
5166
|
}
|
|
@@ -5124,7 +5218,7 @@ export class ClaudeSessionService {
|
|
|
5124
5218
|
return this.sessionMemoryProviderFactory() !== null;
|
|
5125
5219
|
}
|
|
5126
5220
|
sessionMemoryController(sessionId) {
|
|
5127
|
-
if (!this.sessionMemoryEnabled() || !
|
|
5221
|
+
if (!this.sessionMemoryEnabled() || !isSessionId(sessionId)) {
|
|
5128
5222
|
return null;
|
|
5129
5223
|
}
|
|
5130
5224
|
let controller = this.sessionMemoryControllers.get(sessionId);
|