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.
Files changed (86) hide show
  1. package/README.md +16 -2
  2. package/dist/application/durable-follow-up.d.ts +14 -0
  3. package/dist/application/durable-follow-up.js +52 -0
  4. package/dist/application/native-session-transcript.d.ts +72 -0
  5. package/dist/application/native-session-transcript.js +351 -0
  6. package/dist/application/native-sidechain-transcript.d.ts +34 -0
  7. package/dist/application/native-sidechain-transcript.js +156 -0
  8. package/dist/application/session-service.d.ts +15 -0
  9. package/dist/application/session-service.js +611 -69
  10. package/dist/application/subagent-service.d.ts +5 -1
  11. package/dist/application/subagent-service.js +391 -143
  12. package/dist/application/team-agent-runtime.d.ts +29 -0
  13. package/dist/application/team-agent-runtime.js +169 -0
  14. package/dist/application/team-capability.d.ts +27 -0
  15. package/dist/application/team-capability.js +41 -0
  16. package/dist/application/team-lead-operations.d.ts +38 -0
  17. package/dist/application/team-lead-operations.js +155 -0
  18. package/dist/application/team-mailbox.d.ts +54 -0
  19. package/dist/application/team-mailbox.js +196 -0
  20. package/dist/application/team-manager.d.ts +113 -0
  21. package/dist/application/team-manager.js +681 -0
  22. package/dist/application/team-workspace.d.ts +29 -0
  23. package/dist/application/team-workspace.js +197 -0
  24. package/dist/application/transcript-projection.d.ts +2 -0
  25. package/dist/application/transcript-projection.js +30 -1
  26. package/dist/cli/interactive.d.ts +7 -0
  27. package/dist/cli/interactive.js +387 -136
  28. package/dist/cli/tui/claude-style.d.ts +8 -25
  29. package/dist/cli/tui/claude-style.js +158 -160
  30. package/dist/cli/tui/decision-surface-model.d.ts +71 -0
  31. package/dist/cli/tui/decision-surface-model.js +131 -0
  32. package/dist/cli/tui/decision-surface.d.ts +7 -0
  33. package/dist/cli/tui/decision-surface.js +41 -0
  34. package/dist/cli/tui/help-surface-model.d.ts +68 -0
  35. package/dist/cli/tui/help-surface-model.js +131 -0
  36. package/dist/cli/tui/mcp-panel.js +1 -2
  37. package/dist/cli/tui/permission-surface-model.d.ts +195 -0
  38. package/dist/cli/tui/permission-surface-model.js +360 -0
  39. package/dist/cli/tui/permission-surface.d.ts +7 -0
  40. package/dist/cli/tui/permission-surface.js +69 -0
  41. package/dist/cli/tui/presentation-environment.d.ts +32 -0
  42. package/dist/cli/tui/presentation-environment.js +149 -0
  43. package/dist/cli/tui/tool-permission.js +7 -3
  44. package/dist/cli/tui/transcript-presentation.d.ts +19 -1
  45. package/dist/cli/tui/transcript-presentation.js +26 -6
  46. package/dist/cli/tui/transcript-viewport.d.ts +18 -0
  47. package/dist/cli/tui/transcript-viewport.js +517 -111
  48. package/dist/cli/tui/transcript-window-model.d.ts +108 -0
  49. package/dist/cli/tui/transcript-window-model.js +772 -0
  50. package/dist/cli/tui/tui-screen-model.d.ts +114 -0
  51. package/dist/cli/tui/tui-screen-model.js +210 -0
  52. package/dist/cli/tui/tui-view-model.d.ts +2 -49
  53. package/dist/cli/tui/tui-view-model.js +1 -68
  54. package/dist/cli-runtime.d.ts +2 -0
  55. package/dist/cli-runtime.js +397 -32
  56. package/dist/compatibility/claude/transcript-codec.js +6 -0
  57. package/dist/core/permission-policy.d.ts +4 -0
  58. package/dist/core/permission-policy.js +21 -0
  59. package/dist/core/runtime.d.ts +1 -0
  60. package/dist/core/runtime.js +5 -1
  61. package/dist/core/team-mailbox.d.ts +54 -0
  62. package/dist/core/team-mailbox.js +212 -0
  63. package/dist/core/team-ownership.d.ts +84 -0
  64. package/dist/core/team-ownership.js +505 -0
  65. package/dist/core/tool-execution-scheduler.js +6 -1
  66. package/dist/core/transcript-event.d.ts +5 -1
  67. package/dist/core/transcript-event.js +12 -0
  68. package/dist/persistence/claude-transcript-store.d.ts +2 -1
  69. package/dist/persistence/claude-transcript-store.js +2 -10
  70. package/dist/persistence/native-team-mailbox.d.ts +68 -0
  71. package/dist/persistence/native-team-mailbox.js +542 -0
  72. package/dist/persistence/native-team-store.d.ts +34 -0
  73. package/dist/persistence/native-team-store.js +257 -0
  74. package/dist/persistence/native-transcript-store.d.ts +69 -0
  75. package/dist/persistence/native-transcript-store.js +321 -0
  76. package/dist/persistence/transcript-file-append.d.ts +2 -0
  77. package/dist/persistence/transcript-file-append.js +10 -0
  78. package/dist/tools/claude-capabilities.d.ts +4 -0
  79. package/dist/tools/claude-capabilities.js +18 -2
  80. package/dist/tools/team-lead-tools.d.ts +19 -0
  81. package/dist/tools/team-lead-tools.js +479 -0
  82. package/dist/tools/team-member-tools.d.ts +21 -0
  83. package/dist/tools/team-member-tools.js +259 -0
  84. package/package.json +1 -1
  85. package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
  86. 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 capabilityRegistry = new ClaudeCapabilityToolRegistry(interactiveRegistry, capabilities);
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.assertWritable();
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 store = this.turnStore(sessionId);
2799
- const leaseResult = await store.withLease(async (lease) => {
2800
- if (!requireExisting) {
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 (requireExisting &&
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
- this.rememberDurableMetadata(sessionId, snapshot.entries);
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 (this.explicitResumeLeafUuids.has(sessionId)) {
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
- !skipUserPrompt &&
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
- this.rememberDurableMetadata(sessionId, snapshot.entries);
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
- this.rememberDurableMetadata(sessionId, snapshot.entries);
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
- !skipUserPrompt &&
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 (!skipUserPrompt && shellCommand === undefined) {
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 = findUnresolvedClaudeToolCalls(snapshot.entries);
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
- typeof entry.uuid === 'string' ? entry.uuid : lastAssistantUuid;
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
- const [entry] = translateProviderEvents([
3343
- {
3344
- type: 'tool-result',
3345
- toolCallId: call.id,
3346
- content: toolResult.content,
3347
- ...(toolResult.contentBlocks
3348
- ? { contentBlocks: toolResult.contentBlocks }
3349
- : {}),
3350
- ...(toolResult.images ? { images: toolResult.images } : {}),
3351
- ...(toolResult.documents
3352
- ? { documents: toolResult.documents }
3353
- : {}),
3354
- isError: toolResult.isError,
3355
- ...(toolResult.nativeToolUseResult
3356
- ? { nativeToolUseResult: toolResult.nativeToolUseResult }
3357
- : {}),
3358
- ...(toolResult.nativeMcpMeta
3359
- ? { nativeMcpMeta: toolResult.nativeMcpMeta }
3360
- : {}),
3361
- },
3362
- ], this.translationContext(sessionId, snapshot));
3363
- if (!entry)
3364
- throw new Error('Could not translate tool result');
3365
- const tail = await this.append(lease, snapshot.tail, entry);
3366
- snapshot = { entries: [...snapshot.entries, entry], tail };
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: projectClaudeModelMessages(snapshot.entries),
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 = skipUserPrompt
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 && !skipUserPrompt
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
- ...projectClaudeModelMessages(snapshot.entries),
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
- ...projectClaudeModelMessages(snapshot.entries),
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
- this.rememberDurableMetadata(sessionId, snapshot.entries);
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 && !skipUserPrompt) {
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: projectClaudeModelMessages(snapshot.entries),
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(projectClaudeModelMessages(snapshot.entries)),
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(projectClaudeModelMessages(snapshot.entries)),
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
- ...projectClaudeModelMessages(snapshot.entries),
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
- ...projectClaudeModelMessages(snapshot.entries),
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
- this.rememberDurableMetadata(sessionId, snapshot.entries);
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 = projectClaudeModelMessages(snapshot.entries);
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: projectMemoryMessages(snapshot.entries),
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
- if (leaseResult.status === 'conflict') {
4392
- throw new Error(`Claude transcript append conflict: ${leaseResult.reason}`);
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);