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,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MagiTui — Main TUI class that orchestrates the Evangelion-themed interface.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to all 17 MagiEventBus events and drives:
|
|
5
|
+
* Event → State update → Activity log → Animation → Render → ScreenBuffer → stdout
|
|
6
|
+
*/
|
|
7
|
+
import { isUnanimous as isUnanimousDecision, isApproval, isRejection } from '../types/consensus.js';
|
|
8
|
+
import { ScreenBuffer } from './screen-buffer.js';
|
|
9
|
+
import { calculateLayout } from './layout.js';
|
|
10
|
+
import { PanelRenderer, getDisplayName } from './panel.js';
|
|
11
|
+
import { HeaderRenderer } from './header.js';
|
|
12
|
+
import { StatusBarRenderer } from './status-bar.js';
|
|
13
|
+
import { ActivityLog } from './activity-log.js';
|
|
14
|
+
import { AnimationController } from './animations.js';
|
|
15
|
+
import { runBootSequence } from './boot-sequence.js';
|
|
16
|
+
import { getTerminalSize, MIN_COLS, MIN_ROWS } from './terminal-detect.js';
|
|
17
|
+
import { EVA_PALETTE, voteColor } from './colors.js';
|
|
18
|
+
import { CURSOR_SHOW, ALT_SCREEN_LEAVE, CLEAR_SCREEN, CURSOR_HOME } from './ansi.js';
|
|
19
|
+
import { ACTIVITY_LOG_LINES } from './layout.js';
|
|
20
|
+
import { registerCleanup } from '../utils/shutdown.js';
|
|
21
|
+
import { fireAndForget } from '../utils/fire-and-forget.js';
|
|
22
|
+
import { suppressStdin } from './keypress.js';
|
|
23
|
+
import { deriveSourceLabel, derivePriorityLabel, phaseCode, formatElapsedMmSs } from './tui-helpers.js';
|
|
24
|
+
import { stringDisplayWidth, truncateToDisplayWidth } from './screen-buffer.js';
|
|
25
|
+
// ── Unit order for layout mapping ───────────────────────────
|
|
26
|
+
const KNOWN_UNITS = ['MELCHIOR', 'BALTHASAR', 'CASPER'];
|
|
27
|
+
const UNIT_PHASE_OFFSETS = {
|
|
28
|
+
BALTHASAR: 0.0,
|
|
29
|
+
CASPER: 0.18,
|
|
30
|
+
MELCHIOR: 0.36,
|
|
31
|
+
};
|
|
32
|
+
const UNIT_KEYS = {
|
|
33
|
+
'1': 'MELCHIOR',
|
|
34
|
+
'2': 'BALTHASAR',
|
|
35
|
+
'3': 'CASPER',
|
|
36
|
+
q: undefined,
|
|
37
|
+
escape: undefined,
|
|
38
|
+
other: undefined,
|
|
39
|
+
};
|
|
40
|
+
// ── MagiTui ─────────────────────────────────────────────────
|
|
41
|
+
export class MagiTui {
|
|
42
|
+
buf;
|
|
43
|
+
layout;
|
|
44
|
+
panelRenderer = new PanelRenderer();
|
|
45
|
+
headerRenderer = new HeaderRenderer();
|
|
46
|
+
statusBarRenderer = new StatusBarRenderer();
|
|
47
|
+
activityLog;
|
|
48
|
+
animations = new AnimationController();
|
|
49
|
+
stream;
|
|
50
|
+
eventBus;
|
|
51
|
+
skipBoot;
|
|
52
|
+
soundEnabled;
|
|
53
|
+
// State
|
|
54
|
+
unitStates = new Map();
|
|
55
|
+
headerData = { taskTitle: '', taskType: '', deliberationId: '', priorityLabel: 'B', modeLabel: 'STANDBY' };
|
|
56
|
+
statusData = { phase: '', roundNumber: 0, maxRounds: 3, elapsedMs: 0, modeLabel: 'STANDBY', alertCode: 'ALERT:NONE', umbilicalStatus: 'CONNECTED' };
|
|
57
|
+
verdictStamp = { text: '待 機', tone: 'pending' };
|
|
58
|
+
elapsedTimer;
|
|
59
|
+
startTime = 0;
|
|
60
|
+
renderScheduled = false;
|
|
61
|
+
disposed = false;
|
|
62
|
+
backgroundFilled = false;
|
|
63
|
+
completionPromise;
|
|
64
|
+
stdinCleanup;
|
|
65
|
+
deregisterCleanup;
|
|
66
|
+
dismissResolver;
|
|
67
|
+
tuiPhase = 'idle';
|
|
68
|
+
overlayUnit;
|
|
69
|
+
lastOverlayRect;
|
|
70
|
+
connectorPulseIndex = -1;
|
|
71
|
+
berserkFlashActive = false;
|
|
72
|
+
syncRates = {};
|
|
73
|
+
terminalFailure;
|
|
74
|
+
// Event listener references for cleanup
|
|
75
|
+
boundListeners = [];
|
|
76
|
+
resizeHandler;
|
|
77
|
+
constructor(options) {
|
|
78
|
+
this.eventBus = options.eventBus;
|
|
79
|
+
this.stream = options.stream ?? process.stdout;
|
|
80
|
+
this.skipBoot = options.skipBoot ?? false;
|
|
81
|
+
this.soundEnabled = options.soundEnabled ?? false;
|
|
82
|
+
const { cols, rows } = getTerminalSize();
|
|
83
|
+
this.buf = new ScreenBuffer(cols, rows);
|
|
84
|
+
this.layout = calculateLayout(cols, rows);
|
|
85
|
+
this.activityLog = new ActivityLog(ACTIVITY_LOG_LINES);
|
|
86
|
+
this.terminalFailure = this.getTerminalFailure(cols, rows);
|
|
87
|
+
// Initialize unit states
|
|
88
|
+
for (const unit of KNOWN_UNITS) {
|
|
89
|
+
this.unitStates.set(unit, { state: 'idle', breathPhase: 0 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Start the TUI: boot sequence → initial draw → event binding. */
|
|
93
|
+
async start() {
|
|
94
|
+
// Boot sequence
|
|
95
|
+
this.tuiPhase = 'booting';
|
|
96
|
+
await runBootSequence({
|
|
97
|
+
write: (s) => this.write(s),
|
|
98
|
+
cols: this.buf.cols,
|
|
99
|
+
rows: this.buf.rows,
|
|
100
|
+
skipAnimation: this.skipBoot,
|
|
101
|
+
});
|
|
102
|
+
this.tuiPhase = 'idle';
|
|
103
|
+
// Initial full draw
|
|
104
|
+
this.buf.invalidate();
|
|
105
|
+
this.render();
|
|
106
|
+
this.write(this.buf.flush());
|
|
107
|
+
// Bind all events
|
|
108
|
+
this.bindEvents();
|
|
109
|
+
// Suppress stdin echo (prevents ^[[B etc. from appearing on screen)
|
|
110
|
+
this.stdinCleanup = suppressStdin((key) => this.handleKeypress(key));
|
|
111
|
+
// Resize handler (stored for cleanup in dispose())
|
|
112
|
+
if (this.stream === process.stdout) {
|
|
113
|
+
this.resizeHandler = () => this.handleResize();
|
|
114
|
+
process.stdout.on('resize', this.resizeHandler);
|
|
115
|
+
}
|
|
116
|
+
// Register cleanup for graceful shutdown (store deregister to avoid memory leak in REPL)
|
|
117
|
+
this.deregisterCleanup = registerCleanup(() => this.dispose());
|
|
118
|
+
}
|
|
119
|
+
/** Clean up: stop animations, unbind events, restore terminal. */
|
|
120
|
+
dispose() {
|
|
121
|
+
if (this.disposed)
|
|
122
|
+
return;
|
|
123
|
+
// Resolve pending dismiss (FIX-02: prevent hang on Ctrl+C during dismiss)
|
|
124
|
+
this.resolveDismiss();
|
|
125
|
+
// Stop animations and timers
|
|
126
|
+
this.animations.dispose();
|
|
127
|
+
if (this.elapsedTimer)
|
|
128
|
+
clearInterval(this.elapsedTimer);
|
|
129
|
+
// Restore stdin
|
|
130
|
+
this.stdinCleanup?.();
|
|
131
|
+
this.stdinCleanup = undefined;
|
|
132
|
+
// Unbind events
|
|
133
|
+
for (const { event, fn } of this.boundListeners) {
|
|
134
|
+
this.eventBus.off(event, fn);
|
|
135
|
+
}
|
|
136
|
+
this.boundListeners = [];
|
|
137
|
+
// Remove resize listener
|
|
138
|
+
if (this.resizeHandler && this.stream === process.stdout) {
|
|
139
|
+
process.stdout.off('resize', this.resizeHandler);
|
|
140
|
+
this.resizeHandler = undefined;
|
|
141
|
+
}
|
|
142
|
+
// Deregister cleanup callback to prevent memory leak in REPL (CRITICAL-2)
|
|
143
|
+
this.deregisterCleanup?.();
|
|
144
|
+
this.deregisterCleanup = undefined;
|
|
145
|
+
// Restore terminal (must happen BEFORE setting disposed flag)
|
|
146
|
+
this.write(CURSOR_SHOW + ALT_SCREEN_LEAVE);
|
|
147
|
+
this.disposed = true;
|
|
148
|
+
}
|
|
149
|
+
/** Wait for completion animation + keypress before disposing. */
|
|
150
|
+
async waitForDismiss() {
|
|
151
|
+
if (this.disposed)
|
|
152
|
+
return;
|
|
153
|
+
// Wait for completion animation (cascade + unanimousReveal)
|
|
154
|
+
if (this.completionPromise) {
|
|
155
|
+
await this.completionPromise;
|
|
156
|
+
}
|
|
157
|
+
// CRITICAL-1: dispose() may have fired during the await above,
|
|
158
|
+
// resolving dismissResolver before it was assigned. Check now.
|
|
159
|
+
if (this.disposed)
|
|
160
|
+
return;
|
|
161
|
+
// Show prompt in status bar
|
|
162
|
+
this.tuiPhase = 'dismiss';
|
|
163
|
+
this.statusData.showPrompt = true;
|
|
164
|
+
this.scheduleRender();
|
|
165
|
+
// Flush the render synchronously
|
|
166
|
+
await new Promise(resolve => queueMicrotask(() => resolve()));
|
|
167
|
+
// HIGH-10: When stdin is piped (not a TTY), no keypress will ever arrive.
|
|
168
|
+
// Show the final screen briefly then return.
|
|
169
|
+
if (!process.stdin.isTTY) {
|
|
170
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
await new Promise((resolve) => {
|
|
174
|
+
this.dismissResolver = resolve;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// ── Event binding ───────────────────────────────────────
|
|
178
|
+
bindEvents() {
|
|
179
|
+
this.on('deliberation:start', (e) => this.onDeliberationStart(e));
|
|
180
|
+
this.on('phase:start', (e) => this.onPhaseStart(e));
|
|
181
|
+
this.on('unit:start', (e) => this.onUnitStart(e));
|
|
182
|
+
this.on('unit:complete', (e) => this.onUnitComplete(e));
|
|
183
|
+
this.on('unit:error', (e) => this.onUnitError(e));
|
|
184
|
+
this.on('phase:complete', (e) => this.onPhaseComplete(e));
|
|
185
|
+
this.on('phase:skipped', (e) => this.onPhaseSkipped(e));
|
|
186
|
+
this.on('deliberation:complete', (e) => this.onDeliberationComplete(e));
|
|
187
|
+
// Phase H events
|
|
188
|
+
this.on('syncrate:updated', (e) => this.onSyncRateUpdated(e));
|
|
189
|
+
this.on('berserk:warning', (e) => this.onBerserkWarning(e));
|
|
190
|
+
this.on('bias:detected', (e) => this.onBiasDetected(e));
|
|
191
|
+
this.on('gospel:milestone', (e) => this.onGospelMilestone(e));
|
|
192
|
+
this.on('umbilical:status_changed', (e) => this.onUmbilicalStatusChanged(e));
|
|
193
|
+
this.on('dummyplug:activated', (e) => this.onDummyPlugActivated(e));
|
|
194
|
+
this.on('dummyplug:recovery', (e) => this.onDummyPlugRecovery(e));
|
|
195
|
+
this.on('cipher:discovered', (e) => this.onCipherDiscovered(e));
|
|
196
|
+
// Phase I events
|
|
197
|
+
this.on('memory:drift-warning', (e) => this.onMemoryDriftWarning(e));
|
|
198
|
+
this.on('memory:consolidated', (e) => this.onMemoryConsolidated(e));
|
|
199
|
+
this.on('berserk:activated', (e) => this.onBerserkActivated(e));
|
|
200
|
+
this.on('berserk:deactivated', (e) => this.onBerserkDeactivated(e));
|
|
201
|
+
this.on('instrumentality:triggered', (e) => this.onInstrumentalityTriggered(e));
|
|
202
|
+
this.on('fusion:complete', (e) => this.onFusionComplete(e));
|
|
203
|
+
this.on('lcl:contamination-detected', (e) => this.onLCLContamination(e));
|
|
204
|
+
this.on('lcl:purified', (e) => this.onLCLPurified(e));
|
|
205
|
+
}
|
|
206
|
+
on(event, fn) {
|
|
207
|
+
this.eventBus.on(event, fn);
|
|
208
|
+
this.boundListeners.push({ event, fn: fn });
|
|
209
|
+
}
|
|
210
|
+
// ── Core event handlers ─────────────────────────────────
|
|
211
|
+
onDeliberationStart(e) {
|
|
212
|
+
this.headerData = {
|
|
213
|
+
taskTitle: e.taskTitle,
|
|
214
|
+
taskType: e.taskType,
|
|
215
|
+
deliberationId: e.deliberationId,
|
|
216
|
+
sourceLabel: deriveSourceLabel(e.taskType, e.taskTitle),
|
|
217
|
+
priorityLabel: derivePriorityLabel(e.taskType),
|
|
218
|
+
modeLabel: 'NORMAL',
|
|
219
|
+
};
|
|
220
|
+
this.startTime = Date.now();
|
|
221
|
+
this.startElapsedTimer();
|
|
222
|
+
this.tuiPhase = 'deliberating';
|
|
223
|
+
this.overlayUnit = undefined;
|
|
224
|
+
this.statusData = {
|
|
225
|
+
...this.statusData,
|
|
226
|
+
phase: '',
|
|
227
|
+
roundNumber: 0,
|
|
228
|
+
elapsedMs: 0,
|
|
229
|
+
decision: undefined,
|
|
230
|
+
fromCache: false,
|
|
231
|
+
atFieldLevel: 1,
|
|
232
|
+
warning: undefined,
|
|
233
|
+
modeLabel: 'NORMAL',
|
|
234
|
+
alertCode: 'ALERT:NONE',
|
|
235
|
+
syncSummary: 'AVG -- LOW --',
|
|
236
|
+
showPrompt: false,
|
|
237
|
+
};
|
|
238
|
+
this.verdictStamp = { text: '審 議 中', tone: 'pending' };
|
|
239
|
+
this.activityLog.push(`審議開始: ${e.taskTitle}`);
|
|
240
|
+
this.activityLog.setLive('ACTIVE LINK WAITING', EVA_PALETTE.magi);
|
|
241
|
+
this.playSound('start');
|
|
242
|
+
fireAndForget(this.animations.linePulse(this.layout.connectors.length, (index) => {
|
|
243
|
+
this.connectorPulseIndex = index;
|
|
244
|
+
this.scheduleRender();
|
|
245
|
+
}).finally(() => {
|
|
246
|
+
this.connectorPulseIndex = -1;
|
|
247
|
+
this.scheduleRender();
|
|
248
|
+
}), 'line-pulse');
|
|
249
|
+
this.scheduleRender();
|
|
250
|
+
}
|
|
251
|
+
onPhaseStart(e) {
|
|
252
|
+
this.statusData = {
|
|
253
|
+
...this.statusData,
|
|
254
|
+
phase: e.phase,
|
|
255
|
+
roundNumber: e.roundNumber,
|
|
256
|
+
alertCode: 'ALERT:NONE',
|
|
257
|
+
};
|
|
258
|
+
// Reset unit states for new phase
|
|
259
|
+
for (const unit of KNOWN_UNITS) {
|
|
260
|
+
const us = this.unitStates.get(unit);
|
|
261
|
+
if (us && us.state !== 'offline') {
|
|
262
|
+
us.state = 'idle';
|
|
263
|
+
us.vote = undefined;
|
|
264
|
+
us.confidence = undefined;
|
|
265
|
+
us.durationMs = undefined;
|
|
266
|
+
us.startedAtMs = undefined;
|
|
267
|
+
us.reasoning = undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
this.activityLog.push(`Phase ${e.roundNumber}: ${e.phase}`);
|
|
271
|
+
this.updateLiveActivityStatus();
|
|
272
|
+
this.scheduleRender();
|
|
273
|
+
}
|
|
274
|
+
onUnitStart(e) {
|
|
275
|
+
const us = this.getOrCreateUnit(e.unit);
|
|
276
|
+
us.state = 'thinking';
|
|
277
|
+
us.startedAtMs = Date.now();
|
|
278
|
+
this.animations.startBreathingWithOffset(e.unit, UNIT_PHASE_OFFSETS[e.unit] ?? 0, (phase) => {
|
|
279
|
+
const s = this.unitStates.get(e.unit);
|
|
280
|
+
if (s) {
|
|
281
|
+
s.breathPhase = phase;
|
|
282
|
+
this.scheduleRender();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
this.activityLog.push(`${e.unit} thinking...`);
|
|
286
|
+
this.updateLiveActivityStatus();
|
|
287
|
+
this.scheduleRender();
|
|
288
|
+
}
|
|
289
|
+
onUnitComplete(e) {
|
|
290
|
+
this.animations.stopBreathing(e.unit);
|
|
291
|
+
const us = this.getOrCreateUnit(e.unit);
|
|
292
|
+
us.state = 'complete';
|
|
293
|
+
us.vote = e.vote;
|
|
294
|
+
us.confidence = e.confidence;
|
|
295
|
+
us.durationMs = e.durationMs;
|
|
296
|
+
us.startedAtMs = undefined;
|
|
297
|
+
us.reasoning = e.reasoningSummary;
|
|
298
|
+
us.breathPhase = 0;
|
|
299
|
+
const confPct = Math.round(e.confidence * 100);
|
|
300
|
+
const dur = (e.durationMs / 1000).toFixed(1);
|
|
301
|
+
this.statusData.syncSummary = formatConfidenceSummary(this.unitStates);
|
|
302
|
+
this.activityLog.push(`${e.unit} ${e.vote} (${confPct}%) [${dur}s]`, voteColor(e.vote));
|
|
303
|
+
this.updateLiveActivityStatus();
|
|
304
|
+
this.playSound('unit');
|
|
305
|
+
this.scheduleRender();
|
|
306
|
+
}
|
|
307
|
+
onUnitError(e) {
|
|
308
|
+
this.animations.stopBreathing(e.unit);
|
|
309
|
+
const us = this.getOrCreateUnit(e.unit);
|
|
310
|
+
us.state = 'error';
|
|
311
|
+
us.startedAtMs = undefined;
|
|
312
|
+
us.breathPhase = 0;
|
|
313
|
+
this.activityLog.push(`${e.unit} FAILED: ${e.error.slice(0, 50)}`, EVA_PALETTE.warning);
|
|
314
|
+
this.statusData.alertCode = `ERROR:${e.unit}`;
|
|
315
|
+
this.updateLiveActivityStatus();
|
|
316
|
+
this.playSound('alert');
|
|
317
|
+
this.scheduleRender();
|
|
318
|
+
}
|
|
319
|
+
onPhaseSkipped(e) {
|
|
320
|
+
const label = e.reason === 'unanimous_early_exit' ? '全会一致' : '多数決合意';
|
|
321
|
+
this.activityLog.push(`SKIP: ${e.phase} (${label})`, EVA_PALETTE.approve);
|
|
322
|
+
this.statusData.alertCode = `SKIP:${label}`;
|
|
323
|
+
this.scheduleRender();
|
|
324
|
+
}
|
|
325
|
+
onPhaseComplete(e) {
|
|
326
|
+
const dur = (e.durationMs / 1000).toFixed(1);
|
|
327
|
+
this.activityLog.push(`Phase ${e.roundNumber} complete (${dur}s)`);
|
|
328
|
+
this.scheduleRender();
|
|
329
|
+
}
|
|
330
|
+
onDeliberationComplete(e) {
|
|
331
|
+
this.stopElapsedTimer();
|
|
332
|
+
this.statusData.decision = e.decision;
|
|
333
|
+
this.statusData.elapsedMs = e.totalDurationMs;
|
|
334
|
+
this.statusData.fromCache = e.fromCache;
|
|
335
|
+
this.tuiPhase = 'verdict-lock';
|
|
336
|
+
this.activityLog.setLive(undefined);
|
|
337
|
+
if (e.fromCache) {
|
|
338
|
+
this.activityLog.push(`MAGI DECISION: ${e.decision} (cached)`);
|
|
339
|
+
this.verdictStamp = { text: decisionStampText(e.decision), tone: decisionTone(e.decision) };
|
|
340
|
+
this.playSound('decision');
|
|
341
|
+
this.scheduleRender();
|
|
342
|
+
this.completionPromise = Promise.resolve();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Cascade reveal for non-cached results
|
|
346
|
+
const units = [...KNOWN_UNITS];
|
|
347
|
+
const isUnanimous = isUnanimousDecision(e.decision);
|
|
348
|
+
this.verdictStamp = { text: decisionStampText(e.decision), tone: decisionTone(e.decision) };
|
|
349
|
+
this.scheduleRender();
|
|
350
|
+
this.completionPromise = this.animations.cascade(units, (_unit) => {
|
|
351
|
+
this.playSound('unit');
|
|
352
|
+
this.scheduleRender();
|
|
353
|
+
}).then(async () => {
|
|
354
|
+
await this.animations.verdictLock(e.decision, (text, tone, blink) => {
|
|
355
|
+
this.verdictStamp = { text, tone, blink };
|
|
356
|
+
this.scheduleRender();
|
|
357
|
+
});
|
|
358
|
+
if (isUnanimous && !this.disposed) {
|
|
359
|
+
await this.animations.unanimousReveal(e.decision, (text, isApprove) => {
|
|
360
|
+
this.activityLog.push(`MAGI DECISION: ${text} — ${e.decision}`, isApprove ? EVA_PALETTE.approve : EVA_PALETTE.reject);
|
|
361
|
+
this.playSound('decision');
|
|
362
|
+
this.scheduleRender();
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
this.activityLog.push(`MAGI DECISION: ${e.decision}`);
|
|
367
|
+
this.playSound('decision');
|
|
368
|
+
this.scheduleRender();
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
// ── Phase H event handlers ──────────────────────────────
|
|
373
|
+
onSyncRateUpdated(e) {
|
|
374
|
+
this.syncRates = { ...e.rates };
|
|
375
|
+
this.statusData.syncSummary = formatSyncRateSummary(this.syncRates);
|
|
376
|
+
const rates = Object.entries(e.rates)
|
|
377
|
+
.map(([u, r]) => `${u} ${(r * 100).toFixed(1)}%`)
|
|
378
|
+
.join(' | ');
|
|
379
|
+
this.activityLog.push(`シンクロ率: ${rates}`);
|
|
380
|
+
this.scheduleRender();
|
|
381
|
+
}
|
|
382
|
+
onBerserkWarning(e) {
|
|
383
|
+
this.startBerserkFlash('ALERT', 'ALERT:BERSERK', `[BERSERK] 暴走警告: 合計シンクロ率 ${e.totalSyncRate.toFixed(2)}`, EVA_PALETTE.warning);
|
|
384
|
+
}
|
|
385
|
+
onBiasDetected(e) {
|
|
386
|
+
if (e.level === 1)
|
|
387
|
+
return;
|
|
388
|
+
this.statusData.atFieldLevel = e.level;
|
|
389
|
+
this.statusData.alertCode = `A.T.FIELD:${e.level === 'MAX' ? 'MAX' : `Lv${e.level}`}`;
|
|
390
|
+
const levelStr = e.level === 'MAX' ? 'MAX' : String(e.level);
|
|
391
|
+
this.activityLog.push(`[A.T. FIELD Level ${levelStr}] バイアス検出 (エントロピー: ${e.voteEntropy.toFixed(3)})`, EVA_PALETTE.frame);
|
|
392
|
+
this.scheduleRender();
|
|
393
|
+
}
|
|
394
|
+
onGospelMilestone(e) {
|
|
395
|
+
const labels = {
|
|
396
|
+
first_deliberation: '最初の審議完了',
|
|
397
|
+
first_unanimous: '初の全会一致',
|
|
398
|
+
first_deadlock: '初のデッドロック',
|
|
399
|
+
centenary: '100回到達',
|
|
400
|
+
millennium: '1000回到達',
|
|
401
|
+
};
|
|
402
|
+
const label = labels[e.milestone] ?? e.milestone;
|
|
403
|
+
this.activityLog.push(`[GOSPEL] ${label} (第${e.count}審議)`);
|
|
404
|
+
this.scheduleRender();
|
|
405
|
+
}
|
|
406
|
+
onUmbilicalStatusChanged(e) {
|
|
407
|
+
const us = this.unitStates.get(e.unit);
|
|
408
|
+
if (us) {
|
|
409
|
+
if (e.newStatus === 'disconnected') {
|
|
410
|
+
us.state = 'offline';
|
|
411
|
+
}
|
|
412
|
+
else if (us.state === 'offline') {
|
|
413
|
+
// Reconnected — restore to idle (unit:start/complete will set correct state)
|
|
414
|
+
us.state = 'idle';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
this.statusData.umbilicalStatus = e.newStatus.toUpperCase();
|
|
418
|
+
this.statusData.alertCode = `CABLE:${e.newStatus.toUpperCase()}`;
|
|
419
|
+
this.activityLog.push(`[CABLE] ${e.unit} ${e.previousStatus} → ${e.newStatus}`);
|
|
420
|
+
this.scheduleRender();
|
|
421
|
+
}
|
|
422
|
+
onDummyPlugActivated(e) {
|
|
423
|
+
const conf = Math.round(e.confidence * 100);
|
|
424
|
+
this.activityLog.push(`[DUMMY PLUG] ${e.unit} 起動 (confidence: ${conf}%)`);
|
|
425
|
+
this.statusData.alertCode = 'ALERT:DUMMY';
|
|
426
|
+
this.scheduleRender();
|
|
427
|
+
}
|
|
428
|
+
onDummyPlugRecovery(e) {
|
|
429
|
+
this.activityLog.push(`[RECOVERY] ${e.unit} 復帰プロトコル`);
|
|
430
|
+
this.scheduleRender();
|
|
431
|
+
}
|
|
432
|
+
onCipherDiscovered(_e) {
|
|
433
|
+
this.activityLog.push(`[CIPHER] 裏コード発見`);
|
|
434
|
+
this.scheduleRender();
|
|
435
|
+
}
|
|
436
|
+
// ── Phase I event handlers ─────────────────────────────
|
|
437
|
+
onMemoryDriftWarning(e) {
|
|
438
|
+
const asi = (e.asiScore * 100).toFixed(1);
|
|
439
|
+
this.activityLog.push(`[DRIFT] ${e.unit} ASI ${asi}% (${e.consecutiveCount}連続)`, EVA_PALETTE.warning);
|
|
440
|
+
this.statusData.warning = `DRIFT: ${e.unit}`;
|
|
441
|
+
this.statusData.alertCode = `DRIFT:${e.unit}`;
|
|
442
|
+
this.scheduleRender();
|
|
443
|
+
}
|
|
444
|
+
onMemoryConsolidated(e) {
|
|
445
|
+
this.activityLog.push(`[ENGRAM] 記憶統合: ${e.episodicCount}件 → ${e.patternsExtracted}パターン`);
|
|
446
|
+
this.scheduleRender();
|
|
447
|
+
}
|
|
448
|
+
onBerserkActivated(e) {
|
|
449
|
+
this.startBerserkFlash('BERSERK', 'CODE:666', `[BERSERK] CODE:666 起動 R${e.round} (${e.candidateCount}候補)`, EVA_PALETTE.warning);
|
|
450
|
+
}
|
|
451
|
+
/** Shared berserk flash logic — overlay approach (FIX-03 + FIX-06). */
|
|
452
|
+
startBerserkFlash(modeLabel, alertCode, logMsg, logColor) {
|
|
453
|
+
fireAndForget(this.animations.berserkFlash((isRed) => {
|
|
454
|
+
this.berserkFlashActive = isRed;
|
|
455
|
+
this.scheduleRender();
|
|
456
|
+
}).finally(() => {
|
|
457
|
+
this.berserkFlashActive = false;
|
|
458
|
+
this.scheduleRender();
|
|
459
|
+
}), 'berserk-flash');
|
|
460
|
+
this.headerData.modeLabel = modeLabel;
|
|
461
|
+
this.statusData.modeLabel = modeLabel;
|
|
462
|
+
this.statusData.alertCode = alertCode;
|
|
463
|
+
this.activityLog.push(logMsg, logColor);
|
|
464
|
+
this.playSound('alert');
|
|
465
|
+
this.scheduleRender();
|
|
466
|
+
}
|
|
467
|
+
onBerserkDeactivated(e) {
|
|
468
|
+
this.headerData.modeLabel = 'NORMAL';
|
|
469
|
+
this.statusData.modeLabel = 'NORMAL';
|
|
470
|
+
this.statusData.alertCode = `BERSERK:${e.decision}`;
|
|
471
|
+
this.activityLog.push(`[BERSERK] 暴走停止: ${e.decision} (${e.totalRounds}R)`);
|
|
472
|
+
this.scheduleRender();
|
|
473
|
+
}
|
|
474
|
+
onInstrumentalityTriggered(e) {
|
|
475
|
+
this.activityLog.push(`[補完] 人類補完計画発動: ${e.reason} (${e.roundsBeforeFusion}R)`, EVA_PALETTE.frame);
|
|
476
|
+
this.statusData.alertCode = 'ALERT:FUSION';
|
|
477
|
+
this.scheduleRender();
|
|
478
|
+
}
|
|
479
|
+
onFusionComplete(e) {
|
|
480
|
+
const conf = Math.round(e.confidence * 100);
|
|
481
|
+
this.activityLog.push(`[補完] 融合完了: confidence ${conf}%`);
|
|
482
|
+
this.statusData.alertCode = 'FUSION:COMPLETE';
|
|
483
|
+
this.scheduleRender();
|
|
484
|
+
}
|
|
485
|
+
onLCLContamination(e) {
|
|
486
|
+
this.activityLog.push(`[LCL] ${e.unit} 汚染検出: ${e.hallucinationCount}件 (${e.types.join(', ')})`, EVA_PALETTE.warning);
|
|
487
|
+
this.statusData.alertCode = 'LCL:CONTAM';
|
|
488
|
+
this.scheduleRender();
|
|
489
|
+
}
|
|
490
|
+
onLCLPurified(e) {
|
|
491
|
+
const orig = Math.round(e.originalConfidence * 100);
|
|
492
|
+
const adj = Math.round(e.adjustedConfidence * 100);
|
|
493
|
+
this.activityLog.push(`[LCL] ${e.unit} 浄化完了: ${orig}% → ${adj}%`);
|
|
494
|
+
this.statusData.alertCode = 'LCL:PURIFIED';
|
|
495
|
+
this.scheduleRender();
|
|
496
|
+
}
|
|
497
|
+
// ── Render pipeline ─────────────────────────────────────
|
|
498
|
+
scheduleRender() {
|
|
499
|
+
if (this.renderScheduled || this.disposed)
|
|
500
|
+
return;
|
|
501
|
+
this.renderScheduled = true;
|
|
502
|
+
queueMicrotask(() => {
|
|
503
|
+
this.renderScheduled = false;
|
|
504
|
+
if (!this.disposed) {
|
|
505
|
+
this.render();
|
|
506
|
+
this.write(this.buf.flush());
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
render() {
|
|
511
|
+
const l = this.layout;
|
|
512
|
+
// Draw outer frame background (once; reset on resize)
|
|
513
|
+
if (!this.backgroundFilled) {
|
|
514
|
+
const bgc = EVA_PALETTE.background;
|
|
515
|
+
for (let r = 0; r < this.buf.rows; r++) {
|
|
516
|
+
for (let c = 0; c < this.buf.cols; c++) {
|
|
517
|
+
this.buf.writeAt(r, c, ' ', bgc.r, bgc.g, bgc.b, bgc.r, bgc.g, bgc.b);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
this.backgroundFilled = true;
|
|
521
|
+
}
|
|
522
|
+
if (this.lastOverlayRect) {
|
|
523
|
+
this.fillBackgroundRect(this.lastOverlayRect);
|
|
524
|
+
this.lastOverlayRect = undefined;
|
|
525
|
+
}
|
|
526
|
+
if (this.terminalFailure) {
|
|
527
|
+
this.renderTerminalWarning(this.terminalFailure);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
// Header
|
|
531
|
+
this.headerRenderer.drawHeader(this.buf, l.header, this.headerData);
|
|
532
|
+
this.headerRenderer.drawVerdictStamp(this.buf, l.verdictStamp, this.verdictStamp);
|
|
533
|
+
// Panels
|
|
534
|
+
this.panelRenderer.drawPanel(this.buf, l.balthasarPanel, this.buildPanelContent('BALTHASAR'));
|
|
535
|
+
this.panelRenderer.drawPanel(this.buf, l.casperPanel, this.buildPanelContent('CASPER'));
|
|
536
|
+
this.panelRenderer.drawPanel(this.buf, l.melchiorPanel, this.buildPanelContent('MELCHIOR'));
|
|
537
|
+
// MAGI node + connectors
|
|
538
|
+
this.panelRenderer.drawMagiNode(this.buf, l.magiNode, this.buildMagiNodeContent());
|
|
539
|
+
this.panelRenderer.drawConnectors(this.buf, l.connectors, this.connectorPulseIndex);
|
|
540
|
+
// Activity log
|
|
541
|
+
this.activityLog.draw(this.buf, l.activityLog);
|
|
542
|
+
// Status bar
|
|
543
|
+
this.statusBarRenderer.drawStatusBar(this.buf, l.statusBar, this.statusData);
|
|
544
|
+
if (this.overlayUnit) {
|
|
545
|
+
this.lastOverlayRect = this.renderDetailOverlay(this.overlayUnit);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
buildPanelContent(unit) {
|
|
549
|
+
const us = this.unitStates.get(unit) ?? { state: 'idle', breathPhase: 0 };
|
|
550
|
+
// Overlay berserk flash without mutating actual unit state (FIX-03)
|
|
551
|
+
const effectiveState = this.berserkFlashActive && us.state !== 'error' && us.state !== 'offline' && us.state !== 'complete'
|
|
552
|
+
? 'berserk'
|
|
553
|
+
: us.state;
|
|
554
|
+
return {
|
|
555
|
+
unit,
|
|
556
|
+
displayName: getDisplayName(unit),
|
|
557
|
+
state: effectiveState,
|
|
558
|
+
vote: us.vote,
|
|
559
|
+
confidence: us.confidence,
|
|
560
|
+
durationMs: us.durationMs,
|
|
561
|
+
activeDurationMs: us.startedAtMs !== undefined && us.state === 'thinking'
|
|
562
|
+
? Math.max(0, Date.now() - us.startedAtMs)
|
|
563
|
+
: undefined,
|
|
564
|
+
reasoning: us.reasoning,
|
|
565
|
+
breathPhase: us.breathPhase,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
buildMagiNodeContent() {
|
|
569
|
+
return {
|
|
570
|
+
phaseCode: phaseCode(this.statusData.phase),
|
|
571
|
+
roundLabel: this.statusData.roundNumber > 0 ? `R${this.statusData.roundNumber}` : undefined,
|
|
572
|
+
modeLabel: this.statusData.modeLabel,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
// ── Resize ──────────────────────────────────────────────
|
|
576
|
+
handleResize() {
|
|
577
|
+
const { cols, rows } = getTerminalSize();
|
|
578
|
+
this.buf.resize(cols, rows);
|
|
579
|
+
this.layout = calculateLayout(cols, rows);
|
|
580
|
+
this.terminalFailure = this.getTerminalFailure(cols, rows);
|
|
581
|
+
this.buf.invalidate();
|
|
582
|
+
this.backgroundFilled = false;
|
|
583
|
+
this.write(CLEAR_SCREEN + CURSOR_HOME);
|
|
584
|
+
this.render();
|
|
585
|
+
this.write(this.buf.flush());
|
|
586
|
+
}
|
|
587
|
+
// ── Elapsed timer ───────────────────────────────────────
|
|
588
|
+
startElapsedTimer() {
|
|
589
|
+
this.stopElapsedTimer();
|
|
590
|
+
this.elapsedTimer = setInterval(() => {
|
|
591
|
+
this.statusData.elapsedMs = Date.now() - this.startTime;
|
|
592
|
+
this.updateLiveActivityStatus();
|
|
593
|
+
this.scheduleRender();
|
|
594
|
+
}, 500);
|
|
595
|
+
}
|
|
596
|
+
stopElapsedTimer() {
|
|
597
|
+
if (this.elapsedTimer) {
|
|
598
|
+
clearInterval(this.elapsedTimer);
|
|
599
|
+
this.elapsedTimer = undefined;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// ── Helpers ─────────────────────────────────────────────
|
|
603
|
+
getOrCreateUnit(unit) {
|
|
604
|
+
let us = this.unitStates.get(unit);
|
|
605
|
+
if (!us) {
|
|
606
|
+
us = { state: 'idle', breathPhase: 0 };
|
|
607
|
+
this.unitStates.set(unit, us);
|
|
608
|
+
}
|
|
609
|
+
return us;
|
|
610
|
+
}
|
|
611
|
+
getTerminalFailure(cols, rows) {
|
|
612
|
+
if (cols < MIN_COLS || rows < MIN_ROWS) {
|
|
613
|
+
return `Terminal too small (${cols}x${rows}, need ${MIN_COLS}x${MIN_ROWS})`;
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
renderTerminalWarning(message) {
|
|
618
|
+
const frame = EVA_PALETTE.warning;
|
|
619
|
+
const title = 'TACTICAL DISPLAY PAUSED';
|
|
620
|
+
const messageLines = [
|
|
621
|
+
title,
|
|
622
|
+
truncateToDisplayWidth(message, Math.max(12, this.buf.cols - 8)),
|
|
623
|
+
`Resize terminal to at least ${MIN_COLS}x${MIN_ROWS}.`,
|
|
624
|
+
];
|
|
625
|
+
const desiredWidth = Math.max(18, Math.max(...messageLines.map(line => stringDisplayWidth(line))) + 4);
|
|
626
|
+
const boxWidth = Math.max(10, Math.min(this.buf.cols - 2, desiredWidth));
|
|
627
|
+
const boxHeight = messageLines.length + 2;
|
|
628
|
+
const top = Math.max(0, Math.floor((this.buf.rows - boxHeight) / 2));
|
|
629
|
+
const left = Math.max(0, Math.floor((this.buf.cols - boxWidth) / 2));
|
|
630
|
+
for (let x = left; x < left + boxWidth; x++) {
|
|
631
|
+
this.buf.writeAt(top, x, x === left ? '┌' : x === left + boxWidth - 1 ? '┐' : '─', frame.r, frame.g, frame.b);
|
|
632
|
+
this.buf.writeAt(top + boxHeight - 1, x, x === left ? '└' : x === left + boxWidth - 1 ? '┘' : '─', frame.r, frame.g, frame.b);
|
|
633
|
+
}
|
|
634
|
+
for (let y = top + 1; y < top + boxHeight - 1; y++) {
|
|
635
|
+
this.buf.writeAt(y, left, '│', frame.r, frame.g, frame.b);
|
|
636
|
+
this.buf.writeAt(y, left + boxWidth - 1, '│', frame.r, frame.g, frame.b);
|
|
637
|
+
}
|
|
638
|
+
const textColor = EVA_PALETTE.textPrimary;
|
|
639
|
+
for (let i = 0; i < messageLines.length; i++) {
|
|
640
|
+
const line = truncateToDisplayWidth(messageLines[i], boxWidth - 4);
|
|
641
|
+
const startCol = left + Math.max(1, Math.floor((boxWidth - stringDisplayWidth(line)) / 2));
|
|
642
|
+
this.buf.writeAt(top + 1 + i, startCol, line, textColor.r, textColor.g, textColor.b);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
write(s) {
|
|
646
|
+
if (s && !this.disposed) {
|
|
647
|
+
this.stream.write(s);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
handleKeypress(key) {
|
|
651
|
+
if (key === 'escape' || key === 'q') {
|
|
652
|
+
if (this.overlayUnit) {
|
|
653
|
+
this.overlayUnit = undefined;
|
|
654
|
+
this.scheduleRender();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
this.resolveDismiss();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const unit = UNIT_KEYS[key];
|
|
661
|
+
if (unit && this.tuiPhase !== 'booting') {
|
|
662
|
+
this.overlayUnit = unit;
|
|
663
|
+
this.scheduleRender();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
this.resolveDismiss();
|
|
667
|
+
}
|
|
668
|
+
/** Resolve pending dismiss promise if in dismiss phase (FIX-02). */
|
|
669
|
+
resolveDismiss() {
|
|
670
|
+
if (this.dismissResolver) {
|
|
671
|
+
const resolve = this.dismissResolver;
|
|
672
|
+
this.dismissResolver = undefined;
|
|
673
|
+
resolve();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
renderDetailOverlay(unit) {
|
|
677
|
+
const overlayRect = {
|
|
678
|
+
row: 3,
|
|
679
|
+
col: 4,
|
|
680
|
+
width: Math.max(40, this.buf.cols - 8),
|
|
681
|
+
height: Math.max(10, this.buf.rows - 6),
|
|
682
|
+
};
|
|
683
|
+
this.panelRenderer.drawUnitDetailOverlay(this.buf, overlayRect, this.buildPanelContent(unit));
|
|
684
|
+
return overlayRect;
|
|
685
|
+
}
|
|
686
|
+
playSound(kind) {
|
|
687
|
+
if (!this.soundEnabled || this.disposed)
|
|
688
|
+
return;
|
|
689
|
+
const repeat = kind === 'decision' ? 2 : 1;
|
|
690
|
+
for (let i = 0; i < repeat; i++) {
|
|
691
|
+
this.stream.write('\x07');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
fillBackgroundRect(rect) {
|
|
695
|
+
const bgc = EVA_PALETTE.background;
|
|
696
|
+
for (let r = rect.row; r < rect.row + rect.height; r++) {
|
|
697
|
+
for (let c = rect.col; c < rect.col + rect.width; c++) {
|
|
698
|
+
this.buf.writeAt(r, c, ' ', bgc.r, bgc.g, bgc.b, bgc.r, bgc.g, bgc.b);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
updateLiveActivityStatus() {
|
|
703
|
+
const activeUnits = KNOWN_UNITS.filter((unit) => this.unitStates.get(unit)?.state === 'thinking');
|
|
704
|
+
if (this.tuiPhase !== 'deliberating') {
|
|
705
|
+
this.activityLog.setLive(undefined);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (activeUnits.length === 0) {
|
|
709
|
+
this.activityLog.setLive('ACTIVE LINK WAITING', EVA_PALETTE.magi);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const parts = [];
|
|
713
|
+
for (const unit of activeUnits) {
|
|
714
|
+
const state = this.unitStates.get(unit);
|
|
715
|
+
const elapsed = state?.startedAtMs !== undefined ? formatElapsedMmSs(Date.now() - state.startedAtMs) : '--:--';
|
|
716
|
+
parts.push(`${unit} ${elapsed}`);
|
|
717
|
+
}
|
|
718
|
+
this.activityLog.setLive(`ACTIVE ${parts.join(' // ')}`, EVA_PALETTE.magi);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function formatSyncRateSummary(syncRates) {
|
|
722
|
+
const values = KNOWN_UNITS
|
|
723
|
+
.map(unit => syncRates[unit])
|
|
724
|
+
.filter((value) => value !== undefined);
|
|
725
|
+
if (values.length === 0)
|
|
726
|
+
return 'AVG -- LOW --';
|
|
727
|
+
const avg = Math.round(values.reduce((sum, value) => sum + value, 0) / values.length * 100);
|
|
728
|
+
const low = Math.round(Math.min(...values) * 100);
|
|
729
|
+
return `AVG ${avg}% LOW ${low}%`;
|
|
730
|
+
}
|
|
731
|
+
function formatConfidenceSummary(unitStates) {
|
|
732
|
+
const values = KNOWN_UNITS
|
|
733
|
+
.map(unit => unitStates.get(unit)?.confidence)
|
|
734
|
+
.filter((value) => value !== undefined);
|
|
735
|
+
if (values.length === 0)
|
|
736
|
+
return 'AVG -- LOW --';
|
|
737
|
+
const avg = Math.round(values.reduce((sum, value) => sum + value, 0) / values.length * 100);
|
|
738
|
+
const low = Math.round(Math.min(...values) * 100);
|
|
739
|
+
return `AVG ${avg}% LOW ${low}%`;
|
|
740
|
+
}
|
|
741
|
+
function decisionStampText(decision) {
|
|
742
|
+
if (isApproval(decision))
|
|
743
|
+
return '可 決';
|
|
744
|
+
if (isRejection(decision))
|
|
745
|
+
return '否 決';
|
|
746
|
+
return '膠 着';
|
|
747
|
+
}
|
|
748
|
+
function decisionTone(decision) {
|
|
749
|
+
if (isApproval(decision))
|
|
750
|
+
return 'approve';
|
|
751
|
+
if (isRejection(decision))
|
|
752
|
+
return 'reject';
|
|
753
|
+
return 'deadlock';
|
|
754
|
+
}
|