dual-brain 0.2.30 → 0.3.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 (312) hide show
  1. package/.dual-brain/docs/claude-code-extension-points.md +32 -0
  2. package/.dual-brain/docs/data-tools-capabilities.md +181 -0
  3. package/.dual-brain/docs/ecosystem-tools.md +91 -0
  4. package/.dual-brain/docs/panel-handoff.md +124 -0
  5. package/.dual-brain/docs/ruflo-analysis.md +48 -0
  6. package/bin/dual-brain.mjs +56 -56
  7. package/dist/mcp-server/index.d.ts +27 -0
  8. package/dist/mcp-server/index.js +359 -0
  9. package/dist/mcp-server/index.js.map +1 -0
  10. package/dist/src/agent-protocol.d.ts +163 -0
  11. package/dist/src/agent-protocol.js +368 -0
  12. package/dist/src/agent-protocol.js.map +1 -0
  13. package/dist/src/agents/registry.d.ts +52 -0
  14. package/dist/src/agents/registry.js +393 -0
  15. package/dist/src/agents/registry.js.map +1 -0
  16. package/dist/src/awareness.d.ts +93 -0
  17. package/dist/src/awareness.js +406 -0
  18. package/dist/src/awareness.js.map +1 -0
  19. package/dist/src/brief.d.ts +48 -0
  20. package/dist/src/brief.js +179 -0
  21. package/dist/src/brief.js.map +1 -0
  22. package/dist/src/calibration.d.ts +32 -0
  23. package/dist/src/calibration.js +133 -0
  24. package/dist/src/calibration.js.map +1 -0
  25. package/dist/src/checkpoint.d.ts +33 -0
  26. package/dist/src/checkpoint.js +99 -0
  27. package/dist/src/checkpoint.js.map +1 -0
  28. package/dist/src/ci-triage.d.ts +33 -0
  29. package/dist/src/ci-triage.js +193 -0
  30. package/dist/src/ci-triage.js.map +1 -0
  31. package/dist/src/cognitive-loop.d.ts +56 -0
  32. package/dist/src/cognitive-loop.js +495 -0
  33. package/dist/src/cognitive-loop.js.map +1 -0
  34. package/dist/src/collaboration.d.ts +147 -0
  35. package/dist/src/collaboration.js +438 -0
  36. package/dist/src/collaboration.js.map +1 -0
  37. package/dist/src/context-intel.d.ts +47 -0
  38. package/dist/src/context-intel.js +156 -0
  39. package/dist/src/context-intel.js.map +1 -0
  40. package/dist/src/context.d.ts +53 -0
  41. package/dist/src/context.js +332 -0
  42. package/dist/src/context.js.map +1 -0
  43. package/dist/src/continuity.d.ts +89 -0
  44. package/dist/src/continuity.js +230 -0
  45. package/dist/src/continuity.js.map +1 -0
  46. package/dist/src/cost-tracker.d.ts +47 -0
  47. package/dist/src/cost-tracker.js +170 -0
  48. package/dist/src/cost-tracker.js.map +1 -0
  49. package/dist/src/debrief.d.ts +53 -0
  50. package/dist/src/debrief.js +222 -0
  51. package/dist/src/debrief.js.map +1 -0
  52. package/dist/src/decide.d.ts +96 -0
  53. package/dist/src/decide.js +744 -0
  54. package/dist/src/decide.js.map +1 -0
  55. package/dist/src/decompose.d.ts +39 -0
  56. package/dist/src/decompose.js +218 -0
  57. package/dist/src/decompose.js.map +1 -0
  58. package/dist/src/detect.d.ts +91 -0
  59. package/dist/src/detect.js +544 -0
  60. package/dist/src/detect.js.map +1 -0
  61. package/dist/src/dispatch.d.ts +154 -0
  62. package/dist/src/dispatch.js +1306 -0
  63. package/dist/src/dispatch.js.map +1 -0
  64. package/dist/src/doctor.d.ts +421 -0
  65. package/dist/src/doctor.js +1689 -0
  66. package/dist/src/doctor.js.map +1 -0
  67. package/dist/src/engine.d.ts +70 -0
  68. package/dist/src/engine.js +155 -0
  69. package/dist/src/engine.js.map +1 -0
  70. package/dist/src/envelope.d.ts +36 -0
  71. package/dist/src/envelope.js +80 -0
  72. package/dist/src/envelope.js.map +1 -0
  73. package/dist/src/failure-memory.d.ts +55 -0
  74. package/dist/src/failure-memory.js +175 -0
  75. package/dist/src/failure-memory.js.map +1 -0
  76. package/dist/src/fx.d.ts +87 -0
  77. package/dist/src/fx.js +272 -0
  78. package/dist/src/fx.js.map +1 -0
  79. package/dist/src/governance.d.ts +93 -0
  80. package/dist/src/governance.js +261 -0
  81. package/dist/src/governance.js.map +1 -0
  82. package/dist/src/handoff.d.ts +11 -0
  83. package/dist/src/handoff.js +90 -0
  84. package/dist/src/handoff.js.map +1 -0
  85. package/dist/src/head-protocol.d.ts +76 -0
  86. package/dist/src/head-protocol.js +109 -0
  87. package/dist/src/head-protocol.js.map +1 -0
  88. package/dist/src/head.d.ts +222 -0
  89. package/dist/src/head.js +765 -0
  90. package/dist/src/head.js.map +1 -0
  91. package/dist/src/health.d.ts +132 -0
  92. package/dist/src/health.js +435 -0
  93. package/dist/src/health.js.map +1 -0
  94. package/dist/src/inbox.d.ts +70 -0
  95. package/dist/src/inbox.js +218 -0
  96. package/dist/src/inbox.js.map +1 -0
  97. package/dist/src/index.d.ts +33 -0
  98. package/dist/src/index.js +38 -0
  99. package/dist/src/index.js.map +1 -0
  100. package/dist/src/install-hooks.d.ts +13 -0
  101. package/dist/src/install-hooks.js +88 -0
  102. package/dist/src/install-hooks.js.map +1 -0
  103. package/dist/src/integrity.d.ts +59 -0
  104. package/dist/src/integrity.js +206 -0
  105. package/dist/src/integrity.js.map +1 -0
  106. package/dist/src/intelligence.d.ts +104 -0
  107. package/dist/src/intelligence.js +391 -0
  108. package/dist/src/intelligence.js.map +1 -0
  109. package/dist/src/ledger.d.ts +54 -0
  110. package/dist/src/ledger.js +179 -0
  111. package/dist/src/ledger.js.map +1 -0
  112. package/dist/src/living-docs.d.ts +14 -0
  113. package/dist/src/living-docs.js +197 -0
  114. package/dist/src/living-docs.js.map +1 -0
  115. package/dist/src/memory-tiers.d.ts +37 -0
  116. package/dist/src/memory-tiers.js +160 -0
  117. package/dist/src/memory-tiers.js.map +1 -0
  118. package/dist/src/model-profiles.d.ts +65 -0
  119. package/dist/src/model-profiles.js +568 -0
  120. package/dist/src/model-profiles.js.map +1 -0
  121. package/dist/src/models.d.ts +58 -0
  122. package/dist/src/models.js +327 -0
  123. package/dist/src/models.js.map +1 -0
  124. package/dist/src/narrative.d.ts +54 -0
  125. package/dist/src/narrative.js +163 -0
  126. package/dist/src/narrative.js.map +1 -0
  127. package/dist/src/nextstep.d.ts +16 -0
  128. package/dist/src/nextstep.js +103 -0
  129. package/dist/src/nextstep.js.map +1 -0
  130. package/dist/src/observer.d.ts +18 -0
  131. package/dist/src/observer.js +251 -0
  132. package/dist/src/observer.js.map +1 -0
  133. package/dist/src/outcome.d.ts +110 -0
  134. package/dist/src/outcome.js +377 -0
  135. package/dist/src/outcome.js.map +1 -0
  136. package/dist/src/pipeline.d.ts +167 -0
  137. package/dist/src/pipeline.js +1503 -0
  138. package/dist/src/pipeline.js.map +1 -0
  139. package/dist/src/playbook.d.ts +59 -0
  140. package/dist/src/playbook.js +238 -0
  141. package/dist/src/playbook.js.map +1 -0
  142. package/dist/src/pr-agent.d.ts +97 -0
  143. package/dist/src/pr-agent.js +195 -0
  144. package/dist/src/pr-agent.js.map +1 -0
  145. package/dist/src/predictive.d.ts +57 -0
  146. package/dist/src/predictive.js +230 -0
  147. package/dist/src/predictive.js.map +1 -0
  148. package/dist/src/profile.d.ts +294 -0
  149. package/dist/src/profile.js +1347 -0
  150. package/dist/src/profile.js.map +1 -0
  151. package/dist/src/prompt-audit.d.ts +22 -0
  152. package/dist/src/prompt-audit.js +194 -0
  153. package/dist/src/prompt-audit.js.map +1 -0
  154. package/dist/src/prompt-intel.d.ts +12 -0
  155. package/dist/src/prompt-intel.js +321 -0
  156. package/dist/src/prompt-intel.js.map +1 -0
  157. package/dist/src/provider-context.d.ts +121 -0
  158. package/dist/src/provider-context.js +222 -0
  159. package/dist/src/provider-context.js.map +1 -0
  160. package/dist/src/provider-manager.d.ts +92 -0
  161. package/dist/src/provider-manager.js +428 -0
  162. package/dist/src/provider-manager.js.map +1 -0
  163. package/dist/src/receipt.d.ts +87 -0
  164. package/dist/src/receipt.js +326 -0
  165. package/dist/src/receipt.js.map +1 -0
  166. package/dist/src/recommendations.d.ts +13 -0
  167. package/dist/src/recommendations.js +291 -0
  168. package/dist/src/recommendations.js.map +1 -0
  169. package/dist/src/redact.d.ts +15 -0
  170. package/dist/src/redact.js +129 -0
  171. package/dist/src/redact.js.map +1 -0
  172. package/dist/src/replit.d.ts +397 -0
  173. package/dist/src/replit.js +1160 -0
  174. package/dist/src/replit.js.map +1 -0
  175. package/dist/src/repo.d.ts +149 -0
  176. package/dist/src/repo.js +416 -0
  177. package/dist/src/repo.js.map +1 -0
  178. package/dist/src/revert.d.ts +30 -0
  179. package/dist/src/revert.js +166 -0
  180. package/dist/src/revert.js.map +1 -0
  181. package/dist/src/room.d.ts +102 -0
  182. package/dist/src/room.js +212 -0
  183. package/dist/src/room.js.map +1 -0
  184. package/dist/src/routing-advisor.d.ts +57 -0
  185. package/dist/src/routing-advisor.js +221 -0
  186. package/dist/src/routing-advisor.js.map +1 -0
  187. package/dist/src/self-correct.d.ts +40 -0
  188. package/dist/src/self-correct.js +137 -0
  189. package/dist/src/self-correct.js.map +1 -0
  190. package/dist/src/session-lock.d.ts +35 -0
  191. package/dist/src/session-lock.js +134 -0
  192. package/dist/src/session-lock.js.map +1 -0
  193. package/dist/src/session.d.ts +267 -0
  194. package/dist/src/session.js +1660 -0
  195. package/dist/src/session.js.map +1 -0
  196. package/dist/src/settings-tui.d.ts +5 -0
  197. package/dist/src/settings-tui.js +422 -0
  198. package/dist/src/settings-tui.js.map +1 -0
  199. package/dist/src/setup-flow.d.ts +63 -0
  200. package/dist/src/setup-flow.js +233 -0
  201. package/dist/src/setup-flow.js.map +1 -0
  202. package/dist/src/signal.d.ts +19 -0
  203. package/dist/src/signal.js +122 -0
  204. package/dist/src/signal.js.map +1 -0
  205. package/dist/src/simmer.d.ts +85 -0
  206. package/dist/src/simmer.js +224 -0
  207. package/dist/src/simmer.js.map +1 -0
  208. package/dist/src/state-export.d.ts +129 -0
  209. package/dist/src/state-export.js +233 -0
  210. package/dist/src/state-export.js.map +1 -0
  211. package/dist/src/strategy.d.ts +54 -0
  212. package/dist/src/strategy.js +95 -0
  213. package/dist/src/strategy.js.map +1 -0
  214. package/dist/src/subscription.d.ts +40 -0
  215. package/dist/src/subscription.js +189 -0
  216. package/dist/src/subscription.js.map +1 -0
  217. package/dist/src/templates.d.ts +208 -0
  218. package/dist/src/templates.js +238 -0
  219. package/dist/src/templates.js.map +1 -0
  220. package/dist/src/test.d.ts +9 -0
  221. package/dist/src/test.js +1173 -0
  222. package/dist/src/test.js.map +1 -0
  223. package/dist/src/think-engine.d.ts +67 -0
  224. package/dist/src/think-engine.js +412 -0
  225. package/dist/src/think-engine.js.map +1 -0
  226. package/dist/src/tui.d.ts +71 -0
  227. package/dist/src/tui.js +242 -0
  228. package/dist/src/tui.js.map +1 -0
  229. package/dist/src/types.d.ts +177 -0
  230. package/dist/src/types.js +6 -0
  231. package/dist/src/types.js.map +1 -0
  232. package/dist/src/update-check.d.ts +7 -0
  233. package/dist/src/update-check.js +36 -0
  234. package/dist/src/update-check.js.map +1 -0
  235. package/dist/src/wave-planner.d.ts +30 -0
  236. package/dist/src/wave-planner.js +281 -0
  237. package/dist/src/wave-planner.js.map +1 -0
  238. package/hooks/head-guard.sh +41 -0
  239. package/hooks/precompact.mjs +3 -3
  240. package/hooks/session-end.mjs +3 -3
  241. package/hooks/task-classifier.mjs +328 -0
  242. package/hooks/vibe-router.mjs +387 -0
  243. package/install.mjs +2 -2
  244. package/package.json +29 -153
  245. package/src/agents/registry.mjs +0 -405
  246. package/src/awareness.mjs +0 -425
  247. package/src/brief.mjs +0 -266
  248. package/src/calibration.mjs +0 -148
  249. package/src/checkpoint.mjs +0 -109
  250. package/src/ci-triage.mjs +0 -191
  251. package/src/cognitive-loop.mjs +0 -562
  252. package/src/collaboration.mjs +0 -545
  253. package/src/context-intel.mjs +0 -158
  254. package/src/context.mjs +0 -389
  255. package/src/continuity.mjs +0 -298
  256. package/src/cost-tracker.mjs +0 -184
  257. package/src/debrief.mjs +0 -228
  258. package/src/decide.mjs +0 -1099
  259. package/src/decompose.mjs +0 -331
  260. package/src/detect.mjs +0 -702
  261. package/src/dispatch.mjs +0 -1447
  262. package/src/doctor.mjs +0 -1607
  263. package/src/envelope.mjs +0 -139
  264. package/src/failure-memory.mjs +0 -178
  265. package/src/fx.mjs +0 -276
  266. package/src/governance.mjs +0 -279
  267. package/src/handoff.mjs +0 -87
  268. package/src/head-protocol.mjs +0 -128
  269. package/src/head.mjs +0 -952
  270. package/src/health.mjs +0 -528
  271. package/src/inbox.mjs +0 -195
  272. package/src/index.mjs +0 -44
  273. package/src/install-hooks.mjs +0 -100
  274. package/src/integrity.mjs +0 -245
  275. package/src/intelligence.mjs +0 -447
  276. package/src/ledger.mjs +0 -196
  277. package/src/living-docs.mjs +0 -210
  278. package/src/memory-tiers.mjs +0 -193
  279. package/src/models.mjs +0 -363
  280. package/src/narrative.mjs +0 -169
  281. package/src/nextstep.mjs +0 -100
  282. package/src/observer.mjs +0 -241
  283. package/src/outcome.mjs +0 -400
  284. package/src/pipeline.mjs +0 -1711
  285. package/src/playbook.mjs +0 -257
  286. package/src/pr-agent.mjs +0 -214
  287. package/src/predictive.mjs +0 -250
  288. package/src/profile.mjs +0 -1411
  289. package/src/prompt-audit.mjs +0 -231
  290. package/src/prompt-intel.mjs +0 -325
  291. package/src/provider-context.mjs +0 -257
  292. package/src/receipt.mjs +0 -344
  293. package/src/recommendations.mjs +0 -296
  294. package/src/redact.mjs +0 -192
  295. package/src/replit.mjs +0 -1210
  296. package/src/repo.mjs +0 -445
  297. package/src/revert.mjs +0 -149
  298. package/src/routing-advisor.mjs +0 -204
  299. package/src/self-correct.mjs +0 -147
  300. package/src/session-lock.mjs +0 -160
  301. package/src/session.mjs +0 -1655
  302. package/src/settings-tui.mjs +0 -373
  303. package/src/setup-flow.mjs +0 -223
  304. package/src/signal.mjs +0 -115
  305. package/src/simmer.mjs +0 -241
  306. package/src/strategy.mjs +0 -235
  307. package/src/subscription.mjs +0 -212
  308. package/src/templates.mjs +0 -260
  309. package/src/think-engine.mjs +0 -428
  310. package/src/tui.mjs +0 -276
  311. package/src/update-check.mjs +0 -35
  312. package/src/wave-planner.mjs +0 -294
@@ -0,0 +1,221 @@
1
+ // routing-advisor.ts — EMA + epsilon-greedy routing advisor
2
+ // Learns which model works best for which task type from outcome signals.
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
4
+ import { checkFileSurvival } from './outcome.js';
5
+ import { join } from 'node:path';
6
+ const ALPHA = 0.3;
7
+ const MIN_EPSILON = 0.1;
8
+ const MIN_OBSERVATIONS = 5;
9
+ const PRIOR_WEIGHT = 5;
10
+ const STATIC_PRIORS = {
11
+ 'search:haiku': 0.85, 'search:sonnet': 0.70, 'search:opus': 0.50,
12
+ 'execute:haiku': 0.55, 'execute:sonnet': 0.80, 'execute:opus': 0.85,
13
+ 'think:haiku': 0.30, 'think:sonnet': 0.70, 'think:opus': 0.90,
14
+ 'review:haiku': 0.40, 'review:sonnet': 0.75, 'review:opus': 0.85,
15
+ };
16
+ const VALID_MODELS = {
17
+ search: ['haiku', 'sonnet'],
18
+ execute: ['haiku', 'sonnet', 'opus'],
19
+ think: ['sonnet', 'opus'],
20
+ review: ['sonnet', 'opus'],
21
+ };
22
+ function stateFile(cwd) {
23
+ return join(cwd || process.cwd(), '.dualbrain', 'routing-state.json');
24
+ }
25
+ function loadState(cwd) {
26
+ try {
27
+ const p = stateFile(cwd);
28
+ if (!existsSync(p))
29
+ return {};
30
+ const raw = readFileSync(p, 'utf8');
31
+ const parsed = JSON.parse(raw);
32
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
33
+ return {};
34
+ return parsed;
35
+ }
36
+ catch {
37
+ return {};
38
+ }
39
+ }
40
+ function saveState(state, cwd) {
41
+ try {
42
+ const dir = join(cwd || process.cwd(), '.dualbrain');
43
+ if (!existsSync(dir))
44
+ mkdirSync(dir, { recursive: true });
45
+ const p = stateFile(cwd), tmp = p + '.tmp';
46
+ writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf8');
47
+ renameSync(tmp, p);
48
+ }
49
+ catch { /* non-throwing */ }
50
+ }
51
+ /** Cross-cell bias: average EMA from same-tier cells that have >= 8 observations. */
52
+ function getCrossCellBias(state, cellKey, model) {
53
+ const [tier] = cellKey.split(':');
54
+ let biasSum = 0, biasCount = 0;
55
+ for (const [key, models] of Object.entries(state)) {
56
+ if (key.startsWith('_'))
57
+ continue;
58
+ if (key.startsWith(tier + ':') && key !== cellKey && models[model]) {
59
+ const entry = models[model];
60
+ if ((entry.observations ?? 0) >= 8) {
61
+ biasSum += entry.ema;
62
+ biasCount++;
63
+ }
64
+ }
65
+ }
66
+ return biasCount > 0 ? biasSum / biasCount : null;
67
+ }
68
+ const staticPrior = (tier, model) => STATIC_PRIORS[`${tier}:${model}`] ?? 0.5;
69
+ const cellObs = (state, key) => Object.values(state[key] ?? {}).reduce((s, m) => s + (m.observations ?? 0), 0);
70
+ const blended = (ema, n, tier, model) => (n / (n + PRIOR_WEIGHT)) * ema + (PRIOR_WEIGHT / (n + PRIOR_WEIGHT)) * staticPrior(tier, model);
71
+ // taskProfile: { intent, tier, risk, files?, complexity? }
72
+ // Returns: { model, reason, confidence, explored }
73
+ export function adviseModel(taskProfile, cwd) {
74
+ try {
75
+ const { tier, intent } = taskProfile ?? {};
76
+ const validTier = tier && VALID_MODELS[tier] ? tier : 'execute';
77
+ const cellKey = `${validTier}:${intent ?? 'implement'}`;
78
+ const models = VALID_MODELS[validTier];
79
+ const state = loadState(cwd);
80
+ const totalObs = cellObs(state, cellKey);
81
+ const grandTotal = Object.entries(state)
82
+ .filter(([k]) => !k.startsWith('_'))
83
+ .reduce((s, [, cell]) => s + Object.values(cell).reduce((t, e) => t + (e.observations ?? 0), 0), 0);
84
+ if (totalObs < MIN_OBSERVATIONS) {
85
+ // When enough global data exists, blend cross-cell bias with static prior
86
+ if (grandTotal > 100) {
87
+ let bestModel = models[0], bestScore = -Infinity;
88
+ for (const m of models) {
89
+ const xbias = getCrossCellBias(state, cellKey, m);
90
+ const prior = staticPrior(validTier, m);
91
+ const score = xbias != null ? (xbias + prior) / 2 : prior;
92
+ if (score > bestScore) {
93
+ bestScore = score;
94
+ bestModel = m;
95
+ }
96
+ }
97
+ return { model: bestModel, reason: 'cross-cell bias', confidence: 0.4, explored: false };
98
+ }
99
+ const best = models.reduce((a, b) => staticPrior(validTier, a) >= staticPrior(validTier, b) ? a : b);
100
+ return { model: best, reason: 'insufficient data, using heuristic', confidence: 0.3, explored: false };
101
+ }
102
+ const epsilon = Math.max(MIN_EPSILON, 0.5 * Math.pow(0.9, totalObs));
103
+ const explored = Math.random() < epsilon;
104
+ if (explored) {
105
+ const model = models[Math.floor(Math.random() * models.length)];
106
+ return { model, reason: 'exploration', confidence: epsilon, explored: true };
107
+ }
108
+ // Exploitation: pick highest blended score
109
+ const cell = state[cellKey] ?? {};
110
+ let bestModel = models[0];
111
+ let bestScore = -Infinity;
112
+ for (const m of models) {
113
+ const entry = cell[m];
114
+ const ema = entry?.ema ?? staticPrior(validTier, m);
115
+ const n = entry?.observations ?? 0;
116
+ const score = blended(ema, n, validTier, m);
117
+ if (score > bestScore) {
118
+ bestScore = score;
119
+ bestModel = m;
120
+ }
121
+ }
122
+ return { model: bestModel, reason: 'exploitation', confidence: 1 - epsilon, explored: false };
123
+ }
124
+ catch {
125
+ return { model: 'sonnet', reason: 'error fallback', confidence: 0.1, explored: false };
126
+ }
127
+ }
128
+ // reward: number in [0, 1]
129
+ export function recordReward(cellKey, model, reward, cwd) {
130
+ try {
131
+ const state = loadState(cwd);
132
+ if (!state[cellKey])
133
+ state[cellKey] = {};
134
+ const entry = state[cellKey][model] ?? { ema: reward, observations: 0 };
135
+ entry.ema = ALPHA * reward + (1 - ALPHA) * entry.ema;
136
+ entry.observations = (entry.observations ?? 0) + 1;
137
+ entry.lastUpdated = new Date().toISOString();
138
+ entry.lastReward = reward;
139
+ state[cellKey][model] = entry;
140
+ saveState(state, cwd);
141
+ }
142
+ catch {
143
+ // non-throwing
144
+ }
145
+ }
146
+ export function getRoutingStats(cwd) {
147
+ try {
148
+ const state = loadState(cwd);
149
+ const cells = {};
150
+ const flat = [];
151
+ let totalObservations = 0;
152
+ for (const [cellKey, models] of Object.entries(state)) {
153
+ if (cellKey.startsWith('_'))
154
+ continue;
155
+ cells[cellKey] ??= {};
156
+ for (const [model, entry] of Object.entries(models)) {
157
+ const obs = entry.observations ?? 0;
158
+ cells[cellKey][model] = { ema: entry.ema, observations: obs };
159
+ totalObservations += obs;
160
+ flat.push({ cell: cellKey, model, ema: entry.ema, observations: obs });
161
+ }
162
+ }
163
+ flat.sort((a, b) => b.ema - a.ema);
164
+ return { cells, totalObservations, topPerformers: flat.slice(0, 5), worstPerformers: flat.slice(-5).reverse() };
165
+ }
166
+ catch {
167
+ return { cells: {}, totalObservations: 0, topPerformers: [], worstPerformers: [] };
168
+ }
169
+ }
170
+ /**
171
+ * Loads cross-session routing state. If the state was last updated in a prior session,
172
+ * applies a mild decay (x0.95) to all EMA scores to account for staleness.
173
+ */
174
+ export function loadCrossSessionPriors(cwd) {
175
+ try {
176
+ const state = loadState(cwd);
177
+ const sessionStart = state._sessionStart;
178
+ if (!sessionStart)
179
+ return state; // no prior session marker
180
+ const lastMs = new Date(sessionStart).getTime();
181
+ if (isNaN(lastMs))
182
+ return state;
183
+ const stale = (Date.now() - lastMs) > 60_000; // more than 1 min old = different session
184
+ if (!stale)
185
+ return state;
186
+ for (const [cellKey, models] of Object.entries(state)) {
187
+ if (cellKey.startsWith('_'))
188
+ continue;
189
+ for (const entry of Object.values(models)) {
190
+ if (typeof entry.ema === 'number')
191
+ entry.ema = entry.ema * 0.95;
192
+ }
193
+ }
194
+ return state;
195
+ }
196
+ catch {
197
+ return {};
198
+ }
199
+ }
200
+ /**
201
+ * Records session start timestamp and triggers file survival checks.
202
+ * Call once at CLI session start.
203
+ */
204
+ export async function markSessionStart(cwd) {
205
+ try {
206
+ const state = loadState(cwd);
207
+ state._sessionStart = new Date().toISOString();
208
+ saveState(state, cwd);
209
+ await checkFileSurvival(cwd ?? process.cwd()).catch(() => { });
210
+ }
211
+ catch { /* non-throwing */ }
212
+ }
213
+ export function resetAdvisor(cwd) {
214
+ try {
215
+ saveState({}, cwd);
216
+ }
217
+ catch {
218
+ // non-throwing
219
+ }
220
+ }
221
+ //# sourceMappingURL=routing-advisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-advisor.js","sourceRoot":"","sources":["../../src/routing-advisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { FailureClassification, FailureType, RetryStrategyName, DispatchDecision } from './types.ts';
2
+ interface FailureResult {
3
+ error?: string;
4
+ stderr?: string;
5
+ output?: string;
6
+ stdout?: string;
7
+ durationMs?: number;
8
+ timeoutMs?: number;
9
+ quality?: number;
10
+ score?: number;
11
+ }
12
+ export declare function classifyFailure(result: FailureResult): FailureClassification;
13
+ interface StrategyResult {
14
+ strategy: RetryStrategyName;
15
+ reason: string;
16
+ newDecision?: DispatchDecision;
17
+ }
18
+ export declare function selectStrategy(failure: FailureClassification, originalDecision: DispatchDecision, attemptNumber: number): StrategyResult;
19
+ interface RetryDecision extends DispatchDecision {
20
+ _retryAttempt: number;
21
+ _retryReason: FailureType | string;
22
+ _retryStrategy: RetryStrategyName;
23
+ _contextBudget?: number;
24
+ _delayMs?: number;
25
+ _retryAsThink?: boolean;
26
+ _shouldDecompose?: boolean;
27
+ }
28
+ export declare function buildRetryDecision(originalDecision: DispatchDecision & {
29
+ _retryAttempt?: number;
30
+ }, strategy: RetryStrategyName, failure: FailureClassification): RetryDecision;
31
+ interface ShouldRetryResult {
32
+ retry: boolean;
33
+ reason: string;
34
+ strategy: RetryStrategyName;
35
+ decision?: RetryDecision;
36
+ }
37
+ export declare function shouldRetry(result: FailureResult, originalDecision: DispatchDecision & {
38
+ _retryAttempt?: number;
39
+ }, attemptNumber?: number): ShouldRetryResult;
40
+ export {};
@@ -0,0 +1,137 @@
1
+ // self-correct.ts — Failure analysis and retry strategy selection
2
+ const MODEL_TIER = { 'haiku': 1, 'sonnet': 2, 'opus': 3 };
3
+ const TIER_MODEL = { 1: 'haiku', 2: 'sonnet', 3: 'opus' };
4
+ const MAX_ATTEMPTS = 3;
5
+ function modelTier(model = '') {
6
+ const m = model.toLowerCase();
7
+ if (m.includes('haiku'))
8
+ return 1;
9
+ if (m.includes('opus'))
10
+ return 3;
11
+ return 2; // sonnet default
12
+ }
13
+ function matchesAny(text, keywords) {
14
+ const t = text.toLowerCase();
15
+ return keywords.some(k => t.includes(k));
16
+ }
17
+ // Export 1: classifyFailure(result)
18
+ export function classifyFailure(result) {
19
+ try {
20
+ const err = String(result?.error || result?.stderr || '');
21
+ const out = String(result?.output || result?.stdout || '');
22
+ const combined = err + ' ' + out;
23
+ const duration = result?.durationMs ?? 0;
24
+ const timeoutThreshold = result?.timeoutMs ?? 60_000;
25
+ if (matchesAny(combined, ['rate limit', 'ratelimit', '429', 'quota exceeded', 'capacity'])) {
26
+ return { type: 'rate-limit', confidence: 0.95, retryable: true };
27
+ }
28
+ if (matchesAny(combined, ['timeout', 'timed out']) || duration > timeoutThreshold) {
29
+ return { type: 'timeout', confidence: 0.9, retryable: true };
30
+ }
31
+ if (matchesAny(combined, ['context length', 'token limit', 'too long', 'maximum context', 'context window'])) {
32
+ return { type: 'context-overflow', confidence: 0.9, retryable: true };
33
+ }
34
+ if (matchesAny(combined, ['ambiguous', 'unclear', 'did you mean', 'which one', 'could you clarify', 'please clarify'])) {
35
+ return { type: 'specification', confidence: 0.85, retryable: false };
36
+ }
37
+ if (matchesAny(combined, ['unable to', "i don't know how", 'beyond my', 'cannot complete', 'incomplete'])) {
38
+ return { type: 'capability', confidence: 0.8, retryable: true };
39
+ }
40
+ // Heuristic: low quality output without explicit error signals capability gap
41
+ const quality = result?.quality ?? result?.score ?? null;
42
+ if (quality !== null && quality !== undefined && quality < 0.5) {
43
+ return { type: 'capability', confidence: 0.7, retryable: true };
44
+ }
45
+ return { type: 'unknown', confidence: 0.5, retryable: true };
46
+ }
47
+ catch {
48
+ return { type: 'unknown', confidence: 0, retryable: true };
49
+ }
50
+ }
51
+ // Export 2: selectStrategy(failure, originalDecision, attemptNumber)
52
+ export function selectStrategy(failure, originalDecision, attemptNumber) {
53
+ try {
54
+ if (!failure.retryable) {
55
+ return { strategy: 'give-up', reason: `failure type '${failure.type}' requires user input` };
56
+ }
57
+ if (attemptNumber >= MAX_ATTEMPTS) {
58
+ return { strategy: 'give-up', reason: `max attempts (${MAX_ATTEMPTS}) reached` };
59
+ }
60
+ const tier = modelTier(originalDecision?.model);
61
+ if (attemptNumber === 1) {
62
+ switch (failure.type) {
63
+ case 'capability':
64
+ if (tier >= 3)
65
+ return { strategy: 'split', newDecision: originalDecision, reason: 'already at max tier; decompose task' };
66
+ return { strategy: 'escalate', newDecision: originalDecision, reason: 'model lacked capability; escalating tier' };
67
+ case 'timeout':
68
+ return { strategy: 'wait-retry', newDecision: originalDecision, reason: 'timed out; retrying with delay' };
69
+ case 'rate-limit':
70
+ return { strategy: 'wait-retry', newDecision: originalDecision, reason: 'rate limited; retrying after delay' };
71
+ case 'context-overflow':
72
+ return { strategy: 'compress', newDecision: originalDecision, reason: 'context too large; compressing' };
73
+ case 'specification':
74
+ return { strategy: 'give-up', reason: 'ambiguous specification; user clarification needed' };
75
+ default: // unknown
76
+ if (tier >= 3)
77
+ return { strategy: 'split', newDecision: originalDecision, reason: 'unknown failure at max tier; decomposing' };
78
+ return { strategy: 'escalate', newDecision: originalDecision, reason: 'unknown failure; escalating as precaution' };
79
+ }
80
+ }
81
+ if (attemptNumber === 2) {
82
+ if (tier >= 3) {
83
+ return { strategy: 'split', newDecision: originalDecision, reason: 'max tier reached; splitting task' };
84
+ }
85
+ return { strategy: 'escalate', newDecision: originalDecision, reason: 'retry failed; escalating one final tier' };
86
+ }
87
+ return { strategy: 'give-up', reason: 'exhausted retry budget' };
88
+ }
89
+ catch {
90
+ return { strategy: 'give-up', reason: 'internal error in strategy selection' };
91
+ }
92
+ }
93
+ // Export 3: buildRetryDecision(originalDecision, strategy, failure)
94
+ export function buildRetryDecision(originalDecision, strategy, failure) {
95
+ try {
96
+ const base = {
97
+ ...originalDecision,
98
+ _retryAttempt: (originalDecision?._retryAttempt ?? 0) + 1,
99
+ _retryReason: failure.type,
100
+ _retryStrategy: strategy,
101
+ };
102
+ switch (strategy) {
103
+ case 'escalate': {
104
+ const tier = modelTier(originalDecision?.model);
105
+ const nextTier = Math.min(tier + 1, 3);
106
+ return { ...base, model: TIER_MODEL[nextTier] };
107
+ }
108
+ case 'compress':
109
+ return { ...base, _contextBudget: 0.5 };
110
+ case 'wait-retry':
111
+ return { ...base, _delayMs: 5000 };
112
+ default:
113
+ return base;
114
+ }
115
+ }
116
+ catch {
117
+ return { ...originalDecision, _retryAttempt: 1, _retryReason: 'error', _retryStrategy: strategy };
118
+ }
119
+ }
120
+ // Export 4: shouldRetry(result, originalDecision, attemptNumber)
121
+ export function shouldRetry(result, originalDecision, attemptNumber = 1) {
122
+ try {
123
+ if (attemptNumber >= MAX_ATTEMPTS)
124
+ return { retry: false, reason: `max attempts (${MAX_ATTEMPTS}) reached`, strategy: 'give-up' };
125
+ const failure = classifyFailure(result);
126
+ const { strategy, newDecision, reason } = selectStrategy(failure, originalDecision, attemptNumber);
127
+ if (strategy === 'give-up') {
128
+ return { retry: false, reason, strategy };
129
+ }
130
+ const decision = buildRetryDecision(newDecision ?? originalDecision, strategy, failure);
131
+ return { retry: true, decision, reason, strategy };
132
+ }
133
+ catch {
134
+ return { retry: false, reason: 'internal error in shouldRetry', strategy: 'give-up' };
135
+ }
136
+ }
137
+ //# sourceMappingURL=self-correct.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-correct.js","sourceRoot":"","sources":["../../src/self-correct.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ export interface LockResult {
2
+ acquired: boolean;
3
+ sessionId: string;
4
+ existingSession: string | null;
5
+ mode: 'primary' | 'takeover' | 'readonly';
6
+ }
7
+ export interface LockStatus {
8
+ active: boolean;
9
+ sessionId: string | null;
10
+ age: number | null;
11
+ }
12
+ /**
13
+ * Attempt to acquire the session lock.
14
+ * - If no lock exists or lock is stale: acquire as primary
15
+ * - If lock is fresh and held by another: return readonly
16
+ */
17
+ export declare function acquire({ force }?: {
18
+ force?: boolean | undefined;
19
+ }): LockResult;
20
+ /**
21
+ * Release the session lock (called at session end).
22
+ */
23
+ export declare function release(): void;
24
+ /**
25
+ * Check if this session currently holds the lock.
26
+ */
27
+ export declare function isOwner(): boolean;
28
+ /**
29
+ * Get current lock status without modifying it.
30
+ */
31
+ export declare function status(): LockStatus;
32
+ /**
33
+ * Manually heartbeat (useful if the automatic timer isn't running).
34
+ */
35
+ export declare function heartbeat(): void;
@@ -0,0 +1,134 @@
1
+ // session-lock.ts — Ensures one active HEAD session at a time.
2
+ // If two shells/chats open, only one owns the cognitive state.
3
+ // The other gets read-only access (can observe but not dispatch).
4
+ //
5
+ // "One ring rules them all" — no split-brain.
6
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ const STATE_DIR = join(process.cwd(), '.dualbrain');
9
+ const LOCK_FILE = join(STATE_DIR, 'session.lock');
10
+ const STALE_THRESHOLD_MS = 90_000; // 90 seconds without heartbeat = stale
11
+ const HEARTBEAT_INTERVAL_MS = 30_000;
12
+ let _heartbeatTimer = null;
13
+ let _sessionId = null;
14
+ /**
15
+ * Attempt to acquire the session lock.
16
+ * - If no lock exists or lock is stale: acquire as primary
17
+ * - If lock is fresh and held by another: return readonly
18
+ */
19
+ export function acquire({ force = false } = {}) {
20
+ mkdirSync(STATE_DIR, { recursive: true });
21
+ _sessionId = _generateSessionId();
22
+ const existing = _readLock();
23
+ if (!existing) {
24
+ // No lock — claim it
25
+ _writeLock(_sessionId);
26
+ _startHeartbeat();
27
+ return { acquired: true, sessionId: _sessionId, existingSession: null, mode: 'primary' };
28
+ }
29
+ // Same process (re-entry within same session) — always grant
30
+ if (existing.pid === process.pid) {
31
+ _sessionId = existing.sessionId;
32
+ return { acquired: true, sessionId: _sessionId, existingSession: null, mode: 'primary' };
33
+ }
34
+ const age = Date.now() - existing.heartbeat;
35
+ if (age > STALE_THRESHOLD_MS || force) {
36
+ // Stale or forced takeover
37
+ _writeLock(_sessionId);
38
+ _startHeartbeat();
39
+ return { acquired: true, sessionId: _sessionId, existingSession: existing.sessionId, mode: 'takeover' };
40
+ }
41
+ // Another session is active — go readonly
42
+ return { acquired: false, sessionId: _sessionId, existingSession: existing.sessionId, mode: 'readonly' };
43
+ }
44
+ /**
45
+ * Release the session lock (called at session end).
46
+ */
47
+ export function release() {
48
+ if (_heartbeatTimer) {
49
+ clearInterval(_heartbeatTimer);
50
+ _heartbeatTimer = null;
51
+ }
52
+ try {
53
+ const existing = _readLock();
54
+ if (existing && existing.sessionId === _sessionId) {
55
+ unlinkSync(LOCK_FILE);
56
+ }
57
+ }
58
+ catch { /* non-fatal */ }
59
+ }
60
+ /**
61
+ * Check if this session currently holds the lock.
62
+ */
63
+ export function isOwner() {
64
+ const existing = _readLock();
65
+ return existing?.sessionId === _sessionId;
66
+ }
67
+ /**
68
+ * Get current lock status without modifying it.
69
+ */
70
+ export function status() {
71
+ const existing = _readLock();
72
+ if (!existing)
73
+ return { active: false, sessionId: null, age: null };
74
+ return {
75
+ active: (Date.now() - existing.heartbeat) < STALE_THRESHOLD_MS,
76
+ sessionId: existing.sessionId,
77
+ age: Date.now() - existing.heartbeat,
78
+ };
79
+ }
80
+ /**
81
+ * Manually heartbeat (useful if the automatic timer isn't running).
82
+ */
83
+ export function heartbeat() {
84
+ if (!_sessionId)
85
+ return;
86
+ const existing = _readLock();
87
+ if (existing && existing.sessionId === _sessionId) {
88
+ _writeLock(_sessionId);
89
+ }
90
+ }
91
+ // ── Internal ──────────────────────────────────────────────────────────────────
92
+ function _generateSessionId() {
93
+ return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
94
+ }
95
+ function _readLock() {
96
+ try {
97
+ if (!existsSync(LOCK_FILE))
98
+ return null;
99
+ return JSON.parse(readFileSync(LOCK_FILE, 'utf8'));
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ function _writeLock(sessionId) {
106
+ const lock = {
107
+ sessionId,
108
+ heartbeat: Date.now(),
109
+ pid: process.pid,
110
+ };
111
+ writeFileSync(LOCK_FILE, JSON.stringify(lock));
112
+ }
113
+ function _startHeartbeat() {
114
+ if (_heartbeatTimer)
115
+ clearInterval(_heartbeatTimer);
116
+ _heartbeatTimer = setInterval(() => {
117
+ try {
118
+ const existing = _readLock();
119
+ if (existing && existing.sessionId === _sessionId) {
120
+ _writeLock(_sessionId);
121
+ }
122
+ else {
123
+ // Someone else took over — stop heartbeating
124
+ clearInterval(_heartbeatTimer);
125
+ _heartbeatTimer = null;
126
+ }
127
+ }
128
+ catch { /* non-fatal */ }
129
+ }, HEARTBEAT_INTERVAL_MS);
130
+ // Don't keep the process alive just for heartbeats
131
+ if (_heartbeatTimer.unref)
132
+ _heartbeatTimer.unref();
133
+ }
134
+ //# sourceMappingURL=session-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-lock.js","sourceRoot":"","sources":["../../src/session-lock.ts"],"names":[],"mappings":"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"}