praxis-agent 0.30.0 → 0.31.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/dist/application/claude-project-purge.js +18 -5
- package/dist/application/project-memory.d.ts +141 -0
- package/dist/application/project-memory.js +607 -0
- package/dist/application/session-service.d.ts +5 -0
- package/dist/application/session-service.js +130 -12
- package/dist/cli/interactive.js +21 -3
- package/dist/cli/protocol.d.ts +1 -0
- package/dist/cli/protocol.js +24 -0
- package/dist/cli/tui/memory-files.d.ts +2 -1
- package/dist/cli/tui/memory-files.js +30 -30
- package/dist/cli-runtime.js +126 -24
- package/dist/compatibility/claude/context.js +2 -1
- package/dist/compatibility/claude/dynamic-context.js +2 -1
- package/dist/compatibility/claude/paths.js +2 -13
- package/dist/compatibility/claude/shared-resources.d.ts +4 -4
- package/dist/compatibility/claude/shared-resources.js +14 -35
- package/dist/core/context-budget.d.ts +9 -8
- package/dist/core/context-budget.js +16 -21
- package/dist/core/project-memory.d.ts +19 -0
- package/dist/core/project-memory.js +58 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +38 -2
- package/dist/maintenance/doctor.js +29 -5
- package/dist/mcp/claude-mcp-management.js +2 -0
- package/dist/persistence/native-resources.d.ts +3 -1
- package/dist/persistence/native-resources.js +19 -6
- package/dist/platform/path-containment.d.ts +2 -0
- package/dist/platform/path-containment.js +7 -0
- package/dist/platform/project-identity.d.ts +6 -0
- package/dist/platform/project-identity.js +66 -0
- package/dist/platform/project-memory-paths.d.ts +13 -0
- package/dist/platform/project-memory-paths.js +28 -0
- package/dist/platform/project-path-key.d.ts +2 -0
- package/dist/platform/project-path-key.js +16 -0
- package/dist/providers/fallback-provider.js +3 -1
- package/package.json +1 -1
package/dist/cli-runtime.js
CHANGED
|
@@ -7,6 +7,9 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { ClaudeSessionService, } from './application/session-service.js';
|
|
10
|
+
import { ProjectMemoryAgentExtractor, ProjectMemoryExtractionController, ProjectMemoryModelSelector, ProjectMemoryRecallController, } from './application/project-memory.js';
|
|
11
|
+
import { resolveProjectMemoryPolicy } from './core/project-memory.js';
|
|
12
|
+
import { resolveProjectMemoryDirectory, resolveProjectMemoryStatePath, } from './platform/project-memory-paths.js';
|
|
10
13
|
import { ClaudeCostStateStore } from './persistence/claude-cost-state-store.js';
|
|
11
14
|
import { agentColorMessage, parseAgentColorInput, } from './compatibility/claude/agent-color.js';
|
|
12
15
|
import { ClaudeConditionalRuleResolver, ClaudeContextAssembler, } from './compatibility/claude/context.js';
|
|
@@ -16,7 +19,7 @@ import { isClaudeSessionId, resolveClaudePaths, } from './compatibility/claude/p
|
|
|
16
19
|
import { resolveDataPlane, resolveDataPlaneRoot, resolveDataPlanePaths, } from './persistence/data-plane.js';
|
|
17
20
|
import { loadNativeContextResources, loadNativeSettings, loadNativeSharedResources, } from './persistence/native-resources.js';
|
|
18
21
|
import { migrateClaudeData } from './persistence/claude-migration.js';
|
|
19
|
-
import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectIdentity,
|
|
22
|
+
import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectIdentity, } from './compatibility/claude/shared-resources.js';
|
|
20
23
|
import { AgentRunCancelledError, } from './core/runtime.js';
|
|
21
24
|
import { persistTuiPermissionUpdates } from './cli/tui/permission-settings.js';
|
|
22
25
|
import { loadClaudeReleaseNotes } from './cli/tui/release-notes.js';
|
|
@@ -1011,9 +1014,47 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1011
1014
|
: toolProvider;
|
|
1012
1015
|
const automaticSettingSources = cli.safeMode || simpleMode ? [] : cli.settingSources;
|
|
1013
1016
|
const nativeSharedResourcesEnabled = !cli.safeMode && !simpleMode;
|
|
1017
|
+
const baseSettings = dataPlane === 'native'
|
|
1018
|
+
? nativeSharedResourcesEnabled
|
|
1019
|
+
? await loadNativeSettings({ root: configRoot, cwd })
|
|
1020
|
+
: []
|
|
1021
|
+
: await loadClaudeSettings({
|
|
1022
|
+
configRoot,
|
|
1023
|
+
cwd,
|
|
1024
|
+
...(automaticSettingSources === undefined
|
|
1025
|
+
? {}
|
|
1026
|
+
: { settingSources: automaticSettingSources }),
|
|
1027
|
+
});
|
|
1028
|
+
const pluginResources = await loadClaudePlugins({
|
|
1029
|
+
configRoot,
|
|
1030
|
+
dataPlane,
|
|
1031
|
+
cwd,
|
|
1032
|
+
pluginDirectories: cli.pluginDirectories,
|
|
1033
|
+
pluginUrls: cli.pluginUrls,
|
|
1034
|
+
strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
|
|
1035
|
+
loadInstalled: !cli.safeMode && !simpleMode,
|
|
1036
|
+
environment: runtimeEnvironment,
|
|
1037
|
+
});
|
|
1038
|
+
const projectMemoryPolicy = cli.safeMode || simpleMode
|
|
1039
|
+
? { enabled: false, extraction: false, recall: false }
|
|
1040
|
+
: resolveProjectMemoryPolicy({
|
|
1041
|
+
dataPlane,
|
|
1042
|
+
settings: [
|
|
1043
|
+
...baseSettings,
|
|
1044
|
+
...pluginResources.settings,
|
|
1045
|
+
...(cli.additionalSettings ? [cli.additionalSettings] : []),
|
|
1046
|
+
],
|
|
1047
|
+
environment: runtimeEnvironment,
|
|
1048
|
+
});
|
|
1049
|
+
const includeProjectMemory = projectMemoryPolicy.enabled && !projectMemoryPolicy.recall;
|
|
1014
1050
|
const loadedResources = dataPlane === 'native'
|
|
1015
1051
|
? nativeSharedResourcesEnabled
|
|
1016
|
-
? await loadNativeSharedResources({
|
|
1052
|
+
? await loadNativeSharedResources({
|
|
1053
|
+
root: configRoot,
|
|
1054
|
+
cwd,
|
|
1055
|
+
environment: runtimeEnvironment,
|
|
1056
|
+
includeProjectMemory,
|
|
1057
|
+
})
|
|
1017
1058
|
: {
|
|
1018
1059
|
instructions: [],
|
|
1019
1060
|
memory: [],
|
|
@@ -1027,20 +1068,11 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1027
1068
|
configRoot,
|
|
1028
1069
|
cwd,
|
|
1029
1070
|
claudeStatePath,
|
|
1071
|
+
includeProjectMemory,
|
|
1030
1072
|
...(automaticSettingSources === undefined
|
|
1031
1073
|
? {}
|
|
1032
1074
|
: { settingSources: automaticSettingSources }),
|
|
1033
1075
|
});
|
|
1034
|
-
const pluginResources = await loadClaudePlugins({
|
|
1035
|
-
configRoot,
|
|
1036
|
-
dataPlane,
|
|
1037
|
-
cwd,
|
|
1038
|
-
pluginDirectories: cli.pluginDirectories,
|
|
1039
|
-
pluginUrls: cli.pluginUrls,
|
|
1040
|
-
strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
|
|
1041
|
-
loadInstalled: !cli.safeMode && !simpleMode,
|
|
1042
|
-
environment: runtimeEnvironment,
|
|
1043
|
-
});
|
|
1044
1076
|
const settings = [
|
|
1045
1077
|
...loadedResources.settings,
|
|
1046
1078
|
...pluginResources.settings,
|
|
@@ -1081,21 +1113,53 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1081
1113
|
disableSlashCommands: cli.disableSlashCommands,
|
|
1082
1114
|
dataPlane,
|
|
1083
1115
|
});
|
|
1084
|
-
const memoryDirectory =
|
|
1116
|
+
const memoryDirectory = !projectMemoryPolicy.enabled
|
|
1085
1117
|
? undefined
|
|
1086
|
-
:
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
sessionId: randomUUID(),
|
|
1092
|
-
}).memoryRoot
|
|
1093
|
-
: await resolveClaudeProjectMemoryDirectory({ configRoot, cwd });
|
|
1118
|
+
: await resolveProjectMemoryDirectory({
|
|
1119
|
+
dataPlane,
|
|
1120
|
+
configRoot,
|
|
1121
|
+
cwd,
|
|
1122
|
+
});
|
|
1094
1123
|
if (memoryDirectory)
|
|
1095
1124
|
await mkdir(memoryDirectory, { recursive: true });
|
|
1125
|
+
const projectMemoryProviderFactory = providerForMainModel && model
|
|
1126
|
+
? () => providerForMainModel(model)
|
|
1127
|
+
: undefined;
|
|
1128
|
+
const projectMemoryRecall = projectMemoryPolicy.recall &&
|
|
1129
|
+
memoryDirectory &&
|
|
1130
|
+
projectMemoryProviderFactory
|
|
1131
|
+
? new ProjectMemoryRecallController({
|
|
1132
|
+
directory: memoryDirectory,
|
|
1133
|
+
selector: new ProjectMemoryModelSelector({
|
|
1134
|
+
directory: memoryDirectory,
|
|
1135
|
+
providerFactory: projectMemoryProviderFactory,
|
|
1136
|
+
}),
|
|
1137
|
+
})
|
|
1138
|
+
: undefined;
|
|
1139
|
+
const projectMemoryExtraction = projectMemoryPolicy.extraction &&
|
|
1140
|
+
memoryDirectory &&
|
|
1141
|
+
projectMemoryProviderFactory
|
|
1142
|
+
? new ProjectMemoryExtractionController({
|
|
1143
|
+
directory: memoryDirectory,
|
|
1144
|
+
cursorPath: resolveProjectMemoryStatePath({
|
|
1145
|
+
dataPlane,
|
|
1146
|
+
configRoot,
|
|
1147
|
+
memoryDirectory,
|
|
1148
|
+
}),
|
|
1149
|
+
extractor: new ProjectMemoryAgentExtractor({
|
|
1150
|
+
providerFactory: projectMemoryProviderFactory,
|
|
1151
|
+
}),
|
|
1152
|
+
onWarning: (message) => runtimeEventSink({ type: 'warning', message }),
|
|
1153
|
+
})
|
|
1154
|
+
: undefined;
|
|
1096
1155
|
const loadContextResources = (runtimeCwd = workspace.cwd()) => dataPlane === 'native'
|
|
1097
1156
|
? nativeSharedResourcesEnabled
|
|
1098
|
-
? loadNativeContextResources({
|
|
1157
|
+
? loadNativeContextResources({
|
|
1158
|
+
root: configRoot,
|
|
1159
|
+
cwd: runtimeCwd,
|
|
1160
|
+
environment: runtimeEnvironment,
|
|
1161
|
+
includeProjectMemory,
|
|
1162
|
+
})
|
|
1099
1163
|
: Promise.resolve({
|
|
1100
1164
|
instructions: [],
|
|
1101
1165
|
conditionalRules: [],
|
|
@@ -1104,6 +1168,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1104
1168
|
: loadClaudeContextResources({
|
|
1105
1169
|
configRoot,
|
|
1106
1170
|
cwd: runtimeCwd,
|
|
1171
|
+
includeProjectMemory,
|
|
1107
1172
|
...(automaticSettingSources === undefined
|
|
1108
1173
|
? {}
|
|
1109
1174
|
: { settingSources: automaticSettingSources }),
|
|
@@ -1200,6 +1265,8 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1200
1265
|
? await loadNativeSharedResources({
|
|
1201
1266
|
root: configRoot,
|
|
1202
1267
|
cwd: workspace.cwd(),
|
|
1268
|
+
environment: runtimeEnvironment,
|
|
1269
|
+
includeProjectMemory,
|
|
1203
1270
|
})
|
|
1204
1271
|
: {
|
|
1205
1272
|
instructions: [],
|
|
@@ -1214,6 +1281,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1214
1281
|
configRoot,
|
|
1215
1282
|
cwd: workspace.cwd(),
|
|
1216
1283
|
claudeStatePath,
|
|
1284
|
+
includeProjectMemory,
|
|
1217
1285
|
...(automaticSettingSources === undefined
|
|
1218
1286
|
? {}
|
|
1219
1287
|
: { settingSources: automaticSettingSources }),
|
|
@@ -1467,6 +1535,9 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1467
1535
|
...(interactiveTools ? { interactiveTools } : {}),
|
|
1468
1536
|
...(hooks ? { hooks } : {}),
|
|
1469
1537
|
...(selectedMainAgent ? { agent: selectedMainAgent } : {}),
|
|
1538
|
+
...(memoryDirectory ? { projectMemoryDirectory: memoryDirectory } : {}),
|
|
1539
|
+
...(projectMemoryRecall ? { projectMemoryRecall } : {}),
|
|
1540
|
+
...(projectMemoryExtraction ? { projectMemoryExtraction } : {}),
|
|
1470
1541
|
contextAssembler,
|
|
1471
1542
|
conditionalRuleResolver: new ClaudeConditionalRuleResolver({
|
|
1472
1543
|
loadResources: loadContextResources,
|
|
@@ -3544,9 +3615,40 @@ function eventSink(io, outputFormat, legacyJson = false) {
|
|
|
3544
3615
|
}
|
|
3545
3616
|
if (outputFormat !== 'text')
|
|
3546
3617
|
return () => undefined;
|
|
3618
|
+
let turnBuffered = false;
|
|
3619
|
+
let bufferedText = '';
|
|
3620
|
+
const flushBufferedText = () => {
|
|
3621
|
+
if (bufferedText.length > 0)
|
|
3622
|
+
io.stdout(bufferedText);
|
|
3623
|
+
bufferedText = '';
|
|
3624
|
+
};
|
|
3547
3625
|
return (event) => {
|
|
3548
|
-
if (event.type === '
|
|
3549
|
-
|
|
3626
|
+
if (event.type === 'state' && event.state === 'awaiting-model') {
|
|
3627
|
+
turnBuffered = true;
|
|
3628
|
+
bufferedText = '';
|
|
3629
|
+
}
|
|
3630
|
+
if (event.type === 'text-delta') {
|
|
3631
|
+
if (turnBuffered)
|
|
3632
|
+
bufferedText += event.delta;
|
|
3633
|
+
else
|
|
3634
|
+
io.stdout(event.delta);
|
|
3635
|
+
}
|
|
3636
|
+
if (event.type === 'model-attempt-discarded')
|
|
3637
|
+
bufferedText = '';
|
|
3638
|
+
if (event.type === 'terminal' &&
|
|
3639
|
+
event.reason !== 'prompt_too_long' &&
|
|
3640
|
+
turnBuffered) {
|
|
3641
|
+
flushBufferedText();
|
|
3642
|
+
turnBuffered = false;
|
|
3643
|
+
}
|
|
3644
|
+
if (event.type === 'state' && event.state === 'completed' && turnBuffered) {
|
|
3645
|
+
flushBufferedText();
|
|
3646
|
+
turnBuffered = false;
|
|
3647
|
+
}
|
|
3648
|
+
if (event.type === 'failed') {
|
|
3649
|
+
bufferedText = '';
|
|
3650
|
+
turnBuffered = false;
|
|
3651
|
+
}
|
|
3550
3652
|
if (event.type === 'user-message')
|
|
3551
3653
|
io.stdout(`\n${event.message}\n`);
|
|
3552
3654
|
if (event.type === 'warning') {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isAbsolute, matchesGlob, relative, resolve, sep } from 'node:path';
|
|
2
2
|
import { PromptComposer, } from '../../core/prompt-composer.js';
|
|
3
|
+
import { boundProjectMemoryIndex } from '../../core/project-memory.js';
|
|
3
4
|
import { renderClaudeDynamicSystemContext, renderClaudeDynamicUserContext, } from './dynamic-context.js';
|
|
4
5
|
function renderResources(title, resources) {
|
|
5
6
|
const rendered = resources
|
|
@@ -12,7 +13,7 @@ function renderResources(title, resources) {
|
|
|
12
13
|
function limitMemoryIndex(resource) {
|
|
13
14
|
return {
|
|
14
15
|
...resource,
|
|
15
|
-
content: resource.content
|
|
16
|
+
content: boundProjectMemoryIndex(resource.content),
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
export class ClaudeConditionalRuleResolver {
|
|
@@ -145,7 +145,8 @@ export async function loadClaudeDynamicContext(options) {
|
|
|
145
145
|
? [
|
|
146
146
|
'# Memory',
|
|
147
147
|
`Persistent project memory is stored at \`${options.memoryDirectory}\`.`,
|
|
148
|
-
'Use MEMORY.md as
|
|
148
|
+
'Use MEMORY.md as a concise index of one-line links. Keep detailed durable facts in linked Markdown topic files with name, description, and a type of user, feedback, project, or reference. Update existing entries instead of duplicating them.',
|
|
149
|
+
'Do not store codebase architecture, implementation patterns, git history, fix recipes, transient task or conversation state, or duplicates of repository instructions.',
|
|
149
150
|
].join('\n')
|
|
150
151
|
: undefined;
|
|
151
152
|
return {
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
import { lstat, readdir } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { extname, resolve } from 'node:path';
|
|
4
|
+
import { sanitizeProjectPath } from '../../platform/project-path-key.js';
|
|
4
5
|
import { getDataOwnership } from './ownership.js';
|
|
5
6
|
const MAX_SANITIZED_LENGTH = 200;
|
|
6
7
|
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7
8
|
export function isClaudeSessionId(value) {
|
|
8
9
|
return SESSION_ID_PATTERN.test(value);
|
|
9
10
|
}
|
|
10
|
-
function stablePathHash(value) {
|
|
11
|
-
let hash = 0;
|
|
12
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
13
|
-
hash = Math.imul(hash, 31) + value.charCodeAt(index);
|
|
14
|
-
hash |= 0;
|
|
15
|
-
}
|
|
16
|
-
return Math.abs(hash);
|
|
17
|
-
}
|
|
18
11
|
export function sanitizeClaudeProjectPath(path) {
|
|
19
|
-
|
|
20
|
-
if (sanitized.length <= MAX_SANITIZED_LENGTH) {
|
|
21
|
-
return sanitized;
|
|
22
|
-
}
|
|
23
|
-
return `${sanitized.slice(0, MAX_SANITIZED_LENGTH)}-${stablePathHash(path).toString(36)}`;
|
|
12
|
+
return sanitizeProjectPath(path);
|
|
24
13
|
}
|
|
25
14
|
async function isDirectory(path) {
|
|
26
15
|
try {
|
|
@@ -42,17 +42,17 @@ export interface LoadClaudeSharedResourcesOptions {
|
|
|
42
42
|
homeDirectory?: string;
|
|
43
43
|
claudeStatePath?: string;
|
|
44
44
|
settingSources?: readonly ClaudeResourceScope[];
|
|
45
|
+
includeProjectMemory?: boolean;
|
|
45
46
|
}
|
|
46
47
|
export declare function resolveClaudeInstructionImports(resources: readonly ClaudeTextResource[], { homeDirectory }?: {
|
|
47
48
|
homeDirectory?: string;
|
|
48
49
|
}): Promise<ClaudeTextResource[]>;
|
|
49
|
-
export declare function resolveClaudeProjectIdentity({ cwd,
|
|
50
|
+
export declare function resolveClaudeProjectIdentity({ cwd, }: {
|
|
50
51
|
cwd: string;
|
|
51
|
-
homeDirectory?: string;
|
|
52
52
|
}): Promise<string>;
|
|
53
53
|
export declare function loadClaudeSettings({ configRoot, cwd, settingSources, }: LoadClaudeSharedResourcesOptions): Promise<ClaudeJsonResource[]>;
|
|
54
54
|
export declare function resolveClaudeProjectMemoryDirectory({ configRoot, cwd, homeDirectory, }: LoadClaudeSharedResourcesOptions): Promise<string>;
|
|
55
55
|
export declare function loadClaudeMcpResources({ configRoot, cwd, homeDirectory, claudeStatePath, settingSources, }: LoadClaudeSharedResourcesOptions): Promise<ClaudeJsonResource[]>;
|
|
56
|
-
export declare function loadClaudeContextResources({ configRoot, cwd, homeDirectory, settingSources, }: LoadClaudeSharedResourcesOptions): Promise<ClaudeContextResources>;
|
|
57
|
-
export declare function loadClaudeSharedResources({ configRoot, cwd, homeDirectory, claudeStatePath, settingSources, }: LoadClaudeSharedResourcesOptions): Promise<ClaudeSharedResources>;
|
|
56
|
+
export declare function loadClaudeContextResources({ configRoot, cwd, homeDirectory, settingSources, includeProjectMemory, }: LoadClaudeSharedResourcesOptions): Promise<ClaudeContextResources>;
|
|
57
|
+
export declare function loadClaudeSharedResources({ configRoot, cwd, homeDirectory, claudeStatePath, settingSources, includeProjectMemory, }: LoadClaudeSharedResourcesOptions): Promise<ClaudeSharedResources>;
|
|
58
58
|
//# sourceMappingURL=shared-resources.d.ts.map
|
|
@@ -2,6 +2,7 @@ import { readdir, readFile, realpath, stat } from 'node:fs/promises';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
import { resolveProjectIdentity } from '../../platform/project-identity.js';
|
|
5
6
|
import { sanitizeClaudeProjectPath } from './paths.js';
|
|
6
7
|
function selectedScope(scope, settingSources) {
|
|
7
8
|
return settingSources === undefined || settingSources.includes(scope);
|
|
@@ -255,32 +256,6 @@ async function findGitRoot(cwd) {
|
|
|
255
256
|
}
|
|
256
257
|
return current;
|
|
257
258
|
}
|
|
258
|
-
async function readOptionalRaw(path) {
|
|
259
|
-
try {
|
|
260
|
-
return await readFile(path, 'utf8');
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
if (error.code === 'ENOENT')
|
|
264
|
-
return null;
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
async function findMemoryIdentityRoot(gitRoot) {
|
|
269
|
-
const gitMarker = join(gitRoot, '.git');
|
|
270
|
-
const marker = await stat(gitMarker);
|
|
271
|
-
if (!marker.isFile())
|
|
272
|
-
return gitRoot;
|
|
273
|
-
const gitFile = await readFile(gitMarker, 'utf8');
|
|
274
|
-
const match = /^gitdir:\s*(.+)\s*$/m.exec(gitFile);
|
|
275
|
-
if (!match?.[1])
|
|
276
|
-
return gitRoot;
|
|
277
|
-
const gitDirectory = resolve(gitRoot, match[1]);
|
|
278
|
-
const commonDirectory = await readOptionalRaw(join(gitDirectory, 'commondir'));
|
|
279
|
-
if (!commonDirectory)
|
|
280
|
-
return gitRoot;
|
|
281
|
-
const commonGitDirectory = await canonicalPath(resolve(gitDirectory, commonDirectory.trim()));
|
|
282
|
-
return dirname(commonGitDirectory);
|
|
283
|
-
}
|
|
284
259
|
async function resolveProjectContext(cwd, homeDirectory) {
|
|
285
260
|
const canonicalCwd = await canonicalPath(cwd);
|
|
286
261
|
const gitRoot = await findGitRoot(canonicalCwd);
|
|
@@ -289,7 +264,7 @@ async function resolveProjectContext(cwd, homeDirectory) {
|
|
|
289
264
|
directories: directoriesFromRoot(gitRoot, canonicalCwd),
|
|
290
265
|
extensionDirectories: directoriesFromRoot(gitRoot, canonicalCwd),
|
|
291
266
|
homeBoundary: null,
|
|
292
|
-
memoryIdentityRoot: await
|
|
267
|
+
memoryIdentityRoot: await resolveProjectIdentity(gitRoot),
|
|
293
268
|
};
|
|
294
269
|
}
|
|
295
270
|
const canonicalHome = await canonicalPath(homeDirectory);
|
|
@@ -303,8 +278,8 @@ async function resolveProjectContext(cwd, homeDirectory) {
|
|
|
303
278
|
memoryIdentityRoot: canonicalCwd,
|
|
304
279
|
};
|
|
305
280
|
}
|
|
306
|
-
export async function resolveClaudeProjectIdentity({ cwd,
|
|
307
|
-
return (
|
|
281
|
+
export async function resolveClaudeProjectIdentity({ cwd, }) {
|
|
282
|
+
return resolveProjectIdentity(cwd);
|
|
308
283
|
}
|
|
309
284
|
async function loadProjectSettings(configRoot, cwd) {
|
|
310
285
|
const userSettings = await readOptionalJson(join(configRoot, 'settings.json'), 'user');
|
|
@@ -411,13 +386,15 @@ async function loadProjectContextResources(projectDirectories, homeBoundary) {
|
|
|
411
386
|
function present(value) {
|
|
412
387
|
return value !== null;
|
|
413
388
|
}
|
|
414
|
-
async function loadResolvedClaudeContext(configRoot, projectDirectories, homeBoundary, memoryIdentityRoot) {
|
|
389
|
+
async function loadResolvedClaudeContext(configRoot, projectDirectories, homeBoundary, memoryIdentityRoot, includeProjectMemory) {
|
|
415
390
|
const projectMemoryDirectory = join(configRoot, 'projects', sanitizeClaudeProjectPath(memoryIdentityRoot), 'memory');
|
|
416
391
|
const [globalInstruction, globalRules, projectContext, memoryIndex] = await Promise.all([
|
|
417
392
|
readOptionalText(join(configRoot, 'CLAUDE.md'), 'user'),
|
|
418
393
|
loadRuleResources(join(configRoot, 'rules'), 'user', projectDirectories.at(-1) ?? memoryIdentityRoot),
|
|
419
394
|
loadProjectContextResources(projectDirectories, homeBoundary),
|
|
420
|
-
|
|
395
|
+
includeProjectMemory
|
|
396
|
+
? readOptionalText(join(projectMemoryDirectory, 'MEMORY.md'), 'project')
|
|
397
|
+
: Promise.resolve(null),
|
|
421
398
|
]);
|
|
422
399
|
return {
|
|
423
400
|
instructions: [
|
|
@@ -432,9 +409,9 @@ async function loadResolvedClaudeContext(configRoot, projectDirectories, homeBou
|
|
|
432
409
|
memoryIndex,
|
|
433
410
|
};
|
|
434
411
|
}
|
|
435
|
-
export async function loadClaudeContextResources({ configRoot, cwd, homeDirectory = homedir(), settingSources, }) {
|
|
412
|
+
export async function loadClaudeContextResources({ configRoot, cwd, homeDirectory = homedir(), settingSources, includeProjectMemory = true, }) {
|
|
436
413
|
const { directories, homeBoundary, memoryIdentityRoot } = await resolveProjectContext(cwd, homeDirectory);
|
|
437
|
-
const resources = await loadResolvedClaudeContext(configRoot, directories, homeBoundary, memoryIdentityRoot);
|
|
414
|
+
const resources = await loadResolvedClaudeContext(configRoot, directories, homeBoundary, memoryIdentityRoot, includeProjectMemory);
|
|
438
415
|
const instructions = resources.instructions.filter((resource) => selectedScope(resource.scope, settingSources));
|
|
439
416
|
return {
|
|
440
417
|
instructions: await resolveClaudeInstructionImports(instructions, {
|
|
@@ -447,14 +424,16 @@ export async function loadClaudeContextResources({ configRoot, cwd, homeDirector
|
|
|
447
424
|
: null,
|
|
448
425
|
};
|
|
449
426
|
}
|
|
450
|
-
export async function loadClaudeSharedResources({ configRoot, cwd, homeDirectory = homedir(), claudeStatePath = join(configRoot, '.claude.json'), settingSources, }) {
|
|
427
|
+
export async function loadClaudeSharedResources({ configRoot, cwd, homeDirectory = homedir(), claudeStatePath = join(configRoot, '.claude.json'), settingSources, includeProjectMemory = true, }) {
|
|
451
428
|
const { directories: projectDirectories, extensionDirectories, homeBoundary, memoryIdentityRoot, } = await resolveProjectContext(cwd, homeDirectory);
|
|
452
429
|
const projectMemoryDirectory = join(configRoot, 'projects', sanitizeClaudeProjectPath(memoryIdentityRoot), 'memory');
|
|
453
430
|
const [globalInstruction, globalRules, projectInstructions, memory, skills, commands, agents, settings, mcp,] = await Promise.all([
|
|
454
431
|
readOptionalText(join(configRoot, 'CLAUDE.md'), 'user'),
|
|
455
432
|
loadAllRules(join(configRoot, 'rules'), 'user'),
|
|
456
433
|
loadProjectInstructions(projectDirectories, homeBoundary),
|
|
457
|
-
|
|
434
|
+
includeProjectMemory
|
|
435
|
+
? findFiles(projectMemoryDirectory, (name) => name.endsWith('.md')).then((paths) => loadFiles(paths, 'project'))
|
|
436
|
+
: Promise.resolve([]),
|
|
458
437
|
loadScopedFiles(join(configRoot, 'skills'), extensionDirectories.map((path) => join(path, '.claude', 'skills')), (name) => name === 'SKILL.md'),
|
|
459
438
|
loadScopedFiles(join(configRoot, 'commands'), extensionDirectories.map((path) => join(path, '.claude', 'commands')), (name) => name.endsWith('.md')),
|
|
460
439
|
loadScopedFiles(join(configRoot, 'agents'), extensionDirectories.map((path) => join(path, '.claude', 'agents')), (name) => name.endsWith('.md')),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ModelMessage, ModelToolDefinition, ModelUsage } from './runtime.js';
|
|
2
|
+
import { ModelProviderError } from './runtime.js';
|
|
2
3
|
export interface ContextBudgetOptions {
|
|
3
4
|
contextWindowTokens: number;
|
|
4
5
|
reserveTokens?: number;
|
|
@@ -76,15 +77,16 @@ export type ContextRecoveryStage = 'preflight' | 'microcompact' | 'auto-compact'
|
|
|
76
77
|
export interface ContextRecoveryPlannerOptions {
|
|
77
78
|
/** Number of reactive retries allowed before the planner reports `blocked`. */
|
|
78
79
|
maxReactiveRetries?: number;
|
|
79
|
-
/** Consecutive failures that trip the circuit breaker to `blocked`. */
|
|
80
|
-
consecutiveFailureThreshold?: number;
|
|
81
80
|
}
|
|
81
|
+
export interface ContextRecoveryProgress {
|
|
82
|
+
beforeOccupancyTokens: number;
|
|
83
|
+
afterOccupancyTokens: number;
|
|
84
|
+
}
|
|
85
|
+
export declare function contextRecoveryMadeProgress(progress: ContextRecoveryProgress): boolean;
|
|
82
86
|
export declare class ContextRecoveryPlanner {
|
|
83
87
|
private readonly maxReactiveRetries;
|
|
84
|
-
private readonly consecutiveFailureThreshold;
|
|
85
88
|
private currentStage;
|
|
86
89
|
private reactiveRetriesUsed;
|
|
87
|
-
private consecutiveFailures;
|
|
88
90
|
constructor(options?: ContextRecoveryPlannerOptions);
|
|
89
91
|
get stage(): ContextRecoveryStage;
|
|
90
92
|
get reactiveRetriesRemaining(): number;
|
|
@@ -93,14 +95,13 @@ export declare class ContextRecoveryPlanner {
|
|
|
93
95
|
advance(): ContextRecoveryStage;
|
|
94
96
|
/** Consume one reactive retry decision; reports `blocked` once the maximum
|
|
95
97
|
* reactive retries have been consumed. */
|
|
96
|
-
consumeReactiveRetry(): ContextRecoveryStage;
|
|
97
|
-
/**
|
|
98
|
-
* after `consecutiveFailureThreshold` consecutive failures. */
|
|
98
|
+
consumeReactiveRetry(progress: ContextRecoveryProgress): ContextRecoveryStage;
|
|
99
|
+
/** A failed reactive attempt exhausts the only selected context transition. */
|
|
99
100
|
recordFailure(): ContextRecoveryStage;
|
|
100
101
|
/** Record a successful recovery/model attempt; resets the planner. */
|
|
101
102
|
recordSuccess(): ContextRecoveryStage;
|
|
102
103
|
}
|
|
103
104
|
/** True when a provider error signals that the request exceeded the model's
|
|
104
105
|
* context window and a reactive compaction retry is the relevant recovery. */
|
|
105
|
-
export declare function isPromptTooLongError(error: unknown):
|
|
106
|
+
export declare function isPromptTooLongError(error: unknown): error is ModelProviderError;
|
|
106
107
|
//# sourceMappingURL=context-budget.d.ts.map
|
|
@@ -203,26 +203,24 @@ export class ContextBudget {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
const DEFAULT_MAX_REACTIVE_RETRIES = 1;
|
|
206
|
-
|
|
206
|
+
export function contextRecoveryMadeProgress(progress) {
|
|
207
|
+
for (const [name, value] of Object.entries(progress)) {
|
|
208
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
209
|
+
throw new Error(`${name} must be a nonnegative safe integer`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return progress.afterOccupancyTokens < progress.beforeOccupancyTokens;
|
|
213
|
+
}
|
|
207
214
|
export class ContextRecoveryPlanner {
|
|
208
215
|
maxReactiveRetries;
|
|
209
|
-
consecutiveFailureThreshold;
|
|
210
216
|
currentStage = 'preflight';
|
|
211
217
|
reactiveRetriesUsed = 0;
|
|
212
|
-
consecutiveFailures = 0;
|
|
213
218
|
constructor(options = {}) {
|
|
214
219
|
const maxReactiveRetries = options.maxReactiveRetries ?? DEFAULT_MAX_REACTIVE_RETRIES;
|
|
215
|
-
const consecutiveFailureThreshold = options.consecutiveFailureThreshold ??
|
|
216
|
-
DEFAULT_CONSECUTIVE_FAILURE_THRESHOLD;
|
|
217
220
|
if (!Number.isInteger(maxReactiveRetries) || maxReactiveRetries < 0) {
|
|
218
221
|
throw new Error('maxReactiveRetries must be a nonnegative integer');
|
|
219
222
|
}
|
|
220
|
-
if (!Number.isInteger(consecutiveFailureThreshold) ||
|
|
221
|
-
consecutiveFailureThreshold < 1) {
|
|
222
|
-
throw new Error('consecutiveFailureThreshold must be a positive integer');
|
|
223
|
-
}
|
|
224
223
|
this.maxReactiveRetries = maxReactiveRetries;
|
|
225
|
-
this.consecutiveFailureThreshold = consecutiveFailureThreshold;
|
|
226
224
|
}
|
|
227
225
|
get stage() {
|
|
228
226
|
return this.currentStage;
|
|
@@ -253,7 +251,11 @@ export class ContextRecoveryPlanner {
|
|
|
253
251
|
}
|
|
254
252
|
/** Consume one reactive retry decision; reports `blocked` once the maximum
|
|
255
253
|
* reactive retries have been consumed. */
|
|
256
|
-
consumeReactiveRetry() {
|
|
254
|
+
consumeReactiveRetry(progress) {
|
|
255
|
+
if (!contextRecoveryMadeProgress(progress)) {
|
|
256
|
+
this.currentStage = 'blocked';
|
|
257
|
+
return this.currentStage;
|
|
258
|
+
}
|
|
257
259
|
this.reactiveRetriesUsed += 1;
|
|
258
260
|
this.currentStage =
|
|
259
261
|
this.reactiveRetriesUsed <= this.maxReactiveRetries
|
|
@@ -261,28 +263,21 @@ export class ContextRecoveryPlanner {
|
|
|
261
263
|
: 'blocked';
|
|
262
264
|
return this.currentStage;
|
|
263
265
|
}
|
|
264
|
-
/**
|
|
265
|
-
* after `consecutiveFailureThreshold` consecutive failures. */
|
|
266
|
+
/** A failed reactive attempt exhausts the only selected context transition. */
|
|
266
267
|
recordFailure() {
|
|
267
|
-
this.
|
|
268
|
-
if (this.consecutiveFailures >= this.consecutiveFailureThreshold) {
|
|
269
|
-
this.currentStage = 'blocked';
|
|
270
|
-
}
|
|
268
|
+
this.currentStage = 'blocked';
|
|
271
269
|
return this.currentStage;
|
|
272
270
|
}
|
|
273
271
|
/** Record a successful recovery/model attempt; resets the planner. */
|
|
274
272
|
recordSuccess() {
|
|
275
|
-
this.consecutiveFailures = 0;
|
|
276
273
|
this.reactiveRetriesUsed = 0;
|
|
277
274
|
this.currentStage = 'preflight';
|
|
278
275
|
return this.currentStage;
|
|
279
276
|
}
|
|
280
277
|
}
|
|
281
|
-
const PROMPT_TOO_LONG_PATTERN = /context(?:[_\s-]?length|[_\s-]?window)|prompt(?:[_\s-]?is)?[_\s-]?too[_\s-]?long|too[_\s-]?many[_\s-]?tokens|maximum[_\s-]?context/iu;
|
|
282
278
|
/** True when a provider error signals that the request exceeded the model's
|
|
283
279
|
* context window and a reactive compaction retry is the relevant recovery. */
|
|
284
280
|
export function isPromptTooLongError(error) {
|
|
285
|
-
return
|
|
286
|
-
PROMPT_TOO_LONG_PATTERN.test(error.message));
|
|
281
|
+
return error instanceof ModelProviderError && error.kind === 'prompt_too_long';
|
|
287
282
|
}
|
|
288
283
|
//# sourceMappingURL=context-budget.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const PROJECT_MEMORY_INDEX_MAX_LINES = 200;
|
|
2
|
+
export declare const PROJECT_MEMORY_INDEX_MAX_BYTES: number;
|
|
3
|
+
export type ProjectMemoryDataPlane = 'native' | 'claude';
|
|
4
|
+
export interface ProjectMemoryPolicy {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
extraction: boolean;
|
|
7
|
+
recall: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ResolveProjectMemoryPolicyOptions {
|
|
10
|
+
dataPlane: ProjectMemoryDataPlane;
|
|
11
|
+
settings: readonly {
|
|
12
|
+
value: unknown;
|
|
13
|
+
}[];
|
|
14
|
+
environment: Readonly<Record<string, string | undefined>>;
|
|
15
|
+
}
|
|
16
|
+
export declare function boundProjectMemoryText(source: string, maxLines: number, maxBytes: number): string;
|
|
17
|
+
export declare function boundProjectMemoryIndex(source: string): string;
|
|
18
|
+
export declare function resolveProjectMemoryPolicy({ dataPlane, settings, environment, }: ResolveProjectMemoryPolicyOptions): ProjectMemoryPolicy;
|
|
19
|
+
//# sourceMappingURL=project-memory.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const PROJECT_MEMORY_INDEX_MAX_LINES = 200;
|
|
2
|
+
export const PROJECT_MEMORY_INDEX_MAX_BYTES = 25 * 1024;
|
|
3
|
+
function record(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
+
? value
|
|
6
|
+
: null;
|
|
7
|
+
}
|
|
8
|
+
function utf8Prefix(source, maxBytes) {
|
|
9
|
+
const buffer = Buffer.from(source, 'utf8');
|
|
10
|
+
if (buffer.byteLength <= maxBytes)
|
|
11
|
+
return source;
|
|
12
|
+
for (let end = maxBytes; end >= Math.max(0, maxBytes - 4); end -= 1) {
|
|
13
|
+
try {
|
|
14
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, end));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Try the previous Unicode boundary.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
export function boundProjectMemoryText(source, maxLines, maxBytes) {
|
|
23
|
+
return utf8Prefix(source.split(/\r?\n/u).slice(0, maxLines).join('\n'), maxBytes);
|
|
24
|
+
}
|
|
25
|
+
export function boundProjectMemoryIndex(source) {
|
|
26
|
+
return boundProjectMemoryText(source, PROJECT_MEMORY_INDEX_MAX_LINES, PROJECT_MEMORY_INDEX_MAX_BYTES);
|
|
27
|
+
}
|
|
28
|
+
function settingBoolean(settings, path) {
|
|
29
|
+
let selected;
|
|
30
|
+
for (const resource of settings) {
|
|
31
|
+
let value = resource.value;
|
|
32
|
+
for (const key of path)
|
|
33
|
+
value = record(value)?.[key];
|
|
34
|
+
if (typeof value === 'boolean')
|
|
35
|
+
selected = value;
|
|
36
|
+
}
|
|
37
|
+
return selected;
|
|
38
|
+
}
|
|
39
|
+
function enabledEnvironmentValue(value) {
|
|
40
|
+
return value === '1' || value?.toLowerCase() === 'true';
|
|
41
|
+
}
|
|
42
|
+
export function resolveProjectMemoryPolicy({ dataPlane, settings, environment, }) {
|
|
43
|
+
const disabledByEnvironment = enabledEnvironmentValue(dataPlane === 'claude'
|
|
44
|
+
? environment.CLAUDE_CODE_DISABLE_AUTO_MEMORY
|
|
45
|
+
: environment.PRAXIS_DISABLE_AUTO_MEMORY);
|
|
46
|
+
const enabled = !disabledByEnvironment &&
|
|
47
|
+
(settingBoolean(settings, ['autoMemoryEnabled']) ?? true);
|
|
48
|
+
if (!enabled)
|
|
49
|
+
return { enabled: false, extraction: false, recall: false };
|
|
50
|
+
return {
|
|
51
|
+
enabled: true,
|
|
52
|
+
extraction: settingBoolean(settings, ['projectMemory', 'backgroundExtraction']) ??
|
|
53
|
+
enabledEnvironmentValue(environment.PRAXIS_PROJECT_MEMORY_EXTRACTION),
|
|
54
|
+
recall: settingBoolean(settings, ['projectMemory', 'selectiveRecall']) ??
|
|
55
|
+
enabledEnvironmentValue(environment.PRAXIS_PROJECT_MEMORY_RECALL),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=project-memory.js.map
|
package/dist/core/runtime.d.ts
CHANGED
|
@@ -181,6 +181,9 @@ export type RuntimeEvent = {
|
|
|
181
181
|
} | {
|
|
182
182
|
type: 'terminal';
|
|
183
183
|
reason: ModelTerminalReason;
|
|
184
|
+
} | {
|
|
185
|
+
type: 'model-attempt-discarded';
|
|
186
|
+
reason: ProviderErrorKind;
|
|
184
187
|
} | {
|
|
185
188
|
type: 'tool-call';
|
|
186
189
|
call: ModelToolCall;
|
|
@@ -472,6 +475,9 @@ export interface AgentRunRequest {
|
|
|
472
475
|
effort?: string;
|
|
473
476
|
thinking?: ModelThinkingConfig;
|
|
474
477
|
collectMetrics?: boolean;
|
|
478
|
+
/** Provider failures whose presentation is owned by a caller recovery
|
|
479
|
+
* boundary. `true` defers every typed provider failure for an owned retry. */
|
|
480
|
+
deferFailureKinds?: true | readonly ProviderErrorKind[];
|
|
475
481
|
maxModelTurns?: number;
|
|
476
482
|
betas?: readonly string[];
|
|
477
483
|
}
|