praxis-agent 0.36.0 → 0.37.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 +16 -2
- package/dist/application/durable-follow-up.d.ts +14 -0
- package/dist/application/durable-follow-up.js +52 -0
- package/dist/application/native-session-transcript.d.ts +72 -0
- package/dist/application/native-session-transcript.js +351 -0
- package/dist/application/native-sidechain-transcript.d.ts +34 -0
- package/dist/application/native-sidechain-transcript.js +156 -0
- package/dist/application/session-service.d.ts +15 -0
- package/dist/application/session-service.js +611 -69
- package/dist/application/subagent-service.d.ts +5 -1
- package/dist/application/subagent-service.js +391 -143
- package/dist/application/team-agent-runtime.d.ts +29 -0
- package/dist/application/team-agent-runtime.js +169 -0
- package/dist/application/team-capability.d.ts +27 -0
- package/dist/application/team-capability.js +41 -0
- package/dist/application/team-lead-operations.d.ts +38 -0
- package/dist/application/team-lead-operations.js +155 -0
- package/dist/application/team-mailbox.d.ts +54 -0
- package/dist/application/team-mailbox.js +196 -0
- package/dist/application/team-manager.d.ts +113 -0
- package/dist/application/team-manager.js +681 -0
- package/dist/application/team-workspace.d.ts +29 -0
- package/dist/application/team-workspace.js +197 -0
- package/dist/application/transcript-projection.d.ts +2 -0
- package/dist/application/transcript-projection.js +30 -1
- package/dist/cli/interactive.d.ts +7 -0
- package/dist/cli/interactive.js +387 -136
- package/dist/cli/tui/claude-style.d.ts +8 -25
- package/dist/cli/tui/claude-style.js +158 -160
- package/dist/cli/tui/decision-surface-model.d.ts +71 -0
- package/dist/cli/tui/decision-surface-model.js +131 -0
- package/dist/cli/tui/decision-surface.d.ts +7 -0
- package/dist/cli/tui/decision-surface.js +41 -0
- package/dist/cli/tui/help-surface-model.d.ts +68 -0
- package/dist/cli/tui/help-surface-model.js +131 -0
- package/dist/cli/tui/mcp-panel.js +1 -2
- package/dist/cli/tui/permission-surface-model.d.ts +195 -0
- package/dist/cli/tui/permission-surface-model.js +360 -0
- package/dist/cli/tui/permission-surface.d.ts +7 -0
- package/dist/cli/tui/permission-surface.js +69 -0
- package/dist/cli/tui/presentation-environment.d.ts +32 -0
- package/dist/cli/tui/presentation-environment.js +149 -0
- package/dist/cli/tui/tool-permission.js +7 -3
- package/dist/cli/tui/transcript-presentation.d.ts +19 -1
- package/dist/cli/tui/transcript-presentation.js +26 -6
- package/dist/cli/tui/transcript-viewport.d.ts +18 -0
- package/dist/cli/tui/transcript-viewport.js +517 -111
- package/dist/cli/tui/transcript-window-model.d.ts +108 -0
- package/dist/cli/tui/transcript-window-model.js +772 -0
- package/dist/cli/tui/tui-screen-model.d.ts +114 -0
- package/dist/cli/tui/tui-screen-model.js +210 -0
- package/dist/cli/tui/tui-view-model.d.ts +2 -49
- package/dist/cli/tui/tui-view-model.js +1 -68
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/cli-runtime.js +397 -32
- package/dist/compatibility/claude/transcript-codec.js +6 -0
- package/dist/core/permission-policy.d.ts +4 -0
- package/dist/core/permission-policy.js +21 -0
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +5 -1
- package/dist/core/team-mailbox.d.ts +54 -0
- package/dist/core/team-mailbox.js +212 -0
- package/dist/core/team-ownership.d.ts +84 -0
- package/dist/core/team-ownership.js +505 -0
- package/dist/core/tool-execution-scheduler.js +6 -1
- package/dist/core/transcript-event.d.ts +5 -1
- package/dist/core/transcript-event.js +12 -0
- package/dist/persistence/claude-transcript-store.d.ts +2 -1
- package/dist/persistence/claude-transcript-store.js +2 -10
- package/dist/persistence/native-team-mailbox.d.ts +68 -0
- package/dist/persistence/native-team-mailbox.js +542 -0
- package/dist/persistence/native-team-store.d.ts +34 -0
- package/dist/persistence/native-team-store.js +257 -0
- package/dist/persistence/native-transcript-store.d.ts +69 -0
- package/dist/persistence/native-transcript-store.js +321 -0
- package/dist/persistence/transcript-file-append.d.ts +2 -0
- package/dist/persistence/transcript-file-append.js +10 -0
- package/dist/tools/claude-capabilities.d.ts +4 -0
- package/dist/tools/claude-capabilities.js +18 -2
- package/dist/tools/team-lead-tools.d.ts +19 -0
- package/dist/tools/team-lead-tools.js +479 -0
- package/dist/tools/team-member-tools.d.ts +21 -0
- package/dist/tools/team-member-tools.js +259 -0
- package/package.json +1 -1
- package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
- package/dist/cli/tui/fullscreen-renderer.js +0 -13
|
@@ -34,8 +34,10 @@ import { ClaudeFileChangeWatcher } from '../hooks/claude-file-change-watcher.js'
|
|
|
34
34
|
import { ClaudeTranscriptStore, } from '../persistence/claude-transcript-store.js';
|
|
35
35
|
import { ClaudeSessionIndexCandidateError, readClaudeSessionIndexes, } from '../persistence/claude-session-index.js';
|
|
36
36
|
import { readNativeTranscript, readNativeTranscriptIndexes, exportNativeTranscript, NativeTranscriptIndexCandidateError, } from '../persistence/native-transcript-reader.js';
|
|
37
|
-
import { lastUserPrompt, projectTranscriptDisplay, } from './transcript-projection.js';
|
|
37
|
+
import { lastUserPrompt, projectTranscriptDisplay, unresolvedActiveToolCallIds, } from './transcript-projection.js';
|
|
38
38
|
import { InMemoryTranscriptStore } from '../persistence/in-memory-transcript-store.js';
|
|
39
|
+
import { NativeTranscriptStore } from '../persistence/native-transcript-store.js';
|
|
40
|
+
import { NativeSessionTranscript, } from './native-session-transcript.js';
|
|
39
41
|
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
40
42
|
import { ModelCompactor } from './model-compactor.js';
|
|
41
43
|
import { agentMemoryPrompt, ClaudeSubagentExecutor, StructuredOutputRegistry, } from './subagent-service.js';
|
|
@@ -54,6 +56,7 @@ import { FilteredToolRegistry } from '../tools/filtered-tool-registry.js';
|
|
|
54
56
|
import { ClaudeCapabilityToolRegistry, resolveClaudeToolCapabilities, } from '../tools/claude-capabilities.js';
|
|
55
57
|
import { generateToolUseSummary } from './tool-use-summary.js';
|
|
56
58
|
import { ClaudeUserMessageToolRegistry, } from '../tools/claude-user-message.js';
|
|
59
|
+
import { DurableFollowUpTracker } from './durable-follow-up.js';
|
|
57
60
|
function agentPermissionMode(mode) {
|
|
58
61
|
return mode === undefined || mode === 'manual' ? 'default' : mode;
|
|
59
62
|
}
|
|
@@ -712,10 +715,13 @@ export class ClaudeSessionService {
|
|
|
712
715
|
sessionMemoryControllers = new Map();
|
|
713
716
|
resolvedSessionMemoryProvider;
|
|
714
717
|
hookLifecycle;
|
|
718
|
+
leadOperations;
|
|
715
719
|
fileChangeWatcher;
|
|
716
720
|
runtimeCwd;
|
|
717
721
|
constructor(options) {
|
|
718
722
|
this.options = options;
|
|
723
|
+
this.assertNativeTranscriptOptions();
|
|
724
|
+
this.leadOperations = options.teamLeadOperations ?? null;
|
|
719
725
|
this.hookLifecycle = new HookLifecycle(options.hooks, options.eventSink);
|
|
720
726
|
this.runtimeCwd = options.workspace?.cwd() ?? options.cwd;
|
|
721
727
|
this.fileChangeWatcher = options.hooks
|
|
@@ -942,6 +948,7 @@ export class ClaudeSessionService {
|
|
|
942
948
|
await this.closeCostSavePromise;
|
|
943
949
|
this.mcpClosePromise ??= this.options.mcp?.close?.() ?? Promise.resolve();
|
|
944
950
|
await this.mcpClosePromise;
|
|
951
|
+
await this.leadOperations?.close();
|
|
945
952
|
}
|
|
946
953
|
async transitionHookSession(sessionId, reason) {
|
|
947
954
|
await this.hookLifecycle.transition(sessionId, reason);
|
|
@@ -1119,6 +1126,9 @@ export class ClaudeSessionService {
|
|
|
1119
1126
|
throw new Error('A model provider is required to execute hosted Agent tools');
|
|
1120
1127
|
})(),
|
|
1121
1128
|
persistence: this.options.sessionPersistence === false ? 'memory' : 'disk',
|
|
1129
|
+
...(this.options.experimentalNativeTranscriptWrites === true
|
|
1130
|
+
? { experimentalNativeTranscriptWrites: true }
|
|
1131
|
+
: {}),
|
|
1122
1132
|
...(this.options.providerForModel
|
|
1123
1133
|
? { providerForModel: this.options.providerForModel }
|
|
1124
1134
|
: {}),
|
|
@@ -1220,7 +1230,9 @@ export class ClaudeSessionService {
|
|
|
1220
1230
|
const interactiveRegistry = this.options.interactiveTools
|
|
1221
1231
|
? this.options.interactiveTools.registry(messageRegistry, sessionId)
|
|
1222
1232
|
: messageRegistry;
|
|
1223
|
-
const
|
|
1233
|
+
const teamToolNames = this.capabilityToolNames(this.options.teamToolNames, capabilities);
|
|
1234
|
+
const teamRegistry = this.teamRegistry(interactiveRegistry, sessionId, teamToolNames);
|
|
1235
|
+
const capabilityRegistry = new ClaudeCapabilityToolRegistry(teamRegistry, capabilities);
|
|
1224
1236
|
const preferredOrder = [
|
|
1225
1237
|
'Agent',
|
|
1226
1238
|
'AskUserQuestion',
|
|
@@ -1253,6 +1265,11 @@ export class ClaudeSessionService {
|
|
|
1253
1265
|
'ScheduleWakeup',
|
|
1254
1266
|
'Monitor',
|
|
1255
1267
|
'PushNotification',
|
|
1268
|
+
'TeamCreate',
|
|
1269
|
+
'TeamResume',
|
|
1270
|
+
'TeamList',
|
|
1271
|
+
'TeamAccept',
|
|
1272
|
+
'TeamStop',
|
|
1256
1273
|
];
|
|
1257
1274
|
const hostedRegistry = {
|
|
1258
1275
|
definitions: () => {
|
|
@@ -1866,6 +1883,25 @@ export class ClaudeSessionService {
|
|
|
1866
1883
|
}
|
|
1867
1884
|
async interruption(sessionId) {
|
|
1868
1885
|
this.assertSessionPersistence();
|
|
1886
|
+
if (this.options.dataPlane === 'native') {
|
|
1887
|
+
const sessionPaths = this.paths(sessionId);
|
|
1888
|
+
const nativeTranscript = new NativeSessionTranscript({
|
|
1889
|
+
sessionId,
|
|
1890
|
+
store: new NativeTranscriptStore({
|
|
1891
|
+
transcriptFile: sessionPaths.sessionFile,
|
|
1892
|
+
lockFile: join(sessionPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
1893
|
+
}),
|
|
1894
|
+
});
|
|
1895
|
+
return nativeTranscript.withLease({ kind: 'resume' }, async (lease) => {
|
|
1896
|
+
const interruption = lease.interruption();
|
|
1897
|
+
if (interruption.kind === 'recoverable-tools' ||
|
|
1898
|
+
interruption.kind === 'indeterminate-tools')
|
|
1899
|
+
return { kind: 'interrupted-turn' };
|
|
1900
|
+
if (interruption.kind === 'interrupted-prompt')
|
|
1901
|
+
return { kind: 'interrupted-prompt', prompt: interruption.prompt };
|
|
1902
|
+
return { kind: interruption.kind };
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1869
1905
|
await this.discoverProjectRoot(sessionId);
|
|
1870
1906
|
const snapshot = await this.store(sessionId).loadReadOnly();
|
|
1871
1907
|
if (snapshot.entries.length === 0) {
|
|
@@ -2225,6 +2261,97 @@ export class ClaudeSessionService {
|
|
|
2225
2261
|
return { resumeSessionAt, entryCount: result.value.entries.length };
|
|
2226
2262
|
}
|
|
2227
2263
|
async compact(sessionId, signal, selection) {
|
|
2264
|
+
if (this.nativeTranscriptWritesEnabled()) {
|
|
2265
|
+
if (selection)
|
|
2266
|
+
throw new Error('Native compaction supports only the full active branch');
|
|
2267
|
+
this.assertSessionPersistence();
|
|
2268
|
+
const provider = this.provider();
|
|
2269
|
+
await this.activateSessionCostTracker(sessionId);
|
|
2270
|
+
const sessionPaths = this.paths(sessionId);
|
|
2271
|
+
const nativeTranscript = new NativeSessionTranscript({
|
|
2272
|
+
sessionId,
|
|
2273
|
+
store: new NativeTranscriptStore({
|
|
2274
|
+
transcriptFile: sessionPaths.sessionFile,
|
|
2275
|
+
lockFile: join(sessionPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
2276
|
+
}),
|
|
2277
|
+
});
|
|
2278
|
+
return nativeTranscript.withLease({ kind: 'resume' }, async (lease) => {
|
|
2279
|
+
const messages = lease.activeMessages();
|
|
2280
|
+
if (messages.length === 0)
|
|
2281
|
+
throw new Error('Cannot compact an empty native transcript');
|
|
2282
|
+
const preTokens = estimateModelRequestTokens(messages);
|
|
2283
|
+
this.options.eventSink?.({ type: 'state', state: 'compacting' });
|
|
2284
|
+
const contextWindowTokens = this.contextBudget(provider)?.contextWindowTokens ??
|
|
2285
|
+
provider.capabilities.contextWindowTokens ??
|
|
2286
|
+
200_000;
|
|
2287
|
+
const compacted = await (this.options.compactor ?? new ModelCompactor(provider)).compact({
|
|
2288
|
+
messages,
|
|
2289
|
+
targetTokens: Math.min(8192, Math.max(1, Math.floor(contextWindowTokens / 4))),
|
|
2290
|
+
contextWindowTokens,
|
|
2291
|
+
...(signal ? { signal } : {}),
|
|
2292
|
+
});
|
|
2293
|
+
if (signal?.aborted)
|
|
2294
|
+
throw new AgentRunCancelledError();
|
|
2295
|
+
const { durationMs, durationWithoutRetriesMs } = requireCompactionDurations(compacted);
|
|
2296
|
+
requireManualCompactUsage(compacted.usage);
|
|
2297
|
+
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
2298
|
+
? compacted.model
|
|
2299
|
+
: provider.model;
|
|
2300
|
+
const meaningfulMetering = compacted.summary.trim().length > 0 &&
|
|
2301
|
+
(hasNonZeroUsage(compacted.usage) || durationMs > 0);
|
|
2302
|
+
if (meaningfulMetering &&
|
|
2303
|
+
(compactModel === undefined || compactModel.trim() === ''))
|
|
2304
|
+
throw new Error('Manual compact usage requires a nonblank model identity');
|
|
2305
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
2306
|
+
if (!tracker)
|
|
2307
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
2308
|
+
let meteringTurnInput;
|
|
2309
|
+
if (meaningfulMetering &&
|
|
2310
|
+
compactModel !== undefined &&
|
|
2311
|
+
compactModel.trim() !== '') {
|
|
2312
|
+
const pricing = this.options.pricing?.resolve(compactModel);
|
|
2313
|
+
const costUsd = pricing
|
|
2314
|
+
? usageCostUsd(compacted.usage, pricing)
|
|
2315
|
+
: undefined;
|
|
2316
|
+
meteringTurnInput = {
|
|
2317
|
+
model: compactModel,
|
|
2318
|
+
usage: compacted.usage,
|
|
2319
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
2320
|
+
...(compacted.usage.webSearchRequests === undefined
|
|
2321
|
+
? {}
|
|
2322
|
+
: { webSearchRequests: compacted.usage.webSearchRequests }),
|
|
2323
|
+
apiDurationMs: durationMs,
|
|
2324
|
+
apiDurationWithoutRetriesMs: durationWithoutRetriesMs,
|
|
2325
|
+
};
|
|
2326
|
+
const preflight = new ClaudeSessionCostTracker({
|
|
2327
|
+
sessionId,
|
|
2328
|
+
restored: tracker.snapshot(),
|
|
2329
|
+
});
|
|
2330
|
+
preflight.recordTurn(meteringTurnInput);
|
|
2331
|
+
}
|
|
2332
|
+
const postTokens = estimateModelRequestTokens([
|
|
2333
|
+
{ role: 'user', content: compacted.summary },
|
|
2334
|
+
]);
|
|
2335
|
+
if (signal?.aborted)
|
|
2336
|
+
throw new AgentRunCancelledError();
|
|
2337
|
+
const ids = await lease.appendCompaction({
|
|
2338
|
+
summary: compacted.summary,
|
|
2339
|
+
trigger: 'manual',
|
|
2340
|
+
preTokens,
|
|
2341
|
+
postTokens,
|
|
2342
|
+
durationMs,
|
|
2343
|
+
});
|
|
2344
|
+
if (meteringTurnInput !== undefined)
|
|
2345
|
+
tracker.recordTurn(meteringTurnInput);
|
|
2346
|
+
this.options.eventSink?.({
|
|
2347
|
+
type: 'compact-boundary',
|
|
2348
|
+
trigger: 'manual',
|
|
2349
|
+
preTokens,
|
|
2350
|
+
uuid: ids.boundaryId,
|
|
2351
|
+
});
|
|
2352
|
+
return { summary: compacted.summary, usage: compacted.usage, preTokens };
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2228
2355
|
this.assertWritable();
|
|
2229
2356
|
const provider = this.provider();
|
|
2230
2357
|
const result = await this.turnStore(sessionId).withLease(async (lease) => {
|
|
@@ -2432,6 +2559,36 @@ export class ClaudeSessionService {
|
|
|
2432
2559
|
return result.value;
|
|
2433
2560
|
}
|
|
2434
2561
|
async fork(parentSessionId, sessionId = randomUUID(), resumeSessionAt) {
|
|
2562
|
+
if (this.nativeTranscriptWritesEnabled()) {
|
|
2563
|
+
this.assertSessionPersistence();
|
|
2564
|
+
const sourcePaths = this.paths(parentSessionId);
|
|
2565
|
+
const targetPaths = this.paths(sessionId);
|
|
2566
|
+
const source = new NativeSessionTranscript({
|
|
2567
|
+
sessionId: parentSessionId,
|
|
2568
|
+
store: new NativeTranscriptStore({
|
|
2569
|
+
transcriptFile: sourcePaths.sessionFile,
|
|
2570
|
+
lockFile: join(sourcePaths.praxisRoot, 'locks', `${parentSessionId}.lock`),
|
|
2571
|
+
}),
|
|
2572
|
+
});
|
|
2573
|
+
const target = new NativeSessionTranscript({
|
|
2574
|
+
sessionId,
|
|
2575
|
+
store: new NativeTranscriptStore({
|
|
2576
|
+
transcriptFile: targetPaths.sessionFile,
|
|
2577
|
+
lockFile: join(targetPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
2578
|
+
}),
|
|
2579
|
+
});
|
|
2580
|
+
await source.forkTo(target, {
|
|
2581
|
+
...(resumeSessionAt === undefined
|
|
2582
|
+
? {}
|
|
2583
|
+
: { atEventId: resumeSessionAt }),
|
|
2584
|
+
ensureExisting: false,
|
|
2585
|
+
});
|
|
2586
|
+
this.options.contextAssembler?.invalidate?.({
|
|
2587
|
+
lifecycleId: sessionId,
|
|
2588
|
+
reason: 'fork',
|
|
2589
|
+
});
|
|
2590
|
+
return { sessionId, parentSessionId };
|
|
2591
|
+
}
|
|
2435
2592
|
this.assertSessionPersistence();
|
|
2436
2593
|
await this.assertNativeWriteTargetIsLegacy(parentSessionId);
|
|
2437
2594
|
this.assertWritable();
|
|
@@ -2447,6 +2604,39 @@ export class ClaudeSessionService {
|
|
|
2447
2604
|
return { sessionId, parentSessionId };
|
|
2448
2605
|
}
|
|
2449
2606
|
async ensureFork(parentSessionId, sessionId, checkpoint) {
|
|
2607
|
+
if (this.nativeTranscriptWritesEnabled()) {
|
|
2608
|
+
this.assertSessionPersistence();
|
|
2609
|
+
const sourcePaths = this.paths(parentSessionId);
|
|
2610
|
+
const targetPaths = this.paths(sessionId);
|
|
2611
|
+
const source = new NativeSessionTranscript({
|
|
2612
|
+
sessionId: parentSessionId,
|
|
2613
|
+
store: new NativeTranscriptStore({
|
|
2614
|
+
transcriptFile: sourcePaths.sessionFile,
|
|
2615
|
+
lockFile: join(sourcePaths.praxisRoot, 'locks', `${parentSessionId}.lock`),
|
|
2616
|
+
}),
|
|
2617
|
+
});
|
|
2618
|
+
const target = new NativeSessionTranscript({
|
|
2619
|
+
sessionId,
|
|
2620
|
+
store: new NativeTranscriptStore({
|
|
2621
|
+
transcriptFile: targetPaths.sessionFile,
|
|
2622
|
+
lockFile: join(targetPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
2623
|
+
}),
|
|
2624
|
+
});
|
|
2625
|
+
await source.forkTo(target, {
|
|
2626
|
+
...(checkpoint?.resumeSessionAt === undefined
|
|
2627
|
+
? {}
|
|
2628
|
+
: { atEventId: checkpoint.resumeSessionAt }),
|
|
2629
|
+
...(checkpoint?.entryCount === undefined
|
|
2630
|
+
? {}
|
|
2631
|
+
: { recordCount: checkpoint.entryCount }),
|
|
2632
|
+
ensureExisting: true,
|
|
2633
|
+
});
|
|
2634
|
+
this.options.contextAssembler?.invalidate?.({
|
|
2635
|
+
lifecycleId: sessionId,
|
|
2636
|
+
reason: 'fork',
|
|
2637
|
+
});
|
|
2638
|
+
return { sessionId, parentSessionId };
|
|
2639
|
+
}
|
|
2450
2640
|
this.assertSessionPersistence();
|
|
2451
2641
|
this.assertWritable();
|
|
2452
2642
|
const expected = await this.nativeForkEntries(parentSessionId, sessionId, checkpoint?.resumeSessionAt, checkpoint?.entryCount);
|
|
@@ -2764,9 +2954,12 @@ export class ClaudeSessionService {
|
|
|
2764
2954
|
const skipUserPrompt = submission.kind === 'retry';
|
|
2765
2955
|
const controller = new TurnTerminalController(this.options.eventSink ?? (() => undefined));
|
|
2766
2956
|
try {
|
|
2957
|
+
if (this.nativeTranscriptWritesEnabled() && name !== undefined) {
|
|
2958
|
+
throw new Error('experimental native session names are not enabled');
|
|
2959
|
+
}
|
|
2767
2960
|
if (requireExisting)
|
|
2768
2961
|
await this.assertNativeWriteTargetIsLegacy(sessionId);
|
|
2769
|
-
this.
|
|
2962
|
+
this.assertTurnWritable();
|
|
2770
2963
|
if (prompt.length === 0 && images.length === 0 && documents.length === 0)
|
|
2771
2964
|
throw new Error('Prompt must not be empty');
|
|
2772
2965
|
if (name !== undefined && name.length === 0) {
|
|
@@ -2776,7 +2969,8 @@ export class ClaudeSessionService {
|
|
|
2776
2969
|
throw new Error('Shell command must not be empty');
|
|
2777
2970
|
}
|
|
2778
2971
|
await this.activateSessionCostTracker(sessionId);
|
|
2779
|
-
if (this.options.sessionPersistence !== false
|
|
2972
|
+
if (this.options.sessionPersistence !== false &&
|
|
2973
|
+
!this.nativeTranscriptWritesEnabled()) {
|
|
2780
2974
|
this.durableMetadataSessions.add(sessionId);
|
|
2781
2975
|
}
|
|
2782
2976
|
await this.ensureFileResources(sessionId, signal);
|
|
@@ -2795,9 +2989,13 @@ export class ClaudeSessionService {
|
|
|
2795
2989
|
}
|
|
2796
2990
|
const sessionPaths = this.paths(sessionId);
|
|
2797
2991
|
const toolResultDirectory = join(sessionPaths.projectRoot, sessionId, 'tool-results');
|
|
2798
|
-
const
|
|
2799
|
-
|
|
2800
|
-
|
|
2992
|
+
const runUnderLease = async (lease, nativeLease) => {
|
|
2993
|
+
const rememberDurableMetadata = (observed) => {
|
|
2994
|
+
if (!nativeLease)
|
|
2995
|
+
this.rememberDurableMetadata(sessionId, observed);
|
|
2996
|
+
};
|
|
2997
|
+
const store = nativeLease ? undefined : this.turnStore(sessionId);
|
|
2998
|
+
if (store && !requireExisting) {
|
|
2801
2999
|
const initialization = name === undefined
|
|
2802
3000
|
? await store.reserve()
|
|
2803
3001
|
: await store.create(this.sessionNameEntries(sessionId, name));
|
|
@@ -2806,8 +3004,21 @@ export class ClaudeSessionService {
|
|
|
2806
3004
|
}
|
|
2807
3005
|
}
|
|
2808
3006
|
let snapshot = await lease.load();
|
|
3007
|
+
const activeTurnMessages = () => nativeLease
|
|
3008
|
+
? nativeLease.activeMessages()
|
|
3009
|
+
: projectClaudeModelMessages(snapshot.entries);
|
|
3010
|
+
const nativeInterruption = nativeLease?.interruption();
|
|
3011
|
+
if (nativeInterruption?.kind === 'indeterminate-tools')
|
|
3012
|
+
throw new Error(`Native tool execution is indeterminate: ${nativeInterruption.callIds.join(', ')}`);
|
|
3013
|
+
const nativeResumeInterrupted = nativeLease !== undefined &&
|
|
3014
|
+
this.options.resumeInterruptedTurn === true &&
|
|
3015
|
+
(nativeInterruption?.kind === 'interrupted-prompt' ||
|
|
3016
|
+
nativeInterruption?.kind === 'interrupted-turn');
|
|
3017
|
+
const shouldSkipUserPrompt = () => skipUserPrompt || nativeResumeInterrupted;
|
|
2809
3018
|
let automaticReplayPrompt;
|
|
2810
|
-
if (
|
|
3019
|
+
if (store &&
|
|
3020
|
+
!nativeLease &&
|
|
3021
|
+
requireExisting &&
|
|
2811
3022
|
this.options.sessionPersistence === false &&
|
|
2812
3023
|
snapshot.entries.length === 0) {
|
|
2813
3024
|
const persisted = await this.store(sessionId).load();
|
|
@@ -2817,17 +3028,18 @@ export class ClaudeSessionService {
|
|
|
2817
3028
|
snapshot = await lease.load();
|
|
2818
3029
|
}
|
|
2819
3030
|
}
|
|
2820
|
-
if (requireExisting && snapshot.entries.length === 0) {
|
|
3031
|
+
if (!nativeLease && requireExisting && snapshot.entries.length === 0) {
|
|
2821
3032
|
throw new Error(`Claude session not found: ${sessionId}`);
|
|
2822
3033
|
}
|
|
2823
|
-
|
|
2824
|
-
if (resumeSessionAt !== undefined) {
|
|
3034
|
+
rememberDurableMetadata(snapshot.entries);
|
|
3035
|
+
if (!nativeLease && resumeSessionAt !== undefined) {
|
|
2825
3036
|
snapshot = {
|
|
2826
3037
|
entries: selectClaudeTranscriptAtMessage(snapshot.entries, resumeSessionAt),
|
|
2827
3038
|
tail: { ...snapshot.tail, branchParentUuid: resumeSessionAt },
|
|
2828
3039
|
};
|
|
2829
3040
|
}
|
|
2830
|
-
else if (
|
|
3041
|
+
else if (!nativeLease &&
|
|
3042
|
+
this.explicitResumeLeafUuids.has(sessionId)) {
|
|
2831
3043
|
const selected = selectClaudeTranscriptFromNewestLeaf(snapshot.entries);
|
|
2832
3044
|
this.explicitResumeLeafUuids.set(sessionId, selected.leafUuid);
|
|
2833
3045
|
snapshot = {
|
|
@@ -2839,7 +3051,7 @@ export class ClaudeSessionService {
|
|
|
2839
3051
|
resumeSessionAt === undefined &&
|
|
2840
3052
|
this.options.resumeInterruptedTurn === true &&
|
|
2841
3053
|
shellCommand === undefined &&
|
|
2842
|
-
!
|
|
3054
|
+
!shouldSkipUserPrompt() &&
|
|
2843
3055
|
!(this.options.approveRecovery !== undefined &&
|
|
2844
3056
|
findUnresolvedClaudeToolCalls(snapshot.entries).length > 0)) {
|
|
2845
3057
|
const interruption = classifyClaudeInterruption(snapshot.entries);
|
|
@@ -2869,7 +3081,7 @@ export class ClaudeSessionService {
|
|
|
2869
3081
|
entries: [...snapshot.entries, stateEntry],
|
|
2870
3082
|
tail: stateTail,
|
|
2871
3083
|
};
|
|
2872
|
-
|
|
3084
|
+
rememberDurableMetadata(snapshot.entries);
|
|
2873
3085
|
}
|
|
2874
3086
|
this.restoreWorktree(snapshot.entries);
|
|
2875
3087
|
this.options.interactiveTools?.restore(sessionId, snapshot.entries);
|
|
@@ -2885,7 +3097,7 @@ export class ClaudeSessionService {
|
|
|
2885
3097
|
entries: [...snapshot.entries, ...entries],
|
|
2886
3098
|
tail: appendResult.tail,
|
|
2887
3099
|
};
|
|
2888
|
-
|
|
3100
|
+
rememberDurableMetadata(snapshot.entries);
|
|
2889
3101
|
}
|
|
2890
3102
|
const agentName = this.options.agent ?? getClaudeAgentSetting(snapshot.entries);
|
|
2891
3103
|
const agent = this.resolveAgent(agentName);
|
|
@@ -2893,7 +3105,7 @@ export class ClaudeSessionService {
|
|
|
2893
3105
|
this.activeProvider = provider;
|
|
2894
3106
|
const effectivePrompt = automaticReplayPrompt ??
|
|
2895
3107
|
(!requireExisting &&
|
|
2896
|
-
!
|
|
3108
|
+
!shouldSkipUserPrompt() &&
|
|
2897
3109
|
!this.options.agentInitialPromptHandledExternally &&
|
|
2898
3110
|
shellCommand === undefined &&
|
|
2899
3111
|
agent?.initialPrompt
|
|
@@ -2911,7 +3123,7 @@ export class ClaudeSessionService {
|
|
|
2911
3123
|
: {}),
|
|
2912
3124
|
warn: (message) => this.options.eventSink?.({ type: 'warning', message }),
|
|
2913
3125
|
});
|
|
2914
|
-
if (!
|
|
3126
|
+
if (!shouldSkipUserPrompt() && shellCommand === undefined) {
|
|
2915
3127
|
turnMemory.prefetch({
|
|
2916
3128
|
turnId: randomUUID(),
|
|
2917
3129
|
prompt: effectivePrompt,
|
|
@@ -2932,7 +3144,11 @@ export class ClaudeSessionService {
|
|
|
2932
3144
|
...(this.options.fileRewindRoots ?? []),
|
|
2933
3145
|
])
|
|
2934
3146
|
: null;
|
|
2935
|
-
const unresolvedToolCalls =
|
|
3147
|
+
const unresolvedToolCalls = nativeLease
|
|
3148
|
+
? nativeInterruption?.kind === 'recoverable-tools'
|
|
3149
|
+
? [...nativeInterruption.calls]
|
|
3150
|
+
: []
|
|
3151
|
+
: findUnresolvedClaudeToolCalls(snapshot.entries);
|
|
2936
3152
|
const pendingRecoveryToolCallIds = new Set(unresolvedToolCalls.map((call) => call.id));
|
|
2937
3153
|
const pendingRecoveryHookOutcomes = [];
|
|
2938
3154
|
const currentHookCwd = () => this.activeCwd();
|
|
@@ -3062,6 +3278,9 @@ export class ClaudeSessionService {
|
|
|
3062
3278
|
claudeVersion: this.options.claudeVersion,
|
|
3063
3279
|
provider,
|
|
3064
3280
|
persistence: this.options.sessionPersistence === false ? 'memory' : 'disk',
|
|
3281
|
+
...(this.options.experimentalNativeTranscriptWrites === true
|
|
3282
|
+
? { experimentalNativeTranscriptWrites: true }
|
|
3283
|
+
: {}),
|
|
3065
3284
|
...(this.options.providerForModel
|
|
3066
3285
|
? { providerForModel: this.options.providerForModel }
|
|
3067
3286
|
: {}),
|
|
@@ -3222,7 +3441,7 @@ export class ClaudeSessionService {
|
|
|
3222
3441
|
}
|
|
3223
3442
|
: interactiveMessageTools;
|
|
3224
3443
|
const capabilityTools = fileHistoryTools
|
|
3225
|
-
? new ClaudeCapabilityToolRegistry(fileHistoryTools, capabilities)
|
|
3444
|
+
? new ClaudeCapabilityToolRegistry(this.teamRegistry(fileHistoryTools, sessionId, this.capabilityToolNames(this.options.teamToolNames, capabilities)), capabilities)
|
|
3226
3445
|
: undefined;
|
|
3227
3446
|
const structuredCapture = this.options.structuredOutputSchema
|
|
3228
3447
|
? { calls: 0, value: undefined }
|
|
@@ -3283,7 +3502,15 @@ export class ClaudeSessionService {
|
|
|
3283
3502
|
let projectMemoryMaintained = false;
|
|
3284
3503
|
let projectMemoryRecallMessages = [];
|
|
3285
3504
|
let observeModelRequestUsage = () => undefined;
|
|
3505
|
+
const durableFollowUps = new DurableFollowUpTracker();
|
|
3286
3506
|
const observer = {
|
|
3507
|
+
...(nativeLease
|
|
3508
|
+
? {
|
|
3509
|
+
toolExecutionStarted: async (call) => {
|
|
3510
|
+
await nativeLease.beginToolExecution(call.id);
|
|
3511
|
+
},
|
|
3512
|
+
}
|
|
3513
|
+
: {}),
|
|
3287
3514
|
modelRequestCompleted: async (input) => {
|
|
3288
3515
|
observeModelRequestUsage(input);
|
|
3289
3516
|
},
|
|
@@ -3304,8 +3531,25 @@ export class ClaudeSessionService {
|
|
|
3304
3531
|
throw new Error('Could not translate assistant response');
|
|
3305
3532
|
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3306
3533
|
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3307
|
-
lastAssistantUuid =
|
|
3308
|
-
|
|
3534
|
+
lastAssistantUuid = nativeLease
|
|
3535
|
+
? await nativeLease.appendMessages({
|
|
3536
|
+
messages: [
|
|
3537
|
+
{
|
|
3538
|
+
role: 'assistant',
|
|
3539
|
+
content: message.content,
|
|
3540
|
+
...(message.thinkingBlocks?.length
|
|
3541
|
+
? { thinkingBlocks: message.thinkingBlocks }
|
|
3542
|
+
: {}),
|
|
3543
|
+
...(message.toolCalls?.length
|
|
3544
|
+
? { toolCalls: message.toolCalls }
|
|
3545
|
+
: {}),
|
|
3546
|
+
},
|
|
3547
|
+
],
|
|
3548
|
+
model: provider.model ?? 'praxis/provider',
|
|
3549
|
+
})
|
|
3550
|
+
: typeof entry.uuid === 'string'
|
|
3551
|
+
? entry.uuid
|
|
3552
|
+
: lastAssistantUuid;
|
|
3309
3553
|
},
|
|
3310
3554
|
toolCompleted: async (call, toolResult) => {
|
|
3311
3555
|
currentTurnToolCalls += 1;
|
|
@@ -3339,31 +3583,42 @@ export class ClaudeSessionService {
|
|
|
3339
3583
|
tail: modeTail,
|
|
3340
3584
|
};
|
|
3341
3585
|
}
|
|
3342
|
-
|
|
3343
|
-
{
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
: {}),
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3586
|
+
if (nativeLease) {
|
|
3587
|
+
await nativeLease.appendToolCompletion({
|
|
3588
|
+
callId: call.id,
|
|
3589
|
+
result: toolResult,
|
|
3590
|
+
...(toolResult.followUpUserMessages === undefined
|
|
3591
|
+
? {}
|
|
3592
|
+
: { followUpUserMessages: toolResult.followUpUserMessages }),
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
else {
|
|
3596
|
+
const [entry] = translateProviderEvents([
|
|
3597
|
+
{
|
|
3598
|
+
type: 'tool-result',
|
|
3599
|
+
toolCallId: call.id,
|
|
3600
|
+
content: toolResult.content,
|
|
3601
|
+
...(toolResult.contentBlocks
|
|
3602
|
+
? { contentBlocks: toolResult.contentBlocks }
|
|
3603
|
+
: {}),
|
|
3604
|
+
...(toolResult.images ? { images: toolResult.images } : {}),
|
|
3605
|
+
...(toolResult.documents
|
|
3606
|
+
? { documents: toolResult.documents }
|
|
3607
|
+
: {}),
|
|
3608
|
+
isError: toolResult.isError,
|
|
3609
|
+
...(toolResult.nativeToolUseResult
|
|
3610
|
+
? { nativeToolUseResult: toolResult.nativeToolUseResult }
|
|
3611
|
+
: {}),
|
|
3612
|
+
...(toolResult.nativeMcpMeta
|
|
3613
|
+
? { nativeMcpMeta: toolResult.nativeMcpMeta }
|
|
3614
|
+
: {}),
|
|
3615
|
+
},
|
|
3616
|
+
], this.translationContext(sessionId, snapshot));
|
|
3617
|
+
if (!entry)
|
|
3618
|
+
throw new Error('Could not translate tool result');
|
|
3619
|
+
const tail = await this.append(lease, snapshot.tail, entry);
|
|
3620
|
+
snapshot = { entries: [...snapshot.entries, entry], tail };
|
|
3621
|
+
}
|
|
3367
3622
|
if (!toolResult.isError && this.options.projectMemoryDirectory) {
|
|
3368
3623
|
const pathValue = call.input.file_path;
|
|
3369
3624
|
const path = typeof pathValue === 'string'
|
|
@@ -3434,6 +3689,7 @@ export class ClaudeSessionService {
|
|
|
3434
3689
|
currentTurnUserMessages?.push(content);
|
|
3435
3690
|
await Promise.all(this.sessionSubagentExecutors(sessionId, true).map((executor) => executor.acknowledgeNotifications([content])));
|
|
3436
3691
|
}
|
|
3692
|
+
await durableFollowUps.followUpUserMessagesCompleted(messages);
|
|
3437
3693
|
},
|
|
3438
3694
|
};
|
|
3439
3695
|
let turnCompleted = false;
|
|
@@ -3446,7 +3702,7 @@ export class ClaudeSessionService {
|
|
|
3446
3702
|
const recoveryRequest = {
|
|
3447
3703
|
cwd: this.activeCwd(),
|
|
3448
3704
|
toolResultDirectory,
|
|
3449
|
-
messages:
|
|
3705
|
+
messages: activeTurnMessages(),
|
|
3450
3706
|
observer,
|
|
3451
3707
|
permissionUpdates: this.sessionPermissionUpdates.get(sessionId) ?? [],
|
|
3452
3708
|
onPermissionUpdates: (updates) => this.applyPermissionUpdates(sessionId, updates),
|
|
@@ -3525,7 +3781,7 @@ export class ClaudeSessionService {
|
|
|
3525
3781
|
contextMessages = [...contextProjection.systemMessages];
|
|
3526
3782
|
};
|
|
3527
3783
|
await refreshRuntimeContext();
|
|
3528
|
-
const expansion =
|
|
3784
|
+
const expansion = shouldSkipUserPrompt()
|
|
3529
3785
|
? { userMessages: [] }
|
|
3530
3786
|
: shellCommand === undefined
|
|
3531
3787
|
? this.options.extensions
|
|
@@ -3586,7 +3842,7 @@ export class ClaudeSessionService {
|
|
|
3586
3842
|
? { documents }
|
|
3587
3843
|
: {}),
|
|
3588
3844
|
}));
|
|
3589
|
-
const agentMentionMessages = shellCommand === undefined && !
|
|
3845
|
+
const agentMentionMessages = shellCommand === undefined && !shouldSkipUserPrompt()
|
|
3590
3846
|
? (this.options.extensions?.agentMentionMessages(effectivePrompt) ?? [])
|
|
3591
3847
|
: [];
|
|
3592
3848
|
const injectAgentMentionContext = (messages) => {
|
|
@@ -3621,7 +3877,7 @@ export class ClaudeSessionService {
|
|
|
3621
3877
|
const contextTransitionPort = (pendingMessages = [], preservedUserMessages = []) => ({
|
|
3622
3878
|
current: () => {
|
|
3623
3879
|
const historyMessages = [
|
|
3624
|
-
...
|
|
3880
|
+
...activeTurnMessages(),
|
|
3625
3881
|
...projectMemoryRecallMessages,
|
|
3626
3882
|
];
|
|
3627
3883
|
return {
|
|
@@ -3649,11 +3905,155 @@ export class ClaudeSessionService {
|
|
|
3649
3905
|
tools: definitions,
|
|
3650
3906
|
}),
|
|
3651
3907
|
propose: async () => {
|
|
3908
|
+
if (nativeLease) {
|
|
3909
|
+
if (!budget)
|
|
3910
|
+
throw new Error('Context budget is unavailable');
|
|
3911
|
+
const historyMessages = activeTurnMessages();
|
|
3912
|
+
if (historyMessages.length === 0)
|
|
3913
|
+
throw new Error('Cannot compact an empty native transcript');
|
|
3914
|
+
if (unresolvedActiveToolCallIds(historyMessages).length > 0)
|
|
3915
|
+
throw new Error('Cannot compact a native transcript with unresolved tool calls');
|
|
3916
|
+
let compactableMessages = historyMessages;
|
|
3917
|
+
let preservedMessages = [];
|
|
3918
|
+
if (preservedUserMessages.length > 0) {
|
|
3919
|
+
let searchFrom = historyMessages.length;
|
|
3920
|
+
const matchedIndexes = [];
|
|
3921
|
+
for (let markerIndex = preservedUserMessages.length - 1; markerIndex >= 0; markerIndex -= 1) {
|
|
3922
|
+
const marker = preservedUserMessages[markerIndex];
|
|
3923
|
+
let found = -1;
|
|
3924
|
+
for (let index = searchFrom - 1; index >= 0; index -= 1) {
|
|
3925
|
+
const message = historyMessages[index];
|
|
3926
|
+
if (message?.role === 'user' &&
|
|
3927
|
+
message.content === marker) {
|
|
3928
|
+
found = index;
|
|
3929
|
+
break;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
if (found < 0)
|
|
3933
|
+
throw new Error('Native automatic compaction could not match the current-turn suffix');
|
|
3934
|
+
matchedIndexes.push(found);
|
|
3935
|
+
searchFrom = found;
|
|
3936
|
+
}
|
|
3937
|
+
const earliest = Math.min(...matchedIndexes);
|
|
3938
|
+
compactableMessages = historyMessages.slice(0, earliest);
|
|
3939
|
+
preservedMessages = historyMessages.slice(earliest);
|
|
3940
|
+
}
|
|
3941
|
+
if (compactableMessages.length === 0)
|
|
3942
|
+
throw new Error('Cannot compact native transcript: no compactable prefix');
|
|
3943
|
+
const preTokens = estimateModelRequestTokens(compactableMessages);
|
|
3944
|
+
const compacted = await (this.options.compactor ?? new ModelCompactor(provider)).compact({
|
|
3945
|
+
messages: compactableMessages,
|
|
3946
|
+
targetTokens: Math.min(8192, Math.max(1, Math.floor(budget.contextWindowTokens / 4))),
|
|
3947
|
+
contextWindowTokens: budget.contextWindowTokens,
|
|
3948
|
+
...(signal ? { signal } : {}),
|
|
3949
|
+
});
|
|
3950
|
+
const { durationMs, durationWithoutRetriesMs } = requireCompactionDurations(compacted);
|
|
3951
|
+
if (signal?.aborted)
|
|
3952
|
+
throw new AgentRunCancelledError();
|
|
3953
|
+
const postTokens = estimateModelRequestTokens([
|
|
3954
|
+
{ role: 'user', content: compacted.summary },
|
|
3955
|
+
...preservedMessages,
|
|
3956
|
+
]);
|
|
3957
|
+
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3958
|
+
? compacted.model
|
|
3959
|
+
: provider.model;
|
|
3960
|
+
const meaningfulUsage = hasNonZeroUsage(compacted.usage);
|
|
3961
|
+
if (meaningfulUsage &&
|
|
3962
|
+
(compactModel === undefined || compactModel.trim() === ''))
|
|
3963
|
+
throw new Error('Auto compact usage requires a nonblank model identity');
|
|
3964
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
3965
|
+
if (!tracker)
|
|
3966
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
3967
|
+
let meteringTurnInput;
|
|
3968
|
+
if (meaningfulUsage &&
|
|
3969
|
+
compactModel !== undefined &&
|
|
3970
|
+
compactModel.trim() !== '') {
|
|
3971
|
+
const pricing = this.options.pricing?.resolve(compactModel);
|
|
3972
|
+
const costUsd = pricing
|
|
3973
|
+
? usageCostUsd(compacted.usage, pricing)
|
|
3974
|
+
: undefined;
|
|
3975
|
+
meteringTurnInput = {
|
|
3976
|
+
model: compactModel,
|
|
3977
|
+
usage: compacted.usage,
|
|
3978
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
3979
|
+
...(compacted.usage.webSearchRequests === undefined
|
|
3980
|
+
? {}
|
|
3981
|
+
: {
|
|
3982
|
+
webSearchRequests: compacted.usage.webSearchRequests,
|
|
3983
|
+
}),
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3986
|
+
let meteringDurationsInput;
|
|
3987
|
+
if (durationMs > 0 || durationWithoutRetriesMs > 0)
|
|
3988
|
+
meteringDurationsInput = {
|
|
3989
|
+
...(durationMs === 0 ? {} : { apiDurationMs: durationMs }),
|
|
3990
|
+
apiDurationWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3991
|
+
};
|
|
3992
|
+
if (meteringTurnInput !== undefined ||
|
|
3993
|
+
meteringDurationsInput !== undefined) {
|
|
3994
|
+
const preflight = new ClaudeSessionCostTracker({
|
|
3995
|
+
sessionId,
|
|
3996
|
+
restored: tracker.snapshot(),
|
|
3997
|
+
});
|
|
3998
|
+
if (meteringTurnInput !== undefined)
|
|
3999
|
+
preflight.recordTurn(meteringTurnInput);
|
|
4000
|
+
if (meteringDurationsInput !== undefined)
|
|
4001
|
+
preflight.recordDurations(meteringDurationsInput);
|
|
4002
|
+
}
|
|
4003
|
+
const summaryMessage = {
|
|
4004
|
+
role: 'user',
|
|
4005
|
+
content: compacted.summary,
|
|
4006
|
+
};
|
|
4007
|
+
const proposedMessages = [
|
|
4008
|
+
...contextMessages,
|
|
4009
|
+
...injectTurnContext([
|
|
4010
|
+
summaryMessage,
|
|
4011
|
+
...preservedMessages,
|
|
4012
|
+
...pendingMessages,
|
|
4013
|
+
]),
|
|
4014
|
+
];
|
|
4015
|
+
return {
|
|
4016
|
+
envelope: { messages: proposedMessages, tools: definitions },
|
|
4017
|
+
commit: async () => {
|
|
4018
|
+
if (signal?.aborted)
|
|
4019
|
+
throw new AgentRunCancelledError();
|
|
4020
|
+
const ids = await nativeLease.appendCompaction({
|
|
4021
|
+
summary: compacted.summary,
|
|
4022
|
+
trigger: 'auto',
|
|
4023
|
+
preTokens,
|
|
4024
|
+
postTokens,
|
|
4025
|
+
durationMs,
|
|
4026
|
+
preservedMessages,
|
|
4027
|
+
});
|
|
4028
|
+
this.options.eventSink?.({
|
|
4029
|
+
type: 'compact-boundary',
|
|
4030
|
+
trigger: 'auto',
|
|
4031
|
+
preTokens,
|
|
4032
|
+
uuid: ids.boundaryId,
|
|
4033
|
+
});
|
|
4034
|
+
compactionUsage = mergeUsage(compactionUsage, compacted.usage);
|
|
4035
|
+
compactionDurationMs =
|
|
4036
|
+
(compactionDurationMs ?? 0) + durationMs;
|
|
4037
|
+
compactionDurationWithoutRetriesMs =
|
|
4038
|
+
(compactionDurationWithoutRetriesMs ?? 0) +
|
|
4039
|
+
durationWithoutRetriesMs;
|
|
4040
|
+
if (meteringTurnInput !== undefined) {
|
|
4041
|
+
tracker.recordTurn(meteringTurnInput);
|
|
4042
|
+
}
|
|
4043
|
+
if (compactModel !== undefined &&
|
|
4044
|
+
compactModel.trim() !== '') {
|
|
4045
|
+
compactionModelUsage = mergeSessionRawModelUsage(compactionModelUsage, { [compactModel]: compacted.usage });
|
|
4046
|
+
}
|
|
4047
|
+
if (meteringDurationsInput !== undefined)
|
|
4048
|
+
tracker.recordDurations(meteringDurationsInput);
|
|
4049
|
+
},
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
3652
4052
|
if (!budget) {
|
|
3653
4053
|
throw new Error('Context budget is unavailable');
|
|
3654
4054
|
}
|
|
3655
4055
|
const historyMessages = [
|
|
3656
|
-
...
|
|
4056
|
+
...activeTurnMessages(),
|
|
3657
4057
|
...projectMemoryRecallMessages,
|
|
3658
4058
|
];
|
|
3659
4059
|
const irreducibleMessages = [
|
|
@@ -3869,7 +4269,7 @@ export class ClaudeSessionService {
|
|
|
3869
4269
|
tail: metadataAppend.tail,
|
|
3870
4270
|
};
|
|
3871
4271
|
}
|
|
3872
|
-
|
|
4272
|
+
rememberDurableMetadata(snapshot.entries);
|
|
3873
4273
|
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'auto', compact_summary: compacted.summary }, 'auto', signal);
|
|
3874
4274
|
// The boundary is durable: mirror Claude's full-compact behavior by
|
|
3875
4275
|
// rerunning SessionStart with source compact and refreshing the
|
|
@@ -3946,6 +4346,20 @@ export class ClaudeSessionService {
|
|
|
3946
4346
|
entries: [...snapshot.entries, userEntry],
|
|
3947
4347
|
tail: userTail,
|
|
3948
4348
|
};
|
|
4349
|
+
if (nativeLease) {
|
|
4350
|
+
await nativeLease.appendMessages({
|
|
4351
|
+
messages: [
|
|
4352
|
+
{
|
|
4353
|
+
role: 'user',
|
|
4354
|
+
content: message.text,
|
|
4355
|
+
...(messageImages.length ? { images: messageImages } : {}),
|
|
4356
|
+
...(index === attachmentIndex && documents.length > 0
|
|
4357
|
+
? { documents }
|
|
4358
|
+
: {}),
|
|
4359
|
+
},
|
|
4360
|
+
],
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
3949
4363
|
}
|
|
3950
4364
|
if (fileHistory && currentPromptId) {
|
|
3951
4365
|
const historySnapshot = await fileHistory.snapshot(snapshot.entries, currentPromptId);
|
|
@@ -3955,7 +4369,7 @@ export class ClaudeSessionService {
|
|
|
3955
4369
|
tail: historyTail,
|
|
3956
4370
|
};
|
|
3957
4371
|
}
|
|
3958
|
-
if (this.options.hooks && !
|
|
4372
|
+
if (this.options.hooks && !shouldSkipUserPrompt()) {
|
|
3959
4373
|
const outcome = await this.options.hooks.run({
|
|
3960
4374
|
...hookSession,
|
|
3961
4375
|
hook_event_name: 'UserPromptSubmit',
|
|
@@ -3986,7 +4400,7 @@ export class ClaudeSessionService {
|
|
|
3986
4400
|
cwd: this.activeCwd(),
|
|
3987
4401
|
sessionId,
|
|
3988
4402
|
toolResultDirectory,
|
|
3989
|
-
messages:
|
|
4403
|
+
messages: activeTurnMessages(),
|
|
3990
4404
|
observer: {
|
|
3991
4405
|
assistantCompleted: async () => undefined,
|
|
3992
4406
|
toolCompleted: async () => undefined,
|
|
@@ -4038,6 +4452,20 @@ export class ClaudeSessionService {
|
|
|
4038
4452
|
entries: [...snapshot.entries, inputEntry, outputEntry],
|
|
4039
4453
|
tail: shellAppend.tail,
|
|
4040
4454
|
};
|
|
4455
|
+
if (nativeLease) {
|
|
4456
|
+
await nativeLease.appendMessages({
|
|
4457
|
+
messages: [
|
|
4458
|
+
{
|
|
4459
|
+
role: 'user',
|
|
4460
|
+
content: `<bash-input>${shellCommand}</bash-input>`,
|
|
4461
|
+
},
|
|
4462
|
+
{
|
|
4463
|
+
role: 'user',
|
|
4464
|
+
content: `<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`,
|
|
4465
|
+
},
|
|
4466
|
+
],
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4041
4469
|
currentTurnUserMessages.push(`<bash-stdout>${stdout}</bash-stdout><bash-stderr>${stderr}</bash-stderr>`);
|
|
4042
4470
|
this.options.eventSink?.({
|
|
4043
4471
|
type: 'shell-result',
|
|
@@ -4051,7 +4479,7 @@ export class ClaudeSessionService {
|
|
|
4051
4479
|
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4052
4480
|
budget.assertFits(budget.evaluate([
|
|
4053
4481
|
...contextMessages,
|
|
4054
|
-
...injectTurnContext(
|
|
4482
|
+
...injectTurnContext(activeTurnMessages()),
|
|
4055
4483
|
], definitions));
|
|
4056
4484
|
}
|
|
4057
4485
|
let stopHookActive = false;
|
|
@@ -4059,7 +4487,7 @@ export class ClaudeSessionService {
|
|
|
4059
4487
|
sessionId,
|
|
4060
4488
|
messages: [
|
|
4061
4489
|
...contextMessages,
|
|
4062
|
-
...injectTurnContext(
|
|
4490
|
+
...injectTurnContext(activeTurnMessages()),
|
|
4063
4491
|
],
|
|
4064
4492
|
stableSystemMessageCount,
|
|
4065
4493
|
cwd: this.activeCwd(),
|
|
@@ -4090,7 +4518,7 @@ export class ClaudeSessionService {
|
|
|
4090
4518
|
return [
|
|
4091
4519
|
...contextMessages,
|
|
4092
4520
|
...injectTurnContext([
|
|
4093
|
-
...
|
|
4521
|
+
...activeTurnMessages(),
|
|
4094
4522
|
...projectMemoryRecallMessages,
|
|
4095
4523
|
]),
|
|
4096
4524
|
];
|
|
@@ -4099,10 +4527,16 @@ export class ClaudeSessionService {
|
|
|
4099
4527
|
subagentExecutor ||
|
|
4100
4528
|
taskTools ||
|
|
4101
4529
|
this.scheduledPrompts ||
|
|
4102
|
-
this.workflowManager
|
|
4530
|
+
this.workflowManager ||
|
|
4531
|
+
this.leadOperations
|
|
4103
4532
|
? {
|
|
4104
4533
|
onStop: async (text) => {
|
|
4105
4534
|
const messages = [];
|
|
4535
|
+
const teamInbox = await this.leadOperations?.projectInbox(sessionId);
|
|
4536
|
+
if (teamInbox) {
|
|
4537
|
+
durableFollowUps.register(teamInbox);
|
|
4538
|
+
messages.push(...teamInbox.messages);
|
|
4539
|
+
}
|
|
4106
4540
|
const outcome = await this.options.hooks?.run({
|
|
4107
4541
|
...hookSession,
|
|
4108
4542
|
hook_event_name: 'Stop',
|
|
@@ -4211,7 +4645,7 @@ export class ClaudeSessionService {
|
|
|
4211
4645
|
runtimeRequest.messages = [
|
|
4212
4646
|
...contextMessages,
|
|
4213
4647
|
...injectTurnContext([
|
|
4214
|
-
...
|
|
4648
|
+
...activeTurnMessages(),
|
|
4215
4649
|
...projectMemoryRecallMessages,
|
|
4216
4650
|
]),
|
|
4217
4651
|
];
|
|
@@ -4254,7 +4688,7 @@ export class ClaudeSessionService {
|
|
|
4254
4688
|
if (!finalLeafUuid) {
|
|
4255
4689
|
throw new Error('Could not locate final assistant response');
|
|
4256
4690
|
}
|
|
4257
|
-
if (!skipUserPrompt) {
|
|
4691
|
+
if (!nativeLease && !skipUserPrompt) {
|
|
4258
4692
|
const lastPrompt = createClaudeLastPromptEntry({
|
|
4259
4693
|
sessionId,
|
|
4260
4694
|
lastPrompt: effectivePrompt,
|
|
@@ -4266,7 +4700,7 @@ export class ClaudeSessionService {
|
|
|
4266
4700
|
tail,
|
|
4267
4701
|
};
|
|
4268
4702
|
}
|
|
4269
|
-
|
|
4703
|
+
rememberDurableMetadata(snapshot.entries);
|
|
4270
4704
|
const totalUsage = mergeUsage(mergeUsage(mergeUsage(recoveryUsage, compactionUsage), shellUsage), result.usage);
|
|
4271
4705
|
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
4272
4706
|
if (!tracker) {
|
|
@@ -4347,7 +4781,7 @@ export class ClaudeSessionService {
|
|
|
4347
4781
|
: { linesRemoved: foregroundLineChanges.linesRemoved }),
|
|
4348
4782
|
});
|
|
4349
4783
|
}
|
|
4350
|
-
const memorySnapshot =
|
|
4784
|
+
const memorySnapshot = activeTurnMessages();
|
|
4351
4785
|
const providerVisibleMessages = [
|
|
4352
4786
|
...contextMessages,
|
|
4353
4787
|
...injectTurnContext(memorySnapshot),
|
|
@@ -4362,7 +4796,9 @@ export class ClaudeSessionService {
|
|
|
4362
4796
|
occupancyTokens: currentContextTokens,
|
|
4363
4797
|
toolCalls: currentTurnToolCalls,
|
|
4364
4798
|
messages: memorySnapshot,
|
|
4365
|
-
projectMessages:
|
|
4799
|
+
projectMessages: nativeLease
|
|
4800
|
+
? []
|
|
4801
|
+
: projectMemoryMessages(snapshot.entries),
|
|
4366
4802
|
directMaintenance: projectMemoryMaintained,
|
|
4367
4803
|
});
|
|
4368
4804
|
turnCompleted = true;
|
|
@@ -4387,11 +4823,39 @@ export class ClaudeSessionService {
|
|
|
4387
4823
|
await this.hookLifecycle.end(sessionId, 'other');
|
|
4388
4824
|
}
|
|
4389
4825
|
}
|
|
4390
|
-
}
|
|
4391
|
-
|
|
4392
|
-
|
|
4826
|
+
};
|
|
4827
|
+
let result;
|
|
4828
|
+
if (this.nativeTranscriptWritesEnabled()) {
|
|
4829
|
+
const nativeStore = new NativeTranscriptStore({
|
|
4830
|
+
transcriptFile: sessionPaths.sessionFile,
|
|
4831
|
+
lockFile: join(sessionPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
4832
|
+
});
|
|
4833
|
+
const nativeTranscript = new NativeSessionTranscript({
|
|
4834
|
+
sessionId,
|
|
4835
|
+
store: nativeStore,
|
|
4836
|
+
});
|
|
4837
|
+
const activationKind = requireExisting
|
|
4838
|
+
? resumeSessionAt === undefined
|
|
4839
|
+
? { kind: 'resume' }
|
|
4840
|
+
: { kind: 'resume', atEventId: resumeSessionAt }
|
|
4841
|
+
: { kind: 'start' };
|
|
4842
|
+
result = await nativeTranscript.withLease(activationKind, async (nativeLease) => {
|
|
4843
|
+
const scratch = new InMemoryTranscriptStore();
|
|
4844
|
+
const scratchResult = await scratch.withLease((scratchLease) => runUnderLease(scratchLease, nativeLease));
|
|
4845
|
+
if (scratchResult.status === 'conflict') {
|
|
4846
|
+
throw new Error(`native scratch transcript conflict: ${scratchResult.reason}`);
|
|
4847
|
+
}
|
|
4848
|
+
return scratchResult.value;
|
|
4849
|
+
});
|
|
4850
|
+
}
|
|
4851
|
+
else {
|
|
4852
|
+
const store = this.turnStore(sessionId);
|
|
4853
|
+
const leaseResult = await store.withLease((lease) => runUnderLease(lease));
|
|
4854
|
+
if (leaseResult.status === 'conflict') {
|
|
4855
|
+
throw new Error(`Claude transcript append conflict: ${leaseResult.reason}`);
|
|
4856
|
+
}
|
|
4857
|
+
result = leaseResult.value;
|
|
4393
4858
|
}
|
|
4394
|
-
const result = leaseResult.value;
|
|
4395
4859
|
controller.complete();
|
|
4396
4860
|
return result;
|
|
4397
4861
|
}
|
|
@@ -5121,10 +5585,18 @@ export class ClaudeSessionService {
|
|
|
5121
5585
|
}
|
|
5122
5586
|
}
|
|
5123
5587
|
assertWritable() {
|
|
5588
|
+
if (this.nativeTranscriptWritesEnabled()) {
|
|
5589
|
+
throw new Error('experimental native operational writes are not enabled');
|
|
5590
|
+
}
|
|
5124
5591
|
if (this.schema.writeMode !== 'read-write') {
|
|
5125
5592
|
throw new Error(`Claude ${this.options.claudeVersion} session is read-only`);
|
|
5126
5593
|
}
|
|
5127
5594
|
}
|
|
5595
|
+
assertTurnWritable() {
|
|
5596
|
+
if (this.nativeTranscriptWritesEnabled())
|
|
5597
|
+
return;
|
|
5598
|
+
this.assertWritable();
|
|
5599
|
+
}
|
|
5128
5600
|
sessionStatus(issue, entryCount) {
|
|
5129
5601
|
if (issue || entryCount === 0)
|
|
5130
5602
|
return 'corrupt';
|
|
@@ -5135,7 +5607,61 @@ export class ClaudeSessionService {
|
|
|
5135
5607
|
return 'read-only';
|
|
5136
5608
|
if (entryCount === 0 || issue)
|
|
5137
5609
|
return 'corrupt';
|
|
5138
|
-
return 'read-only';
|
|
5610
|
+
return this.nativeTranscriptWritesEnabled() ? 'ready' : 'read-only';
|
|
5611
|
+
}
|
|
5612
|
+
nativeTranscriptWritesEnabled() {
|
|
5613
|
+
return this.options.experimentalNativeTranscriptWrites === true;
|
|
5614
|
+
}
|
|
5615
|
+
assertNativeTranscriptOptions() {
|
|
5616
|
+
if (!this.nativeTranscriptWritesEnabled())
|
|
5617
|
+
return;
|
|
5618
|
+
if (this.options.dataPlane !== 'native')
|
|
5619
|
+
throw new Error('experimental native transcript writes require dataPlane native');
|
|
5620
|
+
if (this.options.sessionPersistence === false)
|
|
5621
|
+
throw new Error('experimental native transcript writes require sessionPersistence');
|
|
5622
|
+
const incompatible = [
|
|
5623
|
+
['hooks', this.options.hooks !== undefined],
|
|
5624
|
+
[
|
|
5625
|
+
'conditionalRuleResolver',
|
|
5626
|
+
this.options.conditionalRuleResolver !== undefined,
|
|
5627
|
+
],
|
|
5628
|
+
['extensions', this.options.extensions !== undefined],
|
|
5629
|
+
['agent', this.options.agent !== undefined],
|
|
5630
|
+
['fileCheckpointing', this.options.fileCheckpointing === true],
|
|
5631
|
+
['fileRewindRoots', (this.options.fileRewindRoots?.length ?? 0) > 0],
|
|
5632
|
+
['interactiveTools', this.options.interactiveTools !== undefined],
|
|
5633
|
+
['mcp', this.options.mcp !== undefined],
|
|
5634
|
+
['taskToolNames', (this.options.taskToolNames?.length ?? 0) > 0],
|
|
5635
|
+
[
|
|
5636
|
+
'scheduledToolNames',
|
|
5637
|
+
(this.options.scheduledToolNames?.length ?? 0) > 0,
|
|
5638
|
+
],
|
|
5639
|
+
['enableDynamicWakeups', this.options.enableDynamicWakeups === true],
|
|
5640
|
+
['enableSessionMemory', this.options.enableSessionMemory === true],
|
|
5641
|
+
[
|
|
5642
|
+
'sessionMemoryProviderFactory',
|
|
5643
|
+
this.options.sessionMemoryProviderFactory !== undefined,
|
|
5644
|
+
],
|
|
5645
|
+
[
|
|
5646
|
+
'projectMemoryDirectory',
|
|
5647
|
+
this.options.projectMemoryDirectory !== undefined,
|
|
5648
|
+
],
|
|
5649
|
+
['projectMemoryRecall', this.options.projectMemoryRecall !== undefined],
|
|
5650
|
+
[
|
|
5651
|
+
'projectMemoryExtraction',
|
|
5652
|
+
this.options.projectMemoryExtraction !== undefined,
|
|
5653
|
+
],
|
|
5654
|
+
['sessionKind', this.options.sessionKind !== undefined],
|
|
5655
|
+
['workspace', this.options.workspace !== undefined],
|
|
5656
|
+
['initialWorktree', this.options.initialWorktree === true],
|
|
5657
|
+
['initialWorktreeName', this.options.initialWorktreeName !== undefined],
|
|
5658
|
+
['enableWorktrees', this.options.enableWorktrees === true],
|
|
5659
|
+
['worktreeToolNames', (this.options.worktreeToolNames?.length ?? 0) > 0],
|
|
5660
|
+
['worktreeBaseRef', this.options.worktreeBaseRef !== undefined],
|
|
5661
|
+
];
|
|
5662
|
+
const active = incompatible.find(([, enabled]) => enabled);
|
|
5663
|
+
if (active)
|
|
5664
|
+
throw new Error(`experimental native transcript writes incompatible with option ${active[0]}`);
|
|
5139
5665
|
}
|
|
5140
5666
|
claudeSessionSummary(sessionId, index) {
|
|
5141
5667
|
const metadata = reduceClaudeSessionMetadata(index.entries, sessionId);
|
|
@@ -5155,6 +5681,8 @@ export class ClaudeSessionService {
|
|
|
5155
5681
|
return this.options.provider;
|
|
5156
5682
|
}
|
|
5157
5683
|
async assertNativeWriteTargetIsLegacy(sessionId) {
|
|
5684
|
+
if (this.nativeTranscriptWritesEnabled())
|
|
5685
|
+
return;
|
|
5158
5686
|
if (this.options.dataPlane !== 'native')
|
|
5159
5687
|
return;
|
|
5160
5688
|
const existing = await readNativeTranscript(this.paths(sessionId).sessionFile);
|
|
@@ -5361,6 +5889,7 @@ export class ClaudeSessionService {
|
|
|
5361
5889
|
...(this.options.enableSubagents === undefined
|
|
5362
5890
|
? {}
|
|
5363
5891
|
: { subagents: this.options.enableSubagents }),
|
|
5892
|
+
teams: this.leadOperations !== null,
|
|
5364
5893
|
...(this.options.toolCapabilityEnvironment
|
|
5365
5894
|
? { env: this.options.toolCapabilityEnvironment }
|
|
5366
5895
|
: {}),
|
|
@@ -5377,7 +5906,20 @@ export class ClaudeSessionService {
|
|
|
5377
5906
|
'CronDelete',
|
|
5378
5907
|
'CronList',
|
|
5379
5908
|
'ScheduleWakeup',
|
|
5380
|
-
].includes(name) || capabilities.has(name))
|
|
5909
|
+
].includes(name) || capabilities.has(name))
|
|
5910
|
+
.filter((name) => !name.startsWith('Team') || capabilities.has(name));
|
|
5911
|
+
}
|
|
5912
|
+
teamRegistry(base, sessionId, enabledTools) {
|
|
5913
|
+
if (enabledTools.length === 0)
|
|
5914
|
+
return base;
|
|
5915
|
+
const operations = this.leadOperations;
|
|
5916
|
+
if (!operations)
|
|
5917
|
+
return base;
|
|
5918
|
+
const factory = this.options.teamLeadToolRegistryFactory;
|
|
5919
|
+
if (!factory) {
|
|
5920
|
+
throw new Error('Team lead operations require a Team lead tool registry factory');
|
|
5921
|
+
}
|
|
5922
|
+
return factory(base, operations, sessionId, enabledTools);
|
|
5381
5923
|
}
|
|
5382
5924
|
async append(lease, tail, entry) {
|
|
5383
5925
|
const result = await lease.append(tail, entry);
|