opencode-acp 1.18.1-pr.408.164 → 1.18.1-pr.409.166

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.
package/dist/index.js CHANGED
@@ -4555,8 +4555,29 @@ function fingerprintMessage(message) {
4555
4555
  });
4556
4556
  return createHash("sha256").update(JSON.stringify({ role: message.info.role, parts })).digest("hex");
4557
4557
  }
4558
+ function normalizeParentMessageIds(messageIds) {
4559
+ if (!messageIds) return void 0;
4560
+ const migrate = (ref) => {
4561
+ const parsed = parseMessageRef(ref);
4562
+ return parsed !== null ? formatMessageRef(parsed) : ref;
4563
+ };
4564
+ const byRawId = {};
4565
+ const byRef = {};
4566
+ for (const [rawId, ref] of Object.entries(messageIds.byRawId || {})) {
4567
+ const normalized = migrate(ref);
4568
+ byRawId[rawId] = normalized;
4569
+ byRef[normalized] = rawId;
4570
+ }
4571
+ for (const [ref, rawId] of Object.entries(messageIds.byRef || {})) {
4572
+ const normalized = migrate(ref);
4573
+ if (byRef[normalized] === void 0) {
4574
+ byRef[normalized] = rawId;
4575
+ }
4576
+ }
4577
+ return { byRef, byRawId, nextRef: messageIds.nextRef || 1 };
4578
+ }
4558
4579
  function mapForkIds(state, parent, parentMessages, forkMessages) {
4559
- const parentRefs = parent.messageIds?.byRef;
4580
+ const parentRefs = normalizeParentMessageIds(parent.messageIds)?.byRef;
4560
4581
  if (!parentRefs) return null;
4561
4582
  const parentById = new Map(parentMessages.map((message) => [message.info.id, message]));
4562
4583
  const forkById = new Map(forkMessages.map((message) => [message.info.id, message]));
@@ -4830,30 +4851,6 @@ async function updatePerTurnState(state, logger, messages) {
4830
4851
  state.currentTurn = countTurns(state, messages);
4831
4852
  }
4832
4853
  var REGISTRY_SOFT_CAP = 32;
4833
- function createSessionGuard() {
4834
- const locks = /* @__PURE__ */ new Map();
4835
- return async (sessionId, fn) => {
4836
- const previous = locks.get(sessionId) ?? Promise.resolve();
4837
- let release;
4838
- const released = new Promise((resolve2) => {
4839
- release = resolve2;
4840
- });
4841
- const chain = previous.then(() => released);
4842
- locks.set(sessionId, chain);
4843
- try {
4844
- await previous;
4845
- } catch {
4846
- }
4847
- try {
4848
- return await fn();
4849
- } finally {
4850
- release();
4851
- if (locks.get(sessionId) === chain) {
4852
- locks.delete(sessionId);
4853
- }
4854
- }
4855
- };
4856
- }
4857
4854
  var SessionStateRegistry = class {
4858
4855
  constructor(logger, projectDir) {
4859
4856
  this.logger = logger;
@@ -4913,15 +4910,9 @@ var SessionStateRegistry = class {
4913
4910
  get size() {
4914
4911
  return this.states.size;
4915
4912
  }
4916
- // [Issue #404] Per-session FIFO mutex serializing same-session mutations
4917
- // (message transforms, compress/decompress tools, event-hook saves,
4918
- // system-hook limit writes). Shared factory so test stubs compose the
4919
- // exact same implementation instead of drifting.
4920
- withSessionGuard = createSessionGuard();
4921
4913
  // Idempotent: ensureSessionInitialized returns immediately once
4922
- // state.sessionId === sessionId, and coalesces concurrent inits per state
4923
- // object ([Issue #404]), so racing calls for the same session never
4924
- // re-reset or observe partially initialized state.
4914
+ // state.sessionId === sessionId (assigned synchronously before any await),
4915
+ // so repeat calls for the same session never re-reset.
4925
4916
  async getOrCreate(client, sessionId, messages, config) {
4926
4917
  let state = this.states.get(sessionId);
4927
4918
  if (!state) {
@@ -5049,25 +5040,10 @@ function resetSessionState(state) {
5049
5040
  state.qualityGateRetryPending = false;
5050
5041
  state.noContextLimitWarned = false;
5051
5042
  }
5052
- var inflightInits = /* @__PURE__ */ new WeakMap();
5053
5043
  async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
5054
- const inflight = inflightInits.get(state);
5055
- if (inflight) {
5056
- await inflight;
5057
- return;
5058
- }
5059
5044
  if (state.sessionId === sessionId) {
5060
5045
  return;
5061
5046
  }
5062
- const run = runSessionInitialization(client, state, sessionId, logger, messages, config, projectDir);
5063
- inflightInits.set(state, run);
5064
- try {
5065
- await run;
5066
- } finally {
5067
- inflightInits.delete(state);
5068
- }
5069
- }
5070
- async function runSessionInitialization(client, state, sessionId, logger, messages, config, projectDir) {
5071
5047
  resetSessionState(state);
5072
5048
  state.sessionId = sessionId;
5073
5049
  state.storageDir = config?.storagePath ? resolveStorageDir(config.storagePath, projectDir ?? cwd()) : void 0;
@@ -5090,7 +5066,10 @@ async function runSessionInitialization(client, state, sessionId, logger, messag
5090
5066
  let restored = 0;
5091
5067
  if (parentSessionId) {
5092
5068
  try {
5093
- const parent = await loadSessionState(parentSessionId, logger);
5069
+ let parent = await loadSessionState(parentSessionId, logger, state.storageDir);
5070
+ if (!parent && state.storageDir) {
5071
+ parent = await loadSessionState(parentSessionId, logger);
5072
+ }
5094
5073
  const response = parent ? await client.session.messages({ path: { id: parentSessionId } }) : void 0;
5095
5074
  const parentMessages = Array.isArray(response?.data) ? response.data : Array.isArray(response) ? response : [];
5096
5075
  if (parent && parentMessages.length > 0) {
@@ -5171,6 +5150,14 @@ async function runSessionInitialization(client, state, sessionId, logger, messag
5171
5150
  if (persistedAny._persistedLastCompaction !== void 0) {
5172
5151
  state.lastCompaction = Math.max(state.lastCompaction, persistedAny._persistedLastCompaction);
5173
5152
  }
5153
+ const persistedBoundary = typeof persistedAny._persistedLastCompaction === "number" ? persistedAny._persistedLastCompaction : 0;
5154
+ if (state.lastCompaction > persistedBoundary) {
5155
+ resetOnCompaction(state);
5156
+ logger.info("Restarted after native compaction - reset stale transient state", {
5157
+ timestamp: state.lastCompaction,
5158
+ persistedBoundary
5159
+ });
5160
+ }
5174
5161
  if (typeof persisted.modelContextLimit === "number" && persisted.modelContextLimit > 0) {
5175
5162
  state.modelContextLimit = persisted.modelContextLimit;
5176
5163
  state.modelProviderID = persisted.modelProviderID;
@@ -7713,217 +7700,214 @@ function createCompressRangeTool(factoryCtx) {
7713
7700
  }
7714
7701
  }
7715
7702
  const callId = typeof toolCtx.callID === "string" ? toolCtx.callID : void 0;
7716
- const run = async () => {
7717
- const { rawMessages, searchContext } = await prepareSession(
7718
- ctx0,
7719
- toolCtx,
7720
- `Compress Range: ${input.topic ?? "(batch)"}`
7703
+ const { rawMessages, searchContext } = await prepareSession(
7704
+ ctx0,
7705
+ toolCtx,
7706
+ `Compress Range: ${input.topic ?? "(batch)"}`
7707
+ );
7708
+ const { providerId, modelId } = getModelInfo(rawMessages);
7709
+ const ctx = {
7710
+ ...ctx0,
7711
+ config: applyCompressOverrides(ctx0.config, providerId, modelId)
7712
+ };
7713
+ const { plans: filteredPlans } = prepareExecutableRangePlans(
7714
+ input,
7715
+ searchContext,
7716
+ ctx.state,
7717
+ ctx.config,
7718
+ ctx.logger
7719
+ );
7720
+ const notifications = [];
7721
+ let preparedPlans = [];
7722
+ let totalCompressedMessages = 0;
7723
+ for (const plan of filteredPlans) {
7724
+ const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
7725
+ validateSummaryPlaceholders(
7726
+ parsedPlaceholders,
7727
+ plan.selection.requiredBlockIds,
7728
+ plan.selection.startReference,
7729
+ plan.selection.endReference,
7730
+ searchContext.summaryByBlockId,
7731
+ ctx.logger
7721
7732
  );
7722
- const { providerId, modelId } = getModelInfo(rawMessages);
7723
- const ctx = {
7724
- ...ctx0,
7725
- config: applyCompressOverrides(ctx0.config, providerId, modelId)
7726
- };
7727
- const { plans: filteredPlans } = prepareExecutableRangePlans(
7728
- input,
7733
+ const injected = injectBlockPlaceholders(
7734
+ plan.entry.summary,
7735
+ parsedPlaceholders,
7736
+ searchContext.summaryByBlockId,
7737
+ plan.selection.startReference,
7738
+ plan.selection.endReference
7739
+ );
7740
+ const summaryWithUsers = appendProtectedUserMessages(
7741
+ injected.expandedSummary,
7742
+ plan.selection,
7729
7743
  searchContext,
7730
7744
  ctx.state,
7731
- ctx.config,
7732
- ctx.logger
7745
+ ctx.config.compress.protectUserMessages
7733
7746
  );
7734
- const notifications = [];
7735
- let preparedPlans = [];
7736
- let totalCompressedMessages = 0;
7737
- for (const plan of filteredPlans) {
7738
- const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
7739
- validateSummaryPlaceholders(
7740
- parsedPlaceholders,
7741
- plan.selection.requiredBlockIds,
7742
- plan.selection.startReference,
7743
- plan.selection.endReference,
7744
- searchContext.summaryByBlockId,
7745
- ctx.logger
7746
- );
7747
- const injected = injectBlockPlaceholders(
7748
- plan.entry.summary,
7749
- parsedPlaceholders,
7750
- searchContext.summaryByBlockId,
7751
- plan.selection.startReference,
7752
- plan.selection.endReference
7753
- );
7754
- const summaryWithUsers = appendProtectedUserMessages(
7755
- injected.expandedSummary,
7756
- plan.selection,
7757
- searchContext,
7758
- ctx.state,
7759
- ctx.config.compress.protectUserMessages
7760
- );
7761
- const summaryWithPromptInfo = appendProtectedPromptInfo(
7762
- summaryWithUsers,
7763
- plan.selection,
7764
- searchContext,
7765
- ctx.state,
7766
- ctx.config.compress.protectTags
7767
- );
7768
- const summaryWithTools = await appendProtectedTools(
7769
- ctx.client,
7770
- ctx.state,
7771
- summaryWithPromptInfo,
7772
- plan.selection,
7773
- searchContext,
7774
- ctx.config.compress.protectedTools,
7775
- ctx.config.protectedFilePatterns
7776
- );
7777
- const completedSummary = appendMissingBlockSummaries(
7778
- summaryWithTools,
7779
- [],
7780
- searchContext.summaryByBlockId,
7781
- injected.consumedBlockIds
7782
- );
7783
- const boundaryConsumed = extractBoundaryConsumedBlocks2(
7784
- plan.selection.startReference,
7785
- plan.selection.endReference
7786
- );
7787
- const seenConsumed = /* @__PURE__ */ new Set();
7788
- const mergeConsumedBlockIds = [
7789
- ...plan.selection.requiredBlockIds,
7790
- ...boundaryConsumed
7791
- ].filter((id) => {
7792
- if (seenConsumed.has(id)) return false;
7793
- seenConsumed.add(id);
7794
- return true;
7795
- });
7796
- preparedPlans.push({
7797
- entry: plan.entry,
7798
- selection: plan.selection,
7799
- anchorMessageId: plan.anchorMessageId,
7800
- finalSummary: completedSummary.expandedSummary,
7801
- consumedBlockIds: mergeConsumedBlockIds
7802
- });
7803
- }
7804
- const partition = partitionPhantomPlans(
7805
- identifyPhantomPlans(
7806
- ctx.state,
7807
- preparedPlans.map((p) => ({
7808
- messageIds: p.selection.messageIds,
7809
- consumedBlockIds: p.consumedBlockIds
7810
- }))
7811
- ),
7812
- preparedPlans.length
7747
+ const summaryWithPromptInfo = appendProtectedPromptInfo(
7748
+ summaryWithUsers,
7749
+ plan.selection,
7750
+ searchContext,
7751
+ ctx.state,
7752
+ ctx.config.compress.protectTags
7813
7753
  );
7814
- let phantomSkipNotice = null;
7815
- if (partition.kind === "all-phantom") {
7816
- ctx.logger.warn("Batch compress: ALL entries phantom", {
7817
- totalCount: preparedPlans.length,
7818
- details: partition.details
7819
- });
7820
- throw new Error(buildPhantomErrorMessage(partition.details));
7821
- }
7822
- if (partition.kind === "partial") {
7823
- ctx.logger.warn("Batch compress: phantom entries detected", {
7824
- phantomCount: partition.dropIndices.length,
7825
- totalCount: preparedPlans.length,
7826
- phantomIndices: partition.dropIndices,
7827
- details: partition.details
7828
- });
7829
- const dropSet = new Set(partition.dropIndices);
7830
- preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
7831
- phantomSkipNotice = partition.notice;
7832
- }
7833
- const acknowledgeRisk = args.acknowledgeRisk === true;
7834
- const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
7835
- const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
7836
- const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
7837
- if (ignoredAcknowledgeRisk) {
7838
- ctx.logger.warn(
7839
- "compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
7754
+ const summaryWithTools = await appendProtectedTools(
7755
+ ctx.client,
7756
+ ctx.state,
7757
+ summaryWithPromptInfo,
7758
+ plan.selection,
7759
+ searchContext,
7760
+ ctx.config.compress.protectedTools,
7761
+ ctx.config.protectedFilePatterns
7762
+ );
7763
+ const completedSummary = appendMissingBlockSummaries(
7764
+ summaryWithTools,
7765
+ [],
7766
+ searchContext.summaryByBlockId,
7767
+ injected.consumedBlockIds
7768
+ );
7769
+ const boundaryConsumed = extractBoundaryConsumedBlocks2(
7770
+ plan.selection.startReference,
7771
+ plan.selection.endReference
7772
+ );
7773
+ const seenConsumed = /* @__PURE__ */ new Set();
7774
+ const mergeConsumedBlockIds = [
7775
+ ...plan.selection.requiredBlockIds,
7776
+ ...boundaryConsumed
7777
+ ].filter((id) => {
7778
+ if (seenConsumed.has(id)) return false;
7779
+ seenConsumed.add(id);
7780
+ return true;
7781
+ });
7782
+ preparedPlans.push({
7783
+ entry: plan.entry,
7784
+ selection: plan.selection,
7785
+ anchorMessageId: plan.anchorMessageId,
7786
+ finalSummary: completedSummary.expandedSummary,
7787
+ consumedBlockIds: mergeConsumedBlockIds
7788
+ });
7789
+ }
7790
+ const partition = partitionPhantomPlans(
7791
+ identifyPhantomPlans(
7792
+ ctx.state,
7793
+ preparedPlans.map((p) => ({
7794
+ messageIds: p.selection.messageIds,
7795
+ consumedBlockIds: p.consumedBlockIds
7796
+ }))
7797
+ ),
7798
+ preparedPlans.length
7799
+ );
7800
+ let phantomSkipNotice = null;
7801
+ if (partition.kind === "all-phantom") {
7802
+ ctx.logger.warn("Batch compress: ALL entries phantom", {
7803
+ totalCount: preparedPlans.length,
7804
+ details: partition.details
7805
+ });
7806
+ throw new Error(buildPhantomErrorMessage(partition.details));
7807
+ }
7808
+ if (partition.kind === "partial") {
7809
+ ctx.logger.warn("Batch compress: phantom entries detected", {
7810
+ phantomCount: partition.dropIndices.length,
7811
+ totalCount: preparedPlans.length,
7812
+ phantomIndices: partition.dropIndices,
7813
+ details: partition.details
7814
+ });
7815
+ const dropSet = new Set(partition.dropIndices);
7816
+ preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
7817
+ phantomSkipNotice = partition.notice;
7818
+ }
7819
+ const acknowledgeRisk = args.acknowledgeRisk === true;
7820
+ const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
7821
+ const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
7822
+ const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
7823
+ if (ignoredAcknowledgeRisk) {
7824
+ ctx.logger.warn(
7825
+ "compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
7826
+ );
7827
+ }
7828
+ ctx.state.qualityGateRetryPending = false;
7829
+ if (!bypassQuality) {
7830
+ for (const plan of preparedPlans) {
7831
+ const result = evaluatePreCommitQuality(
7832
+ rawMessages,
7833
+ plan.selection.messageIds,
7834
+ plan.selection.messageTokenById,
7835
+ plan.finalSummary,
7836
+ ctx.config,
7837
+ ctx.logger
7840
7838
  );
7841
- }
7842
- ctx.state.qualityGateRetryPending = false;
7843
- if (!bypassQuality) {
7844
- for (const plan of preparedPlans) {
7845
- const result = evaluatePreCommitQuality(
7846
- rawMessages,
7847
- plan.selection.messageIds,
7848
- plan.selection.messageTokenById,
7849
- plan.finalSummary,
7850
- ctx.config,
7851
- ctx.logger
7852
- );
7853
- if (result && !result.passed) {
7854
- ctx.state.qualityGateRetryPending = true;
7855
- throw buildQualityRejectionError(
7856
- {
7857
- startId: plan.entry.startId,
7858
- endId: plan.entry.endId,
7859
- summary: plan.finalSummary,
7860
- messageIds: plan.selection.messageIds,
7861
- messageTokenById: plan.selection.messageTokenById
7862
- },
7863
- result
7864
- );
7865
- }
7866
- }
7867
- }
7868
- const snapshot = snapshotCompressionState(ctx.state);
7869
- const runId = allocateRunId(ctx.state);
7870
- try {
7871
- for (const preparedPlan of preparedPlans) {
7872
- const blockId = allocateBlockId(ctx.state);
7873
- const keepResult = resolveKeepMarkers(
7874
- preparedPlan.finalSummary,
7875
- rawMessages,
7876
- ctx.state,
7877
- ctx.config
7878
- );
7879
- preparedPlan.finalSummary = keepResult.summary;
7880
- const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
7881
- const summaryTokens = countTokens2(storedSummary);
7882
- const applied = applyCompressionState(
7883
- ctx.state,
7839
+ if (result && !result.passed) {
7840
+ ctx.state.qualityGateRetryPending = true;
7841
+ throw buildQualityRejectionError(
7884
7842
  {
7885
- topic: preparedPlan.entry.topic ?? input.topic ?? "",
7886
- batchTopic: input.topic,
7887
- startId: preparedPlan.entry.startId,
7888
- endId: preparedPlan.entry.endId,
7889
- mode: "range",
7890
- runId,
7891
- compressMessageId: toolCtx.messageID,
7892
- compressCallId: callId,
7893
- summaryTokens
7843
+ startId: plan.entry.startId,
7844
+ endId: plan.entry.endId,
7845
+ summary: plan.finalSummary,
7846
+ messageIds: plan.selection.messageIds,
7847
+ messageTokenById: plan.selection.messageTokenById
7894
7848
  },
7895
- preparedPlan.selection,
7896
- preparedPlan.anchorMessageId,
7897
- blockId,
7898
- storedSummary,
7899
- preparedPlan.consumedBlockIds,
7900
- ctx.config.gc
7849
+ result
7901
7850
  );
7902
- totalCompressedMessages += applied.messageIds.length;
7903
- notifications.push({
7904
- blockId,
7851
+ }
7852
+ }
7853
+ }
7854
+ const snapshot = snapshotCompressionState(ctx.state);
7855
+ const runId = allocateRunId(ctx.state);
7856
+ try {
7857
+ for (const preparedPlan of preparedPlans) {
7858
+ const blockId = allocateBlockId(ctx.state);
7859
+ const keepResult = resolveKeepMarkers(
7860
+ preparedPlan.finalSummary,
7861
+ rawMessages,
7862
+ ctx.state,
7863
+ ctx.config
7864
+ );
7865
+ preparedPlan.finalSummary = keepResult.summary;
7866
+ const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
7867
+ const summaryTokens = countTokens2(storedSummary);
7868
+ const applied = applyCompressionState(
7869
+ ctx.state,
7870
+ {
7871
+ topic: preparedPlan.entry.topic ?? input.topic ?? "",
7872
+ batchTopic: input.topic,
7873
+ startId: preparedPlan.entry.startId,
7874
+ endId: preparedPlan.entry.endId,
7875
+ mode: "range",
7905
7876
  runId,
7906
- summary: preparedPlan.finalSummary,
7877
+ compressMessageId: toolCtx.messageID,
7878
+ compressCallId: callId,
7907
7879
  summaryTokens
7908
- });
7909
- }
7910
- await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
7911
- } catch (error) {
7912
- restoreCompressionState(ctx.state, snapshot);
7913
- ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
7914
- throw error;
7880
+ },
7881
+ preparedPlan.selection,
7882
+ preparedPlan.anchorMessageId,
7883
+ blockId,
7884
+ storedSummary,
7885
+ preparedPlan.consumedBlockIds,
7886
+ ctx.config.gc
7887
+ );
7888
+ totalCompressedMessages += applied.messageIds.length;
7889
+ notifications.push({
7890
+ blockId,
7891
+ runId,
7892
+ summary: preparedPlan.finalSummary,
7893
+ summaryTokens
7894
+ });
7915
7895
  }
7916
- const skippedNote = phantomSkipNotice !== null ? `
7896
+ await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
7897
+ } catch (error) {
7898
+ restoreCompressionState(ctx.state, snapshot);
7899
+ ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
7900
+ throw error;
7901
+ }
7902
+ const skippedNote = phantomSkipNotice !== null ? `
7917
7903
  \u26A0\uFE0F ${phantomSkipNotice}
7918
7904
  ` : "";
7919
- const ackNote = ignoredAcknowledgeRisk ? `
7905
+ const ackNote = ignoredAcknowledgeRisk ? `
7920
7906
  \u26A0\uFE0F acknowledgeRisk was ignored: no quality gate rejection was pending, so quality checks ran normally. Only pass it when retrying immediately after a quality gate rejection.
7921
7907
  ` : "";
7922
- return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.${skippedNote}${ackNote}
7908
+ return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.${skippedNote}${ackNote}
7923
7909
  IMPORTANT: This was an automatic context compression. You MUST continue your previous task exactly where you left off. Do NOT ask the user what to do next.
7924
7910
  \u{1F4A1} Tip: Use search_context('keyword') to find compressed content when you need it later.`;
7925
- };
7926
- return factoryCtx.registry.withSessionGuard(toolCtx.sessionID, () => run());
7927
7911
  }
7928
7912
  });
7929
7913
  }
@@ -9804,112 +9788,109 @@ function createDecompressTool(factoryCtx) {
9804
9788
  args: buildSchema2(),
9805
9789
  async execute(args, toolCtx) {
9806
9790
  const ctx = resolveToolContext(factoryCtx, toolCtx.sessionID);
9807
- const run = async () => {
9808
- const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
9809
- const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
9810
- const contextUsageBefore = effectiveLimitBefore ? Math.round(
9811
- getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
9812
- ) : void 0;
9813
- const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
9814
- if (!resolved.ok) {
9815
- return resolved.error;
9816
- }
9817
- const targets = resolved.targets;
9818
- const messagesState = ctx.state.prune.messages;
9819
- const activeBlocks = [];
9820
- for (const target of targets) {
9821
- for (const block of target.blocks) {
9822
- if (block.active) {
9823
- activeBlocks.push(block);
9824
- }
9791
+ const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
9792
+ const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
9793
+ const contextUsageBefore = effectiveLimitBefore ? Math.round(
9794
+ getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
9795
+ ) : void 0;
9796
+ const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
9797
+ if (!resolved.ok) {
9798
+ return resolved.error;
9799
+ }
9800
+ const targets = resolved.targets;
9801
+ const messagesState = ctx.state.prune.messages;
9802
+ const activeBlocks = [];
9803
+ for (const target of targets) {
9804
+ for (const block of target.blocks) {
9805
+ if (block.active) {
9806
+ activeBlocks.push(block);
9825
9807
  }
9826
9808
  }
9827
- if (args.toFile) {
9828
- const targetPath = args.toFile;
9829
- const os = await import("os");
9830
- const path = await import("path");
9831
- const allowedDirs = [
9832
- os.tmpdir() + "/",
9833
- path.join(os.homedir(), ".cache", "opencode") + "/"
9834
- ];
9835
- const resolvedPath = path.resolve(targetPath);
9836
- const isAllowed = allowedDirs.some((dir) => {
9837
- const rel = path.relative(dir, resolvedPath);
9838
- return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
9839
- });
9840
- if (!isAllowed) {
9841
- return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
9842
- }
9843
- const msgIdSet = /* @__PURE__ */ new Set();
9844
- for (const block of activeBlocks) {
9845
- for (const id of block.effectiveMessageIds ?? []) {
9846
- msgIdSet.add(id);
9847
- }
9848
- }
9849
- const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
9850
- const lines2 = blockMessages.map(extractMessageText2);
9851
- const { writeFile: writeFile4 } = await import("fs/promises");
9852
- const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : targets[0]?.blocks[0]?.summary ?? "(no content available)";
9853
- await writeFile4(targetPath, fileContent, "utf-8");
9854
- const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
9855
- return `Block(s) ${displayIds2} content (${blockMessages.length} messages, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
9856
- }
9857
- const activeMessagesBefore = snapshotActiveMessages(messagesState);
9858
- const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
9859
- for (const target of targets) {
9860
- deactivateCompressionTarget(messagesState, target, { full: args.full === true });
9861
- }
9862
- syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
9863
- const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
9864
- messagesState,
9865
- activeMessagesBefore
9866
- );
9867
- const reactivatedBlockIds = computeReactivatedBlockIds(
9868
- messagesState,
9869
- activeBlockIdsBefore
9870
- );
9871
- ctx.state.stats.totalPruneTokens = Math.max(
9872
- 0,
9873
- ctx.state.stats.totalPruneTokens - restoredTokens
9874
- );
9875
- const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
9876
- const contextUsageAfter = effectiveLimitAfter ? Math.round(
9877
- getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
9878
- ) : void 0;
9879
- await finalizeDecompressSession(ctx);
9880
- const restoredContentPreview = buildRestoredContentPreview(
9881
- rawMessages,
9882
- activeMessagesBefore,
9883
- messagesState
9884
- );
9885
- const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
9886
- const lines = [];
9887
- const headerNoun = targets.length === 1 ? "block" : "blocks";
9888
- lines.push(
9889
- `Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
9890
- );
9891
- if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
9892
- lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
9893
- }
9894
- if (reactivatedBlockIds.length > 0) {
9895
- const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
9896
- lines.push(`Also restored nested block(s): ${refs}.`);
9809
+ }
9810
+ if (args.toFile) {
9811
+ const targetPath = args.toFile;
9812
+ const os = await import("os");
9813
+ const path = await import("path");
9814
+ const allowedDirs = [
9815
+ os.tmpdir() + "/",
9816
+ path.join(os.homedir(), ".cache", "opencode") + "/"
9817
+ ];
9818
+ const resolvedPath = path.resolve(targetPath);
9819
+ const isAllowed = allowedDirs.some((dir) => {
9820
+ const rel = path.relative(dir, resolvedPath);
9821
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
9822
+ });
9823
+ if (!isAllowed) {
9824
+ return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
9897
9825
  }
9898
- if (restoredContentPreview) {
9899
- lines.push("");
9900
- lines.push("RESTORED CONTENT (condensed):");
9901
- lines.push(restoredContentPreview);
9826
+ const msgIdSet = /* @__PURE__ */ new Set();
9827
+ for (const block of activeBlocks) {
9828
+ for (const id of block.effectiveMessageIds ?? []) {
9829
+ msgIdSet.add(id);
9830
+ }
9902
9831
  }
9903
- ctx.logger.info("Decompress tool completed", {
9904
- mode: typeof args.startId === "string" ? "range" : "block",
9905
- targetBlockIds: targets.map((t) => t.displayId),
9906
- restoredMessageCount,
9907
- restoredTokens,
9908
- reactivatedBlockIds
9909
- });
9910
- return lines.join("\n");
9911
- };
9912
- return factoryCtx.registry.withSessionGuard(toolCtx.sessionID, () => run());
9832
+ const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
9833
+ const lines2 = blockMessages.map(extractMessageText2);
9834
+ const { writeFile: writeFile4 } = await import("fs/promises");
9835
+ const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : targets[0]?.blocks[0]?.summary ?? "(no content available)";
9836
+ await writeFile4(targetPath, fileContent, "utf-8");
9837
+ const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
9838
+ return `Block(s) ${displayIds2} content (${blockMessages.length} messages, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
9839
+ }
9840
+ const activeMessagesBefore = snapshotActiveMessages(messagesState);
9841
+ const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
9842
+ for (const target of targets) {
9843
+ deactivateCompressionTarget(messagesState, target, { full: args.full === true });
9844
+ }
9845
+ syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
9846
+ const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
9847
+ messagesState,
9848
+ activeMessagesBefore
9849
+ );
9850
+ const reactivatedBlockIds = computeReactivatedBlockIds(
9851
+ messagesState,
9852
+ activeBlockIdsBefore
9853
+ );
9854
+ ctx.state.stats.totalPruneTokens = Math.max(
9855
+ 0,
9856
+ ctx.state.stats.totalPruneTokens - restoredTokens
9857
+ );
9858
+ const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
9859
+ const contextUsageAfter = effectiveLimitAfter ? Math.round(
9860
+ getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
9861
+ ) : void 0;
9862
+ await finalizeDecompressSession(ctx);
9863
+ const restoredContentPreview = buildRestoredContentPreview(
9864
+ rawMessages,
9865
+ activeMessagesBefore,
9866
+ messagesState
9867
+ );
9868
+ const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
9869
+ const lines = [];
9870
+ const headerNoun = targets.length === 1 ? "block" : "blocks";
9871
+ lines.push(
9872
+ `Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
9873
+ );
9874
+ if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
9875
+ lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
9876
+ }
9877
+ if (reactivatedBlockIds.length > 0) {
9878
+ const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
9879
+ lines.push(`Also restored nested block(s): ${refs}.`);
9880
+ }
9881
+ if (restoredContentPreview) {
9882
+ lines.push("");
9883
+ lines.push("RESTORED CONTENT (condensed):");
9884
+ lines.push(restoredContentPreview);
9885
+ }
9886
+ ctx.logger.info("Decompress tool completed", {
9887
+ mode: typeof args.startId === "string" ? "range" : "block",
9888
+ targetBlockIds: targets.map((t) => t.displayId),
9889
+ restoredMessageCount,
9890
+ restoredTokens,
9891
+ reactivatedBlockIds
9892
+ });
9893
+ return lines.join("\n");
9913
9894
  }
9914
9895
  });
9915
9896
  }
@@ -10595,7 +10576,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
10595
10576
  import { join as join4 } from "path";
10596
10577
  import { existsSync as existsSync4 } from "fs";
10597
10578
  import { homedir as homedir3 } from "os";
10598
- var LOG_VERSION = true ? "1.18.1-pr.408.164" : "dev";
10579
+ var LOG_VERSION = true ? "1.18.1-pr.409.166" : "dev";
10599
10580
  var LEVEL_RANK = {
10600
10581
  debug: 10,
10601
10582
  info: 20,
@@ -12650,24 +12631,22 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
12650
12631
  logger.info("Skipping DCP system prompt injection for internal agent");
12651
12632
  return;
12652
12633
  }
12653
- if (input.model?.limit?.context && input.sessionID) {
12654
- await registry4.withSessionGuard(input.sessionID, () => {
12655
- const limit = input.model.limit.context;
12656
- const providerID = input.model?.providerID;
12657
- const modelID = input.model?.id;
12658
- const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
12659
- state.modelContextLimit = limit;
12660
- if (providerID !== void 0) {
12661
- state.modelProviderID = providerID;
12662
- }
12663
- if (modelID !== void 0) {
12664
- state.modelID = modelID;
12665
- }
12666
- if (changed) {
12667
- saveSessionState(state, logger).catch(() => {
12668
- });
12669
- }
12670
- });
12634
+ if (input.model?.limit?.context) {
12635
+ const limit = input.model.limit.context;
12636
+ const providerID = input.model?.providerID;
12637
+ const modelID = input.model?.id;
12638
+ const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
12639
+ state.modelContextLimit = limit;
12640
+ if (providerID !== void 0) {
12641
+ state.modelProviderID = providerID;
12642
+ }
12643
+ if (modelID !== void 0) {
12644
+ state.modelID = modelID;
12645
+ }
12646
+ if (changed) {
12647
+ saveSessionState(state, logger).catch(() => {
12648
+ });
12649
+ }
12671
12650
  }
12672
12651
  const effectivePermission = compressPermission(state, config);
12673
12652
  if (effectivePermission === "deny") {
@@ -12702,131 +12681,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
12702
12681
  return;
12703
12682
  }
12704
12683
  const lastUserMessage = getLastUserMessage(messages);
12705
- const runPipeline = async (state) => {
12706
- syncCompressPermissionState(state, config, hostPermissions, output.messages);
12707
- if (state.isSubAgent && !config.allowSubAgents) {
12708
- return;
12709
- }
12710
- stripHallucinations(output.messages);
12711
- const dropReasoningModel = lastUserMessage?.info?.model;
12712
- const reasoningConfig = applyCompressOverrides(
12713
- config,
12714
- dropReasoningModel?.providerID ?? state.modelProviderID,
12715
- dropReasoningModel?.modelID ?? state.modelID
12716
- ).compress.reasoning;
12717
- if (reasoningConfig?.drop !== false) {
12718
- const droppedReasoning = dropCompressReasoning(
12719
- output.messages,
12720
- reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
12721
- );
12722
- if (droppedReasoning > 0) {
12723
- logger.debug("compress.reasoning: dropped oversized reasoning parts", {
12724
- dropped: droppedReasoning,
12725
- threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
12726
- });
12727
- }
12728
- }
12729
- ensureBuiltinFiltersRegistered();
12730
- const effectiveLimit = resolveEffectiveContextLimit(state, config);
12731
- applyMessageFilters(output.messages, config.messageFilters, logger, {
12732
- sessionId: state.sessionId ?? "",
12733
- isSubAgent: state.isSubAgent,
12734
- modelContextLimit: effectiveLimit?.limit
12735
- });
12736
- cacheSystemPromptTokens(state, output.messages);
12737
- assignMessageRefs(state, output.messages);
12738
- const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
12739
- const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
12740
- if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
12741
- saveSessionState(state, logger).catch(() => {
12742
- });
12743
- }
12744
- syncToolCache(state, config, logger, output.messages);
12745
- buildToolIdList(state, output.messages);
12746
- const batchResult = runBatchCleanup(state, config, logger, output.messages);
12747
- if (batchResult.mergedCount > 0) {
12748
- saveSessionState(state, logger).catch(() => {
12749
- });
12750
- }
12751
- const prePruneTokens = getCurrentTokenUsage(state, output.messages);
12752
- const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
12753
- prune(state, logger, config, output.messages);
12754
- hideConsumedCompressCalls(state, output.messages);
12755
- assignMessageRefs(state, output.messages);
12756
- const compressionPriorities = buildPriorityMap(config, state, output.messages);
12757
- prompts.reload();
12758
- injectCompressNudges(
12759
- state,
12760
- config,
12761
- logger,
12762
- output.messages,
12763
- prompts.getRuntimePrompts(),
12764
- compressionPriorities,
12765
- config.debug ? (text) => {
12766
- logger.debug(`[ACP Debug] Nudge injected:
12767
- ${text}`);
12768
- client.tui.showToast({
12769
- body: {
12770
- title: "ACP: Nudge Injected",
12771
- message: text.slice(0, 500),
12772
- variant: "info",
12773
- duration: 5e3
12774
- }
12775
- }).catch(() => {
12776
- });
12777
- } : void 0,
12778
- prePruneTokens,
12779
- candidateMessages
12780
- );
12781
- truncateLargeToolOutputs(
12782
- state,
12783
- config,
12784
- logger,
12785
- output.messages.filter((message) => !isSyntheticMessage(message))
12786
- );
12787
- enforceContextBudget(state, config, logger, output.messages);
12788
- injectMessageIds(state, config, output.messages, compressionPriorities);
12789
- hideFailedCompressCalls(output.messages);
12790
- stripStaleMetadata(output.messages);
12791
- dropEmptyMessages(output.messages);
12792
- const postTokens = getCurrentTokenUsage(state, output.messages);
12793
- if (postTokens !== void 0 && effectiveLimit) {
12794
- const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
12795
- if (postTokens > budget) {
12796
- logger.error(
12797
- "ACP hard guard: context exceeds model budget after in-flight reduction",
12798
- {
12799
- session: state.sessionId,
12800
- postTokens,
12801
- budget,
12802
- contextLimit: effectiveLimit.limit,
12803
- contextLimitSource: effectiveLimit.source,
12804
- hint: "request will likely be rejected; run /compact or start a new session"
12805
- }
12806
- );
12807
- }
12808
- }
12809
- logger.info("Chat transform complete", {
12810
- session: state.sessionId,
12811
- model: state.modelID,
12812
- messages: output.messages.length,
12813
- prePruneTokens,
12814
- postTokens,
12815
- contextLimit: effectiveLimit?.limit,
12816
- contextLimitSource: effectiveLimit?.source,
12817
- usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
12818
- nudged: state.nudges.shouldInjectThisTurn
12819
- });
12820
- if (state.sessionId) {
12821
- await logger.saveContext(state.sessionId, output.messages);
12822
- }
12823
- };
12684
+ let state;
12824
12685
  if (!lastUserMessage) {
12825
- await runPipeline(createSessionState());
12826
- return;
12827
- }
12828
- await registry4.withSessionGuard(lastUserMessage.info.sessionID, async () => {
12829
- const state = await registry4.getOrCreate(
12686
+ state = createSessionState();
12687
+ } else {
12688
+ state = await registry4.getOrCreate(
12830
12689
  client,
12831
12690
  lastUserMessage.info.sessionID,
12832
12691
  messages,
@@ -12883,8 +12742,124 @@ ${text}`);
12883
12742
  }
12884
12743
  );
12885
12744
  }
12886
- await runPipeline(state);
12745
+ }
12746
+ syncCompressPermissionState(state, config, hostPermissions, output.messages);
12747
+ if (state.isSubAgent && !config.allowSubAgents) {
12748
+ return;
12749
+ }
12750
+ stripHallucinations(output.messages);
12751
+ const dropReasoningModel = lastUserMessage?.info?.model;
12752
+ const reasoningConfig = applyCompressOverrides(
12753
+ config,
12754
+ dropReasoningModel?.providerID ?? state.modelProviderID,
12755
+ dropReasoningModel?.modelID ?? state.modelID
12756
+ ).compress.reasoning;
12757
+ if (reasoningConfig?.drop !== false) {
12758
+ const droppedReasoning = dropCompressReasoning(
12759
+ output.messages,
12760
+ reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
12761
+ );
12762
+ if (droppedReasoning > 0) {
12763
+ logger.debug("compress.reasoning: dropped oversized reasoning parts", {
12764
+ dropped: droppedReasoning,
12765
+ threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
12766
+ });
12767
+ }
12768
+ }
12769
+ ensureBuiltinFiltersRegistered();
12770
+ const effectiveLimit = resolveEffectiveContextLimit(state, config);
12771
+ applyMessageFilters(output.messages, config.messageFilters, logger, {
12772
+ sessionId: state.sessionId ?? "",
12773
+ isSubAgent: state.isSubAgent,
12774
+ modelContextLimit: effectiveLimit?.limit
12887
12775
  });
12776
+ cacheSystemPromptTokens(state, output.messages);
12777
+ assignMessageRefs(state, output.messages);
12778
+ const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
12779
+ const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
12780
+ if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
12781
+ saveSessionState(state, logger).catch(() => {
12782
+ });
12783
+ }
12784
+ syncToolCache(state, config, logger, output.messages);
12785
+ buildToolIdList(state, output.messages);
12786
+ const batchResult = runBatchCleanup(state, config, logger, output.messages);
12787
+ if (batchResult.mergedCount > 0) {
12788
+ saveSessionState(state, logger).catch(() => {
12789
+ });
12790
+ }
12791
+ const prePruneTokens = getCurrentTokenUsage(state, output.messages);
12792
+ const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
12793
+ prune(state, logger, config, output.messages);
12794
+ hideConsumedCompressCalls(state, output.messages);
12795
+ assignMessageRefs(state, output.messages);
12796
+ const compressionPriorities = buildPriorityMap(config, state, output.messages);
12797
+ prompts.reload();
12798
+ injectCompressNudges(
12799
+ state,
12800
+ config,
12801
+ logger,
12802
+ output.messages,
12803
+ prompts.getRuntimePrompts(),
12804
+ compressionPriorities,
12805
+ config.debug ? (text) => {
12806
+ logger.debug(`[ACP Debug] Nudge injected:
12807
+ ${text}`);
12808
+ client.tui.showToast({
12809
+ body: {
12810
+ title: "ACP: Nudge Injected",
12811
+ message: text.slice(0, 500),
12812
+ variant: "info",
12813
+ duration: 5e3
12814
+ }
12815
+ }).catch(() => {
12816
+ });
12817
+ } : void 0,
12818
+ prePruneTokens,
12819
+ candidateMessages
12820
+ );
12821
+ truncateLargeToolOutputs(
12822
+ state,
12823
+ config,
12824
+ logger,
12825
+ output.messages.filter((message) => !isSyntheticMessage(message))
12826
+ );
12827
+ enforceContextBudget(state, config, logger, output.messages);
12828
+ injectMessageIds(state, config, output.messages, compressionPriorities);
12829
+ hideFailedCompressCalls(output.messages);
12830
+ stripStaleMetadata(output.messages);
12831
+ dropEmptyMessages(output.messages);
12832
+ const postTokens = getCurrentTokenUsage(state, output.messages);
12833
+ if (postTokens !== void 0 && effectiveLimit) {
12834
+ const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
12835
+ if (postTokens > budget) {
12836
+ logger.error(
12837
+ "ACP hard guard: context exceeds model budget after in-flight reduction",
12838
+ {
12839
+ session: state.sessionId,
12840
+ postTokens,
12841
+ budget,
12842
+ contextLimit: effectiveLimit.limit,
12843
+ contextLimitSource: effectiveLimit.source,
12844
+ hint: "request will likely be rejected; run /compact or start a new session"
12845
+ }
12846
+ );
12847
+ }
12848
+ }
12849
+ logger.info("Chat transform complete", {
12850
+ session: state.sessionId,
12851
+ model: state.modelID,
12852
+ messages: output.messages.length,
12853
+ prePruneTokens,
12854
+ postTokens,
12855
+ contextLimit: effectiveLimit?.limit,
12856
+ contextLimitSource: effectiveLimit?.source,
12857
+ usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
12858
+ nudged: state.nudges.shouldInjectThisTurn
12859
+ });
12860
+ if (state.sessionId) {
12861
+ await logger.saveContext(state.sessionId, output.messages);
12862
+ }
12888
12863
  };
12889
12864
  }
12890
12865
  function buildHelpText() {
@@ -12991,21 +12966,16 @@ function createEventHandler(registry4, logger) {
12991
12966
  durationMs
12992
12967
  });
12993
12968
  for (const state of registry4.all()) {
12994
- if (!state.sessionId) {
12995
- continue;
12969
+ const updates = applyPendingCompressionDurations(state);
12970
+ if (updates > 0) {
12971
+ await saveSessionState(state, logger);
12972
+ logger.info("Attached compression time to blocks", {
12973
+ messageID: part.messageID,
12974
+ callID: part.callID,
12975
+ blocks: updates,
12976
+ durationMs
12977
+ });
12996
12978
  }
12997
- await registry4.withSessionGuard(state.sessionId, async () => {
12998
- const updates = applyPendingCompressionDurations(state);
12999
- if (updates > 0) {
13000
- await saveSessionState(state, logger);
13001
- logger.info("Attached compression time to blocks", {
13002
- messageID: part.messageID,
13003
- callID: part.callID,
13004
- blocks: updates,
13005
- durationMs
13006
- });
13007
- }
13008
- });
13009
12979
  }
13010
12980
  return;
13011
12981
  }
@@ -13290,7 +13260,7 @@ var server = (async (ctx) => {
13290
13260
  }
13291
13261
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13292
13262
  logger.info("ACP plugin initialized", {
13293
- version: true ? "1.18.1-pr.408.164" : "dev",
13263
+ version: true ? "1.18.1-pr.409.166" : "dev",
13294
13264
  workspace: ctx.directory,
13295
13265
  logLevel: logger.level,
13296
13266
  debug: config.debug,