lody 0.93.3 → 0.95.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.
@@ -4240,9 +4240,9 @@ function $constructor(name, initializer3, params) {
4240
4240
  inst._zod.traits.add(name);
4241
4241
  initializer3(inst, def);
4242
4242
  const proto = _.prototype;
4243
- const keys = Object.keys(proto);
4244
- for (let i = 0; i < keys.length; i++) {
4245
- const k = keys[i];
4243
+ const keys2 = Object.keys(proto);
4244
+ for (let i = 0; i < keys2.length; i++) {
4245
+ const k = keys2[i];
4246
4246
  if (!(k in inst)) {
4247
4247
  inst[k] = proto[k].bind(inst);
4248
4248
  }
@@ -4467,12 +4467,12 @@ function getElementAtPath(obj, path10) {
4467
4467
  return path10.reduce((acc, key) => acc?.[key], obj);
4468
4468
  }
4469
4469
  function promiseAllObject(promisesObj) {
4470
- const keys = Object.keys(promisesObj);
4471
- const promises = keys.map((key) => promisesObj[key]);
4470
+ const keys2 = Object.keys(promisesObj);
4471
+ const promises = keys2.map((key) => promisesObj[key]);
4472
4472
  return Promise.all(promises).then((results) => {
4473
4473
  const resolvedObj = {};
4474
- for (let i = 0; i < keys.length; i++) {
4475
- resolvedObj[keys[i]] = results[i];
4474
+ for (let i = 0; i < keys2.length; i++) {
4475
+ resolvedObj[keys2[i]] = results[i];
4476
4476
  }
4477
4477
  return resolvedObj;
4478
4478
  });
@@ -6583,8 +6583,8 @@ function handlePropertyResult(result, final, key, input, isOptionalOut) {
6583
6583
  }
6584
6584
  }
6585
6585
  function normalizeDef(def) {
6586
- const keys = Object.keys(def.shape);
6587
- for (const k of keys) {
6586
+ const keys2 = Object.keys(def.shape);
6587
+ for (const k of keys2) {
6588
6588
  if (!def.shape?.[k]?._zod?.traits?.has("$ZodType")) {
6589
6589
  throw new Error(`Invalid element at key "${k}": expected a Zod schema`);
6590
6590
  }
@@ -6592,9 +6592,9 @@ function normalizeDef(def) {
6592
6592
  const okeys = optionalKeys(def.shape);
6593
6593
  return {
6594
6594
  ...def,
6595
- keys,
6596
- keySet: new Set(keys),
6597
- numKeys: keys.length,
6595
+ keys: keys2,
6596
+ keySet: new Set(keys2),
6597
+ numKeys: keys2.length,
6598
6598
  optionalKeys: new Set(okeys)
6599
6599
  };
6600
6600
  }
@@ -16411,11 +16411,11 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
16411
16411
  inst._zod.processJSONSchema = (ctx, json2, params) => enumProcessor(inst, ctx, json2);
16412
16412
  inst.enum = def.entries;
16413
16413
  inst.options = Object.values(def.entries);
16414
- const keys = new Set(Object.keys(def.entries));
16414
+ const keys2 = new Set(Object.keys(def.entries));
16415
16415
  inst.extract = (values, params) => {
16416
16416
  const newEntries = {};
16417
16417
  for (const value of values) {
16418
- if (keys.has(value)) {
16418
+ if (keys2.has(value)) {
16419
16419
  newEntries[value] = def.entries[value];
16420
16420
  } else
16421
16421
  throw new Error(`Key ${value} not found in enum`);
@@ -16430,7 +16430,7 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
16430
16430
  inst.exclude = (values, params) => {
16431
16431
  const newEntries = { ...def.entries };
16432
16432
  for (const value of values) {
16433
- if (keys.has(value)) {
16433
+ if (keys2.has(value)) {
16434
16434
  delete newEntries[value];
16435
16435
  } else
16436
16436
  throw new Error(`Key ${value} not found in enum`);
@@ -21253,6 +21253,68 @@ function toPromptUsage(tokenCount) {
21253
21253
  thoughtTokens: tokenCount.reasoningOutputTokens
21254
21254
  };
21255
21255
  }
21256
+ var keys = [
21257
+ "inputTokens",
21258
+ "outputTokens",
21259
+ "cacheReadInputTokens",
21260
+ "cacheCreationInputTokens",
21261
+ "reasoningOutputTokens"
21262
+ ];
21263
+ var empty = () => ({
21264
+ inputTokens: 0,
21265
+ outputTokens: 0,
21266
+ cacheReadInputTokens: 0,
21267
+ cacheCreationInputTokens: 0,
21268
+ reasoningOutputTokens: 0
21269
+ });
21270
+ function normalize(total) {
21271
+ return {
21272
+ inputTokens: Math.max(0, total.inputTokens - total.cachedInputTokens - total.cacheWriteInputTokens),
21273
+ outputTokens: Math.max(0, total.outputTokens - total.reasoningOutputTokens),
21274
+ cacheReadInputTokens: total.cachedInputTokens,
21275
+ cacheCreationInputTokens: total.cacheWriteInputTokens,
21276
+ reasoningOutputTokens: total.reasoningOutputTokens
21277
+ };
21278
+ }
21279
+ var CodexTurnUsage = class {
21280
+ previous;
21281
+ turn;
21282
+ requestedModel;
21283
+ constructor(fresh = false, snapshot) {
21284
+ this.previous = snapshot ? normalize(snapshot) : fresh ? empty() : void 0;
21285
+ }
21286
+ prepare(model) {
21287
+ this.requestedModel = model;
21288
+ }
21289
+ start(turnId, model) {
21290
+ if (this.turn?.id === turnId) return;
21291
+ this.turn = { id: turnId, model: this.requestedModel ?? model, usage: empty() };
21292
+ }
21293
+ update(params) {
21294
+ const next = normalize(params.tokenUsage.total);
21295
+ const turn = this.turn;
21296
+ if (!turn) {
21297
+ this.previous = next;
21298
+ return void 0;
21299
+ }
21300
+ if (params.turnId !== turn.id) return void 0;
21301
+ if (!this.previous) {
21302
+ this.previous = next;
21303
+ return void 0;
21304
+ }
21305
+ const delta = empty();
21306
+ for (const key of keys) delta[key] = Math.max(0, (next[key] ?? 0) - (this.previous[key] ?? 0));
21307
+ this.previous = next;
21308
+ if (!keys.some((key) => (delta[key] ?? 0) > 0)) return void 0;
21309
+ for (const key of keys) turn.usage[key] = (turn.usage[key] ?? 0) + (delta[key] ?? 0);
21310
+ return {
21311
+ sessionId: params.threadId,
21312
+ usage: { ...turn.usage, ...params.tokenUsage.modelContextWindow !== null && { contextWindow: params.tokenUsage.modelContextWindow } },
21313
+ modelUsage: { [turn.model]: { ...turn.usage } },
21314
+ _meta: { codex: { usageTurnId: turn.id } }
21315
+ };
21316
+ }
21317
+ };
21256
21318
  function hasOnlyWinLineEndings(string4) {
21257
21319
  return string4.includes("\r\n") && !string4.startsWith("\n") && !string4.match(/[^\r]\n/);
21258
21320
  }
@@ -23595,6 +23657,13 @@ var CodexEventHandler = class _CodexEventHandler {
23595
23657
  case "error":
23596
23658
  return await this.createErrorEvent(notification.params);
23597
23659
  case "turn/started":
23660
+ if (notification.params.threadId === this.sessionState.sessionId) {
23661
+ this.sessionState.turnUsage ??= new CodexTurnUsage();
23662
+ this.sessionState.turnUsage.start(
23663
+ notification.params.turn.id,
23664
+ this.sessionState.currentModelId.replace(/\[.*?]$/, "")
23665
+ );
23666
+ }
23598
23667
  this.sessionState.currentTurnId = notification.params.turn.id;
23599
23668
  await this.flushPendingErrors();
23600
23669
  return null;
@@ -23733,34 +23802,16 @@ var CodexEventHandler = class _CodexEventHandler {
23733
23802
  }
23734
23803
  }
23735
23804
  async emitExtNotification(notification) {
23736
- switch (notification.method) {
23737
- case "thread/tokenUsage/updated":
23738
- await this.notifyExt(
23739
- ACP_EXT_SESSION_USAGE_UPDATE_METHOD,
23740
- this.createSessionUsageExtNotification(notification.params)
23741
- );
23742
- return;
23743
- default:
23744
- return;
23805
+ if (notification.method === "thread/tokenUsage/updated" && notification.params.threadId === this.sessionState.sessionId) {
23806
+ this.sessionState.turnUsage ??= new CodexTurnUsage();
23807
+ const update = this.sessionState.turnUsage.update(notification.params);
23808
+ if (update) await this.notifyExt(ACP_EXT_SESSION_USAGE_UPDATE_METHOD, update);
23745
23809
  }
23746
23810
  }
23747
23811
  async notifyExt(method, params) {
23748
23812
  const extMethod = method.startsWith("_") ? method : `_${method}`;
23749
23813
  await this.connection.notify(extMethod, params);
23750
23814
  }
23751
- createSessionUsageExtNotification(params) {
23752
- const totalUsage = params.tokenUsage.total;
23753
- return {
23754
- sessionId: this.sessionState.sessionId,
23755
- usage: {
23756
- inputTokens: totalUsage.inputTokens,
23757
- outputTokens: totalUsage.outputTokens,
23758
- cacheReadInputTokens: totalUsage.cachedInputTokens,
23759
- reasoningOutputTokens: totalUsage.reasoningOutputTokens,
23760
- ...params.tokenUsage.modelContextWindow === null ? {} : { contextWindow: params.tokenUsage.modelContextWindow }
23761
- }
23762
- };
23763
- }
23764
23815
  createSessionRateLimitsExtNotification(rateLimits) {
23765
23816
  return {
23766
23817
  rateLimits: [toLodyRateLimit(rateLimits)],
@@ -26651,7 +26702,7 @@ var package_default = {
26651
26702
  dependencies: {
26652
26703
  "@agentclientprotocol/sdk": "^1.4.0",
26653
26704
  "@openai/codex": "^0.153.4",
26654
- "acp-extension-core": "0.1.4",
26705
+ "acp-extension-core": "0.1.5",
26655
26706
  diff: "^9.0.0",
26656
26707
  open: "^11.0.1",
26657
26708
  "vscode-jsonrpc": "^9.0.1",
@@ -27710,8 +27761,8 @@ var CodexAcpClient = class {
27710
27761
  delivery: "inline"
27711
27762
  }, onTurnStarted);
27712
27763
  }
27713
- async runCompact(sessionId) {
27714
- await this.codexClient.runCompact({ threadId: sessionId });
27764
+ async runCompact(sessionId, onTurnStarted) {
27765
+ return await this.codexClient.runCompact({ threadId: sessionId }, onTurnStarted);
27715
27766
  }
27716
27767
  async getGoal(sessionId) {
27717
27768
  const response = await this.codexClient.threadGoalGet({ threadId: sessionId });
@@ -28419,24 +28470,29 @@ var CodexAppServerClient = class {
28419
28470
  mcpServerStartupStates = /* @__PURE__ */ new Map();
28420
28471
  mcpServerStartupResolvers = [];
28421
28472
  pendingTurnCompletionResolvers = /* @__PURE__ */ new Map();
28422
- pendingCompactionCompletionResolvers = /* @__PURE__ */ new Map();
28473
+ pendingCompactTurns = /* @__PURE__ */ new Map();
28423
28474
  turnCompletionCaptures = /* @__PURE__ */ new Map();
28424
28475
  turnRoutingCaptures = /* @__PURE__ */ new Map();
28425
28476
  threadStatusCaptures = /* @__PURE__ */ new Map();
28426
28477
  threadGoalUpdateCaptures = /* @__PURE__ */ new Map();
28427
28478
  threadGoalClearedCaptures = /* @__PURE__ */ new Map();
28428
28479
  threadSettings = /* @__PURE__ */ new Map();
28480
+ tokenUsage = /* @__PURE__ */ new Map();
28429
28481
  staleTurnIds = /* @__PURE__ */ new Map();
28430
28482
  turnCompletionTerminalError = null;
28431
28483
  constructor(connection) {
28432
28484
  this.connection = connection;
28433
- const failPendingTurns = () => this.rejectAllPendingTurnCompletions(
28434
- new Error("Codex process exited before completing the turn")
28435
- );
28485
+ const failPendingTurns = () => {
28486
+ const error48 = new Error("Codex process exited before completing the turn");
28487
+ this.rejectAllPendingTurnCompletions(error48);
28488
+ };
28436
28489
  this.connection.onClose(failPendingTurns);
28437
28490
  this.connection.onDispose(failPendingTurns);
28438
28491
  this.connection.onUnhandledNotification((data) => {
28439
28492
  const serverNotification = data;
28493
+ if (serverNotification.method === "thread/tokenUsage/updated") {
28494
+ this.tokenUsage.set(serverNotification.params.threadId, serverNotification.params);
28495
+ }
28440
28496
  if (isMcpServerStatusUpdatedNotification(serverNotification)) {
28441
28497
  this.mcpServerStartupVersion += 1;
28442
28498
  this.mcpServerStartupStates.set(serverNotification.params.name, {
@@ -28448,10 +28504,18 @@ var CodexAppServerClient = class {
28448
28504
  this.resolveMcpServerStartupResolvers();
28449
28505
  }
28450
28506
  if (isTurnCompletedNotification(serverNotification)) {
28507
+ const compact = this.pendingCompactTurns.get(serverNotification.params.threadId);
28508
+ if (compact?.turnId === serverNotification.params.turn.id) {
28509
+ compact.resolve(serverNotification.params);
28510
+ }
28451
28511
  this.recordTurnCompleted(serverNotification.params);
28452
28512
  }
28453
- if (isCompactionCompletedNotification(serverNotification)) {
28454
- this.recordCompactionCompleted(serverNotification);
28513
+ if (serverNotification.method === "turn/started") {
28514
+ const compact = this.pendingCompactTurns.get(serverNotification.params.threadId);
28515
+ if (compact && compact.turnId === null) {
28516
+ compact.turnId = serverNotification.params.turn.id;
28517
+ compact.onTurnStarted?.(compact.turnId);
28518
+ }
28455
28519
  }
28456
28520
  if (isThreadStatusChangedNotification(serverNotification)) {
28457
28521
  this.recordThreadStatusChanged(serverNotification.params);
@@ -28536,6 +28600,7 @@ var CodexAppServerClient = class {
28536
28600
  this.elicitationHandlers.set(threadId, handler);
28537
28601
  }
28538
28602
  clearThreadHandlers(threadId) {
28603
+ this.tokenUsage.delete(threadId);
28539
28604
  this.notificationHandlers.delete(threadId);
28540
28605
  this.approvalHandlers.delete(threadId);
28541
28606
  this.elicitationHandlers.delete(threadId);
@@ -28753,10 +28818,25 @@ var CodexAppServerClient = class {
28753
28818
  threadStatusChanged: handleThreadStatusChanged
28754
28819
  };
28755
28820
  }
28756
- async runCompact(params) {
28757
- const compactionCompleted = this.awaitCompactionCompleted(params.threadId);
28758
- await this.threadCompactStart(params);
28759
- return await compactionCompleted;
28821
+ async runCompact(params, onTurnStarted) {
28822
+ if (this.turnCompletionTerminalError) throw this.turnCompletionTerminalError;
28823
+ if (this.pendingCompactTurns.has(params.threadId)) {
28824
+ throw new Error("A compaction request already owns this thread");
28825
+ }
28826
+ const completion = new Promise((resolve, reject) => {
28827
+ this.pendingCompactTurns.set(params.threadId, {
28828
+ turnId: null,
28829
+ onTurnStarted,
28830
+ resolve,
28831
+ reject
28832
+ });
28833
+ });
28834
+ try {
28835
+ const [, completed] = await Promise.all([this.threadCompactStart(params), completion]);
28836
+ return completed;
28837
+ } finally {
28838
+ this.pendingCompactTurns.delete(params.threadId);
28839
+ }
28760
28840
  }
28761
28841
  async turnInterrupt(params) {
28762
28842
  return await this.sendRequest({ method: "turn/interrupt", params });
@@ -28781,6 +28861,9 @@ var CodexAppServerClient = class {
28781
28861
  async threadProjectUpdate(params) {
28782
28862
  await this.sendRequest({ method: "thread/metadata/update", params });
28783
28863
  }
28864
+ getThreadTokenUsage(threadId) {
28865
+ return this.tokenUsage.get(threadId);
28866
+ }
28784
28867
  async threadStart(params) {
28785
28868
  return await this.sendRequest({ method: "thread/start", params });
28786
28869
  }
@@ -28901,13 +28984,6 @@ var CodexAppServerClient = class {
28901
28984
  threadResolvers.set(turnId, { resolve, reject });
28902
28985
  });
28903
28986
  }
28904
- async awaitCompactionCompleted(threadId) {
28905
- return await new Promise((resolve) => {
28906
- const resolvers = this.pendingCompactionCompletionResolvers.get(threadId) ?? /* @__PURE__ */ new Set();
28907
- resolvers.add(resolve);
28908
- this.pendingCompactionCompletionResolvers.set(threadId, resolvers);
28909
- });
28910
- }
28911
28987
  resolveTurnInterrupted(threadId, turnId) {
28912
28988
  this.recordTurnCompleted({
28913
28989
  threadId,
@@ -28976,20 +29052,6 @@ var CodexAppServerClient = class {
28976
29052
  capture(event);
28977
29053
  }
28978
29054
  }
28979
- recordCompactionCompleted(event) {
28980
- const threadId = extractThreadId(event);
28981
- if (threadId === null) {
28982
- return;
28983
- }
28984
- const resolvers = this.pendingCompactionCompletionResolvers.get(threadId);
28985
- if (!resolvers) {
28986
- return;
28987
- }
28988
- this.pendingCompactionCompletionResolvers.delete(threadId);
28989
- for (const resolve of resolvers) {
28990
- resolve(event);
28991
- }
28992
- }
28993
29055
  recordThreadStatusChanged(event) {
28994
29056
  const captures = this.threadStatusCaptures.get(event.threadId);
28995
29057
  if (!captures) {
@@ -29074,6 +29136,8 @@ var CodexAppServerClient = class {
29074
29136
  */
29075
29137
  rejectAllPendingTurnCompletions(error48) {
29076
29138
  this.turnCompletionTerminalError ??= error48;
29139
+ for (const compact of this.pendingCompactTurns.values()) compact.reject(error48);
29140
+ this.pendingCompactTurns.clear();
29077
29141
  const threads = [...this.pendingTurnCompletionResolvers.values()];
29078
29142
  this.pendingTurnCompletionResolvers.clear();
29079
29143
  for (const threadResolvers of threads) {
@@ -29234,12 +29298,6 @@ function isThreadGoalUpdatedNotification(notification) {
29234
29298
  function isThreadGoalClearedNotification(notification) {
29235
29299
  return notification.method === "thread/goal/cleared";
29236
29300
  }
29237
- function isCompactionCompletedNotification(notification) {
29238
- if (notification.method === "thread/compacted") {
29239
- return true;
29240
- }
29241
- return notification.method === "item/completed" && notification.params.item.type === "contextCompaction";
29242
- }
29243
29301
  function goalsMatch(left, right) {
29244
29302
  return left.threadId === right.threadId && left.objective === right.objective && left.status === right.status && left.tokenBudget === right.tokenBudget && left.updatedAt === right.updatedAt;
29245
29303
  }
@@ -29504,8 +29562,18 @@ var CodexCommands = class {
29504
29562
  return { handled: options.setConfigOption !== void 0 };
29505
29563
  }
29506
29564
  case "compact": {
29507
- await this.runWithProcessCheck(() => this.codexAcpClient.runCompact(sessionId));
29508
- return { handled: true };
29565
+ options.onTurnStartPending?.();
29566
+ options.onCompactionStarted?.();
29567
+ try {
29568
+ const turnCompleted = await this.runWithProcessCheck(
29569
+ () => this.codexAcpClient.runCompact(sessionId, (turnId) => {
29570
+ options.onTurnStarted?.(turnId, sessionId);
29571
+ })
29572
+ );
29573
+ return { handled: true, turnCompleted };
29574
+ } finally {
29575
+ options.onCompactionFinished?.();
29576
+ }
29509
29577
  }
29510
29578
  case "goal": {
29511
29579
  return await this.runGoalCommand(sessionState, command.rest, options);
@@ -31852,6 +31920,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
31852
31920
  sessionState.cwd,
31853
31921
  () => sessionState.sessionTitleSource
31854
31922
  );
31923
+ if (operation.kind === "new") sessionState.turnUsage = new CodexTurnUsage(true);
31855
31924
  this.installSessionState(sessionState);
31856
31925
  this.publishRateLimitsAsync(sessionState);
31857
31926
  subscribed = false;
@@ -32599,7 +32668,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32599
32668
  throw error48;
32600
32669
  }
32601
32670
  logger.error(`Steering request for session ${params.sessionId} failed`, error48);
32602
- return { outcome: "failed" };
32671
+ throw error48;
32603
32672
  } finally {
32604
32673
  if (queue.isIdle && this.steeringQueues.get(params.sessionId) === queue) {
32605
32674
  this.steeringQueues.delete(params.sessionId);
@@ -32636,7 +32705,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
32636
32705
  this.assertSteerInputSupported(params, sessionState);
32637
32706
  const turnId = await this.getSteerableTurnId(sessionState);
32638
32707
  if (turnId) {
32639
- const injected = await this.injectSteerIntoActiveTurn(params, turnId, sessionState);
32708
+ const injected = await this.injectSteerIntoActiveTurn(params, turnId);
32640
32709
  if (injected) {
32641
32710
  logger.log("Steering session injected", { sessionId: params.sessionId, turnId });
32642
32711
  return { outcome: "injected" };
@@ -32657,15 +32726,10 @@ Check ${configPath} and project .codex directories, especially their config.toml
32657
32726
  /**
32658
32727
  * Attempts to inject the prompt into the given running turn.
32659
32728
  *
32660
- * A failed injection is fatal only when the turn is still the session's
32661
- * current turn and Codex reported something other than "no active turn to
32662
- * steer". Otherwise the turn has already ended underneath us and steering
32663
- * reports a failed delivery.
32664
- *
32665
- * @returns true when the prompt was injected; false when the target turn
32666
- * already ended.
32729
+ * After submission, only an explicit refusal proves non-delivery. A turn
32730
+ * ending (including Stop) does not prove whether it consumed the input.
32667
32731
  */
32668
- async injectSteerIntoActiveTurn(params, turnId, sessionState) {
32732
+ async injectSteerIntoActiveTurn(params, turnId) {
32669
32733
  const activePrompt = this.activePrompts.get(params.sessionId);
32670
32734
  const activeTurn = activePrompt?.currentTurn;
32671
32735
  const firstText = params.prompt[0]?.type === "text" ? params.prompt[0].text : "";
@@ -32679,8 +32743,23 @@ Check ${configPath} and project .codex directories, especially their config.toml
32679
32743
  if (pending.has(params.steerId)) {
32680
32744
  throw RequestError.invalidRequest(`Duplicate Codex steer id: ${params.steerId}`);
32681
32745
  }
32682
- pending.set(params.steerId, { activePrompt, turnId });
32746
+ let resolveApplied = () => {
32747
+ };
32748
+ const applied = new Promise((resolve) => {
32749
+ resolveApplied = resolve;
32750
+ });
32751
+ const steer = {
32752
+ activePrompt,
32753
+ threadId: activeTurn.threadId,
32754
+ turnId,
32755
+ requestPending: true,
32756
+ acknowledgement: null,
32757
+ applied,
32758
+ resolveApplied
32759
+ };
32760
+ pending.set(params.steerId, steer);
32683
32761
  this.pendingSteers.set(params.sessionId, pending);
32762
+ let requestAccepted = false;
32684
32763
  try {
32685
32764
  const response = await this.runWithProcessCheck(() => this.codexAcpClient.steerTurn({
32686
32765
  threadId: activeTurn.threadId,
@@ -32694,18 +32773,48 @@ Check ${configPath} and project .codex directories, especially their config.toml
32694
32773
  `Codex steered unexpected turn ${response.turnId}; expected ${turnId}`
32695
32774
  );
32696
32775
  }
32776
+ requestAccepted = true;
32697
32777
  return true;
32698
32778
  } catch (err) {
32699
- if (pending.get(params.steerId)?.activePrompt === activePrompt) {
32700
- pending.delete(params.steerId);
32701
- if (pending.size === 0) this.pendingSteers.delete(params.sessionId);
32779
+ const refused = this.isNoActiveTurnToSteerError(err);
32780
+ if (await this.reconcileSteer(params, steer, !refused)) {
32781
+ await this.acknowledgeSteer(params.sessionId, params.steerId, steer);
32782
+ return true;
32702
32783
  }
32703
- await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
32704
- const turnStillActive = sessionState.currentTurnId === turnId;
32705
- if (turnStillActive && !this.isNoActiveTurnToSteerError(err)) {
32706
- throw err;
32784
+ if (refused) return false;
32785
+ throw err;
32786
+ } finally {
32787
+ steer.requestPending = false;
32788
+ if (!requestAccepted || this.activePrompts.get(params.sessionId) !== activePrompt) {
32789
+ this.removePendingSteer(params.sessionId, params.steerId, steer);
32707
32790
  }
32708
- return false;
32791
+ }
32792
+ }
32793
+ async reconcileSteer(params, steer, readHistory) {
32794
+ let finished = false;
32795
+ let timeout;
32796
+ try {
32797
+ const historyApplied = (async () => {
32798
+ await this.codexAcpClient.waitForSessionNotifications(params.sessionId);
32799
+ if (steer.acknowledgement !== null) return true;
32800
+ if (finished || !readHistory) return false;
32801
+ const thread = await this.codexAcpClient.readSessionHistory(steer.threadId);
32802
+ return thread.id === steer.threadId && thread.turns.some((turn) => turn.id === steer.turnId && turn.items.some((item) => item.type === "userMessage" && item.clientId === params.steerId));
32803
+ })().catch((error48) => {
32804
+ logger.error("Could not reconcile Codex steer history", error48);
32805
+ return false;
32806
+ });
32807
+ return await Promise.race([
32808
+ historyApplied,
32809
+ steer.applied.then(() => true),
32810
+ new Promise((resolve) => {
32811
+ timeout = setTimeout(() => resolve(false), 5e3);
32812
+ timeout.unref?.();
32813
+ })
32814
+ ]) || steer.acknowledgement !== null;
32815
+ } finally {
32816
+ finished = true;
32817
+ clearTimeout(timeout);
32709
32818
  }
32710
32819
  }
32711
32820
  async startGoalContinuationIfCurrent(sessionState, sessionGeneration, goalControlGeneration, expectedGoal) {
@@ -33515,6 +33624,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33515
33624
  signal: abortController.signal,
33516
33625
  currentTurn: null,
33517
33626
  hasCompletedTurn: false,
33627
+ compactionInFlight: false,
33518
33628
  requestCancel: () => {
33519
33629
  if (abortController.signal.aborted) {
33520
33630
  return;
@@ -33549,7 +33659,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33549
33659
  const pending = this.pendingSteers.get(sessionId);
33550
33660
  if (!pending) return;
33551
33661
  for (const [steerId, steer] of pending) {
33552
- if (steer.activePrompt === activePrompt) pending.delete(steerId);
33662
+ if (steer.activePrompt === activePrompt && !steer.requestPending) pending.delete(steerId);
33553
33663
  }
33554
33664
  if (pending.size === 0) this.pendingSteers.delete(sessionId);
33555
33665
  }
@@ -33560,14 +33670,26 @@ Check ${configPath} and project .codex directories, especially their config.toml
33560
33670
  const pending = this.pendingSteers.get(sessionId);
33561
33671
  if (!pending) return;
33562
33672
  const steer = pending.get(steerId);
33563
- if (!steer || steer.activePrompt !== activePrompt || steer.turnId !== event.params.turnId) return;
33673
+ if (!steer || steer.activePrompt !== activePrompt || steer.threadId !== event.params.threadId || steer.turnId !== event.params.turnId) return;
33674
+ await this.acknowledgeSteer(sessionId, steerId, steer);
33675
+ }
33676
+ removePendingSteer(sessionId, steerId, steer) {
33677
+ const pending = this.pendingSteers.get(sessionId);
33678
+ if (pending?.get(steerId) !== steer) return;
33564
33679
  pending.delete(steerId);
33565
33680
  if (pending.size === 0) this.pendingSteers.delete(sessionId);
33566
- await this.connection.notify(CODEX_STEER_APPLIED_METHOD, { sessionId, steerId });
33681
+ }
33682
+ async acknowledgeSteer(sessionId, steerId, steer) {
33683
+ if (steer.acknowledgement === null) {
33684
+ steer.acknowledgement = Promise.resolve().then(() => this.connection.notify(CODEX_STEER_APPLIED_METHOD, { sessionId, steerId }));
33685
+ this.removePendingSteer(sessionId, steerId, steer);
33686
+ steer.resolveApplied();
33687
+ }
33688
+ await steer.acknowledgement;
33567
33689
  }
33568
33690
  cancelBeforeTurnStarted(activePrompt) {
33569
33691
  return activePrompt.cancelSignal.then(() => {
33570
- if (activePrompt.currentTurn === null) {
33692
+ if (activePrompt.currentTurn === null && !activePrompt.compactionInFlight) {
33571
33693
  return null;
33572
33694
  }
33573
33695
  return new Promise(() => {
@@ -33635,7 +33757,12 @@ Check ${configPath} and project .codex directories, especially their config.toml
33635
33757
  logger.error(`${requestName} - turnInterrupt failed`, err);
33636
33758
  }
33637
33759
  }
33638
- interruptLateStartedTurn(turn) {
33760
+ interruptLateStartedTurn(turn, activePrompt) {
33761
+ if (activePrompt.compactionInFlight) {
33762
+ this.codexAcpClient.markTurnStale(turn);
33763
+ void this.requestTurnInterrupt(turn, "Cancel");
33764
+ return;
33765
+ }
33639
33766
  void this.interruptPromptTurn(turn, "Close");
33640
33767
  }
33641
33768
  promptShouldStop(sessionId, activePrompt) {
@@ -33747,6 +33874,11 @@ Check ${configPath} and project .codex directories, especially their config.toml
33747
33874
  return this.cancelledPromptResponse(sessionState);
33748
33875
  };
33749
33876
  try {
33877
+ sessionState.turnUsage ??= new CodexTurnUsage(
33878
+ false,
33879
+ this.codexAcpClient.appServerClient.getThreadTokenUsage(params.sessionId)?.tokenUsage.total
33880
+ );
33881
+ sessionState.turnUsage.prepare(ModelId.fromString(sessionState.currentModelId).model);
33750
33882
  const promptEventHandler = new CodexEventHandler(
33751
33883
  this.connection,
33752
33884
  sessionState,
@@ -33783,7 +33915,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33783
33915
  const turn = { threadId: params.sessionId, turnId: event.params.turn.id };
33784
33916
  activePrompt.currentTurn = turn;
33785
33917
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33786
- this.interruptLateStartedTurn(turn);
33918
+ this.interruptLateStartedTurn(turn, activePrompt);
33787
33919
  return;
33788
33920
  }
33789
33921
  recoverableSessionFailure = sessionState.sessionFailure;
@@ -33825,13 +33957,19 @@ Check ${configPath} and project .codex directories, especially their config.toml
33825
33957
  if (threadId === params.sessionId) goalLifecycle.startTurn(turnId);
33826
33958
  activePrompt.currentTurn = turn;
33827
33959
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33828
- this.interruptLateStartedTurn(turn);
33960
+ this.interruptLateStartedTurn(turn, activePrompt);
33829
33961
  return;
33830
33962
  }
33831
33963
  sessionState.currentTurnId = turnId;
33832
33964
  pendingTurnStart?.resolve(turnId);
33833
33965
  onTurnStarted?.();
33834
33966
  },
33967
+ onCompactionStarted: () => {
33968
+ activePrompt.compactionInFlight = true;
33969
+ },
33970
+ onCompactionFinished: () => {
33971
+ activePrompt.compactionInFlight = false;
33972
+ },
33835
33973
  setConfigOption: async (configId, value) => {
33836
33974
  await this.applySessionConfigOption(sessionState, {
33837
33975
  sessionId: sessionState.sessionId,
@@ -33929,6 +34067,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33929
34067
  sessionState.lastTokenUsage = null;
33930
34068
  ensurePendingTurnStart();
33931
34069
  goalLifecycle.prepareTurn();
34070
+ sessionState.turnUsage.prepare(modelId.model);
33932
34071
  const sendPromptPromise = this.runWithProcessCheck(
33933
34072
  () => this.codexAcpClient.sendPrompt(
33934
34073
  effectiveParams,
@@ -33947,7 +34086,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33947
34086
  }
33948
34087
  activePrompt.currentTurn = turn;
33949
34088
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
33950
- this.interruptLateStartedTurn(turn);
34089
+ this.interruptLateStartedTurn(turn, activePrompt);
33951
34090
  return;
33952
34091
  }
33953
34092
  sessionState.currentTurnId = turnId;
@@ -34031,6 +34170,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34031
34170
  activePrompt.currentTurn = null;
34032
34171
  sessionState.currentTurnId = null;
34033
34172
  goalLifecycle.prepareTurn();
34173
+ sessionState.turnUsage.prepare(modelId.model);
34034
34174
  const implementationPromise = this.runWithProcessCheck(
34035
34175
  () => this.codexAcpClient.sendPrompt(
34036
34176
  implementationRequest,
@@ -34045,7 +34185,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
34045
34185
  if (!goalLifecycle.startSubmittedTurn(turnId)) return;
34046
34186
  activePrompt.currentTurn = turn;
34047
34187
  if (this.promptShouldStop(params.sessionId, activePrompt)) {
34048
- this.interruptLateStartedTurn(turn);
34188
+ this.interruptLateStartedTurn(turn, activePrompt);
34049
34189
  return;
34050
34190
  }
34051
34191
  sessionState.currentTurnId = turnId;
@@ -34287,6 +34427,10 @@ ${stderr}` : "";
34287
34427
  return;
34288
34428
  }
34289
34429
  const activePrompt = this.activePrompts.get(params.sessionId);
34430
+ if (activePrompt?.compactionInFlight) {
34431
+ activePrompt.requestCancel();
34432
+ if (activePrompt.currentTurn === null) return;
34433
+ }
34290
34434
  if (activePrompt?.hasCompletedTurn && activePrompt.currentTurn === null) {
34291
34435
  activePrompt.requestCancel();
34292
34436
  return;