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.
- package/.dual-brain/docs/claude-code-extension-points.md +32 -0
- package/.dual-brain/docs/data-tools-capabilities.md +181 -0
- package/.dual-brain/docs/ecosystem-tools.md +91 -0
- package/.dual-brain/docs/panel-handoff.md +124 -0
- package/.dual-brain/docs/ruflo-analysis.md +48 -0
- package/bin/dual-brain.mjs +56 -56
- package/dist/mcp-server/index.d.ts +27 -0
- package/dist/mcp-server/index.js +359 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/src/agent-protocol.d.ts +163 -0
- package/dist/src/agent-protocol.js +368 -0
- package/dist/src/agent-protocol.js.map +1 -0
- package/dist/src/agents/registry.d.ts +52 -0
- package/dist/src/agents/registry.js +393 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/awareness.d.ts +93 -0
- package/dist/src/awareness.js +406 -0
- package/dist/src/awareness.js.map +1 -0
- package/dist/src/brief.d.ts +48 -0
- package/dist/src/brief.js +179 -0
- package/dist/src/brief.js.map +1 -0
- package/dist/src/calibration.d.ts +32 -0
- package/dist/src/calibration.js +133 -0
- package/dist/src/calibration.js.map +1 -0
- package/dist/src/checkpoint.d.ts +33 -0
- package/dist/src/checkpoint.js +99 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/ci-triage.d.ts +33 -0
- package/dist/src/ci-triage.js +193 -0
- package/dist/src/ci-triage.js.map +1 -0
- package/dist/src/cognitive-loop.d.ts +56 -0
- package/dist/src/cognitive-loop.js +495 -0
- package/dist/src/cognitive-loop.js.map +1 -0
- package/dist/src/collaboration.d.ts +147 -0
- package/dist/src/collaboration.js +438 -0
- package/dist/src/collaboration.js.map +1 -0
- package/dist/src/context-intel.d.ts +47 -0
- package/dist/src/context-intel.js +156 -0
- package/dist/src/context-intel.js.map +1 -0
- package/dist/src/context.d.ts +53 -0
- package/dist/src/context.js +332 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/continuity.d.ts +89 -0
- package/dist/src/continuity.js +230 -0
- package/dist/src/continuity.js.map +1 -0
- package/dist/src/cost-tracker.d.ts +47 -0
- package/dist/src/cost-tracker.js +170 -0
- package/dist/src/cost-tracker.js.map +1 -0
- package/dist/src/debrief.d.ts +53 -0
- package/dist/src/debrief.js +222 -0
- package/dist/src/debrief.js.map +1 -0
- package/dist/src/decide.d.ts +96 -0
- package/dist/src/decide.js +744 -0
- package/dist/src/decide.js.map +1 -0
- package/dist/src/decompose.d.ts +39 -0
- package/dist/src/decompose.js +218 -0
- package/dist/src/decompose.js.map +1 -0
- package/dist/src/detect.d.ts +91 -0
- package/dist/src/detect.js +544 -0
- package/dist/src/detect.js.map +1 -0
- package/dist/src/dispatch.d.ts +154 -0
- package/dist/src/dispatch.js +1306 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/doctor.d.ts +421 -0
- package/dist/src/doctor.js +1689 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/engine.d.ts +70 -0
- package/dist/src/engine.js +155 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/envelope.d.ts +36 -0
- package/dist/src/envelope.js +80 -0
- package/dist/src/envelope.js.map +1 -0
- package/dist/src/failure-memory.d.ts +55 -0
- package/dist/src/failure-memory.js +175 -0
- package/dist/src/failure-memory.js.map +1 -0
- package/dist/src/fx.d.ts +87 -0
- package/dist/src/fx.js +272 -0
- package/dist/src/fx.js.map +1 -0
- package/dist/src/governance.d.ts +93 -0
- package/dist/src/governance.js +261 -0
- package/dist/src/governance.js.map +1 -0
- package/dist/src/handoff.d.ts +11 -0
- package/dist/src/handoff.js +90 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/head-protocol.d.ts +76 -0
- package/dist/src/head-protocol.js +109 -0
- package/dist/src/head-protocol.js.map +1 -0
- package/dist/src/head.d.ts +222 -0
- package/dist/src/head.js +765 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/health.d.ts +132 -0
- package/dist/src/health.js +435 -0
- package/dist/src/health.js.map +1 -0
- package/dist/src/inbox.d.ts +70 -0
- package/dist/src/inbox.js +218 -0
- package/dist/src/inbox.js.map +1 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/install-hooks.d.ts +13 -0
- package/dist/src/install-hooks.js +88 -0
- package/dist/src/install-hooks.js.map +1 -0
- package/dist/src/integrity.d.ts +59 -0
- package/dist/src/integrity.js +206 -0
- package/dist/src/integrity.js.map +1 -0
- package/dist/src/intelligence.d.ts +104 -0
- package/dist/src/intelligence.js +391 -0
- package/dist/src/intelligence.js.map +1 -0
- package/dist/src/ledger.d.ts +54 -0
- package/dist/src/ledger.js +179 -0
- package/dist/src/ledger.js.map +1 -0
- package/dist/src/living-docs.d.ts +14 -0
- package/dist/src/living-docs.js +197 -0
- package/dist/src/living-docs.js.map +1 -0
- package/dist/src/memory-tiers.d.ts +37 -0
- package/dist/src/memory-tiers.js +160 -0
- package/dist/src/memory-tiers.js.map +1 -0
- package/dist/src/model-profiles.d.ts +65 -0
- package/dist/src/model-profiles.js +568 -0
- package/dist/src/model-profiles.js.map +1 -0
- package/dist/src/models.d.ts +58 -0
- package/dist/src/models.js +327 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/narrative.d.ts +54 -0
- package/dist/src/narrative.js +163 -0
- package/dist/src/narrative.js.map +1 -0
- package/dist/src/nextstep.d.ts +16 -0
- package/dist/src/nextstep.js +103 -0
- package/dist/src/nextstep.js.map +1 -0
- package/dist/src/observer.d.ts +18 -0
- package/dist/src/observer.js +251 -0
- package/dist/src/observer.js.map +1 -0
- package/dist/src/outcome.d.ts +110 -0
- package/dist/src/outcome.js +377 -0
- package/dist/src/outcome.js.map +1 -0
- package/dist/src/pipeline.d.ts +167 -0
- package/dist/src/pipeline.js +1503 -0
- package/dist/src/pipeline.js.map +1 -0
- package/dist/src/playbook.d.ts +59 -0
- package/dist/src/playbook.js +238 -0
- package/dist/src/playbook.js.map +1 -0
- package/dist/src/pr-agent.d.ts +97 -0
- package/dist/src/pr-agent.js +195 -0
- package/dist/src/pr-agent.js.map +1 -0
- package/dist/src/predictive.d.ts +57 -0
- package/dist/src/predictive.js +230 -0
- package/dist/src/predictive.js.map +1 -0
- package/dist/src/profile.d.ts +294 -0
- package/dist/src/profile.js +1347 -0
- package/dist/src/profile.js.map +1 -0
- package/dist/src/prompt-audit.d.ts +22 -0
- package/dist/src/prompt-audit.js +194 -0
- package/dist/src/prompt-audit.js.map +1 -0
- package/dist/src/prompt-intel.d.ts +12 -0
- package/dist/src/prompt-intel.js +321 -0
- package/dist/src/prompt-intel.js.map +1 -0
- package/dist/src/provider-context.d.ts +121 -0
- package/dist/src/provider-context.js +222 -0
- package/dist/src/provider-context.js.map +1 -0
- package/dist/src/provider-manager.d.ts +92 -0
- package/dist/src/provider-manager.js +428 -0
- package/dist/src/provider-manager.js.map +1 -0
- package/dist/src/receipt.d.ts +87 -0
- package/dist/src/receipt.js +326 -0
- package/dist/src/receipt.js.map +1 -0
- package/dist/src/recommendations.d.ts +13 -0
- package/dist/src/recommendations.js +291 -0
- package/dist/src/recommendations.js.map +1 -0
- package/dist/src/redact.d.ts +15 -0
- package/dist/src/redact.js +129 -0
- package/dist/src/redact.js.map +1 -0
- package/dist/src/replit.d.ts +397 -0
- package/dist/src/replit.js +1160 -0
- package/dist/src/replit.js.map +1 -0
- package/dist/src/repo.d.ts +149 -0
- package/dist/src/repo.js +416 -0
- package/dist/src/repo.js.map +1 -0
- package/dist/src/revert.d.ts +30 -0
- package/dist/src/revert.js +166 -0
- package/dist/src/revert.js.map +1 -0
- package/dist/src/room.d.ts +102 -0
- package/dist/src/room.js +212 -0
- package/dist/src/room.js.map +1 -0
- package/dist/src/routing-advisor.d.ts +57 -0
- package/dist/src/routing-advisor.js +221 -0
- package/dist/src/routing-advisor.js.map +1 -0
- package/dist/src/self-correct.d.ts +40 -0
- package/dist/src/self-correct.js +137 -0
- package/dist/src/self-correct.js.map +1 -0
- package/dist/src/session-lock.d.ts +35 -0
- package/dist/src/session-lock.js +134 -0
- package/dist/src/session-lock.js.map +1 -0
- package/dist/src/session.d.ts +267 -0
- package/dist/src/session.js +1660 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/settings-tui.d.ts +5 -0
- package/dist/src/settings-tui.js +422 -0
- package/dist/src/settings-tui.js.map +1 -0
- package/dist/src/setup-flow.d.ts +63 -0
- package/dist/src/setup-flow.js +233 -0
- package/dist/src/setup-flow.js.map +1 -0
- package/dist/src/signal.d.ts +19 -0
- package/dist/src/signal.js +122 -0
- package/dist/src/signal.js.map +1 -0
- package/dist/src/simmer.d.ts +85 -0
- package/dist/src/simmer.js +224 -0
- package/dist/src/simmer.js.map +1 -0
- package/dist/src/state-export.d.ts +129 -0
- package/dist/src/state-export.js +233 -0
- package/dist/src/state-export.js.map +1 -0
- package/dist/src/strategy.d.ts +54 -0
- package/dist/src/strategy.js +95 -0
- package/dist/src/strategy.js.map +1 -0
- package/dist/src/subscription.d.ts +40 -0
- package/dist/src/subscription.js +189 -0
- package/dist/src/subscription.js.map +1 -0
- package/dist/src/templates.d.ts +208 -0
- package/dist/src/templates.js +238 -0
- package/dist/src/templates.js.map +1 -0
- package/dist/src/test.d.ts +9 -0
- package/dist/src/test.js +1173 -0
- package/dist/src/test.js.map +1 -0
- package/dist/src/think-engine.d.ts +67 -0
- package/dist/src/think-engine.js +412 -0
- package/dist/src/think-engine.js.map +1 -0
- package/dist/src/tui.d.ts +71 -0
- package/dist/src/tui.js +242 -0
- package/dist/src/tui.js.map +1 -0
- package/dist/src/types.d.ts +177 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/update-check.d.ts +7 -0
- package/dist/src/update-check.js +36 -0
- package/dist/src/update-check.js.map +1 -0
- package/dist/src/wave-planner.d.ts +30 -0
- package/dist/src/wave-planner.js +281 -0
- package/dist/src/wave-planner.js.map +1 -0
- package/hooks/head-guard.sh +41 -0
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/vibe-router.mjs +387 -0
- package/package.json +29 -153
- package/src/agents/registry.mjs +0 -405
- package/src/awareness.mjs +0 -425
- package/src/brief.mjs +0 -266
- package/src/calibration.mjs +0 -148
- package/src/checkpoint.mjs +0 -109
- package/src/ci-triage.mjs +0 -191
- package/src/cognitive-loop.mjs +0 -562
- package/src/collaboration.mjs +0 -545
- package/src/context-intel.mjs +0 -158
- package/src/context.mjs +0 -389
- package/src/continuity.mjs +0 -298
- package/src/cost-tracker.mjs +0 -184
- package/src/debrief.mjs +0 -228
- package/src/decide.mjs +0 -1099
- package/src/decompose.mjs +0 -331
- package/src/detect.mjs +0 -702
- package/src/dispatch.mjs +0 -1447
- package/src/doctor.mjs +0 -1607
- package/src/envelope.mjs +0 -139
- package/src/failure-memory.mjs +0 -178
- package/src/fx.mjs +0 -276
- package/src/governance.mjs +0 -279
- package/src/handoff.mjs +0 -87
- package/src/head-protocol.mjs +0 -128
- package/src/head.mjs +0 -952
- package/src/health.mjs +0 -528
- package/src/inbox.mjs +0 -195
- package/src/index.mjs +0 -44
- package/src/install-hooks.mjs +0 -100
- package/src/integrity.mjs +0 -245
- package/src/intelligence.mjs +0 -447
- package/src/ledger.mjs +0 -196
- package/src/living-docs.mjs +0 -210
- package/src/memory-tiers.mjs +0 -193
- package/src/models.mjs +0 -363
- package/src/narrative.mjs +0 -169
- package/src/nextstep.mjs +0 -100
- package/src/observer.mjs +0 -241
- package/src/outcome.mjs +0 -400
- package/src/pipeline.mjs +0 -1711
- package/src/playbook.mjs +0 -257
- package/src/pr-agent.mjs +0 -214
- package/src/predictive.mjs +0 -250
- package/src/profile.mjs +0 -1411
- package/src/prompt-audit.mjs +0 -231
- package/src/prompt-intel.mjs +0 -325
- package/src/provider-context.mjs +0 -257
- package/src/receipt.mjs +0 -344
- package/src/recommendations.mjs +0 -296
- package/src/redact.mjs +0 -192
- package/src/replit.mjs +0 -1210
- package/src/repo.mjs +0 -445
- package/src/revert.mjs +0 -149
- package/src/routing-advisor.mjs +0 -204
- package/src/self-correct.mjs +0 -147
- package/src/session-lock.mjs +0 -160
- package/src/session.mjs +0 -1655
- package/src/settings-tui.mjs +0 -373
- package/src/setup-flow.mjs +0 -223
- package/src/signal.mjs +0 -115
- package/src/simmer.mjs +0 -241
- package/src/strategy.mjs +0 -235
- package/src/subscription.mjs +0 -212
- package/src/templates.mjs +0 -260
- package/src/think-engine.mjs +0 -428
- package/src/tui.mjs +0 -276
- package/src/update-check.mjs +0 -35
- package/src/wave-planner.mjs +0 -294
package/src/envelope.mjs
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// envelope.mjs — Dispatch envelopes that carry understanding to workers.
|
|
2
|
-
//
|
|
3
|
-
// Instead of workers getting bare instructions ("edit file X, add Y"),
|
|
4
|
-
// they get an envelope containing:
|
|
5
|
-
// 1. Context preamble — narrative excerpt explaining the "why"
|
|
6
|
-
// 2. Contract — the typed task (objective, scope, acceptance criteria)
|
|
7
|
-
// 3. Preventions — predicted failure modes and how to avoid them
|
|
8
|
-
// 4. Debrief format — how to report back
|
|
9
|
-
//
|
|
10
|
-
// This is the difference between "do this thing" and "here's where we are,
|
|
11
|
-
// here's why this matters, here's what to do, here's what to watch for."
|
|
12
|
-
|
|
13
|
-
import * as narrative from './narrative.mjs';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {object} Envelope
|
|
17
|
-
* @property {string} preamble - Narrative context for the worker
|
|
18
|
-
* @property {string} contract - The actual task specification
|
|
19
|
-
* @property {string} preventions - Predicted failure modes and mitigations
|
|
20
|
-
* @property {string} debriefFormat - How to report back
|
|
21
|
-
* @property {string} full - Complete prompt ready to send
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Build a dispatch envelope for a worker agent.
|
|
26
|
-
*
|
|
27
|
-
* @param {object} agentSpec - From wave-planner
|
|
28
|
-
* @param {string} agentSpec.objective - What to accomplish
|
|
29
|
-
* @param {string[]} agentSpec.scope - Files/areas in scope
|
|
30
|
-
* @param {string} agentSpec.tier - Worker tier (execute, search, review, etc)
|
|
31
|
-
* @param {object} opts
|
|
32
|
-
* @param {string} opts.preventions - From predictive.mjs
|
|
33
|
-
* @param {string} opts.debriefInstruction - From debrief.mjs
|
|
34
|
-
* @param {string} opts.inboxBrief - Messages for this worker
|
|
35
|
-
* @param {object} opts.contract - Additional contract fields (acceptance criteria, risk, allowed ops)
|
|
36
|
-
* @returns {Envelope}
|
|
37
|
-
*/
|
|
38
|
-
export function build(agentSpec, opts = {}) {
|
|
39
|
-
const { preventions, debriefInstruction, inboxBrief, contract } = opts;
|
|
40
|
-
|
|
41
|
-
// Get narrative excerpt — the "being in the song" piece
|
|
42
|
-
const preamble = _buildPreamble(agentSpec);
|
|
43
|
-
|
|
44
|
-
// Build contract section
|
|
45
|
-
const contractText = _buildContract(agentSpec, contract);
|
|
46
|
-
|
|
47
|
-
// Assemble full prompt
|
|
48
|
-
const sections = [];
|
|
49
|
-
|
|
50
|
-
if (preamble) {
|
|
51
|
-
sections.push(`## Context\n${preamble}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
sections.push(`## Task\n${contractText}`);
|
|
55
|
-
|
|
56
|
-
if (inboxBrief) {
|
|
57
|
-
sections.push(`## Notes\n${inboxBrief}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (preventions) {
|
|
61
|
-
sections.push(`## Watch For\n${preventions}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (debriefInstruction) {
|
|
65
|
-
sections.push(`## When Done\n${debriefInstruction}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const full = sections.join('\n\n');
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
preamble,
|
|
72
|
-
contract: contractText,
|
|
73
|
-
preventions: preventions || '',
|
|
74
|
-
debriefFormat: debriefInstruction || '',
|
|
75
|
-
full,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Build a lightweight envelope for simple/fast dispatches.
|
|
81
|
-
* Used when the task is straightforward and doesn't need full context.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} objective
|
|
84
|
-
* @param {string[]} scope
|
|
85
|
-
* @returns {string} Simple prompt string
|
|
86
|
-
*/
|
|
87
|
-
export function buildLight(objective, scope = []) {
|
|
88
|
-
const parts = [objective];
|
|
89
|
-
if (scope.length > 0) {
|
|
90
|
-
parts.push(`Scope: ${scope.join(', ')}`);
|
|
91
|
-
}
|
|
92
|
-
return parts.join('\n');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Internal ──────────────────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
function _buildPreamble(agentSpec) {
|
|
98
|
-
const narr = narrative.excerpt(400);
|
|
99
|
-
if (!narr) return '';
|
|
100
|
-
|
|
101
|
-
// Tailor the preamble based on tier
|
|
102
|
-
const tier = (agentSpec.tier || '').toLowerCase();
|
|
103
|
-
|
|
104
|
-
if (tier === 'search' || tier === 'recon') {
|
|
105
|
-
// Search agents need less context, more focus on what to look for
|
|
106
|
-
return narr.length > 200 ? narr.slice(-200) : narr;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Implementation agents get fuller context
|
|
110
|
-
return narr;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function _buildContract(agentSpec, extra = {}) {
|
|
114
|
-
const parts = [];
|
|
115
|
-
|
|
116
|
-
parts.push(agentSpec.objective);
|
|
117
|
-
|
|
118
|
-
if (agentSpec.scope?.length) {
|
|
119
|
-
parts.push(`\nScope: ${agentSpec.scope.join(', ')}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (extra?.acceptanceCriteria) {
|
|
123
|
-
parts.push(`\nDone when: ${extra.acceptanceCriteria}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (extra?.risk) {
|
|
127
|
-
parts.push(`\nRisk level: ${extra.risk}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (extra?.allowedOps) {
|
|
131
|
-
parts.push(`\nAllowed: ${extra.allowedOps.join(', ')}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (agentSpec.conditionalPivot) {
|
|
135
|
-
parts.push(`\nConditional: if ${agentSpec.conditionalPivot.if} → ${agentSpec.conditionalPivot.then}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return parts.join('');
|
|
139
|
-
}
|
package/src/failure-memory.mjs
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* failure-memory.mjs — Track task failures and enable automatic escalation.
|
|
4
|
-
*
|
|
5
|
-
* Exports: recordFailure, checkFailureHistory, formatEscalation,
|
|
6
|
-
* clearFailures, getFailureStats
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { readFileSync, appendFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import { randomUUID } from 'crypto';
|
|
12
|
-
|
|
13
|
-
const STOP_WORDS = new Set(['the','a','an','is','in','on','at','to','for','of','and','or','with','this','that','it','be','was','are','were','has','have','had','do','does','did','not','from','by','as','if','but','we','i','you']);
|
|
14
|
-
const WINDOW_48H = 48 * 60 * 60 * 1000;
|
|
15
|
-
|
|
16
|
-
const DEPTH_ORDER = ['low', 'medium', 'high', 'ultra'];
|
|
17
|
-
const MODEL_ORDER = ['haiku', 'sonnet', 'opus'];
|
|
18
|
-
|
|
19
|
-
function failuresPath(cwd) {
|
|
20
|
-
const dir = join(cwd, '.dualbrain');
|
|
21
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
22
|
-
return join(dir, 'failures.jsonl');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function categorizeError(error = '') {
|
|
26
|
-
const e = error.toLowerCase();
|
|
27
|
-
if (/test|assert|expect/.test(e)) return 'test-failure';
|
|
28
|
-
if (/timeout|timed out/.test(e)) return 'timeout';
|
|
29
|
-
if (/syntax|parse|unexpected token/.test(e)) return 'syntax-error';
|
|
30
|
-
if (/permission|eacces/.test(e)) return 'permission-error';
|
|
31
|
-
if (/not found|enoent/.test(e)) return 'not-found';
|
|
32
|
-
return 'unknown';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function tokenize(text = '') {
|
|
36
|
-
return text.toLowerCase().split(/\W+/).filter(w => w.length > 2 && !STOP_WORDS.has(w));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function similarity(promptA, promptB, filesA = [], filesB = []) {
|
|
40
|
-
const wordsA = new Set(tokenize(promptA));
|
|
41
|
-
const wordsB = new Set(tokenize(promptB));
|
|
42
|
-
if (!wordsA.size && !wordsB.size) return 0;
|
|
43
|
-
const shared = [...wordsA].filter(w => wordsB.has(w)).length;
|
|
44
|
-
const wordScore = shared / Math.max(wordsA.size, wordsB.size);
|
|
45
|
-
const sharedFiles = filesA.some(f => filesB.includes(f));
|
|
46
|
-
return sharedFiles ? Math.max(wordScore, 0.5) : wordScore;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function readFailures(cwd) {
|
|
50
|
-
const path = failuresPath(cwd);
|
|
51
|
-
if (!existsSync(path)) return [];
|
|
52
|
-
return readFileSync(path, 'utf8')
|
|
53
|
-
.split('\n')
|
|
54
|
-
.filter(Boolean)
|
|
55
|
-
.map(line => { try { return JSON.parse(line); } catch { return null; } })
|
|
56
|
-
.filter(Boolean);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function writeAll(cwd, records) {
|
|
60
|
-
writeFileSync(failuresPath(cwd), records.map(r => JSON.stringify(r)).join('\n') + '\n');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function bumpDepth(depth) {
|
|
64
|
-
const idx = DEPTH_ORDER.indexOf(depth);
|
|
65
|
-
return idx === -1 || idx >= DEPTH_ORDER.length - 1 ? 'ultra' : DEPTH_ORDER[idx + 1];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function bumpModel(model = '') {
|
|
69
|
-
const m = model.toLowerCase();
|
|
70
|
-
const match = MODEL_ORDER.find(k => m.includes(k)) ?? 'sonnet';
|
|
71
|
-
const idx = MODEL_ORDER.indexOf(match);
|
|
72
|
-
return idx >= MODEL_ORDER.length - 1 ? `claude-opus-4-5` : `claude-${MODEL_ORDER[idx + 1]}-4-5`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
export async function recordFailure(prompt, plan = {}, error = '', cwd = process.cwd()) {
|
|
78
|
-
const record = {
|
|
79
|
-
id: randomUUID(),
|
|
80
|
-
timestamp: Date.now(),
|
|
81
|
-
prompt,
|
|
82
|
-
promptWords: tokenize(prompt),
|
|
83
|
-
model: plan.model ?? null,
|
|
84
|
-
reasoningDepth: plan.reasoningDepth ?? null,
|
|
85
|
-
tier: plan.tier ?? null,
|
|
86
|
-
error: String(error),
|
|
87
|
-
errorCategory: categorizeError(error),
|
|
88
|
-
files: plan.files ?? [],
|
|
89
|
-
escalatedFrom: plan.escalatedFrom ?? null,
|
|
90
|
-
resolved: false,
|
|
91
|
-
};
|
|
92
|
-
appendFileSync(failuresPath(cwd), JSON.stringify(record) + '\n');
|
|
93
|
-
return record;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function checkFailureHistory(prompt, files = [], cwd = process.cwd()) {
|
|
97
|
-
const cutoff = Date.now() - WINDOW_48H;
|
|
98
|
-
const all = readFailures(cwd);
|
|
99
|
-
const recent = all.filter(r => !r.resolved && r.timestamp >= cutoff);
|
|
100
|
-
const matches = recent
|
|
101
|
-
.map(r => ({ r, score: similarity(prompt, r.prompt, files, r.files ?? []) }))
|
|
102
|
-
.filter(({ score }) => score >= 0.4)
|
|
103
|
-
.sort((a, b) => b.r.timestamp - a.r.timestamp);
|
|
104
|
-
|
|
105
|
-
const count = matches.length;
|
|
106
|
-
const last = matches[0]?.r ?? null;
|
|
107
|
-
|
|
108
|
-
const escalation = { recommended: false, fromModel: null, toModel: null, fromDepth: null, toDepth: null, useChallenger: false, reason: '' };
|
|
109
|
-
|
|
110
|
-
if (count >= 1) {
|
|
111
|
-
escalation.recommended = true;
|
|
112
|
-
escalation.fromModel = last.model;
|
|
113
|
-
escalation.fromDepth = last.reasoningDepth;
|
|
114
|
-
|
|
115
|
-
if (count === 1) {
|
|
116
|
-
escalation.toDepth = bumpDepth(last.reasoningDepth ?? 'medium');
|
|
117
|
-
escalation.toModel = last.model;
|
|
118
|
-
escalation.useChallenger = false;
|
|
119
|
-
escalation.reason = `1 prior failure on similar task, bumping depth to ${escalation.toDepth}`;
|
|
120
|
-
} else if (count === 2) {
|
|
121
|
-
escalation.toDepth = 'ultra';
|
|
122
|
-
escalation.toModel = last.model?.includes('opus') ? last.model : bumpModel(last.model);
|
|
123
|
-
escalation.useChallenger = false;
|
|
124
|
-
escalation.reason = `2 prior failures on similar task, escalating to Opus + ultrathink`;
|
|
125
|
-
} else {
|
|
126
|
-
escalation.toDepth = 'ultra';
|
|
127
|
-
escalation.toModel = last.model?.includes('opus') ? last.model : bumpModel(last.model);
|
|
128
|
-
escalation.useChallenger = true;
|
|
129
|
-
escalation.reason = `${count} prior failures on similar task, forcing dual-brain`;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { hasPriorFailures: count > 0, failureCount: count, lastFailure: last, escalation };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function formatEscalation(escalation) {
|
|
137
|
-
if (!escalation?.recommended) return '';
|
|
138
|
-
const prev = [escalation.fromModel, escalation.fromDepth].filter(Boolean).join(', ') || 'unknown';
|
|
139
|
-
const next = [escalation.toModel, escalation.toDepth, escalation.useChallenger ? 'GPT challenger' : null].filter(Boolean).join(' + ');
|
|
140
|
-
return `⚡ Strategy changed\n Previous: failed (${prev})\n Escalated: ${next}\n Reason: ${escalation.reason}`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export async function clearFailures(prompt, cwd = process.cwd()) {
|
|
144
|
-
const all = readFailures(cwd);
|
|
145
|
-
const promptWords = tokenize(prompt);
|
|
146
|
-
const fakePrompt = promptWords.join(' ');
|
|
147
|
-
let changed = false;
|
|
148
|
-
const updated = all.map(r => {
|
|
149
|
-
if (!r.resolved && similarity(fakePrompt, r.prompt) >= 0.4) {
|
|
150
|
-
changed = true;
|
|
151
|
-
return { ...r, resolved: true };
|
|
152
|
-
}
|
|
153
|
-
return r;
|
|
154
|
-
});
|
|
155
|
-
if (changed) writeAll(cwd, updated);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export async function getFailureStats(cwd = process.cwd()) {
|
|
159
|
-
const all = readFailures(cwd);
|
|
160
|
-
const byCategory = {};
|
|
161
|
-
let resolved = 0;
|
|
162
|
-
let escalationSum = 0;
|
|
163
|
-
let escalationCount = 0;
|
|
164
|
-
|
|
165
|
-
for (const r of all) {
|
|
166
|
-
if (r.resolved) resolved++;
|
|
167
|
-
byCategory[r.errorCategory] = (byCategory[r.errorCategory] ?? 0) + 1;
|
|
168
|
-
if (r.escalatedFrom) { escalationSum++; escalationCount++; }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
total: all.length,
|
|
173
|
-
resolved,
|
|
174
|
-
unresolved: all.length - resolved,
|
|
175
|
-
byCategory,
|
|
176
|
-
avgEscalationsToResolve: escalationCount ? +(escalationSum / escalationCount).toFixed(2) : 0,
|
|
177
|
-
};
|
|
178
|
-
}
|
package/src/fx.mjs
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
// fx.mjs — zero-dependency animated shell effects for dual-brain CLI
|
|
2
|
-
|
|
3
|
-
const isTTY = process.stdout.isTTY && !process.env.CI;
|
|
4
|
-
const hasColor = isTTY && !process.env.NO_COLOR;
|
|
5
|
-
const isUnicode = process.platform !== 'win32' || process.env.WT_SESSION;
|
|
6
|
-
|
|
7
|
-
const c = {
|
|
8
|
-
reset: '\x1b[0m',
|
|
9
|
-
bold: '\x1b[1m',
|
|
10
|
-
dim: '\x1b[2m',
|
|
11
|
-
green: '\x1b[32m',
|
|
12
|
-
red: '\x1b[31m',
|
|
13
|
-
yellow: '\x1b[33m',
|
|
14
|
-
blue: '\x1b[34m',
|
|
15
|
-
magenta: '\x1b[35m',
|
|
16
|
-
cyan: '\x1b[36m',
|
|
17
|
-
white: '\x1b[37m',
|
|
18
|
-
gray: '\x1b[90m',
|
|
19
|
-
bgGreen: '\x1b[42m',
|
|
20
|
-
bgRed: '\x1b[41m',
|
|
21
|
-
bgYellow: '\x1b[43m',
|
|
22
|
-
bgBlue: '\x1b[44m',
|
|
23
|
-
bgMagenta: '\x1b[45m',
|
|
24
|
-
clearLine: '\x1b[2K',
|
|
25
|
-
cursorUp: '\x1b[1A',
|
|
26
|
-
cursorHide: '\x1b[?25l',
|
|
27
|
-
cursorShow: '\x1b[?25h',
|
|
28
|
-
saveCursor: '\x1b[s',
|
|
29
|
-
restoreCursor: '\x1b[u'
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function color(text, ...styles) {
|
|
33
|
-
if (!hasColor) return text;
|
|
34
|
-
return styles.join('') + text + c.reset;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const colors = c;
|
|
38
|
-
|
|
39
|
-
export function sleep(ms) {
|
|
40
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function clearScreen() {
|
|
44
|
-
if (isTTY) process.stdout.write('\x1b[2J\x1b[H');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function nl(n = 1) {
|
|
48
|
-
process.stdout.write('\n'.repeat(n));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function getMode() {
|
|
52
|
-
if (process.env.CI) return 'ci';
|
|
53
|
-
if (!process.stdout.isTTY) return 'plain';
|
|
54
|
-
if (process.env.DUAL_BRAIN_FX === 'subtle') return 'subtle';
|
|
55
|
-
return 'full';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function spinner(text) {
|
|
59
|
-
const frames = isUnicode ? ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'] : ['|','/','-','\\'];
|
|
60
|
-
let i = 0, interval = null, currentText = text;
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
start() {
|
|
64
|
-
if (!isTTY) { process.stdout.write(currentText + '\n'); return this; }
|
|
65
|
-
process.stdout.write(c.cursorHide);
|
|
66
|
-
interval = setInterval(() => {
|
|
67
|
-
process.stdout.write(`\r${c.clearLine} ${color(frames[i % frames.length], c.cyan)} ${currentText}`);
|
|
68
|
-
i++;
|
|
69
|
-
}, 80);
|
|
70
|
-
return this;
|
|
71
|
-
},
|
|
72
|
-
update(newText) { currentText = newText; return this; },
|
|
73
|
-
succeed(msg) {
|
|
74
|
-
if (interval) clearInterval(interval);
|
|
75
|
-
const sym = isUnicode ? '✓' : '+';
|
|
76
|
-
process.stdout.write(`\r${c.clearLine} ${color(sym, c.green)} ${msg || currentText}\n`);
|
|
77
|
-
if (isTTY) process.stdout.write(c.cursorShow);
|
|
78
|
-
return this;
|
|
79
|
-
},
|
|
80
|
-
fail(msg) {
|
|
81
|
-
if (interval) clearInterval(interval);
|
|
82
|
-
const sym = isUnicode ? '✗' : 'x';
|
|
83
|
-
process.stdout.write(`\r${c.clearLine} ${color(sym, c.red)} ${msg || currentText}\n`);
|
|
84
|
-
if (isTTY) process.stdout.write(c.cursorShow);
|
|
85
|
-
return this;
|
|
86
|
-
},
|
|
87
|
-
warn(msg) {
|
|
88
|
-
if (interval) clearInterval(interval);
|
|
89
|
-
const sym = isUnicode ? '⚠' : '!';
|
|
90
|
-
process.stdout.write(`\r${c.clearLine} ${color(sym, c.yellow)} ${msg || currentText}\n`);
|
|
91
|
-
if (isTTY) process.stdout.write(c.cursorShow);
|
|
92
|
-
return this;
|
|
93
|
-
},
|
|
94
|
-
stop() {
|
|
95
|
-
if (interval) clearInterval(interval);
|
|
96
|
-
process.stdout.write(`\r${c.clearLine}`);
|
|
97
|
-
if (isTTY) process.stdout.write(c.cursorShow);
|
|
98
|
-
return this;
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function progress(current, total, label = '', width = 30) {
|
|
104
|
-
const pct = Math.min(1, current / total);
|
|
105
|
-
if (!isTTY) {
|
|
106
|
-
process.stdout.write(`${Math.round(pct * 100)}% ${label}\n`);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
const filled = Math.round(pct * width);
|
|
110
|
-
const empty = width - filled;
|
|
111
|
-
const bar = isUnicode
|
|
112
|
-
? color('█'.repeat(filled) + '░'.repeat(empty), c.cyan)
|
|
113
|
-
: color('#'.repeat(filled) + '-'.repeat(empty), c.cyan);
|
|
114
|
-
const pctStr = String(Math.round(pct * 100)).padStart(3) + '%';
|
|
115
|
-
process.stdout.write(`\r${c.clearLine} ${bar} ${color(pctStr, c.bold)} ${label}`);
|
|
116
|
-
if (current >= total) process.stdout.write('\n');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function success(text) {
|
|
120
|
-
const sym = isUnicode ? '✓' : '+';
|
|
121
|
-
process.stdout.write(` ${color(sym, c.green)} ${text}\n`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function error(text) {
|
|
125
|
-
const sym = isUnicode ? '✗' : 'x';
|
|
126
|
-
process.stdout.write(` ${color(sym, c.red)} ${text}\n`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function warn(text) {
|
|
130
|
-
const sym = isUnicode ? '⚠' : '!';
|
|
131
|
-
process.stdout.write(` ${color(sym, c.yellow)} ${text}\n`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function info(text) {
|
|
135
|
-
const sym = isUnicode ? 'ℹ' : 'i';
|
|
136
|
-
process.stdout.write(` ${color(sym, c.blue)} ${text}\n`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function dim(text) {
|
|
140
|
-
process.stdout.write(`${color(text, c.gray)}\n`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function step(current, total, text) {
|
|
144
|
-
if (!isUnicode) {
|
|
145
|
-
process.stdout.write(` [${current}/${total}] ${text}\n`);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const dots = [];
|
|
149
|
-
for (let i = 1; i <= total; i++) {
|
|
150
|
-
if (i < current) dots.push(color('●', c.green));
|
|
151
|
-
else if (i === current) dots.push(color('●', c.cyan));
|
|
152
|
-
else dots.push(color('○', c.gray));
|
|
153
|
-
}
|
|
154
|
-
process.stdout.write(` ${dots.join(' ')} ${color(`Step ${current} of ${total}`, c.bold)} · ${text}\n`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function banner(text) {
|
|
158
|
-
const pkg = 'DUAL-BRAIN';
|
|
159
|
-
const inner = ` ${isUnicode ? '🧠' : '**'} ${pkg} ${text} `;
|
|
160
|
-
const width = inner.length + 2;
|
|
161
|
-
if (!isUnicode || !hasColor) {
|
|
162
|
-
process.stdout.write(`\n +${'='.repeat(width - 2)}+\n | ${inner} |\n +${'='.repeat(width - 2)}+\n\n`);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const top = ` ╔${'═'.repeat(width)}╗`;
|
|
166
|
-
const mid = ` ║${inner}║`;
|
|
167
|
-
const bot = ` ╚${'═'.repeat(width)}╝`;
|
|
168
|
-
process.stdout.write(`\n${color(top, c.cyan, c.bold)}\n${color(mid, c.cyan, c.bold)}\n${color(bot, c.cyan, c.bold)}\n\n`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function box(content, options = {}) {
|
|
172
|
-
const { color: colorName = 'cyan', padding = 1, title = '' } = options;
|
|
173
|
-
const ansiColor = c[colorName] || c.cyan;
|
|
174
|
-
const lines = Array.isArray(content) ? content : content.split('\n');
|
|
175
|
-
const innerWidth = Math.max(...lines.map(l => stripAnsi(l).length), title ? stripAnsi(title).length : 0) + padding * 2;
|
|
176
|
-
|
|
177
|
-
function draw(text, ansi) {
|
|
178
|
-
if (!hasColor) return text;
|
|
179
|
-
return ansi + text + c.reset;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const titleStr = title ? ` ${title} ` : '';
|
|
183
|
-
const topFill = '─'.repeat(Math.max(0, innerWidth - stripAnsi(titleStr).length));
|
|
184
|
-
const top = isUnicode
|
|
185
|
-
? draw(`┌${titleStr}${'─'.repeat(Math.floor(topFill.length / 2))}${'─'.repeat(Math.ceil(topFill.length / 2))}┐`, ansiColor)
|
|
186
|
-
: draw(`+${titleStr}${'-'.repeat(topFill.length)}+`, ansiColor);
|
|
187
|
-
const bot = isUnicode
|
|
188
|
-
? draw(`└${'─'.repeat(innerWidth)}┘`, ansiColor)
|
|
189
|
-
: draw(`+${'-'.repeat(innerWidth)}+`, ansiColor);
|
|
190
|
-
|
|
191
|
-
process.stdout.write(` ${top}\n`);
|
|
192
|
-
for (const line of lines) {
|
|
193
|
-
const pad = ' '.repeat(padding);
|
|
194
|
-
const visible = stripAnsi(line).length;
|
|
195
|
-
const right = ' '.repeat(Math.max(0, innerWidth - padding - visible));
|
|
196
|
-
const border = isUnicode ? draw('│', ansiColor) : draw('|', ansiColor);
|
|
197
|
-
process.stdout.write(` ${border}${pad}${line}${right}${border}\n`);
|
|
198
|
-
}
|
|
199
|
-
process.stdout.write(` ${bot}\n`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function stripAnsi(str) {
|
|
203
|
-
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function gradient(text, fromColor = 196, toColor = 226) {
|
|
207
|
-
if (!hasColor) return process.stdout.write(text + '\n');
|
|
208
|
-
const chars = [...text];
|
|
209
|
-
const result = chars.map((ch, i) => {
|
|
210
|
-
const t = chars.length <= 1 ? 0 : i / (chars.length - 1);
|
|
211
|
-
const colorIdx = Math.round(fromColor + t * (toColor - fromColor));
|
|
212
|
-
return `\x1b[38;5;${colorIdx}m${ch}`;
|
|
213
|
-
}).join('') + c.reset;
|
|
214
|
-
process.stdout.write(result + '\n');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export async function celebrate(text) {
|
|
218
|
-
const sym = isUnicode ? '✨' : '*';
|
|
219
|
-
if (!isTTY || getMode() === 'ci' || getMode() === 'plain') {
|
|
220
|
-
process.stdout.write(` ${sym} ${text} ${sym}\n`);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
process.stdout.write(`\r${c.clearLine} ${color(`${sym} ${text} ${sym}`, c.bgGreen, c.bold)}`);
|
|
224
|
-
await sleep(100);
|
|
225
|
-
process.stdout.write(`\r${c.clearLine} ${color(`${sym} ${text} ${sym}`, c.green, c.bold)}\n`);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export async function loadingSequence(steps) {
|
|
229
|
-
for (const s of steps) {
|
|
230
|
-
const sp = spinner(s.text).start();
|
|
231
|
-
await sleep(s.duration || 800);
|
|
232
|
-
sp.succeed(s.successText || s.text);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export async function agentDispatch(model, task) {
|
|
237
|
-
const mode = getMode();
|
|
238
|
-
if (mode === 'ci' || mode === 'plain') {
|
|
239
|
-
process.stdout.write(`Dispatching ${model}...\n`);
|
|
240
|
-
process.stdout.write(`Agent dispatched: ${task}\n`);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
const sp = spinner(`Dispatching ${color(model, c.cyan)}...`).start();
|
|
244
|
-
await sleep(mode === 'subtle' ? 0 : 600);
|
|
245
|
-
sp.succeed(`Agent dispatched: ${task}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export async function thinkRound(round, provider, question) {
|
|
249
|
-
const mode = getMode();
|
|
250
|
-
const providerLabel = color(provider, c.magenta);
|
|
251
|
-
const roundLabel = color(`Round ${round}`, c.bold);
|
|
252
|
-
|
|
253
|
-
if (mode === 'ci' || mode === 'plain') {
|
|
254
|
-
process.stdout.write(`Dual-Brain Think · ${roundLabel} · ${provider} analyzing: ${question}\n`);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const title = `Dual-Brain Think · ${roundLabel}`;
|
|
259
|
-
const titleVisible = stripAnsi(title);
|
|
260
|
-
const width = Math.max(titleVisible.length + 4, question.length + 4, 36);
|
|
261
|
-
const topFill = '─'.repeat(Math.max(0, width - titleVisible.length - 2));
|
|
262
|
-
|
|
263
|
-
if (isUnicode && hasColor) {
|
|
264
|
-
process.stdout.write(` ${color(`╭─ ${title} ${'─'.repeat(topFill.length)}╮`, c.cyan)}\n`);
|
|
265
|
-
process.stdout.write(` ${color('│', c.cyan)} ${isUnicode ? '🤖' : '>>'} ${providerLabel} analyzing...${' '.repeat(Math.max(0, width - 4 - stripAnsi(provider).length - 13))}${color('│', c.cyan)}\n`);
|
|
266
|
-
process.stdout.write(` ${color(`╰${'─'.repeat(width)}╯`, c.cyan)}\n`);
|
|
267
|
-
} else {
|
|
268
|
-
process.stdout.write(` +-- ${title} --+\n`);
|
|
269
|
-
process.stdout.write(` | ${provider} analyzing: ${question}\n`);
|
|
270
|
-
process.stdout.write(` +${'─'.repeat(width + 2)}+\n`);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const sp = spinner(`${provider} thinking on: ${question}`).start();
|
|
274
|
-
await sleep(mode === 'subtle' ? 0 : 900);
|
|
275
|
-
sp.succeed(`${provider} analysis complete`);
|
|
276
|
-
}
|