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

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,7 @@ 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
+ const parent = await loadSessionState(parentSessionId, logger, state.storageDir);
5094
5070
  const response = parent ? await client.session.messages({ path: { id: parentSessionId } }) : void 0;
5095
5071
  const parentMessages = Array.isArray(response?.data) ? response.data : Array.isArray(response) ? response : [];
5096
5072
  if (parent && parentMessages.length > 0) {
@@ -5171,6 +5147,14 @@ async function runSessionInitialization(client, state, sessionId, logger, messag
5171
5147
  if (persistedAny._persistedLastCompaction !== void 0) {
5172
5148
  state.lastCompaction = Math.max(state.lastCompaction, persistedAny._persistedLastCompaction);
5173
5149
  }
5150
+ const persistedBoundary = typeof persistedAny._persistedLastCompaction === "number" ? persistedAny._persistedLastCompaction : 0;
5151
+ if (state.lastCompaction > persistedBoundary) {
5152
+ resetOnCompaction(state);
5153
+ logger.info("Restarted after native compaction - reset stale transient state", {
5154
+ timestamp: state.lastCompaction,
5155
+ persistedBoundary
5156
+ });
5157
+ }
5174
5158
  if (typeof persisted.modelContextLimit === "number" && persisted.modelContextLimit > 0) {
5175
5159
  state.modelContextLimit = persisted.modelContextLimit;
5176
5160
  state.modelProviderID = persisted.modelProviderID;
@@ -7713,217 +7697,214 @@ function createCompressRangeTool(factoryCtx) {
7713
7697
  }
7714
7698
  }
7715
7699
  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)"}`
7700
+ const { rawMessages, searchContext } = await prepareSession(
7701
+ ctx0,
7702
+ toolCtx,
7703
+ `Compress Range: ${input.topic ?? "(batch)"}`
7704
+ );
7705
+ const { providerId, modelId } = getModelInfo(rawMessages);
7706
+ const ctx = {
7707
+ ...ctx0,
7708
+ config: applyCompressOverrides(ctx0.config, providerId, modelId)
7709
+ };
7710
+ const { plans: filteredPlans } = prepareExecutableRangePlans(
7711
+ input,
7712
+ searchContext,
7713
+ ctx.state,
7714
+ ctx.config,
7715
+ ctx.logger
7716
+ );
7717
+ const notifications = [];
7718
+ let preparedPlans = [];
7719
+ let totalCompressedMessages = 0;
7720
+ for (const plan of filteredPlans) {
7721
+ const parsedPlaceholders = parseBlockPlaceholders(plan.entry.summary);
7722
+ validateSummaryPlaceholders(
7723
+ parsedPlaceholders,
7724
+ plan.selection.requiredBlockIds,
7725
+ plan.selection.startReference,
7726
+ plan.selection.endReference,
7727
+ searchContext.summaryByBlockId,
7728
+ ctx.logger
7721
7729
  );
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,
7730
+ const injected = injectBlockPlaceholders(
7731
+ plan.entry.summary,
7732
+ parsedPlaceholders,
7733
+ searchContext.summaryByBlockId,
7734
+ plan.selection.startReference,
7735
+ plan.selection.endReference
7736
+ );
7737
+ const summaryWithUsers = appendProtectedUserMessages(
7738
+ injected.expandedSummary,
7739
+ plan.selection,
7729
7740
  searchContext,
7730
7741
  ctx.state,
7731
- ctx.config,
7732
- ctx.logger
7742
+ ctx.config.compress.protectUserMessages
7733
7743
  );
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
7744
+ const summaryWithPromptInfo = appendProtectedPromptInfo(
7745
+ summaryWithUsers,
7746
+ plan.selection,
7747
+ searchContext,
7748
+ ctx.state,
7749
+ ctx.config.compress.protectTags
7813
7750
  );
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"
7751
+ const summaryWithTools = await appendProtectedTools(
7752
+ ctx.client,
7753
+ ctx.state,
7754
+ summaryWithPromptInfo,
7755
+ plan.selection,
7756
+ searchContext,
7757
+ ctx.config.compress.protectedTools,
7758
+ ctx.config.protectedFilePatterns
7759
+ );
7760
+ const completedSummary = appendMissingBlockSummaries(
7761
+ summaryWithTools,
7762
+ [],
7763
+ searchContext.summaryByBlockId,
7764
+ injected.consumedBlockIds
7765
+ );
7766
+ const boundaryConsumed = extractBoundaryConsumedBlocks2(
7767
+ plan.selection.startReference,
7768
+ plan.selection.endReference
7769
+ );
7770
+ const seenConsumed = /* @__PURE__ */ new Set();
7771
+ const mergeConsumedBlockIds = [
7772
+ ...plan.selection.requiredBlockIds,
7773
+ ...boundaryConsumed
7774
+ ].filter((id) => {
7775
+ if (seenConsumed.has(id)) return false;
7776
+ seenConsumed.add(id);
7777
+ return true;
7778
+ });
7779
+ preparedPlans.push({
7780
+ entry: plan.entry,
7781
+ selection: plan.selection,
7782
+ anchorMessageId: plan.anchorMessageId,
7783
+ finalSummary: completedSummary.expandedSummary,
7784
+ consumedBlockIds: mergeConsumedBlockIds
7785
+ });
7786
+ }
7787
+ const partition = partitionPhantomPlans(
7788
+ identifyPhantomPlans(
7789
+ ctx.state,
7790
+ preparedPlans.map((p) => ({
7791
+ messageIds: p.selection.messageIds,
7792
+ consumedBlockIds: p.consumedBlockIds
7793
+ }))
7794
+ ),
7795
+ preparedPlans.length
7796
+ );
7797
+ let phantomSkipNotice = null;
7798
+ if (partition.kind === "all-phantom") {
7799
+ ctx.logger.warn("Batch compress: ALL entries phantom", {
7800
+ totalCount: preparedPlans.length,
7801
+ details: partition.details
7802
+ });
7803
+ throw new Error(buildPhantomErrorMessage(partition.details));
7804
+ }
7805
+ if (partition.kind === "partial") {
7806
+ ctx.logger.warn("Batch compress: phantom entries detected", {
7807
+ phantomCount: partition.dropIndices.length,
7808
+ totalCount: preparedPlans.length,
7809
+ phantomIndices: partition.dropIndices,
7810
+ details: partition.details
7811
+ });
7812
+ const dropSet = new Set(partition.dropIndices);
7813
+ preparedPlans = preparedPlans.filter((_, i) => !dropSet.has(i));
7814
+ phantomSkipNotice = partition.notice;
7815
+ }
7816
+ const acknowledgeRisk = args.acknowledgeRisk === true;
7817
+ const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
7818
+ const bypassQuality = acknowledgeRisk && ctx.state.qualityGateRetryPending;
7819
+ const ignoredAcknowledgeRisk = acknowledgeRisk && !ctx.state.qualityGateRetryPending;
7820
+ if (ignoredAcknowledgeRisk) {
7821
+ ctx.logger.warn(
7822
+ "compress: acknowledgeRisk ignored \u2014 no quality gate rejection pending"
7823
+ );
7824
+ }
7825
+ ctx.state.qualityGateRetryPending = false;
7826
+ if (!bypassQuality) {
7827
+ for (const plan of preparedPlans) {
7828
+ const result = evaluatePreCommitQuality(
7829
+ rawMessages,
7830
+ plan.selection.messageIds,
7831
+ plan.selection.messageTokenById,
7832
+ plan.finalSummary,
7833
+ ctx.config,
7834
+ ctx.logger
7840
7835
  );
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,
7836
+ if (result && !result.passed) {
7837
+ ctx.state.qualityGateRetryPending = true;
7838
+ throw buildQualityRejectionError(
7884
7839
  {
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
7840
+ startId: plan.entry.startId,
7841
+ endId: plan.entry.endId,
7842
+ summary: plan.finalSummary,
7843
+ messageIds: plan.selection.messageIds,
7844
+ messageTokenById: plan.selection.messageTokenById
7894
7845
  },
7895
- preparedPlan.selection,
7896
- preparedPlan.anchorMessageId,
7897
- blockId,
7898
- storedSummary,
7899
- preparedPlan.consumedBlockIds,
7900
- ctx.config.gc
7846
+ result
7901
7847
  );
7902
- totalCompressedMessages += applied.messageIds.length;
7903
- notifications.push({
7904
- blockId,
7848
+ }
7849
+ }
7850
+ }
7851
+ const snapshot = snapshotCompressionState(ctx.state);
7852
+ const runId = allocateRunId(ctx.state);
7853
+ try {
7854
+ for (const preparedPlan of preparedPlans) {
7855
+ const blockId = allocateBlockId(ctx.state);
7856
+ const keepResult = resolveKeepMarkers(
7857
+ preparedPlan.finalSummary,
7858
+ rawMessages,
7859
+ ctx.state,
7860
+ ctx.config
7861
+ );
7862
+ preparedPlan.finalSummary = keepResult.summary;
7863
+ const storedSummary = wrapCompressedSummary(blockId, preparedPlan.finalSummary);
7864
+ const summaryTokens = countTokens2(storedSummary);
7865
+ const applied = applyCompressionState(
7866
+ ctx.state,
7867
+ {
7868
+ topic: preparedPlan.entry.topic ?? input.topic ?? "",
7869
+ batchTopic: input.topic,
7870
+ startId: preparedPlan.entry.startId,
7871
+ endId: preparedPlan.entry.endId,
7872
+ mode: "range",
7905
7873
  runId,
7906
- summary: preparedPlan.finalSummary,
7874
+ compressMessageId: toolCtx.messageID,
7875
+ compressCallId: callId,
7907
7876
  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;
7877
+ },
7878
+ preparedPlan.selection,
7879
+ preparedPlan.anchorMessageId,
7880
+ blockId,
7881
+ storedSummary,
7882
+ preparedPlan.consumedBlockIds,
7883
+ ctx.config.gc
7884
+ );
7885
+ totalCompressedMessages += applied.messageIds.length;
7886
+ notifications.push({
7887
+ blockId,
7888
+ runId,
7889
+ summary: preparedPlan.finalSummary,
7890
+ summaryTokens
7891
+ });
7915
7892
  }
7916
- const skippedNote = phantomSkipNotice !== null ? `
7893
+ await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
7894
+ } catch (error) {
7895
+ restoreCompressionState(ctx.state, snapshot);
7896
+ ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
7897
+ throw error;
7898
+ }
7899
+ const skippedNote = phantomSkipNotice !== null ? `
7917
7900
  \u26A0\uFE0F ${phantomSkipNotice}
7918
7901
  ` : "";
7919
- const ackNote = ignoredAcknowledgeRisk ? `
7902
+ const ackNote = ignoredAcknowledgeRisk ? `
7920
7903
  \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
7904
  ` : "";
7922
- return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.${skippedNote}${ackNote}
7905
+ return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.${skippedNote}${ackNote}
7923
7906
  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
7907
  \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
7908
  }
7928
7909
  });
7929
7910
  }
@@ -9804,112 +9785,109 @@ function createDecompressTool(factoryCtx) {
9804
9785
  args: buildSchema2(),
9805
9786
  async execute(args, toolCtx) {
9806
9787
  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
- }
9825
- }
9826
- }
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
- }
9788
+ const { rawMessages } = await prepareDecompressSession(ctx, toolCtx);
9789
+ const effectiveLimitBefore = resolveEffectiveContextLimit(ctx.state, ctx.config);
9790
+ const contextUsageBefore = effectiveLimitBefore ? Math.round(
9791
+ getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
9792
+ ) : void 0;
9793
+ const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
9794
+ if (!resolved.ok) {
9795
+ return resolved.error;
9796
+ }
9797
+ const targets = resolved.targets;
9798
+ const messagesState = ctx.state.prune.messages;
9799
+ const activeBlocks = [];
9800
+ for (const target of targets) {
9801
+ for (const block of target.blocks) {
9802
+ if (block.active) {
9803
+ activeBlocks.push(block);
9848
9804
  }
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
9805
  }
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}.`);
9806
+ }
9807
+ if (args.toFile) {
9808
+ const targetPath = args.toFile;
9809
+ const os = await import("os");
9810
+ const path = await import("path");
9811
+ const allowedDirs = [
9812
+ os.tmpdir() + "/",
9813
+ path.join(os.homedir(), ".cache", "opencode") + "/"
9814
+ ];
9815
+ const resolvedPath = path.resolve(targetPath);
9816
+ const isAllowed = allowedDirs.some((dir) => {
9817
+ const rel = path.relative(dir, resolvedPath);
9818
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
9819
+ });
9820
+ if (!isAllowed) {
9821
+ return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
9897
9822
  }
9898
- if (restoredContentPreview) {
9899
- lines.push("");
9900
- lines.push("RESTORED CONTENT (condensed):");
9901
- lines.push(restoredContentPreview);
9823
+ const msgIdSet = /* @__PURE__ */ new Set();
9824
+ for (const block of activeBlocks) {
9825
+ for (const id of block.effectiveMessageIds ?? []) {
9826
+ msgIdSet.add(id);
9827
+ }
9902
9828
  }
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());
9829
+ const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
9830
+ const lines2 = blockMessages.map(extractMessageText2);
9831
+ const { writeFile: writeFile4 } = await import("fs/promises");
9832
+ const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : targets[0]?.blocks[0]?.summary ?? "(no content available)";
9833
+ await writeFile4(targetPath, fileContent, "utf-8");
9834
+ const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
9835
+ 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.`;
9836
+ }
9837
+ const activeMessagesBefore = snapshotActiveMessages(messagesState);
9838
+ const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
9839
+ for (const target of targets) {
9840
+ deactivateCompressionTarget(messagesState, target, { full: args.full === true });
9841
+ }
9842
+ syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
9843
+ const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
9844
+ messagesState,
9845
+ activeMessagesBefore
9846
+ );
9847
+ const reactivatedBlockIds = computeReactivatedBlockIds(
9848
+ messagesState,
9849
+ activeBlockIdsBefore
9850
+ );
9851
+ ctx.state.stats.totalPruneTokens = Math.max(
9852
+ 0,
9853
+ ctx.state.stats.totalPruneTokens - restoredTokens
9854
+ );
9855
+ const effectiveLimitAfter = resolveEffectiveContextLimit(ctx.state, ctx.config);
9856
+ const contextUsageAfter = effectiveLimitAfter ? Math.round(
9857
+ getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitAfter.limit * 100
9858
+ ) : void 0;
9859
+ await finalizeDecompressSession(ctx);
9860
+ const restoredContentPreview = buildRestoredContentPreview(
9861
+ rawMessages,
9862
+ activeMessagesBefore,
9863
+ messagesState
9864
+ );
9865
+ const displayIds = targets.map((t) => `b${t.displayId}`).join(", ");
9866
+ const lines = [];
9867
+ const headerNoun = targets.length === 1 ? "block" : "blocks";
9868
+ lines.push(
9869
+ `Decompressed ${headerNoun} ${displayIds}. Restored ${restoredMessageCount} message(s) (~${formatTokenCount(restoredTokens)}).`
9870
+ );
9871
+ if (contextUsageBefore !== void 0 && contextUsageAfter !== void 0) {
9872
+ lines.push(`Context usage: ${contextUsageBefore}% \u2192 ${contextUsageAfter}%.`);
9873
+ }
9874
+ if (reactivatedBlockIds.length > 0) {
9875
+ const refs = reactivatedBlockIds.map((id) => `b${id}`).join(", ");
9876
+ lines.push(`Also restored nested block(s): ${refs}.`);
9877
+ }
9878
+ if (restoredContentPreview) {
9879
+ lines.push("");
9880
+ lines.push("RESTORED CONTENT (condensed):");
9881
+ lines.push(restoredContentPreview);
9882
+ }
9883
+ ctx.logger.info("Decompress tool completed", {
9884
+ mode: typeof args.startId === "string" ? "range" : "block",
9885
+ targetBlockIds: targets.map((t) => t.displayId),
9886
+ restoredMessageCount,
9887
+ restoredTokens,
9888
+ reactivatedBlockIds
9889
+ });
9890
+ return lines.join("\n");
9913
9891
  }
9914
9892
  });
9915
9893
  }
@@ -10595,7 +10573,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
10595
10573
  import { join as join4 } from "path";
10596
10574
  import { existsSync as existsSync4 } from "fs";
10597
10575
  import { homedir as homedir3 } from "os";
10598
- var LOG_VERSION = true ? "1.18.1-pr.408.164" : "dev";
10576
+ var LOG_VERSION = true ? "1.18.1-pr.409.165" : "dev";
10599
10577
  var LEVEL_RANK = {
10600
10578
  debug: 10,
10601
10579
  info: 20,
@@ -12650,24 +12628,22 @@ function createSystemPromptHandler(registry4, logger, config, prompts) {
12650
12628
  logger.info("Skipping DCP system prompt injection for internal agent");
12651
12629
  return;
12652
12630
  }
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
- });
12631
+ if (input.model?.limit?.context) {
12632
+ const limit = input.model.limit.context;
12633
+ const providerID = input.model?.providerID;
12634
+ const modelID = input.model?.id;
12635
+ const changed = state.modelContextLimit !== limit || providerID !== void 0 && state.modelProviderID !== providerID || modelID !== void 0 && state.modelID !== modelID;
12636
+ state.modelContextLimit = limit;
12637
+ if (providerID !== void 0) {
12638
+ state.modelProviderID = providerID;
12639
+ }
12640
+ if (modelID !== void 0) {
12641
+ state.modelID = modelID;
12642
+ }
12643
+ if (changed) {
12644
+ saveSessionState(state, logger).catch(() => {
12645
+ });
12646
+ }
12671
12647
  }
12672
12648
  const effectivePermission = compressPermission(state, config);
12673
12649
  if (effectivePermission === "deny") {
@@ -12702,131 +12678,11 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
12702
12678
  return;
12703
12679
  }
12704
12680
  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
- };
12681
+ let state;
12824
12682
  if (!lastUserMessage) {
12825
- await runPipeline(createSessionState());
12826
- return;
12827
- }
12828
- await registry4.withSessionGuard(lastUserMessage.info.sessionID, async () => {
12829
- const state = await registry4.getOrCreate(
12683
+ state = createSessionState();
12684
+ } else {
12685
+ state = await registry4.getOrCreate(
12830
12686
  client,
12831
12687
  lastUserMessage.info.sessionID,
12832
12688
  messages,
@@ -12883,8 +12739,124 @@ ${text}`);
12883
12739
  }
12884
12740
  );
12885
12741
  }
12886
- await runPipeline(state);
12742
+ }
12743
+ syncCompressPermissionState(state, config, hostPermissions, output.messages);
12744
+ if (state.isSubAgent && !config.allowSubAgents) {
12745
+ return;
12746
+ }
12747
+ stripHallucinations(output.messages);
12748
+ const dropReasoningModel = lastUserMessage?.info?.model;
12749
+ const reasoningConfig = applyCompressOverrides(
12750
+ config,
12751
+ dropReasoningModel?.providerID ?? state.modelProviderID,
12752
+ dropReasoningModel?.modelID ?? state.modelID
12753
+ ).compress.reasoning;
12754
+ if (reasoningConfig?.drop !== false) {
12755
+ const droppedReasoning = dropCompressReasoning(
12756
+ output.messages,
12757
+ reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
12758
+ );
12759
+ if (droppedReasoning > 0) {
12760
+ logger.debug("compress.reasoning: dropped oversized reasoning parts", {
12761
+ dropped: droppedReasoning,
12762
+ threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
12763
+ });
12764
+ }
12765
+ }
12766
+ ensureBuiltinFiltersRegistered();
12767
+ const effectiveLimit = resolveEffectiveContextLimit(state, config);
12768
+ applyMessageFilters(output.messages, config.messageFilters, logger, {
12769
+ sessionId: state.sessionId ?? "",
12770
+ isSubAgent: state.isSubAgent,
12771
+ modelContextLimit: effectiveLimit?.limit
12887
12772
  });
12773
+ cacheSystemPromptTokens(state, output.messages);
12774
+ assignMessageRefs(state, output.messages);
12775
+ const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
12776
+ const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
12777
+ if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
12778
+ saveSessionState(state, logger).catch(() => {
12779
+ });
12780
+ }
12781
+ syncToolCache(state, config, logger, output.messages);
12782
+ buildToolIdList(state, output.messages);
12783
+ const batchResult = runBatchCleanup(state, config, logger, output.messages);
12784
+ if (batchResult.mergedCount > 0) {
12785
+ saveSessionState(state, logger).catch(() => {
12786
+ });
12787
+ }
12788
+ const prePruneTokens = getCurrentTokenUsage(state, output.messages);
12789
+ const candidateMessages = config.compress.candidates === true ? output.messages.slice() : void 0;
12790
+ prune(state, logger, config, output.messages);
12791
+ hideConsumedCompressCalls(state, output.messages);
12792
+ assignMessageRefs(state, output.messages);
12793
+ const compressionPriorities = buildPriorityMap(config, state, output.messages);
12794
+ prompts.reload();
12795
+ injectCompressNudges(
12796
+ state,
12797
+ config,
12798
+ logger,
12799
+ output.messages,
12800
+ prompts.getRuntimePrompts(),
12801
+ compressionPriorities,
12802
+ config.debug ? (text) => {
12803
+ logger.debug(`[ACP Debug] Nudge injected:
12804
+ ${text}`);
12805
+ client.tui.showToast({
12806
+ body: {
12807
+ title: "ACP: Nudge Injected",
12808
+ message: text.slice(0, 500),
12809
+ variant: "info",
12810
+ duration: 5e3
12811
+ }
12812
+ }).catch(() => {
12813
+ });
12814
+ } : void 0,
12815
+ prePruneTokens,
12816
+ candidateMessages
12817
+ );
12818
+ truncateLargeToolOutputs(
12819
+ state,
12820
+ config,
12821
+ logger,
12822
+ output.messages.filter((message) => !isSyntheticMessage(message))
12823
+ );
12824
+ enforceContextBudget(state, config, logger, output.messages);
12825
+ injectMessageIds(state, config, output.messages, compressionPriorities);
12826
+ hideFailedCompressCalls(output.messages);
12827
+ stripStaleMetadata(output.messages);
12828
+ dropEmptyMessages(output.messages);
12829
+ const postTokens = getCurrentTokenUsage(state, output.messages);
12830
+ if (postTokens !== void 0 && effectiveLimit) {
12831
+ const budget = effectiveLimit.limit - (state.systemPromptTokens ?? 0) - OUTPUT_RESERVE_TOKENS;
12832
+ if (postTokens > budget) {
12833
+ logger.error(
12834
+ "ACP hard guard: context exceeds model budget after in-flight reduction",
12835
+ {
12836
+ session: state.sessionId,
12837
+ postTokens,
12838
+ budget,
12839
+ contextLimit: effectiveLimit.limit,
12840
+ contextLimitSource: effectiveLimit.source,
12841
+ hint: "request will likely be rejected; run /compact or start a new session"
12842
+ }
12843
+ );
12844
+ }
12845
+ }
12846
+ logger.info("Chat transform complete", {
12847
+ session: state.sessionId,
12848
+ model: state.modelID,
12849
+ messages: output.messages.length,
12850
+ prePruneTokens,
12851
+ postTokens,
12852
+ contextLimit: effectiveLimit?.limit,
12853
+ contextLimitSource: effectiveLimit?.source,
12854
+ usagePct: postTokens !== void 0 && effectiveLimit ? `${(postTokens / effectiveLimit.limit * 100).toFixed(1)}%` : void 0,
12855
+ nudged: state.nudges.shouldInjectThisTurn
12856
+ });
12857
+ if (state.sessionId) {
12858
+ await logger.saveContext(state.sessionId, output.messages);
12859
+ }
12888
12860
  };
12889
12861
  }
12890
12862
  function buildHelpText() {
@@ -12991,21 +12963,16 @@ function createEventHandler(registry4, logger) {
12991
12963
  durationMs
12992
12964
  });
12993
12965
  for (const state of registry4.all()) {
12994
- if (!state.sessionId) {
12995
- continue;
12966
+ const updates = applyPendingCompressionDurations(state);
12967
+ if (updates > 0) {
12968
+ await saveSessionState(state, logger);
12969
+ logger.info("Attached compression time to blocks", {
12970
+ messageID: part.messageID,
12971
+ callID: part.callID,
12972
+ blocks: updates,
12973
+ durationMs
12974
+ });
12996
12975
  }
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
12976
  }
13010
12977
  return;
13011
12978
  }
@@ -13290,7 +13257,7 @@ var server = (async (ctx) => {
13290
13257
  }
13291
13258
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13292
13259
  logger.info("ACP plugin initialized", {
13293
- version: true ? "1.18.1-pr.408.164" : "dev",
13260
+ version: true ? "1.18.1-pr.409.165" : "dev",
13294
13261
  workspace: ctx.directory,
13295
13262
  logLevel: logger.level,
13296
13263
  debug: config.debug,