Skip to content

fix(core): deduplicate geminiChat recovery continuation text#3966

Merged
chiga0 merged 11 commits into
mainfrom
fix/geminichat-stream-recovery-dedup
May 21, 2026
Merged

fix(core): deduplicate geminiChat recovery continuation text#3966
chiga0 merged 11 commits into
mainfrom
fix/geminichat-stream-recovery-dedup

Conversation

@chiga0

@chiga0 chiga0 commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

When a provider hits MAX_TOKENS mid-response, the recovery loop injects a user turn and the model resumes. Some providers start the continuation stream by re-sending a few characters (or an entire table/section) from the end of the previous response as a context anchor.

Without deduplication this replayed prefix gets appended verbatim to history, causing repeated Markdown tables and prose in all subsequent turns — even if the live UI suppressed it visually.

Fix

Two new helpers in geminiChat.ts:

  • getRecoveryContinuationSuffix — strips a suffix-overlap between the end of the previous model turn and the start of the continuation (the common case: provider re-sends the last sentence as a context bridge).
  • findContainedRecoveryPrefixReplayLength — handles the less common case where the provider re-sends a chunk that appeared somewhere in the tail of the previous response (e.g. a full Markdown table header), not necessarily the exact last bytes.

appendRecoveryContinuationParts calls both and splices the deduped suffix into precedingModel.parts instead of naively concatenating.

The recovery prompt also now includes the last 1 200 chars of the previous response (buildOutputRecoveryMessage) so the model can see its stopping point.

Tests

Two new cases in geminiChat.test.ts:

  • should coalesce overlapping recovery continuation text — exact shared suffix
  • should coalesce recovery text that replays a previous tail anchor — Markdown table prefix replayed as anchor mid-text

All 61 tests pass.

Scope

Pure packages/core change. No CLI/rendering changes. Independent of the ink 7 upgrade and virtual viewport work.

🤖 Generated with Qwen Code

When a provider hits MAX_TOKENS and the model resumes via the recovery
loop, the continuation stream sometimes re-sends characters from the end
of the previous response as a context anchor. Without deduplication this
causes repeated Markdown tables/prose in the final history even if the
live UI suppresses them.

Add getRecoveryContinuationSuffix / findContainedRecoveryPrefixReplayLength
to strip the replayed prefix before appending the continuation parts.
Also include the last 1200 chars of the previous response in the recovery
prompt so the model can see where it left off.

Two new tests cover:
- exact suffix overlap (shared recovery suffix and continuation)
- contained tail anchor replay (Markdown table prefix replayed mid-text)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 75.69% 75.69% 76.76% 80.53%
Core 78.61% 78.61% 81.47% 82.88%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   75.69 |    80.53 |   76.76 |   75.69 |                   
 src               |   75.73 |    69.15 |   80.55 |   75.73 |                   
  gemini.tsx       |   68.53 |     66.4 |   76.47 |   68.53 | ...29,946-949,957 
  ...ractiveCli.ts |      80 |    68.61 |   78.57 |      80 | ...1020,1058,1161 
  ...liCommands.ts |   74.51 |     72.5 |     100 |   74.51 | ...41-265,290,391 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   57.09 |    71.81 |      60 |   57.09 |                   
  acpAgent.ts      |   59.58 |    72.22 |   66.66 |   59.58 | ...18-920,934-942 
  authMethods.ts   |   12.19 |      100 |       0 |   12.19 | 11-31,34-38,41-50 
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   68.65 |    83.33 |   66.66 |   68.65 |                   
  filesystem.ts    |   68.65 |    83.33 |   66.66 |   68.65 | ...32,77-94,97-98 
 ...ration/session |   76.01 |    70.59 |      84 |   76.01 |                   
  ...ryReplayer.ts |   65.93 |    75.67 |   81.81 |   65.93 | ...40-255,268-269 
  Session.ts       |   75.11 |    68.89 |    85.1 |   75.11 | ...2455,2461-2464 
  ...entTracker.ts |   90.85 |    84.84 |      90 |   90.85 | ...35,199,251-260 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   84.21 |    77.77 |     100 |   84.21 | ...37-153,209-211 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ssion/emitters |   96.01 |    90.75 |    92.3 |   96.01 |                   
  BaseEmitter.ts   |   76.92 |    66.66 |      80 |   76.92 | 23-24,39-40,55-56 
  ...ageEmitter.ts |     100 |    89.47 |     100 |     100 | 109,111           
  PlanEmitter.ts   |     100 |      100 |     100 |     100 |                   
  ...allEmitter.ts |   98.06 |     92.3 |     100 |   98.06 | 227-228,327,335   
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
 ...ession/rewrite |   90.36 |    87.83 |   94.11 |   90.36 |                   
  LlmRewriter.ts   |      81 |       84 |     100 |      81 | ...,88-89,155-159 
  ...Middleware.ts |   95.83 |    85.71 |     100 |   95.83 | 119,127-129       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/auth          |    97.7 |    94.81 |   95.45 |    97.7 |                   
  allProviders.ts  |     100 |      100 |     100 |     100 |                   
  ...iderConfig.ts |    97.6 |    95.04 |     100 |    97.6 | ...61,411,433-434 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/auth/install  |   98.57 |    88.88 |     100 |   98.57 |                   
  ...nstallPlan.ts |   98.57 |    88.88 |     100 |   98.57 | 80,93             
 ...viders/alibaba |   96.96 |    66.66 |   66.66 |   96.96 |                   
  ...baStandard.ts |     100 |      100 |     100 |     100 |                   
  codingPlan.ts    |   93.67 |    66.66 |   66.66 |   93.67 | 83,87-89,94       
  tokenPlan.ts     |     100 |      100 |     100 |     100 |                   
 ...oviders/custom |     100 |      100 |     100 |     100 |                   
  ...omProvider.ts |     100 |      100 |     100 |     100 |                   
 ...roviders/oauth |    91.5 |    77.03 |   97.05 |    91.5 |                   
  openrouter.ts    |   84.37 |    33.33 |     100 |   84.37 | 43-48             
  ...outerOAuth.ts |    91.9 |    79.06 |   96.87 |    91.9 | ...53-655,699-701 
 ...ers/thirdParty |     100 |      100 |     100 |     100 |                   
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/commands      |   58.43 |    85.71 |   43.47 |   58.43 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   56.66 |      100 |       0 |   56.66 | 15-19,27-34       
  extensions.tsx   |   96.55 |      100 |      50 |   96.55 | 37                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   94.73 |      100 |      50 |   94.73 | 28                
  review.ts        |   51.85 |      100 |       0 |   51.85 | 24-35,38          
  serve.ts         |   10.97 |      100 |       0 |   10.97 | ...5,45-95,97-134 
 ...mmands/channel |   39.25 |    79.45 |      50 |   39.25 |                   
  ...l-registry.ts |    8.57 |      100 |       0 |    8.57 | 6-21,24-42        
  config-utils.ts  |      92 |      100 |   66.66 |      92 | 21-26             
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  pairing.ts       |   26.31 |      100 |       0 |   26.31 | ...30,40-50,52-65 
  pidfile.ts       |   96.34 |    86.95 |     100 |   96.34 | 49,59,91          
  start.ts         |   30.98 |       52 |   69.23 |   30.98 | ...72-475,484-486 
  status.ts        |   17.85 |      100 |       0 |   17.85 | 15-26,32-76       
  stop.ts          |      20 |      100 |       0 |      20 | 14-48             
 ...nds/extensions |    84.5 |    88.95 |   81.81 |    84.5 |                   
  consent.ts       |   71.65 |    89.28 |   42.85 |   71.65 | ...85-141,156-162 
  disable.ts       |     100 |      100 |     100 |     100 |                   
  enable.ts        |     100 |      100 |     100 |     100 |                   
  install.ts       |    75.6 |    66.66 |   66.66 |    75.6 | ...39-142,145-153 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |      100 |     100 |     100 |                   
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  uninstall.ts     |    37.5 |      100 |   33.33 |    37.5 | 23-45,57-64,67-70 
  update.ts        |   96.32 |      100 |     100 |   96.32 | 101-105           
  utils.ts         |   60.24 |    28.57 |     100 |   60.24 | ...81,83-87,89-93 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 src/commands/mcp  |   92.29 |    86.08 |   88.88 |   92.29 |                   
  add.ts           |     100 |    98.03 |     100 |     100 | 293               
  list.ts          |   91.22 |    80.76 |      80 |   91.22 | ...19-121,146-147 
  reconnect.ts     |   76.72 |    71.42 |   85.71 |   76.72 | 35-48,153-175     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   11.57 |      100 |       0 |   11.57 |                   
  cleanup.ts       |   17.94 |      100 |       0 |   17.94 | ...01-106,108-109 
  deterministic.ts |   13.75 |      100 |       0 |   13.75 | ...22-738,740-741 
  fetch-pr.ts      |   11.36 |      100 |       0 |   11.36 | ...80-201,203-204 
  load-rules.ts    |   11.32 |      100 |       0 |   11.32 | ...41-153,155-156 
  pr-context.ts    |    6.22 |      100 |       0 |    6.22 | ...97-312,314-315 
  presubmit.ts     |    9.35 |      100 |       0 |    9.35 | ...62-287,289-290 
 ...nds/review/lib |      30 |      100 |       0 |      30 |                   
  gh.ts            |   22.58 |      100 |       0 |   22.58 | ...49,53-54,62-69 
  git.ts           |   22.72 |      100 |       0 |   22.72 | 15-18,29-39,43-44 
  paths.ts         |   52.94 |      100 |       0 |   52.94 | ...26,37-38,42-43 
 src/config        |   92.71 |    85.23 |    86.9 |   92.71 |                   
  auth.ts          |   86.98 |    80.32 |     100 |   86.98 | ...26-227,243-244 
  config.ts        |   88.32 |    85.38 |      80 |   88.32 | ...1739,1763-1764 
  keyBindings.ts   |   96.15 |       50 |     100 |   96.15 | 177-180           
  ...idersScope.ts |      92 |       90 |     100 |      92 | 11-12             
  sandboxConfig.ts |    58.9 |    61.53 |   66.66 |    58.9 | ...54-68,73,77-89 
  settings.ts      |   85.51 |    87.19 |   86.48 |   85.51 | ...1148,1153-1156 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...tedFolders.ts |   96.22 |       94 |     100 |   96.22 | ...88-190,205-206 
 ...nfig/migration |   94.89 |    78.94 |   83.33 |   94.89 |                   
  index.ts         |   94.87 |    88.88 |     100 |   94.87 | 91-92             
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.74 |       96 |     100 |   94.74 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |    90.19 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   63.09 |    64.51 |   55.55 |   63.09 |                   
  ...tputBridge.ts |   62.94 |    65.51 |   56.25 |   62.94 | ...22-323,331-334 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/i18n          |   81.47 |    75.94 |   65.71 |   81.47 |                   
  index.ts         |   63.68 |    69.56 |   53.84 |   63.68 | ...70-271,281-286 
  languages.ts     |   96.92 |    86.66 |     100 |   96.92 | 134-135,167,184   
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   72.57 |    71.12 |   74.07 |   72.57 |                   
  session.ts       |   76.64 |     69.4 |   85.71 |   76.64 | ...23-824,833-843 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...80-581,584-585 
 ...active/control |   77.04 |    88.23 |      80 |   77.04 |                   
  ...rolContext.ts |    7.14 |        0 |       0 |    7.14 | 49-84             
  ...Dispatcher.ts |   91.66 |    91.83 |   88.88 |   91.66 | ...54-372,388,391 
  ...rolService.ts |       8 |        0 |       0 |       8 | 46-179            
 ...ol/controllers |    7.04 |       80 |   13.33 |    7.04 |                   
  ...Controller.ts |   19.32 |      100 |      60 |   19.32 | 81-118,127-210    
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |    3.96 |      100 |   11.11 |    3.96 | ...61-379,389-494 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |    5.21 |      100 |       0 |    5.21 | ...21-433,442-471 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   97.98 |    93.72 |   95.18 |   97.98 |                   
  ...putAdapter.ts |   97.89 |    92.82 |   98.07 |   97.89 | ...1303,1398-1399 
  ...putAdapter.ts |      96 |    91.66 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.28 |      100 |      90 |   98.28 | 81-82,122-123     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   86.98 |       75 |   85.71 |   86.98 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.12 |    76.08 |   91.66 |   88.12 | ...21-222,233-236 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/serve         |   81.36 |    80.43 |   89.36 |   81.36 |                   
  auth.ts          |   85.86 |    83.87 |      80 |   85.86 | ...47-148,151-153 
  eventBus.ts      |   87.07 |    85.71 |      85 |   87.07 | ...46-354,415-417 
  httpAcpBridge.ts |   78.13 |    77.03 |   95.34 |   78.13 | ...2670,2701-2742 
  ...oryChannel.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  loopbackBinds.ts |     100 |      100 |     100 |     100 |                   
  runQwenServe.ts  |   79.01 |     87.5 |   83.33 |   79.01 | ...24-440,465-467 
  server.ts        |   84.07 |    80.85 |   82.35 |   84.07 | ...-894,1023-1032 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |   91.56 |    91.16 |   97.56 |   91.56 |                   
  ...mandLoader.ts |     100 |     92.3 |     100 |     100 | 91                
  ...killLoader.ts |     100 |    96.15 |     100 |     100 | 45                
  ...andService.ts |    98.7 |      100 |     100 |    98.7 | 107               
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.84 |    80.64 |   83.33 |   75.84 | ...10-211,277-278 
  ...mandLoader.ts |     100 |      100 |     100 |     100 |                   
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.21 |    96.66 |     100 |   98.21 | 83,87             
  commandUtils.ts  |      96 |    91.66 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |    85.9 |    85.61 |   90.47 |    85.9 |                   
  DataProcessor.ts |   85.63 |     85.6 |   92.85 |   85.63 | ...1122,1126-1133 
  ...tGenerator.ts |   98.21 |    85.71 |     100 |   98.21 | 46                
  ...teRenderer.ts |   45.45 |      100 |       0 |   45.45 | 13-51             
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.35 |    83.07 |     100 |   97.35 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.45 |       70 |     100 |   92.45 | ...22,144,151,160 
  tipRegistry.ts   |     100 |    95.23 |     100 |     100 | 33                
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/test-utils    |   93.75 |    83.33 |      80 |   93.75 |                   
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   64.89 |    69.64 |   48.93 |   64.89 |                   
  App.tsx          |     100 |      100 |     100 |     100 |                   
  AppContainer.tsx |    67.5 |    65.05 |   52.94 |    67.5 | ...2789,2793-2797 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |   29.23 |      100 |       0 |   29.23 | 25-75             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |   52.72 |      100 |   23.52 |   52.72 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   95.91 |    96.42 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   48.01 |    58.73 |   21.42 |   48.01 |                   
  AuthDialog.tsx   |   64.26 |    44.44 |   16.66 |   64.26 | ...59,366-388,392 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |    9.61 |      100 |       0 |    9.61 | ...35-352,391-476 
  useAuth.ts       |   76.63 |    68.29 |     100 |   76.63 | ...48,493-499,560 
  ...rSetupFlow.ts |   44.61 |    33.33 |      50 |   44.61 | ...57-378,395-438 
 src/ui/commands   |   69.73 |    79.31 |   79.01 |   69.73 |                   
  aboutCommand.ts  |     100 |    85.71 |     100 |     100 | 36                
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |     100 |      100 |     100 |     100 |                   
  arenaCommand.ts  |   62.81 |    58.73 |   65.21 |   62.81 | ...91-596,681-689 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   95.59 |    71.42 |     100 |   95.59 | 72,154-159        
  bugCommand.ts    |   81.13 |    71.42 |     100 |   81.13 | 60-69             
  clearCommand.ts  |      92 |    76.47 |     100 |      92 | 43-44,72-73,91-92 
  ...essCommand.ts |    64.7 |       50 |      75 |    64.7 | ...48-149,163-166 
  ...extCommand.ts |   34.78 |    22.22 |   45.45 |   34.78 | ...86-521,532-533 
  copyCommand.ts   |   98.28 |    94.89 |     100 |   98.28 | ...80,280,321,327 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |   99.02 |    86.11 |     100 |   99.02 | 222,226           
  ...ryCommand.tsx |   68.09 |    77.77 |   77.77 |   68.09 | ...56-261,315-323 
  docsCommand.ts   |     100 |    88.88 |     100 |     100 | 25                
  doctorCommand.ts |     100 |    93.33 |     100 |     100 | 21                
  dreamCommand.ts  |      75 |    66.66 |   66.66 |      75 | 22-27,44-47       
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |      60 |    92.85 |   77.77 |      60 | 176-317           
  ...onsCommand.ts |   48.66 |     90.9 |   63.63 |   48.66 | ...05-109,159-211 
  forgetCommand.ts |   26.82 |      100 |      50 |   26.82 | 18-51             
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |    20.4 |       40 |      40 |    20.4 | ...48-180,204-205 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |   74.56 |    68.42 |     100 |   74.56 | ...31-245,250-273 
  ...ageCommand.ts |   92.17 |    82.69 |     100 |   92.17 | ...43,164,173-183 
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   75.09 |    78.18 |      75 |   75.09 | ...20-225,262-267 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...berCommand.ts |   32.43 |      100 |      50 |   32.43 | 23-57             
  renameCommand.ts |    85.4 |    76.36 |     100 |    85.4 | ...08-315,322-327 
  ...oreCommand.ts |    92.3 |    87.87 |     100 |    92.3 | ...,83-88,129-130 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |      80 |      100 |      50 |      80 | 19-21             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   81.43 |    65.21 |      80 |   81.43 | ...70-173,176-179 
  skillsCommand.ts |   15.04 |      100 |      25 |   15.04 | ...90-106,109-136 
  statsCommand.ts  |   88.19 |    84.21 |     100 |   88.19 | ...,58-61,143-146 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.46 |      100 |      50 |    6.46 | 31-329            
  tasksCommand.ts  |   77.45 |    73.43 |     100 |   77.45 | ...55-159,181-186 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
 src/ui/components |   61.41 |    75.52 |   66.66 |   61.41 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |   89.39 |       75 |     100 |   89.39 | 35,37-42,44       
  ...odeDialog.tsx |     9.7 |      100 |       0 |     9.7 | 35-47,50-182      
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   14.63 |      100 |       0 |   14.63 | 18-56             
  ...TextInput.tsx |   77.01 |       76 |     100 |   77.01 | ...20,234-236,263 
  Composer.tsx     |    80.8 |     64.7 |     100 |    80.8 | ...85,103,154,167 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  ...ification.tsx |   28.57 |      100 |       0 |   28.57 | 16-36             
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |    12.4 |      100 |       0 |    12.4 | 63-474            
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   79.67 |    58.06 |     100 |   79.67 | ...98-102,104-108 
  ...ngSpinner.tsx |   68.42 |       80 |      50 |   68.42 | 35-52,73,80-81    
  Header.tsx       |   98.62 |    94.28 |     100 |   98.62 | 162,164           
  Help.tsx         |   98.32 |    89.88 |     100 |   98.32 | ...24,381,447-448 
  ...emDisplay.tsx |   63.27 |    36.73 |     100 |   63.27 | ...29-338,341,344 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   82.25 |    77.43 |   83.33 |   82.25 | ...1347,1412,1462 
  ...Shortcuts.tsx |   20.87 |      100 |       0 |   20.87 | ...6,49-51,67-125 
  ...Indicator.tsx |     100 |    91.42 |     100 |     100 | 65,74             
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   81.75 |       75 |     100 |   81.75 | ...70-274,282-286 
  ...elsDialog.tsx |   16.07 |    89.18 |      50 |   16.07 | ...58-159,162-648 
  MemoryDialog.tsx |   53.21 |    51.21 |   57.14 |   53.21 | ...54,366,379-381 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   80.12 |    63.55 |     100 |   80.12 | ...39-555,612-616 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |   18.18 |      100 |       0 |   18.18 | 15-58             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   88.14 |    83.87 |     100 |   88.14 | ...01-105,133-138 
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |    8.57 |      100 |       0 |    8.57 | 24-55,58-134      
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |    4.45 |      100 |       0 |    4.45 | 28-92,100-328     
  ...ionPicker.tsx |   78.43 |    66.66 |     100 |   78.43 | ...20-422,444-466 
  ...onPreview.tsx |   92.42 |    84.37 |     100 |   92.42 | ...,70-71,143-145 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...ngsDialog.tsx |   66.88 |    73.52 |     100 |   66.88 | ...11-819,825-826 
  ...ionDialog.tsx |    87.8 |      100 |   33.33 |    87.8 | 36-39,44-51       
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...ionPicker.tsx |   17.59 |      100 |       0 |   17.59 | 55-172            
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...yTodoList.tsx |   94.17 |       80 |     100 |   94.17 | 56-57,131-134     
  ...nsDisplay.tsx |   87.25 |       64 |     100 |   87.25 | ...45-147,154-156 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    81.81 |     100 |     100 | 71-86             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
 ...nts/agent-view |    25.2 |       90 |      10 |    25.2 |                   
  ...atContent.tsx |    8.79 |      100 |       0 |    8.79 | 53-265,271-273    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |    9.95 |      100 |       0 |    9.95 | 57-308            
  AgentFooter.tsx  |   17.07 |      100 |       0 |   17.07 | 28-66             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    8.13 |      100 |       0 |    8.13 | 39-59,64-187      
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.72 |    70.53 |   60.86 |   45.72 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |   10.15 |      100 |       0 |   10.15 | 27-161            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |   75.44 |     83.7 |   85.29 |   75.44 |                   
  ...sksDialog.tsx |   70.05 |     79.2 |   76.19 |   70.05 | ...1119,1195-1197 
  ...TasksPill.tsx |   70.83 |    86.95 |     100 |   70.83 | 44,84-96,104-112  
  ...gentPanel.tsx |   99.52 |    93.18 |     100 |   99.52 | 123               
 ...nts/extensions |   45.28 |    33.33 |      60 |   45.28 |                   
  ...gerDialog.tsx |   44.31 |    34.14 |      75 |   44.31 | ...71-480,483-488 
  index.ts         |       0 |        0 |       0 |       0 | 1-9               
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   54.77 |    94.23 |   66.66 |   54.77 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |    6.18 |      100 |       0 |    6.18 | 17-128            
  ...nListStep.tsx |   88.35 |    94.73 |      80 |   88.35 | 51-52,58-71,105   
  ...electStep.tsx |   13.46 |      100 |       0 |   13.46 | 20-70             
  ...nfirmStep.tsx |   19.56 |      100 |       0 |   19.56 | 23-65             
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...mponents/hooks |   68.64 |    69.07 |   69.56 |   68.64 |                   
  ...etailStep.tsx |   74.68 |    66.66 |   66.66 |   74.68 | ...71-184,188-201 
  ...etailStep.tsx |    87.4 |    73.68 |     100 |    87.4 | 41-42,99-113,119  
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   34.35 |    47.05 |   42.85 |   34.35 | ...77,481-494,498 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   20.83 |    83.72 |   83.33 |   20.83 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |    3.64 |      100 |       0 |    3.64 | 41-717            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-30              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   94.79 |    85.71 |     100 |   94.79 | 16,20,35,109-110  
 ...ents/mcp/steps |    6.88 |      100 |       0 |    6.88 |                   
  ...icateStep.tsx |    5.88 |      100 |       0 |    5.88 | 40-55,58-296      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |    5.26 |      100 |       0 |    5.26 | 31-247            
  ...rListStep.tsx |    5.88 |      100 |       0 |    5.88 | 20-176            
  ...etailStep.tsx |   10.41 |      100 |       0 |   10.41 | ...1,67-79,82-139 
  ToolListStep.tsx |    7.14 |      100 |       0 |    7.14 | 16-146            
 ...nents/messages |   82.15 |    80.23 |   72.85 |   82.15 |                   
  ...ionDialog.tsx |   77.35 |    74.54 |    62.5 |   77.35 | ...90,508,526-528 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |   97.67 |    83.72 |     100 |   97.67 | 119,142,150       
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   79.06 |      100 |      70 |   79.06 | ...51-264,268-280 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...tsDisplay.tsx |   97.82 |    77.27 |     100 |   97.82 | 87,89             
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   16.66 |      100 |       0 |   16.66 | 22-38             
  ...sMessages.tsx |   55.67 |       40 |   28.57 |   55.67 | ...20-125,133-145 
  ...ryMessage.tsx |   14.28 |      100 |       0 |   14.28 | 23-62             
  ...onMessage.tsx |   81.02 |    69.23 |   33.33 |   81.02 | ...24-426,433-435 
  ...upMessage.tsx |      84 |    93.61 |     100 |      84 | ...56-383,405-420 
  ToolMessage.tsx  |   88.84 |    75.71 |    92.3 |   88.84 | ...44-749,776-778 
 ...ponents/shared |   82.37 |    77.36 |   92.75 |   82.37 |                   
  ...ctionList.tsx |   99.03 |    95.65 |     100 |   99.03 | 85                
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  MaxSizedBox.tsx  |   83.01 |    86.25 |   88.88 |   83.01 | ...12-513,618-619 
  MultiSelect.tsx  |    6.29 |      100 |       0 |    6.29 | 35-42,45-176      
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  TextInput.tsx    |   72.98 |    55.55 |      80 |   72.98 | ...08-212,224-230 
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  text-buffer.ts   |   83.62 |    75.62 |   97.61 |   83.62 | ...2272,2300,2368 
  ...er-actions.ts |   86.71 |    67.79 |     100 |   86.71 | ...07-608,809-811 
 ...ents/subagents |   30.87 |        0 |       0 |   30.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   10.95 |      100 |       0 |   10.95 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |    8.39 |      100 |       0 |    8.39 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |    2.29 |      100 |       0 |    2.29 | 28-449            
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...mponents/views |   42.16 |    69.23 |   21.42 |   42.16 |                   
  ContextUsage.tsx |     4.7 |      100 |       0 |     4.7 | ...52-167,170-456 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   87.69 |    73.68 |     100 |   87.69 | 65-72             
  McpStatus.tsx    |   89.53 |    60.52 |     100 |   89.53 | ...72,175-177,262 
  SkillsList.tsx   |   27.27 |      100 |       0 |   27.27 | 18-35             
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   77.05 |    78.24 |   82.14 |   77.05 |                   
  ...ewContext.tsx |   65.77 |      100 |      75 |   65.77 | ...22-225,231-241 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   93.37 |    68.57 |      50 |   93.37 | ...94-195,222-226 
  ...deContext.tsx |     100 |      100 |     100 |     100 |                   
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   81.88 |    82.26 |     100 |   81.88 | ...1153,1159-1161 
  ...owContext.tsx |   89.28 |       80 |   66.66 |   89.28 | 34,47-48,60-62    
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   43.28 |     62.5 |    62.5 |   43.28 | ...56-259,263-266 
  ...gsContext.tsx |   83.33 |       50 |     100 |   83.33 | 17-18             
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...nsContext.tsx |   88.23 |       50 |     100 |   88.23 | 109-110           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 173-174           
  ...deContext.tsx |   76.08 |    72.72 |     100 |   76.08 | 47-48,52-59,77-78 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |   81.92 |    82.18 |   86.53 |   81.92 |                   
  ...dProcessor.ts |   83.12 |    82.56 |     100 |   83.12 | ...88-389,408-435 
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...dProcessor.ts |    94.8 |    70.58 |     100 |    94.8 | ...76-277,282-283 
  ...dProcessor.ts |   75.72 |    63.19 |   61.53 |   75.72 | ...74,898,917-921 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...ationFrame.ts |      32 |       60 |     100 |      32 | 42-44,51-90       
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   19.81 |    66.66 |      25 |   19.81 | 57-175            
  ...Completion.ts |   92.77 |    89.09 |     100 |   92.77 | ...86-187,220-223 
  ...ifications.ts |   92.07 |    96.29 |     100 |   92.07 | 116-124           
  ...tIndicator.ts |     100 |    93.75 |     100 |     100 | 63                
  ...waySummary.ts |   96.22 |    69.69 |     100 |   96.22 | 125-127,169       
  ...ndTaskView.ts |   94.11 |    76.92 |     100 |   94.11 | 119-123,216,222   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   94.73 |    75.67 |     100 |   94.73 | ...48,159,167-168 
  ...ompletion.tsx |   95.95 |    82.75 |     100 |   95.95 | ...22-223,225-226 
  ...dMigration.ts |   90.62 |       75 |     100 |   90.62 | 38-40             
  useCompletion.ts |    92.4 |     87.5 |     100 |    92.4 | 68-69,93-94,98-99 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   76.92 |       50 |     100 |   76.92 | 55,68,71-75,88-96 
  ...eteCommand.ts |   78.53 |    88.57 |     100 |   78.53 | ...96-104,112-113 
  ...ialogClose.ts |   16.66 |      100 |     100 |   16.66 | 79-139            
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |     97.7 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.45 |     92.3 |     100 |   93.45 | ...83-287,300-306 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |   54.47 |       50 |   33.33 |   54.47 | ...69-171,193-194 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   89.15 |     62.5 |      50 |   89.15 | ...22-124,149-150 
  ...miniStream.ts |   76.64 |    73.93 |   91.66 |   76.64 | ...2425,2438-2446 
  ...BranchName.ts |    90.9 |     92.3 |     100 |    90.9 | 19-20,55-58       
  ...oryManager.ts |   93.15 |    93.75 |     100 |   93.15 | 44,107-110        
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |    9.67 |      100 |       0 |    9.67 | 11-32,39-90       
  ...gIndicator.ts |     100 |      100 |     100 |     100 |                   
  useLogger.ts     |   21.05 |      100 |       0 |   21.05 | 15-37             
  useMCPHealth.ts  |   63.15 |       75 |      50 |   63.15 | 42-52,64-67       
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...delCommand.ts |     100 |       75 |     100 |     100 | 22                
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...derUpdates.ts |   86.38 |    77.19 |     100 |   86.38 | ...22,281-293,341 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |    84.7 |    93.33 |     100 |    84.7 | ...71-276,372-382 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   97.95 |    86.36 |     100 |   97.95 | 102-103           
  ...ompletion.tsx |   90.59 |    83.33 |     100 |   90.59 | ...01,104,137-140 
  ...ectionList.ts |   96.96 |    95.69 |     100 |   96.96 | ...82-183,237-240 
  ...sionPicker.ts |   85.67 |    81.37 |     100 |   85.67 | ...25-527,536-538 
  ...earchInput.ts |     100 |      100 |     100 |     100 |                   
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   91.74 |    79.41 |     100 |   91.74 | ...74,122-123,133 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-73              
  ...Completion.ts |   82.67 |    85.41 |   94.73 |   82.67 | ...68-670,678-714 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |     100 |    98.79 |     100 |     100 | 257               
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...tification.ts |     100 |    85.71 |     100 |     100 | 47                
  ...alProgress.ts |   53.06 |       50 |   66.66 |   53.06 | ...53,61-68,79-85 
  ...rminalSize.ts |   76.19 |      100 |      50 |   76.19 | 21-25             
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   88.09 |    85.71 |     100 |   88.09 | 44-45,51-53       
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |      100 |     100 |     100 |                   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  vim.ts           |   83.77 |    80.31 |     100 |   83.77 | ...55,759-767,776 
 src/ui/layouts    |   89.72 |     87.5 |     100 |   89.72 |                   
  ...AppLayout.tsx |   89.88 |     87.5 |     100 |   89.88 | 51-53,93-98       
  ...AppLayout.tsx |   89.47 |     87.5 |     100 |   89.47 | 58-63             
 ...i/manageModels |   93.61 |       48 |     100 |   93.61 |                   
  manageModels.ts  |   93.61 |       48 |     100 |   93.61 | ...63-166,179,209 
 src/ui/models     |   80.24 |    79.16 |   71.42 |   80.24 |                   
  ...ableModels.ts |   80.24 |    79.16 |   71.42 |   80.24 | ...,61-71,123-125 
 ...noninteractive |     100 |      100 |    7.14 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    7.14 |     100 |                   
 src/ui/state      |   94.91 |    81.81 |     100 |   94.91 |                   
  extensions.ts    |   94.91 |    81.81 |     100 |   94.91 | 68-69,88          
 src/ui/themes     |   98.53 |    70.58 |     100 |   98.53 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |     100 |      100 |     100 |     100 |                   
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   87.98 |    82.89 |     100 |   87.98 | ...48-357,362-363 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   83.72 |    82.68 |   92.41 |   83.72 |                   
  ...Colorizer.tsx |   79.53 |    83.78 |     100 |   79.53 | ...51-152,249-275 
  ...nRenderer.tsx |   68.83 |    70.14 |      50 |   68.83 | ...52-254,274-293 
  ...wnDisplay.tsx |   86.01 |    87.41 |     100 |   86.01 | ...87,704,729-754 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   92.08 |    80.45 |      95 |   92.08 | ...76-679,723-728 
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   59.61 |    58.82 |     100 |   59.61 | ...,86-88,107-149 
  commandUtils.ts  |    95.9 |    88.29 |     100 |    95.9 | ...62,164-165,289 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   88.37 |    72.22 |     100 |   88.37 | 23,25,29,31,33    
  formatters.ts    |   95.23 |    98.27 |     100 |   95.23 | 117-120           
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |    94.28 |     100 |     100 | 29,51             
  historyUtils.ts  |   94.02 |    93.87 |     100 |   94.02 | 93-96             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |    89.47 |     100 |     100 | 81,110            
  ...nUtilities.ts |   69.84 |    85.71 |     100 |   69.84 | 75-91,100-101     
  ...ToolGroups.ts |   98.66 |    96.77 |     100 |   98.66 | 48-49             
  ...geRenderer.ts |   86.23 |    69.06 |   95.12 |   86.23 | ...1284,1324-1330 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  osc8.ts          |   94.71 |    87.41 |     100 |   94.71 | ...43,428,432-433 
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   61.06 |    69.62 |      90 |   61.06 | ...64,412,417-439 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.35 |    94.38 |   91.66 |   97.35 | ...50-251,386-387 
  todoSnapshot.ts  |   89.11 |    93.33 |     100 |   89.11 | ...,66-78,180-181 
  updateCheck.ts   |     100 |    80.95 |     100 |     100 | 30-42             
 ...i/utils/export |   56.77 |     40.8 |   79.41 |   56.77 |                   
  collect.ts       |   55.92 |    50.58 |   86.36 |   55.92 | ...25-640,642-647 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   57.47 |    20.51 |      80 |   57.47 | ...09-310,324-359 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |      40 |      100 |       0 |      40 | 11-13             
 ...ort/formatters |    3.38 |      100 |       0 |    3.38 |                   
  html.ts          |    9.61 |      100 |       0 |    9.61 | ...28,34-76,82-84 
  json.ts          |      50 |      100 |       0 |      50 | 14-15             
  jsonl.ts         |     3.5 |      100 |       0 |     3.5 | 14-76             
  markdown.ts      |    0.94 |      100 |       0 |    0.94 | 13-295            
 src/utils         |   73.97 |    90.18 |   93.89 |   73.97 |                   
  acpModelUtils.ts |     100 |      100 |     100 |     100 |                   
  apiPreconnect.ts |   96.72 |    97.14 |     100 |   96.72 | 165-168           
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  cleanup.ts       |   84.12 |    93.33 |      80 |   84.12 | 75,106-115        
  commands.ts      |     100 |      100 |     100 |     100 |                   
  commentJson.ts   |   87.17 |    90.47 |     100 |   87.17 | 64-73             
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   71.06 |       75 |     100 |   71.06 | ...95-301,325-341 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   94.28 |       88 |     100 |   94.28 | 28-29,125-126     
  errors.ts        |   98.67 |    96.36 |     100 |   98.67 | 67-68             
  events.ts        |     100 |      100 |     100 |     100 |                   
  gitUtils.ts      |   91.91 |    84.61 |     100 |   91.91 | 78-81,124-127     
  ...AutoUpdate.ts |   90.76 |    93.33 |   88.88 |   90.76 | 103-114           
  ...lationInfo.ts |     100 |      100 |     100 |     100 |                   
  languageUtils.ts |   97.89 |    96.42 |     100 |   97.89 | 132-133           
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...onfigUtils.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   96.79 |    93.28 |     100 |   96.79 | ...76-477,575,588 
  osc.ts           |    97.5 |      100 |   88.88 |    97.5 | 195-196           
  package.ts       |   88.88 |       80 |     100 |   88.88 | 33-34             
  processUtils.ts  |     100 |      100 |     100 |     100 |                   
  readStdin.ts     |   79.62 |       90 |      80 |   79.62 | 33-40,52-54       
  relaunch.ts      |   98.07 |    76.92 |     100 |   98.07 | 70                
  resolvePath.ts   |   66.66 |       25 |     100 |   66.66 | 12-13,16,18-19    
  sandbox.ts       |       0 |        0 |       0 |       0 | 1-1047            
  settingsUtils.ts |   82.89 |    90.75 |   89.47 |   82.89 | ...52-663,670-678 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...upProfiler.ts |   98.46 |    94.52 |     100 |   98.46 | 130-131,305       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   92.52 |     90.9 |   83.33 |   92.52 | 63-69,184         
  ...InfoFields.ts |    87.5 |     64.1 |     100 |    87.5 | ...21-122,143-144 
  ...iffPreview.ts |   94.11 |    83.33 |     100 |   94.11 | 13                
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   91.17 |    82.35 |     100 |   91.17 | 67-68,73-74,77-78 
  version.ts       |     100 |       50 |     100 |     100 | 11                
  windowTitle.ts   |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |   63.15 |    81.25 |     100 |   63.15 | 93,118-157        
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   78.61 |    82.88 |   81.47 |   78.61 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   86.11 |    76.88 |   91.66 |   86.11 |                   
  ...transcript.ts |   88.92 |    76.66 |     100 |   88.92 | ...82,306-307,438 
  ...ent-resume.ts |   81.23 |    69.89 |   77.41 |   81.23 | ...1021,1024-1026 
  ...ound-tasks.ts |   95.13 |    86.61 |     100 |   95.13 | ...06-707,733-734 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/arena  |   76.54 |    66.87 |   78.72 |   76.54 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.37 |    63.37 |   78.26 |   75.37 | ...1860,1866-1867 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   76.29 |    86.15 |   73.04 |   76.29 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.25 |    90.62 |   86.66 |   91.25 | ...94,249-269,328 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   81.14 |     76.7 |   71.42 |   81.14 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   76.49 |    72.35 |   60.86 |   76.49 | ...1608,1635-1682 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   81.19 |    71.73 |   60.86 |   81.19 | ...98-399,402-403 
  ...nteractive.ts |   79.71 |    79.62 |      75 |   79.71 | ...54,456,458,461 
  ...statistics.ts |   98.19 |    82.35 |     100 |   98.19 | 127,151,192,225   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/config        |   77.17 |    79.31 |   63.67 |   77.17 |                   
  config.ts        |   75.25 |    77.44 |   58.93 |   75.25 | ...3392,3403-3415 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   95.07 |    93.44 |   89.47 |   95.07 | ...66-267,270-271 
 ...nfirmation-bus |   98.29 |    97.14 |     100 |   98.29 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   85.63 |    83.38 |   90.62 |   85.63 |                   
  baseLlmClient.ts |   92.67 |    80.48 |   85.71 |   92.67 | ...99,307-321,460 
  client.ts        |   81.53 |    80.83 |    87.5 |   81.53 | ...1699,1738-1741 
  ...tGenerator.ts |    72.1 |    61.11 |     100 |    72.1 | ...63,365,372-375 
  ...lScheduler.ts |   81.12 |    82.25 |   93.47 |   81.12 | ...2334,2386-2390 
  geminiChat.ts    |   90.33 |    85.66 |   92.85 |   90.33 | ...1808,1875-1876 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | 34-42,45-49,52-87 
  logger.ts        |   87.33 |    87.02 |     100 |   87.33 | ...61-565,611-625 
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   92.59 |       75 |      50 |   92.59 | 41-42             
  ...on-helpers.ts |   85.71 |    70.58 |     100 |   85.71 | ...90-191,205-214 
  ...issionFlow.ts |   98.59 |    94.73 |     100 |   98.59 | 93                
  prompts.ts       |   89.16 |    86.41 |   76.92 |   89.16 | ...-965,1168-1169 
  tokenLimits.ts   |     100 |    89.47 |     100 |     100 | 51-52             
  ...okTriggers.ts |   99.31 |    90.41 |     100 |   99.31 | 124,135           
  turn.ts          |   96.42 |    88.88 |     100 |   96.42 | ...00,413-414,462 
 ...ntentGenerator |   94.92 |    82.59 |   93.87 |   94.92 |                   
  ...tGenerator.ts |   96.48 |    84.28 |   92.59 |   96.48 | ...01,919-923,963 
  converter.ts     |   94.51 |    80.72 |     100 |   94.51 | ...06-607,617,823 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   91.53 |    71.64 |   93.33 |   91.53 |                   
  ...tGenerator.ts |      90 |    70.96 |   92.85 |      90 | ...80-286,304-305 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   92.11 |     82.7 |   90.32 |   92.11 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   92.09 |     82.7 |   90.32 |   92.09 | ...33,843-844,872 
 ...ntentGenerator |   81.66 |    84.08 |    90.9 |   81.66 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   76.88 |    82.25 |    87.5 |   76.88 | ...1589,1610-1616 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   52.38 |    44.44 |      50 |   52.38 | ...77,81-85,89-93 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   93.67 |     84.9 |     100 |   93.67 | ...80-481,489,554 
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   90.66 |    88.57 |     100 |   90.66 | ...15-319,349-350 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   96.62 |    88.82 |   95.45 |   96.62 |                   
  dashscope.ts     |   97.14 |    89.02 |   93.33 |   97.14 | ...53-254,330-331 
  deepseek.ts      |   95.55 |    90.56 |     100 |   95.55 | ...31-132,145-146 
  default.ts       |   94.62 |    86.36 |   85.71 |   94.62 | 86-87,157-159     
  index.ts         |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
 src/extension     |   60.56 |    79.46 |    78.4 |   60.56 |                   
  ...-converter.ts |   62.35 |    47.82 |      90 |   62.35 | ...90-791,800-832 
  ...ionManager.ts |   47.04 |    82.06 |    65.9 |   47.04 | ...1398,1408-1427 
  ...onSettings.ts |   93.46 |    93.05 |     100 |   93.46 | ...17-221,228-232 
  ...-converter.ts |   54.88 |    94.44 |      60 |   54.88 | ...35-146,158-192 
  github.ts        |   44.94 |    88.52 |      60 |   44.94 | ...53-359,398-451 
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   97.29 |    93.75 |     100 |   97.29 | ...64,184-185,274 
  npm.ts           |   48.66 |    76.08 |      75 |   48.66 | ...18-420,427-431 
  override.ts      |   94.11 |    88.88 |     100 |   94.11 | 63-64,81-82       
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-108,143-149    
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   46.91 |     92.3 |   71.87 |   46.91 |                   
  followupState.ts |      96 |    89.74 |     100 |      96 | 159-161,218-219   
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   95.06 |       84 |     100 |   95.06 | 78,108,122,133    
  speculation.ts   |   13.22 |      100 |   16.66 |   13.22 | 88-458,518-568    
  ...onToolGate.ts |     100 |    96.29 |     100 |     100 | 93                
  ...nGenerator.ts |    38.4 |    95.12 |   33.33 |    38.4 | ...16-318,353-383 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/hooks         |   82.63 |    84.23 |   85.29 |   82.63 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |    96.4 |    90.78 |     100 |    96.4 | ...91,293-294,367 
  ...entHandler.ts |   94.56 |    83.78 |   93.33 |   94.56 | ...38,795-796,806 
  hookPlanner.ts   |   84.13 |    76.59 |      90 |   84.13 | ...38,144,162-173 
  hookRegistry.ts  |   90.17 |    83.33 |     100 |   90.17 | ...33,352,356,360 
  hookRunner.ts    |   58.56 |    71.26 |   66.66 |   58.56 | ...48-749,758-759 
  hookSystem.ts    |   77.55 |      100 |   58.53 |   77.55 | ...21-622,628-629 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   93.63 |    89.47 |      90 |   93.63 | ...45-353,427-428 
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |    96.5 |     91.8 |     100 |    96.5 | ...90,209-210,223 
  ssrfGuard.ts     |   77.22 |    85.36 |     100 |   77.22 | ...57,261-267,273 
  trustedHooks.ts  |       0 |        0 |       0 |       0 | 1-124             
  types.ts         |   91.18 |     90.8 |   85.71 |   91.18 | ...40-441,501-505 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
 src/ide           |   74.28 |    83.39 |   78.33 |   74.28 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |    64.2 |    81.48 |   66.66 |    64.2 | ...9-970,999-1007 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   33.92 |    45.16 |   45.76 |   33.92 |                   
  ...nfigLoader.ts |   70.27 |    35.89 |   94.73 |   70.27 | ...20-422,426-432 
  ...ionFactory.ts |    4.29 |      100 |       0 |    4.29 | ...20-371,377-394 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   13.52 |    81.25 |   29.16 |   13.52 | ...75-694,700-730 
  ...eLspClient.ts |   17.89 |      100 |       0 |   17.89 | ...37-244,254-258 
  ...LspService.ts |   45.87 |    62.13 |   66.66 |   45.87 | ...1282,1299-1309 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |   78.69 |    75.34 |   75.92 |   78.69 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   73.82 |    53.92 |     100 |   73.82 | ...88-895,902-904 
  ...en-storage.ts |   98.62 |    97.72 |     100 |   98.62 | 87-88             
  oauth-utils.ts   |   70.58 |    85.29 |    90.9 |   70.58 | ...70-290,315-344 
  ...n-provider.ts |   89.83 |    95.83 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   79.52 |    86.66 |   86.36 |   79.52 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   82.87 |    82.35 |   92.85 |   82.87 | ...63-173,181-182 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   67.43 |       76 |   65.62 |   67.43 |                   
  const.ts         |     100 |      100 |     100 |     100 |                   
  dream.ts         |   65.65 |    73.33 |      50 |   65.65 | 50,107-148        
  ...entPlanner.ts |   57.84 |    72.72 |   33.33 |   57.84 | ...35,140-147,152 
  entries.ts       |   63.77 |    79.16 |      50 |   63.77 | ...72-180,183-189 
  extract.ts       |    95.2 |    79.16 |     100 |    95.2 | 81-86,125         
  ...entPlanner.ts |   63.08 |    65.71 |   41.17 |   63.08 | ...17,222-223,332 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |    45.8 |    61.53 |   44.44 |    45.8 | ...04,211,214-346 
  indexer.ts       |   83.87 |    45.45 |     100 |   83.87 | ...50,56-57,69-70 
  manager.ts       |   75.31 |    81.04 |    75.6 |   75.31 | ...1278,1291-1293 
  memoryAge.ts     |   90.47 |    77.77 |     100 |   90.47 | 50-51             
  paths.ts         |   55.47 |    89.47 |   85.71 |   55.47 | ...,89-90,106-114 
  prompt.ts        |   93.36 |    71.42 |     100 |   93.36 | ...58,161,228-229 
  recall.ts        |   79.56 |    69.38 |   88.88 |   79.56 | ...40-245,269-280 
  ...ceSelector.ts |   91.86 |    77.27 |     100 |   91.86 | ...07,109-110,118 
  scan.ts          |   87.91 |    68.42 |     100 |   87.91 | ...47-48,58,82-87 
  ...entPlanner.ts |    11.5 |      100 |       0 |    11.5 | ...57-192,210-298 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   94.44 |    83.33 |     100 |   94.44 | 56-57,92-93       
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   89.31 |    86.02 |    87.5 |   89.31 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   90.24 |    91.42 |     100 |   90.24 | 142,148,151-160   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |    47.82 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.63 |    92.53 |     100 |   98.63 | 161,323,329       
  modelRegistry.ts |     100 |    98.59 |     100 |     100 | 222               
  modelsConfig.ts  |   84.57 |    82.14 |   81.57 |   84.57 | ...1223,1252-1253 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   71.18 |    88.73 |   48.57 |   71.18 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   81.42 |    86.66 |      80 |   81.42 | ...29-830,837-846 
  rule-parser.ts   |   95.99 |    93.18 |     100 |   95.99 | ...-864,1013-1015 
  ...-semantics.ts |   58.28 |    85.27 |    30.2 |   58.28 | ...1604-1614,1643 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/qwen          |   86.01 |    79.48 |   97.18 |   86.01 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   84.99 |    74.81 |   93.33 |   84.99 | ...,985-1001,1031 
  ...kenManager.ts |   83.76 |    76.22 |     100 |   83.76 | ...62-767,788-793 
 src/services      |   85.49 |       85 |   90.41 |   85.49 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   97.82 |    94.73 |     100 |   97.82 | 172-173           
  ...ionService.ts |   95.54 |    96.29 |     100 |   95.54 | ...18,386,388-392 
  ...ingService.ts |   84.15 |       84 |   82.85 |   84.15 | ...1241,1258-1259 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |     100 |    96.77 |     100 |     100 | 133,182           
  cronScheduler.ts |   97.56 |    92.98 |     100 |   97.56 | 62-63,77,155      
  ...eryService.ts |   80.43 |    95.45 |      75 |   80.43 | ...19-134,140-141 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |      90 |    84.44 |   88.88 |      90 | ...89,191,269-276 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  gitService.ts    |   68.75 |     92.3 |   55.55 |   68.75 | ...12-122,125-129 
  ...reeService.ts |   73.79 |       70 |   94.87 |   73.79 | ...1365,1393-1394 
  ...ionService.ts |   98.13 |     97.8 |   95.45 |   98.13 | ...32-333,380-381 
  ...orRegistry.ts |   96.34 |    91.66 |     100 |   96.34 | ...90-391,542-543 
  sessionRecap.ts  |   12.04 |      100 |       0 |   12.04 | 49-160            
  ...ionService.ts |   90.05 |    79.71 |   96.55 |   90.05 | ...1272,1276-1277 
  sessionTitle.ts  |   93.87 |    69.81 |     100 |   93.87 | ...33-236,267-268 
  ...ionService.ts |   83.01 |    78.66 |   87.75 |   83.01 | ...1482,1488-1493 
  ...UseSummary.ts |   94.73 |    87.71 |     100 |   94.73 | ...73-175,225-226 
  ...reeCleanup.ts |   14.56 |      100 |   33.33 |   14.56 | 58-185            
 ...icrocompaction |   97.69 |    89.79 |     100 |   97.69 |                   
  microcompact.ts  |   97.69 |    89.79 |     100 |   97.69 | ...68,229,233,314 
 src/skills        |    87.5 |     83.8 |   94.23 |    87.5 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |     93.1 |     100 |     100 | 93,112            
  skill-load.ts    |   92.94 |    81.63 |     100 |   92.94 | ...06,226,238-240 
  skill-manager.ts |   83.31 |    79.66 |   90.32 |   83.31 | ...1120,1127-1131 
  skill-paths.ts   |   86.74 |    77.77 |     100 |   86.74 | ...00-101,106-107 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/subagents     |   83.13 |    80.24 |   95.23 |   83.13 |                   
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   77.21 |    72.09 |   92.85 |   77.21 | ...1180,1202-1203 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 51-56,69-74,78-83 
 src/telemetry     |   73.63 |    87.18 |   77.73 |   73.63 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...-exporters.ts |   46.37 |      100 |   44.44 |   46.37 | ...85,88-89,92-93 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   93.93 |    90.21 |   94.11 |   93.93 | ...75-280,299-300 
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |    51.9 |       64 |   57.77 |    51.9 | ...1214,1231-1251 
  metrics.ts       |    74.9 |    82.95 |   74.54 |    74.9 | ...58-978,981-992 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  sdk.ts           |   90.45 |    83.56 |   76.92 |   90.45 | ...17-318,338-342 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   92.77 |     87.3 |     100 |   92.77 | 79-93,379-383     
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |    99.3 |    91.83 |     100 |    99.3 | 53                
  types.ts         |   79.17 |    94.49 |   83.33 |   79.17 | ...1149,1152-1181 
  uiTelemetry.ts   |   92.97 |    96.96 |   81.25 |   92.97 | ...93-194,200-207 
 ...ry/qwen-logger |   68.24 |    79.56 |   64.91 |   68.24 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   68.24 |    79.34 |   64.28 |   68.24 | ...1055,1093-1094 
 src/test-utils    |   93.16 |    95.83 |   73.52 |   93.16 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   91.19 |    97.05 |   68.96 |   91.19 | ...38,202-203,216 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   77.54 |    81.35 |      86 |   77.54 |                   
  ...erQuestion.ts |   88.93 |    76.74 |    90.9 |   88.93 | ...39-340,347-348 
  cron-create.ts   |   97.75 |    88.88 |   83.33 |   97.75 | 30-31             
  cron-delete.ts   |   96.82 |      100 |   83.33 |   96.82 | 26-27             
  cron-list.ts     |   96.66 |      100 |   83.33 |   96.66 | 25-26             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |   78.01 |    84.76 |   73.33 |   78.01 | ...86-687,774-824 
  ...r-worktree.ts |   82.43 |    68.75 |    87.5 |   82.43 | ...67-170,236-237 
  exit-worktree.ts |   83.47 |       84 |    90.9 |   83.47 | ...84-285,290-303 
  exitPlanMode.ts  |   85.09 |    85.71 |     100 |   85.09 | ...60-163,177-189 
  glob.ts          |   90.63 |    88.33 |   84.61 |   90.63 | ...28,171,302,305 
  grep.ts          |   79.19 |    85.71 |   78.94 |   79.19 | ...20,560,569-576 
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.77 |    60.09 |   90.32 |   72.77 | ...1211,1213-1214 
  ...nt-manager.ts |   69.73 |    75.29 |   71.42 |   69.73 | ...29-732,749-786 
  mcp-client.ts    |   33.18 |    77.41 |   66.66 |   33.18 | ...1490,1494-1497 
  mcp-tool.ts      |   90.98 |    88.88 |   96.42 |   90.98 | ...95-596,646-647 
  memory-config.ts |       0 |        0 |       0 |       0 | 1-47              
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 102,109           
  monitor.ts       |   92.27 |    83.94 |      92 |   92.27 | ...18,547-550,563 
  ...nforcement.ts |   82.44 |       90 |     100 |   82.44 | 174-185,234-247   
  read-file.ts     |   95.07 |     88.6 |      90 |   95.07 | ...99,290-293,296 
  ripGrep.ts       |   94.59 |    85.71 |   93.33 |   94.59 | ...60,463,541-542 
  ...-transport.ts |    6.34 |        0 |       0 |    6.34 | 47-145            
  send-message.ts  |   89.32 |    91.66 |   83.33 |   89.32 | 44-45,68-76       
  shell.ts         |   72.18 |    80.23 |   89.65 |   72.18 | ...3659,3708-3714 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   88.11 |    91.17 |   84.61 |   88.11 | ...95,399,422-444 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  todoWrite.ts     |   89.17 |    82.05 |   92.85 |   89.17 | ...41-546,568-569 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   74.79 |       75 |   80.48 |   74.79 | ...92-793,801-802 
  tool-search.ts   |   95.19 |    86.48 |    92.3 |   95.19 | ...47-153,208-213 
  tools.ts         |   91.98 |    90.19 |   88.88 |   91.98 | ...50-451,467-473 
  web-fetch.ts     |   88.59 |    79.48 |    92.3 |   88.59 | ...12-313,315-316 
  write-file.ts    |    79.2 |    79.26 |   83.33 |    79.2 | ...39-642,654-689 
 src/tools/agent   |   73.19 |    82.18 |   74.24 |   73.19 |                   
  agent.ts         |   73.39 |     82.5 |      75 |   73.39 | ...2095,2131-2138 
  fork-subagent.ts |   69.62 |    71.42 |   66.66 |   69.62 | ...04-105,140-151 
 src/utils         |   88.77 |    87.42 |   93.55 |   88.77 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   77.96 |    80.48 |     100 |   77.96 | ...35,156,173-176 
  bareMode.ts      |   27.27 |      100 |       0 |   27.27 | 9-15,18-19        
  browser.ts       |    7.69 |      100 |       0 |    7.69 | 17-56             
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   89.11 |    86.66 |     100 |   89.11 | ...28-129,132-133 
  cronDisplay.ts   |   42.85 |    23.07 |     100 |   42.85 | 26-31,33-45,47-54 
  cronParser.ts    |   89.74 |    85.71 |     100 |   89.74 | ...,63-64,183-186 
  debugLogger.ts   |    95.9 |    93.84 |   94.73 |    95.9 | 106-107,214-218   
  editHelper.ts    |   93.63 |    83.52 |     100 |   93.63 | ...28-429,463-464 
  editor.ts        |   97.61 |    95.71 |     100 |   97.61 | ...70-271,273-274 
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |     100 |    95.45 |     100 |     100 | 83                
  errorParsing.ts  |    97.7 |    97.05 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   88.46 |       90 |     100 |   88.46 | 69-74             
  errors.ts        |   70.92 |    79.59 |   53.33 |   70.92 | ...03-219,223-229 
  fetch.ts         |   70.18 |    71.42 |   71.42 |   70.18 | ...42,148,161,186 
  fileUtils.ts     |   91.41 |    86.07 |      95 |   91.41 | ...1182,1186-1192 
  forkedAgent.ts   |    78.5 |    70.73 |   85.71 |    78.5 | ...30-436,441-447 
  formatters.ts    |   54.54 |       50 |     100 |   54.54 | 12-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |    12.5 |      100 |       0 |    12.5 | 21-34             
  gitDiff.ts       |   92.36 |    79.53 |     100 |   92.36 | ...55-856,928-929 
  ...noreParser.ts |    92.3 |    89.36 |     100 |    92.3 | ...15-116,186-187 
  gitUtils.ts      |   56.66 |    85.71 |      75 |   56.66 | ...2,72-73,97-148 
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 26                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |    74.1 |    90.76 |   58.33 |    74.1 | ...23-326,336-342 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...yDiscovery.ts |    83.9 |    79.36 |     100 |    83.9 | ...16,319,411-414 
  ...tProcessor.ts |   93.63 |       90 |     100 |   93.63 | ...96-302,384-385 
  ...Inspectors.ts |   61.53 |      100 |      50 |   61.53 | 18-23             
  modelId.ts       |   98.55 |    96.87 |     100 |   98.55 | 103               
  ...kerChecker.ts |   82.55 |    78.57 |     100 |   82.55 | 68-69,79-84,92-98 
  notebook.ts      |   94.35 |    84.78 |     100 |   94.35 | ...10,122,174-176 
  openaiLogger.ts  |   88.05 |    84.09 |     100 |   88.05 | ...44-146,169-174 
  partUtils.ts     |     100 |    98.61 |     100 |     100 | 206               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   93.21 |    91.86 |     100 |   93.21 | ...89-390,392-394 
  pdf.ts           |   93.68 |    87.05 |     100 |   93.68 | ...96-297,321-325 
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  ...ectSummary.ts |   89.39 |    72.41 |     100 |   89.39 | ...37-142,193-196 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   58.57 |       76 |     100 |   58.57 | ...4,88-89,95-100 
  ...noreParser.ts |   85.45 |    85.18 |     100 |   85.45 | ...59,65-66,72-73 
  rateLimit.ts     |   92.55 |    85.92 |     100 |   92.55 | ...70-272,309-310 
  readManyFiles.ts |   87.96 |    86.95 |     100 |   87.96 | ...05-207,223-234 
  retry.ts         |   89.81 |    88.05 |     100 |   89.81 | ...29,350,357-358 
  ripgrepUtils.ts  |   46.79 |    84.37 |   66.66 |   46.79 | ...45-246,258-335 
  ...sDiscovery.ts |   97.42 |    92.85 |     100 |   97.42 | ...04,182-183,202 
  ...tchOptions.ts |   81.72 |    85.04 |   95.23 |   81.72 | ...11,536,565-574 
  runtimeStatus.ts |    97.5 |    88.57 |     100 |    97.5 | 167-168           
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   90.78 |    88.23 |     100 |   90.78 | ...41-42,93,95-96 
  ...aValidator.ts |   94.57 |    80.26 |     100 |   94.57 | ...04,213-216,270 
  ...r-launcher.ts |   76.92 |     91.3 |   66.66 |   76.92 | ...34,136,157-195 
  ...orageUtils.ts |   96.89 |    85.84 |     100 |   96.89 | ...51,367,447,466 
  shell-utils.ts   |   82.93 |    89.55 |     100 |   82.93 | ...1522,1529-1533 
  ...lAstParser.ts |   95.58 |    85.79 |     100 |   95.58 | ...1059-1061,1071 
  ...nlyChecker.ts |   95.75 |    92.39 |     100 |   95.75 | ...00-301,313-314 
  sideQuery.ts     |   98.73 |    94.59 |     100 |   98.73 | 111               
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |       50 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  textUtils.ts     |      60 |      100 |   66.66 |      60 | 36-55             
  thoughtUtils.ts  |     100 |    92.85 |     100 |     100 | 71                
  ...-converter.ts |   94.59 |    85.71 |     100 |   94.59 | 35-36             
  tool-utils.ts    |    93.6 |     91.3 |     100 |    93.6 | ...58-159,162-163 
  truncation.ts    |     100 |       92 |     100 |     100 | 52,71             
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   93.71 |    89.28 |   93.33 |   93.71 | ...24-225,249-251 
  xml.ts           |     100 |      100 |     100 |     100 |                   
  yaml-parser.ts   |      92 |    84.31 |     100 |      92 | 49-53,65-69       
 ...ils/filesearch |   85.77 |    81.06 |   96.42 |   85.77 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   82.84 |    77.49 |   94.82 |   82.84 | ...1451,1485-1486 
  fileSearch.ts    |   93.58 |    87.32 |     100 |   93.58 | ...46-247,249-250 
  ignore.ts        |     100 |      100 |     100 |     100 |                   
  result-cache.ts  |     100 |     92.3 |     100 |     100 | 46                
 ...uest-tokenizer |   56.63 |    74.52 |   74.19 |   56.63 |                   
  ...eTokenizer.ts |   41.86 |    76.47 |   69.23 |   41.86 | ...70-443,453-507 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |      76 |      100 |   33.33 |      76 | 45-48,55-56       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

function getRecoveryContinuationSuffix(
previousText: string,
continuationText: string,
): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] getRecoveryContinuationSuffix 的空输入守卫分支 (previousText.length === 0 || continuationText.length === 0) 未被测试覆盖。若该分支损坏,空字符串场景下的合并行为可能异常。

Suggested change
): string {
// 建议添加测试:previousText='' 或 continuationText='',断言返回 continuationText 原样

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 549f27aprompt-recovery-thought-only exercises the empty-text guard end-to-end (previous turn has only a thought part, so getPlainTextFromParts returns ''). The continuation is asserted to pass through verbatim, and the same test verifies that buildOutputRecoveryMessage does NOT append a <previous_response_suffix> block when there's no plain text to anchor on.

): string {
if (previousText.length === 0 || continuationText.length === 0) {
return continuationText;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] getRecoveryContinuationSuffix 的完全重叠守卫分支 (previousText.endsWith(continuationText) && isSignificantRecoveryOverlap) 未被测试。当续写与上一轮末尾完全相同时应返回 '',但该路径未被直接验证。

Suggested change
}
// 建议添加测试:previousText='Hello world', continuationText='world'(≥6字节),断言返回 ''

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 549f27aprompt-recovery-full-overlap covers exactly this branch: previous text ends with tail-fragment, continuation is tail-fragment (≥ RECOVERY_OVERLAP_MIN_BYTES), and the test asserts the merged history is just the previous text with no duplication appended (i.e. getRecoveryContinuationSuffix returned '').

previousModelTurn?.role === 'model'
? getPlainTextFromParts(previousModelTurn.parts)
: '';
if (previousText.trim().length === 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] buildOutputRecoveryMessage 的空文本分支 (previousText.trim().length === 0) 未被测试。当上一轮模型回复无有效文本时(如仅有 thought part),该分支应返回原始 OUTPUT_RECOVERY_MESSAGE 而不附加 <previous_response_suffix> 块。

Suggested change
if (previousText.trim().length === 0) {
// 建议添加测试:上一轮 parts 仅为 [{thought: true}] 或空文本,验证恢复消息不包含 <previous_response_suffix> 块

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 549f27a — same prompt-recovery-thought-only test: it captures the recovery user-turn payload from the mock and asserts recoveryMessage contains the Output token limit hit string but does NOT contain <previous_response_suffix>. That covers the previousText.trim().length === 0 branch in buildOutputRecoveryMessage.

@tanzhenxin tanzhenxin added the type/bug Something isn't working as expected label May 9, 2026

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The bug being fixed is real — providers that re-send a context anchor at the start of a max-tokens recovery do leave duplicate Markdown tables in history, and dedup at coalesce time is the right place to do it. The architecture is sound: pure functions, minimal call-site change, defensive bailouts on non-text parts. But the contained-prefix fallback's matching logic is too loose, and it can silently drop legitimate continuation text in plausible cases — which is a worse failure mode than the duplication it's replacing (silent loss is harder for users to notice).

1. The contained-prefix fallback strips legitimate continuation text on common short phrases (severity: high · confidence: very high)

The fallback walks candidate prefix lengths from longest to 1 and returns the first one that (a) is at least 6 bytes (or 4 bytes if it contains a Markdown structural character) AND (b) appears anywhere in the last 4000 characters of the previous response. There's no anchor: no per-line check, no requirement that the match sit near the truncation tail, no Markdown structural anchor. With the floor at 6 bytes, almost any short prose phrase qualifies.

Reproducible false positives running the helper in isolation:

Previous turn ends with Continuation begins with What gets dropped
… In summary, this concludes. In summary, the answer is 42. In summary, th
… In conclusion, cats are awesome. In conclusion, dogs are also awesome. In conclusion,
… Here is the breakdown: a, b, c. Here is the rest of the explanation. Here is the
… I think they are great. I I think we should also discuss dogs. I think
Markdown table in previous turn → continuation re-emits the same header rows + a new data row The header rows + the leading | of the new data row are stripped — table is mangled

The greedy "longest match wins" descent makes the loss bigger, not smaller: a 14-byte coincidental match (In conclusion,) wins over a 6-byte one and deletes more. And since the recovery prompt now puts the previous suffix verbatim inside <previous_response_suffix>, the model is actively encouraged to start the continuation with bytes that overlap — exactly the case this helper mishandles.

Tightening the threshold (≥30 bytes for substring matches), anchoring the contained match to a Markdown structural boundary (start of a table row, code fence, heading), or requiring the match to sit at/near the truncation tail rather than anywhere in the 4000-char window would all close this without losing the intended fix for replayed table headers.

2. The suffix-overlap path can over-strip when the model legitimately re-uses a short tail-matching phrase (severity: medium · confidence: high)

Same root cause, less severe. The suffix-overlap loop returns the first overlap that satisfies both previousText.endsWith(overlap) and the significance threshold. Anchoring on endsWith is much stronger than substring-anywhere, so most legitimate continuations are safe. The risk is when the model genuinely starts with bytes that match the truncation tail — e.g. previous ends with Some content.\n and the continuation starts Some content.\nand more! because the model is restating before continuing. The path returns and more! and silently drops the restated opener.

There's also a hard short-circuit when the entire continuation matches the previous tail: a 4-byte structural string like \n# A discards the entire continuation when the previous turn ends with the same bytes. That's the desired behavior for a true duplicate, but offers no escape valve when the continuation genuinely is just a header restart.

Verdict

REQUEST_CHANGES — the contained-prefix fallback in its current form replaces silent duplication with silent loss across a wide class of plausible cases. Tightening the significance threshold and anchoring the match to a structural boundary should close most of the risk in one change. The rest of the PR (architecture, suffix path, recovery-prompt augmentation) is good.

Address review feedback on PR #3966: the contained-prefix fallback in
geminiChat recovery dedup was too permissive — a 6-byte minimum plus a
4000-char lookahead window allowed common opener phrases ("In summary,",
"In conclusion,", "Here is the …") to silently strip legitimate
continuation text whenever they happened to coincide with any substring
in the previous turn. Silent loss is a worse failure mode than the
duplication we were fixing.

Constrain the fallback to its real intended use case — replayed
Markdown blocks that providers re-emit at the start of a recovery
continuation (table headers, headings, fenced code, lists, blockquotes):

- Require the continuation to *open* with a Markdown structural anchor
  before considering any contained-prefix replay; plain prose openers
  fall through with no dedup attempted.
- Restrict the substring search to the immediate truncation tail
  (last 400 chars) so a coincidental match far above the truncation
  point cannot win.
- Raise the contained-prefix byte floor (12 bytes) above the suffix-
  overlap floor.

Also add coverage for the previously-untested guard branches
(empty input, full-overlap drop, empty previous-text path that skips
the <previous_response_suffix> block) and regression tests for the
prose-loss scenarios called out in review.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0

chiga0 commented May 9, 2026

Copy link
Copy Markdown
Collaborator Author

@tanzhenxin Thanks for the careful review — the prose-loss failure mode you flagged is real and a strict regression on the bug we were trying to fix. Pushed 549f27a addressing concerns #1 and partially #2:

#1 (high) — contained-prefix fallback dropping legitimate prose: Tightened on three axes simultaneously, since each one alone leaves a residual class of false positives:

  • Structural anchor required. The contained-prefix path now bails out unless the continuation opens with a Markdown block-level marker (table row, ATX heading, fenced code, blockquote, list item). The marker check requires the syntactic gap the spec mandates — # not #abc, | … | not |-in-prose — so incidental punctuation in prose openers doesn't trigger. All your reproducers (In summary,, In conclusion,, Here is the , I think ) now fall through with no dedup attempted because none of them open with a structural marker.
  • Lookback narrowed to the truncation tail. Substring search is now restricted to the last 400 chars of the previous response (RECOVERY_CONTAINED_TAIL_LOOKBACK_CHARS), down from 4000. A coincidental match thousands of chars above the cut point can no longer win — added prompt-recovery-far-prose regression test.
  • Byte floor raised. Bumped to 12 bytes for contained-prefix matches (RECOVERY_CONTAINED_PREFIX_MIN_BYTES), well above the suffix path's 6/4-byte floors. The existing replayed-table test (### 常用语法速查\n| 语法 | 说明 | = 42 bytes) still passes comfortably.

The greedy "longest match wins" descent is preserved because once the structural anchor + tail-lookback + 12-byte floor are all required, the longest match is the right behavior — it's matching an entire replayed block rather than coincidentally matching trailing tokens of a prose phrase.

#2 (medium) — suffix-overlap path: Left intact deliberately. The endsWith anchor is genuinely strong (your own assessment: "much stronger than substring-anywhere, so most legitimate continuations are safe"), and the structural-overlap floor of 4 bytes is gated by the same endsWith constraint — a 4-byte structural overlap only fires when the previous text literally ends with those exact bytes, which is the textbook "model is replaying the truncation tail" case. The escape-valve concern for header-restarts (where the continuation genuinely is just a re-emitted header) is, I think, the wrong tradeoff to optimize for here: a re-emitted header that the model also intended to keep would already be duplicated in the durable history without dedup, and surfacing it as "lost text" is a UX win because the user sees exactly one header instead of two. Happy to revisit if you have a concrete case where this misbehaves.

Also addressed @wenshao's three uncovered-branch suggestions inline (regression tests for empty-input, full-overlap drop, and the empty-previous-text path that suppresses <previous_response_suffix>).

@chiga0 chiga0 requested review from tanzhenxin and wenshao May 9, 2026 08:02
Comment thread packages/core/src/core/geminiChat.ts Outdated
* incidental `#` or `|` characters in prose do not count.
*/
function startsWithMarkdownStructuralAnchor(text: string): boolean {
const trimmed = text.replace(/^\n+/, '');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] startsWithMarkdownStructuralAnchor 仅去除 \n,未处理空格/制表符。注释说明「跳过前导空白/换行符」,但 text.replace(/^\n+/, '') 不匹配 \t。若提供方在恢复时添加前导空格,结构性锚点检测将失败,导致 contained-prefix 路径完全跳过。

Suggested change
const trimmed = text.replace(/^\n+/, '');
const trimmed = text.replace(/^\s+/, '');

— deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 7bd70dc. Switched startsWithMarkdownStructuralAnchor to text.replace(/^\s+/, '') so leading spaces and tabs (not just \n) are skipped before the anchor check. The contained-prefix dedup search itself still operates on the unmodified continuation, so this only widens the gate — it doesn't change what gets dedup'd once the gate opens.


const tail =
previousText.length > OUTPUT_RECOVERY_TAIL_CHARS
? previousText.slice(-OUTPUT_RECOVERY_TAIL_CHARS)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] buildOutputRecoveryMessage 截断分支(previousText.slice(-OUTPUT_RECOVERY_TAIL_CHARS),即前一个回复 >1200 字符时)未被测试覆盖。这是一条核心生产路径,但所有新测试的前置响应均较短(最长约 540 字符),slice(-1200) 与多字节字符的交互未被验证。

建议添加测试:使用 'x'.repeat(1300) 作为前置响应,通过捕获 generateContentStream 参数断言 <previous_response_suffix> 块包含被截断的 1200 字符尾部。

— deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — added a regression test in 7bd70dc. It uses a 1300-char previous response ('A'.repeat(100) + 'B'.repeat(1200)), captures the recovery user-turn payload, and asserts the <previous_response_suffix> block contains exactly the trailing 1200 chars and that the 100-char head does not leak. This locks down the previousText.slice(-OUTPUT_RECOVERY_TAIL_CHARS) branch.

…l truncation

Address wenshao review on PR #3966:

- `startsWithMarkdownStructuralAnchor` now strips all leading whitespace
  (`/^\s+/`) instead of only newlines (`/^\n+/`). Some providers re-emit
  a recovered Markdown block with leading spaces or tabs, not just
  newlines; the old regex caused the structural-anchor gate to fail and
  the contained-prefix dedup path was silently skipped.
- Add a regression test for `buildOutputRecoveryMessage` that exercises
  the `previousText.slice(-OUTPUT_RECOVERY_TAIL_CHARS)` truncation
  branch with a 1300-char previous response, asserting the
  <previous_response_suffix> block contains exactly the trailing 1200
  chars and that the dropped head does not leak.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0

chiga0 commented May 11, 2026

Copy link
Copy Markdown
Collaborator Author

All review feedback addressed: @wenshao's whitespace-in-anchor concern fixed in 7bd70dc, untested >1200-char truncation branch fixed in 7bd70dc, @tanzhenxin's earlier dedup concerns addressed in 549f27a. 66/66 geminiChat tests passing, CI all green. Could you take another look?

@chiga0 chiga0 requested a review from wenshao May 11, 2026 05:19

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Test coverage gaps: the startsWithMarkdownStructuralAnchor regex has 7 anchor patterns but only 2 (headings, tables) are tested — fenced code, blockquote, unordered/ordered list markers are not exercised. Missing: multi-byte content tests for byte-length thresholds, appendRecoveryContinuationParts with non-text parts, and buildOutputRecoveryMessage with role !== 'model'. Adding these would harden the dedup pipeline against future refactors.

— deepseek-v4-pro via Qwen Code /review

Comment thread packages/core/src/core/geminiChat.ts Outdated
*/
function startsWithMarkdownStructuralAnchor(text: string): boolean {
const trimmed = text.replace(/^\s+/, '');
return /^(\|[^\n]*\||#{1,6} |```|>\s|[-*+] |\d+\. )/.test(trimmed);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] startsWithMarkdownStructuralAnchor fenced code block regex ``` (three backticks + space) does not match the two most common fenced code opening formats: ```python (language directly after backticks, no space) and ```\n (newline after backticks). This causes startsWithMarkdownStructuralAnchor to return false, which disables the entire contained-prefix dedup path for fenced code blocks — contradicting the stated design intent to handle replayed code blocks.

Suggested change
return /^(\|[^\n]*\||#{1,6} |```|>\s|[-*+] |\d+\. )/.test(trimmed);
return /^(\|[^\n]*\||#{1,6} |```|>\s|[-*+] |\d+\. )/.test(trimmed);

— deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current regex is actually already what you suggested — there's no trailing space after the three backticks. Looking at the file bytes around line 158:

return /^(\\|[^\\n]*\\||#{1,6} |```|>\\s|[-*+] |\\d+\\. )/.test(trimmed);

The ``` alternative has no space after it, so it correctly matches all three common fenced-code openings:

  • 
    

Verified at the byte level (od -c on lines 156–159) and with a quick Node check. The diff hunk in the review UI may have visually inserted whitespace that doesn't exist in the source. Leaving as-is — happy to revisit if you see a concrete case where it doesn't fire.

return continuationText;
}

function isPlainTextPart(part: Part | undefined): part is Part & {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] isPlainTextPart is a near-duplicate of the existing isValidNonThoughtTextPart (line 412), but omits three guards: thoughtSignature, inlineData, and fileData. It also uses stricter equality (part.thought !== true) vs the existing function's truthiness check (!part.thought). Two nearly-identical guard functions with subtly different semantics create a maintenance trap — future bugfixes to one will likely miss the other. Consider wrapping isValidNonThoughtTextPart or adding a comment explaining why the checks intentionally diverge.

— deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 24582d4. isPlainTextPart now delegates to isValidNonThoughtTextPart, so the recovery-merge path and the consolidated-history path (line 1323) agree on what counts as plain text. The type predicate is preserved at the wrapper level for callers that rely on part.text: string narrowing; the underlying guards (thought, thoughtSignature, function*, inlineData, fileData) now live in one place.

`${OUTPUT_RECOVERY_MESSAGE}\n\n` +
'The previous assistant response ended with this exact suffix. ' +
'Do not repeat any line, table row, code line, or prose that already ' +
'appears in it; output only text that comes after this suffix:\n\n' +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] buildOutputRecoveryMessage embeds raw model output inside <previous_response_suffix>...</previous_response_suffix> pseudo-XML tags without escaping. If the model's own response happens to contain the literal string </previous_response_suffix> (e.g., when generating XML/HTML examples), the recovery prompt structure breaks — the model would see a prematurely closed tag and misinterpret the suffix boundary. Low collision probability, but cheap to fix: escape </ as \<\/ before embedding, or switch to a delimiter with lower collision risk (e.g., a hash-delimited block).

— deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 24582d4. Added sanitizeRecoverySuffixTail which neutralizes any literal <previous_response_suffix> / </previous_response_suffix> strings inside the tail by inserting a zero-width space (U+200B) between the angle bracket and the rest of the tag. The text remains visually identical to the model and the recovery instruction's intent is preserved, but the literal byte sequence no longer collides with our delimiter scan.

Regression test prompt-recovery-delimiter-collision asserts the recovery prompt has exactly one opening and one closing delimiter even when the previous response contains a raw </previous_response_suffix>. Went with sanitization over a random-nonce delimiter to keep the change minimal and the prompt text deterministic across runs.

Address two review concerns on geminiChat output-recovery:

- `isPlainTextPart` was a near-duplicate of `isValidNonThoughtTextPart`
  with subtly weaker guards (missing thoughtSignature/inlineData/fileData
  and using `!== true` vs `!part.thought`). Delegate to the shared
  predicate so the recovery-merge and consolidated-history paths agree
  on what counts as plain text.
- `buildOutputRecoveryMessage` embedded the previous response inside a
  `<previous_response_suffix>` pseudo-XML block without sanitization. If
  the model's own truncated output contained the literal closing tag
  (e.g. while generating XML/HTML examples), the recovery prompt's
  structure would break. Neutralize literal opening/closing delimiters
  inside the tail with a zero-width space so the prompt always has
  exactly one well-formed block; add a regression test that asserts the
  delimiter pair count stays at 1/1 even when the tail contains a raw
  `</previous_response_suffix>`.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0 chiga0 requested a review from wenshao May 12, 2026 07:12
!tail.includes('<previous_response_suffix>')
) {
return tail;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] sanitizeRecoverySuffixTail 的开放标签中性化路径(.replace(/<previous_response_suffix>/g, ...))未被测试覆盖。prompt-recovery-delimiter-collision 测试仅验证了闭合标签 </previous_response_suffix> 的中性化。如果开放标签的替换逻辑有 bug,模型输出中的 <previous_response_suffix> 将直接破坏恢复提示的分隔符结构。

Suggested change
}
// 建议新增测试:previous = 'Tag: <previous_response_suffix> was in the output.'
// 断言 recoveryMessage 中 openCount === 1 且原生开放标签被转换为 <​previous_response_suffix>

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — added a regression test in 9eb7f72. The new prompt-recovery-delimiter-collision-open test mirrors the existing closing-tag test but seeds the previous model turn with a literal <previous_response_suffix> opening tag. It asserts openCount === 1 and closeCount === 1 in the recovery message (so the recovery prompt's structural pair stays unique), checks that the neutralized variant (<​previous_response_suffix>, with U+200B) appears inside the embedded tail, and verifies the block still parses with one well-formed match preserving the surrounding prose. Full suite: 68 tests, all green.

The existing prompt-recovery-delimiter-collision test only exercises
the closing-tag (`</previous_response_suffix>`) neutralization path.
Add a sibling test that emits a literal opening tag in the previous
model turn so the opening-tag replace branch is also covered. Asserts
exactly one opening/closing delimiter pair in the recovery message and
that the neutralized variant (with zero-width space) appears in the
embedded tail.
@chiga0

chiga0 commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author

All feedback addressed:

  • Round-3 (24582d4): regex false-positive verified at byte level; isPlainTextPart refactored to delegate to existing predicate; sanitizeRecoverySuffixTail added for delimiter collisions.
  • Round-4 (9eb7f72): added prompt-recovery-delimiter-collision-open test exercising the open-tag replacement branch (previously only the closing-tag path had coverage).

68/68 geminiChat tests passing, CI green. Ready for re-review.

@chiga0 chiga0 requested a review from wenshao May 12, 2026 12:35

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Reviewed the deduplication logic added to the output token recovery path in geminiChat.ts. The PR is well-designed with layered defenses (suffix-anchored scan → contained-prefix fallback → prose preservation guard), bounded search windows, and thorough test coverage (9 new tests covering overlap, replay, prose regression, delimiter collisions, and tail truncation).

No critical issues found. Build passes, all 68 tests pass, typecheck and lint are clean.

Key observations (low-confidence suggestions — for human review):

  1. startsWithMarkdownStructuralAnchor table-row regex (\|[^\n]*\|) matches any line with 2+ pipe chars, which could theoretically match prose containing pipe notation. The 12-byte minimum and 400-char tail lookback provide strong guards though.
  2. coalesceRecoveryPairs missing debugLogger.warn on shape mismatch bail — inconsistent with the file's pattern of logging operational failures.
  3. Leading whitespace in continuation text can bypass suffix-anchored overlap detection for prose continuations (the contained-prefix path handles whitespace but requires Markdown structural anchors).
  4. findContainedRecoveryPrefixReplayLength includes() can match Markdown anchors inside code blocks in the previous tail, potentially causing false-positive prefix stripping.
  5. Test gaps: newline edge cases, additional Markdown anchor types (code fences, blockquotes, lists), and content assertion in the existing recovery loop test.

Overall the code is solid and the design is well-thought-out. Good to merge.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review notes — see inline comments. Overall LGTM; the conservative anchor-gated dedup design and the 9 new tests (including the two prose-opener / far-substring negative regression tests) are solid. The items below are non-blocking suggestions around readability, one subtle anchor-side check, and constants documentation.

Comment on lines +116 to +119
const OUTPUT_RECOVERY_TAIL_CHARS = 1200;
const RECOVERY_OVERLAP_MAX_SCAN_CHARS = 4000;
const RECOVERY_OVERLAP_MIN_BYTES = 6;
const RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES = 4;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these magic numbers deserve a sentence of rationale each, the way RECOVERY_CONTAINED_TAIL_LOOKBACK_CHARS and RECOVERY_CONTAINED_PREFIX_MIN_BYTES already get below. Specifically:

  • OUTPUT_RECOVERY_TAIL_CHARS = 1200 — why 1200? (token budget consideration? empirically enough context?)
  • RECOVERY_OVERLAP_MAX_SCAN_CHARS = 4000 — caps the inner loop; worth saying so.
  • RECOVERY_OVERLAP_MIN_BYTES = 6 — minimum length before a plain-text overlap is treated as significant (presumably to avoid coincidental short shared suffixes like ". " or "the ").
  • RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES = 4 — lower floor when the overlap contains Markdown structure chars; explain why structural overlaps get a lower bar.

Future readers will need to adjust these and the reasoning shouldn't have to be reverse-engineered from the tests.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — done in 3663e8b. Each of the four constants now has a JSDoc block explaining the rationale:

  • OUTPUT_RECOVERY_TAIL_CHARS = 1200 — pragmatic balance: large enough for ~200–400 tokens of trailing prose or a multi-row Markdown table to give the model coherent resume context, small enough to stay well under provider input budgets even combined with the rest of history.
  • RECOVERY_OVERLAP_MAX_SCAN_CHARS = 4000 — hard cap on both the suffix-anchored overlap scan and the contained-prefix scan, so recovery dedup stays O(min(prev, cont, 4000)) in iteration count.
  • RECOVERY_OVERLAP_MIN_BYTES = 6 — minimum bytes for a plain-text overlap to count as significant; explicitly calls out the ". "/"the "/", and " false-positive class your comment named.
  • RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES = 4 — lower floor when the overlap contains #/|/backtick/\n, since those don't collide coincidentally the way short prose does.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — added a JSDoc block per constant in 3663e8ba. Each rationale captures the angle you flagged:

  • OUTPUT_RECOVERY_TAIL_CHARS = 1200 — large enough for ~200–400 tokens of trailing context (or a multi-row table), small enough to stay under any provider's input budget alongside the rest of history.
  • RECOVERY_OVERLAP_MAX_SCAN_CHARS = 4000 — explicit hard cap on the inner loops so dedup stays O(min(previous, continuation, 4000)).
  • RECOVERY_OVERLAP_MIN_BYTES = 6 — exactly the ". "/"the " false-positive floor you suggested.
  • RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES = 4 — explains why structural overlaps get a lower bar ("| a ", "## " are almost certainly replayed block markers).

See geminiChat.ts L116-L153.

Comment thread packages/core/src/core/geminiChat.ts Outdated
const prefix = continuationText.slice(0, length);
if (
byteLength(prefix) >= RECOVERY_CONTAINED_PREFIX_MIN_BYTES &&
previousTail.includes(prefix)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle: the structural-anchor check at line 184 only enforces that the continuation starts with a Markdown anchor — but previousTail.includes(prefix) here is a plain substring match, so the matched occurrence inside previousTail does not have to begin at a line boundary.

Example: continuation = "### Heading\nfoo bar baz" could match a previousTail that contains "...prose ### Heading\nfoo bar baz" mid-paragraph (perhaps inside a code block discussing Markdown). The 400-char lookback + 12-byte floor make this very unlikely in practice, but if you want full symmetry you could require the match to be at index 0 or preceded by \n in previousTail. Up to you whether the extra strictness is worth it given how narrow the window already is.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — tightened in 3663e8b. Added a previousTailContainsAtLineBoundary helper that requires the match inside previousTail to begin at index 0 or immediately after a \n, mirroring the structural-anchor semantics enforced on the continuation side.

A couple of details worth flagging:

  • All occurrences are walked (not just the first indexOf), so a benign mid-paragraph hit doesn't shadow a real line-anchored replay later in the 400-char tail window. Worst case is still bounded by the lookback × scan cap.
  • This only narrows the gate; it can't widen what gets dedup'd. So the change is strictly safer — no risk of stripping additional continuation text that wasn't already eligible.

Test suite (68 tests) still green.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — went ahead and took the extra strictness. 3663e8ba replaces the plain previousTail.includes(prefix) with a new previousTailContainsAtLineBoundary helper that requires the match inside previousTail to begin at index 0 or immediately after \n, mirroring the structural-anchor check on the continuation side.

All occurrences are walked rather than just the first hit, so a benign mid-paragraph match (e.g. inside a fenced code block discussing Markdown) doesn't shadow a real line-anchored replay later in the 400-char tail window. See L246-L262 and the call site at L226.

Comment on lines +222 to +230
for (let length = maxOverlap; length > 0; length -= 1) {
const overlap = continuationText.slice(0, length);
if (
isSignificantRecoveryOverlap(overlap) &&
previousText.endsWith(overlap)
) {
return continuationText.slice(length);
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perf note (non-blocking): this loop is O(n²) — up to RECOVERY_OVERLAP_MAX_SCAN_CHARS (4000) iterations, each doing previousText.endsWith(overlap) (O(m)) plus byteLength(overlap) (also O(m)) inside isSignificantRecoveryOverlap. Worst-case ~16M char ops per recovery event. Fine for the current scale, but worth being aware of if recovery tails ever grow.

If you ever want to optimize: byteLength can be hoisted (the overlap grows monotonically from the previous iteration when scanning down) and the endsWith check could be replaced by precomputing a Z-array / failure function on continuationText and scanning once. Not needed today.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed it's non-blocking — opted to document rather than refactor in this PR to keep the change focused on review polish. Added an inline comment in 3663e8b above the loop that:

  1. Names the O(n²) characteristic explicitly (RECOVERY_OVERLAP_MAX_SCAN_CHARS iterations × O(m) per endsWith + byteLength).
  2. Quantifies the current worst case (~16M char-ops per recovery event at the 4000-char cap) and notes why that's fine today (recovery is rare, cap is small).
  3. Records the Z-array / failure-function rewrite as the documented next step if the cap ever grows materially.

Happy to do the actual optimization in a follow-up if/when the scan cap becomes a real concern — keeping this PR scoped to the dedup correctness work felt cleaner than mixing in a perf rewrite that isn't needed yet.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed it's fine at current scale — keeping the simple loop as-is. Did add an inline comment in 3663e8ba making the bound explicit so the next reader doesn't have to derive it:

Worst-case complexity here is O(n²): up to RECOVERY_OVERLAP_MAX_SCAN_CHARS iterations, each calling previousText.endsWith(overlap) plus byteLength(overlap) (both O(m)). At the current 4000-char scan cap that is ~16M char-ops per recovery event, which is fine because recovery is rare and the cap is small. If the cap ever grows materially, this can be rewritten with a precomputed Z-array / failure function on continuationText to scan once instead of repeatedly slicing/comparing.

See L285-L291. Recovery only fires after a MAX_TOKENS truncation (rare per session), so the ~16M-op worst case is not on any hot path.

Comment on lines +346 to +349
if (suffix.length > 0) {
previousLastPart.text += suffix;
}
nextParts.shift();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reads as if nextParts.shift() could be a bug at first glance because it runs even when suffix.length === 0. It's correct — empty suffix means the continuation's first text part was a complete replay of the previous tail and should be dropped — but a one-line comment would save the next reader a double-take, e.g.:

// Always shift the first continuation text part: a non-empty suffix has
// already been appended to previousLastPart, and an empty suffix means
// the part was a pure replay and should be discarded.
nextParts.shift();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — added the explanatory comment in 3663e8b, very close to your suggested wording:

if (suffix.length > 0) {
  previousLastPart.text += suffix;
}
// Always drop the first continuation text part: a non-empty suffix has
// already been appended to previousLastPart above, and an empty suffix
// means the part was a pure replay of the previous tail and should be
// discarded so it does not duplicate into history.
nextParts.shift();

Saves the double-take exactly where you flagged it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed intentional, and added the clarifying comment you suggested (slightly reworded) in 3663e8ba:

// Always drop the first continuation text part: a non-empty suffix has
// already been appended to previousLastPart above, and an empty suffix
// means the part was a pure replay of the previous tail and should be
// discarded so it does not duplicate into history.
nextParts.shift();

See L419-L423. The two cases (non-empty suffix already merged vs. empty suffix = pure replay) both want the part gone, so the unconditional shift() is correct — just non-obvious without the note.

…refix anchor

Address PR #3966 review polish items from wenshao:
- Add JSDoc rationale to each magic constant (OUTPUT_RECOVERY_TAIL_CHARS,
  RECOVERY_OVERLAP_MAX_SCAN_CHARS, RECOVERY_OVERLAP_MIN_BYTES,
  RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES) so future tuning is grounded.
- Make the contained-prefix scan symmetric: require the match inside
  previousTail to begin at index 0 or immediately after a newline, mirroring
  the structural-anchor check on the continuation side. All occurrences are
  walked so a benign mid-paragraph hit doesn't shadow a real line-anchored
  match later in the 400-char tail window.
- Document the suffix-anchored overlap loop's O(n^2) bound and the bounded
  scan cap so the perf characteristic is explicit rather than reverse-
  engineered.
- Explain why appendRecoveryContinuationParts always shifts the first
  continuation text part even when the dedup suffix is empty (empty suffix
  means a pure replay that must be discarded).

All 68 tests in geminiChat.test.ts still pass; typecheck and lint clean.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
continuationParts: Part[] | undefined,
): Part[] {
const mergedParts = [...(previousParts ?? [])];
const nextParts = [...(continuationParts ?? [])];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] appendRecoveryContinuationParts 检查 continuationFirstPart(即 nextParts[0])是否为纯文本来决定是否去重。但 processStreamResponse[thoughtPart?, ...consolidatedHistoryParts] 的顺序构建 parts。当思维模型(如 gemini-2.5-pro)从 MAX_TOKENS 恢复时,续写的第一个 part 是 thought part(thought: true),isPlainTextPart 返回 false,整个去重代码块被跳过,重复文本泄漏到历史记录中。

Suggested change
const nextParts = [...(continuationParts ?? [])];
const prevTextIdx = findLastIndex(mergedParts, isPlainTextPart);
const contTextIdx = nextParts.findIndex(isPlainTextPart);
if (prevTextIdx >= 0 && contTextIdx >= 0) {
const suffix = getRecoveryContinuationSuffix(
mergedParts[prevTextIdx].text,
nextParts[contTextIdx].text,
);
if (suffix.length > 0) {
mergedParts[prevTextIdx].text += suffix;
}
nextParts.splice(contTextIdx, 1);
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed real bug — traced the part ordering in processStreamResponse (model turn parts = [thoughtContentPart?, ...consolidatedHistoryParts]), so for thinking models the first continuation part is the recovery turn's thought and the dedup block was being skipped wholesale.

Fixed in 08f2abd by scanning both sides for the plain-text anchor instead of locking onto the boundary indices, and splicing the matched text part instead of shifting the head. Added a regression test (should dedup recovery continuation when the continuation begins with a thought part) that mirrors the exact [thought, text] shape processStreamResponse produces.

const RECOVERY_CONTAINED_PREFIX_MIN_BYTES = 12;
// Limit the substring search to the immediate truncation tail so a coincidental
// match thousands of characters earlier in the previous turn cannot win.
const RECOVERY_CONTAINED_TAIL_LOOKBACK_CHARS = 400;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] 包含前缀去重路径存在多处缺口:(1) RECOVERY_CONTAINED_TAIL_LOOKBACK_CHARS = 400 vs OUTPUT_RECOVERY_TAIL_CHARS = 1200 — 模型在恢复提示中看到 1200 字符的尾部,但 findContainedRecoveryPrefixReplayLength 仅扫描最后 400 字符,导致 -800 到 -401 位置的重放 Markdown 块无法被去重。(2) startsWithMarkdownStructuralAnchor 剥离前导空白进行锚点检查,但前缀匹配循环使用未剥离的文本,导致带前导空白的续写匹配失败。(3) startsWithMarkdownStructuralAnchor 中 blockquote、fenced code、有序/无序列表锚点未被测试覆盖。

建议:(1) 将 RECOVERY_CONTAINED_TAIL_LOOKBACK_CHARS 对齐到 1200,(2) 在 findContainedRecoveryPrefixReplayLength 中使用剥离空白后的文本进行匹配,(3) 补充对应测试用例。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting the three sub-points:

(1) LOOKBACK=400 vs TAIL=1200 — deferring. The asymmetry is intentional, and it's actually load-bearing for the false-positive guard you praised in #discussion_r3230705438 ("the 400-char lookback + 12-byte floor make this very unlikely in practice"). The 1200-char tail is what the recovery prompt sends to the model so it can resume coherently; the 400-char lookback is what we scan for dedup. Widening the dedup window to 1200 would increase the surface for coincidental contained-prefix matches far above the truncation point — exactly the regression that #3 from the earlier review and should not strip prose that coincides with a far-earlier substring of the previous turn test were added to prevent. A replayed Markdown block 800 chars above the truncation tail is rare enough (most providers replay from the immediate tail) that I'd rather leave that duplicate in history than risk eating legitimate prose. Happy to revisit if you have a concrete production trace where the lookback was too narrow.

(2) anchor-trim asymmetry — fixed in 08f2abd. Real bug: startsWithMarkdownStructuralAnchor strips leading whitespace before the regex check, but findContainedRecoveryPrefixReplayLength then matched the un-trimmed continuationText.slice(0, length) against previousTail. A continuation like " ### Heading\nfoo" passed the anchor check but never found "### Heading\nfoo" in the previous tail. Now strips the same leading whitespace before the substring scan, and folds the stripped length back into the returned replayedLength so the caller's slice(replayedLength) invariant still holds. Regression test: should dedup a replayed structural prefix even when the continuation has leading whitespace.

(3) blockquote / fenced code / list anchor tests — deferring. Cheap to add but the structural-anchor regex itself is already covered by the existing table+heading replay test; the remaining branches are pure regex literals with no surrounding logic. Will add in a follow-up if you'd prefer explicit per-anchor coverage.

/**
* Lower floor for overlaps that contain Markdown structural characters
* (`#`, `|`, backtick, newline). Structural anchors are far less likely to
* collide coincidentally than prose — a 4-byte overlap like `"| a "` or

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] RECOVERY_OVERLAP_MIN_BYTES = 6 对英文合理(需 6 个 ASCII 字符才可能巧合匹配),但对中文不成立——CJK 字符每个 3 字节,6 字节仅等于 2 个汉字。两个汉字的巧合边界重合非常常见(如「我们」「但是」),可能导致中文对话中合法续写被误删。建议提高到 12 字节,或根据内容语言动态调整。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — byteLength uses Buffer.byteLength(text, 'utf8'), so the 6-byte floor genuinely admits a 2-character CJK overlap. Two-character boundary coincidences ("我们" / "但是" / "需要") are extremely common in Chinese prose.

Fixed in 08f2abd by adding a companion code-point floor (RECOVERY_OVERLAP_MIN_CHARS = 4) on top of the byte floor for the prose branch. Effect:

  • ASCII: still requires ≥6 chars (gated by the 6-byte floor; 4-byte ASCII never gets through).
  • CJK: now requires ≥4 characters (≥12 bytes), so 2-char and 3-char coincidences are rejected.
  • Structural anchors are intentionally exempt — the structural branch already governs them and structural collisions are far rarer than prose.

Chose the 4-codepoint floor rather than bumping MIN_BYTES to 12 because the latter would also reject useful 6–11-byte ASCII overlaps. Spreading via [...overlap] to count code points so emoji surrogate pairs don't double-count.

Regression test added (should preserve a coincidental 2-character CJK overlap (byte floor insufficient for CJK)).

/**
* Symmetric line-boundary check for the contained-prefix scan: returns true
* iff `prefix` occurs in `previousTail` starting at index 0 or immediately
* after a newline. The structural-anchor check on the continuation side only

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] 整个去重流程(getRecoveryContinuationSuffixfindContainedRecoveryPrefixReplayLengthappendRecoveryContinuationPartsbuildOutputRecoveryMessage)中没有任何 debugLogger 调用。如果去重在线上误删或漏删内容,oncall 无法追溯发生了什么。建议在内容被修改的关键点添加 debugLogger.info,至少记录被丢弃/修改的字符数。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferring to a follow-up. The observability gap is real, but adding structured logging to the dedup pipeline is a separable concern from the correctness fixes this PR is scoped to, and the right shape (call site, fields, log level, throttling) deserves its own design pass — especially because recovery is rare enough that we want every dedup event logged, not just "info" sampled. Will file a follow-up issue referencing this thread; happy to land it before any release where dedup behavior is user-visible if you'd prefer.

isSignificantRecoveryOverlap(overlap) &&
previousText.endsWith(overlap)
) {
return continuationText.slice(length);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] previousLastPart.text += suffix 原地修改 history 中 part 对象的 text 属性。虽然创建了新数组,但元素是共享引用。若未来有代码缓存 part 引用,将产生难以调试的不一致。建议创建新对象:mergedParts[mergedParts.length - 1] = { ...previousLastPart, text: previousLastPart.text + suffix }

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08f2abd. Replaced the in-place previousLastPart.text += suffix with a fresh-allocated part:

mergedParts[previousTextIndex] = {
  ...previousTextPart,
  text: previousTextPart.text + suffix,
};

Currently no downstream caller appears to cache part references across the recovery merge, but the mutation invariant was fragile and the allocation is essentially free, so worth fixing defensively. (The same change happens to be necessary for the [Critical] line-404 fix, since the new code splices into nextParts and we no longer hold a stable boundary index.)

// Delegate to the shared predicate used by normal history consolidation
// (see `isValidNonThoughtTextPart` below) so the recovery-merge path and
// the consolidated-history path agree on what counts as "plain text".
// Keeping the type predicate here gives callers `part.text: string`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] buildOutputRecoveryMessage 通过 getPlainTextFromParts 拼接所有文本 part 来构建「精确后缀」,但若模型轮次有多个被非文本 part 分隔的文本 part,拼接结果与实际末尾不匹配。建议至少用 debugLogger 记录差异,或仅使用最后一个非 thought 文本 part 的尾部。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferring with reasoning. Traced when this edge case actually fires:

  • processStreamResponse consolidates adjacent text parts (lastPart.text += part.text), so the only way a model turn ends up with multiple non-adjacent text parts is if structure interleaves text → non-text → text within a single response (e.g. text → functionCall → text).
  • The recovery code path only runs when MAX_TOKENS truncated output. Empirically, model turns that hit MAX_TOKENS while emitting interleaved text+tool-calls are uncommon: most truncation events happen in long prose/table generation, where the parts collapse to a single consolidated text run.
  • When the interleaved case does occur, the current concatenation produces a slightly misleading <previous_response_suffix> (joins disjoint text runs without a separator). That weakens the "exact suffix" instruction but doesn't corrupt the dedup logic, because appendRecoveryContinuationParts operates on the live text part, not on getPlainTextFromParts's output.

I'd rather not switch to "last non-thought text part only" in this PR because that also misses recovery context in the more common case (single consolidated text part with leading non-thought parts above it). The right fix is probably to render parts as a structured block in the recovery prompt, which is scope creep for this PR. Will file a follow-up issue referencing this thread.

`appendRecoveryContinuationParts` previously only inspected the boundary
parts (last of previous, first of continuation). `processStreamResponse`
orders parts as `[thoughtPart?, ...consolidatedHistoryParts]`, so for
thinking models the first continuation part is the recovery turn's
thought — the plain-text predicate failed on it and the entire dedup
block was skipped, leaking the replayed overlap into durable history.
Now scan both sides for the plain-text anchor and splice the matched
text part rather than shifting the head. Allocate a fresh merged part
instead of mutating `mergedParts[i].text` in place so callers caching
part references never observe a half-merged turn.

Two additional hardening fixes on the overlap path:

- `isSignificantRecoveryOverlap` adds a 4-code-point floor on top of
  the 6-byte floor for prose. CJK characters are 3 UTF-8 bytes each,
  so the byte-only floor admitted 2-character coincidences like
  "我们" / "但是" that recur constantly across unrelated Chinese
  turns. The structural-anchor branch is exempted (those collisions
  are far rarer and the structural floor already governs them).
- `findContainedRecoveryPrefixReplayLength` now strips leading
  whitespace from the continuation before matching. The structural-
  anchor check already tolerated leading spaces/tabs (some providers
  re-emit replayed blocks with extra indentation), but the substring
  scan still used the un-trimmed prefix and silently failed to match
  the corresponding `previousTail` occurrence.

Adds three regression tests covering: a thinking-model recovery
continuation whose first part is a thought, a 2-CJK-character
coincidence that must NOT be dedup'd, and a leading-whitespace
structural replay that must be dedup'd.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] previousTailContainsAtLineBoundary rejection path (match found mid-paragraph, not at line boundary) is untested. Only the accept path is exercised by existing tests. If the line-boundary check regresses to a plain substring match, legitimate continuation text would be silently dropped with no test failure. Suggested fix: add a test where a structural-anchor prefix appears mid-paragraph in previousTail (e.g., "some text### Heading newline content"), asserting the contained-prefix match is rejected and continuation is preserved verbatim.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

previousText: string,
continuationText: string,
): string {
if (previousText.length === 0 || continuationText.length === 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The continuationText.length === 0 sub-path of the empty-input guard in getRecoveryContinuationSuffix is untested. The previousText.length === 0 path is covered (via prompt-recovery-thought-only), but a scenario where the continuation text part is empty (e.g., thought-only continuation) would exercise the || short-circuit. If this guard regresses, empty continuation text would flow into the endsWith and scan logic.

Consider adding a test where the continuation turn has only thought parts (no text parts), asserting continuationText passes through verbatim.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@chiga0 chiga0 May 14, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked at this carefully — the continuationText.length === 0 sub-path is defensive-only and unreachable from the production caller, so a "thought-only continuation" test would actually skip the call entirely rather than exercise the guard. Tracing the flow:

appendRecoveryContinuationParts only invokes getRecoveryContinuationSuffix when both findLastPlainTextPartIndex(mergedParts) >= 0 and nextParts.findIndex(isPlainTextPart) >= 0. A thought-only continuation would have continuationTextIndex < 0, the if (… && continuationTextIndex >= 0) block would be skipped, and getRecoveryContinuationSuffix would never run — the empty-input guard inside it is never hit from production. The same applies to previousText.length === 0 (the round-6 reply pointing at prompt-recovery-thought-only was technically imprecise: that test covers buildOutputRecoveryMessage's previousText.trim().length === 0 branch, not the guard inside getRecoveryContinuationSuffix).

Rather than add a synthetic test that exports an internal helper just to hit defensive-only code, I documented the intent in 556b015. The new JSDoc on getRecoveryContinuationSuffix explicitly calls out that the empty-input guard is defensive only — kept in place so a future refactor or direct unit-test caller bypassing the production filter cannot crash, but not reachable from the live recovery pipeline. If you'd still prefer an export + direct unit test for paranoia, happy to add it — but the JSDoc makes the contract explicit either way.

// duplicated Markdown tables/prose even if the live UI suppresses them.
const containedPrefixLength = findContainedRecoveryPrefixReplayLength(
previousText,
continuationText,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The newline-normalization path in getRecoveryContinuationSuffix (when replayedPrefix ends with newline, previousText does not, and suffix does not start with newline) is untested. Both existing contained-prefix tests use prefixes that do NOT end with a newline, so the newline-prepend block is never entered. If the three-condition normalization breaks, no test catches it.

Consider adding a test: previousText ends without newline (e.g., ### Section), continuation starts with ### Section followed by newline and more text, asserting coalesced text has no duplicate anchor and the newline separator is preserved.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a regression test in 556b015. The new prompt-recovery-newline-normalization test exercises exactly the three-condition branch: previous is 'Intro paragraph.\n### Section' (no trailing newline), continuation is '### Section\nbody prose continuation' so replayedPrefix ends with \n, previousText does NOT, and suffix does NOT start with \n. The assertion text === '${previous}\nbody prose continuation' would fail (missing separator → '### Sectionbody prose continuation') if the normalization branch regressed.

continuationParts: Part[] | undefined,
): Part[] {
const mergedParts = [...(previousParts ?? [])];
const nextParts = [...(continuationParts ?? [])];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] appendRecoveryContinuationParts has an implicit, undocumented dependency on processStreamResponse's text-part consolidation (lastPart.text += part.text). It assumes the last plain-text part in history contains ALL text output from that turn. If someone refactors processStreamResponse to remove in-place consolidation (e.g., for immutability), the dedup logic would silently break, comparing continuation against only the last unconsolidated text fragment and missing real overlaps. No compile-time or runtime error would flag this.

Consider adding a prominent comment above appendRecoveryContinuationParts documenting this coupling, and an integration test that verifies dedup works even when history contains multiple small adjacent text parts (bypassing consolidation).

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — documented in 556b015. Added a JSDoc block above appendRecoveryContinuationParts titled Coupling with processStreamResponse that explicitly states the function assumes its parts arrays came from processStreamResponse's in-place text-part consolidation (lastPart.text += part.text), notes that only the last plain-text part of previousParts and the first plain-text part of continuationParts are inspected, and warns that any refactor emitting multiple adjacent unconsolidated text parts would silently miss real overlaps. The doc-block closes by noting both functions live in this file precisely so the coupling is reviewable in a single window.

Re: the integration test for multiple adjacent unconsolidated text parts — deferring. Such an array shape is unreachable from the live recovery pipeline (every callsite of appendRecoveryContinuationParts is coalesceRecoveryPairs, which feeds parts produced by processStreamResponse), so the test would need to bypass the recovery loop entirely and call appendRecoveryContinuationParts directly. Exporting an internal helper purely to document a "what if processStreamResponse changes" invariant adds API surface for a hypothetical regression; the JSDoc is the load-bearing signal here. Happy to revisit if the consolidation logic ever moves toward immutability.

const mergedParts = [...(previousParts ?? [])];
const nextParts = [...(continuationParts ?? [])];

// `processStreamResponse` orders parts as

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] appendRecoveryContinuationParts return value structure convention is undocumented. coalesceRecoveryPairs reuses the merged result as continuationParts input for subsequent recovery iterations, relying on the invariant that the returned parts array has the same shape as processStreamResponse output ([thoughtPart?, ...textParts, ...nonTextParts]). If the return shape changes, multi-iteration recovery would fail silently.

Consider documenting this convention in the function's JSDoc.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented in 556b015. The new JSDoc on appendRecoveryContinuationParts includes a Return-value shape section that explicitly states the returned array preserves processStreamResponse's output convention ([thoughtPart?, ...consolidatedTextParts, ...nonTextParts]) and that coalesceRecoveryPairs relies on this by feeding the merged result back as previousParts on the next recovery iteration. The block also calls out that multi-iteration recovery dedup would fail silently against the wrong part if the shape ever diverged.

Add JSDoc to getRecoveryContinuationSuffix calling out that its empty-input
guard is defensive-only (the production caller already filters both sides),
and document appendRecoveryContinuationParts' implicit coupling with
processStreamResponse's text-part consolidation plus its return-shape
convention that coalesceRecoveryPairs relies on for multi-iteration recovery.

Add two regression tests:
- mid-paragraph match rejection: a structural anchor that appears in the
  previous tail but is not preceded by a newline must NOT trigger the
  contained-prefix strip, so legitimate continuation survives verbatim.
- newline-normalization branch: when the replayed prefix ends with \n but
  the previous tail does not and the suffix does not start with \n, the
  helper must insert a separator so the coalesced text keeps its block
  boundary.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0

chiga0 commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author

Re: review-body suggestion on the previousTailContainsAtLineBoundary rejection path — added a regression test in 556b015.

The new prompt-recovery-line-boundary-reject test follows your suggested shape exactly: it constructs a previousTail of 'some text ### Heading and then more inline prose follows' where ### Heading appears mid-paragraph (preceded by ' ', not '\n'), and asserts the continuation '### Heading\nfresh continuation that should not be stripped' survives verbatim. The structural-anchor check on the continuation side still passes (it starts with ### ), so the test forces the line-boundary helper to do the rejection work — if it ever regressed to a plain previousTail.includes(prefix), the mid-paragraph occurrence would win and the continuation would be silently dropped, failing the assertion.

(Posted the inline replies on the 4 line-level threads separately.)

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues found in this incremental update. The JSDoc additions for getRecoveryContinuationSuffix and appendRecoveryContinuationParts are well-written and accurately document the defensive guards, coupling contracts, and return-value conventions. The two new test cases (line-boundary-reject and newline-normalization) cleanly cover the remaining untested branches identified in prior reviews.

All 73 tests pass, tsc and eslint are clean. LGTM! ✅

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The prior round's prose-loss critical is solidly closed via the three-axis tightening (structural-anchor gate + 12-byte floor + 400-char lookback + line-boundary check) — every false-positive shape from last round now passes through verbatim. @wenshao's 20 inline comments are largely addressed; on the ones that didn't apply, the pushback was correct (the fenced-code regex does match ```python, ```\n, and bare forms). One new correctness item below, plus two low-severity follow-ups (W5's 400-vs-1200 lookback asymmetry is still addressed in docs only; the closing-fence arm of the structural-anchor check is slightly over-broad).

1. Thought ordering is corrupted when a thinking-model continuation is [thought, text] (severity: medium · confidence: high)

The fix scans past the leading thought to find the text part, splices that text out, and appends the remaining continuation parts after the merged previous turn. The leftover thought ends up at the end: durable history becomes [..., previousText + suffix, recoveryThought], with the thought trailing the visible text it conceptually generated. Thinking-model providers (Gemini 2.5+, Anthropic, OpenAI o-series) validate thought-signature provenance and expect thoughts to precede their content. The existing thought-first regression test pins the joined non-thought text but not structural position, so this isn't caught. Fix shape: keep the continuation's pre-text non-text parts with the folded suffix (thought ends up before merged text), or drop them.

Verdict

COMMENT — issue 1 is the only must-address before merge.

wenshao
wenshao previously approved these changes May 14, 2026
const trimmed = text.replace(/^\s+/, '');
return /^(\|[^\n]*\||#{1,6} |```|>\s|[-*+] |\d+\. )/.test(trimmed);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] startsWithMarkdownStructuralAnchor table-row regex /^\|[^\n]*\|/ matches any line starting with | containing another | — no column-count or whitespace requirement. This contradicts the JSDoc claim that "incidental | characters in prose do not count." Prose like |expression| more text (math/technical writing) passes the anchor gate.

Impact: If continuation starts with a pipe-containing prose line and that same text appears at a line boundary in the last 400 chars of the previous response, the contained-prefix path would silently strip it. Mitigated by the 12-byte minimum but not eliminated.

Suggested change
// Tighten: require ≥3 pipes (minimum GFM table row) or a separator-row pattern
return /^(\|[^\n]*\|[^\n]*\||\|[\s\-:]+\||#{1,6} |```|>\s|[-*+] |\d+\. )/.test(trimmed);

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 25ea94e.

Tightened the table-row alternation to your suggested form: \|[^\n]*\|[^\n]*\| (≥3 pipes = real GFM row with ≥2 cells) OR \|[\s\-:]+\| (separator row). The JSDoc now explicitly documents why a bare |expression| is rejected.

Added a regression test (prompt-recovery-single-cell-pipe-prose) that constructs the exact scenario you described: continuation starts with |expression| evaluates to a scalar value. and that fragment appears at a line boundary mid-tail of the previous response (so the suffix-anchored scan doesn't catch it, only the contained-prefix path could fire). Confirmed by temporarily reverting the regex — the test fails with a silent strip of |expression| evaluates to a scalar value. , and passes once the regex is tightened.

return Buffer.byteLength(text, 'utf8');
}

function isSignificantRecoveryOverlap(overlap: string): boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] isSignificantRecoveryOverlap character class [#|\\n]matches#, `` ``, |, or `\n` anywhere in the overlap string. This means prose fragments like `"C# programming"` (15 bytes), `"tag #xyz"` (9 bytes), or `"see `backtick`"` (21 bytes) qualify for the lowered 4-byte structural floor instead of the 6-byte prose floor.

Impact: Minimal — the gap is only 2 bytes and coincidence at truncation boundaries is rare. This is primarily a classification-accuracy concern: the implementation is looser than the JSDoc's "structural anchors" implies.

Suggested fix: Scope to Markdown contexts (e.g., /(^|\n)#{1,6}\s/ for headings) or add a comment acknowledging the intentional over-classification and its negligible risk.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented in 25ea94e (no behavior change).

Taking your "add a comment acknowledging the intentional over-classification" path rather than scoping to Markdown contexts. Reasoning: the 4-byte structural floor vs the 6-byte prose floor only differs by a 2-byte window. Realistic prose at 4–5 bytes containing #/|/`/\n (e.g. "C#dev", "a|b|c") qualifying structurally requires that exact fragment to be identical at the truncation boundary on both sides — extraordinarily rare in practice, and the prose floor (6 bytes + 4 chars) catches anything longer. Tightening the char-class would add code complexity without changing real-world recovery outcomes.

The new inline comment makes the trade-off explicit so future readers don't have to reverse-engineer the intent.

Tightens `startsWithMarkdownStructuralAnchor`'s table-row alternation so a
bare `|expression|` (2 pipes) in technical prose no longer qualifies as a
Markdown block anchor — real GFM table rows have ≥3 pipes (≥2 cells) or a
separator row like `|---|`. Without this, prose continuation starting with
a 2-pipe expression that re-appears at a line boundary mid-tail of the
previous response would be silently stripped by the contained-prefix path,
contradicting the JSDoc's stated invariant that "incidental `|` characters
in prose do not count."

Also adds an inline comment to `isSignificantRecoveryOverlap` documenting
why the structural-class detection (`[#|`\n]`) is intentionally loose —
the 2-byte gap between the 4-byte structural floor and the 6-byte prose
floor only matters for 4–5 byte fragments that coincide on both sides of
a truncation boundary, which is far rarer than the structural-replay
scenarios the lower floor exists to catch.

Adds a regression test asserting that a continuation opening with
`|expression| ...` is left intact even when it matches at a line boundary
in the previous tail.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed with one non-blocking suggestion. Deterministic checks reported no changed-file findings, CI is passing, and the targeted packages/core geminiChat.test.ts run passed (74/74).

— gpt-5.5 via Qwen Code /review

* pattern is not a valid GFM table row anyway.
*/
function startsWithMarkdownStructuralAnchor(text: string): boolean {
const trimmed = text.replace(/^\s+/, '');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This tightened table-row anchor now excludes valid one-column GFM tables. A row like | Name | has only two pipes, so it fails this branch, while the following separator row | --- | is accepted only if the replay starts there. If a provider resumes by replaying the header of a one-column table, the contained-prefix dedup path will skip it and the header/table can still be duplicated in durable history. Consider treating a two-pipe row as structural when it is immediately followed by a valid separator row, so bare prose like |expression| remains rejected but | Name |\n| --- | still dedups.

— gpt-5.5 via Qwen Code /review

秦奇 and others added 2 commits May 15, 2026 17:59
Adds a regression test for @tanzhenxin's review comment: the existing
`prompt-recovery-thinking-continuation` test only asserts joined non-
thought text, so a regression where the recovery turn's leading thought
ends up *after* the merged text part slips through. The new test
explicitly asserts `thoughtIdx < mergedTextIdx` in the final history
entry.

Thinking-model providers (Gemini 2.5+, Anthropic, OpenAI o-series)
validate thought-signature provenance and expect a thought to precede
the content it generated; without an ordering assertion the dedup path
could silently violate that invariant.

The new test fails on the current implementation
(`appendRecoveryContinuationParts` appends the leftover leading thought
at the end of the part list). Fix follows in a separate commit so the
red → green transition is reviewable.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The recovery dedup path in `appendRecoveryContinuationParts` previously
spliced only the matched continuation text part out of `nextParts` and
appended the leftover parts (including any leading thought) after the
merged text. For thinking-model providers (Gemini 2.5+, Anthropic,
OpenAI o-series) that validate thought-signature provenance, this
violated the invariant that a thought precedes the content it
generated: durable history ended up as `[..., previousText + suffix,
recoveryThought]`, with the recovery turn's thought trailing its own
text.

Hoist any non-text parts that preceded the matched text on the
continuation side (typically the recovery turn's thought) into
`mergedParts` directly before the merged text part. Trailing non-text
parts (tool calls etc.) keep their position via the final concat.
Existing `prompt-recovery-thinking-continuation` test still passes
because it only asserts joined non-thought text; the new
`...-order` test now passes as well.

Reported by @tanzhenxin in PR review on commit 556b015.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0

chiga0 commented May 15, 2026

Copy link
Copy Markdown
Collaborator Author

@tanzhenxin Re: Issue 1 (thought-ordering on [thought, text] continuation) — addressed in a916c6886, with a failing regression test landed first in 347f6abf9 so the red→green transition is reviewable in isolation.

Repro confirmed at byte level: with the previous head (25ea94e1), appendRecoveryContinuationParts only spliced the matched continuation text part out of nextParts, so the leading thought trailed behind the merged text in the final concat — durable history became [..., previousText + suffix, recoveryThought], exactly the shape you flagged. The new prompt-recovery-thinking-continuation-order test asserts thoughtIdx < mergedTextIdx on the last history entry, and fails on 25ea94e1 with expected 1 to be less than 0.

Fix: went with your option (1) — hoist the continuation's pre-text non-text parts into mergedParts directly before the merged text part rather than dropping them. Two-line change in the splice block: collect the leading slice, drop the matched text, splice the leading parts in at previousTextIndex. Trailing non-text parts (tool calls etc.) keep their post-merged-text position via the final [...mergedParts, ...nextParts] concat, so the shape convention [thoughtPart?, ...consolidatedTextParts, ...nonTextParts] documented on the function is preserved.

Existing tests: all 75 in geminiChat.test.ts pass. The sibling prompt-recovery-thinking-continuation test (which only pins joined non-thought text) is unchanged and still passes; the new -order companion is what enforces the structural invariant going forward.

Re: the two low-severity follow-ups you mentioned (W5's 400-vs-1200 lookback asymmetry doc-only response, and the closing-fence arm being slightly over-broad) — happy to take either in this PR if you'd like; my read is they're separable. Let me know.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incremental review of 25ea94e1..a916c6886: the appendRecoveryContinuationParts hoisting fix correctly repositions the recovery turn's leading non-text parts (thoughts) before the merged text, resolving the thought-signature provenance issue for thinking-model providers. The new test should keep the recovery thought before the merged text part explicitly validates structural ordering. All 75 tests pass, tsc/eslint clean. One Nice to have micro-optimization noted (double array shift could be merged into single splice) but not blocking. LGTM! ✅ — DeepSeek/deepseek-v4-pro via Qwen Code /review

@chiga0 chiga0 enabled auto-merge (squash) May 18, 2026 11:30
@chiga0 chiga0 requested a review from tanzhenxin May 18, 2026 11:30
@wenshao

wenshao commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Maintainer test report — PR #3966

Built and validated locally in a dedicated tmux session (pr3966) on the pr-3966 fetch branch (head a916c68, 11 commits ahead of merge-base 2d222c4). Posting as a merge reference.

This PR has gone through multiple rounds of review with extensive feedback on the dedup logic's false-positive risk. The test focus below is deliberately weighted toward verifying that the latest revision actually closes those concerns, not just that the happy paths still work.

Environment

  • macOS 26.4.1 (arm64)
  • Node v22.17.0 / npm 11.8.0
  • Branch: pull/3966/head → local pr-3966
  • Worktree: /Users/wenshao/git/qwen-code-x6
  • Scope verified vs main: 2 files, +1251/-6 (matches PR metadata; geminiChat.ts +448/-5, geminiChat.test.ts +803/-1)

Results

Stage Result Notes
npm install PASS no PR-driven dep changes
npm run build (workspaces) PASS clean
Bundle (esbuild) PASS all dedup helpers + constants ship in dist/cli.js — verified by string count, see below
Focused geminiChat.test.ts 75/75 pass matches the count from the most recent reviews
Full packages/core vitest 7246 passed / 3 skipped / 0 failed across 273 files fully green; this PR's merge-base predates the unrelated treats unset baseURL Anthropic UA test failure I've reported on other PRs, so the suite is clean here
packages/core typecheck PASS
Lint on the 2 PR-touched files PASS zero issues
Targeted false-positive guard verification 11/11 pass the specific tests that pin each reviewer concern, see breakdown below

Targeted verification — every prior review concern has a passing test

Tanzhenxin's Round-1 review flagged the contained-prefix fallback as silently dropping legitimate continuation text on common short phrases. I re-ran the tests that exist because of that critique, plus the Round-2 thought-ordering critical:

✓ should coalesce overlapping recovery continuation text                                          (positive: PR's primary fix)
✓ should coalesce recovery text that replays a previous tail anchor                               (positive: Markdown-table replay)
✓ should preserve prose continuation that coincidentally repeats an opener phrase                 (R1 critical — false-positive guard)
✓ should not strip prose that coincides with a far-earlier substring of the previous turn         (R1 critical — far-substring guard)
✓ should preserve continuation when its structural prefix appears mid-paragraph in the previous tail
                                                                                                  (R1 — line-boundary rejection)
✓ should preserve prose continuation that opens with a single-cell pipe expression matching mid-tail
                                                                                                  (R1 — table-row regex false-positive)
✓ should keep the recovery thought before the merged text part (thought-signature provenance)     (R2 critical — thought ordering)
✓ should truncate the previous_response_suffix to the trailing 1200 chars when the previous turn is longer
                                                                                                  (bound check)
✓ should neutralize a literal previous_response_suffix delimiter inside the tail                  (delimiter injection)
✓ should neutralize a literal opening previous_response_suffix delimiter inside the tail          (delimiter injection)
✓ should coalesce successful recovery iterations into the preceding model turn                    (multi-iteration coalesce)

Every shape from tanzhenxin's "prose loss" table (the In summary, …, Here is the breakdown, In conclusion, …, mid-paragraph match, single-cell pipe expression scenarios) has a dedicated regression test that asserts the continuation is preserved verbatim. The Round-2 thought-ordering critical is pinned by should keep the recovery thought before the merged text part.

Bundle proof — dedup pipeline is shipped

In dist/cli.js (24 MB, single-file bundle on this base), occurrence counts of the new symbols/constants:

Symbol Count
getRecoveryContinuationSuffix 4
findContainedRecoveryPrefixReplayLength 4
appendRecoveryContinuationParts 4
coalesceRecoveryPairs 2
isSignificantRecoveryOverlap 5
startsWithMarkdownStructuralAnchor 4
previousTailContainsAtLineBoundary 4
buildOutputRecoveryMessage 4
sanitizeRecoverySuffixTail 4
RECOVERY_CONTAINED_PREFIX_MIN_BYTES 3
RECOVERY_OVERLAP_MAX_SCAN_CHARS 4
OUTPUT_RECOVERY_TAIL_CHARS 4

All non-zero → none was dead-code-eliminated; the layered defense (suffix-anchored scan → contained-prefix fallback gated by structural anchor + line boundary + 12-byte floor + 400-char lookback) is actually compiled into the binary.

Risk assessment

Low-medium risk. The behavioral surface is large and the failure mode of an over-eager dedup (silent text loss) is harder for users to notice than the duplication it replaces — that's the reason this PR went through 11 commits and ~5 review rounds. The final revision is conservative:

  • Three independent anchors must all agree before the contained-prefix fallback strips anything: (a) ≥12 bytes, (b) starts with a Markdown structural anchor (# , table row, fenced code, blockquote, ordered/unordered list), (c) the match must sit at a line boundary in the previous tail's 400-char lookback.
  • The suffix-overlap path uses endsWith anchoring, which is much tighter than substring-anywhere.
  • The recovery prompt now carries the previous 1,200 chars so the model has stopping-point context, reducing the need to dedup at all in the steady state.
  • The thought-ordering fix (a916c68) repositions the recovery turn's leading thought parts before the merged text, preserving thought-signature provenance for thinking-model providers (Gemini 2.5+, Anthropic, OpenAI o-series).

Mergeability status

GitHub reports mergeable_state: blocked while mergeable: MERGEABLE. Looking at the review history, this appears to be because the originally-CHANGES_REQUESTED reviews predate the addressing commits and were never formally re-approved on this final head (a916c68). The two /review runs against this final head are both APPROVED. The mechanical block is workflow, not code.

Not covered locally

  • No live multi-provider integration run (no credentials for triggering a real MAX_TOKENS mid-stream from MiMo/DeepSeek/OpenAI in this environment). The PR's dedup logic is provider-agnostic and exercised in unit tests across the documented continuation shapes; the production wire path has been carefully isolated behind pure helpers that the test suite covers exhaustively.
  • The coalesceRecoveryPairs "missing debugLogger.warn on shape mismatch" stylistic note from my earlier review is unaddressed but explicitly non-blocking.

Reproduce

git fetch origin pull/3966/head:pr-3966 && git checkout pr-3966
npm install
cd packages/core
npx vitest run src/core/geminiChat.test.ts --no-coverage                       # 75/75
npx vitest run src/core/geminiChat.test.ts \
  -t "prose|line-boundary|coalesce|opener|far-earlier|thought before|single-cell pipe|previous_response_suffix" \
  --no-coverage --reporter=verbose                                              # 11 targeted guard tests
npm run typecheck
cd ../.. && npx eslint packages/core/src/core/geminiChat.ts \
                       packages/core/src/core/geminiChat.test.ts

Recommendation: safe to merge once a maintainer (re-)dismisses the stale CHANGES_REQUESTED review. The architecture is right (pure helpers + bounded windows + multi-axis anchoring), the false-positive surface that the review pushed back on is now explicitly pinned by regression tests, and the binary contains the fix.

@LaZzyMan LaZzyMan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

This addresses a real, reproducible bug, and the layered defenses — structural-anchor gating on the contained-prefix path, dual byte/codepoint floors that handle CJK, the line-boundary check, the <previous_response_suffix> prompt, and the thought-signature ordering fix — are genuinely careful engineering. The 27+ regression tests pin the edges down precisely. This should merge.

I do want to flag a forward-looking architectural concern, since I think it's worth raising even though it doesn't block this PR. Almost all of the complexity here exists because coalesceRecoveryPairs collapses [modelTruncated, userRecovery, modelContinuation] into a single merged assistant turn. Once you decide to physically concatenate the two assistant parts in history, any replay the provider emits becomes real duplication inside the stored text — and the only fix is to model what each provider does at the boundary. That makes geminiChat.ts the place where provider-quirk knowledge has to live, and the surface is open-ended (DeepSeek's table-header replay today, the next provider's pattern tomorrow). Each new pattern adds a branch and a tuned constant; nothing ever gets to come back out.

claude-code takes a different shape: it keeps the three messages separate and flags the recovery user turn as isMeta, letting the chat-recording, compaction, export, and rewind layers each filter meta turns for their own concerns. The UI layer renders the continuation seamlessly via the existing buffer-keep logic. geminiChat then doesn't need any provider-specific knowledge of replay shapes at all — the duplication stops being a problem because it's no longer concatenated. From a quick read of the qwen-code codebase, that refactor looks like ~80 lines spread across the layers that already own each concern, and the dedup helpers introduced here could come back out over time.

This PR is the right fix under the current architecture and it shouldn't wait on a redesign. If you're open to it, I'm happy to file a tracking issue with a concrete sketch so we can discuss whether the merging assumption is worth revisiting separately.

Verdict

APPROVE — fix is correct and well-tested; the architectural note is for a follow-up discussion, not a blocker.

@chiga0 chiga0 dismissed tanzhenxin’s stale review May 21, 2026 06:25

already fixed the issue, and two reviewer has approved the pr, I will dismiss the and merge the pr

@chiga0 chiga0 merged commit 80895d5 into main May 21, 2026
10 checks passed
@chiga0 chiga0 deleted the fix/geminichat-stream-recovery-dedup branch May 21, 2026 06:25
DragonnZhang pushed a commit that referenced this pull request May 21, 2026
* fix(core): deduplicate geminiChat recovery continuation text

When a provider hits MAX_TOKENS and the model resumes via the recovery
loop, the continuation stream sometimes re-sends characters from the end
of the previous response as a context anchor. Without deduplication this
causes repeated Markdown tables/prose in the final history even if the
live UI suppresses them.

Add getRecoveryContinuationSuffix / findContainedRecoveryPrefixReplayLength
to strip the replayed prefix before appending the continuation parts.
Also include the last 1200 chars of the previous response in the recovery
prompt so the model can see where it left off.

Two new tests cover:
- exact suffix overlap (shared recovery suffix and continuation)
- contained tail anchor replay (Markdown table prefix replayed mid-text)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): tighten contained-prefix recovery dedup to avoid prose loss

Address review feedback on PR #3966: the contained-prefix fallback in
geminiChat recovery dedup was too permissive — a 6-byte minimum plus a
4000-char lookahead window allowed common opener phrases ("In summary,",
"In conclusion,", "Here is the …") to silently strip legitimate
continuation text whenever they happened to coincide with any substring
in the previous turn. Silent loss is a worse failure mode than the
duplication we were fixing.

Constrain the fallback to its real intended use case — replayed
Markdown blocks that providers re-emit at the start of a recovery
continuation (table headers, headings, fenced code, lists, blockquotes):

- Require the continuation to *open* with a Markdown structural anchor
  before considering any contained-prefix replay; plain prose openers
  fall through with no dedup attempted.
- Restrict the substring search to the immediate truncation tail
  (last 400 chars) so a coincidental match far above the truncation
  point cannot win.
- Raise the contained-prefix byte floor (12 bytes) above the suffix-
  overlap floor.

Also add coverage for the previously-untested guard branches
(empty input, full-overlap drop, empty previous-text path that skips
the <previous_response_suffix> block) and regression tests for the
prose-loss scenarios called out in review.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): handle leading whitespace in structural anchor + cover tail truncation

Address wenshao review on PR #3966:

- `startsWithMarkdownStructuralAnchor` now strips all leading whitespace
  (`/^\s+/`) instead of only newlines (`/^\n+/`). Some providers re-emit
  a recovered Markdown block with leading spaces or tabs, not just
  newlines; the old regex caused the structural-anchor gate to fail and
  the contained-prefix dedup path was silently skipped.
- Add a regression test for `buildOutputRecoveryMessage` that exercises
  the `previousText.slice(-OUTPUT_RECOVERY_TAIL_CHARS)` truncation
  branch with a 1300-char previous response, asserting the
  <previous_response_suffix> block contains exactly the trailing 1200
  chars and that the dropped head does not leak.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): unify plain-text predicate and harden recovery delimiter

Address two review concerns on geminiChat output-recovery:

- `isPlainTextPart` was a near-duplicate of `isValidNonThoughtTextPart`
  with subtly weaker guards (missing thoughtSignature/inlineData/fileData
  and using `!== true` vs `!part.thought`). Delegate to the shared
  predicate so the recovery-merge and consolidated-history paths agree
  on what counts as plain text.
- `buildOutputRecoveryMessage` embedded the previous response inside a
  `<previous_response_suffix>` pseudo-XML block without sanitization. If
  the model's own truncated output contained the literal closing tag
  (e.g. while generating XML/HTML examples), the recovery prompt's
  structure would break. Neutralize literal opening/closing delimiters
  inside the tail with a zero-width space so the prompt always has
  exactly one well-formed block; add a regression test that asserts the
  delimiter pair count stays at 1/1 even when the tail contains a raw
  `</previous_response_suffix>`.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): cover opening-tag branch of sanitizeRecoverySuffixTail

The existing prompt-recovery-delimiter-collision test only exercises
the closing-tag (`</previous_response_suffix>`) neutralization path.
Add a sibling test that emits a literal opening tag in the previous
model turn so the opening-tag replace branch is also covered. Asserts
exactly one opening/closing delimiter pair in the recovery message and
that the neutralized variant (with zero-width space) appears in the
embedded tail.

* docs(core): document recovery-dedup constants and tighten contained-prefix anchor

Address PR #3966 review polish items from wenshao:
- Add JSDoc rationale to each magic constant (OUTPUT_RECOVERY_TAIL_CHARS,
  RECOVERY_OVERLAP_MAX_SCAN_CHARS, RECOVERY_OVERLAP_MIN_BYTES,
  RECOVERY_STRUCTURAL_OVERLAP_MIN_BYTES) so future tuning is grounded.
- Make the contained-prefix scan symmetric: require the match inside
  previousTail to begin at index 0 or immediately after a newline, mirroring
  the structural-anchor check on the continuation side. All occurrences are
  walked so a benign mid-paragraph hit doesn't shadow a real line-anchored
  match later in the 400-char tail window.
- Document the suffix-anchored overlap loop's O(n^2) bound and the bounded
  scan cap so the perf characteristic is explicit rather than reverse-
  engineered.
- Explain why appendRecoveryContinuationParts always shifts the first
  continuation text part even when the dedup suffix is empty (empty suffix
  means a pure replay that must be discarded).

All 68 tests in geminiChat.test.ts still pass; typecheck and lint clean.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): scan recovery parts for plain-text + CJK-safe overlap floor

`appendRecoveryContinuationParts` previously only inspected the boundary
parts (last of previous, first of continuation). `processStreamResponse`
orders parts as `[thoughtPart?, ...consolidatedHistoryParts]`, so for
thinking models the first continuation part is the recovery turn's
thought — the plain-text predicate failed on it and the entire dedup
block was skipped, leaking the replayed overlap into durable history.
Now scan both sides for the plain-text anchor and splice the matched
text part rather than shifting the head. Allocate a fresh merged part
instead of mutating `mergedParts[i].text` in place so callers caching
part references never observe a half-merged turn.

Two additional hardening fixes on the overlap path:

- `isSignificantRecoveryOverlap` adds a 4-code-point floor on top of
  the 6-byte floor for prose. CJK characters are 3 UTF-8 bytes each,
  so the byte-only floor admitted 2-character coincidences like
  "我们" / "但是" that recur constantly across unrelated Chinese
  turns. The structural-anchor branch is exempted (those collisions
  are far rarer and the structural floor already governs them).
- `findContainedRecoveryPrefixReplayLength` now strips leading
  whitespace from the continuation before matching. The structural-
  anchor check already tolerated leading spaces/tabs (some providers
  re-emit replayed blocks with extra indentation), but the substring
  scan still used the un-trimmed prefix and silently failed to match
  the corresponding `previousTail` occurrence.

Adds three regression tests covering: a thinking-model recovery
continuation whose first part is a thought, a 2-CJK-character
coincidence that must NOT be dedup'd, and a leading-whitespace
structural replay that must be dedup'd.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(core): cover recovery-dedup line-boundary + normalization branches

Add JSDoc to getRecoveryContinuationSuffix calling out that its empty-input
guard is defensive-only (the production caller already filters both sides),
and document appendRecoveryContinuationParts' implicit coupling with
processStreamResponse's text-part consolidation plus its return-shape
convention that coalesceRecoveryPairs relies on for multi-iteration recovery.

Add two regression tests:
- mid-paragraph match rejection: a structural anchor that appears in the
  previous tail but is not preceded by a newline must NOT trigger the
  contained-prefix strip, so legitimate continuation survives verbatim.
- newline-normalization branch: when the replayed prefix ends with \n but
  the previous tail does not and the suffix does not start with \n, the
  helper must insert a separator so the coalesced text keeps its block
  boundary.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): tighten table-row anchor + document structural-class scope

Tightens `startsWithMarkdownStructuralAnchor`'s table-row alternation so a
bare `|expression|` (2 pipes) in technical prose no longer qualifies as a
Markdown block anchor — real GFM table rows have ≥3 pipes (≥2 cells) or a
separator row like `|---|`. Without this, prose continuation starting with
a 2-pipe expression that re-appears at a line boundary mid-tail of the
previous response would be silently stripped by the contained-prefix path,
contradicting the JSDoc's stated invariant that "incidental `|` characters
in prose do not count."

Also adds an inline comment to `isSignificantRecoveryOverlap` documenting
why the structural-class detection (`[#|`\n]`) is intentionally loose —
the 2-byte gap between the 4-byte structural floor and the 6-byte prose
floor only matters for 4–5 byte fragments that coincide on both sides of
a truncation boundary, which is far rarer than the structural-replay
scenarios the lower floor exists to catch.

Adds a regression test asserting that a continuation opening with
`|expression| ...` is left intact even when it matches at a line boundary
in the previous tail.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): pin recovery thought-before-text ordering

Adds a regression test for @tanzhenxin's review comment: the existing
`prompt-recovery-thinking-continuation` test only asserts joined non-
thought text, so a regression where the recovery turn's leading thought
ends up *after* the merged text part slips through. The new test
explicitly asserts `thoughtIdx < mergedTextIdx` in the final history
entry.

Thinking-model providers (Gemini 2.5+, Anthropic, OpenAI o-series)
validate thought-signature provenance and expect a thought to precede
the content it generated; without an ordering assertion the dedup path
could silently violate that invariant.

The new test fails on the current implementation
(`appendRecoveryContinuationParts` appends the leftover leading thought
at the end of the part list). Fix follows in a separate commit so the
red → green transition is reviewable.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): keep recovery thought before merged text part

The recovery dedup path in `appendRecoveryContinuationParts` previously
spliced only the matched continuation text part out of `nextParts` and
appended the leftover parts (including any leading thought) after the
merged text. For thinking-model providers (Gemini 2.5+, Anthropic,
OpenAI o-series) that validate thought-signature provenance, this
violated the invariant that a thought precedes the content it
generated: durable history ended up as `[..., previousText + suffix,
recoveryThought]`, with the recovery turn's thought trailing its own
text.

Hoist any non-text parts that preceded the matched text on the
continuation side (typically the recovery turn's thought) into
`mergedParts` directly before the merged text part. Trailing non-text
parts (tool calls etc.) keep their position via the final concat.
Existing `prompt-recovery-thinking-continuation` test still passes
because it only asserts joined non-thought text; the new
`...-order` test now passes as well.

Reported by @tanzhenxin in PR review on commit 556b015.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants