opencode-acp 1.12.10 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/NOTICE +29 -0
  2. package/README.md +116 -36
  3. package/README.zh-CN.md +94 -20
  4. package/dist/index.js +878 -44
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/compress/pipeline.d.ts.map +1 -1
  7. package/dist/lib/compress/quality-gate/algorithms/index.d.ts +3 -0
  8. package/dist/lib/compress/quality-gate/algorithms/index.d.ts.map +1 -0
  9. package/dist/lib/compress/quality-gate/evaluate.d.ts +8 -0
  10. package/dist/lib/compress/quality-gate/evaluate.d.ts.map +1 -0
  11. package/dist/lib/compress/quality-gate/index.d.ts +5 -0
  12. package/dist/lib/compress/quality-gate/index.d.ts.map +1 -0
  13. package/dist/lib/compress/quality-gate/registry.d.ts +6 -0
  14. package/dist/lib/compress/quality-gate/registry.d.ts.map +1 -0
  15. package/dist/lib/compress/quality-gate/types.d.ts +51 -0
  16. package/dist/lib/compress/quality-gate/types.d.ts.map +1 -0
  17. package/dist/lib/config-validation.d.ts.map +1 -1
  18. package/dist/lib/config.d.ts +9 -0
  19. package/dist/lib/config.d.ts.map +1 -1
  20. package/dist/lib/messages/inject/policy/index.d.ts +5 -0
  21. package/dist/lib/messages/inject/policy/index.d.ts.map +1 -0
  22. package/dist/lib/messages/inject/policy/registry.d.ts +8 -0
  23. package/dist/lib/messages/inject/policy/registry.d.ts.map +1 -0
  24. package/dist/lib/messages/inject/policy/types.d.ts +2 -0
  25. package/dist/lib/messages/inject/policy/types.d.ts.map +1 -0
  26. package/dist/lib/messages/inject/utils.d.ts +0 -9
  27. package/dist/lib/messages/inject/utils.d.ts.map +1 -1
  28. package/dist/lib/messages/utils.d.ts.map +1 -1
  29. package/dist/lib/ui/notification.d.ts.map +1 -1
  30. package/package.json +5 -2
  31. package/dist/lib/prompts/compression-rules.d.ts +0 -20
  32. package/dist/lib/prompts/compression-rules.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -926,7 +926,11 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
926
926
  "strategies.purgeErrors",
927
927
  "strategies.purgeErrors.enabled",
928
928
  "strategies.purgeErrors.turns",
929
- "strategies.purgeErrors.protectedTools"
929
+ "strategies.purgeErrors.protectedTools",
930
+ "qualityGate",
931
+ "qualityGate.enabled",
932
+ "qualityGate.algorithm",
933
+ "qualityGate.algorithms"
930
934
  ]);
931
935
  function getConfigKeyPaths(obj, prefix = "") {
932
936
  const keys = [];
@@ -1554,7 +1558,10 @@ var defaultConfig = {
1554
1558
  autoUpdate: true,
1555
1559
  debug: false,
1556
1560
  pruneNotification: "detailed",
1557
- pruneNotificationType: "chat",
1561
+ // [FIX #20] Default to toast — chat-mode notifications inject an empty
1562
+ // user message that freezes the session on providers that reject empty
1563
+ // messages (zhipuai-lb code 1214). See lib/ui/notification.ts.
1564
+ pruneNotificationType: "toast",
1558
1565
  commands: {
1559
1566
  enabled: true,
1560
1567
  protectedTools: [...DEFAULT_PROTECTED_TOOLS]
@@ -1618,6 +1625,18 @@ var defaultConfig = {
1618
1625
  highThreshold: "75%",
1619
1626
  forceThreshold: "90%"
1620
1627
  }
1628
+ },
1629
+ qualityGate: {
1630
+ enabled: false,
1631
+ algorithm: "rouge-recall-v1",
1632
+ algorithms: {
1633
+ "rouge-recall-v1": {
1634
+ layer1MinChars: 200,
1635
+ layer1MinRetentionPct: 1,
1636
+ layer2MaxRougeF1: 0.05,
1637
+ layer2MaxTop20Recall: 0.2
1638
+ }
1639
+ }
1621
1640
  }
1622
1641
  };
1623
1642
  var GLOBAL_CONFIG_DIR = process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, "opencode") : join(homedir(), ".config", "opencode");
@@ -1815,6 +1834,11 @@ function deepCloneConfig(config) {
1815
1834
  gc: {
1816
1835
  ...config.gc,
1817
1836
  batchCleanup: { ...config.gc.batchCleanup }
1837
+ },
1838
+ qualityGate: {
1839
+ enabled: config.qualityGate.enabled,
1840
+ algorithm: config.qualityGate.algorithm,
1841
+ algorithms: { ...config.qualityGate.algorithms }
1818
1842
  }
1819
1843
  };
1820
1844
  }
@@ -1828,6 +1852,14 @@ function mergeGC(base, override) {
1828
1852
  batchCleanup: { ...base.batchCleanup, ...override.batchCleanup ?? {} }
1829
1853
  };
1830
1854
  }
1855
+ function mergeQualityGate(base, override) {
1856
+ if (!override) return base;
1857
+ return {
1858
+ enabled: override.enabled ?? base.enabled,
1859
+ algorithm: override.algorithm ?? base.algorithm,
1860
+ algorithms: { ...base.algorithms, ...override.algorithms ?? {} }
1861
+ };
1862
+ }
1831
1863
  function mergeLayer(config, data) {
1832
1864
  return {
1833
1865
  enabled: data.enabled ?? config.enabled,
@@ -1847,7 +1879,8 @@ function mergeLayer(config, data) {
1847
1879
  ],
1848
1880
  compress: mergeCompress(config.compress, data.compress),
1849
1881
  gc: mergeGC(config.gc, data.gc),
1850
- strategies: mergeStrategies(config.strategies, data.strategies)
1882
+ strategies: mergeStrategies(config.strategies, data.strategies),
1883
+ qualityGate: mergeQualityGate(config.qualityGate, data.qualityGate)
1851
1884
  };
1852
1885
  }
1853
1886
  function scheduleParseWarning(ctx, title, message) {
@@ -3287,6 +3320,8 @@ var SoftIssue = class extends Error {
3287
3320
  this.kind = kind;
3288
3321
  this.messageId = messageId;
3289
3322
  }
3323
+ kind;
3324
+ messageId;
3290
3325
  };
3291
3326
  function validateArgs(args) {
3292
3327
  if (typeof args.topic !== "string" || args.topic.trim().length === 0) {
@@ -5145,20 +5180,22 @@ ${progressBar}`;
5145
5180
  \u2192 Compression (~${summaryTokensStr}): ${displaySummary}`;
5146
5181
  }
5147
5182
  }
5148
- if (config.pruneNotificationType === "toast") {
5149
- let toastMessage = message;
5150
- toastMessage = config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage);
5151
- await client.tui.showToast({
5152
- body: {
5153
- title: "ACP: Compress Notification",
5154
- message: toastMessage,
5155
- variant: "info",
5156
- duration: 5e3
5157
- }
5158
- });
5159
- return true;
5183
+ if (config.pruneNotificationType === "chat") {
5184
+ logger.warn(
5185
+ "compress.pruneNotificationType 'chat' is no longer supported (it injects an empty user message that causes provider 400 errors); falling back to toast. Set pruneNotificationType to 'toast' (or pruneNotification to 'off') to silence this warning.",
5186
+ { sessionId }
5187
+ );
5160
5188
  }
5161
- await sendIgnoredMessage(client, sessionId, message, params, logger);
5189
+ let toastMessage = message;
5190
+ toastMessage = config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage);
5191
+ await client.tui.showToast({
5192
+ body: {
5193
+ title: "ACP: Compress Notification",
5194
+ message: toastMessage,
5195
+ variant: "info",
5196
+ duration: 5e3
5197
+ }
5198
+ });
5162
5199
  return true;
5163
5200
  }
5164
5201
  async function sendIgnoredMessage(client, sessionID, text, params, logger) {
@@ -5192,6 +5229,737 @@ async function sendIgnoredMessage(client, sessionID, text, params, logger) {
5192
5229
  }
5193
5230
  }
5194
5231
 
5232
+ // lib/compress/quality-gate/registry.ts
5233
+ var registry = /* @__PURE__ */ new Map();
5234
+ function registerQualityGate(gate) {
5235
+ if (registry.has(gate.name)) {
5236
+ const existing = registry.get(gate.name);
5237
+ if (existing !== gate && existing.version !== gate.version) {
5238
+ throw new Error(
5239
+ `Quality gate "${gate.name}" already registered with version ${existing.version} (attempted ${gate.version})`
5240
+ );
5241
+ }
5242
+ }
5243
+ registry.set(gate.name, gate);
5244
+ }
5245
+ function getQualityGate(name) {
5246
+ return registry.get(name);
5247
+ }
5248
+
5249
+ // node_modules/context-compress-algorithms/dist/chunk-E6LYOQFY.js
5250
+ var ENGLISH_WORD_RE = /[a-z][a-z0-9_]+/g;
5251
+ var CJK_RE = /[\u4e00-\u9fff]/g;
5252
+ var FILE_PATH_RE = /(?:[a-zA-Z0-9_-]+\/){1,}[a-zA-Z0-9_-]+\.[a-zA-Z]{1,5}/g;
5253
+ var STOPWORDS = /* @__PURE__ */ new Set([
5254
+ "the",
5255
+ "that",
5256
+ "this",
5257
+ "these",
5258
+ "those",
5259
+ "there",
5260
+ "their",
5261
+ "them",
5262
+ "then",
5263
+ "than",
5264
+ "thats",
5265
+ "with",
5266
+ "will",
5267
+ "would",
5268
+ "could",
5269
+ "should",
5270
+ "from",
5271
+ "have",
5272
+ "has",
5273
+ "had",
5274
+ "having",
5275
+ "were",
5276
+ "what",
5277
+ "which",
5278
+ "when",
5279
+ "where",
5280
+ "while",
5281
+ "your",
5282
+ "yours",
5283
+ "theirs",
5284
+ "ours",
5285
+ "mine",
5286
+ "hers",
5287
+ "whose",
5288
+ "they",
5289
+ "them",
5290
+ "those",
5291
+ "these",
5292
+ "this",
5293
+ "that",
5294
+ "such",
5295
+ "some",
5296
+ "same",
5297
+ "other",
5298
+ "another",
5299
+ "each",
5300
+ "into",
5301
+ "onto",
5302
+ "upon",
5303
+ "over",
5304
+ "under",
5305
+ "between",
5306
+ "through",
5307
+ "during",
5308
+ "before",
5309
+ "after",
5310
+ "above",
5311
+ "below",
5312
+ "among",
5313
+ "across",
5314
+ "along",
5315
+ "around",
5316
+ "about",
5317
+ "because",
5318
+ "since",
5319
+ "unless",
5320
+ "although",
5321
+ "though",
5322
+ "whereas",
5323
+ "whether",
5324
+ "either",
5325
+ "neither",
5326
+ "both",
5327
+ "also",
5328
+ "only",
5329
+ "just",
5330
+ "very",
5331
+ "more",
5332
+ "most",
5333
+ "much",
5334
+ "many",
5335
+ "less",
5336
+ "least",
5337
+ "several",
5338
+ "enough",
5339
+ "make",
5340
+ "made",
5341
+ "makes",
5342
+ "making",
5343
+ "take",
5344
+ "took",
5345
+ "taken",
5346
+ "takes",
5347
+ "taking",
5348
+ "get",
5349
+ "got",
5350
+ "getting",
5351
+ "gets",
5352
+ "give",
5353
+ "gave",
5354
+ "given",
5355
+ "gives",
5356
+ "giving",
5357
+ "come",
5358
+ "came",
5359
+ "comes",
5360
+ "coming",
5361
+ "were",
5362
+ "been",
5363
+ "being",
5364
+ "have",
5365
+ "make",
5366
+ "want",
5367
+ "need",
5368
+ "using",
5369
+ "true",
5370
+ "false",
5371
+ "null",
5372
+ "undefined",
5373
+ "void",
5374
+ "return",
5375
+ "returns",
5376
+ "returning",
5377
+ "function",
5378
+ "const",
5379
+ "class",
5380
+ "interface",
5381
+ "type",
5382
+ "typeof",
5383
+ "instanceof",
5384
+ "import",
5385
+ "export",
5386
+ "require",
5387
+ "module",
5388
+ "default",
5389
+ "async",
5390
+ "await",
5391
+ "static",
5392
+ "public",
5393
+ "private",
5394
+ "protected",
5395
+ "readonly",
5396
+ "partial",
5397
+ "abstract",
5398
+ "virtual",
5399
+ "override",
5400
+ "final",
5401
+ "super",
5402
+ "value",
5403
+ "values",
5404
+ "param",
5405
+ "params",
5406
+ "name",
5407
+ "names",
5408
+ "test",
5409
+ "tests",
5410
+ "testing",
5411
+ "tested",
5412
+ "expect",
5413
+ "expected",
5414
+ "actual",
5415
+ "actuals",
5416
+ "result",
5417
+ "results",
5418
+ "output",
5419
+ "outputs",
5420
+ "input",
5421
+ "inputs",
5422
+ "data",
5423
+ "record",
5424
+ "records",
5425
+ "item",
5426
+ "items",
5427
+ "like",
5428
+ "want",
5429
+ "need",
5430
+ "used",
5431
+ "uses",
5432
+ "said",
5433
+ "say",
5434
+ "says",
5435
+ "went",
5436
+ "goes",
5437
+ "here",
5438
+ "there",
5439
+ "where",
5440
+ "when",
5441
+ "what",
5442
+ "who",
5443
+ "how",
5444
+ "why",
5445
+ "which",
5446
+ "whose",
5447
+ "whom",
5448
+ "yourself",
5449
+ "myself",
5450
+ "itself",
5451
+ "themselves",
5452
+ "ourselves",
5453
+ "himself",
5454
+ "herself",
5455
+ "yeah",
5456
+ "okay",
5457
+ "ok",
5458
+ "yes",
5459
+ "no",
5460
+ "not",
5461
+ "nor",
5462
+ "or",
5463
+ "and",
5464
+ "but",
5465
+ "if",
5466
+ "then",
5467
+ "else",
5468
+ "elif",
5469
+ "when",
5470
+ "while",
5471
+ "for",
5472
+ "to",
5473
+ "of",
5474
+ "in",
5475
+ "on",
5476
+ "at",
5477
+ "by",
5478
+ "with",
5479
+ "from",
5480
+ "into",
5481
+ "onto",
5482
+ "\u7684",
5483
+ "\u662F",
5484
+ "\u4E86",
5485
+ "\u5728",
5486
+ "\u548C",
5487
+ "\u4E0E",
5488
+ "\u6216",
5489
+ "\u4E5F",
5490
+ "\u90FD",
5491
+ "\u5C31",
5492
+ "\u8FD8",
5493
+ "\u53C8",
5494
+ "\u624D",
5495
+ "\u518D",
5496
+ "\u5DF2",
5497
+ "\u5C06",
5498
+ "\u4F1A",
5499
+ "\u80FD",
5500
+ "\u53EF",
5501
+ "\u53EF\u4EE5",
5502
+ "\u8981",
5503
+ "\u60F3",
5504
+ "\u9700\u8981",
5505
+ "\u5E94\u8BE5",
5506
+ "\u5FC5\u987B",
5507
+ "\u6CA1",
5508
+ "\u6CA1\u6709",
5509
+ "\u4E0D",
5510
+ "\u975E",
5511
+ "\u65E0",
5512
+ "\u83AB",
5513
+ "\u6211",
5514
+ "\u4F60",
5515
+ "\u4ED6",
5516
+ "\u5979",
5517
+ "\u5B83",
5518
+ "\u6211\u4EEC",
5519
+ "\u4F60\u4EEC",
5520
+ "\u4ED6\u4EEC",
5521
+ "\u5979\u4EEC",
5522
+ "\u5B83\u4EEC",
5523
+ "\u54B1",
5524
+ "\u54B1\u4EEC",
5525
+ "\u81EA\u5DF1",
5526
+ "\u8FD9",
5527
+ "\u90A3",
5528
+ "\u8FD9\u4E2A",
5529
+ "\u90A3\u4E2A",
5530
+ "\u8FD9\u4E9B",
5531
+ "\u90A3\u4E9B",
5532
+ "\u8FD9\u6837",
5533
+ "\u90A3\u6837",
5534
+ "\u8FD9\u91CC",
5535
+ "\u90A3\u91CC",
5536
+ "\u8FD9\u4E48",
5537
+ "\u90A3\u4E48",
5538
+ "\u4EC0\u4E48",
5539
+ "\u600E\u4E48",
5540
+ "\u4E3A\u4EC0\u4E48",
5541
+ "\u54EA",
5542
+ "\u54EA\u4E2A",
5543
+ "\u54EA\u4E9B",
5544
+ "\u54EA\u91CC",
5545
+ "\u600E\u6837",
5546
+ "\u591A\u5C11",
5547
+ "\u51E0",
5548
+ "\u591A",
5549
+ "\u5C11",
5550
+ "\u4E8E",
5551
+ "\u4ECE",
5552
+ "\u5411",
5553
+ "\u5F80",
5554
+ "\u5230",
5555
+ "\u81F3",
5556
+ "\u4E3A",
5557
+ "\u5BF9\u4E8E",
5558
+ "\u5173\u4E8E",
5559
+ "\u81F3\u4E8E",
5560
+ "\u7531\u4E8E",
5561
+ "\u56E0\u4E3A",
5562
+ "\u6240\u4EE5",
5563
+ "\u4F46\u662F",
5564
+ "\u4F46",
5565
+ "\u4E0D\u8FC7",
5566
+ "\u7136\u800C",
5567
+ "\u53EF\u662F",
5568
+ "\u53EA\u662F",
5569
+ "\u53EA\u6709",
5570
+ "\u9664\u4E86",
5571
+ "\u9664\u975E",
5572
+ "\u65E0\u8BBA",
5573
+ "\u4E0D\u7BA1",
5574
+ "\u5C3D\u7BA1",
5575
+ "\u867D\u7136",
5576
+ "\u867D\u8BF4",
5577
+ "\u5373\u4F7F",
5578
+ "\u5373\u4FBF",
5579
+ "\u54EA\u6015",
5580
+ "\u4E00\u65E6",
5581
+ "\u5982\u679C",
5582
+ "\u8981\u662F",
5583
+ "\u5047\u5982",
5584
+ "\u5047\u4F7F",
5585
+ "\u5018\u82E5",
5586
+ "\u4E4B",
5587
+ "\u5176",
5588
+ "\u5176\u4E2D",
5589
+ "\u5176\u4ED6",
5590
+ "\u5176\u5B83",
5591
+ "\u5176\u4F59",
5592
+ "\u53E6\u4E00",
5593
+ "\u53E6\u5916",
5594
+ "\u6B64\u5916",
5595
+ "\u5E76\u4E14",
5596
+ "\u5E76",
5597
+ "\u4E14",
5598
+ "\u7740",
5599
+ "\u8FC7",
5600
+ "\u5427",
5601
+ "\u5417",
5602
+ "\u5462",
5603
+ "\u554A",
5604
+ "\u54E6",
5605
+ "\u55EF",
5606
+ "\u5440",
5607
+ "\u54C7",
5608
+ "\u54C8",
5609
+ "\u561B",
5610
+ "\u54AF",
5611
+ "\u54DF"
5612
+ ]);
5613
+ var ZH_STOPWORD_BIGRAMS = /* @__PURE__ */ new Set([
5614
+ "\u6211\u4EEC",
5615
+ "\u4F60\u4EEC",
5616
+ "\u4ED6\u4EEC",
5617
+ "\u5979\u4EEC",
5618
+ "\u5B83\u4EEC",
5619
+ "\u54B1\u4EEC",
5620
+ "\u8FD9\u4E2A",
5621
+ "\u90A3\u4E2A",
5622
+ "\u8FD9\u4E9B",
5623
+ "\u90A3\u4E9B",
5624
+ "\u4EC0\u4E48",
5625
+ "\u600E\u4E48",
5626
+ "\u4E3A\u4EC0\u4E48",
5627
+ "\u5982\u4F55",
5628
+ "\u53EF\u4EE5",
5629
+ "\u5E94\u8BE5",
5630
+ "\u4F46\u662F",
5631
+ "\u56E0\u4E3A",
5632
+ "\u6240\u4EE5",
5633
+ "\u5982\u679C",
5634
+ "\u867D\u7136",
5635
+ "\u5373\u4F7F",
5636
+ "\u5C3D\u7BA1",
5637
+ "\u4E3A\u4E86",
5638
+ "\u7531\u4E8E",
5639
+ "\u4E0D\u4F46",
5640
+ "\u800C\u4E14",
5641
+ "\u5E76\u4E14",
5642
+ "\u6216\u8005",
5643
+ "\u8FD8\u662F",
5644
+ "\u4EE5\u53CA",
5645
+ "\u4EE5\u4E3A",
5646
+ "\u4E8E\u662F",
5647
+ "\u7136\u800C",
5648
+ "\u5176\u5B9E",
5649
+ "\u5C31\u662F",
5650
+ "\u53EA\u662F",
5651
+ "\u53EA\u6709",
5652
+ "\u9664\u4E86",
5653
+ "\u9664\u975E",
5654
+ "\u8FD9\u6837",
5655
+ "\u90A3\u6837",
5656
+ "\u8FD9\u4E48",
5657
+ "\u90A3\u4E48",
5658
+ "\u8FD9\u4E9B",
5659
+ "\u90A3\u4E9B",
5660
+ "\u8FD9\u91CC",
5661
+ "\u90A3\u91CC",
5662
+ "\u73B0\u5728",
5663
+ "\u4EE5\u540E",
5664
+ "\u4EE5\u524D",
5665
+ "\u4E4B\u540E",
5666
+ "\u4E4B\u524D",
5667
+ "\u7136\u540E",
5668
+ "\u5F53\u7136",
5669
+ "\u53EF\u80FD",
5670
+ "\u4E00\u4E9B",
5671
+ "\u8BB8\u591A",
5672
+ "\u975E\u5E38",
5673
+ "\u5341\u5206",
5674
+ "\u6BD4\u8F83",
5675
+ "\u66F4\u52A0",
5676
+ "\u6700\u4E3A",
5677
+ "\u4E5F\u662F",
5678
+ "\u8FD8\u662F",
5679
+ "\u5C31\u662F",
5680
+ "\u4E0D\u8FC7",
5681
+ "\u4E0D\u8981",
5682
+ "\u4E0D\u80FD",
5683
+ "\u4E0D\u4F1A",
5684
+ "\u6CA1\u6709",
5685
+ "\u4E0D\u662F",
5686
+ "\u4E0D\u7528",
5687
+ "\u4E0D\u5FC5",
5688
+ "\u4E00\u76F4",
5689
+ "\u5DF2\u7ECF",
5690
+ "\u6B63\u5728",
5691
+ "\u9A6C\u4E0A"
5692
+ ]);
5693
+ var DEFAULT_OPTS = {
5694
+ english: true,
5695
+ zhUnigrams: true,
5696
+ zhBigrams: true
5697
+ };
5698
+ function tokenize(text, opts = {}) {
5699
+ if (!text || typeof text !== "string") return [];
5700
+ const options = { ...DEFAULT_OPTS, ...opts };
5701
+ const tokens = [];
5702
+ const lower = text.toLowerCase();
5703
+ if (options.english) {
5704
+ const matches = lower.match(ENGLISH_WORD_RE);
5705
+ if (matches) {
5706
+ for (const w of matches) {
5707
+ if (w.length >= 4 && !STOPWORDS.has(w) && !/^\d+$/.test(w)) {
5708
+ tokens.push(w);
5709
+ }
5710
+ }
5711
+ }
5712
+ }
5713
+ if (options.zhUnigrams || options.zhBigrams) {
5714
+ const cjkChars = text.match(CJK_RE);
5715
+ if (cjkChars && cjkChars.length > 0) {
5716
+ const cjkStr = cjkChars.join("");
5717
+ if (options.zhUnigrams) {
5718
+ for (const c of cjkStr) {
5719
+ if (!STOPWORDS.has(c)) tokens.push(c);
5720
+ }
5721
+ }
5722
+ if (options.zhBigrams) {
5723
+ for (let i = 0; i < cjkStr.length - 1; i++) {
5724
+ const bg = cjkStr.slice(i, i + 2);
5725
+ if (!ZH_STOPWORD_BIGRAMS.has(bg) && !STOPWORDS.has(bg[0]) && !STOPWORDS.has(bg[1])) {
5726
+ tokens.push(bg);
5727
+ }
5728
+ }
5729
+ }
5730
+ }
5731
+ }
5732
+ return tokens;
5733
+ }
5734
+ function termFrequency(tokens) {
5735
+ const tf = /* @__PURE__ */ new Map();
5736
+ for (const t of tokens) {
5737
+ tf.set(t, (tf.get(t) ?? 0) + 1);
5738
+ }
5739
+ return tf;
5740
+ }
5741
+ function topKByTf(tokens, k) {
5742
+ if (tokens.length === 0 || k <= 0) return [];
5743
+ const tf = termFrequency(tokens);
5744
+ const sorted = [...tf.entries()].sort((a, b) => {
5745
+ if (b[1] !== a[1]) return b[1] - a[1];
5746
+ return a[0].localeCompare(b[0]);
5747
+ });
5748
+ return sorted.slice(0, k).map((e) => e[0]);
5749
+ }
5750
+ function extractFilePaths(text) {
5751
+ if (!text) return /* @__PURE__ */ new Set();
5752
+ const paths = /* @__PURE__ */ new Set();
5753
+ const matches = text.match(FILE_PATH_RE);
5754
+ if (matches) {
5755
+ for (const m of matches) paths.add(m);
5756
+ }
5757
+ return paths;
5758
+ }
5759
+ function rouge1Recall(summaryTokens, originalTokens) {
5760
+ if (originalTokens.length === 0) return 0;
5761
+ const summarySet = new Set(summaryTokens);
5762
+ const originalSet = new Set(originalTokens);
5763
+ let hit = 0;
5764
+ for (const t of originalSet) if (summarySet.has(t)) hit++;
5765
+ return hit / originalSet.size;
5766
+ }
5767
+ function rouge1Precision(summaryTokens, originalTokens) {
5768
+ if (summaryTokens.length === 0) return 0;
5769
+ const summarySet = new Set(summaryTokens);
5770
+ const originalSet = new Set(originalTokens);
5771
+ let hit = 0;
5772
+ for (const t of summarySet) if (originalSet.has(t)) hit++;
5773
+ return hit / summarySet.size;
5774
+ }
5775
+ function rouge1F1(summaryTokens, originalTokens) {
5776
+ const r = rouge1Recall(summaryTokens, originalTokens);
5777
+ const p = rouge1Precision(summaryTokens, originalTokens);
5778
+ if (r + p === 0) return 0;
5779
+ return 2 * r * p / (r + p);
5780
+ }
5781
+ function topKRecall(summaryTokens, originalTokens, k) {
5782
+ if (originalTokens.length === 0 || k <= 0) return 0;
5783
+ const top = topKByTf(originalTokens, k);
5784
+ if (top.length === 0) return 0;
5785
+ const summarySet = new Set(summaryTokens);
5786
+ let hit = 0;
5787
+ for (const t of top) if (summarySet.has(t)) hit++;
5788
+ return hit / top.length;
5789
+ }
5790
+ var DEFAULT_ROUGE_RECALL_V1_CONFIG = {
5791
+ layer1MinChars: 200,
5792
+ layer1MinRetentionPct: 1,
5793
+ layer2MaxRougeF1: 0.05,
5794
+ layer2MaxTop20Recall: 0.2
5795
+ };
5796
+ var TOP_K = 20;
5797
+ var ORIGINAL_TOKEN_ESTIMATE_CHARS_PER_TOKEN = 4;
5798
+ function resolveConfig(input) {
5799
+ if (!input || typeof input !== "object") return DEFAULT_ROUGE_RECALL_V1_CONFIG;
5800
+ const c = input;
5801
+ return {
5802
+ layer1MinChars: typeof c.layer1MinChars === "number" && c.layer1MinChars > 0 ? c.layer1MinChars : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer1MinChars,
5803
+ layer1MinRetentionPct: typeof c.layer1MinRetentionPct === "number" && c.layer1MinRetentionPct >= 0 ? c.layer1MinRetentionPct : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer1MinRetentionPct,
5804
+ layer2MaxRougeF1: typeof c.layer2MaxRougeF1 === "number" && c.layer2MaxRougeF1 >= 0 ? c.layer2MaxRougeF1 : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer2MaxRougeF1,
5805
+ layer2MaxTop20Recall: typeof c.layer2MaxTop20Recall === "number" && c.layer2MaxTop20Recall >= 0 ? c.layer2MaxTop20Recall : DEFAULT_ROUGE_RECALL_V1_CONFIG.layer2MaxTop20Recall
5806
+ };
5807
+ }
5808
+ var rougeRecallV1 = {
5809
+ name: "rouge-recall-v1",
5810
+ version: "1.0.0",
5811
+ description: "Two-layer gate: length floor (L1) then ROUGE-1 F1 AND top-20 keyword recall (L2)",
5812
+ evaluate(ctx, rawConfig) {
5813
+ const cfg = resolveConfig(rawConfig);
5814
+ const summaryLen = ctx.summary.length;
5815
+ const originalChars = ctx.originalText.length;
5816
+ const retentionPct = ctx.block.compressedTokens > 0 ? summaryLen / (ctx.block.compressedTokens * ORIGINAL_TOKEN_ESTIMATE_CHARS_PER_TOKEN) * 100 : 0;
5817
+ const baseMetrics = [
5818
+ { name: "summaryLen", value: summaryLen },
5819
+ { name: "retentionPct", value: +retentionPct.toFixed(2), format: "percent" },
5820
+ { name: "originalTokens", value: ctx.block.compressedTokens }
5821
+ ];
5822
+ if (summaryLen < cfg.layer1MinChars || originalChars > 0 && retentionPct < cfg.layer1MinRetentionPct) {
5823
+ return {
5824
+ passed: false,
5825
+ layer: "L1-length",
5826
+ reason: `Summary too short: ${summaryLen} chars, ${retentionPct.toFixed(2)}% retention (threshold: ${cfg.layer1MinChars} chars OR ${cfg.layer1MinRetentionPct}% retention)`,
5827
+ metrics: baseMetrics
5828
+ };
5829
+ }
5830
+ if (ctx.originalText.length === 0) {
5831
+ return { passed: true, metrics: baseMetrics };
5832
+ }
5833
+ const summaryTokens = tokenize(ctx.summary);
5834
+ const originalTokens = tokenize(ctx.originalText);
5835
+ const rougeF1 = rouge1F1(summaryTokens, originalTokens);
5836
+ const rougeRecall = rouge1Recall(summaryTokens, originalTokens);
5837
+ const top20 = topKRecall(summaryTokens, originalTokens, TOP_K);
5838
+ const summaryPaths = extractFilePaths(ctx.summary);
5839
+ const originalPaths = extractFilePaths(ctx.originalText);
5840
+ const pathCoverage = originalPaths.size >= 5 ? [...summaryPaths].filter((p) => originalPaths.has(p)).length / originalPaths.size : -1;
5841
+ const contentMetrics = [
5842
+ ...baseMetrics,
5843
+ { name: "rougeF1", value: +rougeF1.toFixed(4), format: "ratio" },
5844
+ { name: "rougeRecall", value: +rougeRecall.toFixed(4), format: "ratio" },
5845
+ { name: "top20Recall", value: +top20.toFixed(4), format: "ratio" },
5846
+ { name: "nOriginalPaths", value: originalPaths.size },
5847
+ { name: "nSummaryPaths", value: summaryPaths.size }
5848
+ ];
5849
+ if (pathCoverage >= 0) {
5850
+ contentMetrics.push({ name: "pathCoverage", value: +pathCoverage.toFixed(3), format: "ratio" });
5851
+ }
5852
+ if (rougeF1 < cfg.layer2MaxRougeF1 && top20 < cfg.layer2MaxTop20Recall) {
5853
+ return {
5854
+ passed: false,
5855
+ layer: "L2-recall",
5856
+ reason: `Content coverage too low: rougeF1=${rougeF1.toFixed(3)}, top20Recall=${top20.toFixed(3)} (threshold: rougeF1<${cfg.layer2MaxRougeF1} AND top20<${cfg.layer2MaxTop20Recall})`,
5857
+ metrics: contentMetrics
5858
+ };
5859
+ }
5860
+ return { passed: true, metrics: contentMetrics };
5861
+ }
5862
+ };
5863
+
5864
+ // lib/compress/quality-gate/algorithms/index.ts
5865
+ function ensureBuiltinGatesRegistered() {
5866
+ registerQualityGate(rougeRecallV1);
5867
+ }
5868
+
5869
+ // lib/compress/quality-gate/evaluate.ts
5870
+ var CHARS_PER_TOKEN_ESTIMATE = 4;
5871
+ var TOOL_OUTPUT_MAX_CHARS = 1500;
5872
+ var TOOL_INPUT_MAX_CHARS = 500;
5873
+ function extractMessageText(parts) {
5874
+ if (!parts || !Array.isArray(parts)) return "";
5875
+ let text = "";
5876
+ for (const part of parts) {
5877
+ if (!part || typeof part !== "object") continue;
5878
+ if (part.type === "text") {
5879
+ text += part.text + "\n";
5880
+ } else if (part.type === "tool") {
5881
+ const state = part.state;
5882
+ const input = state.status === "completed" && typeof state.input === "object" ? JSON.stringify(state.input).slice(0, TOOL_INPUT_MAX_CHARS) : "";
5883
+ const output = state.status === "completed" && typeof state.output === "string" ? state.output.slice(0, TOOL_OUTPUT_MAX_CHARS) : state.status === "completed" && typeof state.output === "object" ? JSON.stringify(state.output).slice(0, TOOL_OUTPUT_MAX_CHARS) : "";
5884
+ text += `[tool:${part.tool}] ${input}
5885
+ ${output}
5886
+ `;
5887
+ }
5888
+ }
5889
+ return text;
5890
+ }
5891
+ function buildContext(block, rawMessages) {
5892
+ const directIds = block.directMessageIds;
5893
+ if (!directIds || directIds.length === 0) return null;
5894
+ const idToMsg = /* @__PURE__ */ new Map();
5895
+ for (const m of rawMessages) {
5896
+ const id = m?.info?.id;
5897
+ if (typeof id === "string") idToMsg.set(id, m);
5898
+ }
5899
+ const chunks = [];
5900
+ for (const id of directIds) {
5901
+ const m = idToMsg.get(id);
5902
+ if (!m) continue;
5903
+ chunks.push(extractMessageText(m.parts));
5904
+ }
5905
+ if (chunks.length === 0) return null;
5906
+ const originalText = chunks.join("\n");
5907
+ return {
5908
+ block,
5909
+ summary: block.summary ?? "",
5910
+ originalChunks: chunks,
5911
+ originalText,
5912
+ originalTokens: Math.ceil(originalText.length / CHARS_PER_TOKEN_ESTIMATE)
5913
+ };
5914
+ }
5915
+ function evaluateBlockQuality(state, rawMessages, entry, config, logger) {
5916
+ const qg = config.qualityGate;
5917
+ if (!qg || qg.enabled !== true) return null;
5918
+ ensureBuiltinGatesRegistered();
5919
+ const algoName = qg.algorithm;
5920
+ if (!algoName) {
5921
+ logger.warn("Quality gate enabled but no algorithm specified", {});
5922
+ return null;
5923
+ }
5924
+ const gate = getQualityGate(algoName);
5925
+ if (!gate) {
5926
+ logger.warn("Quality gate algorithm not found in registry", { algorithm: algoName });
5927
+ return null;
5928
+ }
5929
+ const block = state.prune.messages.blocksById.get(entry.blockId);
5930
+ if (!block) {
5931
+ logger.warn("Quality gate: block not found", { blockId: entry.blockId });
5932
+ return null;
5933
+ }
5934
+ const ctx = buildContext(block, rawMessages);
5935
+ if (!ctx) return null;
5936
+ const algoConfig = (qg.algorithms && qg.algorithms[algoName]) ?? {};
5937
+ try {
5938
+ return gate.evaluate(ctx, algoConfig);
5939
+ } catch (err) {
5940
+ logger.warn("Quality gate threw \u2014 treating as pass", {
5941
+ gate: gate.name,
5942
+ blockId: entry.blockId,
5943
+ error: err instanceof Error ? err.message : String(err)
5944
+ });
5945
+ return { passed: true, metrics: [] };
5946
+ }
5947
+ }
5948
+ function evaluateBatchQuality(state, rawMessages, entries, config, logger) {
5949
+ const failures = [];
5950
+ for (const entry of entries) {
5951
+ const result = evaluateBlockQuality(state, rawMessages, entry, config, logger);
5952
+ if (result && !result.passed) {
5953
+ failures.push({ blockId: entry.blockId, result });
5954
+ }
5955
+ }
5956
+ return {
5957
+ total: entries.length,
5958
+ passed: entries.length - failures.length,
5959
+ failures
5960
+ };
5961
+ }
5962
+
5195
5963
  // lib/compress/pipeline.ts
5196
5964
  function snapshotCompressionState(state) {
5197
5965
  return {
@@ -5240,6 +6008,27 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
5240
6008
  ctx.state.manualMode = ctx.state.manualMode ? "active" : false;
5241
6009
  applyPendingCompressionDurations(ctx.state);
5242
6010
  await saveSessionState(ctx.state, ctx.logger);
6011
+ if (entries.length > 0) {
6012
+ const qualityReport = evaluateBatchQuality(
6013
+ ctx.state,
6014
+ rawMessages,
6015
+ entries,
6016
+ ctx.config,
6017
+ ctx.logger
6018
+ );
6019
+ for (const failure of qualityReport.failures) {
6020
+ const metrics = Object.fromEntries(
6021
+ failure.result.metrics.map((m) => [m.name, m.value])
6022
+ );
6023
+ ctx.logger.warn("Compression quality gate FAILED", {
6024
+ blockId: failure.blockId,
6025
+ algorithm: ctx.config.qualityGate.algorithm,
6026
+ layer: failure.result.layer,
6027
+ reason: failure.result.reason,
6028
+ ...metrics
6029
+ });
6030
+ }
6031
+ }
5243
6032
  const params = getCurrentParams(ctx.state, rawMessages, ctx.logger);
5244
6033
  const sessionMessageIds = rawMessages.filter((msg) => !isIgnoredUserMessage(msg)).map((msg) => msg.info.id);
5245
6034
  const contextTokensBefore = getCurrentTokenUsage(ctx.state, rawMessages);
@@ -6220,7 +7009,7 @@ var dropEmptyMessages = (messages) => {
6220
7009
  for (let i = messages.length - 1; i >= 0; i--) {
6221
7010
  const parts = Array.isArray(messages[i].parts) ? messages[i].parts : [];
6222
7011
  const isEmpty = parts.every(
6223
- (part) => part.type === "text" && (typeof part.text !== "string" || part.text.trim().length === 0)
7012
+ (part) => part.type === "text" && (typeof part.text !== "string" || part.text.trim().length === 0 || part.ignored === true)
6224
7013
  );
6225
7014
  if (isEmpty) {
6226
7015
  messages.splice(i, 1);
@@ -6388,6 +7177,62 @@ function listPriorityRefsBeforeIndex(messages, priorities, anchorIndex, priority
6388
7177
  return refs;
6389
7178
  }
6390
7179
 
7180
+ // node_modules/context-compress-algorithms/dist/chunk-UX2UW5FY.js
7181
+ var NUDGE_GROWTH_FLOOR = 6e3;
7182
+ var NUDGE_GROWTH_CAP = 5e4;
7183
+ var NUDGE_GROWTH_RATIO = 0.05;
7184
+ function computeShouldNudge(params) {
7185
+ const { currentTokens, overMinLimit, overMaxLimit } = params;
7186
+ if (currentTokens === void 0) {
7187
+ return { shouldNudge: false, tipsVariant: null };
7188
+ }
7189
+ if (params.lastNudgeTokens === void 0) {
7190
+ return { shouldNudge: false, tipsVariant: null };
7191
+ }
7192
+ const growthSinceLastNudge = currentTokens - params.lastNudgeTokens;
7193
+ const shouldNudge = growthSinceLastNudge >= params.nudgeGrowthTokens || overMaxLimit;
7194
+ if (!shouldNudge) {
7195
+ return { shouldNudge: false, tipsVariant: null };
7196
+ }
7197
+ const tipsVariant = overMaxLimit ? "maxLimit" : overMinLimit ? "minLimit" : "normal";
7198
+ return { shouldNudge: true, tipsVariant };
7199
+ }
7200
+ function resolveAdaptiveNudgeGrowth(modelContextLimit) {
7201
+ if (!modelContextLimit || modelContextLimit <= 0) return NUDGE_GROWTH_FLOOR;
7202
+ return Math.min(
7203
+ NUDGE_GROWTH_CAP,
7204
+ Math.max(NUDGE_GROWTH_FLOOR, Math.round(modelContextLimit * NUDGE_GROWTH_RATIO))
7205
+ );
7206
+ }
7207
+ var defaultTriggerPolicy = {
7208
+ name: "context-compress-algorithms-trigger",
7209
+ version: "1.0.0",
7210
+ description: "Growth-only cadence: nudge when context growth since last nudge exceeds adaptive threshold, or when over max limit.",
7211
+ computeShouldNudge,
7212
+ resolveAdaptiveNudgeGrowth
7213
+ };
7214
+
7215
+ // lib/messages/inject/policy/registry.ts
7216
+ var registry2 = /* @__PURE__ */ new Map();
7217
+ var defaultPolicy = null;
7218
+ function registerTriggerPolicy2(policy) {
7219
+ if (!policy.name) {
7220
+ throw new Error("TriggerPolicy must have a name");
7221
+ }
7222
+ registry2.set(policy.name, policy);
7223
+ if (!defaultPolicy) {
7224
+ defaultPolicy = policy;
7225
+ }
7226
+ }
7227
+ function getDefaultTriggerPolicy() {
7228
+ return defaultPolicy;
7229
+ }
7230
+
7231
+ // lib/messages/inject/policy/index.ts
7232
+ function ensureBuiltinTriggerPolicyRegistered() {
7233
+ registerTriggerPolicy2(defaultTriggerPolicy);
7234
+ }
7235
+
6391
7236
  // lib/messages/inject/utils.ts
6392
7237
  var MESSAGE_MODE_NUDGE_PRIORITY = "high";
6393
7238
  function getNudgeFrequency(config) {
@@ -6500,31 +7345,20 @@ function isContextOverLimits(config, state, providerId, modelId, messages) {
6500
7345
  modelContextLimit: state.modelContextLimit
6501
7346
  };
6502
7347
  }
6503
- function computeShouldNudge(params) {
6504
- const { currentTokens, overMinLimit, overMaxLimit } = params;
6505
- if (currentTokens === void 0) {
6506
- return { shouldNudge: false, tipsVariant: null };
6507
- }
6508
- if (params.lastNudgeTokens === void 0) {
6509
- return { shouldNudge: false, tipsVariant: null };
6510
- }
6511
- const growthSinceLastNudge = currentTokens - params.lastNudgeTokens;
6512
- const shouldNudge = growthSinceLastNudge >= params.nudgeGrowthTokens || overMaxLimit;
6513
- if (!shouldNudge) {
7348
+ ensureBuiltinTriggerPolicyRegistered();
7349
+ function computeShouldNudge2(params) {
7350
+ const policy = getDefaultTriggerPolicy();
7351
+ if (!policy) {
6514
7352
  return { shouldNudge: false, tipsVariant: null };
6515
7353
  }
6516
- const tipsVariant = overMaxLimit ? "maxLimit" : overMinLimit ? "minLimit" : "normal";
6517
- return { shouldNudge: true, tipsVariant };
7354
+ return policy.computeShouldNudge(params);
6518
7355
  }
6519
- var NUDGE_GROWTH_FLOOR = 6e3;
6520
- var NUDGE_GROWTH_CAP = 5e4;
6521
- var NUDGE_GROWTH_RATIO = 0.05;
6522
- function resolveAdaptiveNudgeGrowth(modelContextLimit) {
6523
- if (!modelContextLimit || modelContextLimit <= 0) return NUDGE_GROWTH_FLOOR;
6524
- return Math.min(
6525
- NUDGE_GROWTH_CAP,
6526
- Math.max(NUDGE_GROWTH_FLOOR, Math.round(modelContextLimit * NUDGE_GROWTH_RATIO))
6527
- );
7356
+ function resolveAdaptiveNudgeGrowth2(modelContextLimit) {
7357
+ const policy = getDefaultTriggerPolicy();
7358
+ if (!policy) {
7359
+ return 6e3;
7360
+ }
7361
+ return policy.resolveAdaptiveNudgeGrowth(modelContextLimit);
6528
7362
  }
6529
7363
  function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
6530
7364
  if (anchorMessageIndex < 0) {
@@ -7100,7 +7934,7 @@ ${lines2.join("\n")}`;
7100
7934
  ${lines.join("\n")}`;
7101
7935
  }
7102
7936
 
7103
- // lib/prompts/compression-rules.ts
7937
+ // node_modules/context-compress-algorithms/dist/chunk-ZRHPFN6B.js
7104
7938
  var COMPRESS_PHILOSOPHY = `Compression Philosophy:
7105
7939
  - All compression serves the primary task, but be frugal.
7106
7940
  - Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
@@ -7276,7 +8110,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
7276
8110
  }
7277
8111
  }
7278
8112
  const suffixMessage = createSuffixMessage(messages);
7279
- const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ?? resolveAdaptiveNudgeGrowth(modelContextLimit);
8113
+ const nudgeGrowthTokens = config.compress?.nudgeGrowthTokens ?? resolveAdaptiveNudgeGrowth2(modelContextLimit);
7280
8114
  const growthFloor = Math.max(
7281
8115
  config.compress?.minNudgeGrowthFloor ?? 5e3,
7282
8116
  (config.compress?.minNudgeGrowthRatio ?? 0.45) * nudgeGrowthTokens
@@ -7291,7 +8125,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
7291
8125
  const hasPendingNudge = state.nudges.lastNudgeShownTokens !== void 0;
7292
8126
  const effectiveThreshold = hasPendingNudge ? Math.floor(nudgeGrowthTokens / 2) : nudgeGrowthTokens;
7293
8127
  const growthReference = state.nudges.lastNudgeShownTokens ?? state.nudges.lastPerMessageNudgeTokens;
7294
- const decision = computeShouldNudge({
8128
+ const decision = computeShouldNudge2({
7295
8129
  currentTokens,
7296
8130
  modelContextLimit,
7297
8131
  overMinLimit,
@@ -8025,7 +8859,7 @@ function buildSchema3() {
8025
8859
  function extractMessageId(m) {
8026
8860
  return m.id ?? m.messageId ?? "";
8027
8861
  }
8028
- function extractMessageText(m) {
8862
+ function extractMessageText2(m) {
8029
8863
  const msg = m;
8030
8864
  const role = msg.role || msg.type || "unknown";
8031
8865
  const content = typeof msg.content === "string" ? msg.content : typeof msg.text === "string" ? msg.text : JSON.stringify(msg.content || msg.text || "");
@@ -8078,7 +8912,7 @@ function createDecompressTool(ctx) {
8078
8912
  }
8079
8913
  }
8080
8914
  const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
8081
- const lines2 = blockMessages.map(extractMessageText);
8915
+ const lines2 = blockMessages.map(extractMessageText2);
8082
8916
  const { writeFile: writeFile3 } = await import("fs/promises");
8083
8917
  const fileContent = lines2.length > 0 ? lines2.join("\n\n---\n\n") : activeBlocks[0]?.summary ?? "(no content available)";
8084
8918
  await writeFile3(targetPath, fileContent, "utf-8");