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
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { appendFile, copyFile, link, lstat, mkdir, readdir, realpath, stat, unlink, } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, extname, isAbsolute, join, relative, resolve, } from 'node:path';
|
|
5
|
+
import { isPathWithin } from '../platform/path-containment.js';
|
|
5
6
|
import { AGENT_COLOR_DEFAULT, agentColorMessage, getClaudeEffectiveAgentColor, } from '../compatibility/claude/agent-color.js';
|
|
6
7
|
import { createClaudeCompactEntries, formatClaudeCompactSummary, getCumulativeDroppedTokens, } from '../compatibility/claude/compaction.js';
|
|
7
8
|
import { discoverClaudeProjectRoot, isClaudeSessionId, resolveClaudePaths, resolveClaudeScheduledTaskFile, } from '../compatibility/claude/paths.js';
|
|
@@ -19,7 +20,7 @@ import { AgentRunCancelledError, AgentRuntime, } from '../core/runtime.js';
|
|
|
19
20
|
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
20
21
|
import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
21
22
|
import { usageCostUsd } from '../core/usage.js';
|
|
22
|
-
import { ContextBudget, ContextRecoveryPlanner, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
23
|
+
import { ContextBudget, ContextRecoveryPlanner, contextRecoveryMadeProgress, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
23
24
|
import { injectFirstUserMessageContext, } from '../core/context.js';
|
|
24
25
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
25
26
|
import { ClaudeTranscriptStore, } from '../persistence/claude-transcript-store.js';
|
|
@@ -464,6 +465,22 @@ function validSessionName(value) {
|
|
|
464
465
|
? name.toLocaleLowerCase()
|
|
465
466
|
: null;
|
|
466
467
|
}
|
|
468
|
+
function projectMemoryMessages(entries) {
|
|
469
|
+
return selectClaudeActiveTranscript(entries).flatMap((entry) => {
|
|
470
|
+
if (typeof entry.uuid !== 'string')
|
|
471
|
+
return [];
|
|
472
|
+
return projectClaudeModelMessages([entry]).flatMap((message) => (message.role === 'user' || message.role === 'assistant') &&
|
|
473
|
+
message.content.trim().length > 0
|
|
474
|
+
? [
|
|
475
|
+
{
|
|
476
|
+
id: entry.uuid,
|
|
477
|
+
role: message.role,
|
|
478
|
+
content: message.content,
|
|
479
|
+
},
|
|
480
|
+
]
|
|
481
|
+
: []);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
467
484
|
const SESSION_END_HOOK_TIMEOUT_MS = 15_000;
|
|
468
485
|
class HookLifecycle {
|
|
469
486
|
hooks;
|
|
@@ -741,6 +758,7 @@ export class ClaudeSessionService {
|
|
|
741
758
|
async close() {
|
|
742
759
|
await this.hookLifecycle.close();
|
|
743
760
|
this.scheduledPrompts?.close();
|
|
761
|
+
await this.options.projectMemoryExtraction?.close(5_000);
|
|
744
762
|
await Promise.all([...this.hostedSubagents].map((executor) => executor.close()));
|
|
745
763
|
await Promise.resolve();
|
|
746
764
|
await Promise.all([...this.backgroundNotificationWrites.values()]);
|
|
@@ -756,6 +774,9 @@ export class ClaudeSessionService {
|
|
|
756
774
|
}
|
|
757
775
|
async transitionHookSession(sessionId, reason) {
|
|
758
776
|
await this.hookLifecycle.transition(sessionId, reason);
|
|
777
|
+
if (reason === 'clear') {
|
|
778
|
+
this.options.projectMemoryRecall?.clearSession?.(sessionId);
|
|
779
|
+
}
|
|
759
780
|
this.options.contextAssembler?.invalidate?.({
|
|
760
781
|
lifecycleId: sessionId,
|
|
761
782
|
reason: reason === 'resume' ? 'restore' : 'clear',
|
|
@@ -1784,6 +1805,7 @@ export class ClaudeSessionService {
|
|
|
1784
1805
|
if (appendResult.status === 'conflict') {
|
|
1785
1806
|
throw new Error(`Claude transcript append conflict: ${appendResult.reason}`);
|
|
1786
1807
|
}
|
|
1808
|
+
this.options.projectMemoryRecall?.recordCompact(sessionId);
|
|
1787
1809
|
this.options.eventSink?.({
|
|
1788
1810
|
type: 'compact-boundary',
|
|
1789
1811
|
trigger: 'manual',
|
|
@@ -2147,6 +2169,14 @@ export class ClaudeSessionService {
|
|
|
2147
2169
|
agent?.initialPrompt
|
|
2148
2170
|
? `${agent.initialPrompt}\n\n${prompt}`
|
|
2149
2171
|
: prompt;
|
|
2172
|
+
const projectMemoryRecallTurn = !skipUserPrompt && shellCommand === undefined
|
|
2173
|
+
? this.options.projectMemoryRecall?.prefetch({
|
|
2174
|
+
sessionId,
|
|
2175
|
+
turnId: randomUUID(),
|
|
2176
|
+
prompt: effectivePrompt,
|
|
2177
|
+
...(signal ? { signal } : {}),
|
|
2178
|
+
})
|
|
2179
|
+
: undefined;
|
|
2150
2180
|
const initialPricing = this.options.pricing?.resolve(provider.model ?? 'praxis/provider');
|
|
2151
2181
|
if (this.options.maxBudgetUsd !== undefined && !initialPricing) {
|
|
2152
2182
|
throw new Error(`Cannot enforce --max-budget-usd: no pricing is configured for model ${provider.model ?? 'praxis/provider'}`);
|
|
@@ -2456,6 +2486,8 @@ export class ClaudeSessionService {
|
|
|
2456
2486
|
});
|
|
2457
2487
|
let currentTurnUserMessages = null;
|
|
2458
2488
|
let currentTurnToolCalls = 0;
|
|
2489
|
+
let projectMemoryMaintained = false;
|
|
2490
|
+
let projectMemoryRecallMessages = [];
|
|
2459
2491
|
const observer = {
|
|
2460
2492
|
assistantCompleted: async (message) => {
|
|
2461
2493
|
const [entry] = translateProviderEvents([
|
|
@@ -2534,6 +2566,29 @@ export class ClaudeSessionService {
|
|
|
2534
2566
|
throw new Error('Could not translate tool result');
|
|
2535
2567
|
const tail = await this.append(lease, snapshot.tail, entry);
|
|
2536
2568
|
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
2569
|
+
if (!toolResult.isError && this.options.projectMemoryDirectory) {
|
|
2570
|
+
const pathValue = call.input.file_path;
|
|
2571
|
+
const path = typeof pathValue === 'string'
|
|
2572
|
+
? resolve(this.activeCwd(), pathValue)
|
|
2573
|
+
: null;
|
|
2574
|
+
if (path &&
|
|
2575
|
+
isPathWithin(this.options.projectMemoryDirectory, path)) {
|
|
2576
|
+
if (call.name === 'Read') {
|
|
2577
|
+
this.options.projectMemoryRecall?.recordRead(sessionId, path);
|
|
2578
|
+
}
|
|
2579
|
+
else if (call.name === 'Write' || call.name === 'Edit') {
|
|
2580
|
+
projectMemoryMaintained = true;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (call.name === 'Read') {
|
|
2584
|
+
for (const accessedPath of toolResult.accessedPaths ?? []) {
|
|
2585
|
+
const resolvedAccessedPath = resolve(this.activeCwd(), accessedPath);
|
|
2586
|
+
if (isPathWithin(this.options.projectMemoryDirectory, resolvedAccessedPath)) {
|
|
2587
|
+
this.options.projectMemoryRecall?.recordRead(sessionId, resolvedAccessedPath);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2537
2592
|
if (toolResult.isError ||
|
|
2538
2593
|
call.name !== 'Read' ||
|
|
2539
2594
|
!this.options.conditionalRuleResolver ||
|
|
@@ -2807,7 +2862,10 @@ export class ClaudeSessionService {
|
|
|
2807
2862
|
const compactIfNeeded = async (pendingMessages = [], preservedUserMessages = [], options = {}) => {
|
|
2808
2863
|
if (!budget || this.options.autoCompact === false)
|
|
2809
2864
|
return;
|
|
2810
|
-
const historyMessages =
|
|
2865
|
+
const historyMessages = [
|
|
2866
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
2867
|
+
...projectMemoryRecallMessages,
|
|
2868
|
+
];
|
|
2811
2869
|
const predicted = budget.evaluate([
|
|
2812
2870
|
...contextMessages,
|
|
2813
2871
|
...injectTurnContext([...historyMessages, ...pendingMessages]),
|
|
@@ -2925,17 +2983,27 @@ export class ClaudeSessionService {
|
|
|
2925
2983
|
createUuid: () => replayUuids.shift() ?? randomUUID(),
|
|
2926
2984
|
now: () => timestamp,
|
|
2927
2985
|
});
|
|
2928
|
-
const compactedHistory =
|
|
2929
|
-
...
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2986
|
+
const compactedHistory = [
|
|
2987
|
+
...projectClaudeModelMessages([
|
|
2988
|
+
...snapshot.entries,
|
|
2989
|
+
...provisionalEntries,
|
|
2990
|
+
...replayEntries,
|
|
2991
|
+
]),
|
|
2992
|
+
...projectMemoryRecallMessages,
|
|
2993
|
+
];
|
|
2933
2994
|
const afterHistory = budget.evaluate([...contextMessages, ...injectTurnContext(compactedHistory)], definitions);
|
|
2934
2995
|
const afterPending = budget.evaluate([
|
|
2935
2996
|
...contextMessages,
|
|
2936
2997
|
...injectTurnContext([...compactedHistory, ...pendingMessages]),
|
|
2937
2998
|
], definitions);
|
|
2938
2999
|
budget.assertFits(afterPending);
|
|
3000
|
+
if (options.promptTooLong === true &&
|
|
3001
|
+
!contextRecoveryMadeProgress({
|
|
3002
|
+
beforeOccupancyTokens: predicted.occupancyTokens,
|
|
3003
|
+
afterOccupancyTokens: afterPending.occupancyTokens,
|
|
3004
|
+
})) {
|
|
3005
|
+
throw new Error('Reactive context compaction made no occupancy progress');
|
|
3006
|
+
}
|
|
2939
3007
|
const entries = [
|
|
2940
3008
|
...compactEntries(afterHistory.estimatedTokens),
|
|
2941
3009
|
...replayEntries,
|
|
@@ -3016,6 +3084,7 @@ export class ClaudeSessionService {
|
|
|
3016
3084
|
lifecycleId: sessionId,
|
|
3017
3085
|
reason: 'compact',
|
|
3018
3086
|
});
|
|
3087
|
+
this.options.projectMemoryRecall?.recordCompact(sessionId);
|
|
3019
3088
|
await refreshRuntimeContext();
|
|
3020
3089
|
this.options.eventSink?.({
|
|
3021
3090
|
type: 'compact-boundary',
|
|
@@ -3201,7 +3270,16 @@ export class ClaudeSessionService {
|
|
|
3201
3270
|
...(this.options.collectMetrics || this.options.costStateStore
|
|
3202
3271
|
? { collectMetrics: true }
|
|
3203
3272
|
: {}),
|
|
3273
|
+
...(budget
|
|
3274
|
+
? { deferFailureKinds: ['prompt_too_long'] }
|
|
3275
|
+
: {}),
|
|
3204
3276
|
reloadMessages: async () => {
|
|
3277
|
+
const recalled = projectMemoryRecallTurn?.consumeIfSettled();
|
|
3278
|
+
if (recalled) {
|
|
3279
|
+
projectMemoryRecallMessages = [
|
|
3280
|
+
{ role: 'user', content: recalled.content },
|
|
3281
|
+
];
|
|
3282
|
+
}
|
|
3205
3283
|
await compactIfNeeded([], currentTurnUserMessages ?? []);
|
|
3206
3284
|
if (stableSystemMessageCount === undefined) {
|
|
3207
3285
|
delete runtimeRequest.stableSystemMessageCount;
|
|
@@ -3211,7 +3289,10 @@ export class ClaudeSessionService {
|
|
|
3211
3289
|
}
|
|
3212
3290
|
return [
|
|
3213
3291
|
...contextMessages,
|
|
3214
|
-
...injectTurnContext(
|
|
3292
|
+
...injectTurnContext([
|
|
3293
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
3294
|
+
...projectMemoryRecallMessages,
|
|
3295
|
+
]),
|
|
3215
3296
|
];
|
|
3216
3297
|
},
|
|
3217
3298
|
...(this.options.hooks ||
|
|
@@ -3297,6 +3378,13 @@ export class ClaudeSessionService {
|
|
|
3297
3378
|
const attemptMainTurn = () => signal
|
|
3298
3379
|
? runtime.run({ ...runtimeRequest, signal })
|
|
3299
3380
|
: runtime.run(runtimeRequest);
|
|
3381
|
+
const surfaceExhaustedRecovery = (error) => {
|
|
3382
|
+
this.options.eventSink?.({
|
|
3383
|
+
type: 'failed',
|
|
3384
|
+
message: error.message,
|
|
3385
|
+
retryable: false,
|
|
3386
|
+
});
|
|
3387
|
+
};
|
|
3300
3388
|
let result;
|
|
3301
3389
|
try {
|
|
3302
3390
|
result = await attemptMainTurn();
|
|
@@ -3304,24 +3392,35 @@ export class ClaudeSessionService {
|
|
|
3304
3392
|
catch (error) {
|
|
3305
3393
|
if (!budget || !isPromptTooLongError(error))
|
|
3306
3394
|
throw error;
|
|
3307
|
-
if (recoveryPlanner.
|
|
3395
|
+
if (recoveryPlanner.reactiveRetriesRemaining === 0) {
|
|
3396
|
+
surfaceExhaustedRecovery(error);
|
|
3308
3397
|
throw error;
|
|
3398
|
+
}
|
|
3399
|
+
const beforeRecovery = budget.evaluate(runtimeRequest.messages, definitions);
|
|
3309
3400
|
try {
|
|
3310
3401
|
await compactIfNeeded([], currentTurnUserMessages ?? [], {
|
|
3311
3402
|
promptTooLong: true,
|
|
3312
3403
|
});
|
|
3313
3404
|
}
|
|
3314
|
-
catch {
|
|
3405
|
+
catch (compactionError) {
|
|
3406
|
+
if (signal?.aborted ||
|
|
3407
|
+
compactionError instanceof AgentRunCancelledError) {
|
|
3408
|
+
throw new AgentRunCancelledError();
|
|
3409
|
+
}
|
|
3315
3410
|
// Compaction could not free the provider-bounded context; surface
|
|
3316
3411
|
// the original prompt-too-long error rather than the compaction
|
|
3317
3412
|
// failure.
|
|
3413
|
+
surfaceExhaustedRecovery(error);
|
|
3318
3414
|
throw error;
|
|
3319
3415
|
}
|
|
3320
3416
|
// The single reactive retry must use the compacted transcript, not
|
|
3321
3417
|
// the stale request copy captured before the compact boundary.
|
|
3322
3418
|
runtimeRequest.messages = [
|
|
3323
3419
|
...contextMessages,
|
|
3324
|
-
...injectTurnContext(
|
|
3420
|
+
...injectTurnContext([
|
|
3421
|
+
...projectClaudeModelMessages(snapshot.entries),
|
|
3422
|
+
...projectMemoryRecallMessages,
|
|
3423
|
+
]),
|
|
3325
3424
|
];
|
|
3326
3425
|
if (stableSystemMessageCount === undefined) {
|
|
3327
3426
|
delete runtimeRequest.stableSystemMessageCount;
|
|
@@ -3329,13 +3428,27 @@ export class ClaudeSessionService {
|
|
|
3329
3428
|
else {
|
|
3330
3429
|
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
3331
3430
|
}
|
|
3431
|
+
const afterRecovery = budget.evaluate(runtimeRequest.messages, definitions);
|
|
3432
|
+
if (recoveryPlanner.consumeReactiveRetry({
|
|
3433
|
+
beforeOccupancyTokens: beforeRecovery.occupancyTokens,
|
|
3434
|
+
afterOccupancyTokens: afterRecovery.occupancyTokens,
|
|
3435
|
+
}) !== 'reactive-retry') {
|
|
3436
|
+
surfaceExhaustedRecovery(error);
|
|
3437
|
+
throw error;
|
|
3438
|
+
}
|
|
3439
|
+
runtimeRequest.deferFailureKinds = true;
|
|
3332
3440
|
try {
|
|
3333
3441
|
result = await attemptMainTurn();
|
|
3334
3442
|
}
|
|
3335
|
-
catch {
|
|
3443
|
+
catch (retryError) {
|
|
3336
3444
|
// Exactly one reactive retry is consumed; fail deterministically
|
|
3337
3445
|
// and surface the original prompt-too-long error.
|
|
3338
3446
|
recoveryPlanner.recordFailure();
|
|
3447
|
+
if (signal?.aborted ||
|
|
3448
|
+
retryError instanceof AgentRunCancelledError) {
|
|
3449
|
+
throw new AgentRunCancelledError();
|
|
3450
|
+
}
|
|
3451
|
+
surfaceExhaustedRecovery(error);
|
|
3339
3452
|
throw error;
|
|
3340
3453
|
}
|
|
3341
3454
|
}
|
|
@@ -3465,6 +3578,11 @@ export class ClaudeSessionService {
|
|
|
3465
3578
|
});
|
|
3466
3579
|
}
|
|
3467
3580
|
}
|
|
3581
|
+
this.options.projectMemoryExtraction?.observe({
|
|
3582
|
+
sessionId,
|
|
3583
|
+
directMaintenance: projectMemoryMaintained,
|
|
3584
|
+
messages: projectMemoryMessages(snapshot.entries),
|
|
3585
|
+
});
|
|
3468
3586
|
turnCompleted = true;
|
|
3469
3587
|
return {
|
|
3470
3588
|
sessionId,
|
package/dist/cli/interactive.js
CHANGED
|
@@ -449,6 +449,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
449
449
|
const [costUsd, setCostUsd] = useState();
|
|
450
450
|
const [contextWindowTokens, setContextWindowTokens] = useState(display.contextWindowTokens);
|
|
451
451
|
const [history, setHistory] = useState([...initialHistory]);
|
|
452
|
+
const activeAttemptThinkingItemsRef = useRef([]);
|
|
452
453
|
const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
|
|
453
454
|
// The pure TUI view model classifies the session identity (fresh/resumed/
|
|
454
455
|
// started) and projects the rendered transcript. Startup diagnostics are
|
|
@@ -1183,28 +1184,45 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
1183
1184
|
// Signatures authenticate a thinking block for provider replay; they are
|
|
1184
1185
|
// intentionally not part of the user-visible reasoning summary.
|
|
1185
1186
|
break;
|
|
1186
|
-
case 'thinking-stop':
|
|
1187
|
+
case 'thinking-stop': {
|
|
1187
1188
|
// append flushes pending thinking deltas before the retained boundary
|
|
1188
1189
|
// item, keeping streaming order correct; the effective thinking getter
|
|
1189
1190
|
// already includes any deltas not yet published.
|
|
1190
|
-
|
|
1191
|
+
const thinkingItem = {
|
|
1191
1192
|
kind: 'thinking',
|
|
1192
1193
|
text: event.block.type === 'thinking'
|
|
1193
1194
|
? redactSensitiveText(event.block.thinking, sensitiveValues)
|
|
1194
1195
|
: (streamingFrameRef.current?.thinking ?? ''),
|
|
1195
|
-
}
|
|
1196
|
+
};
|
|
1197
|
+
activeAttemptThinkingItemsRef.current.push(thinkingItem);
|
|
1198
|
+
append(thinkingItem);
|
|
1196
1199
|
streamingFrameRef.current?.resetThinking();
|
|
1197
1200
|
streamingFrameRef.current?.flush();
|
|
1198
1201
|
break;
|
|
1202
|
+
}
|
|
1199
1203
|
case 'user-message':
|
|
1200
1204
|
append({ kind: 'assistant', text: event.message });
|
|
1201
1205
|
break;
|
|
1202
1206
|
case 'state':
|
|
1207
|
+
if (event.state === 'awaiting-model') {
|
|
1208
|
+
activeAttemptThinkingItemsRef.current = [];
|
|
1209
|
+
}
|
|
1203
1210
|
setStatus(event.state);
|
|
1204
1211
|
break;
|
|
1205
1212
|
case 'usage':
|
|
1206
1213
|
setUsage(event.usage);
|
|
1207
1214
|
break;
|
|
1215
|
+
case 'model-attempt-discarded':
|
|
1216
|
+
streamingFrameRef.current?.resetText();
|
|
1217
|
+
streamingFrameRef.current?.resetThinking();
|
|
1218
|
+
streamingFrameRef.current?.flush();
|
|
1219
|
+
setUsage({ inputTokens: 0, outputTokens: 0 });
|
|
1220
|
+
if (activeAttemptThinkingItemsRef.current.length > 0) {
|
|
1221
|
+
const discarded = new Set(activeAttemptThinkingItemsRef.current);
|
|
1222
|
+
setHistory((current) => current.filter((item) => !discarded.has(item)));
|
|
1223
|
+
activeAttemptThinkingItemsRef.current = [];
|
|
1224
|
+
}
|
|
1225
|
+
break;
|
|
1208
1226
|
case 'tool-call':
|
|
1209
1227
|
if (['Edit', 'Write', 'NotebookEdit'].includes(event.call.name))
|
|
1210
1228
|
turnMutatedFilesRef.current = true;
|
package/dist/cli/protocol.d.ts
CHANGED
package/dist/cli/protocol.js
CHANGED
|
@@ -1753,6 +1753,10 @@ export class StreamJsonOutput {
|
|
|
1753
1753
|
this.modelTurns = 0;
|
|
1754
1754
|
}
|
|
1755
1755
|
onEvent(event) {
|
|
1756
|
+
if (event.type === 'model-attempt-discarded') {
|
|
1757
|
+
this.discardTurn(event.reason);
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1756
1760
|
if (event.type === 'state') {
|
|
1757
1761
|
if (this.compacting && event.state !== 'compacting') {
|
|
1758
1762
|
this.compacting = false;
|
|
@@ -2109,6 +2113,26 @@ export class StreamJsonOutput {
|
|
|
2109
2113
|
if (!this.turnActive)
|
|
2110
2114
|
this.startTurn();
|
|
2111
2115
|
}
|
|
2116
|
+
discardTurn(reason) {
|
|
2117
|
+
this.write({
|
|
2118
|
+
type: 'system',
|
|
2119
|
+
subtype: 'model_attempt_discarded',
|
|
2120
|
+
reason,
|
|
2121
|
+
uuid: randomUUID(),
|
|
2122
|
+
session_id: this.sessionId,
|
|
2123
|
+
});
|
|
2124
|
+
this.turnText = '';
|
|
2125
|
+
this.turnThinking = [];
|
|
2126
|
+
this.turnCalls = [];
|
|
2127
|
+
this.turnUsage = emptyUsage();
|
|
2128
|
+
this.turnTerminalReason = undefined;
|
|
2129
|
+
this.turnActive = false;
|
|
2130
|
+
this.assistantFlushed = true;
|
|
2131
|
+
this.contentStarted = false;
|
|
2132
|
+
this.textContentIndex = undefined;
|
|
2133
|
+
this.activeThinkingIndex = undefined;
|
|
2134
|
+
this.nextContentIndex = 0;
|
|
2135
|
+
}
|
|
2112
2136
|
startTurn() {
|
|
2113
2137
|
if (this.turnActive)
|
|
2114
2138
|
this.finishTurn();
|
|
@@ -18,9 +18,10 @@ export interface LoadTuiMemoryFilesOptions {
|
|
|
18
18
|
cwd: string;
|
|
19
19
|
homeDirectory?: string;
|
|
20
20
|
dataPlane?: DataPlane;
|
|
21
|
+
environment?: Readonly<Record<string, string | undefined>>;
|
|
21
22
|
}
|
|
22
23
|
export declare function displayTuiMemoryPath(path: string, cwd: string, homeDirectory?: string): string;
|
|
23
|
-
export declare function loadTuiMemoryFiles({ configRoot, cwd, homeDirectory, dataPlane, }: LoadTuiMemoryFilesOptions): Promise<TuiMemoryFiles>;
|
|
24
|
+
export declare function loadTuiMemoryFiles({ configRoot, cwd, homeDirectory, dataPlane, environment, }: LoadTuiMemoryFilesOptions): Promise<TuiMemoryFiles>;
|
|
24
25
|
type TuiMemoryFolderLauncher = (command: string, args: readonly string[]) => Promise<void>;
|
|
25
26
|
export declare function openTuiMemoryFolder(path: string, launcher?: TuiMemoryFolderLauncher): Promise<void>;
|
|
26
27
|
export {};
|
|
@@ -2,9 +2,10 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { mkdir, realpath } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, isAbsolute, relative, resolve } from 'node:path';
|
|
5
|
-
import { loadClaudeContextResources, loadClaudeSettings,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { loadClaudeContextResources, loadClaudeSettings, } from '../../compatibility/claude/shared-resources.js';
|
|
6
|
+
import { resolveProjectMemoryPolicy } from '../../core/project-memory.js';
|
|
7
|
+
import { resolveProjectMemoryDirectory } from '../../platform/project-memory-paths.js';
|
|
8
|
+
import { loadNativeContextResources, loadNativeSettings, } from '../../persistence/native-resources.js';
|
|
8
9
|
function isWithin(root, path) {
|
|
9
10
|
const value = relative(root, path);
|
|
10
11
|
return value === '' || (!value.startsWith('..') && !isAbsolute(value));
|
|
@@ -41,40 +42,39 @@ function importedEntries(resource, instructions, cwd, homeDirectory, sourceDispl
|
|
|
41
42
|
imported: true,
|
|
42
43
|
}));
|
|
43
44
|
}
|
|
44
|
-
function
|
|
45
|
-
let enabled = true;
|
|
46
|
-
for (const resource of settings) {
|
|
47
|
-
if (typeof resource.value === 'object' &&
|
|
48
|
-
resource.value !== null &&
|
|
49
|
-
!Array.isArray(resource.value)) {
|
|
50
|
-
const value = resource.value
|
|
51
|
-
.autoMemoryEnabled;
|
|
52
|
-
if (typeof value === 'boolean')
|
|
53
|
-
enabled = value;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return enabled;
|
|
57
|
-
}
|
|
58
|
-
export async function loadTuiMemoryFiles({ configRoot, cwd, homeDirectory = homedir(), dataPlane = 'claude', }) {
|
|
45
|
+
export async function loadTuiMemoryFiles({ configRoot, cwd, homeDirectory = homedir(), dataPlane = 'claude', environment = process.env, }) {
|
|
59
46
|
const [canonicalCwd, canonicalHome] = await Promise.all([
|
|
60
47
|
realpath(cwd),
|
|
61
48
|
realpath(homeDirectory).catch(() => resolve(homeDirectory)),
|
|
62
49
|
]);
|
|
63
|
-
const
|
|
64
|
-
? await
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
50
|
+
const settings = dataPlane === 'native'
|
|
51
|
+
? await loadNativeSettings({ root: configRoot, cwd })
|
|
52
|
+
: await loadClaudeSettings({ configRoot, cwd });
|
|
53
|
+
const memoryPolicy = resolveProjectMemoryPolicy({
|
|
54
|
+
dataPlane,
|
|
55
|
+
settings,
|
|
56
|
+
environment,
|
|
57
|
+
});
|
|
58
|
+
const [context, autoMemoryDirectory] = await Promise.all([
|
|
59
|
+
dataPlane === 'native'
|
|
60
|
+
? loadNativeContextResources({
|
|
61
|
+
root: configRoot,
|
|
62
|
+
cwd,
|
|
63
|
+
environment,
|
|
64
|
+
includeProjectMemory: memoryPolicy.enabled,
|
|
65
|
+
})
|
|
66
|
+
: loadClaudeContextResources({
|
|
73
67
|
configRoot,
|
|
74
68
|
cwd,
|
|
75
69
|
homeDirectory,
|
|
70
|
+
includeProjectMemory: memoryPolicy.enabled,
|
|
76
71
|
}),
|
|
77
|
-
|
|
72
|
+
resolveProjectMemoryDirectory({
|
|
73
|
+
dataPlane,
|
|
74
|
+
configRoot,
|
|
75
|
+
cwd,
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
78
|
const instructionName = dataPlane === 'native' ? 'PRAXIS.md' : 'CLAUDE.md';
|
|
79
79
|
const userPath = resolve(configRoot, instructionName);
|
|
80
80
|
const projectResources = context.instructions.filter((resource) => resource.importedFrom === undefined &&
|
|
@@ -129,7 +129,7 @@ export async function loadTuiMemoryFiles({ configRoot, cwd, homeDirectory = home
|
|
|
129
129
|
});
|
|
130
130
|
entries.push(...importedEntries(resource, context.instructions, canonicalCwd, canonicalHome, displayPath));
|
|
131
131
|
}
|
|
132
|
-
const autoMemoryEnabled =
|
|
132
|
+
const autoMemoryEnabled = memoryPolicy.enabled;
|
|
133
133
|
if (autoMemoryEnabled) {
|
|
134
134
|
entries.push({
|
|
135
135
|
kind: 'folder',
|