@gkorepanov/ccodex 0.4.14 → 0.4.16

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.
@@ -27,6 +27,7 @@ import { StaleClaudeRuntimeSettingsError, createProviderRuntime, providerPermiss
27
27
  import { isRetryableClaudeRuntimeStartupError, } from "./providerRuntime.js";
28
28
  import { assistantHasTools, backgroundOutputFile, backgroundTaskId, isNoQueryAcknowledgement, serverToolResult, toolResults, } from "./providerFacts.js";
29
29
  import { normalizeClaudeModelIdentifier } from "../modelSelection.js";
30
+ import { StreamingFileChangePreview } from "../streamingFilePreview.js";
30
31
  const nullSource = { providerEventId: null, providerEventType: null };
31
32
  const providerJournalMaxEvents = 20_000;
32
33
  const providerJournalMaxBytes = 256 * 1_024 * 1_024;
@@ -100,7 +101,7 @@ function commandLane(command) {
100
101
  return "control";
101
102
  if (["providerEventStarted", "providerEventFinished", "providerBoundary", "providerRetract",
102
103
  "conversationReset", "modelFallback", "fastModeStatus", "systemNotice", "runtimeNotification", "mainStream",
103
- "accountUsage", "accountCost", "publishUsage", "lifecycle", "compactBoundary", "compactFailed",
104
+ "accountUsage", "accountCost", "publishUsage", "lifecycle", "autoCompactStarted", "compactBoundary", "compactFailed",
104
105
  "postCompact",
105
106
  ].includes(command.type))
106
107
  return "provider";
@@ -134,6 +135,7 @@ export class ClaudeSession {
134
135
  scopes = new Map();
135
136
  tasks = new Map();
136
137
  compaction;
138
+ automaticCompaction;
137
139
  compactionActions = [];
138
140
  lastPublishedUsage;
139
141
  lifecycle;
@@ -594,6 +596,7 @@ export class ClaudeSession {
594
596
  const projection = {
595
597
  context: new AsyncLocalStorage(),
596
598
  processEpoch: uuidv7(),
599
+ filePreviews: new Map(),
597
600
  providerSequence: 0,
598
601
  };
599
602
  try {
@@ -1340,6 +1343,13 @@ export class ClaudeSession {
1340
1343
  if (status === "requesting") {
1341
1344
  await this.providerSubmitLifecycle(projection, runtimeGeneration, { type: "request", messageStarted: false });
1342
1345
  }
1346
+ else if (status === "compacting") {
1347
+ await this.submitProviderProjection(runtimeGeneration, {
1348
+ type: "autoCompactStarted",
1349
+ runtimeGeneration,
1350
+ source: this.providerFactSource(projection),
1351
+ });
1352
+ }
1343
1353
  return "stateOnly";
1344
1354
  }
1345
1355
  if (message.type === "system" && message.subtype === "session_state_changed") {
@@ -1762,6 +1772,37 @@ export class ClaudeSession {
1762
1772
  fact,
1763
1773
  });
1764
1774
  }
1775
+ async applyProviderToolInput(projection, runtimeGeneration, index, delta, ownerThreadId = this.threadId) {
1776
+ const projected = await this.applyProviderMainStream(projection, runtimeGeneration, {
1777
+ kind: "toolInput", index, delta,
1778
+ }, ownerThreadId);
1779
+ const tool = projected?.tool;
1780
+ if (tool?.name !== "Write" && tool?.name !== "Edit")
1781
+ return;
1782
+ const key = `${ownerThreadId}\0${index}`;
1783
+ let state = projection.filePreviews.get(key);
1784
+ if (!state) {
1785
+ state = { providerId: tool.providerId, preview: new StreamingFileChangePreview(tool.name, tool.cwd) };
1786
+ projection.filePreviews.set(key, state);
1787
+ }
1788
+ const change = await state.preview.push(delta);
1789
+ if (change)
1790
+ await this.applyProviderMainStream(projection, runtimeGeneration, {
1791
+ kind: "toolFiles", providerId: state.providerId, changes: [change],
1792
+ }, ownerThreadId);
1793
+ }
1794
+ async finishProviderToolInput(projection, runtimeGeneration, index, ownerThreadId = this.threadId) {
1795
+ const key = `${ownerThreadId}\0${index}`;
1796
+ const state = projection.filePreviews.get(key);
1797
+ if (!state)
1798
+ return;
1799
+ projection.filePreviews.delete(key);
1800
+ const change = await state.preview.finish();
1801
+ if (change)
1802
+ await this.applyProviderMainStream(projection, runtimeGeneration, {
1803
+ kind: "toolFiles", providerId: state.providerId, changes: [change],
1804
+ }, ownerThreadId);
1805
+ }
1765
1806
  async canUseProviderTool(projection, runtimeGeneration, name, input, options) {
1766
1807
  const context = await this.providerCallbackContext(runtimeGeneration);
1767
1808
  if (!context)
@@ -2183,15 +2224,12 @@ export class ClaudeSession {
2183
2224
  }, childThreadId);
2184
2225
  }
2185
2226
  else if (event.delta.type === "input_json_delta") {
2186
- await this.applyProviderMainStream(projection, runtimeGeneration, {
2187
- kind: "toolInput",
2188
- index: event.index,
2189
- delta: event.delta.partial_json,
2190
- }, childThreadId);
2227
+ await this.applyProviderToolInput(projection, runtimeGeneration, event.index, event.delta.partial_json, childThreadId);
2191
2228
  }
2192
2229
  return;
2193
2230
  }
2194
2231
  if (event.type === "content_block_stop") {
2232
+ await this.finishProviderToolInput(projection, runtimeGeneration, event.index, childThreadId);
2195
2233
  await this.applyProviderMainStream(projection, runtimeGeneration, { kind: "blockStop", index: event.index }, childThreadId);
2196
2234
  }
2197
2235
  }
@@ -2510,15 +2548,12 @@ export class ClaudeSession {
2510
2548
  });
2511
2549
  }
2512
2550
  else if (event.delta.type === "input_json_delta") {
2513
- await this.applyProviderMainStream(projection, runtimeGeneration, {
2514
- kind: "toolInput",
2515
- index: event.index,
2516
- delta: event.delta.partial_json,
2517
- });
2551
+ await this.applyProviderToolInput(projection, runtimeGeneration, event.index, event.delta.partial_json);
2518
2552
  }
2519
2553
  return;
2520
2554
  }
2521
2555
  if (event.type === "content_block_stop") {
2556
+ await this.finishProviderToolInput(projection, runtimeGeneration, event.index);
2522
2557
  await this.applyProviderMainStream(projection, runtimeGeneration, { kind: "blockStop", index: event.index });
2523
2558
  }
2524
2559
  }
@@ -4472,10 +4507,15 @@ export class ClaudeSession {
4472
4507
  if (command.runtimeGeneration !== this.runtimeGeneration)
4473
4508
  return { admitted: false, hidden: false };
4474
4509
  return {
4475
- admitted: command.trigger === "auto" ? !this.compaction && Boolean(this.activeNormalTurn())
4510
+ admitted: command.trigger === "auto"
4511
+ ? !this.compaction && Boolean(this.automaticCompaction || this.activeNormalTurn())
4476
4512
  : Boolean(this.compaction && !this.compaction.cancellation),
4477
4513
  hidden: Boolean(this.compaction?.hidden),
4478
4514
  };
4515
+ case "autoCompactStarted":
4516
+ if (command.runtimeGeneration !== this.runtimeGeneration)
4517
+ return undefined;
4518
+ return this.startAutomaticCompaction(command.runtimeGeneration, command.source);
4479
4519
  case "compactBoundary":
4480
4520
  if (command.runtimeGeneration !== this.runtimeGeneration)
4481
4521
  return undefined;
@@ -4501,8 +4541,10 @@ export class ClaudeSession {
4501
4541
  : this.compactionProjection();
4502
4542
  }
4503
4543
  case "compactFailed":
4504
- if (command.runtimeGeneration !== this.runtimeGeneration || !this.compaction)
4544
+ if (command.runtimeGeneration !== this.runtimeGeneration)
4505
4545
  return undefined;
4546
+ if (!this.compaction)
4547
+ return this.completeAutomaticCompaction(command.source);
4506
4548
  if (this.compaction.cancellation)
4507
4549
  return this.compactionProjection();
4508
4550
  return this.completeCompaction("failed", command.message, command.codexErrorInfo, command.source);
@@ -4842,21 +4884,77 @@ export class ClaudeSession {
4842
4884
  if (this.compaction)
4843
4885
  return this.compactionProjection();
4844
4886
  const record = this.requireRecord(true);
4845
- const turn = this.activeNormalTurn(record);
4887
+ const automatic = this.automaticCompaction;
4888
+ const turn = automatic
4889
+ ? this.repository.readTurn(this.threadId, automatic.turnId)
4890
+ : this.activeNormalTurn(record);
4846
4891
  if (!turn)
4847
4892
  return undefined;
4893
+ const item = automatic
4894
+ ? turn.items.find((candidate) => candidate.id === automatic.itemId)
4895
+ : undefined;
4848
4896
  const updated = { ...record, lastClaudeMessageUuid: boundary };
4849
- this.commitState(updated, [{
4897
+ this.commitState(updated, [
4898
+ ...(item?.type === "contextCompaction" ? [{
4899
+ turnId: turn.id,
4900
+ method: "item/completed",
4901
+ params: { item, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() },
4902
+ providerEventId: source.providerEventId,
4903
+ providerEventType: source.providerEventType,
4904
+ }] : []),
4905
+ {
4850
4906
  turnId: turn.id,
4851
4907
  method: "thread/compacted",
4852
4908
  params: { threadId: this.threadId, turnId: turn.id },
4853
4909
  providerEventId: source.providerEventId,
4854
4910
  providerEventType: source.providerEventType,
4855
- }], turn, false, {
4911
+ },
4912
+ ], turn, false, {
4856
4913
  ownerThreadId: this.threadId,
4857
4914
  turnId: turn.id,
4858
4915
  messageUuid: boundary,
4859
4916
  });
4917
+ if (automatic)
4918
+ this.automaticCompaction = undefined;
4919
+ return { turnId: turn.id, terminal: false };
4920
+ }
4921
+ startAutomaticCompaction(runtimeGeneration, source) {
4922
+ if (this.compaction)
4923
+ return this.compactionProjection();
4924
+ const turn = this.activeNormalTurn();
4925
+ if (!turn)
4926
+ return undefined;
4927
+ if (this.automaticCompaction?.turnId === turn.id) {
4928
+ return { turnId: turn.id, terminal: false };
4929
+ }
4930
+ const item = {
4931
+ type: "contextCompaction",
4932
+ id: uuidv7(),
4933
+ };
4934
+ turn.items.push(item);
4935
+ this.publishTurn(turn, "item/started", {
4936
+ item, threadId: this.threadId, turnId: turn.id, startedAtMs: Date.now(),
4937
+ }, source);
4938
+ this.automaticCompaction = { turnId: turn.id, itemId: item.id, runtimeGeneration };
4939
+ return { turnId: turn.id, terminal: false };
4940
+ }
4941
+ completeAutomaticCompaction(source) {
4942
+ const automatic = this.automaticCompaction;
4943
+ if (!automatic)
4944
+ return undefined;
4945
+ const record = this.requireRecord(false);
4946
+ const turn = this.repository.readTurn(this.threadId, automatic.turnId);
4947
+ const item = turn?.items.find((candidate) => candidate.id === automatic.itemId);
4948
+ if (!turn || item?.type !== "contextCompaction")
4949
+ return undefined;
4950
+ this.commitState(record, [{
4951
+ turnId: turn.id,
4952
+ method: "item/completed",
4953
+ params: { item, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() },
4954
+ providerEventId: source.providerEventId,
4955
+ providerEventType: source.providerEventType,
4956
+ }], turn);
4957
+ this.automaticCompaction = undefined;
4860
4958
  return { turnId: turn.id, terminal: false };
4861
4959
  }
4862
4960
  completeCompaction(status, errorMessage, codexErrorInfo, source, boundary) {
@@ -4906,7 +5004,7 @@ export class ClaudeSession {
4906
5004
  }
4907
5005
  activeNormalTurn(record = this.requireRecord(true)) {
4908
5006
  return record.thread.turns.findLast((turn) => turn.status === "inProgress"
4909
- && !turn.items.some((item) => item.type === "contextCompaction"));
5007
+ && turn.items.some((item) => item.type !== "contextCompaction"));
4910
5008
  }
4911
5009
  startTurn(items, threadPatch = {}, id = uuidv7(), emitEvents = true) {
4912
5010
  const record = this.requireRecord(false);
@@ -5076,7 +5174,21 @@ export class ClaudeSession {
5076
5174
  updatedAt: turn.completedAt, recencyAt: turn.completedAt,
5077
5175
  },
5078
5176
  };
5079
- const events = [...terminalEvents.map((event) => ({
5177
+ const automatic = this.automaticCompaction?.turnId === turn.id
5178
+ ? this.automaticCompaction
5179
+ : undefined;
5180
+ const automaticItem = automatic
5181
+ ? turn.items.find((item) => item.id === automatic.itemId)
5182
+ : undefined;
5183
+ const events = [
5184
+ ...(automaticItem?.type === "contextCompaction" ? [{
5185
+ turnId: turn.id,
5186
+ method: "item/completed",
5187
+ params: { item: automaticItem, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() },
5188
+ providerEventId: source.providerEventId,
5189
+ providerEventType: source.providerEventType,
5190
+ }] : []),
5191
+ ...terminalEvents.map((event) => ({
5080
5192
  ...event,
5081
5193
  providerEventId: event.providerEventId ?? source.providerEventId,
5082
5194
  providerEventType: event.providerEventType ?? source.providerEventType,
@@ -5086,7 +5198,8 @@ export class ClaudeSession {
5086
5198
  params: { threadId: this.threadId, status: updated.thread.status },
5087
5199
  providerEventId: source.providerEventId,
5088
5200
  providerEventType: source.providerEventType,
5089
- }];
5201
+ }
5202
+ ];
5090
5203
  if (turn.error && emitError)
5091
5204
  events.push({
5092
5205
  turnId: turn.id,
@@ -5105,6 +5218,8 @@ export class ClaudeSession {
5105
5218
  providerEventType: source.providerEventType,
5106
5219
  });
5107
5220
  this.commitState(updated, events, turn, false, providerBoundary, emitEvents);
5221
+ if (automatic)
5222
+ this.automaticCompaction = undefined;
5108
5223
  return { record: updated, turn };
5109
5224
  }
5110
5225
  goalContext() {