@goodandready/dsh-moa 0.2.71 → 0.2.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -180,6 +180,12 @@ Seamlessly retains synthesized baseline code from prior turns while pruning inte
180
180
  ### 18. Graceful Degradation & Local Fallback Resilience
181
181
  When `local_fallback_enabled: true`, candidates that fail due to network outages, timeouts, or rate limits (429/500) automatically recover using configured local models (Ollama / MiniPC). Each fallback model operates with an independent execution budget and an isolated controller linked to the parent turn signal, preventing cloud timeouts from starving fallback models. Additionally, terminal errors and aborted streams are strictly classified, ensuring reliable error propagation and failover.
182
182
 
183
+ ### 19. Zero-Token Local Pre-Judge Syntax Filter
184
+ Validates markdown code blocks across all candidate outputs locally using JavaScript/TypeScript AST checking, JSON parsing, and bracket/quote balance verification before calling the Judge LLM. When malformed candidates are detected, they are automatically disqualified or annotated with structured syntax error reports, saving judge tokens and preventing syntax errors from propagating. Configured via `local_preflight_check: true` (enabled by default).
185
+
186
+ ### 20. Early Exit upon Semantic Consensus
187
+ Compares normalized candidate proposals using token-level Sørensen-Dice similarity. When models arrive at identical or near-identical solutions (meeting or exceeding `early_exit_threshold`, default: `0.95`), the expensive Judge synthesis phase is automatically bypassed. The consensus solution is promoted directly to the workspace, saving up to 100% of judge token costs while accelerating pipeline latency. Configured via `early_exit_consensus_enabled: true` and `early_exit_threshold: 0.95`.
188
+
183
189
  ### 6. Live Canvas 1-Click Preview (optional)
184
190
  If `@goodandready/dsh-live-canvas` is installed in the same profile, `dsh-moa` pushes the promoted HTML file to the Live Canvas REST contract (`POST /dsh-live-canvas/api/preview`, served by the same harness webServer) and appends a one-click preview link (`/dsh-live-canvas/sandbox/<id>`) to the answer. Without the plugin the step is skipped silently — no errors in the log, no dead links.
185
191
 
@@ -289,6 +295,9 @@ dsh-moa:
289
295
  | `presets[].quorum_enabled` | `boolean` | `false` | Straggler mitigation: proceed with synthesis once >= 60% candidates respond |
290
296
  | `presets[].grace_period_sec` | `number` | `10` | Grace period in seconds to wait for stragglers after quorum is reached |
291
297
  | `presets[].aggregator_fallbacks` | `array` | `[]` | Ordered fallback judge models tried if primary aggregator encounters transient errors |
298
+ | `presets[].local_preflight_check` | `boolean` | `true` | Zero-token local AST, JSON, and bracket balance verification for candidate code blocks before the Judge panel |
299
+ | `presets[].early_exit_consensus_enabled` | `boolean` | `false` | Bypass Judge evaluation and promote directly when candidate solutions reach high semantic consensus |
300
+ | `presets[].early_exit_threshold` | `number` | `0.95` | Token-level similarity threshold (0.50 to 1.00) required to trigger early exit consensus |
292
301
  | `presets[].blind_evaluation` | `boolean` | `false` | Anonymize candidate model names for the judge/curator to eliminate family/brand bias |
293
302
  | `presets[].reference_timeout_sec` | `number` | `60` | Per-candidate execution timeout in seconds |
294
303
  | `presets[].aggregator_timeout_sec` | `number` | `180` | Aggregator/judge synthesis timeout in seconds |
@@ -318,6 +327,8 @@ dsh-moa:
318
327
  | `/dsh-moa/run` | `POST` | Runs the full MoA pipeline over HTTP (400 when `enabled: false`) |
319
328
  | `/dsh-moa/diff` | `GET` | Computes line-by-line diff between candidate runs or curator synthesis |
320
329
  | `/dsh-moa/promote` | `POST` | Manually promotes candidate workspace files to project root |
330
+ | `/dsh-moa/cache` | `GET` | Returns semantic cache stats, hit/miss counters, and saved token metrics |
331
+ | `/dsh-moa/cache` | `POST` | Purges cached candidate generations with optional olderThanMs filter |
321
332
  | `/api/dsh-moa/update` | `POST` | One-click plugin updater from npm with safe-write verification |
322
333
 
323
334
  ---
package/README.ru.md CHANGED
@@ -180,6 +180,12 @@ graph TD
180
180
  ### 18. Плавная деградация и локальный фоллбэк
181
181
  Опция `local_fallback_enabled` позволяет задать резервные локальные модели (Ollama, vLLM, MiniPC), к которым плагин бесшовно обращается при сбоях сетевых API, таймаутах или превышении лимитов rate limit (429/500). Каждая фоллбэк-модель запускается с независимым бюджетом времени и изолированным контроллером отмены, связанным с сигналом основного хода, предотвращая исчерпание времени ожидания из-за задержек облачных провайдеров. Стриминговые сбои и терминальные ошибки классифицируются типизированными исключениями с гарантированной защитой от позднего промоушна.
182
182
 
183
+ ### 19. Локальный синтаксический шлюз кандидатов без расхода токенов
184
+ Локально парсит блоки кода из Markdown-ответов кандидатов и проверяет их синтаксис (JavaScript, TypeScript, JSON, Python) и баланс скобок/кавычек до вызова дорогостоящей модели-судьи. Кандидаты с синтаксическими ошибками дисквалифицируются или помечаются структурированными аннотациями для судьи, полностью исключая лишний расход токенов на заведомо невалидный код. Включается настройкой `local_preflight_check: true` (включена по умолчанию).
185
+
186
+ ### 20. Ранний выход по семантическому консенсусу
187
+ Сравнивает нормализованные решения кандидатов по токенному сходству Сёренсена-Дайса. Если решения моделей совпадают или практически идентичны (достигнут порог `early_exit_threshold`, по умолчанию 0.95), стадия вызова судьи (Judge) пропускается. Согласованный вариант немедленно продвигается в проект, экономя до 100% токенов судейской коллегии и сокращая задержку ответа. Управляется параметрами `early_exit_consensus_enabled: true` и `early_exit_threshold: 0.95`.
188
+
183
189
  ### 6. Live Canvas 1-клик предпросмотр (опционально)
184
190
  Если в профиле установлен `@goodandready/dsh-live-canvas`, `dsh-moa` отправляет промоученный HTML-файл в REST-контракт Live Canvas (`POST /dsh-live-canvas/api/preview`, тот же webServer харнесса) и добавляет к ответу ссылку на предпросмотр (`/dsh-live-canvas/sandbox/<id>`). Без плагина шаг пропускается тихо — без ошибок в журнале и без битых ссылок.
185
191
 
@@ -289,6 +295,9 @@ dsh-moa:
289
295
  | `presets[].quorum_enabled` | `boolean` | `false` | Защита от зависших моделей (stragglers): запуск синтеза при ответе от >= 60% кандидатов |
290
296
  | `presets[].grace_period_sec` | `number` | `10` | Грейс-период (в секундах) ожидания оставшихся моделей после достижения кворума |
291
297
  | `presets[].aggregator_fallbacks` | `array` | `[]` | Список запасных моделей-судей при сбоях основной модели агрегатора |
298
+ | `presets[].local_preflight_check` | `boolean` | `true` | Локальная проверка AST, JSON и баланса скобок в блоках кода кандидатов без расхода токенов |
299
+ | `presets[].early_exit_consensus_enabled` | `boolean` | `false` | Пропуск этапа судьи и прямое продвижение при достижении семантического консенсуса кандидатов |
300
+ | `presets[].early_exit_threshold` | `number` | `0.95` | Порог токенного сходства (0.50–1.00) для срабатывания раннего выхода по консенсусу |
292
301
  | `presets[].blind_evaluation` | `boolean` | `false` | Обезличивание имен кандидатов («Candidate 1», «Candidate 2») для исключения предвзятости судьи |
293
302
  | `presets[].reference_timeout_sec` | `number` | `60` | Таймаут опроса каждого кандидата в секундах |
294
303
  | `presets[].aggregator_timeout_sec` | `number` | `180` | Таймаут синтеза решения судьей в секундах |
@@ -318,6 +327,8 @@ dsh-moa:
318
327
  | `/dsh-moa/run` | `POST` | Запускает полный пайплайн MoA по HTTP (400 при `enabled: false`) |
319
328
  | `/dsh-moa/diff` | `GET` | Вычисляет построчный diff между решениями кандидатов или синтезом куратора |
320
329
  | `/dsh-moa/promote` | `POST` | Ручной промоушн файлов кандидата в корень проекта с созданием снимка |
330
+ | `/dsh-moa/cache` | `GET` | Статистика семантического кэша, число попаданий и сэкономленные токены |
331
+ | `/dsh-moa/cache` | `POST` | Очистка семантического кэша с опциональным фильтром olderThanMs |
321
332
  | `/api/dsh-moa/update` | `POST` | One-click обновление плагина из npm с проверкой безопасности источника |
322
333
 
323
334
  ---
package/README.zh.md CHANGED
@@ -157,6 +157,12 @@ graph TD
157
157
  ### 18. 平滑降级与本地容灾恢复 (Local Fallback Resilience)
158
158
  当 `local_fallback_enabled: true` 时,因网络中断、超时或速率限制(429/500)而失败的候选模型会自动使用配置的本地模型(Ollama / MiniPC)恢复。每个后备模型都拥有独立的超时执行配额与关联主 turn signal 的隔离取消控制器,防止云端超时耗尽本地恢复时间。同时严格检测终止错误与流中断,确保安全容灾且杜绝已取消 turn 的延迟文件提升。
159
159
 
160
+ ### 19. 零 Token 本地预审语法网关 (Zero-Token Pre-Judge Syntax Filter)
161
+ 在调用裁判模型之前,在本地解析所有候选方案的 Markdown 代码块,进行 JavaScript/TypeScript AST 语法校验、JSON 解析以及括号/引号匹配检查。检测到语法损坏的候选将被自动过滤或附带结构化语法错误注解,无需消耗模型 Token 即可杜绝语法缺陷代码流入最终裁决。通过 `local_preflight_check: true` 配置(默认开启)。
162
+
163
+ ### 20. 语义共识提前退出机制 (Early Exit upon Semantic Consensus)
164
+ 基于 Token 级 Sørensen-Dice 相似度算法比对归一化后的候选方案。当多个候选模型给出高度一致的代码方案(达到或超过 `early_exit_threshold` 阈值,默认 0.95)时,自动跳过高耗费的裁判综合评审阶段,直接将共识方案提升至项目,在大幅缩短响应延迟的同时节省多达 100% 的裁判 Token 支出。通过 `early_exit_consensus_enabled: true` 与 `early_exit_threshold: 0.95` 配置。
165
+
160
166
  ### 6. Live Canvas 一键预览(可选)
161
167
  若同一 profile 中安装了 `@goodandready/dsh-live-canvas`,`dsh-moa` 会将提升后的 HTML 文件推送到 Live Canvas 的 REST 契约(`POST /dsh-live-canvas/api/preview`,由同一 harness webServer 提供服务),并在回答中附上一键预览链接(`/dsh-live-canvas/sandbox/<id>`)。未安装该插件时此步骤静默跳过——日志无报错,也不会出现死链接。
162
168
 
@@ -225,6 +231,9 @@ dsh-moa:
225
231
  | `presets[].reference_models` | `array` | `[...]` | 并行提案阶段并发调用的顾问模型列表 |
226
232
  | `presets[].aggregator` | `object` | `{...}` | 负责综合评审、代码审查与裁决胜出者的裁判模型 |
227
233
  | `presets[].ask_clarifying_questions` | `boolean` | `true` | 针对宽泛需求合成澄清问卷(预设级别开关) |
234
+ | `presets[].local_preflight_check` | `boolean` | `true` | 在裁判评审前本地验证候选代码块的 AST、JSON 与括号平衡(零 Token 消耗) |
235
+ | `presets[].early_exit_consensus_enabled` | `boolean` | `false` | 当候选方案达成高语义共识时跳过裁判评审并直接提升获胜方案 |
236
+ | `presets[].early_exit_threshold` | `number` | `0.95` | 触发语义共识提前退出的 Token 相似度阈值(0.50 至 1.00) |
228
237
  | `presets[].reference_temperature` / `.aggregator_temperature` | `number` | `0.6` / `0.4` | 顾问与裁判的采样温度 |
229
238
  | `presets[].max_tokens` | `number` | `4096` | 每次模型调用的最大输出 Token 数 |
230
239
  | `presets[].judge_criteria` | `string` | `""` | 传给裁判的可选附加评审准则 |
@@ -248,6 +257,8 @@ dsh-moa:
248
257
  | `/dsh-moa/run` | `POST` | 通过 HTTP 运行完整 MoA 管线(`enabled: false` 时返回 400) |
249
258
  | `/dsh-moa/diff` | `GET` | 计算候选运行结果或综合输出的行级别差异(支持按 runId 解析工作区) |
250
259
  | `/dsh-moa/promote` | `POST` | 手动将候选工作区文件提升到项目根目录(带时间机器快照) |
260
+ | `/dsh-moa/cache` | `GET` | 返回语义缓存统计信息、命中次数与节省 Token 指标 |
261
+ | `/dsh-moa/cache` | `POST` | 清除语义缓存条目,支持 olderThanMs 过滤 |
251
262
  | `/api/dsh-moa/update` | `POST` | 从 npm 一键更新插件,带安全写入源校验 |
252
263
 
253
264
  ---
package/lib/client.js CHANGED
@@ -167,6 +167,12 @@ window.__ModuleLoader__.load({
167
167
  "config.smart_routing_desc": "Analyze task complexity and automatically select the optimal preset.",
168
168
  "config.smart_routing_model": "Routing Model:",
169
169
  // Multi-Turn Context
170
+ "aggregator.context_pruning_title": "\u2702\uFE0F Cross-Candidate Context Pruning (Diff-Only Judge Context)",
171
+ "aggregator.context_pruning_desc": "Replaces unchanged base project files with compact unified diffs (---/+++ hunks) in the Judge prompt, slashing prompt tokens by up to 85%.",
172
+ "aggregator.context_pruning_lines": "Context lines around changes:",
173
+ "aggregator.semantic_cache_title": "\u26A1 Semantic Caching for Candidate Generations",
174
+ "aggregator.semantic_cache_desc": "Persists intermediate candidate outputs locally by deterministic SHA-256 fingerprint, eliminating duplicate API costs on retry or repeat requests.",
175
+ "aggregator.semantic_cache_ttl": "Cache TTL (hours):",
170
176
  "aggregator.multi_turn_title": "Multi-Turn Context & Diff Pruning",
171
177
  "aggregator.multi_turn_desc": "Retain conversation context across turns while pruning duplicate code blocks.",
172
178
  // UI States & Errors
@@ -185,6 +191,10 @@ window.__ModuleLoader__.load({
185
191
  "aggregator.workbench_desc": "Judge scores each candidate file from 1 to 10 with pros/cons. Select desired files across candidates; top model harmonizes and glues them, followed by final judge audit.",
186
192
  "aggregator.workbench_companion": "Companion Plugin:",
187
193
  "aggregator.workbench_companion_note": "safe shadow Git checkpoints before file promotion",
194
+ "aggregator.preflight_syntax_title": "\u{1F50D} Pre-Judge Local Syntax Gate (Zero-Token Linter)",
195
+ "aggregator.preflight_syntax_desc": "Validate candidate code syntax locally before judge invocation to filter broken code without token costs.",
196
+ "aggregator.early_consensus_title": "\u26A1 Early Exit upon Semantic Consensus",
197
+ "aggregator.early_consensus_desc": "Bypass the expensive judge round when reference models converge on equivalent code solutions.",
188
198
  "workbench.title": "\u{1F6E0}\uFE0F Interactive Synthesis Workbench",
189
199
  "workbench.desc": "Inspect candidate file quality scores (1\u201310), mix and match files across proposals, harmonize with top-model glue, and run a final judge audit.",
190
200
  "workbench.select_run": "MoA Run:",
@@ -361,6 +371,12 @@ window.__ModuleLoader__.load({
361
371
  "config.smart_routing_desc": "\u5206\u6790\u4EFB\u52A1\u590D\u6742\u5EA6\u5E76\u81EA\u52A8\u5339\u914D\u6700\u4F73\u9884\u8BBE\u3002",
362
372
  "config.smart_routing_model": "\u5206\u6D41\u51B3\u7B56\u6A21\u578B\uFF1A",
363
373
  // Multi-Turn Context
374
+ "aggregator.context_pruning_title": "\u2702\uFE0F \u5019\u9009\u8DE8\u6A21\u578B\u4E0A\u4E0B\u6587\u7CBE\u7B80 (Judge \u4EC5\u5DEE\u5F02\u8865\u4E01)",
375
+ "aggregator.context_pruning_desc": "\u5728 Judge \u63D0\u793A\u8BCD\u4E2D\u5C06\u672A\u4FEE\u6539\u7684\u57FA\u7840\u9879\u76EE\u6587\u4EF6\u66FF\u6362\u4E3A\u7D27\u51D1\u7684\u7EDF\u4E00\u5DEE\u5F02\u5757 (---/+++)\uFF0C\u5728\u5927\u6587\u4EF6\u573A\u666F\u4E0B\u524A\u51CF\u9AD8\u8FBE 85% \u7684\u63D0\u793A\u8BCD Token\u3002",
376
+ "aggregator.context_pruning_lines": "\u5DEE\u5F02\u4E0A\u4E0B\u6587\u884C\u6570:",
377
+ "aggregator.semantic_cache_title": "\u26A1 \u5019\u9009\u751F\u6210\u8BED\u4E49\u7F13\u5B58",
378
+ "aggregator.semantic_cache_desc": "\u901A\u8FC7\u786E\u5B9A\u7684 SHA-256 \u6307\u7EB9\u5728\u672C\u5730\u6301\u4E45\u5316\u5019\u9009\u6A21\u578B\u4E2D\u95F4\u8F93\u51FA\uFF0C\u5728\u91CD\u8BD5\u6216\u91CD\u590D\u8FED\u4EE3\u4E2D\u6D88\u9664\u5197\u4F59 API \u8D39\u7528\u3002",
379
+ "aggregator.semantic_cache_ttl": "\u7F13\u5B58\u6709\u6548\u671F (\u5C0F\u65F6):",
364
380
  "aggregator.multi_turn_title": "\u591A\u8F6E\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u4E0E\u5DEE\u5F02\u526A\u679D",
365
381
  "aggregator.multi_turn_desc": "\u8DE8\u8F6E\u6B21\u4FDD\u7559\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u5E76\u526A\u679D\u91CD\u590D\u4EE3\u7801\u5757\u3002",
366
382
  // UI States & Errors
@@ -379,6 +395,10 @@ window.__ModuleLoader__.load({
379
395
  "aggregator.workbench_desc": "\u88C1\u5224\u4E3A\u6BCF\u4E2A\u5019\u9009\u6587\u4EF6\u6253\u5206 (1-10) \u5E76\u6807\u6CE8\u4F18\u7F3A\u70B9\u3002\u5728 UI \u4E2D\u81EA\u7531\u7EC4\u5408\u5404\u5019\u9009\u6587\u4EF6\uFF0C\u7531\u5F53\u8F6E\u6700\u9AD8\u5206\u6A21\u578B\u8FDB\u884C\u4EE3\u7801\u7C98\u5408\u7EDF\u4E00\uFF0C\u6700\u7EC8\u7531\u88C1\u5224\u590D\u5BA1\u3002",
380
396
  "aggregator.workbench_companion": "\u914D\u5957\u63D2\u4EF6\uFF1A",
381
397
  "aggregator.workbench_companion_note": "\u6587\u4EF6\u63D0\u5347\u524D\u5B89\u5168\u521B\u5EFA\u5F71\u5B50 Git \u5FEB\u7167",
398
+ "aggregator.preflight_syntax_title": "\u{1F50D} \u8BC4\u5BA1\u524D\u672C\u5730\u8BED\u6CD5\u68C0\u67E5\u95E8\u7981\uFF08\u96F6Token\u5F00\u9500\uFF09",
399
+ "aggregator.preflight_syntax_desc": "\u5728\u8C03\u7528\u88C1\u5224\u6A21\u578B\u524D\u5BF9\u5019\u9009\u4EE3\u7801\u8FDB\u884C\u672C\u5730\u8BED\u6CD5\u68C0\u67E5\uFF0C\u96F6Token\u5254\u9664\u8BED\u6CD5\u7834\u635F\u4EE3\u7801\u3002",
400
+ "aggregator.early_consensus_title": "\u26A1 \u8BED\u4E49\u5171\u8BC6\u5FEB\u901F\u76F4\u51FA\uFF08Early Exit\uFF09",
401
+ "aggregator.early_consensus_desc": "\u5F53\u591A\u5019\u9009\u6A21\u578B\u751F\u6210\u4E00\u81F4\u4EE3\u7801\uFF08\u76F8\u4F3C\u5EA6\u8FBE\u6807\uFF09\u65F6\uFF0C\u76F4\u63A5\u8FD4\u56DE\u5171\u8BC6\u89E3\uFF0C\u8DF3\u8FC7\u88C1\u5224\u9636\u6BB5\u8282\u7EA6Token\u4E0E\u8017\u65F6\u3002",
382
402
  "workbench.title": "\u{1F6E0}\uFE0F \u4EA4\u4E92\u5F0F\u5408\u6210\u5DE5\u4F5C\u53F0",
383
403
  "workbench.desc": "\u67E5\u770B\u5019\u9009\u6A21\u578B\u7684\u6587\u4EF6\u8D28\u91CF\u6253\u5206 (1\u201310)\uFF0C\u81EA\u7531\u6DF7\u642D\u7EC4\u5408\u76EE\u6807\u6587\u4EF6\uFF0C\u7531\u6700\u9AD8\u5206\u6A21\u578B\u7C98\u5408\u7EDF\u4E00\u5E76\u6267\u884C\u6700\u7EC8\u88C1\u5224\u590D\u5BA1\u3002",
384
404
  "workbench.select_run": "MoA \u8FD0\u884C\uFF1A",
@@ -2312,6 +2332,43 @@ window.__ModuleLoader__.load({
2312
2332
  )
2313
2333
  )
2314
2334
  ),
2335
+ /* Pre-Judge Local Syntax Gate (#303) & Early Semantic Consensus (#299) */
2336
+ React.createElement(
2337
+ "div",
2338
+ { style: { display: "flex", flexDirection: "column", gap: 6, padding: "8px 12px", background: "var(--dsw-alias-bg-layer-2)", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2)", marginTop: 8 } },
2339
+ React.createElement(
2340
+ "label",
2341
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
2342
+ React.createElement("input", {
2343
+ type: "checkbox",
2344
+ checked: currentPreset.local_preflight_check !== false,
2345
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, local_preflight_check: e.target.checked })),
2346
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2347
+ }),
2348
+ t("aggregator.preflight_syntax_title") || "\u{1F50D} Pre-Judge Local Syntax Gate (Zero-Token Linter)"
2349
+ ),
2350
+ React.createElement(
2351
+ "div",
2352
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2353
+ t("aggregator.preflight_syntax_desc") || "Validate candidate code syntax locally before judge invocation to filter broken code without token costs."
2354
+ ),
2355
+ React.createElement(
2356
+ "label",
2357
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500, marginTop: 4 } },
2358
+ React.createElement("input", {
2359
+ type: "checkbox",
2360
+ checked: Boolean(currentPreset.early_exit_consensus_enabled),
2361
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, early_exit_consensus_enabled: e.target.checked })),
2362
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2363
+ }),
2364
+ t("aggregator.early_consensus_title") || "\u26A1 Early Exit upon Semantic Consensus"
2365
+ ),
2366
+ React.createElement(
2367
+ "div",
2368
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2369
+ t("aggregator.early_consensus_desc") || "Bypass the expensive judge round when reference models converge on equivalent code solutions."
2370
+ )
2371
+ ),
2315
2372
  /* Multi-Turn Context & Diff Pruning (#176) */
2316
2373
  React.createElement(
2317
2374
  "div",
@@ -2333,6 +2390,43 @@ window.__ModuleLoader__.load({
2333
2390
  t("aggregator.multi_turn_desc") || "Retain conversation context across turns while pruning duplicate code blocks."
2334
2391
  )
2335
2392
  ),
2393
+ /* Context Pruning (#305) & Semantic Caching (#318) */
2394
+ React.createElement(
2395
+ "div",
2396
+ { style: { display: "flex", flexDirection: "column", gap: 6, padding: "8px 12px", background: "var(--dsw-alias-bg-layer-2)", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2)", marginTop: 8 } },
2397
+ React.createElement(
2398
+ "label",
2399
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
2400
+ React.createElement("input", {
2401
+ type: "checkbox",
2402
+ checked: currentPreset.context_pruning_enabled !== false,
2403
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, context_pruning_enabled: e.target.checked })),
2404
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2405
+ }),
2406
+ t("aggregator.context_pruning_title") || "\u2702\uFE0F Cross-Candidate Context Pruning (Diff-Only Judge Context)"
2407
+ ),
2408
+ React.createElement(
2409
+ "div",
2410
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2411
+ t("aggregator.context_pruning_desc") || "Replaces unchanged base project files with compact unified diffs (---/+++ hunks) in the Judge prompt, slashing prompt tokens by up to 85%."
2412
+ ),
2413
+ React.createElement(
2414
+ "label",
2415
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500, marginTop: 4 } },
2416
+ React.createElement("input", {
2417
+ type: "checkbox",
2418
+ checked: Boolean(currentPreset.semantic_cache_enabled),
2419
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, semantic_cache_enabled: e.target.checked })),
2420
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2421
+ }),
2422
+ t("aggregator.semantic_cache_title") || "\u26A1 Semantic Caching for Candidate Generations"
2423
+ ),
2424
+ React.createElement(
2425
+ "div",
2426
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2427
+ t("aggregator.semantic_cache_desc") || "Persists intermediate candidate outputs locally by deterministic SHA-256 fingerprint, eliminating duplicate API costs on retry or repeat requests."
2428
+ )
2429
+ ),
2336
2430
  /* Fallback Judges Chain */
2337
2431
  React.createElement(
2338
2432
  "div",
package/lib/events.js CHANGED
@@ -5,8 +5,12 @@
5
5
  export const MOA_EVENTS = {
6
6
  RUN_START: 'moa/run-start',
7
7
  CANDIDATE_COMPLETE: 'moa/candidate-complete',
8
+ SYNTAX_VALIDATION: 'moa/syntax-validation',
9
+ EARLY_CONSENSUS: 'moa/early-consensus',
8
10
  CRITIQUE_COMPLETE: 'moa/critique-complete',
9
11
  JUDGE_VERDICT: 'moa/judge-verdict',
12
+ CONTEXT_PRUNING: 'moa/context-pruning',
13
+ CACHE_HIT: 'moa/cache-hit',
10
14
  RUN_FINISH: 'moa/run-finish',
11
15
  }
12
16
 
@@ -28,3 +32,4 @@ export function emitEvent(ctx, eventName, payload) {
28
32
  /* best-effort event dispatch: listener exceptions must never abort pipeline */
29
33
  }
30
34
  }
35
+
@@ -184,7 +184,9 @@ export async function runAdversarialTddLoop({
184
184
  try {
185
185
  const dest = assertPathContained(candDir, tf.relativePath)
186
186
  await fs.rm(dest, { force: true })
187
- } catch {}
187
+ } catch (err) {
188
+ logger.debug('[dsh-moa:tdd] cleanup test file error:', err?.message || err)
189
+ }
188
190
  }
189
191
 
190
192
  if (gateRes) {
@@ -217,3 +219,4 @@ export async function runAdversarialTddLoop({
217
219
  directive: results.length > 0 ? reportLines.join('\n') : '',
218
220
  }
219
221
  }
222
+
@@ -24,7 +24,9 @@ export function resolveContextLensService(ctx, explicitLens = null) {
24
24
  const refl = ctx.reflect.get('contextLens') || ctx.reflect.get('dsh-context-lens')
25
25
  if (refl) return refl
26
26
  }
27
- } catch {}
27
+ } catch (err) {
28
+ logger.debug('[dsh-moa:ast] context-lens reflection error:', err?.message || err)
29
+ }
28
30
  return null
29
31
  }
30
32
 
@@ -228,3 +230,4 @@ export async function buildAstCompositeMergeDirective(candidates = [], options =
228
230
  directive,
229
231
  }
230
232
  }
233
+
package/lib/moa-schema.js CHANGED
@@ -76,6 +76,13 @@ export const PresetSchema = z.object({
76
76
  speculative_moa_enabled: z.boolean().default(false),
77
77
  speculative_draft_model: ModelSlotSchema.default({ provider: "", model: "" }),
78
78
  adversarial_tdd_enabled: z.boolean().default(false),
79
+ local_preflight_check: z.boolean().default(true),
80
+ early_exit_consensus_enabled: z.boolean().default(false),
81
+ early_exit_threshold: z.number().default(0.95),
82
+ context_pruning_enabled: z.boolean().default(true),
83
+ context_pruning_diff_lines: z.number().default(5),
84
+ semantic_cache_enabled: z.boolean().default(false),
85
+ semantic_cache_ttl_hours: z.number().default(168),
79
86
  })
80
87
 
81
88
  export const Config = z.object({
@@ -98,6 +105,13 @@ export const Config = z.object({
98
105
  speculative_moa_enabled: z.boolean().default(false).volatile(),
99
106
  speculative_draft_model: ModelSlotSchema.default({ provider: "", model: "" }).volatile(),
100
107
  adversarial_tdd_enabled: z.boolean().default(false).volatile(),
108
+ local_preflight_check: z.boolean().default(true).volatile(),
109
+ early_exit_consensus_enabled: z.boolean().default(false).volatile(),
110
+ early_exit_threshold: z.number().default(0.95).volatile(),
111
+ context_pruning_enabled: z.boolean().default(true).volatile(),
112
+ context_pruning_diff_lines: z.number().default(5).volatile(),
113
+ semantic_cache_enabled: z.boolean().default(false).volatile(),
114
+ semantic_cache_ttl_hours: z.number().default(168).volatile(),
101
115
  smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
102
116
  prices: z.dict(PriceRow).default({}).volatile(),
103
117
  presets: z.array(PresetSchema).default([
@@ -114,6 +128,10 @@ export const Config = z.object({
114
128
  aggregator_temperature: 0.4,
115
129
  max_tokens: 4096,
116
130
  judge_criteria: '',
131
+ context_pruning_enabled: true,
132
+ context_pruning_diff_lines: 5,
133
+ semantic_cache_enabled: false,
134
+ semantic_cache_ttl_hours: 168,
117
135
  },
118
136
  {
119
137
  name: 'fast',
@@ -127,6 +145,10 @@ export const Config = z.object({
127
145
  aggregator_temperature: 0.2,
128
146
  max_tokens: 4096,
129
147
  judge_criteria: '',
148
+ context_pruning_enabled: true,
149
+ context_pruning_diff_lines: 5,
150
+ semantic_cache_enabled: false,
151
+ semantic_cache_ttl_hours: 168,
130
152
  },
131
153
  {
132
154
  name: 'deep-reasoning',
@@ -159,3 +181,4 @@ export function plainConfig(value) {
159
181
  }
160
182
  return value
161
183
  }
184
+
package/lib/moa-swarm.js CHANGED
@@ -114,17 +114,23 @@ export async function cleanupSwarmWorktrees(cwd, runId, worktrees = []) {
114
114
  if (wt.isGitWorktree) {
115
115
  try {
116
116
  await execFileAsync('git', ['worktree', 'remove', '--force', wt.worktreePath], { cwd, timeout: 8000 })
117
- } catch {}
117
+ } catch (err) {
118
+ logger.debug('[dsh-moa:swarm] cleanup worktree remove error:', err?.message || err)
119
+ }
118
120
  }
119
121
  try {
120
122
  await fs.rm(wt.worktreePath, { recursive: true, force: true })
121
- } catch {}
123
+ } catch (err) {
124
+ logger.debug('[dsh-moa:swarm] cleanup rm error:', err?.message || err)
125
+ }
122
126
  }
123
127
 
124
128
  if (await isGitRepository(cwd)) {
125
129
  try {
126
130
  await execFileAsync('git', ['worktree', 'prune'], { cwd, timeout: 5000 })
127
- } catch {}
131
+ } catch (err) {
132
+ logger.debug('[dsh-moa:swarm] cleanup prune error:', err?.message || err)
133
+ }
128
134
  }
129
135
 
130
136
  const safeRunId = String(runId || '').replace(/[^a-zA-Z0-9_-]/g, '')
@@ -132,7 +138,9 @@ export async function cleanupSwarmWorktrees(cwd, runId, worktrees = []) {
132
138
  const swarmRoot = path.join(cwd, '.moa', `swarm-${safeRunId}`)
133
139
  try {
134
140
  await fs.rm(swarmRoot, { recursive: true, force: true })
135
- } catch {}
141
+ } catch (err) {
142
+ logger.debug('[dsh-moa:swarm] cleanup swarmRoot error:', err?.message || err)
143
+ }
136
144
  }
137
145
  }
138
146
 
@@ -217,3 +225,4 @@ export async function runSubagentSwarm(references, messages, options = {}, callL
217
225
  await cleanupSwarmWorktrees(cwd, runId, worktrees)
218
226
  }
219
227
  }
228
+
@@ -1,3 +1,4 @@
1
+ import { createCachingCallLlm } from './semantic-cache.js'
1
2
  import { runSpeculativeMoa } from '../moa-speculative.js'
2
3
  import { runAdversarialTddLoop } from '../moa-adversarial-tdd.js'
3
4
  import { assignAdaptivePersonas } from '../moa-adaptive-personas.js'
@@ -33,7 +34,19 @@ export async function stageCandidateRound1({
33
34
  testGateEnabled,
34
35
  testCommand,
35
36
  execFn,
37
+ semanticCacheEnabled,
38
+ semanticCacheTtlHours,
39
+ ctx = null,
36
40
  }) {
41
+ const isCacheEnabled = Boolean(preset?.semantic_cache_enabled || semanticCacheEnabled)
42
+ const effectiveCallLlm = isCacheEnabled
43
+ ? createCachingCallLlm(callLlm, {
44
+ enabled: true,
45
+ ttlMs: (semanticCacheTtlHours || preset?.semantic_cache_ttl_hours || 168) * 60 * 60 * 1000,
46
+ cwd,
47
+ ctx,
48
+ })
49
+ : callLlm
37
50
  const lastMsgText = enrichedMessages[enrichedMessages.length - 1]?.content || ''
38
51
  const adaptiveRefs = preset?.adaptive_personas_enabled
39
52
  ? assignAdaptivePersonas(effectiveReferences, typeof lastMsgText === 'string' ? lastMsgText : '').references
@@ -52,7 +65,7 @@ export async function stageCandidateRound1({
52
65
  prices,
53
66
  signal,
54
67
  },
55
- callLlm,
68
+ effectiveCallLlm,
56
69
  onProgress
57
70
  )
58
71
  if (!specRes.fallbackToStandard && Array.isArray(specRes.referenceOutputs)) {
@@ -62,7 +75,7 @@ export async function stageCandidateRound1({
62
75
 
63
76
  if (!referenceOutputs) {
64
77
  referenceOutputs = preset?.subagent_swarm_enabled
65
- ? await runSubagentSwarm(adaptiveRefs, enrichedMessages, { temperature: refTemp, maxTokens, signal, cwd, runId }, callLlm, onProgress)
78
+ ? await runSubagentSwarm(adaptiveRefs, enrichedMessages, { temperature: refTemp, maxTokens, signal, cwd, runId }, effectiveCallLlm, onProgress)
66
79
  : await runReferencesParallel(
67
80
  adaptiveRefs,
68
81
  enrichedMessages,
@@ -80,7 +93,7 @@ export async function stageCandidateRound1({
80
93
  maxRetries: candidateRetries,
81
94
  signal,
82
95
  },
83
- callLlm,
96
+ effectiveCallLlm,
84
97
  onProgress
85
98
  )
86
99
  }