dual-brain 0.2.30 → 0.3.0

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 (309) 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/task-classifier.mjs +328 -0
  240. package/hooks/vibe-router.mjs +387 -0
  241. package/package.json +29 -153
  242. package/src/agents/registry.mjs +0 -405
  243. package/src/awareness.mjs +0 -425
  244. package/src/brief.mjs +0 -266
  245. package/src/calibration.mjs +0 -148
  246. package/src/checkpoint.mjs +0 -109
  247. package/src/ci-triage.mjs +0 -191
  248. package/src/cognitive-loop.mjs +0 -562
  249. package/src/collaboration.mjs +0 -545
  250. package/src/context-intel.mjs +0 -158
  251. package/src/context.mjs +0 -389
  252. package/src/continuity.mjs +0 -298
  253. package/src/cost-tracker.mjs +0 -184
  254. package/src/debrief.mjs +0 -228
  255. package/src/decide.mjs +0 -1099
  256. package/src/decompose.mjs +0 -331
  257. package/src/detect.mjs +0 -702
  258. package/src/dispatch.mjs +0 -1447
  259. package/src/doctor.mjs +0 -1607
  260. package/src/envelope.mjs +0 -139
  261. package/src/failure-memory.mjs +0 -178
  262. package/src/fx.mjs +0 -276
  263. package/src/governance.mjs +0 -279
  264. package/src/handoff.mjs +0 -87
  265. package/src/head-protocol.mjs +0 -128
  266. package/src/head.mjs +0 -952
  267. package/src/health.mjs +0 -528
  268. package/src/inbox.mjs +0 -195
  269. package/src/index.mjs +0 -44
  270. package/src/install-hooks.mjs +0 -100
  271. package/src/integrity.mjs +0 -245
  272. package/src/intelligence.mjs +0 -447
  273. package/src/ledger.mjs +0 -196
  274. package/src/living-docs.mjs +0 -210
  275. package/src/memory-tiers.mjs +0 -193
  276. package/src/models.mjs +0 -363
  277. package/src/narrative.mjs +0 -169
  278. package/src/nextstep.mjs +0 -100
  279. package/src/observer.mjs +0 -241
  280. package/src/outcome.mjs +0 -400
  281. package/src/pipeline.mjs +0 -1711
  282. package/src/playbook.mjs +0 -257
  283. package/src/pr-agent.mjs +0 -214
  284. package/src/predictive.mjs +0 -250
  285. package/src/profile.mjs +0 -1411
  286. package/src/prompt-audit.mjs +0 -231
  287. package/src/prompt-intel.mjs +0 -325
  288. package/src/provider-context.mjs +0 -257
  289. package/src/receipt.mjs +0 -344
  290. package/src/recommendations.mjs +0 -296
  291. package/src/redact.mjs +0 -192
  292. package/src/replit.mjs +0 -1210
  293. package/src/repo.mjs +0 -445
  294. package/src/revert.mjs +0 -149
  295. package/src/routing-advisor.mjs +0 -204
  296. package/src/self-correct.mjs +0 -147
  297. package/src/session-lock.mjs +0 -160
  298. package/src/session.mjs +0 -1655
  299. package/src/settings-tui.mjs +0 -373
  300. package/src/setup-flow.mjs +0 -223
  301. package/src/signal.mjs +0 -115
  302. package/src/simmer.mjs +0 -241
  303. package/src/strategy.mjs +0 -235
  304. package/src/subscription.mjs +0 -212
  305. package/src/templates.mjs +0 -260
  306. package/src/think-engine.mjs +0 -428
  307. package/src/tui.mjs +0 -276
  308. package/src/update-check.mjs +0 -35
  309. 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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"}