@gkorepanov/ccodex 0.4.8 → 0.4.10

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