pi-mega-compact 0.20.64 → 0.20.66

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 (102) hide show
  1. package/README.md +1 -1
  2. package/dist/config/vector-cortex-enc2b.js +36 -0
  3. package/dist/config/vector-cortex.js +2 -0
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-setup-enc2b.js +77 -0
  7. package/dist/extensions/dashboard-server/routes-setup.js +39 -3
  8. package/dist/extensions/mega-compact-s38.test/_helpers.js +2 -1
  9. package/dist/extensions/mega-compact.test/_helpers.js +4 -1
  10. package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +2 -2
  11. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry-transient.js +131 -0
  12. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +13 -137
  13. package/dist/extensions/mega-events/compact-handlers.js +3 -3
  14. package/dist/extensions/mega-events/outage-advisor.js +5 -5
  15. package/dist/extensions/mega-events/send-safe.js +16 -0
  16. package/dist/src/config/vector-cortex-enc2b.js +36 -0
  17. package/dist/src/config/vector-cortex.js +2 -0
  18. package/dist/src/config.js +1 -1
  19. package/dist/src/vector-cortex/encoder/native-qualify-retest.js +225 -0
  20. package/dist/vector-cortex/encoder/native-qualify-retest.js +225 -0
  21. package/extensions/dashboard-client/dist/assets/{AdminTab-sjV8P5A4.js → AdminTab-B31efZ_R.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{AdminTab-sjV8P5A4.js.map → AdminTab-B31efZ_R.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{AreaChart-C03yuld4.js → AreaChart-BpjHNUZm.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{AreaChart-C03yuld4.js.map → AreaChart-BpjHNUZm.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{BarChart-B9jtJ7ud.js → BarChart-CY2FndXC.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{BarChart-B9jtJ7ud.js.map → BarChart-CY2FndXC.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{CacheTab-B7Eh3qfp.js → CacheTab-kMSrMD3e.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{CacheTab-B7Eh3qfp.js.map → CacheTab-kMSrMD3e.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{ConfigTab-DDTqosv2.js → ConfigTab-BrKpv2YG.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{ConfigTab-DDTqosv2.js.map → ConfigTab-BrKpv2YG.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{EventsTab-BmyQGmUa.js → EventsTab-DaClreCm.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{EventsTab-BmyQGmUa.js.map → EventsTab-DaClreCm.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{HealthTab-DzPqNqpm.js → HealthTab-CxKQDqF3.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{HealthTab-DzPqNqpm.js.map → HealthTab-CxKQDqF3.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-6g3d-M1p.js → MaintenanceTab-BKSlSjv0.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-6g3d-M1p.js.map → MaintenanceTab-BKSlSjv0.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Nk2yYPMS.js → MemoryMapTab-C2goWDt8.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Nk2yYPMS.js.map → MemoryMapTab-C2goWDt8.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{MetricsCards-BlIiuZp5.js → MetricsCards-DCPuCnrw.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{MetricsCards-BlIiuZp5.js.map → MetricsCards-DCPuCnrw.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/MetricsTab-Bz0Mdyb_.js +2 -0
  42. package/extensions/dashboard-client/dist/assets/{MetricsTab-DfRDIAEU.js.map → MetricsTab-Bz0Mdyb_.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{OverviewTab-xkuFE5GV.js → OverviewTab-B-eZ-eDR.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{OverviewTab-xkuFE5GV.js.map → OverviewTab-B-eZ-eDR.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{ReposTab-C6qZmmgQ.js → ReposTab-W_NKRWpk.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{ReposTab-C6qZmmgQ.js.map → ReposTab-W_NKRWpk.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{SessionsTab-Bkd_In3n.js → SessionsTab-GYrtNt4_.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{SessionsTab-Bkd_In3n.js.map → SessionsTab-GYrtNt4_.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/SetupTab-_1xa6eTJ.js +3 -0
  50. package/extensions/dashboard-client/dist/assets/SetupTab-_1xa6eTJ.js.map +1 -0
  51. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dr0KNUed.js → TimeSavedCard-Ciu8anVG.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dr0KNUed.js.map → TimeSavedCard-Ciu8anVG.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-CjmSiRQ_.js → TurnMemoryView-BhXLIjWr.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-CjmSiRQ_.js.map → TurnMemoryView-BhXLIjWr.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/TurnsTab-C-f3DggL.js +2 -0
  56. package/extensions/dashboard-client/dist/assets/{TurnsTab-JsDstDDA.js.map → TurnsTab-C-f3DggL.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-C96wfemg.js → VcStatusBadge-D97V04WE.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-C96wfemg.js.map → VcStatusBadge-D97V04WE.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-DGV3kA7X.js → VectorCortexTab-cyDJPAPB.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-DGV3kA7X.js.map → VectorCortexTab-cyDJPAPB.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{WikiTab-EFfy5v-D.js → WikiTab-dQ96DyjP.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{WikiTab-EFfy5v-D.js.map → WikiTab-dQ96DyjP.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{button-CvaPx9Bu.js → button-B1beLEiV.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{button-CvaPx9Bu.js.map → button-B1beLEiV.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{card-Crs5SwRQ.js → card-CK9KH_ob.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{card-Crs5SwRQ.js.map → card-CK9KH_ob.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{client-extra-Bd27WKSo.js → client-extra-gN7xjggX.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{client-extra-Bd27WKSo.js.map → client-extra-gN7xjggX.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-qnHql9Es.js → generateCategoricalChart-MB2_0u55.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-qnHql9Es.js.map → generateCategoricalChart-MB2_0u55.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/{index-B4twCFnn.js → index-CKVd_Ym_.js} +3 -3
  72. package/extensions/dashboard-client/dist/assets/{index-B4twCFnn.js.map → index-CKVd_Ym_.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{switch-BXutQI5d.js → switch-CswmYrO_.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{switch-BXutQI5d.js.map → switch-CswmYrO_.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{toggle-uE-OSaba.js → toggle-CMepFsf0.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{toggle-uE-OSaba.js.map → toggle-CMepFsf0.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{useSSE-BRgPAqeS.js → useSSE-Bl55LQFZ.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{useSSE-BRgPAqeS.js.map → useSSE-Bl55LQFZ.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/index.html +1 -1
  80. package/extensions/dashboard-client/src/tabs/SetupTab/CortexRetestCard.tsx +104 -0
  81. package/extensions/dashboard-client/src/tabs/SetupTab/CortexSetup.tsx +2 -0
  82. package/extensions/dashboard-server/api-contracts/setup.ts +30 -0
  83. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  84. package/extensions/dashboard-server/routes-setup-enc2b.ts +82 -0
  85. package/extensions/dashboard-server/routes-setup.ts +39 -3
  86. package/extensions/mega-compact-s38.test/_helpers.ts +1 -1
  87. package/extensions/mega-compact.test/_helpers.ts +3 -1
  88. package/extensions/mega-events/agent-handlers/agentEndHandler.ts +2 -2
  89. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry-transient.ts +170 -0
  90. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +12 -147
  91. package/extensions/mega-events/compact-handlers.ts +3 -3
  92. package/extensions/mega-events/outage-advisor.ts +5 -5
  93. package/extensions/mega-events/send-safe.ts +35 -4
  94. package/package.json +1 -1
  95. package/src/config/vector-cortex-enc2b.ts +38 -0
  96. package/src/config/vector-cortex.ts +2 -0
  97. package/src/config.ts +1 -0
  98. package/src/vector-cortex/encoder/native-qualify-retest.ts +264 -0
  99. package/extensions/dashboard-client/dist/assets/MetricsTab-DfRDIAEU.js +0 -2
  100. package/extensions/dashboard-client/dist/assets/SetupTab-CEXL71VP.js +0 -3
  101. package/extensions/dashboard-client/dist/assets/SetupTab-CEXL71VP.js.map +0 -1
  102. package/extensions/dashboard-client/dist/assets/TurnsTab-JsDstDDA.js +0 -2
package/README.md CHANGED
@@ -90,7 +90,7 @@ Set env vars before starting pi. Defaults are in `src/config/dedup.ts`.
90
90
  | `MEGACOMPACT_WIKI_INCREMENTAL` | `true` | Incremental wiki updates (no full rebuild) |
91
91
  | `MEGACOMPACT_AUTO_WIKI` | `true` | Auto-generate wiki from turns |
92
92
  | `MEGACOMPACT_NEW_UI` | `true` | Use the new Tailwind/shadcn dashboard shell |
93
- | `MEGACOMPACT_COST_API_ENABLED` | `false` | Opt-in: fetch model pricing from an external API (PREVENT-PI-004 network exception). Enriches dashboard cost data for models not in the local pricing table |
93
+ | `MEGACOMPACT_COST_API_ENABLED` | `false` | Opt-in: fetch model pricing from an external API (PREVENT-PI-004 applies to defaults; opt-in features are exempt). Enriches dashboard cost data for models not in the local pricing table |
94
94
  | `MEGACOMPACT_COST_API_URL` | _(unset)_ | OpenRouter-compatible model pricing endpoint (e.g. `https://openrouter.ai/api/v1/models`). Only contacted when `MEGACOMPACT_COST_API_ENABLED=true` |
95
95
 
96
96
  Full config reference: [`docs/CONFIGURATION.md`](docs/CONFIGURATION.md)
@@ -0,0 +1,36 @@
1
+ /**
2
+ * config/vector-cortex-enc2b.ts — ENC-2b native onnxruntime qualification
3
+ * retest flag.
4
+ *
5
+ * Sibling of vector-cortex-enc2a.ts / vector-cortex-enc2budget.ts so
6
+ * vector-cortex.ts stays under the 300-line soft limit (soft-as-hard gate).
7
+ * vector-cortex.ts re-exports the flag below and root src/config.ts re-exports
8
+ * it, so no consumer import path changes.
9
+ *
10
+ * ENC-2b re-probes and re-qualifies the installed native onnxruntime binding
11
+ * (`onnxruntime-node` in `~/.pi/mega-compact/native-ort/`, operator-installed
12
+ * via the ENC-2a guide). When the operator installs the binding the read path
13
+ * does not automatically re-evaluate the qualification verdict; this sprint
14
+ * delivers a reader-only retest that loads the LOCAL on-disk binding, runs a
15
+ * bounded warmup + p95 probe against it, measures RSS, computes a fresh
16
+ * qualification verdict against the ENC-0f p95 budget + the operator
17
+ * install-budget, and surfaces it on the Setup Cortex sub-tab and the GET
18
+ * `/api/setup-status` response.
19
+ *
20
+ * `MEGACOMPACT_ENC_2B=0` disables: the GET omits both new retest fields, the
21
+ * POST ignores the retest-request key, the Cortex sub-tab renders no retest
22
+ * card — byte-identical to the ENC-2a predecessor. The flag MUST also be a
23
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS
24
+ * — see `feedback_dashboard-flags-toggleable` memory).
25
+ *
26
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
27
+ */
28
+ import { sprintFlag } from "./vector-cortex-flag.js";
29
+ /**
30
+ * ENC-2b — native onnxruntime qualification retest (detect installed binding,
31
+ * re-probe p95/RSS, re-qualify). Default ON. `MEGACOMPACT_ENC_2B=0` disables and
32
+ * is byte-identical to the ENC-2a predecessor: no retest GET fields, no retest
33
+ * POST branch, no retest card. This flag MUST also be a dashboard SETTINGS
34
+ * toggle (visible in config UI, never in EXCLUDED_SETTINGS).
35
+ */
36
+ export const ENC_2B_ENABLED = () => sprintFlag("MEGACOMPACT_ENC_2B");
@@ -52,5 +52,7 @@ export { ENC_1B_ENABLED, ENC_1B_MAX_EMBEDDING_DIM, ENC_1B_EMBEDDING_DIM_ENV, ENC
52
52
  export { ENC_2BUDGET_ENABLED, ENC_2BUDGET_NATIVE_ORT_BUDGET_ENV, ENC_2BUDGET_MAX_MIB, ENC_2BUDGET_DEFAULT_MIB, } from "./vector-cortex-enc2budget.js";
53
53
  // ENC-2a native onnxruntime install-guide flag extracted to vector-cortex-enc2a.ts.
54
54
  export { ENC_2A_ENABLED } from "./vector-cortex-enc2a.js";
55
+ // ENC-2b native onnxruntime qualification-retest flag extracted to vector-cortex-enc2b.ts.
56
+ export { ENC_2B_ENABLED } from "./vector-cortex-enc2b.js";
55
57
  // Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
56
58
  export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
package/dist/config.js CHANGED
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_ENABLED, ENC_0C_ENABLED, ENC_0D_ENABLED, ENC_0E_ENABLED, ENC_0F_ENABLED, ENC_0G_ENABLED, ENC_1A_ENABLED, ENC_1B_ENABLED, ENC_2BUDGET_ENABLED, ENC_2BUDGET_NATIVE_ORT_BUDGET_ENV, ENC_2BUDGET_MAX_MIB, ENC_2BUDGET_DEFAULT_MIB, ENC_2A_ENABLED, COSINE_FP_BENCH_ENABLED, COSINE_FP_REAL_ENABLED, REPO_CORPUS_ENABLED, DASH_0A_ENABLED, DASH_0B_ENABLED, DASH_0C_ENABLED, DASH_0D_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_ENABLED, ENC_0C_ENABLED, ENC_0D_ENABLED, ENC_0E_ENABLED, ENC_0F_ENABLED, ENC_0G_ENABLED, ENC_1A_ENABLED, ENC_1B_ENABLED, ENC_2BUDGET_ENABLED, ENC_2BUDGET_NATIVE_ORT_BUDGET_ENV, ENC_2BUDGET_MAX_MIB, ENC_2BUDGET_DEFAULT_MIB, ENC_2A_ENABLED, ENC_2B_ENABLED, COSINE_FP_BENCH_ENABLED, COSINE_FP_REAL_ENABLED, REPO_CORPUS_ENABLED, DASH_0A_ENABLED, DASH_0B_ENABLED, DASH_0C_ENABLED, DASH_0D_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -71,6 +71,7 @@ export const VECTOR_CORTEX_SETTINGS = {
71
71
  boolDirect("MEGACOMPACT_ENC_1B", "ENC-1b ONNX runtime backend + embedder API settings", "ENC-1b completes the embedder API Settings surface (dim / headers / allow-remote persisted to the per-repo .mega-compact.env) and surfaces the ONNX runtime selection knob (MEGACOMPACT_ENCODER_NATIVE) as a toggle plus the current effective backend + demotion reason. OFF = no new GET fields, no writer branch, no new Settings rows or toggle (byte-identical ENC-1a predecessor).", true),
72
72
  boolDirect("MEGACOMPACT_ENC_2BUDGET", "ENC-2a operator-configurable native install-budget knob", "ENC-2a exposes the runtime operator knob MEGACOMPACT_NATIVE_ORT_BUDGET_MIB (default 300 MiB, clamp 8192) as a dashboard Settings field. The knob gates budgetOk in the encoder runtime decision (decision.ts installBudgetMib + runtime-select.ts selectRuntimeBackend). Persisted to the per-repo .mega-compact.env via the setup-configure writer branch; the GET status route surfaces the persisted value AND the effective integer installBudgetMib() resolves to (so the dashboard shows the runtime's actual operand). OFF = no new GET fields, no writer branch, no new Settings rows (byte-identical ENC-1b predecessor).", true),
73
73
  boolDirect("MEGACOMPACT_ENC_2A", "ENC-2a native onnxruntime install GUIDE (detect + copy-paste)", "ENC-2a surfaces the platform-matched native onnxruntime install-guide (three copy-paste steps + script path) on the Setup Cortex sub-tab when the operator opted into the native backend but the effective runtime is still wasm (onnxruntime-node absent). Pure guidance — the extension NEVER executes or fetches (PREVENT-PI-004 unchanged); the committed operator script scripts/encoder/install-native-ort.mjs runs in the USER's shell. OFF = no guide GET fields, no guide-request POST branch, no install card (byte-identical ENC-1b predecessor).", true),
74
+ boolDirect("MEGACOMPACT_ENC_2B", "ENC-2b native ONNX runtime qualification retest (detect + re-qualify)", "ENC-2b re-probes and re-qualifies the installed native onnxruntime binding (onnxruntime-node in ~/.pi/mega-compact/native-ort/, operator-installed via the ENC-2a guide): loads the LOCAL on-disk binding, runs a bounded warmup + p95 probe, measures RSS, and computes a fresh qualification verdict against the ENC-0f p95 budget + the operator install-budget. Never downloads or trains (PREVENT-PI-004 / HG-1 unchanged); surfaces nativeOrtRetestResult + nativeOrtBackendEffective on the GET /api/setup-status. OFF = no retest GET fields, no retest POST branch, no retest card (byte-identical ENC-2a predecessor).", true),
74
75
  boolDirect("MEGACOMPACT_COSINE_FP_BENCH", "COS-FP-A Synthetic FP Bench", "Synthetic FP harness + L2 cosine threshold calibration (COS-FP-A): drives the deterministic synthetic-corpus grid sweep (scripts/cosine-fp/bench.mjs) that measures L2 cosine false-positive rates and emits a recommended default, and serves the reader-only GET /api/cosine-fp-report. Synthetic-corpus-only — never reads real session/ledger bytes. OFF = bench inert, endpoint 404s, no report write, L2_COSINE plain MEGACOMPACT_L2_THRESHOLD (byte-identical predecessor).", true),
75
76
  boolDirect("MEGACOMPACT_COSINE_FP_REAL", "COS-FP-R Real-Corpus Validation", "Real-corpus L2 cosine FP validation (COS-FP-R): runs the real-corpus grid sweep (scripts/cosine-fp/real-bench.mjs) against donated, consent-approved sessions only (SECURITY_PRIVACY Lifecycle/Consent), scoring FP/FN with Wilson intervals + session-grouped bootstrap, and appends CI-backed per-content-type recommendations to the cosine-threshold report. Runs only when a valid consented corpus exists; absent corpus = no_corpus (normative pre-donation state, nothing written). OFF = script inert, nothing executes, no writes (byte-identical predecessor).", true),
76
77
  boolDirect("MEGACOMPACT_REPO_CORPUS", "Cross-Repo Corpus (REPO-A)", "Cross-repo corpus preparation (REPO-A): the governed, consent-gated reader-only path that builds a pseudonymous corpus manifest from per-repo events.log slices and serves GET /api/repo-corpus answering which pseudonymous repos/sessions are in the corpus, what cross-repo overlap exists, and whether consent is active for each — counts + IDs + status only, never payload content. Consent is append-only (consent.mjs, CLI/ops-only); revocation freezes the builder + route. OFF = builder refuses to run, reader route 404s, single-repo recall untouched (byte-identical predecessor).", true),
@@ -0,0 +1,77 @@
1
+ /**
2
+ * dashboard-server/routes-setup-enc2b.ts — ENC-2b native onnxruntime
3
+ * qualification retest (reader-only probe) + POST retest action.
4
+ *
5
+ * Extracted out of routes-setup.ts (which hovers at the 300-line source soft
6
+ * cap, mirroring the ENC-1b/ENC-2a sibling extracts) so the ENC-2b additive
7
+ * GET field — the native qualification retest result + effective backend —
8
+ * lives in a sibling impl file and routes-setup.ts stays a thin host.
9
+ *
10
+ * The retest's job is RE-QUALIFICATION: when the operator installed
11
+ * onnxruntime-node via the ENC-2a guide, the encoder read path does not
12
+ * automatically re-evaluate the qualification verdict. This sibling surface
13
+ * runs `runNativeRetest` (a bounded, reader-only, local probe) and:
14
+ *
15
+ * - surfaces `nativeOrtRetestResult` + `nativeOrtBackendEffective` on the GET
16
+ * when the flag is on AND a binding is installed — absent otherwise
17
+ * (byte-identical to the ENC-2a-era shape on flag-off/none);
18
+ * - handles the POST `nativeOrtRetest` key (`false` → 400
19
+ * `retest_rejected_false_nothing_to_do`; `true` → run + echo the fresh
20
+ * result; flag-off → the key is unrecognized, byte-identical predecessor).
21
+ *
22
+ * `nativeOrtBackendEffective` is `"native"` only when the retest verdict is
23
+ * `qualified`; any `degraded`/`failed` verdict keeps the backend `"wasm"` — the
24
+ * runtime never silently switches to a backend whose retest failed.
25
+ *
26
+ * Guardrails: PREVENT-PI-004 (local filesystem + in-process binding load only,
27
+ * zero network — the retest NEVER fetches), PREVENT-011 (no `any`).
28
+ */
29
+ import { ENC_2B_ENABLED } from "../../src/config/vector-cortex.js";
30
+ import { runNativeRetest, } from "../../src/vector-cortex/encoder/native-qualify-retest.js";
31
+ /**
32
+ * Resolve the ENC-2b additive GET fields. Returns BOTH fields when the flag is
33
+ * on AND a native binding is installed (the retest ran); returns `{}` on
34
+ * flag-off AND when no binding is installed — both fields are OMITTED (absent,
35
+ * not null), so the GET stays byte-identical to the ENC-2a-era shape and the
36
+ * client hides the retest card. Flag-off omits, never nulls: the ENC-2a-era
37
+ * host spreads `...{}` so no new key appears. Mirror of the ENC-2a/ENC-1b
38
+ * sibling idiom; async because the retest loads the binding in-process.
39
+ */
40
+ export async function readEnc2bRetest(stateDir) {
41
+ if (!ENC_2B_ENABLED())
42
+ return {};
43
+ const result = await runNativeRetest(stateDir);
44
+ if (result === null)
45
+ return {};
46
+ return {
47
+ nativeOrtRetestResult: result,
48
+ nativeOrtBackendEffective: result.verdict === "qualified" ? "native" : "wasm",
49
+ };
50
+ }
51
+ /**
52
+ * ENC-2b POST retest-action key handling (flag-gated, additive).
53
+ *
54
+ * `true` → { action: "run" } (the host runs the retest and returns the result)
55
+ * `false` → { action: "reject" } (400 `retest_rejected_false_nothing_to_do`)
56
+ * other → null (key absent or flag off — falls through unrecognized,
57
+ * byte-identical ENC-2a-era predecessor).
58
+ */
59
+ export function enc2bRetestRequest(body) {
60
+ if (!ENC_2B_ENABLED())
61
+ return null;
62
+ if (body.nativeOrtRetest === true)
63
+ return "run";
64
+ if (body.nativeOrtRetest === false)
65
+ return "reject";
66
+ return null;
67
+ }
68
+ /**
69
+ * Run the ENC-2b retest now (bounded, synchronous on the request) and return
70
+ * the fresh result. Flag-off or no binding → null. Used by the POST "run"
71
+ * branch; the result is returned on the response body, never persisted.
72
+ */
73
+ export async function runEnc2bRetest(stateDir) {
74
+ if (!ENC_2B_ENABLED())
75
+ return null;
76
+ return runNativeRetest(stateDir);
77
+ }
@@ -15,6 +15,7 @@ import { enc1aStatusFields, writeEnc1aEnv, tryEnc1aConfigure, wantsEnc1a } from
15
15
  import { enc1bStatusFields, tryEnc1bConfigure, tryEnc1bInto, enc1bValidateCombined } from "./routes-setup-enc1b.js";
16
16
  import { enc2BudgetStatusFields, tryEnc2BudgetInto, enc2BudgetValidateCombined, } from "./routes-setup-enc2budget.js";
17
17
  import { readEnc2aGuide, enc2aGuideRequest } from "./routes-setup-enc2a.js";
18
+ import { readEnc2bRetest, enc2bRetestRequest, runEnc2bRetest } from "./routes-setup-enc2b.js";
18
19
  import { writeEmbedderEnv } from "./routes-setup-env-upsert.js";
19
20
  import { detectOllama, detectLlamaCpp, detectOnnx, memoizedDetectOllama, memoizedDetectLlamaCpp, memoizedDetectOnnx, } from "./routes-setup-detect-cache.js";
20
21
  // ---------------------------------------------------------------------------
@@ -69,7 +70,7 @@ export function handleSetupStatus(req, res, ctx) {
69
70
  // ENC-1a (flag-gated, additive): echo the persisted endpoint URL and an
70
71
  // embeddingApiKeySet boolean ONLY — the raw API key is never returned. When
71
72
  // the flag is off (or neither is set) both fields are simply omitted.
72
- const body = {
73
+ const base = {
73
74
  currentEmbedder: active,
74
75
  configuredEmbedder: configured.embedder,
75
76
  configuredUrl: configured.url,
@@ -82,9 +83,20 @@ export function handleSetupStatus(req, res, ctx) {
82
83
  ...(enc2a.guide !== null ? { nativeOrtInstallGuide: enc2a.guide } : {}),
83
84
  ...(enc2a.installedVersion !== null ? { nativeOrtInstalledVersion: enc2a.installedVersion } : {}),
84
85
  };
86
+ // ENC-2b (flag-gated, additive): the native qualification retest fields. The
87
+ // retest loads the on-disk binding in-process (bounded, never a fetch), so
88
+ // readEnc2bRetest is async; the handler returns true synchronously for the
89
+ // dispatch and writes the response after the await. Flag-off/none mutates
90
+ // the body additively and stays byte-identical to the ENC-2a-era shape.
85
91
  // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
86
- res.writeHead(200, { "Content-Type": "application/json" });
87
- res.end(JSON.stringify(body));
92
+ readEnc2bRetest(ctx.stateDir).then((enc2b) => {
93
+ const body = { ...base, ...enc2b };
94
+ res.writeHead(200, { "Content-Type": "application/json" });
95
+ res.end(JSON.stringify(body));
96
+ }).catch(() => {
97
+ res.writeHead(200, { "Content-Type": "application/json" });
98
+ res.end(JSON.stringify(base));
99
+ });
88
100
  return true;
89
101
  }
90
102
  // ---------------------------------------------------------------------------
@@ -204,6 +216,30 @@ export function handleSetupConfigure(req, res, ctx) {
204
216
  res.end(JSON.stringify({ nativeOrtInstallGuide: readEnc2aGuide(ctx.stateDir).guide }));
205
217
  return;
206
218
  }
219
+ // ENC-2b retest-action key (flag-gated, additive): `false` → 400 (an
220
+ // operator-driven no-op — nothing to retest), `true` → run the bounded
221
+ // retest synchronously and return the fresh result. Flag-off: the key
222
+ // falls through unrecognized (byte-identical ENC-2a-era predecessor).
223
+ // The retest never fetches — the binding is on disk from the ENC-2a
224
+ // install (PREVENT-PI-004 clean).
225
+ const retestReq = enc2bRetestRequest(body);
226
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
227
+ if (retestReq === "reject") {
228
+ res.writeHead(400, { "Content-Type": "application/json" });
229
+ res.end(JSON.stringify({ error: "retest_rejected_false_nothing_to_do" }));
230
+ return;
231
+ }
232
+ if (retestReq === "run") {
233
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
234
+ runEnc2bRetest(ctx.stateDir).then((fresh) => {
235
+ res.writeHead(200, { "Content-Type": "application/json" });
236
+ res.end(JSON.stringify({ nativeOrtRetestResult: fresh }));
237
+ }).catch(() => {
238
+ res.writeHead(500, { "Content-Type": "application/json" });
239
+ res.end(JSON.stringify({ error: "retest_failed" }));
240
+ });
241
+ return;
242
+ }
207
243
  if (tryEnc1aConfigure(body, res, ctx))
208
244
  return;
209
245
  if (tryEnc1bConfigure(body, res, ctx))
@@ -131,7 +131,8 @@ export function harness(opts = {}) {
131
131
  registerCommand: () => { }, registerTool: () => { }, registerShortcut: () => { },
132
132
  registerFlag: () => { }, getFlag: () => undefined,
133
133
  registerMessageRenderer: () => { }, registerEntryRenderer: () => { },
134
- sendMessage: () => { }, sendUserMessage: (m) => { sendUserMessages.push(m); },
134
+ sendMessage: (m) => { if (typeof m?.content === "string")
135
+ sendUserMessages.push(m.content); }, sendUserMessage: (m) => { sendUserMessages.push(m); },
135
136
  appendEntry: (t, d) => appended.push({ t, d }),
136
137
  setSessionName: () => { }, getSessionName: () => undefined, setLabel: () => { },
137
138
  exec: async () => ({ stdout: "", stderr: "", code: 0 }),
@@ -186,7 +186,10 @@ export function harness(opts = {}) {
186
186
  getFlag: () => undefined,
187
187
  registerMessageRenderer: () => { },
188
188
  registerEntryRenderer: () => { },
189
- sendMessage: (_m) => { },
189
+ sendMessage: (m) => {
190
+ if (typeof m?.content === "string")
191
+ sendUserMessages.push(m.content);
192
+ },
190
193
  sendUserMessage: (m) => {
191
194
  sendUserMessages.push(m);
192
195
  },
@@ -1,5 +1,5 @@
1
1
  import { piCompactWouldNoop } from "../../mega-pipeline.js";
2
- import { safeSendUserMessage } from "../send-safe.js";
2
+ import { safeSendInvisibleMessage } from "../send-safe.js";
3
3
  /** Handle the `agent_end` pi event. Non-fatal end-to-end. */
4
4
  export async function handleAgentEnd(ctx, pi, runtime, config) {
5
5
  runtime.activeAgents = Math.max(0, runtime.activeAgents - 1);
@@ -200,7 +200,7 @@ export async function handleAgentEnd(ctx, pi, runtime, config) {
200
200
  const nudgeMsg = lengthStop && !didDurableTrim
201
201
  ? "[mega-compact] the last response hit the output-token cap; continue from where it stopped."
202
202
  : "[mega-compact] continue from the compacted context above.";
203
- await safeSendUserMessage(pi, nudgeMsg);
203
+ await safeSendInvisibleMessage(pi, nudgeMsg);
204
204
  }
205
205
  }
206
206
  catch {
@@ -0,0 +1,131 @@
1
+ import { errorRetryBackoffMs } from "../../error-classifier.js";
2
+ import { safeSendInvisibleMessage } from "../../send-safe.js";
3
+ import { maybeSendProviderOutageAdvisory } from "../../outage-advisor.js";
4
+ /** Handle the transient or permanent error-retry path. Non-fatal. */
5
+ export async function transientRetry(event, _ctx, pi, runtime, config, tc) {
6
+ const { effectiveCategory, detail, errSig } = tc;
7
+ // S38.7: hard-stop switch — bypass ALL retry logic when set.
8
+ if (config.errorRetryHardStop) {
9
+ runtime.rt.errorRetryCount = 0;
10
+ runtime.dashboard.event("error_retry_disabled", {
11
+ category: effectiveCategory,
12
+ turnIndex: event.turnIndex,
13
+ reason: "hard-stop",
14
+ });
15
+ return; // early exit — no retry
16
+ }
17
+ // S38.6: circuit-breaker — stop retrying after too many consecutive errors.
18
+ runtime.rt.consecutiveErrors++;
19
+ // R10: send calm "provider outage" advisory once per episode.
20
+ await maybeSendProviderOutageAdvisory(effectiveCategory, runtime, pi, config, { signal: detail.signal, rawText: (errSig || '').slice(0, 500) });
21
+ if (runtime.rt.consecutiveErrors > config.maxConsecutiveErrors) {
22
+ runtime.dashboard.event("error_retry_circuit_open", {
23
+ consecutive: runtime.rt.consecutiveErrors,
24
+ max: config.maxConsecutiveErrors,
25
+ turnIndex: event.turnIndex,
26
+ });
27
+ runtime.logger.warn("error-retry-circuit-open", {
28
+ sessionId: runtime.rt.sessionId,
29
+ consecutive: runtime.rt.consecutiveErrors,
30
+ max: config.maxConsecutiveErrors,
31
+ });
32
+ return; // early exit — circuit breaker tripped
33
+ }
34
+ const max = effectiveCategory === "transient"
35
+ ? config.autoRetryTransientMax
36
+ : config.autoRetryPermanentMax;
37
+ // max === 0 disables the category entirely (revert to S28-only).
38
+ if (max <= 0) {
39
+ runtime.rt.errorRetryCount = 0;
40
+ return;
41
+ }
42
+ runtime.rt.errorRetryCount++;
43
+ if (runtime.rt.errorRetryCount > max) {
44
+ // Exhausted — surface the error, reset for the next burst.
45
+ runtime.dashboard.event("error_retry_exhausted", {
46
+ category: effectiveCategory,
47
+ count: runtime.rt.errorRetryCount,
48
+ max,
49
+ turnIndex: event.turnIndex,
50
+ });
51
+ runtime.logger.info("error-retry-exhausted", {
52
+ sessionId: runtime.rt.sessionId,
53
+ category: effectiveCategory,
54
+ count: runtime.rt.errorRetryCount,
55
+ max,
56
+ });
57
+ runtime.rt.errorRetryCount = 0;
58
+ return;
59
+ }
60
+ // R2: session-global cap — total S38 nudges per session across ALL bursts.
61
+ // Independent of the per-burst max and the circuit breaker. Hitting it is
62
+ // terminal for the session: log + dashboard event, stop nudging. `0` disables.
63
+ if (config.errorRetrySessionMax > 0 &&
64
+ runtime.rt.errorRetrySessionCount >= config.errorRetrySessionMax) {
65
+ runtime.dashboard.event("error_retry_session_exhausted", {
66
+ count: runtime.rt.errorRetrySessionCount,
67
+ max: config.errorRetrySessionMax,
68
+ category: effectiveCategory,
69
+ turnIndex: event.turnIndex,
70
+ });
71
+ runtime.logger.warn("error-retry-session-exhausted", {
72
+ sessionId: runtime.rt.sessionId,
73
+ count: runtime.rt.errorRetrySessionCount,
74
+ max: config.errorRetrySessionMax,
75
+ category: effectiveCategory,
76
+ });
77
+ runtime.rt.errorRetryCount = 0;
78
+ return; // terminal for the session — no nudge
79
+ }
80
+ // R1: in-flight nudge dedup — a nudge queued via deliverAs:'followUp' must
81
+ // not be re-sent until consumed by an actual new agent turn (turn_start
82
+ // resets retryNudgePending). Without this, a fast-erroring provider + a
83
+ // per-turn nudge → N nudges queue up and pi dispatches N retry turns, each
84
+ // re-submitting the same failing prompt (the 2026-07-28 incident).
85
+ if (runtime.rt.retryNudgePending) {
86
+ runtime.dashboard.event("error_retry_dedup_skip", {
87
+ category: effectiveCategory,
88
+ count: runtime.rt.errorRetryCount,
89
+ max,
90
+ turnIndex: event.turnIndex,
91
+ });
92
+ return; // pending nudge not yet consumed — skip
93
+ }
94
+ // R1: gating backoff — errorRetryUntil is GATING. A nudge cannot fire before
95
+ // the previous backoff elapses. Paces retries (5s/10s/20s/30s default).
96
+ const now = Date.now();
97
+ if (now < runtime.rt.errorRetryUntil) {
98
+ runtime.dashboard.event("error_retry_backoff_skip", {
99
+ category: effectiveCategory,
100
+ count: runtime.rt.errorRetryCount,
101
+ max,
102
+ turnIndex: event.turnIndex,
103
+ });
104
+ return; // backoff not elapsed — skip
105
+ }
106
+ // Fire the retry nudge. Set pending (R1 dedup) + backoff (R1 pacing) +
107
+ // session count (R2 cap) BEFORE the await so a re-entrant turn_end during
108
+ // the send can't double-fire.
109
+ runtime.rt.retryNudgePending = true;
110
+ runtime.rt.lastErrorRetryAt = now;
111
+ runtime.rt.errorRetryUntil =
112
+ now +
113
+ errorRetryBackoffMs(runtime.rt.errorRetryCount, config.errorRetryBackoffMs);
114
+ runtime.rt.errorRetrySessionCount++;
115
+ runtime.dashboard.event("error_retry", {
116
+ category: effectiveCategory,
117
+ count: runtime.rt.errorRetryCount,
118
+ max,
119
+ turnIndex: event.turnIndex,
120
+ });
121
+ runtime.logger.info("error-retry", {
122
+ sessionId: runtime.rt.sessionId,
123
+ category: effectiveCategory,
124
+ count: runtime.rt.errorRetryCount,
125
+ max,
126
+ });
127
+ // PREVENT-PI-003: user-role only (queued + catch-guarded).
128
+ // Invisible: display:false so the retry trigger fires without
129
+ // rendering "Follow-up:" spam in the conversation UI.
130
+ await safeSendInvisibleMessage(pi, "[mega-compact] the last turn ended with an error; please retry.");
131
+ }
@@ -1,7 +1,7 @@
1
1
  import { piCompactWouldNoop } from "../../../mega-pipeline.js";
2
- import { classifyError, classifyErrorDetailed, errorRetryBackoffMs, extractErrorSignature, isKnownRetryableTransient, } from "../../error-classifier.js";
3
- import { safeSendUserMessage } from "../../send-safe.js";
4
- import { maybeSendProviderOutageAdvisory } from "../../outage-advisor.js";
2
+ import { classifyError, classifyErrorDetailed, extractErrorSignature, isKnownRetryableTransient, } from "../../error-classifier.js";
3
+ import { safeSendInvisibleMessage } from "../../send-safe.js";
4
+ import { transientRetry } from "./errorRetry-transient.js";
5
5
  /** S38: broader error-retry safety net. Non-fatal end-to-end. */
6
6
  export async function errorRetry(event, ctx, pi, runtime, config) {
7
7
  // S38: broader error-retry safety net. S28 only catches stopReason==='length';
@@ -209,7 +209,10 @@ export async function errorRetry(event, ctx, pi, runtime, config) {
209
209
  // Default ON (advisoryChannel=true): dashboard-only, no user message.
210
210
  if (!config.advisoryChannel) {
211
211
  // PREVENT-PI-003: user-role sendUserMessage only.
212
- await safeSendUserMessage(pi, "[mega-compact] this session's context may be poisoned (the provider is rejecting every request). Run /clear or /new to start a fresh context.");
212
+ // Invisible: display:false the dashboard event + log already
213
+ // capture the poisoned-context signal; the user does not need a
214
+ // visible "Follow-up:" message.
215
+ await safeSendInvisibleMessage(pi, "[mega-compact] this session's context may be poisoned (the provider is rejecting every request). Run /clear or /new to start a fresh context.");
213
216
  }
214
217
  // (c) one guarded compact per error signature (attempt to remove
215
218
  // the poisoned region). Race-guarded + deferred, mirroring the
@@ -245,139 +248,12 @@ export async function errorRetry(event, ctx, pi, runtime, config) {
245
248
  }
246
249
  else {
247
250
  // (5) transient or permanent — retry with exponential backoff.
248
- // S38.7: hard-stop switch — bypass ALL retry logic when set.
249
- if (config.errorRetryHardStop) {
250
- runtime.rt.errorRetryCount = 0;
251
- runtime.dashboard.event("error_retry_disabled", {
252
- category: effectiveCategory,
253
- turnIndex: event.turnIndex,
254
- reason: "hard-stop",
255
- });
256
- return; // early exit — no retry
257
- }
258
- // S38.6: circuit-breaker — stop retrying after too many consecutive errors.
259
- runtime.rt.consecutiveErrors++;
260
- // R10: send calm "provider outage" advisory once per episode.
261
- await maybeSendProviderOutageAdvisory(effectiveCategory, runtime, pi, config, { signal: detail.signal, rawText: (errSig || '').slice(0, 500) });
262
- if (runtime.rt.consecutiveErrors > config.maxConsecutiveErrors) {
263
- runtime.dashboard.event("error_retry_circuit_open", {
264
- consecutive: runtime.rt.consecutiveErrors,
265
- max: config.maxConsecutiveErrors,
266
- turnIndex: event.turnIndex,
267
- });
268
- runtime.logger.warn("error-retry-circuit-open", {
269
- sessionId: runtime.rt.sessionId,
270
- consecutive: runtime.rt.consecutiveErrors,
271
- max: config.maxConsecutiveErrors,
272
- });
273
- return; // early exit — circuit breaker tripped
274
- }
275
- const max = effectiveCategory === "transient"
276
- ? config.autoRetryTransientMax
277
- : config.autoRetryPermanentMax;
278
- // max === 0 disables the category entirely (revert to S28-only).
279
- if (max <= 0) {
280
- runtime.rt.errorRetryCount = 0;
281
- }
282
- else {
283
- runtime.rt.errorRetryCount++;
284
- if (runtime.rt.errorRetryCount > max) {
285
- // Exhausted — surface the error, reset for the next burst.
286
- runtime.dashboard.event("error_retry_exhausted", {
287
- category: effectiveCategory,
288
- count: runtime.rt.errorRetryCount,
289
- max,
290
- turnIndex: event.turnIndex,
291
- });
292
- runtime.logger.info("error-retry-exhausted", {
293
- sessionId: runtime.rt.sessionId,
294
- category: effectiveCategory,
295
- count: runtime.rt.errorRetryCount,
296
- max,
297
- });
298
- runtime.rt.errorRetryCount = 0;
299
- }
300
- else {
301
- // R2: session-global cap — total S38 nudges per session across
302
- // ALL bursts. Independent of the per-burst max and the circuit
303
- // breaker. Hitting it is terminal for the session: log +
304
- // dashboard event, stop nudging. `0` disables (reverts to
305
- // per-burst + circuit-breaker only).
306
- if (config.errorRetrySessionMax > 0 &&
307
- runtime.rt.errorRetrySessionCount >= config.errorRetrySessionMax) {
308
- runtime.dashboard.event("error_retry_session_exhausted", {
309
- count: runtime.rt.errorRetrySessionCount,
310
- max: config.errorRetrySessionMax,
311
- category: effectiveCategory,
312
- turnIndex: event.turnIndex,
313
- });
314
- runtime.logger.warn("error-retry-session-exhausted", {
315
- sessionId: runtime.rt.sessionId,
316
- count: runtime.rt.errorRetrySessionCount,
317
- max: config.errorRetrySessionMax,
318
- category: effectiveCategory,
319
- });
320
- runtime.rt.errorRetryCount = 0;
321
- return; // terminal for the session — no nudge
322
- }
323
- // R1: in-flight nudge dedup — a nudge queued via
324
- // deliverAs:'followUp' must not be re-sent until it has been
325
- // consumed by an actual new agent turn (turn_start resets
326
- // retryNudgePending). Without this, a fast-erroring provider +
327
- // a per-turn nudge → N nudges queue up and pi dispatches N
328
- // retry turns, each re-submitting the same failing prompt
329
- // (the 2026-07-28 incident). errorRetryCount still advances
330
- // (this IS an error turn), so the per-burst max + circuit
331
- // breaker still bound the burst.
332
- if (runtime.rt.retryNudgePending) {
333
- runtime.dashboard.event("error_retry_dedup_skip", {
334
- category: effectiveCategory,
335
- count: runtime.rt.errorRetryCount,
336
- max,
337
- turnIndex: event.turnIndex,
338
- });
339
- return; // pending nudge not yet consumed — skip
340
- }
341
- // R1: gating backoff — errorRetryUntil is now GATING
342
- // (previously documented as non-gating). A nudge cannot fire
343
- // before the previous backoff elapses. This paces retries
344
- // (5s/10s/20s/30s by default) so a fast-erroring provider
345
- // doesn't slam N nudges in <1s.
346
- const now = Date.now();
347
- if (now < runtime.rt.errorRetryUntil) {
348
- runtime.dashboard.event("error_retry_backoff_skip", {
349
- category: effectiveCategory,
350
- count: runtime.rt.errorRetryCount,
351
- max,
352
- turnIndex: event.turnIndex,
353
- });
354
- return; // backoff not elapsed — skip
355
- }
356
- // Fire the retry nudge. Set pending (R1 dedup) + backoff
357
- // (R1 pacing) + session count (R2 cap) BEFORE the await so a
358
- // re-entrant turn_end during the send can't double-fire.
359
- runtime.rt.retryNudgePending = true;
360
- runtime.rt.lastErrorRetryAt = now;
361
- runtime.rt.errorRetryUntil =
362
- now +
363
- errorRetryBackoffMs(runtime.rt.errorRetryCount, config.errorRetryBackoffMs);
364
- runtime.rt.errorRetrySessionCount++;
365
- runtime.dashboard.event("error_retry", {
366
- category: effectiveCategory,
367
- count: runtime.rt.errorRetryCount,
368
- max,
369
- turnIndex: event.turnIndex,
370
- });
371
- runtime.logger.info("error-retry", {
372
- sessionId: runtime.rt.sessionId,
373
- category: effectiveCategory,
374
- count: runtime.rt.errorRetryCount,
375
- max,
376
- });
377
- // PREVENT-PI-003: user-role sendUserMessage only (queued + catch-guarded).
378
- await safeSendUserMessage(pi, "[mega-compact] the last turn ended with an error; please retry.");
379
- }
380
- }
251
+ // Delegated to errorRetry-transient.ts (delegate-shell split).
252
+ await transientRetry(event, ctx, pi, runtime, config, {
253
+ effectiveCategory,
254
+ detail,
255
+ errSig,
256
+ });
381
257
  }
382
258
  }
383
259
  }
@@ -3,7 +3,7 @@ import { estimateBlockTokens } from "../../src/tokens.js";
3
3
  import { recordScore, getDedupStats } from "../../src/store/sqlite.js";
4
4
  import { evaluateAndUnlockAchievements } from "../../src/store/sqlite/game-achievements.js";
5
5
  import { resolveRepoRoot } from "../mega-config.js";
6
- import { safeSendUserMessage } from "./send-safe.js";
6
+ import { safeSendInvisibleMessage } from "./send-safe.js";
7
7
  /**
8
8
  * Build a minimal fallback compaction so pi never runs its throwing compact().
9
9
  *
@@ -36,7 +36,7 @@ function fallbackCompaction(event) {
36
36
  * Debounced resume-nudge: restart the agent loop after a compaction (which
37
37
  * may have stopped it). Idempotent — one nudge per 30s, never blocks.
38
38
  *
39
- * Uses safeSendUserMessage ({ deliverAs: 'followUp' } + catch-guard) so that a
39
+ * Uses safeSendInvisibleMessage ({ deliverAs: 'followUp' } + catch-guard) so that a
40
40
  * nudge fired during session_before_compact (which is mid-prompt-submission,
41
41
  * so the agent can be busy) QUEUES instead of throwing
42
42
  * "Agent is already processing. Specify streamingBehavior (steer or followUp)".
@@ -47,7 +47,7 @@ async function nudgeResume(pi, runtime) {
47
47
  if (now >= runtime.resumeNudgeUntil) {
48
48
  runtime.resumeNudgeUntil = now + 30_000;
49
49
  runtime.rt.extensionInitiatedTurn = true; // R13: suppress self-classification
50
- await safeSendUserMessage(pi, "[mega-compact] continue from the compacted context above.");
50
+ await safeSendInvisibleMessage(pi, "[mega-compact] continue from the compacted context above.");
51
51
  }
52
52
  }
53
53
  catch {
@@ -9,10 +9,10 @@
9
9
  * Distinct from the poisoned-context /clear advise: the outage advisory is
10
10
  * for transient errors where the user's context is fine.
11
11
  *
12
- * PREVENT-PI-003: sends via safeSendUserMessage (user-role only).
12
+ * PREVENT-PI-003: sends via safeSendInvisibleMessage (user-role only).
13
13
  * PREVENT-PI-004: local ctx call, no network.
14
14
  */
15
- import { safeSendUserMessage } from "./send-safe.js";
15
+ import { safeSendInvisibleMessage } from "./send-safe.js";
16
16
  /**
17
17
  * Check the provider-outage advisory condition and fire once per episode.
18
18
  *
@@ -21,7 +21,7 @@ import { safeSendUserMessage } from "./send-safe.js";
21
21
  *
22
22
  * @param effectiveCategory — must be "transient" for the advisory to fire.
23
23
  * @param runtime — the live MegaRuntime (mutated on advisory fire).
24
- * @param pi — pi ExtensionAPI for safeSendUserMessage.
24
+ * @param pi — pi ExtensionAPI for safeSendInvisibleMessage.
25
25
  * @param config — providerOutageAdviseThreshold config.
26
26
  * @param detail — R11: optional signal + rawText for forensics.
27
27
  */
@@ -53,7 +53,7 @@ export async function maybeSendProviderOutageAdvisory(effectiveCategory, runtime
53
53
  // injecting into the conversation scares users and triggers false-positive
54
54
  // re-classification of the resulting turn (2026-07-31 incident).
55
55
  if (!config.advisoryChannel) {
56
- // Legacy path (flag OFF): user-role message injection.
57
- await safeSendUserMessage(pi, `[mega-compact] the provider is having issues (${runtime.rt.consecutiveErrors} consecutive failures — timeouts/5xx/rate-limits). Retries are bounded and continue automatically; your context is fine — do NOT clear or reset it. Work resumes as soon as the provider recovers.`);
56
+ // Legacy path (flag OFF): invisible user-role message (display:false).
57
+ await safeSendInvisibleMessage(pi, `[mega-compact] the provider is having issues (${runtime.rt.consecutiveErrors} consecutive failures — timeouts/5xx/rate-limits). Retries are bounded and continue automatically; your context is fine — do NOT clear or reset it. Work resumes as soon as the provider recovers.`);
58
58
  }
59
59
  }