@gkorepanov/ccodex 0.4.9 → 0.4.11

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 (220) hide show
  1. package/README.md +1 -1
  2. package/config/compatibility.json +3 -3
  3. package/dist/claude/goalTools.js +1 -1
  4. package/dist/claude/goalTools.js.map +1 -1
  5. package/dist/claude/hookMapper.d.ts +1 -0
  6. package/dist/claude/hookMapper.js +1 -0
  7. package/dist/claude/hookMapper.js.map +1 -1
  8. package/dist/claude/inputMapper.js +4 -1
  9. package/dist/claude/inputMapper.js.map +1 -1
  10. package/dist/claude/rateLimits.d.ts +1 -0
  11. package/dist/claude/rateLimits.js +2 -1
  12. package/dist/claude/rateLimits.js.map +1 -1
  13. package/dist/claude/service.d.ts +13 -1
  14. package/dist/claude/service.js +156 -21
  15. package/dist/claude/service.js.map +1 -1
  16. package/dist/claude/session/commands.d.ts +3 -0
  17. package/dist/claude/session/goalState.js +9 -0
  18. package/dist/claude/session/goalState.js.map +1 -1
  19. package/dist/claude/session/repository.d.ts +1 -1
  20. package/dist/claude/session/repository.js +2 -2
  21. package/dist/claude/session/repository.js.map +1 -1
  22. package/dist/claude/session/scopeState.js +3 -1
  23. package/dist/claude/session/scopeState.js.map +1 -1
  24. package/dist/claude/session/session.js +13 -2
  25. package/dist/claude/session/session.js.map +1 -1
  26. package/dist/claude/session/usage.js +1 -0
  27. package/dist/claude/session/usage.js.map +1 -1
  28. package/dist/claude/toolMapper.js +2 -2
  29. package/dist/claude/toolMapper.js.map +1 -1
  30. package/dist/codex/generated/ClientRequest.d.ts +20 -0
  31. package/dist/codex/generated/CodexResponseHandoffMode.d.ts +1 -0
  32. package/dist/codex/generated/CodexResponseHandoffMode.js +3 -0
  33. package/dist/codex/generated/CodexResponseHandoffMode.js.map +1 -0
  34. package/dist/codex/generated/ContentItem.d.ts +3 -0
  35. package/dist/codex/generated/FunctionCallOutputContentItem.d.ts +3 -0
  36. package/dist/codex/generated/InputModality.d.ts +1 -1
  37. package/dist/codex/generated/LegacyAppPathString.d.ts +7 -6
  38. package/dist/codex/generated/RealtimeConversationVersion.d.ts +1 -1
  39. package/dist/codex/generated/ResponseItem.d.ts +15 -14
  40. package/dist/codex/generated/ResponseItemId.d.ts +5 -0
  41. package/dist/codex/generated/ResponseItemId.js +3 -0
  42. package/dist/codex/generated/ResponseItemId.js.map +1 -0
  43. package/dist/codex/generated/ResponseItemRuntimeSchema.js +2 -2
  44. package/dist/codex/generated/ResponseItemRuntimeSchema.js.map +1 -1
  45. package/dist/codex/generated/ReviewDecision.d.ts +5 -1
  46. package/dist/codex/generated/ServerNotification.d.ts +11 -0
  47. package/dist/codex/generated/ServerNotificationEnvelope.d.ts +302 -0
  48. package/dist/codex/generated/ServerNotificationEnvelope.js +3 -0
  49. package/dist/codex/generated/ServerNotificationEnvelope.js.map +1 -0
  50. package/dist/codex/generated/SleepItem.d.ts +7 -0
  51. package/dist/codex/generated/SleepItem.js +3 -0
  52. package/dist/codex/generated/SleepItem.js.map +1 -0
  53. package/dist/codex/generated/WebSearchItem.d.ts +8 -0
  54. package/dist/codex/generated/index.d.ts +4 -1
  55. package/dist/codex/generated/index.js.map +1 -1
  56. package/dist/codex/generated/v2/Account.d.ts +1 -2
  57. package/dist/codex/generated/v2/AppToolSummary.d.ts +8 -0
  58. package/dist/codex/generated/v2/AppToolSummary.js +3 -0
  59. package/dist/codex/generated/v2/AppToolSummary.js.map +1 -0
  60. package/dist/codex/generated/v2/AppsInstalledParams.d.ts +14 -0
  61. package/dist/codex/generated/v2/AppsInstalledParams.js +3 -0
  62. package/dist/codex/generated/v2/AppsInstalledParams.js.map +1 -0
  63. package/dist/codex/generated/v2/AppsInstalledResponse.d.ts +7 -0
  64. package/dist/codex/generated/v2/AppsInstalledResponse.js +3 -0
  65. package/dist/codex/generated/v2/AppsInstalledResponse.js.map +1 -0
  66. package/dist/codex/generated/v2/AppsReadParams.d.ts +14 -0
  67. package/dist/codex/generated/v2/AppsReadParams.js +3 -0
  68. package/dist/codex/generated/v2/AppsReadParams.js.map +1 -0
  69. package/dist/codex/generated/v2/AppsReadResponse.d.ts +8 -0
  70. package/dist/codex/generated/v2/AppsReadResponse.js +3 -0
  71. package/dist/codex/generated/v2/AppsReadResponse.js.map +1 -0
  72. package/dist/codex/generated/v2/ConfiguredHookHandler.d.ts +7 -0
  73. package/dist/codex/generated/v2/ConnectorMetadata.d.ts +15 -0
  74. package/dist/codex/generated/v2/ConnectorMetadata.js +3 -0
  75. package/dist/codex/generated/v2/ConnectorMetadata.js.map +1 -0
  76. package/dist/codex/generated/v2/DynamicToolCallOutputContentItem.d.ts +3 -0
  77. package/dist/codex/generated/v2/EnvironmentConnectionNotification.d.ts +4 -0
  78. package/dist/codex/generated/v2/EnvironmentConnectionNotification.js +3 -0
  79. package/dist/codex/generated/v2/EnvironmentConnectionNotification.js.map +1 -0
  80. package/dist/codex/generated/v2/EnvironmentStatusKind.d.ts +7 -0
  81. package/dist/codex/generated/v2/EnvironmentStatusKind.js +3 -0
  82. package/dist/codex/generated/v2/EnvironmentStatusKind.js.map +1 -0
  83. package/dist/codex/generated/v2/EnvironmentStatusParams.d.ts +9 -0
  84. package/dist/codex/generated/v2/EnvironmentStatusParams.js +3 -0
  85. package/dist/codex/generated/v2/EnvironmentStatusParams.js.map +1 -0
  86. package/dist/codex/generated/v2/EnvironmentStatusResponse.d.ts +14 -0
  87. package/dist/codex/generated/v2/EnvironmentStatusResponse.js +3 -0
  88. package/dist/codex/generated/v2/EnvironmentStatusResponse.js.map +1 -0
  89. package/dist/codex/generated/v2/ExternalAgentConfigDetectParams.d.ts +9 -0
  90. package/dist/codex/generated/v2/ExternalAgentConfigImportHistoriesReadResponse.d.ts +2 -0
  91. package/dist/codex/generated/v2/ExternalAgentConfigImportItemTypeFailure.d.ts +1 -0
  92. package/dist/codex/generated/v2/ExternalAgentConfigImportParams.d.ts +6 -1
  93. package/dist/codex/generated/v2/ExternalAgentConfigMigrationItemType.d.ts +1 -1
  94. package/dist/codex/generated/v2/ExternalAgentImportedConnectorCandidate.d.ts +6 -0
  95. package/dist/codex/generated/v2/ExternalAgentImportedConnectorCandidate.js +3 -0
  96. package/dist/codex/generated/v2/ExternalAgentImportedConnectorCandidate.js.map +1 -0
  97. package/dist/codex/generated/v2/ExternalAgentImportedConnectorSource.d.ts +1 -0
  98. package/dist/codex/generated/v2/ExternalAgentImportedConnectorSource.js +3 -0
  99. package/dist/codex/generated/v2/ExternalAgentImportedConnectorSource.js.map +1 -0
  100. package/dist/codex/generated/v2/FileSystemSpecialPath.d.ts +3 -2
  101. package/dist/codex/generated/v2/HookEventName.d.ts +1 -1
  102. package/dist/codex/generated/v2/HookMetadata.d.ts +5 -0
  103. package/dist/codex/generated/v2/InstalledApp.d.ts +21 -0
  104. package/dist/codex/generated/v2/InstalledApp.js +3 -0
  105. package/dist/codex/generated/v2/InstalledApp.js.map +1 -0
  106. package/dist/codex/generated/v2/LoginAccountParams.d.ts +4 -0
  107. package/dist/codex/generated/v2/LoginAccountResponse.d.ts +2 -0
  108. package/dist/codex/generated/v2/ManagedHooksRequirements.d.ts +1 -0
  109. package/dist/codex/generated/v2/McpToolCallAppContext.d.ts +0 -1
  110. package/dist/codex/generated/v2/MigrationDetails.d.ts +1 -0
  111. package/dist/codex/generated/v2/PluginDetail.d.ts +2 -0
  112. package/dist/codex/generated/v2/PluginShareUpdateDiscoverability.d.ts +1 -1
  113. package/dist/codex/generated/v2/PluginSummary.d.ts +1 -0
  114. package/dist/codex/generated/v2/RateLimitSnapshot.d.ts +4 -0
  115. package/dist/codex/generated/v2/RawResponseCompletedNotification.d.ts +11 -0
  116. package/dist/codex/generated/v2/RawResponseCompletedNotification.js +3 -0
  117. package/dist/codex/generated/v2/RawResponseCompletedNotification.js.map +1 -0
  118. package/dist/codex/generated/v2/ScheduledTaskSchedule.d.ts +16 -0
  119. package/dist/codex/generated/v2/ScheduledTaskSchedule.js +3 -0
  120. package/dist/codex/generated/v2/ScheduledTaskSchedule.js.map +1 -0
  121. package/dist/codex/generated/v2/ScheduledTaskSummary.d.ts +7 -0
  122. package/dist/codex/generated/v2/ScheduledTaskSummary.js +3 -0
  123. package/dist/codex/generated/v2/ScheduledTaskSummary.js.map +1 -0
  124. package/dist/codex/generated/v2/ScheduledTaskWeekday.d.ts +1 -0
  125. package/dist/codex/generated/v2/ScheduledTaskWeekday.js +3 -0
  126. package/dist/codex/generated/v2/ScheduledTaskWeekday.js.map +1 -0
  127. package/dist/codex/generated/v2/Thread.d.ts +5 -0
  128. package/dist/codex/generated/v2/ThreadForkParams.d.ts +11 -0
  129. package/dist/codex/generated/v2/ThreadItem.d.ts +2 -3
  130. package/dist/codex/generated/v2/ThreadItemEntry.d.ts +8 -0
  131. package/dist/codex/generated/v2/ThreadItemEntry.js +3 -0
  132. package/dist/codex/generated/v2/ThreadItemEntry.js.map +1 -0
  133. package/dist/codex/generated/v2/ThreadItemsListResponse.d.ts +2 -2
  134. package/dist/codex/generated/v2/ThreadRealtimeInitialItem.d.ts +8 -0
  135. package/dist/codex/generated/v2/ThreadRealtimeInitialItem.js +3 -0
  136. package/dist/codex/generated/v2/ThreadRealtimeInitialItem.js.map +1 -0
  137. package/dist/codex/generated/v2/ThreadRealtimeStartParams.d.ts +11 -4
  138. package/dist/codex/generated/v2/ThreadResumeResponse.d.ts +14 -0
  139. package/dist/codex/generated/v2/ThreadSearchOccurrence.d.ts +17 -0
  140. package/dist/codex/generated/v2/ThreadSearchOccurrence.js +3 -0
  141. package/dist/codex/generated/v2/ThreadSearchOccurrence.js.map +1 -0
  142. package/dist/codex/generated/v2/ThreadSearchOccurrencesParams.d.ts +19 -0
  143. package/dist/codex/generated/v2/ThreadSearchOccurrencesParams.js +3 -0
  144. package/dist/codex/generated/v2/ThreadSearchOccurrencesParams.js.map +1 -0
  145. package/dist/codex/generated/v2/ThreadSearchOccurrencesResponse.d.ts +11 -0
  146. package/dist/codex/generated/v2/ThreadSearchOccurrencesResponse.js +3 -0
  147. package/dist/codex/generated/v2/ThreadSearchOccurrencesResponse.js.map +1 -0
  148. package/dist/codex/generated/v2/ThreadSearchTextRange.d.ts +13 -0
  149. package/dist/codex/generated/v2/ThreadSearchTextRange.js +3 -0
  150. package/dist/codex/generated/v2/ThreadSearchTextRange.js.map +1 -0
  151. package/dist/codex/generated/v2/TokenUsageBreakdown.d.ts +1 -0
  152. package/dist/codex/generated/v2/TurnEnvironmentParams.d.ts +4 -0
  153. package/dist/codex/generated/v2/UserInput.d.ts +6 -0
  154. package/dist/codex/generated/v2/index.d.ts +23 -0
  155. package/dist/daemon/daemon.js +5 -20
  156. package/dist/daemon/daemon.js.map +1 -1
  157. package/dist/daemon/probe.js +1 -1
  158. package/dist/daemon/settings.d.ts +6 -0
  159. package/dist/daemon/settings.js +23 -0
  160. package/dist/daemon/settings.js.map +1 -0
  161. package/dist/gateway/clientConnection.d.ts +3 -2
  162. package/dist/gateway/clientConnection.js +47 -50
  163. package/dist/gateway/clientConnection.js.map +1 -1
  164. package/dist/gateway/logicalThreadProjection.d.ts +5 -0
  165. package/dist/gateway/logicalThreadProjection.js +58 -1
  166. package/dist/gateway/logicalThreadProjection.js.map +1 -1
  167. package/dist/gateway/remoteControlController.d.ts +29 -0
  168. package/dist/gateway/remoteControlController.js +78 -0
  169. package/dist/gateway/remoteControlController.js.map +1 -0
  170. package/dist/gateway/remoteControlHub.js +2 -1
  171. package/dist/gateway/remoteControlHub.js.map +1 -1
  172. package/dist/gateway/remoteControlMode.js +2 -12
  173. package/dist/gateway/remoteControlMode.js.map +1 -1
  174. package/dist/gateway/server.js +42 -15
  175. package/dist/gateway/server.js.map +1 -1
  176. package/dist/gateway/sideFork.d.ts +3 -0
  177. package/dist/gateway/sideFork.js +11 -0
  178. package/dist/gateway/sideFork.js.map +1 -0
  179. package/dist/gateway/stockSideThreads.js +2 -4
  180. package/dist/gateway/stockSideThreads.js.map +1 -1
  181. package/dist/gateway/subscriptions.d.ts +9 -2
  182. package/dist/gateway/subscriptions.js +40 -9
  183. package/dist/gateway/subscriptions.js.map +1 -1
  184. package/dist/gateway/threadList.d.ts +25 -0
  185. package/dist/gateway/threadList.js +112 -26
  186. package/dist/gateway/threadList.js.map +1 -1
  187. package/dist/handoff/lineageStore.d.ts +102 -0
  188. package/dist/handoff/lineageStore.js +515 -0
  189. package/dist/handoff/lineageStore.js.map +1 -0
  190. package/dist/handoff/providerEpochs.d.ts +19 -5
  191. package/dist/handoff/providerEpochs.js +119 -41
  192. package/dist/handoff/providerEpochs.js.map +1 -1
  193. package/dist/handoff/service.d.ts +16 -2
  194. package/dist/handoff/service.js +502 -140
  195. package/dist/handoff/service.js.map +1 -1
  196. package/dist/handoff/startupMigration.d.ts +22 -0
  197. package/dist/handoff/startupMigration.js +52 -0
  198. package/dist/handoff/startupMigration.js.map +1 -0
  199. package/dist/handoff/store.d.ts +10 -0
  200. package/dist/handoff/store.js +141 -28
  201. package/dist/handoff/store.js.map +1 -1
  202. package/dist/runtime/dependencies.js +1 -1
  203. package/dist/state/stockStateTracker.js +1 -0
  204. package/dist/state/stockStateTracker.js.map +1 -1
  205. package/dist/store/HybridStore.d.ts +3 -1
  206. package/dist/store/HybridStore.js.map +1 -1
  207. package/dist/store/memoryStore.d.ts +3 -2
  208. package/dist/store/memoryStore.js +18 -4
  209. package/dist/store/memoryStore.js.map +1 -1
  210. package/dist/store/sqliteStore.d.ts +2 -2
  211. package/dist/store/sqliteStore.js +84 -24
  212. package/dist/store/sqliteStore.js.map +1 -1
  213. package/dist/store/threadFilter.js +2 -0
  214. package/dist/store/threadFilter.js.map +1 -1
  215. package/legal/LICENSES.md +1 -1
  216. package/legal/THIRD_PARTY_NOTICES.md +136 -124
  217. package/package.json +5 -5
  218. package/dist/codex/generated/AmazonBedrockCredentialSource.d.ts +0 -1
  219. package/dist/codex/generated/AmazonBedrockCredentialSource.js +0 -3
  220. package/dist/codex/generated/AmazonBedrockCredentialSource.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { join, resolve } from "node:path";
2
2
  import { statSync } from "node:fs";
3
- import { deleteSession, renameSession, } from "@anthropic-ai/claude-agent-sdk";
3
+ import { deleteSession, listSessions, renameSession, } from "@anthropic-ai/claude-agent-sdk";
4
4
  import { v7 as uuidv7 } from "uuid";
5
5
  import { settingsGeneration } from "../store/HybridStore.js";
6
6
  import { SqliteHybridStore } from "../store/sqliteStore.js";
@@ -79,7 +79,11 @@ function threadResponse(record, includeTurns) {
79
79
  const { sandboxPolicy: sandbox, effort: reasoningEffort, summary: _summary, collaborationMode: _collaborationMode, personality: _personality, ...settings } = threadSettings(record);
80
80
  return {
81
81
  ...settings,
82
- thread: { ...record.thread, turns: includeTurns ? record.thread.turns : [] },
82
+ thread: {
83
+ ...record.thread,
84
+ canAcceptDirectInput: record.thread.parentThreadId ? false : true,
85
+ turns: includeTurns ? record.thread.turns : [],
86
+ },
83
87
  runtimeWorkspaceRoots: [record.thread.cwd],
84
88
  instructionSources: [],
85
89
  sandbox,
@@ -169,10 +173,12 @@ export class ClaudeService {
169
173
  threadAdminEffects;
170
174
  availabilityProbe;
171
175
  skillsChanged;
176
+ nativeSessionCatalog;
172
177
  ephemeralReleases = new Map();
173
178
  ephemeralReleaseTimers = new Map();
174
179
  removalRetries = new Map();
175
180
  terminalAdmins = new Map();
181
+ settingsUpdates = new Map();
176
182
  idleTimer;
177
183
  store;
178
184
  sessionOutput;
@@ -183,10 +189,13 @@ export class ClaudeService {
183
189
  closing = false;
184
190
  closePromise;
185
191
  standaloneStatusRead;
192
+ nativeMetadata = new Map();
193
+ nativeMetadataRead;
194
+ nativeMetadataReadAt = 0;
186
195
  constructor(config, hub, logger, durableStore = new SqliteHybridStore(join(config.dataDir, "state.sqlite")), queryFactory = createClaudeQuery, modelCatalog, metrics = new MetricsRegistry(), transcripts = new SdkTranscriptBrancher(), threadAdminEffects = sdkThreadAdminEffects, shellRunner = new ShellRunner(), availabilityProbe = async () => ({
187
196
  provider: "claude",
188
197
  state: "ready",
189
- }), skillsChanged) {
198
+ }), skillsChanged, nativeSessionCatalog = () => listSessions()) {
190
199
  this.config = config;
191
200
  this.hub = hub;
192
201
  this.logger = logger;
@@ -197,6 +206,7 @@ export class ClaudeService {
197
206
  this.threadAdminEffects = threadAdminEffects;
198
207
  this.availabilityProbe = availabilityProbe;
199
208
  this.skillsChanged = skillsChanged;
209
+ this.nativeSessionCatalog = nativeSessionCatalog;
200
210
  this.store = new LayeredHybridStore(durableStore);
201
211
  const sessionRepository = new ClaudeSessionRepository(this.store);
202
212
  this.sessionOutput = new ClaudeOutputAdapter(hub);
@@ -260,14 +270,7 @@ export class ClaudeService {
260
270
  }
261
271
  }
262
272
  this.restartRecovery = this.resumeThreadRemovals(pendingRemovals.map((removal) => removal.rootThreadId))
263
- .then(() => this.reconcileAfterRestart(orphanProjectionIds, restartRecoveryRootIds))
264
- .then(() => {
265
- for (const record of this.store.allThreadRecords()) {
266
- if (record.thread.ephemeral && !record.thread.parentThreadId) {
267
- this.scheduleEphemeralRelease(record.thread.id);
268
- }
269
- }
270
- });
273
+ .then(() => this.reconcileAfterRestart(orphanProjectionIds, restartRecoveryRootIds));
271
274
  for (const record of this.store.allThreadRecords()) {
272
275
  if (orphanProjectionIds.has(record.thread.id))
273
276
  continue;
@@ -326,7 +329,7 @@ export class ClaudeService {
326
329
  throw invalidParams(`Unknown Claude turn '${lastTurnId}'.`);
327
330
  const turns = snapshot.record.thread.turns.slice(0, through + 1);
328
331
  return {
329
- thread: { ...snapshot.record.thread, turns },
332
+ thread: { ...this.withNativeMetadata(snapshot.record), turns },
330
333
  turns,
331
334
  settings: threadSettings(snapshot.record),
332
335
  };
@@ -337,11 +340,13 @@ export class ClaudeService {
337
340
  sideSnapshot(sourceThreadId, targetThreadId, visibleForkedFromId) {
338
341
  const record = this.requireRecord(sourceThreadId, false);
339
342
  const response = threadResponse(record, false);
343
+ const source = this.withNativeMetadata(record);
340
344
  return {
341
345
  ...response,
342
346
  thread: {
343
- ...record.thread,
347
+ ...source,
344
348
  id: targetThreadId,
349
+ sessionId: targetThreadId,
345
350
  forkedFromId: visibleForkedFromId,
346
351
  ephemeral: true,
347
352
  path: null,
@@ -417,6 +422,8 @@ export class ClaudeService {
417
422
  if (record.thread.parentThreadId) {
418
423
  return {
419
424
  ...threadResponse(record, !resume.excludeTurns),
425
+ turnsBackwardsCursor: record.thread.turns.length ? "hyb-turn:0" : null,
426
+ itemsBackwardsCursor: record.thread.turns.some((turn) => turn.items.length) ? "hyb-item:0" : null,
420
427
  initialTurnsPage: resume.initialTurnsPage
421
428
  ? this.turnsPage({
422
429
  threadId,
@@ -431,6 +438,8 @@ export class ClaudeService {
431
438
  record = await this.sessions.submit(threadId, { type: "readThread", includeTurns: true });
432
439
  return {
433
440
  ...threadResponse(record, !resume.excludeTurns),
441
+ turnsBackwardsCursor: record.thread.turns.length ? "hyb-turn:0" : null,
442
+ itemsBackwardsCursor: record.thread.turns.some((turn) => turn.items.length) ? "hyb-item:0" : null,
434
443
  initialTurnsPage: resume.initialTurnsPage
435
444
  ? this.turnsPage({
436
445
  threadId,
@@ -477,7 +486,7 @@ export class ClaudeService {
477
486
  const record = this.store.getThreadRecord(threadId, includeTurns);
478
487
  if (!record)
479
488
  throw invalidParams(`Unknown Claude thread '${threadId}'.`);
480
- return { thread: record.thread };
489
+ return { thread: this.withNativeMetadata(record) };
481
490
  }
482
491
  async prepareTurn(params, review) {
483
492
  this.requireIndependentThread(params.threadId, "start a turn in");
@@ -794,8 +803,31 @@ export class ClaudeService {
794
803
  }
795
804
  listThreads(params) {
796
805
  return this.store.listThreads(params)
806
+ .map((thread) => {
807
+ const record = this.store.getThreadRecord(thread.id, false);
808
+ return record ? this.withNativeMetadata({ ...record, thread }) : thread;
809
+ })
797
810
  .filter((thread) => !this.pendingThreadRemoval(thread.id) && !this.hub.isSuppressed(thread.id));
798
811
  }
812
+ refreshNativeMetadata() {
813
+ if (Date.now() - this.nativeMetadataReadAt < 30_000)
814
+ return Promise.resolve();
815
+ if (this.nativeMetadataRead)
816
+ return this.nativeMetadataRead;
817
+ const read = this.nativeSessionCatalog().then((sessions) => {
818
+ this.nativeMetadata = new Map(sessions.map((session) => [session.sessionId, session]));
819
+ this.nativeMetadataReadAt = Date.now();
820
+ }).catch((error) => {
821
+ this.logger.warn("claude.catalog.native-metadata-failed", {
822
+ error: error instanceof Error ? error.message : String(error),
823
+ });
824
+ }).finally(() => {
825
+ if (this.nativeMetadataRead === read)
826
+ this.nativeMetadataRead = undefined;
827
+ });
828
+ this.nativeMetadataRead = read;
829
+ return read;
830
+ }
799
831
  async setThreadName(params) {
800
832
  const record = this.requireRecord(params.threadId, false);
801
833
  if (record.thread.parentThreadId) {
@@ -810,6 +842,14 @@ export class ClaudeService {
810
842
  await this.threadAdminEffects.rename(prepared.record.claudeSessionId, params.name, prepared.record.thread.cwd);
811
843
  }
812
844
  }, params.name);
845
+ const native = this.nativeMetadata.get(record.claudeSessionId);
846
+ if (native)
847
+ this.nativeMetadata.set(record.claudeSessionId, {
848
+ ...native,
849
+ customTitle: params.name,
850
+ summary: params.name,
851
+ lastModified: Date.now(),
852
+ });
813
853
  return {};
814
854
  }
815
855
  updateThreadMetadata(params) {
@@ -921,7 +961,9 @@ export class ClaudeService {
921
961
  listItems(params) {
922
962
  this.assertThreadAvailable(params.threadId);
923
963
  const items = this.store.listTurns(params.threadId)
924
- .flatMap((turn) => params.turnId && turn.id !== params.turnId ? [] : turn.items);
964
+ .flatMap((turn) => params.turnId && turn.id !== params.turnId
965
+ ? []
966
+ : turn.items.map((item) => ({ turnId: turn.id, item })));
925
967
  const ordered = params.sortDirection === "desc" ? [...items].reverse() : items;
926
968
  if (params.cursor && !params.cursor.startsWith("hyb-item:"))
927
969
  throw invalidParams("Invalid Claude item cursor.");
@@ -936,10 +978,55 @@ export class ClaudeService {
936
978
  backwardsCursor: data.length > 0 ? `hyb-item:${Math.max(0, offset - limit)}` : null,
937
979
  };
938
980
  }
981
+ searchOccurrences(params) {
982
+ this.assertThreadAvailable(params.threadId);
983
+ const searchTerm = params.searchTerm.trim();
984
+ if (!searchTerm)
985
+ throw invalidParams("thread/searchOccurrences requires a non-empty searchTerm.");
986
+ if (params.cursor && !params.cursor.startsWith("hyb-search:"))
987
+ throw invalidParams("Invalid Claude search cursor.");
988
+ const offset = params.cursor ? Number(params.cursor.slice("hyb-search:".length)) : 0;
989
+ if (!Number.isInteger(offset) || offset < 0)
990
+ throw invalidParams("Invalid Claude search cursor.");
991
+ const turns = this.store.listTurns(params.threadId);
992
+ const needle = searchTerm.toLocaleLowerCase();
993
+ const occurrences = turns.flatMap((turn, turnIndex) => turn.items.flatMap((item) => {
994
+ const text = item.type === "userMessage"
995
+ ? item.content.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n")
996
+ : item.type === "agentMessage" && item.phase !== "commentary" ? item.text : undefined;
997
+ if (text === undefined)
998
+ return [];
999
+ const haystack = text.toLocaleLowerCase();
1000
+ const matches = [];
1001
+ for (let start = haystack.indexOf(needle); start >= 0; start = haystack.indexOf(needle, start + needle.length)) {
1002
+ matches.push({
1003
+ turnId: turn.id,
1004
+ itemId: item.id,
1005
+ snippet: text,
1006
+ snippetMatchRange: { start, end: start + needle.length },
1007
+ turnCursor: `hyb-turn:${turns.length - 1 - turnIndex}`,
1008
+ });
1009
+ }
1010
+ return matches;
1011
+ }));
1012
+ const limit = Math.max(1, Math.min(params.limit ?? 50, 100));
1013
+ const data = occurrences.slice(offset, offset + limit);
1014
+ return {
1015
+ data,
1016
+ nextCursor: offset + data.length < occurrences.length ? `hyb-search:${offset + data.length}` : null,
1017
+ };
1018
+ }
939
1019
  async forkThread(params, visibleForkedFromId = params.threadId) {
940
1020
  if (params.path)
941
1021
  throw invalidParams("Claude thread forks must use threadId, not a Codex rollout path.");
942
1022
  this.requireIndependentThread(params.threadId, "fork");
1023
+ if (params.lastTurnId && params.beforeTurnId)
1024
+ throw invalidParams("`beforeTurnId` cannot be combined with `lastTurnId`.");
1025
+ if (params.ephemeral && params.deferGoalContinuation)
1026
+ throw invalidParams("`deferGoalContinuation` cannot be combined with `ephemeral`.");
1027
+ const inheritedGoal = params.deferGoalContinuation
1028
+ ? await this.sessions.submit(params.threadId, { type: "goal", command: { kind: "snapshotFork" } })
1029
+ : undefined;
943
1030
  const source = await this.sessions.submit(params.threadId, { type: "snapshotBranch" });
944
1031
  const sourceRecord = source.record;
945
1032
  const sidePromotion = Boolean((this.config.features?.sideChatPromotion ?? true)
@@ -954,15 +1041,20 @@ export class ClaudeService {
954
1041
  if (!claudeModelValue)
955
1042
  throw invalidParams("Cannot fork a Claude thread to a Codex model.");
956
1043
  const activeSideFork = Boolean(params.ephemeral === true && params.excludeTurns === true
957
- && params.threadSource === "user" && !params.lastTurnId);
1044
+ && params.threadSource === "user" && !params.lastTurnId && !params.beforeTurnId);
958
1045
  if (params.permissions && params.sandbox)
959
1046
  throw invalidParams("Claude thread fork cannot combine permissions with sandbox.");
960
1047
  const activeIndex = sourceRecord.thread.turns.findIndex((turn) => turn.status === "inProgress");
961
1048
  const selectedIndex = params.lastTurnId
962
1049
  ? sourceRecord.thread.turns.findIndex((turn) => turn.id === params.lastTurnId)
963
1050
  : -1;
1051
+ const beforeIndex = params.beforeTurnId
1052
+ ? sourceRecord.thread.turns.findIndex((turn) => turn.id === params.beforeTurnId)
1053
+ : -1;
964
1054
  if (params.lastTurnId && selectedIndex < 0)
965
1055
  throw invalidParams(`Unknown Claude turn '${params.lastTurnId}'.`);
1056
+ if (params.beforeTurnId && beforeIndex < 0)
1057
+ throw invalidParams(`Unknown Claude turn '${params.beforeTurnId}'.`);
966
1058
  const selectedTurn = selectedIndex >= 0 ? sourceRecord.thread.turns[selectedIndex] : undefined;
967
1059
  const selectedBoundary = params.lastTurnId
968
1060
  ? source.boundaries.find((entry) => entry.turnId === params.lastTurnId)
@@ -974,7 +1066,9 @@ export class ClaudeService {
974
1066
  ? activeIndex >= 0 ? activeIndex - 1 : sourceRecord.thread.turns.length - 1
975
1067
  : params.lastTurnId
976
1068
  ? selectedIndex
977
- : sourceRecord.thread.turns.length - 1;
1069
+ : params.beforeTurnId
1070
+ ? beforeIndex - 1
1071
+ : sourceRecord.thread.turns.length - 1;
978
1072
  if (sidePromotion && through < 0) {
979
1073
  throw invalidParams("Cannot promote a Claude side chat before it has a completed turn.");
980
1074
  }
@@ -1008,11 +1102,13 @@ export class ClaudeService {
1008
1102
  }
1009
1103
  const createdAt = nowSeconds();
1010
1104
  const cwd = existingThreadCwd(params.cwd ?? sourceRecord.thread.cwd);
1105
+ const threadId = uuidv7();
1011
1106
  const thread = {
1012
- ...sourceRecord.thread, id: uuidv7(), ephemeral: params.ephemeral ?? false,
1013
- sessionId: sourceRecord.thread.sessionId, forkedFromId: visibleForkedFromId,
1107
+ ...sourceRecord.thread, id: threadId, ephemeral: params.ephemeral ?? false,
1108
+ sessionId: threadId, forkedFromId: visibleForkedFromId,
1014
1109
  cwd, modelProvider: "claude", createdAt, updatedAt: createdAt, recencyAt: createdAt,
1015
1110
  status: params.ephemeral ? { type: "idle" } : { type: "notLoaded" },
1111
+ canAcceptDirectInput: true,
1016
1112
  name: params.ephemeral
1017
1113
  ? sourceRecord.thread.name
1018
1114
  : sourceRecord.thread.name ? `${sourceRecord.thread.name} (fork)` : null,
@@ -1036,6 +1132,7 @@ export class ClaudeService {
1036
1132
  responseRecord = await this.sessions.submit(thread.id, {
1037
1133
  type: "commitForkTarget", record, turns: copiedTurns,
1038
1134
  sourceBoundaries, uuidMap: [...branch.uuidMap],
1135
+ ...(inheritedGoal ? { inheritedGoal } : {}),
1039
1136
  });
1040
1137
  }
1041
1138
  catch (error) {
@@ -1061,7 +1158,7 @@ export class ClaudeService {
1061
1158
  }
1062
1159
  }
1063
1160
  if (sidePromotion)
1064
- this.scheduleEphemeralRelease(params.threadId);
1161
+ await this.releaseEphemeralThread(params.threadId);
1065
1162
  return threadResponse(responseRecord, !params.excludeTurns);
1066
1163
  }
1067
1164
  async rollbackThread(params) {
@@ -1416,6 +1513,28 @@ export class ClaudeService {
1416
1513
  throw invalidParams(`Unknown Claude thread '${threadId}'.`);
1417
1514
  return record;
1418
1515
  }
1516
+ withNativeMetadata(record) {
1517
+ const native = this.nativeMetadata.get(record.claudeSessionId);
1518
+ if (!native)
1519
+ return {
1520
+ ...record.thread,
1521
+ canAcceptDirectInput: record.thread.parentThreadId ? false : true,
1522
+ };
1523
+ const createdAt = native.createdAt === undefined
1524
+ ? record.thread.createdAt
1525
+ : Math.floor(native.createdAt / 1_000);
1526
+ const updatedAt = Math.floor(native.lastModified / 1_000);
1527
+ return {
1528
+ ...record.thread,
1529
+ canAcceptDirectInput: record.thread.parentThreadId ? false : true,
1530
+ name: (native.customTitle ?? native.summary) || record.thread.name,
1531
+ preview: native.firstPrompt ?? record.thread.preview,
1532
+ cwd: native.cwd ?? record.thread.cwd,
1533
+ createdAt,
1534
+ updatedAt,
1535
+ recencyAt: updatedAt,
1536
+ };
1537
+ }
1419
1538
  requireIndependentThread(threadId, operation) {
1420
1539
  const record = this.requireRecord(threadId, false);
1421
1540
  if (record.thread.parentThreadId) {
@@ -1492,6 +1611,7 @@ export class ClaudeService {
1492
1611
  updatedAt: createdAt,
1493
1612
  recencyAt: createdAt,
1494
1613
  status: { type: "idle" },
1614
+ canAcceptDirectInput: true,
1495
1615
  path: null,
1496
1616
  cwd,
1497
1617
  cliVersion: "claude-code",
@@ -1525,7 +1645,10 @@ export class ClaudeService {
1525
1645
  ? null
1526
1646
  : syncedCollaborationMode(settings.collaborationMode, modelPickerId, reasoningEffort),
1527
1647
  outputSchema: null,
1528
- tokenUsageTotal: { totalTokens: 0, inputTokens: 0, cachedInputTokens: 0, outputTokens: 0, reasoningOutputTokens: 0 },
1648
+ tokenUsageTotal: {
1649
+ totalTokens: 0, inputTokens: 0, cachedInputTokens: 0, cacheWriteInputTokens: 0,
1650
+ outputTokens: 0, reasoningOutputTokens: 0,
1651
+ },
1529
1652
  tokenUsageLast: null,
1530
1653
  modelContextWindow: null,
1531
1654
  providerCostUsdTotal: 0,
@@ -1535,6 +1658,18 @@ export class ClaudeService {
1535
1658
  await this.applySettings({ ...params, effort: turnReasoningEffort(params) }, params.outputSchema ?? null);
1536
1659
  }
1537
1660
  async applySettings(params, outputSchema) {
1661
+ const previous = this.settingsUpdates.get(params.threadId) ?? Promise.resolve();
1662
+ const update = previous.catch(() => undefined).then(() => this.applySettingsNow(params, outputSchema));
1663
+ this.settingsUpdates.set(params.threadId, update);
1664
+ try {
1665
+ await update;
1666
+ }
1667
+ finally {
1668
+ if (this.settingsUpdates.get(params.threadId) === update)
1669
+ this.settingsUpdates.delete(params.threadId);
1670
+ }
1671
+ }
1672
+ async applySettingsNow(params, outputSchema) {
1538
1673
  if (params.permissions && params.sandboxPolicy)
1539
1674
  throw invalidParams("Claude thread settings cannot combine permissions with sandboxPolicy.");
1540
1675
  if (params.effort && !["low", "medium", "high", "xhigh", "max"].includes(params.effort))