magi-ai 0.1.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/LICENSE +21 -0
- package/README.ja.md +377 -0
- package/README.md +377 -0
- package/dist/bin/magi-benchmark.d.ts +14 -0
- package/dist/bin/magi-benchmark.js +93 -0
- package/dist/bin/magi-mcp.d.ts +8 -0
- package/dist/bin/magi-mcp.js +28 -0
- package/dist/bin/magi.d.ts +2 -0
- package/dist/bin/magi.js +634 -0
- package/dist/src/adapters/base.d.ts +34 -0
- package/dist/src/adapters/base.js +149 -0
- package/dist/src/adapters/claude.d.ts +29 -0
- package/dist/src/adapters/claude.js +65 -0
- package/dist/src/adapters/codex.d.ts +21 -0
- package/dist/src/adapters/codex.js +41 -0
- package/dist/src/adapters/gemini.d.ts +18 -0
- package/dist/src/adapters/gemini.js +31 -0
- package/dist/src/adapters/registry.d.ts +19 -0
- package/dist/src/adapters/registry.js +59 -0
- package/dist/src/audit/hash-chain.d.ts +21 -0
- package/dist/src/audit/hash-chain.js +70 -0
- package/dist/src/audit/types.d.ts +25 -0
- package/dist/src/audit/types.js +1 -0
- package/dist/src/audit/writer.d.ts +18 -0
- package/dist/src/audit/writer.js +100 -0
- package/dist/src/benchmark/golden-tasks.d.ts +9 -0
- package/dist/src/benchmark/golden-tasks.js +476 -0
- package/dist/src/benchmark/reporter.d.ts +5 -0
- package/dist/src/benchmark/reporter.js +107 -0
- package/dist/src/benchmark/runner.d.ts +30 -0
- package/dist/src/benchmark/runner.js +224 -0
- package/dist/src/benchmark/scorer.d.ts +12 -0
- package/dist/src/benchmark/scorer.js +124 -0
- package/dist/src/benchmark/types.d.ts +54 -0
- package/dist/src/benchmark/types.js +1 -0
- package/dist/src/cache/deliberation-cache.d.ts +49 -0
- package/dist/src/cache/deliberation-cache.js +127 -0
- package/dist/src/cli/commands/config-cmd.d.ts +11 -0
- package/dist/src/cli/commands/config-cmd.js +190 -0
- package/dist/src/cli/commands/demo.d.ts +12 -0
- package/dist/src/cli/commands/demo.js +66 -0
- package/dist/src/cli/commands/setup.d.ts +7 -0
- package/dist/src/cli/commands/setup.js +182 -0
- package/dist/src/cli/i18n.d.ts +89 -0
- package/dist/src/cli/i18n.js +176 -0
- package/dist/src/cli/interactive-select.d.ts +27 -0
- package/dist/src/cli/interactive-select.js +130 -0
- package/dist/src/cli/tui-setup.d.ts +24 -0
- package/dist/src/cli/tui-setup.js +42 -0
- package/dist/src/config/cli-detector.d.ts +37 -0
- package/dist/src/config/cli-detector.js +99 -0
- package/dist/src/config/user-config.d.ts +81 -0
- package/dist/src/config/user-config.js +134 -0
- package/dist/src/context/auto-collector.d.ts +43 -0
- package/dist/src/context/auto-collector.js +337 -0
- package/dist/src/context/manager.d.ts +35 -0
- package/dist/src/context/manager.js +162 -0
- package/dist/src/context/serializer.d.ts +20 -0
- package/dist/src/context/serializer.js +52 -0
- package/dist/src/demo/recorded-deliberation.d.ts +13 -0
- package/dist/src/demo/recorded-deliberation.js +277 -0
- package/dist/src/engine/angel-detector.d.ts +83 -0
- package/dist/src/engine/angel-detector.js +334 -0
- package/dist/src/engine/at-field.d.ts +40 -0
- package/dist/src/engine/at-field.js +195 -0
- package/dist/src/engine/berserk-orchestrator.d.ts +66 -0
- package/dist/src/engine/berserk-orchestrator.js +378 -0
- package/dist/src/engine/change-metrics.d.ts +56 -0
- package/dist/src/engine/change-metrics.js +214 -0
- package/dist/src/engine/consensus.d.ts +20 -0
- package/dist/src/engine/consensus.js +146 -0
- package/dist/src/engine/dead-sea-scrolls.d.ts +132 -0
- package/dist/src/engine/dead-sea-scrolls.js +610 -0
- package/dist/src/engine/drift-detector.d.ts +39 -0
- package/dist/src/engine/drift-detector.js +225 -0
- package/dist/src/engine/dummy-plug.d.ts +44 -0
- package/dist/src/engine/dummy-plug.js +190 -0
- package/dist/src/engine/engram-manager.d.ts +55 -0
- package/dist/src/engine/engram-manager.js +306 -0
- package/dist/src/engine/events.d.ts +130 -0
- package/dist/src/engine/events.js +44 -0
- package/dist/src/engine/gospel.d.ts +30 -0
- package/dist/src/engine/gospel.js +129 -0
- package/dist/src/engine/hallucination-detector.d.ts +33 -0
- package/dist/src/engine/hallucination-detector.js +215 -0
- package/dist/src/engine/human-resolver.d.ts +19 -0
- package/dist/src/engine/human-resolver.js +89 -0
- package/dist/src/engine/instrumentality.d.ts +64 -0
- package/dist/src/engine/instrumentality.js +297 -0
- package/dist/src/engine/iruel-battle.d.ts +79 -0
- package/dist/src/engine/iruel-battle.js +319 -0
- package/dist/src/engine/kernel/deliberation-kernel.d.ts +12 -0
- package/dist/src/engine/kernel/deliberation-kernel.js +303 -0
- package/dist/src/engine/kernel/index.d.ts +8 -0
- package/dist/src/engine/kernel/index.js +7 -0
- package/dist/src/engine/kernel/phase-runner.d.ts +10 -0
- package/dist/src/engine/kernel/phase-runner.js +155 -0
- package/dist/src/engine/kernel/post-processor.d.ts +17 -0
- package/dist/src/engine/kernel/post-processor.js +131 -0
- package/dist/src/engine/kernel/types.d.ts +107 -0
- package/dist/src/engine/kernel/types.js +1 -0
- package/dist/src/engine/kernel/unit-executor.d.ts +6 -0
- package/dist/src/engine/kernel/unit-executor.js +132 -0
- package/dist/src/engine/lcl-manager.d.ts +44 -0
- package/dist/src/engine/lcl-manager.js +143 -0
- package/dist/src/engine/middleware/cache.d.ts +7 -0
- package/dist/src/engine/middleware/cache.js +29 -0
- package/dist/src/engine/middleware/chain.d.ts +18 -0
- package/dist/src/engine/middleware/chain.js +45 -0
- package/dist/src/engine/middleware/firewall.d.ts +8 -0
- package/dist/src/engine/middleware/firewall.js +24 -0
- package/dist/src/engine/middleware/index.d.ts +4 -0
- package/dist/src/engine/middleware/index.js +3 -0
- package/dist/src/engine/middleware/types.d.ts +43 -0
- package/dist/src/engine/middleware/types.js +1 -0
- package/dist/src/engine/nebuchadnezzar-key.d.ts +61 -0
- package/dist/src/engine/nebuchadnezzar-key.js +203 -0
- package/dist/src/engine/neon-genesis.d.ts +52 -0
- package/dist/src/engine/neon-genesis.js +203 -0
- package/dist/src/engine/objective-judge.d.ts +53 -0
- package/dist/src/engine/objective-judge.js +214 -0
- package/dist/src/engine/offline-mode.d.ts +18 -0
- package/dist/src/engine/offline-mode.js +46 -0
- package/dist/src/engine/orchestrator.d.ts +79 -0
- package/dist/src/engine/orchestrator.js +58 -0
- package/dist/src/engine/secret-cipher.d.ts +26 -0
- package/dist/src/engine/secret-cipher.js +114 -0
- package/dist/src/engine/seele-council.d.ts +90 -0
- package/dist/src/engine/seele-council.js +482 -0
- package/dist/src/engine/self-destruct.d.ts +61 -0
- package/dist/src/engine/self-destruct.js +231 -0
- package/dist/src/engine/self-evolution.d.ts +64 -0
- package/dist/src/engine/self-evolution.js +368 -0
- package/dist/src/engine/sync-rate.d.ts +45 -0
- package/dist/src/engine/sync-rate.js +151 -0
- package/dist/src/engine/type666-firewall.d.ts +76 -0
- package/dist/src/engine/type666-firewall.js +343 -0
- package/dist/src/engine/umbilical-cable.d.ts +41 -0
- package/dist/src/engine/umbilical-cable.js +192 -0
- package/dist/src/index.d.ts +106 -0
- package/dist/src/index.js +426 -0
- package/dist/src/mcp/server.d.ts +38 -0
- package/dist/src/mcp/server.js +196 -0
- package/dist/src/metrics/token-tracker.d.ts +38 -0
- package/dist/src/metrics/token-tracker.js +112 -0
- package/dist/src/parsers/json-extractor.d.ts +9 -0
- package/dist/src/parsers/json-extractor.js +239 -0
- package/dist/src/parsers/opinion-schema.d.ts +81 -0
- package/dist/src/parsers/opinion-schema.js +147 -0
- package/dist/src/parsers/unstructured-parser.d.ts +20 -0
- package/dist/src/parsers/unstructured-parser.js +122 -0
- package/dist/src/pipelines/architecture.d.ts +10 -0
- package/dist/src/pipelines/architecture.js +9 -0
- package/dist/src/pipelines/bug-analysis.d.ts +9 -0
- package/dist/src/pipelines/bug-analysis.js +8 -0
- package/dist/src/pipelines/code-review.d.ts +10 -0
- package/dist/src/pipelines/code-review.js +30 -0
- package/dist/src/pipelines/custom.d.ts +14 -0
- package/dist/src/pipelines/custom.js +29 -0
- package/dist/src/pipelines/registry.d.ts +9 -0
- package/dist/src/pipelines/registry.js +20 -0
- package/dist/src/prompts/personas.d.ts +6 -0
- package/dist/src/prompts/personas.js +44 -0
- package/dist/src/prompts/schemas.d.ts +4 -0
- package/dist/src/prompts/schemas.js +24 -0
- package/dist/src/prompts/templates.d.ts +6 -0
- package/dist/src/prompts/templates.js +91 -0
- package/dist/src/repl/accessibility.d.ts +23 -0
- package/dist/src/repl/accessibility.js +46 -0
- package/dist/src/repl/banner.d.ts +4 -0
- package/dist/src/repl/banner.js +28 -0
- package/dist/src/repl/boot-animation.d.ts +13 -0
- package/dist/src/repl/boot-animation.js +143 -0
- package/dist/src/repl/completer.d.ts +21 -0
- package/dist/src/repl/completer.js +168 -0
- package/dist/src/repl/context.d.ts +24 -0
- package/dist/src/repl/context.js +42 -0
- package/dist/src/repl/display-utils.d.ts +13 -0
- package/dist/src/repl/display-utils.js +65 -0
- package/dist/src/repl/event-listener.d.ts +18 -0
- package/dist/src/repl/event-listener.js +112 -0
- package/dist/src/repl/export-formatter.d.ts +8 -0
- package/dist/src/repl/export-formatter.js +73 -0
- package/dist/src/repl/ghost-text.d.ts +31 -0
- package/dist/src/repl/ghost-text.js +119 -0
- package/dist/src/repl/handoff-animation.d.ts +15 -0
- package/dist/src/repl/handoff-animation.js +65 -0
- package/dist/src/repl/history.d.ts +16 -0
- package/dist/src/repl/history.js +130 -0
- package/dist/src/repl/job-registry.d.ts +26 -0
- package/dist/src/repl/job-registry.js +80 -0
- package/dist/src/repl/magi-repl.d.ts +72 -0
- package/dist/src/repl/magi-repl.js +1008 -0
- package/dist/src/repl/multiline-input.d.ts +45 -0
- package/dist/src/repl/multiline-input.js +78 -0
- package/dist/src/repl/prompt-builder.d.ts +19 -0
- package/dist/src/repl/prompt-builder.js +36 -0
- package/dist/src/repl/repl-state.d.ts +5 -0
- package/dist/src/repl/repl-state.js +19 -0
- package/dist/src/repl/result-display.d.ts +8 -0
- package/dist/src/repl/result-display.js +195 -0
- package/dist/src/repl/session-stats.d.ts +26 -0
- package/dist/src/repl/session-stats.js +119 -0
- package/dist/src/repl/slash-commands.d.ts +60 -0
- package/dist/src/repl/slash-commands.js +725 -0
- package/dist/src/repl/terminal-sanitize.d.ts +14 -0
- package/dist/src/repl/terminal-sanitize.js +19 -0
- package/dist/src/reporters/console.d.ts +7 -0
- package/dist/src/reporters/console.js +78 -0
- package/dist/src/reporters/json.d.ts +2 -0
- package/dist/src/reporters/json.js +3 -0
- package/dist/src/reporters/markdown.d.ts +2 -0
- package/dist/src/reporters/markdown.js +65 -0
- package/dist/src/reporters/streaming.d.ts +20 -0
- package/dist/src/reporters/streaming.js +178 -0
- package/dist/src/tui/activity-log.d.ts +23 -0
- package/dist/src/tui/activity-log.js +67 -0
- package/dist/src/tui/animations.d.ts +39 -0
- package/dist/src/tui/animations.js +167 -0
- package/dist/src/tui/ansi.d.ts +28 -0
- package/dist/src/tui/ansi.js +51 -0
- package/dist/src/tui/boot-sequence.d.ts +11 -0
- package/dist/src/tui/boot-sequence.js +98 -0
- package/dist/src/tui/colors.d.ts +101 -0
- package/dist/src/tui/colors.js +71 -0
- package/dist/src/tui/header.d.ts +24 -0
- package/dist/src/tui/header.js +122 -0
- package/dist/src/tui/index.d.ts +3 -0
- package/dist/src/tui/index.js +3 -0
- package/dist/src/tui/keypress.d.ts +25 -0
- package/dist/src/tui/keypress.js +95 -0
- package/dist/src/tui/layout.d.ts +74 -0
- package/dist/src/tui/layout.js +171 -0
- package/dist/src/tui/magi-tui.d.ts +101 -0
- package/dist/src/tui/magi-tui.js +754 -0
- package/dist/src/tui/panel.d.ts +45 -0
- package/dist/src/tui/panel.js +292 -0
- package/dist/src/tui/screen-buffer.d.ts +54 -0
- package/dist/src/tui/screen-buffer.js +262 -0
- package/dist/src/tui/status-bar.d.ts +25 -0
- package/dist/src/tui/status-bar.js +124 -0
- package/dist/src/tui/terminal-detect.d.ts +26 -0
- package/dist/src/tui/terminal-detect.js +44 -0
- package/dist/src/tui/tui-helpers.d.ts +12 -0
- package/dist/src/tui/tui-helpers.js +37 -0
- package/dist/src/types/adapter.d.ts +75 -0
- package/dist/src/types/adapter.js +36 -0
- package/dist/src/types/config.d.ts +108 -0
- package/dist/src/types/config.js +85 -0
- package/dist/src/types/consensus.d.ts +55 -0
- package/dist/src/types/consensus.js +17 -0
- package/dist/src/types/core.d.ts +178 -0
- package/dist/src/types/core.js +85 -0
- package/dist/src/types/magi-api.d.ts +62 -0
- package/dist/src/types/magi-api.js +7 -0
- package/dist/src/types/phase-h.d.ts +142 -0
- package/dist/src/types/phase-h.js +7 -0
- package/dist/src/types/phase-i.d.ts +186 -0
- package/dist/src/types/phase-i.js +6 -0
- package/dist/src/types/phase-k.d.ts +259 -0
- package/dist/src/types/phase-k.js +6 -0
- package/dist/src/types/phase-l.d.ts +199 -0
- package/dist/src/types/phase-l.js +6 -0
- package/dist/src/types/pipeline.d.ts +37 -0
- package/dist/src/types/pipeline.js +2 -0
- package/dist/src/utils/abstain-factory.d.ts +2 -0
- package/dist/src/utils/abstain-factory.js +18 -0
- package/dist/src/utils/errors.d.ts +34 -0
- package/dist/src/utils/errors.js +59 -0
- package/dist/src/utils/file-validator.d.ts +50 -0
- package/dist/src/utils/file-validator.js +124 -0
- package/dist/src/utils/fire-and-forget.d.ts +5 -0
- package/dist/src/utils/fire-and-forget.js +10 -0
- package/dist/src/utils/flag-validator.d.ts +21 -0
- package/dist/src/utils/flag-validator.js +79 -0
- package/dist/src/utils/freeze.d.ts +8 -0
- package/dist/src/utils/freeze.js +16 -0
- package/dist/src/utils/language-detector.d.ts +16 -0
- package/dist/src/utils/language-detector.js +159 -0
- package/dist/src/utils/latency-tracker.d.ts +45 -0
- package/dist/src/utils/latency-tracker.js +100 -0
- package/dist/src/utils/logger.d.ts +33 -0
- package/dist/src/utils/logger.js +112 -0
- package/dist/src/utils/process.d.ts +40 -0
- package/dist/src/utils/process.js +253 -0
- package/dist/src/utils/retry.d.ts +12 -0
- package/dist/src/utils/retry.js +30 -0
- package/dist/src/utils/safe-fs.d.ts +38 -0
- package/dist/src/utils/safe-fs.js +56 -0
- package/dist/src/utils/safe-json-parse.d.ts +15 -0
- package/dist/src/utils/safe-json-parse.js +49 -0
- package/dist/src/utils/sanitize.d.ts +14 -0
- package/dist/src/utils/sanitize.js +186 -0
- package/dist/src/utils/semaphore.d.ts +22 -0
- package/dist/src/utils/semaphore.js +57 -0
- package/dist/src/utils/shutdown.d.ts +6 -0
- package/dist/src/utils/shutdown.js +51 -0
- package/dist/src/utils/tty.d.ts +5 -0
- package/dist/src/utils/tty.js +7 -0
- package/package.json +82 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NERV-style boot animation for MAGI REPL.
|
|
3
|
+
*
|
|
4
|
+
* Displays a typewriter-style boot sequence (0.8-1.2s total).
|
|
5
|
+
* Skips on: non-TTY, CI, MAGI_NO_ANIMATION=1, or any keypress.
|
|
6
|
+
* Does NOT use alt screen — output stays in normal scrollback.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { EVA_PALETTE } from '../tui/colors.js';
|
|
10
|
+
import { shouldSkipAnimation } from './accessibility.js';
|
|
11
|
+
import { centerVisible, frameLines, getResponsiveBoxWidth, truncateVisible, wrapDisplayText } from './display-utils.js';
|
|
12
|
+
const { frame, scanline, textPrimary } = EVA_PALETTE;
|
|
13
|
+
const GREEN = chalk.rgb(scanline.r, scanline.g, scanline.b);
|
|
14
|
+
const ORANGE = chalk.rgb(frame.r, frame.g, frame.b);
|
|
15
|
+
const TEXT = chalk.rgb(textPrimary.r, textPrimary.g, textPrimary.b);
|
|
16
|
+
const BOOT_LINES = [
|
|
17
|
+
{ text: ' [NERV] REPL link ................. ESTABLISHED', color: 'green', delay: 60 },
|
|
18
|
+
{ text: ' [MAGI] MELCHIOR-1 ............... ONLINE', color: 'green', delay: 40 },
|
|
19
|
+
{ text: ' [MAGI] BALTHASAR-2 .............. ONLINE', color: 'green', delay: 40 },
|
|
20
|
+
{ text: ' [MAGI] CASPER-3 ................. ONLINE', color: 'green', delay: 40 },
|
|
21
|
+
{ text: ' [SYNC] Context window ........... 50,000 chars', color: 'green', delay: 40 },
|
|
22
|
+
{ text: ' [I/O ] Slash commands ........... 15 loaded', color: 'green', delay: 40 },
|
|
23
|
+
{ text: ' [A.T.] Interactive shell ........ ACTIVE', color: 'green', delay: 40 },
|
|
24
|
+
];
|
|
25
|
+
function colorize(text, color) {
|
|
26
|
+
switch (color) {
|
|
27
|
+
case 'green': return GREEN(text);
|
|
28
|
+
case 'orange': return ORANGE.bold(text);
|
|
29
|
+
case 'text': return TEXT(text);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function defaultDelay(ms) {
|
|
33
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
34
|
+
}
|
|
35
|
+
function buildReadyBox() {
|
|
36
|
+
const width = getResponsiveBoxWidth(44, 24);
|
|
37
|
+
const innerWidth = Math.max(1, width - 4);
|
|
38
|
+
const title = width >= 32 ? 'M A G I S Y S T E M' : 'MAGI SYSTEM';
|
|
39
|
+
return [
|
|
40
|
+
'',
|
|
41
|
+
...frameLines([centerVisible(title, innerWidth), centerVisible('REPL INTERFACE READY', innerWidth)], { width, borderColor: (text) => ORANGE.bold(text) }),
|
|
42
|
+
'',
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
function shouldSkip(opts) {
|
|
46
|
+
if (opts.skipCondition)
|
|
47
|
+
return true;
|
|
48
|
+
return shouldSkipAnimation();
|
|
49
|
+
}
|
|
50
|
+
export async function runReplBoot(options) {
|
|
51
|
+
const { write } = options;
|
|
52
|
+
const delay = options.delayFn ?? defaultDelay;
|
|
53
|
+
const readyBox = buildReadyBox();
|
|
54
|
+
const readyInnerWidth = Math.max(1, getResponsiveBoxWidth(44, 24) - 4);
|
|
55
|
+
const helpLines = wrapDisplayText('Type a question to deliberate. /help for commands.', readyInnerWidth);
|
|
56
|
+
if (shouldSkip(options)) {
|
|
57
|
+
// Print static banner instead
|
|
58
|
+
write('\n');
|
|
59
|
+
for (const line of readyBox) {
|
|
60
|
+
write(line + '\n');
|
|
61
|
+
}
|
|
62
|
+
for (const line of helpLines) {
|
|
63
|
+
write(GREEN(` ${line}\n`));
|
|
64
|
+
}
|
|
65
|
+
write('\n');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Detect keypress to abort animation
|
|
69
|
+
let aborted = false;
|
|
70
|
+
let rawModeWasSet = false;
|
|
71
|
+
const keypressHandler = () => { aborted = true; };
|
|
72
|
+
// Safety: restore raw mode on unexpected signals.
|
|
73
|
+
// Only cleans up raw mode — does NOT re-raise signal because
|
|
74
|
+
// setupGracefulShutdown() already has global handlers for SIGTERM/SIGHUP
|
|
75
|
+
// and re-raising would cause the global handler to run twice.
|
|
76
|
+
const cleanupRawMode = () => {
|
|
77
|
+
if (rawModeWasSet) {
|
|
78
|
+
try {
|
|
79
|
+
process.stdin.setRawMode(false);
|
|
80
|
+
}
|
|
81
|
+
catch { /* best-effort */ }
|
|
82
|
+
try {
|
|
83
|
+
process.stdin.pause();
|
|
84
|
+
}
|
|
85
|
+
catch { /* best-effort */ }
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
process.once('SIGTERM', cleanupRawMode);
|
|
89
|
+
process.once('SIGHUP', cleanupRawMode);
|
|
90
|
+
try {
|
|
91
|
+
if (process.stdin.isTTY && !process.stdin.isRaw) {
|
|
92
|
+
try {
|
|
93
|
+
process.stdin.setRawMode(true);
|
|
94
|
+
rawModeWasSet = true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Can't set raw mode — continue without keypress detection
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (rawModeWasSet) {
|
|
101
|
+
process.stdin.once('data', keypressHandler);
|
|
102
|
+
process.stdin.resume();
|
|
103
|
+
}
|
|
104
|
+
write('\n');
|
|
105
|
+
// Boot lines
|
|
106
|
+
for (const line of BOOT_LINES) {
|
|
107
|
+
if (aborted)
|
|
108
|
+
break;
|
|
109
|
+
const cols = process.stdout.columns ?? 80;
|
|
110
|
+
const margin = cols >= 32 ? 4 : 2;
|
|
111
|
+
const truncated = truncateVisible(line.text, Math.max(8, cols - margin));
|
|
112
|
+
write(colorize(truncated, line.color) + '\n');
|
|
113
|
+
await delay(line.delay);
|
|
114
|
+
}
|
|
115
|
+
// Ready box with flash
|
|
116
|
+
if (!aborted) {
|
|
117
|
+
await delay(120);
|
|
118
|
+
write('\n');
|
|
119
|
+
for (const line of readyBox) {
|
|
120
|
+
write(line + '\n');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const line of helpLines) {
|
|
124
|
+
write(GREEN(` ${line}\n`));
|
|
125
|
+
}
|
|
126
|
+
write('\n');
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
// Cleanup: restore raw mode and remove listener
|
|
130
|
+
process.removeListener('SIGTERM', cleanupRawMode);
|
|
131
|
+
process.removeListener('SIGHUP', cleanupRawMode);
|
|
132
|
+
if (rawModeWasSet) {
|
|
133
|
+
process.stdin.removeListener('data', keypressHandler);
|
|
134
|
+
try {
|
|
135
|
+
process.stdin.setRawMode(false);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Best-effort restore
|
|
139
|
+
}
|
|
140
|
+
process.stdin.pause();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab completion for MAGI REPL.
|
|
3
|
+
*
|
|
4
|
+
* - `/` prefix: slash command name completion
|
|
5
|
+
* - `/review <path>`: sync file path completion (cwd-restricted, no symlinks)
|
|
6
|
+
* - `/decide`, `/berserk`: no completion (freeform)
|
|
7
|
+
* - Other commands: no completion (none)
|
|
8
|
+
*/
|
|
9
|
+
export type ArgKind = 'none' | 'file' | 'freeform';
|
|
10
|
+
export interface CompletableCommand {
|
|
11
|
+
name: string;
|
|
12
|
+
aliases?: string[];
|
|
13
|
+
argKind: ArgKind;
|
|
14
|
+
}
|
|
15
|
+
/** Exported for testing — clear the directory cache. */
|
|
16
|
+
export declare function clearCompleterCache(): void;
|
|
17
|
+
export declare function createCompleter(commands: CompletableCommand[], opts?: {
|
|
18
|
+
ghostEngine?: {
|
|
19
|
+
consumeAccepted(): boolean;
|
|
20
|
+
};
|
|
21
|
+
}): (line: string) => [string[], string];
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab completion for MAGI REPL.
|
|
3
|
+
*
|
|
4
|
+
* - `/` prefix: slash command name completion
|
|
5
|
+
* - `/review <path>`: sync file path completion (cwd-restricted, no symlinks)
|
|
6
|
+
* - `/decide`, `/berserk`: no completion (freeform)
|
|
7
|
+
* - Other commands: no completion (none)
|
|
8
|
+
*/
|
|
9
|
+
import { readdirSync, lstatSync, realpathSync } from 'node:fs';
|
|
10
|
+
import { resolve, join, dirname, basename, normalize } from 'node:path';
|
|
11
|
+
const CACHE_TTL_MS = 2_000;
|
|
12
|
+
const CACHE_MAX_SIZE = 5;
|
|
13
|
+
const MAX_CANDIDATES = 50;
|
|
14
|
+
const dirCache = new Map();
|
|
15
|
+
function getCachedDir(dirPath) {
|
|
16
|
+
const cached = dirCache.get(dirPath);
|
|
17
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS)
|
|
18
|
+
return cached;
|
|
19
|
+
dirCache.delete(dirPath);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function readDirCached(dirPath) {
|
|
23
|
+
const cached = getCachedDir(dirPath);
|
|
24
|
+
if (cached)
|
|
25
|
+
return cached;
|
|
26
|
+
try {
|
|
27
|
+
const names = readdirSync(dirPath);
|
|
28
|
+
const entries = [];
|
|
29
|
+
const isDir = [];
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
if (name.startsWith('.'))
|
|
32
|
+
continue; // skip hidden
|
|
33
|
+
const full = join(dirPath, name);
|
|
34
|
+
try {
|
|
35
|
+
const st = lstatSync(full);
|
|
36
|
+
if (st.isSymbolicLink())
|
|
37
|
+
continue; // skip symlinks
|
|
38
|
+
entries.push(name);
|
|
39
|
+
isDir.push(st.isDirectory());
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// permission error etc — skip
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const entry = { entries, isDir, timestamp: Date.now() };
|
|
46
|
+
// LRU eviction
|
|
47
|
+
if (dirCache.size >= CACHE_MAX_SIZE) {
|
|
48
|
+
const oldest = dirCache.keys().next().value;
|
|
49
|
+
if (oldest !== undefined)
|
|
50
|
+
dirCache.delete(oldest);
|
|
51
|
+
}
|
|
52
|
+
dirCache.set(dirPath, entry);
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return { entries: [], isDir: [], timestamp: Date.now() };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Exported for testing — clear the directory cache. */
|
|
60
|
+
export function clearCompleterCache() {
|
|
61
|
+
dirCache.clear();
|
|
62
|
+
}
|
|
63
|
+
// ── Main completer factory ───────────────────────────────────
|
|
64
|
+
export function createCompleter(commands, opts) {
|
|
65
|
+
// Build lookup maps
|
|
66
|
+
const allNames = [];
|
|
67
|
+
const nameToArgKind = new Map();
|
|
68
|
+
for (const cmd of commands) {
|
|
69
|
+
const n = `/${cmd.name}`;
|
|
70
|
+
allNames.push(n);
|
|
71
|
+
nameToArgKind.set(cmd.name, cmd.argKind);
|
|
72
|
+
if (cmd.aliases) {
|
|
73
|
+
for (const alias of cmd.aliases) {
|
|
74
|
+
const a = `/${alias}`;
|
|
75
|
+
if (!allNames.includes(a))
|
|
76
|
+
allNames.push(a);
|
|
77
|
+
nameToArgKind.set(alias, cmd.argKind);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return function completeLine(line) {
|
|
82
|
+
// Suppress completion if ghost text was just accepted via Tab
|
|
83
|
+
if (opts?.ghostEngine?.consumeAccepted()) {
|
|
84
|
+
return [[], line];
|
|
85
|
+
}
|
|
86
|
+
// Slash command name completion
|
|
87
|
+
if (line.startsWith('/') && !line.includes(' ')) {
|
|
88
|
+
const matches = allNames
|
|
89
|
+
.filter(n => n.startsWith(line))
|
|
90
|
+
.slice(0, MAX_CANDIDATES);
|
|
91
|
+
// Append space if single match
|
|
92
|
+
if (matches.length === 1) {
|
|
93
|
+
const match = matches[0];
|
|
94
|
+
const cmd = match.slice(1);
|
|
95
|
+
const kind = nameToArgKind.get(cmd);
|
|
96
|
+
matches[0] = kind === 'none' ? match : `${match} `;
|
|
97
|
+
}
|
|
98
|
+
return [matches, line];
|
|
99
|
+
}
|
|
100
|
+
// After a command with arguments
|
|
101
|
+
const spaceIdx = line.indexOf(' ');
|
|
102
|
+
if (spaceIdx > 0 && line.startsWith('/')) {
|
|
103
|
+
const cmdName = line.slice(1, spaceIdx).toLowerCase();
|
|
104
|
+
const kind = nameToArgKind.get(cmdName);
|
|
105
|
+
if (kind === 'file') {
|
|
106
|
+
const partial = line.slice(spaceIdx + 1);
|
|
107
|
+
return completeFilePath(partial, line);
|
|
108
|
+
}
|
|
109
|
+
// freeform or none — no completion
|
|
110
|
+
}
|
|
111
|
+
return [[], line];
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// ── File path completion ─────────────────────────────────────
|
|
115
|
+
function completeFilePath(partial, fullLine) {
|
|
116
|
+
// Security: reject null bytes
|
|
117
|
+
if (partial.includes('\0')) {
|
|
118
|
+
return [[], fullLine];
|
|
119
|
+
}
|
|
120
|
+
// Segment-based path traversal check (allows filenames containing '..' like 'foo..bar.ts')
|
|
121
|
+
const normalized = normalize(partial);
|
|
122
|
+
const segments = normalized.split('/');
|
|
123
|
+
if (segments.some(s => s === '..')) {
|
|
124
|
+
return [[], fullLine];
|
|
125
|
+
}
|
|
126
|
+
const cwd = process.cwd();
|
|
127
|
+
// Handle trailing slash: list directory contents
|
|
128
|
+
const dirPart = partial.endsWith('/') ? partial.slice(0, -1) || '.'
|
|
129
|
+
: partial.includes('/') ? dirname(partial) : '.';
|
|
130
|
+
const prefix = partial.endsWith('/') ? '' : basename(partial);
|
|
131
|
+
// Resolve and verify within cwd (follow symlinks to detect escapes)
|
|
132
|
+
const resolvedDir = resolve(cwd, dirPart);
|
|
133
|
+
if (!resolvedDir.startsWith(cwd + '/') && resolvedDir !== cwd) {
|
|
134
|
+
return [[], fullLine]; // outside cwd (logical path)
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const realDir = realpathSync(resolvedDir);
|
|
138
|
+
const realCwd = realpathSync(cwd);
|
|
139
|
+
if (!realDir.startsWith(realCwd + '/') && realDir !== realCwd) {
|
|
140
|
+
return [[], fullLine]; // outside cwd (physical path via symlink)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return [[], fullLine]; // can't resolve — reject
|
|
145
|
+
}
|
|
146
|
+
const cached = readDirCached(resolvedDir);
|
|
147
|
+
const completions = [];
|
|
148
|
+
for (let i = 0; i < cached.entries.length && completions.length < MAX_CANDIDATES; i++) {
|
|
149
|
+
const name = cached.entries[i];
|
|
150
|
+
if (!name.startsWith(prefix))
|
|
151
|
+
continue;
|
|
152
|
+
const relPath = dirPart === '.' ? name : `${dirPart}/${name}`;
|
|
153
|
+
if (cached.isDir[i]) {
|
|
154
|
+
completions.push(`${relPath}/`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
completions.push(relPath);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Reconstruct full-line completions (command + path)
|
|
161
|
+
const cmdPrefix = fullLine.slice(0, fullLine.indexOf(' ') + 1);
|
|
162
|
+
const fullCompletions = completions.map(c => `${cmdPrefix}${c}`);
|
|
163
|
+
// Single match: append space for files
|
|
164
|
+
if (fullCompletions.length === 1 && !fullCompletions[0].endsWith('/')) {
|
|
165
|
+
fullCompletions[0] += ' ';
|
|
166
|
+
}
|
|
167
|
+
return [fullCompletions, fullLine];
|
|
168
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReplContext — accumulates deliberation summaries for follow-up context injection.
|
|
3
|
+
*
|
|
4
|
+
* Only summaries are retained (not full results) to avoid token exhaustion
|
|
5
|
+
* when injected into MagiTask.context (max 200,000 chars).
|
|
6
|
+
*/
|
|
7
|
+
import type { MagiDeliberation } from '../types/core.js';
|
|
8
|
+
import type { ConsensusDecision } from '../types/consensus.js';
|
|
9
|
+
export interface ContextEntry {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
decision: ConsensusDecision;
|
|
13
|
+
summary: string;
|
|
14
|
+
timestamp: Date;
|
|
15
|
+
}
|
|
16
|
+
export declare class ReplContext {
|
|
17
|
+
private entries;
|
|
18
|
+
private readonly maxEntries;
|
|
19
|
+
constructor(maxEntries?: number);
|
|
20
|
+
add(deliberation: MagiDeliberation): void;
|
|
21
|
+
buildContextString(): string | undefined;
|
|
22
|
+
getHistory(): readonly ContextEntry[];
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReplContext — accumulates deliberation summaries for follow-up context injection.
|
|
3
|
+
*
|
|
4
|
+
* Only summaries are retained (not full results) to avoid token exhaustion
|
|
5
|
+
* when injected into MagiTask.context (max 200,000 chars).
|
|
6
|
+
*/
|
|
7
|
+
const MAX_CONTEXT_CHARS = 200_000;
|
|
8
|
+
export class ReplContext {
|
|
9
|
+
entries = [];
|
|
10
|
+
maxEntries;
|
|
11
|
+
constructor(maxEntries = 5) {
|
|
12
|
+
this.maxEntries = maxEntries;
|
|
13
|
+
}
|
|
14
|
+
add(deliberation) {
|
|
15
|
+
this.entries.push({
|
|
16
|
+
id: deliberation.id,
|
|
17
|
+
title: deliberation.task.title,
|
|
18
|
+
decision: deliberation.consensus.decision,
|
|
19
|
+
summary: deliberation.consensus.summary ?? '',
|
|
20
|
+
timestamp: new Date(),
|
|
21
|
+
});
|
|
22
|
+
if (this.entries.length > this.maxEntries) {
|
|
23
|
+
this.entries = this.entries.slice(-this.maxEntries);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
buildContextString() {
|
|
27
|
+
if (this.entries.length === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
const lines = this.entries.map((e, i) => `[Previous deliberation ${i + 1}] "${e.title}" → ${e.decision}: ${e.summary}`);
|
|
30
|
+
const result = `Previous deliberation context:\n${lines.join('\n')}`;
|
|
31
|
+
if (result.length > MAX_CONTEXT_CHARS) {
|
|
32
|
+
return result.slice(0, MAX_CONTEXT_CHARS) + '\n[...truncated]';
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
getHistory() {
|
|
37
|
+
return [...this.entries];
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
this.entries = [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function stripAnsi(text: string): string;
|
|
2
|
+
export declare function visibleWidth(text: string): number;
|
|
3
|
+
export declare function padVisibleRight(text: string, width: number): string;
|
|
4
|
+
export declare function centerVisible(text: string, width: number): string;
|
|
5
|
+
export declare function wrapDisplayText(text: string, maxWidth: number): string[];
|
|
6
|
+
export declare function truncateVisible(text: string, width: number): string;
|
|
7
|
+
export declare function getTerminalColumns(fallback?: number): number;
|
|
8
|
+
export declare function getResponsiveBoxWidth(maxWidth: number, minWidth: number): number;
|
|
9
|
+
export declare function frameLines(lines: string[], options: {
|
|
10
|
+
width: number;
|
|
11
|
+
borderColor: (text: string) => string;
|
|
12
|
+
contentColor?: (text: string) => string;
|
|
13
|
+
}): string[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { charDisplayWidth, stringDisplayWidth, truncateToDisplayWidth } from '../tui/screen-buffer.js';
|
|
2
|
+
const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
3
|
+
export function stripAnsi(text) {
|
|
4
|
+
return text.replace(ANSI_RE, '');
|
|
5
|
+
}
|
|
6
|
+
export function visibleWidth(text) {
|
|
7
|
+
return stringDisplayWidth(stripAnsi(text));
|
|
8
|
+
}
|
|
9
|
+
export function padVisibleRight(text, width) {
|
|
10
|
+
return text + ' '.repeat(Math.max(0, width - visibleWidth(text)));
|
|
11
|
+
}
|
|
12
|
+
export function centerVisible(text, width) {
|
|
13
|
+
const contentWidth = visibleWidth(text);
|
|
14
|
+
const leftPad = Math.max(0, Math.floor((width - contentWidth) / 2));
|
|
15
|
+
const rightPad = Math.max(0, width - contentWidth - leftPad);
|
|
16
|
+
return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
|
|
17
|
+
}
|
|
18
|
+
export function wrapDisplayText(text, maxWidth) {
|
|
19
|
+
if (maxWidth <= 0)
|
|
20
|
+
return [];
|
|
21
|
+
const normalized = text.replace(/\r\n/g, '\n');
|
|
22
|
+
const lines = [];
|
|
23
|
+
for (const paragraph of normalized.split('\n')) {
|
|
24
|
+
if (!paragraph) {
|
|
25
|
+
lines.push('');
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let current = '';
|
|
29
|
+
let currentWidth = 0;
|
|
30
|
+
for (const ch of paragraph) {
|
|
31
|
+
const width = charDisplayWidth(ch.codePointAt(0) ?? 0);
|
|
32
|
+
if (current && currentWidth + width > maxWidth) {
|
|
33
|
+
lines.push(current.trimEnd());
|
|
34
|
+
current = ch === ' ' ? '' : ch;
|
|
35
|
+
currentWidth = ch === ' ' ? 0 : width;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
current += ch;
|
|
39
|
+
currentWidth += width;
|
|
40
|
+
}
|
|
41
|
+
lines.push(current.trimEnd());
|
|
42
|
+
}
|
|
43
|
+
return lines.length > 0 ? lines : [''];
|
|
44
|
+
}
|
|
45
|
+
export function truncateVisible(text, width) {
|
|
46
|
+
return truncateToDisplayWidth(text, Math.max(0, width));
|
|
47
|
+
}
|
|
48
|
+
export function getTerminalColumns(fallback = 80) {
|
|
49
|
+
return Math.max(16, process.stdout.columns ?? fallback);
|
|
50
|
+
}
|
|
51
|
+
export function getResponsiveBoxWidth(maxWidth, minWidth) {
|
|
52
|
+
const cols = getTerminalColumns();
|
|
53
|
+
const margin = cols >= 32 ? 4 : 2;
|
|
54
|
+
const width = Math.min(maxWidth, cols - margin);
|
|
55
|
+
return Math.max(Math.min(cols, minWidth), width);
|
|
56
|
+
}
|
|
57
|
+
export function frameLines(lines, options) {
|
|
58
|
+
const innerWidth = Math.max(1, options.width - 4);
|
|
59
|
+
const colorize = options.contentColor ?? ((text) => text);
|
|
60
|
+
return [
|
|
61
|
+
options.borderColor(` ╔${'═'.repeat(innerWidth)}╗`),
|
|
62
|
+
...lines.map(line => options.borderColor(' ║') + colorize(padVisibleRight(line, innerWidth)) + options.borderColor('║')),
|
|
63
|
+
options.borderColor(` ╚${'═'.repeat(innerWidth)}╝`),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus subscription for REPL progress display.
|
|
3
|
+
*
|
|
4
|
+
* Active only during deliberation when TUI is NOT running.
|
|
5
|
+
* Outputs inline progress using EVA_PALETTE colors.
|
|
6
|
+
* Uses setImmediate batching to avoid prompt corruption.
|
|
7
|
+
* Does NOT reuse StreamingProgressReporter (no unbind support).
|
|
8
|
+
*/
|
|
9
|
+
import type { MagiEventBus } from '../engine/events.js';
|
|
10
|
+
export interface ReplEventListenerOptions {
|
|
11
|
+
eventBus: MagiEventBus;
|
|
12
|
+
write: (s: string) => void;
|
|
13
|
+
onSyncRateUpdate?: (rates: Record<string, number>) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function createReplEventListener(options: ReplEventListenerOptions): {
|
|
16
|
+
subscribe: () => void;
|
|
17
|
+
unsubscribe: () => void;
|
|
18
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus subscription for REPL progress display.
|
|
3
|
+
*
|
|
4
|
+
* Active only during deliberation when TUI is NOT running.
|
|
5
|
+
* Outputs inline progress using EVA_PALETTE colors.
|
|
6
|
+
* Uses setImmediate batching to avoid prompt corruption.
|
|
7
|
+
* Does NOT reuse StreamingProgressReporter (no unbind support).
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { unitChalk, EVA_PALETTE } from '../tui/colors.js';
|
|
11
|
+
import { sanitizeForTerminal } from './terminal-sanitize.js';
|
|
12
|
+
import { voteSymbol } from './accessibility.js';
|
|
13
|
+
const { approve, reject, abstain, frame, warning } = EVA_PALETTE;
|
|
14
|
+
const APPROVE_C = chalk.rgb(approve.r, approve.g, approve.b);
|
|
15
|
+
const REJECT_C = chalk.rgb(reject.r, reject.g, reject.b);
|
|
16
|
+
const ABSTAIN_C = chalk.rgb(abstain.r, abstain.g, abstain.b);
|
|
17
|
+
const FRAME_C = chalk.rgb(frame.r, frame.g, frame.b);
|
|
18
|
+
const WARN_C = chalk.rgb(warning.r, warning.g, warning.b);
|
|
19
|
+
const DIM_C = chalk.rgb(abstain.r, abstain.g, abstain.b);
|
|
20
|
+
export function createReplEventListener(options) {
|
|
21
|
+
const { eventBus, write, onSyncRateUpdate } = options;
|
|
22
|
+
// Buffer for setImmediate batching
|
|
23
|
+
let pendingLines = [];
|
|
24
|
+
let flushScheduled = false;
|
|
25
|
+
function queueLine(line) {
|
|
26
|
+
pendingLines.push(line);
|
|
27
|
+
if (!flushScheduled) {
|
|
28
|
+
flushScheduled = true;
|
|
29
|
+
setImmediate(() => {
|
|
30
|
+
for (const l of pendingLines) {
|
|
31
|
+
write(l + '\n');
|
|
32
|
+
}
|
|
33
|
+
pendingLines = [];
|
|
34
|
+
flushScheduled = false;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function voteLabel(vote) {
|
|
39
|
+
const sym = voteSymbol(vote);
|
|
40
|
+
switch (vote) {
|
|
41
|
+
case 'APPROVE': return APPROVE_C(`${sym} APPROVE`);
|
|
42
|
+
case 'REJECT': return REJECT_C(`${sym} REJECT`);
|
|
43
|
+
case 'ABSTAIN': return ABSTAIN_C(`${sym} ABSTAIN`);
|
|
44
|
+
default: return vote;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ── Listener functions (stored for cleanup) ────────────────
|
|
48
|
+
const onUnitStart = (e) => {
|
|
49
|
+
queueLine(` ${unitChalk(e.unit)(e.unit)} thinking...`);
|
|
50
|
+
};
|
|
51
|
+
const onUnitComplete = (e) => {
|
|
52
|
+
const conf = e.confidence != null ? ` (${Math.round(e.confidence * 100)}%)` : '';
|
|
53
|
+
const dur = e.durationMs != null ? DIM_C(` ${(e.durationMs / 1000).toFixed(1)}s`) : '';
|
|
54
|
+
const vote = e.vote ? voteLabel(e.vote) : 'done';
|
|
55
|
+
queueLine(` ${unitChalk(e.unit)(e.unit)}: ${vote}${conf}${dur}`);
|
|
56
|
+
};
|
|
57
|
+
const onUnitError = (e) => {
|
|
58
|
+
queueLine(` ${unitChalk(e.unit)(e.unit)}: ${WARN_C('ERROR')} ${DIM_C(sanitizeForTerminal(e.error ?? ''))}`);
|
|
59
|
+
};
|
|
60
|
+
const onPhaseStart = (e) => {
|
|
61
|
+
const round = e.roundNumber != null ? ` (round ${e.roundNumber})` : '';
|
|
62
|
+
queueLine(` ${FRAME_C(`Phase: ${e.phase}`)}${DIM_C(round)}`);
|
|
63
|
+
};
|
|
64
|
+
const onPhaseComplete = (e) => {
|
|
65
|
+
const dur = e.durationMs != null ? ` (${(e.durationMs / 1000).toFixed(1)}s)` : '';
|
|
66
|
+
queueLine(` ${DIM_C(`Phase complete${dur}`)}`);
|
|
67
|
+
};
|
|
68
|
+
const onPhaseSkipped = (e) => {
|
|
69
|
+
queueLine(` ${APPROVE_C(`Early exit: ${e.reason}`)}`);
|
|
70
|
+
};
|
|
71
|
+
const onSyncRate = (e) => {
|
|
72
|
+
onSyncRateUpdate?.(e.rates);
|
|
73
|
+
};
|
|
74
|
+
const onUmbilicalStatus = (e) => {
|
|
75
|
+
const color = e.newStatus === 'connected' ? APPROVE_C
|
|
76
|
+
: e.newStatus === 'degraded' ? ABSTAIN_C
|
|
77
|
+
: WARN_C;
|
|
78
|
+
queueLine(` ${unitChalk(e.unit)(e.unit)} cable: ${color(e.newStatus)}`);
|
|
79
|
+
};
|
|
80
|
+
const onBerserkActivated = () => {
|
|
81
|
+
queueLine(` ${WARN_C.bold('WARNING: BERSERK MODE ACTIVATED')}`);
|
|
82
|
+
};
|
|
83
|
+
// ── Subscribe / Unsubscribe ────────────────────────────────
|
|
84
|
+
function subscribe() {
|
|
85
|
+
eventBus.on('unit:start', onUnitStart);
|
|
86
|
+
eventBus.on('unit:complete', onUnitComplete);
|
|
87
|
+
eventBus.on('unit:error', onUnitError);
|
|
88
|
+
eventBus.on('phase:start', onPhaseStart);
|
|
89
|
+
eventBus.on('phase:complete', onPhaseComplete);
|
|
90
|
+
eventBus.on('phase:skipped', onPhaseSkipped);
|
|
91
|
+
eventBus.on('syncrate:updated', onSyncRate);
|
|
92
|
+
eventBus.on('umbilical:status_changed', onUmbilicalStatus);
|
|
93
|
+
eventBus.on('berserk:activated', onBerserkActivated);
|
|
94
|
+
}
|
|
95
|
+
function unsubscribe() {
|
|
96
|
+
eventBus.off('unit:start', onUnitStart);
|
|
97
|
+
eventBus.off('unit:complete', onUnitComplete);
|
|
98
|
+
eventBus.off('unit:error', onUnitError);
|
|
99
|
+
eventBus.off('phase:start', onPhaseStart);
|
|
100
|
+
eventBus.off('phase:complete', onPhaseComplete);
|
|
101
|
+
eventBus.off('phase:skipped', onPhaseSkipped);
|
|
102
|
+
eventBus.off('syncrate:updated', onSyncRate);
|
|
103
|
+
eventBus.off('umbilical:status_changed', onUmbilicalStatus);
|
|
104
|
+
eventBus.off('berserk:activated', onBerserkActivated);
|
|
105
|
+
// Flush any pending lines
|
|
106
|
+
for (const l of pendingLines) {
|
|
107
|
+
write(l + '\n');
|
|
108
|
+
}
|
|
109
|
+
pendingLines = [];
|
|
110
|
+
}
|
|
111
|
+
return { subscribe, unsubscribe };
|
|
112
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deliberation export formatters (JSON / Markdown).
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no side-effects, no chalk, no filesystem.
|
|
5
|
+
*/
|
|
6
|
+
import type { MagiDeliberation } from '../types/core.js';
|
|
7
|
+
export declare function formatDeliberationAsJson(d: MagiDeliberation): string;
|
|
8
|
+
export declare function formatDeliberationAsMarkdown(d: MagiDeliberation): string;
|