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,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B-07 Seele Council (ゼーレ会議)
|
|
3
|
+
*
|
|
4
|
+
* 分散MAGI合議 — 複数のMAGIノード間でPBFT/Raftによるコンセンサスを実現。
|
|
5
|
+
* シミュレーションモード (インメモリ) とネットワークモード (WebSocket) を切替可能。
|
|
6
|
+
*
|
|
7
|
+
* - PBFT (Layer 1): サブグループ内の合意形成
|
|
8
|
+
* - Raft (Layer 2): リーダー選出と提案管理
|
|
9
|
+
* - Ed25519署名: メッセージの真正性検証
|
|
10
|
+
* - SOUND ONLYパネル: 匿名参加者の可視化
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID, sign, verify, generateKeyPairSync } from 'node:crypto';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
14
|
+
// ── Constants ────────────────────────────────────────────────────
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
pbftTimeout: 5000,
|
|
17
|
+
raftElectionTimeout: 3000,
|
|
18
|
+
maxRetries: 3,
|
|
19
|
+
};
|
|
20
|
+
const SOUND_ONLY_PANEL_WIDTH = 60;
|
|
21
|
+
/** The 6 MAGI installations worldwide */
|
|
22
|
+
const MAGI_NODES = [
|
|
23
|
+
{ id: 'MAGI-01', location: 'NERV HQ (箱根)', host: 'localhost', port: 7701 },
|
|
24
|
+
{ id: 'MAGI-02', location: 'NERV Berlin', host: 'localhost', port: 7702 },
|
|
25
|
+
{ id: 'MAGI-03', location: 'NERV Massachusetts', host: 'localhost', port: 7703 },
|
|
26
|
+
{ id: 'MAGI-04', location: 'NERV Beijing', host: 'localhost', port: 7704 },
|
|
27
|
+
{ id: 'MAGI-05', location: 'NERV Matsushiro', host: 'localhost', port: 7705 },
|
|
28
|
+
{ id: 'MAGI-06', location: 'NERV Moscow', host: 'localhost', port: 7706 },
|
|
29
|
+
];
|
|
30
|
+
/** PBFT requires 2f+1 nodes to tolerate f faults */
|
|
31
|
+
function pbftQuorum(totalNodes) {
|
|
32
|
+
const f = Math.floor((totalNodes - 1) / 3);
|
|
33
|
+
return 2 * f + 1;
|
|
34
|
+
}
|
|
35
|
+
export class SeeleCouncil {
|
|
36
|
+
localNode;
|
|
37
|
+
eventBus;
|
|
38
|
+
config;
|
|
39
|
+
nodes = new Map();
|
|
40
|
+
keyPairs = new Map();
|
|
41
|
+
session = null;
|
|
42
|
+
raftRole = 'follower';
|
|
43
|
+
raftTerm = 0;
|
|
44
|
+
messageSequence = 0;
|
|
45
|
+
constructor(localNode, eventBus, config) {
|
|
46
|
+
this.localNode = localNode;
|
|
47
|
+
this.eventBus = eventBus;
|
|
48
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
49
|
+
}
|
|
50
|
+
// ── Node Management ───────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Initialize nodes for simulation mode.
|
|
53
|
+
*/
|
|
54
|
+
initializeNodes(nodeIds) {
|
|
55
|
+
const ids = nodeIds ?? MAGI_NODES.map(n => n.id);
|
|
56
|
+
for (const id of ids) {
|
|
57
|
+
const template = MAGI_NODES.find(n => n.id === id);
|
|
58
|
+
const keyPair = generateKeyPairSync('ed25519');
|
|
59
|
+
const publicKeyHex = keyPair.publicKey.export({ type: 'spki', format: 'der' }).toString('hex');
|
|
60
|
+
this.keyPairs.set(id, {
|
|
61
|
+
publicKey: keyPair.publicKey,
|
|
62
|
+
privateKey: keyPair.privateKey,
|
|
63
|
+
publicKeyHex,
|
|
64
|
+
});
|
|
65
|
+
const node = {
|
|
66
|
+
id,
|
|
67
|
+
location: template?.location ?? `Node ${id}`,
|
|
68
|
+
host: template?.host ?? 'localhost',
|
|
69
|
+
port: template?.port ?? 7700,
|
|
70
|
+
publicKey: publicKeyHex,
|
|
71
|
+
status: 'online',
|
|
72
|
+
};
|
|
73
|
+
this.nodes.set(id, node);
|
|
74
|
+
}
|
|
75
|
+
logger.info('Seele: nodes initialized', { count: this.nodes.size });
|
|
76
|
+
}
|
|
77
|
+
// ── PBFT Consensus (Layer 1) ──────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Run PBFT consensus among a subgroup of nodes.
|
|
80
|
+
*/
|
|
81
|
+
async pbftConsensus(subgroup, proposal) {
|
|
82
|
+
const quorum = pbftQuorum(subgroup.length);
|
|
83
|
+
const votes = new Map();
|
|
84
|
+
// Pre-prepare phase (broadcast to session log)
|
|
85
|
+
this.createMessage(this.localNode, this.localNode, 'pre-prepare', {
|
|
86
|
+
proposal,
|
|
87
|
+
view: this.raftTerm,
|
|
88
|
+
});
|
|
89
|
+
// Prepare phase — each node validates and responds
|
|
90
|
+
for (const nodeId of subgroup) {
|
|
91
|
+
const node = this.nodes.get(nodeId);
|
|
92
|
+
if (!node || node.status !== 'online') {
|
|
93
|
+
votes.set(nodeId, false);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Simulate validation (online nodes agree)
|
|
97
|
+
const prepareMsg = this.createMessage(nodeId, this.localNode, 'prepare', {
|
|
98
|
+
proposal,
|
|
99
|
+
accept: true,
|
|
100
|
+
});
|
|
101
|
+
votes.set(nodeId, this.verifyMessage(prepareMsg, nodeId));
|
|
102
|
+
}
|
|
103
|
+
// Commit phase — check quorum
|
|
104
|
+
const agreeCount = [...votes.values()].filter(v => v).length;
|
|
105
|
+
const agreed = agreeCount >= quorum;
|
|
106
|
+
// Send commit messages
|
|
107
|
+
for (const nodeId of subgroup) {
|
|
108
|
+
if (votes.get(nodeId)) {
|
|
109
|
+
this.createMessage(nodeId, this.localNode, 'commit', {
|
|
110
|
+
proposal,
|
|
111
|
+
agreed,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
logger.info('Seele: PBFT consensus', {
|
|
116
|
+
agreed,
|
|
117
|
+
votes: agreeCount,
|
|
118
|
+
quorum,
|
|
119
|
+
total: subgroup.length,
|
|
120
|
+
});
|
|
121
|
+
return { agreed, votes };
|
|
122
|
+
}
|
|
123
|
+
// ── Raft Leader Election (Layer 2) ─────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* Perform Raft leader election among all nodes.
|
|
126
|
+
*/
|
|
127
|
+
async raftElection() {
|
|
128
|
+
this.raftTerm++;
|
|
129
|
+
this.raftRole = 'candidate';
|
|
130
|
+
const onlineNodes = [...this.nodes.entries()]
|
|
131
|
+
.filter(([_, n]) => n.status === 'online')
|
|
132
|
+
.map(([id]) => id);
|
|
133
|
+
const votes = new Map();
|
|
134
|
+
// Self-vote
|
|
135
|
+
votes.set(this.localNode, this.localNode);
|
|
136
|
+
// Request votes from other nodes
|
|
137
|
+
for (const nodeId of onlineNodes) {
|
|
138
|
+
if (nodeId === this.localNode)
|
|
139
|
+
continue;
|
|
140
|
+
// Simulate: nodes vote for the candidate (simplified Raft)
|
|
141
|
+
const msg = this.createMessage(nodeId, this.localNode, 'raft-vote', {
|
|
142
|
+
term: this.raftTerm,
|
|
143
|
+
candidateId: this.localNode,
|
|
144
|
+
granted: true,
|
|
145
|
+
});
|
|
146
|
+
if (this.verifyMessage(msg, nodeId)) {
|
|
147
|
+
votes.set(nodeId, this.localNode);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const majority = Math.floor(onlineNodes.length / 2) + 1;
|
|
151
|
+
const voteCount = [...votes.values()].filter(v => v === this.localNode).length;
|
|
152
|
+
let leader;
|
|
153
|
+
if (voteCount >= majority) {
|
|
154
|
+
this.raftRole = 'leader';
|
|
155
|
+
leader = this.localNode;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.raftRole = 'follower';
|
|
159
|
+
leader = onlineNodes[0]; // Fallback to first online node
|
|
160
|
+
}
|
|
161
|
+
logger.info('Seele: Raft election complete', {
|
|
162
|
+
leader,
|
|
163
|
+
term: this.raftTerm,
|
|
164
|
+
votes: voteCount,
|
|
165
|
+
majority,
|
|
166
|
+
});
|
|
167
|
+
return leader;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Propose a decision through Raft (leader only).
|
|
171
|
+
*/
|
|
172
|
+
async raftPropose(decision) {
|
|
173
|
+
if (this.raftRole !== 'leader') {
|
|
174
|
+
logger.warn('Seele: cannot propose — not leader');
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const onlineNodes = [...this.nodes.entries()]
|
|
178
|
+
.filter(([_, n]) => n.status === 'online')
|
|
179
|
+
.map(([id]) => id);
|
|
180
|
+
// Use PBFT for the actual consensus
|
|
181
|
+
const result = await this.pbftConsensus(onlineNodes, decision);
|
|
182
|
+
return result.agreed;
|
|
183
|
+
}
|
|
184
|
+
// ── Attack Mode ───────────────────────────────────────────
|
|
185
|
+
/**
|
|
186
|
+
* Simulate an attack from attacker nodes against a target.
|
|
187
|
+
*/
|
|
188
|
+
async executeAttack(attackers, target) {
|
|
189
|
+
if (!this.session) {
|
|
190
|
+
this.session = this.createSession();
|
|
191
|
+
}
|
|
192
|
+
this.session.phase = 'ATTACK';
|
|
193
|
+
this.eventBus?.emit('seele:attack', {
|
|
194
|
+
sessionId: this.session.id,
|
|
195
|
+
attackers,
|
|
196
|
+
target,
|
|
197
|
+
emittedAt: new Date(),
|
|
198
|
+
});
|
|
199
|
+
// Mark attackers as compromised
|
|
200
|
+
for (const attacker of attackers) {
|
|
201
|
+
const node = this.nodes.get(attacker);
|
|
202
|
+
if (node) {
|
|
203
|
+
node.status = 'compromised';
|
|
204
|
+
this.nodes.set(attacker, node);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Check if defense holds: target + remaining online nodes form quorum
|
|
208
|
+
const onlineNodes = [...this.nodes.entries()]
|
|
209
|
+
.filter(([_, n]) => n.status === 'online')
|
|
210
|
+
.map(([id]) => id);
|
|
211
|
+
const totalNodes = this.nodes.size;
|
|
212
|
+
const quorum = pbftQuorum(totalNodes);
|
|
213
|
+
const defenseHeld = onlineNodes.length >= quorum;
|
|
214
|
+
logger.info('Seele: attack executed', {
|
|
215
|
+
attackers,
|
|
216
|
+
target,
|
|
217
|
+
defenseHeld,
|
|
218
|
+
onlineNodes: onlineNodes.length,
|
|
219
|
+
quorum,
|
|
220
|
+
});
|
|
221
|
+
return { success: !defenseHeld, defenseHeld };
|
|
222
|
+
}
|
|
223
|
+
// ── Session Management ────────────────────────────────────
|
|
224
|
+
/**
|
|
225
|
+
* Start a new Seele council session.
|
|
226
|
+
*/
|
|
227
|
+
async startSession() {
|
|
228
|
+
this.session = this.createSession();
|
|
229
|
+
this.eventBus?.emit('seele:gathering', {
|
|
230
|
+
sessionId: this.session.id,
|
|
231
|
+
nodes: [...this.nodes.keys()],
|
|
232
|
+
emittedAt: new Date(),
|
|
233
|
+
});
|
|
234
|
+
logger.info('Seele: session started', { sessionId: this.session.id });
|
|
235
|
+
return this.session;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Conclude a session with a decision.
|
|
239
|
+
*/
|
|
240
|
+
async concludeSession(decision) {
|
|
241
|
+
if (!this.session) {
|
|
242
|
+
throw new Error('Seele: no active session');
|
|
243
|
+
}
|
|
244
|
+
this.session.phase = 'CONCLUDED';
|
|
245
|
+
this.session.decision = decision;
|
|
246
|
+
this.session.completedAt = new Date().toISOString();
|
|
247
|
+
this.eventBus?.emit('seele:concluded', {
|
|
248
|
+
sessionId: this.session.id,
|
|
249
|
+
decision,
|
|
250
|
+
leader: this.session.leader,
|
|
251
|
+
emittedAt: new Date(),
|
|
252
|
+
});
|
|
253
|
+
logger.info('Seele: session concluded', {
|
|
254
|
+
sessionId: this.session.id,
|
|
255
|
+
decision,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// ── Simulation Mode ───────────────────────────────────────
|
|
259
|
+
/**
|
|
260
|
+
* Run a full simulated Seele council session.
|
|
261
|
+
*/
|
|
262
|
+
async simulateSession() {
|
|
263
|
+
// Initialize nodes if not already done
|
|
264
|
+
if (this.nodes.size === 0) {
|
|
265
|
+
this.initializeNodes();
|
|
266
|
+
}
|
|
267
|
+
// Start session
|
|
268
|
+
const session = await this.startSession();
|
|
269
|
+
// Leader election
|
|
270
|
+
session.phase = 'LEADER_ELECTION';
|
|
271
|
+
const leader = await this.raftElection();
|
|
272
|
+
session.leader = leader;
|
|
273
|
+
// Deliberation via PBFT
|
|
274
|
+
session.phase = 'DELIBERATION';
|
|
275
|
+
const allNodes = [...this.nodes.keys()];
|
|
276
|
+
const result = await this.pbftConsensus(allNodes, 'simulation-proposal');
|
|
277
|
+
// Consensus
|
|
278
|
+
session.phase = 'CONSENSUS';
|
|
279
|
+
const decision = result.agreed ? 'APPROVED' : 'REJECTED';
|
|
280
|
+
// Conclude
|
|
281
|
+
await this.concludeSession(decision);
|
|
282
|
+
return session;
|
|
283
|
+
}
|
|
284
|
+
// ── Message Signing & Verification ─────────────────────────
|
|
285
|
+
/**
|
|
286
|
+
* Create and sign a message.
|
|
287
|
+
*/
|
|
288
|
+
createMessage(from, to, type, payload) {
|
|
289
|
+
this.messageSequence++;
|
|
290
|
+
const msg = {
|
|
291
|
+
from,
|
|
292
|
+
to,
|
|
293
|
+
type,
|
|
294
|
+
payload,
|
|
295
|
+
signature: '',
|
|
296
|
+
timestamp: new Date().toISOString(),
|
|
297
|
+
sequence: this.messageSequence,
|
|
298
|
+
};
|
|
299
|
+
// Sign the message
|
|
300
|
+
msg.signature = this.signMessage(msg, from);
|
|
301
|
+
if (this.session) {
|
|
302
|
+
this.session.messages.push(msg);
|
|
303
|
+
}
|
|
304
|
+
return msg;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Sign a message with Ed25519 private key.
|
|
308
|
+
*/
|
|
309
|
+
signMessage(msg, nodeId) {
|
|
310
|
+
const keyPair = this.keyPairs.get(nodeId);
|
|
311
|
+
if (!keyPair)
|
|
312
|
+
return 'unsigned';
|
|
313
|
+
try {
|
|
314
|
+
const data = Buffer.from(JSON.stringify({
|
|
315
|
+
from: msg.from,
|
|
316
|
+
to: msg.to,
|
|
317
|
+
type: msg.type,
|
|
318
|
+
payload: msg.payload,
|
|
319
|
+
timestamp: msg.timestamp,
|
|
320
|
+
sequence: msg.sequence,
|
|
321
|
+
}));
|
|
322
|
+
const signature = sign(null, data, keyPair.privateKey);
|
|
323
|
+
return signature.toString('hex');
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
logger.debug('Seele: message signing failed', { error: String(err) });
|
|
327
|
+
return 'sign-error';
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Verify a message signature.
|
|
332
|
+
*/
|
|
333
|
+
verifyMessage(msg, nodeId) {
|
|
334
|
+
const keyPair = this.keyPairs.get(nodeId);
|
|
335
|
+
if (!keyPair || msg.signature === 'unsigned' || msg.signature === 'sign-error') {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const data = Buffer.from(JSON.stringify({
|
|
340
|
+
from: msg.from,
|
|
341
|
+
to: msg.to,
|
|
342
|
+
type: msg.type,
|
|
343
|
+
payload: msg.payload,
|
|
344
|
+
timestamp: msg.timestamp,
|
|
345
|
+
sequence: msg.sequence,
|
|
346
|
+
}));
|
|
347
|
+
return verify(null, data, keyPair.publicKey, Buffer.from(msg.signature, 'hex'));
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
logger.debug('Seele: message verification failed', { error: String(err) });
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// ── SOUND ONLY Panel ──────────────────────────────────────
|
|
355
|
+
/**
|
|
356
|
+
* Generate SOUND ONLY panel visualization (ANSI art).
|
|
357
|
+
*/
|
|
358
|
+
formatSoundOnlyPanel() {
|
|
359
|
+
const lines = [];
|
|
360
|
+
const border = '═'.repeat(SOUND_ONLY_PANEL_WIDTH);
|
|
361
|
+
lines.push(`╔${border}╗`);
|
|
362
|
+
lines.push(`║${centerToDisplayWidth('SEELE', SOUND_ONLY_PANEL_WIDTH)}║`);
|
|
363
|
+
lines.push(`║${centerToDisplayWidth('SOUND ONLY', SOUND_ONLY_PANEL_WIDTH)}║`);
|
|
364
|
+
lines.push(`╠${border}╣`);
|
|
365
|
+
const nodeList = [...this.nodes.entries()];
|
|
366
|
+
for (const [id, node] of nodeList) {
|
|
367
|
+
const statusIcon = node.status === 'online' ? '●'
|
|
368
|
+
: node.status === 'compromised' ? '✗' : '○';
|
|
369
|
+
const label = `${statusIcon} ${id} — ${node.location}`;
|
|
370
|
+
lines.push(`║${padToDisplayWidth(` ${label}`, SOUND_ONLY_PANEL_WIDTH)}║`);
|
|
371
|
+
}
|
|
372
|
+
lines.push(`╠${border}╣`);
|
|
373
|
+
if (this.session) {
|
|
374
|
+
const sessionInfo = `Session: ${this.session.id.slice(0, 8)} | Phase: ${this.session.phase}`;
|
|
375
|
+
lines.push(`║${padToDisplayWidth(` ${sessionInfo}`, SOUND_ONLY_PANEL_WIDTH)}║`);
|
|
376
|
+
const leaderInfo = `Leader: ${this.session.leader ?? 'N/A'} | Messages: ${this.session.messages.length}`;
|
|
377
|
+
lines.push(`║${padToDisplayWidth(` ${leaderInfo}`, SOUND_ONLY_PANEL_WIDTH)}║`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
lines.push(`║${padToDisplayWidth(' No active session', SOUND_ONLY_PANEL_WIDTH)}║`);
|
|
381
|
+
}
|
|
382
|
+
lines.push(`╚${border}╝`);
|
|
383
|
+
return lines.join('\n');
|
|
384
|
+
}
|
|
385
|
+
// ── Key Material Cleanup ─────────────────────────────────
|
|
386
|
+
/**
|
|
387
|
+
* Securely destroy all private key material.
|
|
388
|
+
* Best-effort: zeros exported DER buffers and clears references.
|
|
389
|
+
*/
|
|
390
|
+
destroy() {
|
|
391
|
+
for (const [, pair] of this.keyPairs) {
|
|
392
|
+
try {
|
|
393
|
+
const der = pair.privateKey.export({ type: 'pkcs8', format: 'der' });
|
|
394
|
+
der.fill(0);
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// KeyObject may already be unusable — ignore
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
this.keyPairs.clear();
|
|
401
|
+
this.nodes.clear();
|
|
402
|
+
this.session = null;
|
|
403
|
+
logger.info('Seele: key material destroyed');
|
|
404
|
+
}
|
|
405
|
+
// ── State Accessors ───────────────────────────────────────
|
|
406
|
+
getSession() {
|
|
407
|
+
return this.session;
|
|
408
|
+
}
|
|
409
|
+
getNodes() {
|
|
410
|
+
return new Map(this.nodes);
|
|
411
|
+
}
|
|
412
|
+
getLocalNode() {
|
|
413
|
+
return this.localNode;
|
|
414
|
+
}
|
|
415
|
+
getRaftRole() {
|
|
416
|
+
return this.raftRole;
|
|
417
|
+
}
|
|
418
|
+
getRaftTerm() {
|
|
419
|
+
return this.raftTerm;
|
|
420
|
+
}
|
|
421
|
+
// ── Helpers ───────────────────────────────────────────────
|
|
422
|
+
createSession() {
|
|
423
|
+
return {
|
|
424
|
+
id: randomUUID(),
|
|
425
|
+
nodes: [...this.nodes.values()],
|
|
426
|
+
leader: null,
|
|
427
|
+
phase: 'GATHERING',
|
|
428
|
+
messages: [],
|
|
429
|
+
decision: null,
|
|
430
|
+
startedAt: new Date().toISOString(),
|
|
431
|
+
completedAt: null,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function charDisplayWidth(codePoint) {
|
|
436
|
+
if ((codePoint >= 0x1100 && codePoint <= 0x115F)
|
|
437
|
+
|| (codePoint >= 0x2E80 && codePoint <= 0x303E)
|
|
438
|
+
|| (codePoint >= 0x3041 && codePoint <= 0x33BF)
|
|
439
|
+
|| (codePoint >= 0x3400 && codePoint <= 0x4DBF)
|
|
440
|
+
|| (codePoint >= 0x4E00 && codePoint <= 0x9FFF)
|
|
441
|
+
|| (codePoint >= 0xA000 && codePoint <= 0xA4CF)
|
|
442
|
+
|| (codePoint >= 0xAC00 && codePoint <= 0xD7AF)
|
|
443
|
+
|| (codePoint >= 0xF900 && codePoint <= 0xFAFF)
|
|
444
|
+
|| (codePoint >= 0xFE30 && codePoint <= 0xFE4F)
|
|
445
|
+
|| (codePoint >= 0xFF01 && codePoint <= 0xFF60)
|
|
446
|
+
|| (codePoint >= 0xFFE0 && codePoint <= 0xFFE6)
|
|
447
|
+
|| (codePoint >= 0x20000 && codePoint <= 0x2FA1F)) {
|
|
448
|
+
return 2;
|
|
449
|
+
}
|
|
450
|
+
return 1;
|
|
451
|
+
}
|
|
452
|
+
function stringDisplayWidth(text) {
|
|
453
|
+
let width = 0;
|
|
454
|
+
for (const ch of text) {
|
|
455
|
+
width += charDisplayWidth(ch.codePointAt(0) ?? 0);
|
|
456
|
+
}
|
|
457
|
+
return width;
|
|
458
|
+
}
|
|
459
|
+
function truncateToDisplayWidth(text, maxWidth) {
|
|
460
|
+
let width = 0;
|
|
461
|
+
let result = '';
|
|
462
|
+
for (const ch of text) {
|
|
463
|
+
const charWidth = charDisplayWidth(ch.codePointAt(0) ?? 0);
|
|
464
|
+
if (width + charWidth > maxWidth) {
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
result += ch;
|
|
468
|
+
width += charWidth;
|
|
469
|
+
}
|
|
470
|
+
return result;
|
|
471
|
+
}
|
|
472
|
+
function padToDisplayWidth(text, width) {
|
|
473
|
+
const truncated = truncateToDisplayWidth(text, width);
|
|
474
|
+
const fillWidth = Math.max(0, width - stringDisplayWidth(truncated));
|
|
475
|
+
return truncated + ' '.repeat(fillWidth);
|
|
476
|
+
}
|
|
477
|
+
function centerToDisplayWidth(text, width) {
|
|
478
|
+
const truncated = truncateToDisplayWidth(text, width);
|
|
479
|
+
const displayWidth = stringDisplayWidth(truncated);
|
|
480
|
+
const leftPad = Math.max(0, Math.floor((width - displayWidth) / 2));
|
|
481
|
+
return ' '.repeat(leftPad) + padToDisplayWidth(truncated, width - leftPad);
|
|
482
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B-04 Self-Destruct Sequence (自爆シーケンス)
|
|
3
|
+
*
|
|
4
|
+
* Unanimous irreversible operations. All MAGI units must vote APPROVE
|
|
5
|
+
* for the sequence to execute. A single REJECT vote blocks execution (f=0 BFT).
|
|
6
|
+
*
|
|
7
|
+
* Phase state machine:
|
|
8
|
+
* IDLE -> MOTION_FILED -> DELIBERATING -> COUNTDOWN(200ms) -> EXECUTING -> COMPLETED
|
|
9
|
+
* \-> ABORTED (any REJECT or 2+ units request abort)
|
|
10
|
+
*/
|
|
11
|
+
import type { MagiUnit, Vote } from '../types/core.js';
|
|
12
|
+
import type { SelfDestructPhase, SelfDestructMotion } from '../types/phase-k.js';
|
|
13
|
+
import type { MagiEventBus } from './events.js';
|
|
14
|
+
import type { SecretCipher } from './secret-cipher.js';
|
|
15
|
+
export declare class SelfDestructSequence {
|
|
16
|
+
private phase;
|
|
17
|
+
private motion;
|
|
18
|
+
private readonly units;
|
|
19
|
+
private readonly eventBus;
|
|
20
|
+
private readonly secretCipher;
|
|
21
|
+
constructor(units: MagiUnit[], eventBus?: MagiEventBus, secretCipher?: SecretCipher);
|
|
22
|
+
/**
|
|
23
|
+
* File a self-destruct motion. Must be in IDLE phase.
|
|
24
|
+
*/
|
|
25
|
+
fileMotion(reason: string, filedBy: string): SelfDestructMotion;
|
|
26
|
+
/**
|
|
27
|
+
* Cast a vote on the motion. Must be in MOTION_FILED phase.
|
|
28
|
+
* When all units have voted, transitions to DELIBERATING and evaluates.
|
|
29
|
+
*/
|
|
30
|
+
castVote(motionId: string, unit: MagiUnit, vote: Vote): void;
|
|
31
|
+
/**
|
|
32
|
+
* Evaluate the votes. Requires all units to have voted.
|
|
33
|
+
* - All APPROVE -> COUNTDOWN
|
|
34
|
+
* - Any REJECT -> ABORTED
|
|
35
|
+
* - Otherwise stays DELIBERATING
|
|
36
|
+
*/
|
|
37
|
+
evaluate(motionId: string): SelfDestructPhase;
|
|
38
|
+
/**
|
|
39
|
+
* Execute the countdown sequence. Must be in COUNTDOWN phase.
|
|
40
|
+
* Iterates through geoid depths, emitting events at each step.
|
|
41
|
+
* After countdown, attempts B-08 cipher reveal, then transitions to COMPLETED.
|
|
42
|
+
*/
|
|
43
|
+
executeCountdown(motionId: string): Promise<SelfDestructMotion>;
|
|
44
|
+
/**
|
|
45
|
+
* Request abort from multiple units. Requires 2+ units to agree.
|
|
46
|
+
* Can abort from MOTION_FILED, DELIBERATING, or COUNTDOWN.
|
|
47
|
+
*/
|
|
48
|
+
requestAbort(motionId: string, units: MagiUnit[]): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Get the current motion, if any.
|
|
51
|
+
*/
|
|
52
|
+
getMotion(): SelfDestructMotion | null;
|
|
53
|
+
/**
|
|
54
|
+
* Get the current phase.
|
|
55
|
+
*/
|
|
56
|
+
getPhase(): SelfDestructPhase;
|
|
57
|
+
/**
|
|
58
|
+
* Reset the sequence to IDLE state.
|
|
59
|
+
*/
|
|
60
|
+
reset(): void;
|
|
61
|
+
}
|