@bitkyc08/opencodex 2.28.0 → 2.29.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 (90) hide show
  1. package/README.md +9 -1
  2. package/gui/dist/assets/index-BNESwCzn.js +102 -0
  3. package/gui/dist/assets/index-CH7ncHCC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/base.ts +3 -1
  7. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  8. package/src/adapters/cursor/discovery.ts +25 -0
  9. package/src/adapters/cursor/live-transport.ts +19 -2
  10. package/src/adapters/cursor/native-exec.ts +47 -2
  11. package/src/adapters/cursor/protobuf-request.ts +80 -23
  12. package/src/adapters/cursor/request-builder.ts +119 -5
  13. package/src/adapters/cursor/transport.ts +5 -0
  14. package/src/adapters/cursor/types.ts +13 -0
  15. package/src/adapters/cursor.ts +109 -3
  16. package/src/adapters/google-antigravity-replay.ts +31 -4
  17. package/src/adapters/google.ts +323 -38
  18. package/src/adapters/openai-chat.ts +19 -0
  19. package/src/adapters/openai-responses.ts +218 -37
  20. package/src/bridge.ts +38 -20
  21. package/src/claude/desktop-3p.ts +15 -6
  22. package/src/cli/agent.ts +44 -1
  23. package/src/cli/claude-agent-startup-sync.ts +73 -0
  24. package/src/cli/dispatch.ts +24 -1
  25. package/src/cli/ensure-desired-integrations.ts +152 -0
  26. package/src/cli/help.ts +1 -1
  27. package/src/cli/index.ts +40 -38
  28. package/src/cli/integrations.ts +1 -1
  29. package/src/cli/registry.ts +2 -2
  30. package/src/clients/config-export.ts +119 -20
  31. package/src/codex/affinity-debug.ts +162 -0
  32. package/src/codex/inject.ts +46 -14
  33. package/src/codex/journal.ts +22 -8
  34. package/src/config.ts +1 -0
  35. package/src/generated/compatibility-version.json +134 -66
  36. package/src/integrations/mutation-flight.ts +71 -0
  37. package/src/integrations/owned-refresh.ts +74 -0
  38. package/src/integrations/registry.ts +25 -2
  39. package/src/integrations/writer.ts +32 -1
  40. package/src/lab/public/signature.ts +25 -1
  41. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  42. package/src/lib/redact.ts +2 -2
  43. package/src/oauth/log.ts +3 -1
  44. package/src/providers/derive.ts +9 -0
  45. package/src/providers/fastwire.ts +24 -18
  46. package/src/providers/openai-tiers.ts +60 -1
  47. package/src/providers/registry.ts +14 -7
  48. package/src/providers/xai-responses-opt-in.ts +15 -0
  49. package/src/responses/compaction.ts +18 -0
  50. package/src/responses/custom-tool-compat.ts +70 -5
  51. package/src/responses/namespace-tool-compat.ts +356 -0
  52. package/src/responses/parser.ts +2 -2
  53. package/src/responses/provider-continuation.ts +98 -0
  54. package/src/responses/reasoning-replay-cache.ts +125 -7
  55. package/src/responses/spill-store.ts +6 -1
  56. package/src/responses/state.ts +11 -0
  57. package/src/router.ts +14 -0
  58. package/src/routing/compatibility/behavior.ts +1 -0
  59. package/src/server/auth-cors.ts +4 -0
  60. package/src/server/management/agent-settings-routes.ts +57 -9
  61. package/src/server/management/config-routes.ts +134 -15
  62. package/src/server/management/integration-routes.ts +8 -55
  63. package/src/server/management/model-routes.ts +23 -1
  64. package/src/server/management/provider-routes.ts +22 -0
  65. package/src/server/management/vision-sidecar-options.ts +18 -11
  66. package/src/server/management/web-search-sidecar-options.ts +120 -0
  67. package/src/server/responses/core.ts +588 -78
  68. package/src/server/responses/responses-field-backfill.ts +7 -11
  69. package/src/server/responses/terminal-guard.ts +22 -11
  70. package/src/server/responses-custom-tool-repair.ts +3 -1
  71. package/src/server/responses-reasoning-summary-rewrite.ts +7 -0
  72. package/src/server/responses-tool-search-repair.ts +64 -14
  73. package/src/sidecar/auth.ts +92 -0
  74. package/src/sidecar/candidates.ts +83 -0
  75. package/src/types/config.ts +26 -5
  76. package/src/types/provider.ts +18 -0
  77. package/src/types/request.ts +26 -0
  78. package/src/types.ts +1 -0
  79. package/src/usage/log.ts +2 -0
  80. package/src/vision/index.ts +8 -9
  81. package/src/web-search/backends.ts +108 -0
  82. package/src/web-search/exa-executor.ts +88 -0
  83. package/src/web-search/gemini-executor.ts +141 -0
  84. package/src/web-search/index.ts +139 -12
  85. package/src/web-search/loop.ts +45 -5
  86. package/src/web-search/parse.ts +34 -23
  87. package/src/web-search/sources.ts +60 -0
  88. package/src/web-search/xai-executor.ts +219 -0
  89. package/gui/dist/assets/index-D2sP-biU.js +0 -102
  90. package/gui/dist/assets/index-DQsMZzI5.css +0 -1
@@ -9,7 +9,7 @@ import type {
9
9
  } from "../../types";
10
10
  import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
11
11
  import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types";
12
- import { cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
12
+ import { cursorCheckpointModelAffinityId, cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
13
13
  import { cursorEffortSuffix, cursorRequestWireModelIdWithEffort } from "./effort-map";
14
14
  import {
15
15
  cursorMcpToolEncodedSize,
@@ -25,6 +25,12 @@ import {
25
25
  isCursorWaitTool,
26
26
  } from "./tool-definitions";
27
27
  import { lookupCursorThreadConversation } from "./thread-continuity";
28
+ import {
29
+ getCursorCheckpoint,
30
+ getCursorCheckpointForPrefix,
31
+ type CursorCheckpointInvalidationReason,
32
+ type CursorCheckpointSnapshot,
33
+ } from "./checkpoint-store";
28
34
 
29
35
  /** Probe-verified Cursor Connect boundaries, with byte headroom for the enclosing field. */
30
36
  export const CURSOR_TOOL_COUNT_LIMIT = 330;
@@ -285,24 +291,120 @@ export function resolveCursorConversationId(
285
291
  options: CreateCursorRequestOptions = {},
286
292
  ): string {
287
293
  if (options.forceFreshConversation === true) return generatedCursorConversationId();
288
- // Helper/shadow/compaction turns must not append into the parent's Cursor conversation,
289
- // even when previous_response_id restored the parent's remembered id.
290
294
  if (parsed._cursorIsolateConversation === true) return generatedCursorConversationId();
291
295
  if (parsed._cursorConversationId) return parsed._cursorConversationId;
292
296
  const threadId = parsed._clientThreadId?.trim();
293
297
  if (threadId) {
294
298
  const recovered = lookupCursorThreadConversation(threadId, parsed._cursorIdentityScope);
295
299
  if (recovered) return recovered;
296
- return cursorConversationIdFromClientThread(threadId, parsed._cursorIdentityScope);
300
+ return cursorConversationIdFromClientThread(`thread:${threadId}`, parsed._cursorIdentityScope);
297
301
  }
298
302
  return generatedCursorConversationId();
299
303
  }
300
304
 
305
+ function updateFramed(hash: ReturnType<typeof createHash>, value: string): void {
306
+ const bytes = Buffer.from(value, "utf8");
307
+ const length = Buffer.allocUnsafe(4);
308
+ length.writeUInt32BE(bytes.byteLength);
309
+ hash.update(length);
310
+ hash.update(bytes);
311
+ }
312
+
313
+ export function cursorInstructionDigest(parsed: OcxParsedRequest): string {
314
+ const hash = createHash("sha256").update("ocx:cursor:sys:");
315
+ for (const line of parsed.context.systemPrompt ?? []) updateFramed(hash, line);
316
+ for (const message of parsed.context.messages) {
317
+ if (message.role !== "developer") continue;
318
+ updateFramed(hash, contentToText(message.content));
319
+ }
320
+ return hash.digest("hex");
321
+ }
322
+
323
+ export function cursorCoveredPrefixDigest(parsed: OcxParsedRequest, coveredMessageCount: number): string {
324
+ const hash = createHash("sha256").update("ocx:cursor:prefix:");
325
+ updateFramed(hash, cursorInstructionDigest(parsed));
326
+ for (const message of parsed.context.messages.slice(0, coveredMessageCount)) {
327
+ updateFramed(hash, message.role);
328
+ updateFramed(hash, contentToText(message.content));
329
+ }
330
+ return hash.digest("hex");
331
+ }
332
+
301
333
  export interface CreateCursorRequestOptions {
302
334
  /** Force a brand-new Cursor conversation id even when remembered state exists. */
303
335
  forceFreshConversation?: boolean;
304
336
  }
305
337
 
338
+ function lookupPrefixSnapshot(
339
+ parsed: OcxParsedRequest,
340
+ request: CursorRunRequest,
341
+ identityScope: string,
342
+ ): CursorCheckpointSnapshot | undefined {
343
+ const systemDigest = cursorInstructionDigest(parsed);
344
+ const modelId = cursorCheckpointModelAffinityId(request.modelId);
345
+ for (let covered = parsed.context.messages.length; covered >= 1; covered--) {
346
+ const snapshot = getCursorCheckpointForPrefix({
347
+ prefixDigest: cursorCoveredPrefixDigest(parsed, covered),
348
+ systemDigest,
349
+ coveredMessageCount: covered,
350
+ identityScope,
351
+ modelId,
352
+ });
353
+ if (snapshot) return snapshot;
354
+ }
355
+ return undefined;
356
+ }
357
+
358
+ function lineageMismatch(
359
+ parsed: OcxParsedRequest,
360
+ snapshot: CursorCheckpointSnapshot,
361
+ ): CursorCheckpointInvalidationReason | undefined {
362
+ const covered = snapshot.coveredMessageCount;
363
+ if (covered === undefined || covered < 0 || covered > parsed.context.messages.length) {
364
+ return "lineage_mismatch";
365
+ }
366
+ if (!snapshot.prefixDigest || !snapshot.systemDigest) return "lineage_mismatch";
367
+ if (snapshot.systemDigest !== cursorInstructionDigest(parsed)) return "lineage_mismatch";
368
+ if (snapshot.prefixDigest !== cursorCoveredPrefixDigest(parsed, covered)) return "lineage_mismatch";
369
+ const lastRole = parsed.context.messages.at(-1)?.role;
370
+ if (lastRole === "toolResult" && covered >= parsed.context.messages.length) return "trailing_tool_result";
371
+ return undefined;
372
+ }
373
+
374
+ function resolveCursorCheckpoint(
375
+ parsed: OcxParsedRequest,
376
+ request: CursorRunRequest,
377
+ options: CreateCursorRequestOptions,
378
+ ): { snapshot: CursorCheckpointSnapshot } | { reason: CursorCheckpointInvalidationReason } {
379
+ if (options.forceFreshConversation === true) return { reason: "force_fresh" };
380
+ if (parsed._compactionRequest === true || parsed._contextCompactionBoundary === true) return { reason: "compaction" };
381
+ const isolated = parsed._cursorIsolateConversation === true;
382
+ const cursorState = parsed._providerContinuation?.cursor;
383
+ const ref = isolated ? undefined : cursorState?.checkpointRef;
384
+ const identityScope = parsed._cursorIdentityScope?.trim() || "local";
385
+ let snapshot: CursorCheckpointSnapshot | undefined;
386
+ if (ref) {
387
+ snapshot = getCursorCheckpoint(ref);
388
+ if (!snapshot) return { reason: "expired" };
389
+ } else {
390
+ snapshot = lookupPrefixSnapshot(parsed, request, identityScope);
391
+ if (!snapshot) return { reason: "missing_ref" };
392
+ }
393
+ if (!isolated && snapshot.conversationId !== request.conversationId && ref) {
394
+ return { reason: "conversation_changed" };
395
+ }
396
+ if (snapshot.identityScope !== identityScope) return { reason: "identity_changed" };
397
+ if (cursorCheckpointModelAffinityId(snapshot.modelId) !== cursorCheckpointModelAffinityId(request.modelId)) {
398
+ return { reason: "model_changed" };
399
+ }
400
+ if (parsed.context.messages.at(-1)?.role !== "toolResult" && cursorState?.checkpointUsable === false) {
401
+ return { reason: "trailing_tool_result" };
402
+ }
403
+ const lineage = lineageMismatch(parsed, snapshot);
404
+ if (lineage) return { reason: lineage };
405
+ return { snapshot };
406
+ }
407
+
306
408
  export function createCursorRequest(
307
409
  parsed: OcxParsedRequest,
308
410
  options: CreateCursorRequestOptions = {},
@@ -315,7 +417,7 @@ export function createCursorRequest(
315
417
  const budget = applyCursorToolBudget(visibleTools, parsed.options.toolChoice);
316
418
  const limitNote = catalogLimitNote(budget.tools, budget.omitted);
317
419
  const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning);
318
- return {
420
+ const request: CursorRunRequest = {
319
421
  modelId: model.modelId,
320
422
  ...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
321
423
  ...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
@@ -329,4 +431,16 @@ export function createCursorRequest(
329
431
  ...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
330
432
  ...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
331
433
  };
434
+ const resolved = resolveCursorCheckpoint(parsed, request, options);
435
+ if ("reason" in resolved) {
436
+ request.continuationMode = "full-replay";
437
+ request.checkpointInvalidationReason = resolved.reason;
438
+ return request;
439
+ }
440
+ request.checkpointBytes = resolved.snapshot.checkpointBytes;
441
+ request.continuationMode = "checkpoint";
442
+ if (parsed.context.messages.at(-1)?.role === "toolResult" && resolved.snapshot.coveredMessageCount !== undefined) {
443
+ request.checkpointSuffixStart = resolved.snapshot.coveredMessageCount;
444
+ }
445
+ return request;
332
446
  }
@@ -12,6 +12,11 @@ export interface CursorTransport {
12
12
  * accepted is never replayed. Absent (undefined) is treated as "committed" — safe by default.
13
13
  */
14
14
  requestCommitted?(): boolean;
15
+ /**
16
+ * Last ConversationStateStructure captured from conversationCheckpointUpdate on this transport.
17
+ * Test and adapter seams use this instead of reaching into LiveCursorTransport.
18
+ */
19
+ capturedConversationCheckpoint?(): Uint8Array | undefined;
15
20
  }
16
21
 
17
22
  export interface CursorTransportFactoryInput {
@@ -1,6 +1,7 @@
1
1
  import type { OcxUsage } from "../../types";
2
2
  import type { OcxMessage, OcxRequestOptions, OcxTool } from "../../types";
3
3
  import type { CursorRoutingLevel } from "./discovery";
4
+ import type { CursorCheckpointInvalidationReason } from "./checkpoint-store";
4
5
 
5
6
  export interface CursorRequestedModelParameter {
6
7
  id: string;
@@ -31,6 +32,18 @@ export interface CursorRunRequest {
31
32
  * pre-compaction history being summarized and must not become the next turn's carry-forward total.
32
33
  */
33
34
  contextUsageStoreCheckpoints?: boolean;
35
+ /**
36
+ * Reuse a previously captured ConversationStateStructure instead of rebuilding historical
37
+ * root/turn blobs. Absent means the existing full-replay path.
38
+ */
39
+ checkpointBytes?: Uint8Array;
40
+ continuationMode?: "full-replay" | "checkpoint";
41
+ checkpointInvalidationReason?: CursorCheckpointInvalidationReason;
42
+ /**
43
+ * When set with checkpointBytes, only this suffix of rawMessages is replayed onto the
44
+ * decoded ConversationStateStructure. Used for tool-result continuations.
45
+ */
46
+ checkpointSuffixStart?: number;
34
47
  }
35
48
 
36
49
  export interface CursorRequestMessage {
@@ -4,16 +4,27 @@ import type { ProviderAdapter } from "./base";
4
4
  import { isTranslatorBudgetExceededError } from "../lib/translator-budget";
5
5
  import { cursorExecDeniedMessage, cursorRequestDeclaresFullAccess } from "./cursor/exec-policy";
6
6
  import { isCursorBenignCancelError, isCursorInvalidArgumentError, safeCursorErrorMessage } from "./cursor/cursor-errors";
7
- import { isCursorExternalWireModel } from "./cursor/discovery";
7
+ import { cursorCheckpointModelAffinityId, isCursorExternalWireModel } from "./cursor/discovery";
8
8
  import { createCursorKvStore, type CursorKvStore } from "./cursor/kv-store";
9
9
  import { mapCursorServerMessage } from "./cursor/message-mapper";
10
- import { createCursorRequest } from "./cursor/request-builder";
10
+ import {
11
+ createCursorRequest,
12
+ cursorCoveredPrefixDigest,
13
+ cursorInstructionDigest,
14
+ } from "./cursor/request-builder";
11
15
  import {
12
16
  createLiveCursorTransport,
13
17
  CursorMissingCredentialError,
14
18
  rekeyCursorContextUsage,
15
19
  resolveCursorToken,
20
+ capturedCursorCheckpointBytes,
16
21
  } from "./cursor/live-transport";
22
+ import {
23
+ commitCursorCheckpoint,
24
+ cursorCheckpointRefHash,
25
+ invalidateCursorCheckpoint,
26
+ } from "./cursor/checkpoint-store";
27
+ import { debugProviderDiagnostic } from "../lib/debug";
17
28
  import { rememberCursorThreadConversation } from "./cursor/thread-continuity";
18
29
  import { runCursorTurnWithRetry } from "./cursor/transport-retry";
19
30
  import {
@@ -96,6 +107,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
96
107
  /* Missing credential is handled by the live transport path below. */
97
108
  }
98
109
  }
110
+ const inheritedCheckpointRef = _parsed._providerContinuation?.cursor?.checkpointRef;
99
111
  const previousConversationId = _parsed._cursorConversationId;
100
112
  let request = createCursorRequest(_parsed);
101
113
  // The builder may derive a stable provider id from the client thread when Responses state
@@ -112,6 +124,48 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
112
124
  let emittedOutput = false;
113
125
  let replayUnsafe = false;
114
126
  const lastRawIsToolResult = _parsed.context.messages.at(-1)?.role === "toolResult";
127
+ let completedNormally = false;
128
+ let lastTransport: { captured?: Uint8Array } | undefined;
129
+ let emittedClientTool = false;
130
+
131
+ const commitCapturedCheckpoint = (activeRequest: ReturnType<typeof createCursorRequest>): void => {
132
+ if (
133
+ replayUnsafe
134
+ || emittedClientTool
135
+ || activeRequest.contextUsageStoreCheckpoints === false
136
+ || !lastTransport?.captured
137
+ || lastTransport.captured.byteLength === 0
138
+ ) return;
139
+ const previousRef = _parsed._providerContinuation?.cursor?.checkpointRef;
140
+ const coveredMessageCount = _parsed.context.messages.length;
141
+ const checkpointRef = commitCursorCheckpoint({
142
+ conversationId: activeRequest.conversationId,
143
+ identityScope: _parsed._cursorIdentityScope,
144
+ modelId: cursorCheckpointModelAffinityId(activeRequest.modelId),
145
+ checkpointBytes: lastTransport.captured,
146
+ coveredMessageCount,
147
+ prefixDigest: cursorCoveredPrefixDigest(_parsed, coveredMessageCount),
148
+ systemDigest: cursorInstructionDigest(_parsed),
149
+ });
150
+ if (!checkpointRef) return;
151
+ if (previousRef && previousRef !== checkpointRef) invalidateCursorCheckpoint(previousRef);
152
+ _parsed._providerContinuation = {
153
+ ...(_parsed._providerContinuation ?? {}),
154
+ cursor: {
155
+ ...(_parsed._providerContinuation?.cursor ?? {}),
156
+ conversationId: activeRequest.conversationId,
157
+ checkpointUsable: true,
158
+ checkpointRef,
159
+ },
160
+ };
161
+ debugProviderDiagnostic("cursor", "checkpoint-continuation", {
162
+ mode: activeRequest.continuationMode ?? "full-replay",
163
+ conversationHash: activeRequest.conversationId.slice(0, 16),
164
+ checkpointRefHash: cursorCheckpointRefHash(checkpointRef),
165
+ checkpointBytes: lastTransport.captured.byteLength,
166
+ wireModel: activeRequest.modelId,
167
+ });
168
+ };
115
169
 
116
170
  const runOnce = async (activeRequest: ReturnType<typeof createCursorRequest>) => {
117
171
  await runCursorTurnWithRetry(
@@ -132,6 +186,10 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
132
186
  return;
133
187
  }
134
188
  if (message.type === "local_side_effect") replayUnsafe = true;
189
+ if (message.type === "done") completedNormally = true;
190
+ if (message.type === "tool_call_end") emittedClientTool = true;
191
+ const captured = capturedCursorCheckpointBytes(activeTransport);
192
+ if (captured) lastTransport = { captured };
135
193
  const events = mapCursorServerMessage(message, {
136
194
  kv,
137
195
  writeClient: clientMessage => {
@@ -140,7 +198,28 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
140
198
  });
141
199
  for (const event of events) {
142
200
  if (event.type !== "heartbeat") emittedOutput = true;
143
- emit(event);
201
+ if (event.type === "done") {
202
+ commitCapturedCheckpoint(activeRequest);
203
+ const inheritedCursor = _parsed._providerContinuation?.cursor;
204
+ const isolatedOrCompaction =
205
+ _parsed._cursorIsolateConversation === true
206
+ || activeRequest.contextUsageStoreCheckpoints === false;
207
+ const providerState = inheritedCursor
208
+ ? {
209
+ cursor: isolatedOrCompaction
210
+ ? {
211
+ conversationId: activeRequest.conversationId,
212
+ ...(inheritedCursor.checkpointUsable !== undefined
213
+ ? { checkpointUsable: inheritedCursor.checkpointUsable }
214
+ : {}),
215
+ }
216
+ : { ...inheritedCursor, conversationId: activeRequest.conversationId },
217
+ }
218
+ : undefined;
219
+ emit(providerState ? { ...event, providerState } : event);
220
+ } else {
221
+ emit(event);
222
+ }
144
223
  }
145
224
  },
146
225
  );
@@ -163,6 +242,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
163
242
  throw err;
164
243
  }
165
244
  const failedConversationId = request.conversationId;
245
+ lastTransport = undefined;
166
246
  _parsed._cursorConversationId = undefined;
167
247
  request = createCursorRequest(_parsed, { forceFreshConversation: true });
168
248
  rekeyContextUsage(failedConversationId, request.conversationId);
@@ -179,6 +259,32 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
179
259
  }
180
260
  await runOnce(request);
181
261
  }
262
+ if (
263
+ request.checkpointInvalidationReason
264
+ && request.checkpointInvalidationReason !== "missing_ref"
265
+ ) {
266
+ invalidateCursorCheckpoint(inheritedCheckpointRef);
267
+ debugProviderDiagnostic("cursor", "checkpoint-invalidated", {
268
+ reason: request.checkpointInvalidationReason,
269
+ });
270
+ } else if (!completedNormally && request.checkpointInvalidationReason) {
271
+ debugProviderDiagnostic("cursor", "checkpoint-invalidated", {
272
+ reason: request.checkpointInvalidationReason,
273
+ });
274
+ }
275
+ if (
276
+ _parsed._cursorIsolateConversation === true
277
+ || request.contextUsageStoreCheckpoints === false
278
+ ) {
279
+ const inherited = _parsed._providerContinuation?.cursor;
280
+ if (inherited) {
281
+ const { checkpointRef: _ignoredCheckpointRef, ...cursorWithoutCheckpointRef } = inherited;
282
+ _parsed._providerContinuation = {
283
+ ...(_parsed._providerContinuation ?? {}),
284
+ cursor: cursorWithoutCheckpointRef,
285
+ };
286
+ }
287
+ }
182
288
  } catch (err) {
183
289
  if (isCursorBenignCancelError(err)) return;
184
290
  const partialUsage = (err as { partialUsage?: import("../types").OcxUsage }).partialUsage;
@@ -702,11 +702,38 @@ export function applyAntigravityReplay(model: string, sessionId: string, content
702
702
  if (!fc) continue;
703
703
  if (part.thoughtSignature !== undefined || part.thought_signature !== undefined) continue;
704
704
  const ck = functionCallKey(fc.name, fc.args);
705
- const call = ck ? entry.byCall.get(ck) : undefined;
706
- if (call && ck) {
705
+ let call = ck ? entry.byCall.get(ck) : undefined;
706
+ let matchedKey = ck;
707
+ if (!call && typeof fc.name === "string" && typeof fc.args === "object" && fc.args !== null) {
708
+ // Freeform / custom tool replay unwrap:
709
+ // The client replays custom_tool_call with arguments: { input: "..." }.
710
+ // Upstream was invoked with args: { input: "..." } or raw string or parsed JSON.
711
+ const argsObj = fc.args as Record<string, unknown>;
712
+ if (typeof argsObj.input === "string") {
713
+ const trimmedInput = argsObj.input.trim();
714
+ if (
715
+ trimmedInput.length <= REPLAY_MAX_CANONICAL_ARGS_BYTES
716
+ && utf8.encode(trimmedInput).byteLength <= REPLAY_MAX_CANONICAL_ARGS_BYTES
717
+ ) {
718
+ try {
719
+ const parsedInput = JSON.parse(trimmedInput);
720
+ if (parsedInput && typeof parsedInput === "object") {
721
+ const altKey = functionCallKey(fc.name, parsedInput);
722
+ if (altKey && entry.byCall.has(altKey)) {
723
+ call = entry.byCall.get(altKey);
724
+ matchedKey = altKey;
725
+ }
726
+ }
727
+ } catch {
728
+ // not JSON, keep default
729
+ }
730
+ }
731
+ }
732
+ }
733
+ if (call && matchedKey) {
707
734
  part.thoughtSignature = call.signature;
708
- entry.byCall.delete(ck);
709
- entry.byCall.set(ck, { ...call, touchedAtMs: now });
735
+ entry.byCall.delete(matchedKey);
736
+ entry.byCall.set(matchedKey, { ...call, touchedAtMs: now });
710
737
  touched = true;
711
738
  }
712
739
  }