monomind 2.0.3 → 2.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/package.json +9 -5
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
- package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +75 -18
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +16 -7
- package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +4 -4
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +40 -22
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +54 -4
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
- package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
- package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
- package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
- package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
- package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
- package/packages/@monomind/cli/bin/cli.js +10 -1
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
- package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
- package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
- package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
- package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +306 -129
- package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
- package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
- package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
- package/packages/@monomind/cli/dist/src/index.js +64 -28
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
- package/packages/@monomind/cli/dist/src/init/executor.js +63 -49
- package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
- package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +51 -34
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +65 -43
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +24 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +68 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +30 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +96 -7
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +105 -8
- package/packages/@monomind/cli/dist/src/orgrt/live.html +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.js +72 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
- package/packages/@monomind/cli/dist/src/output.js +12 -5
- package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
- package/packages/@monomind/cli/dist/src/parser.js +130 -5
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
- package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +40 -40
- package/packages/@monomind/cli/dist/src/ui/orgs.html +110 -11
- package/packages/@monomind/cli/dist/src/ui/server.mjs +304 -133
- package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
- package/packages/@monomind/cli/package.json +6 -2
|
@@ -11,6 +11,22 @@ import { dirname } from 'path';
|
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
13
13
|
const MAX_EXEC_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
14
|
+
/**
|
|
15
|
+
* Probe whether an optionalDependency actually resolved in this install
|
|
16
|
+
* (npm silently skips optionalDependencies it can't satisfy — see
|
|
17
|
+
* docs/AUDIT-BACKLOG.md P1-1/P1-23). Used to caveat generated docs instead
|
|
18
|
+
* of presenting these features as unconditionally working.
|
|
19
|
+
*/
|
|
20
|
+
function _isOptionalPackageResolvable(pkg) {
|
|
21
|
+
try {
|
|
22
|
+
const req = createRequire(import.meta.url);
|
|
23
|
+
req.resolve(pkg);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
14
30
|
/**
|
|
15
31
|
* Atomic write helper — writes to a sibling .tmp file then renames into place.
|
|
16
32
|
* SIGINT or crash during a partial write would otherwise corrupt user-critical
|
|
@@ -1180,13 +1196,15 @@ function findSourceHelpersDir(sourceBaseDir) {
|
|
|
1180
1196
|
possiblePaths.push(helpersPath);
|
|
1181
1197
|
currentDir = parentDir;
|
|
1182
1198
|
}
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1199
|
+
// NOTE: deliberately no cwd-ancestor-search fallback here (removed — see
|
|
1200
|
+
// docs/AUDIT-BACKLOG.md P3-25). Searching process.cwd() and its parents for
|
|
1201
|
+
// ".claude/helpers" could pick up an unrelated project's own helper scripts
|
|
1202
|
+
// (stale, customized, or untrusted) when `monomind init` is run from a
|
|
1203
|
+
// nested subdirectory of some other checkout. Helper source resolution is
|
|
1204
|
+
// restricted to the package's own bundled location(s) above; if none of
|
|
1205
|
+
// those are found, callers should treat it as a corrupt install rather than
|
|
1206
|
+
// silently falling back to scanning ancestor directories for someone else's
|
|
1207
|
+
// files.
|
|
1190
1208
|
// Return first path that exists AND contains ALL sentinel files
|
|
1191
1209
|
for (const p of possiblePaths) {
|
|
1192
1210
|
if (fs.existsSync(p) && SENTINEL_FILES.every(f => fs.existsSync(path.join(p, f)))) {
|
|
@@ -1208,6 +1226,9 @@ async function writeHelpers(targetDir, options, result) {
|
|
|
1208
1226
|
const copyRecursive = (srcDir, destDir, relBase) => {
|
|
1209
1227
|
fs.mkdirSync(destDir, { recursive: true });
|
|
1210
1228
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
1229
|
+
// Skip exFAT/macOS AppleDouble junk files (e.g. "._foo.cjs").
|
|
1230
|
+
if (entry.name.startsWith('._'))
|
|
1231
|
+
continue;
|
|
1211
1232
|
const srcPath = path.join(srcDir, entry.name);
|
|
1212
1233
|
const destPath = path.join(destDir, entry.name);
|
|
1213
1234
|
const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
@@ -1549,6 +1570,7 @@ async function writeCapabilitiesDoc(targetDir, options, result) {
|
|
|
1549
1570
|
result.skipped.push('.monomind/CAPABILITIES.md');
|
|
1550
1571
|
return;
|
|
1551
1572
|
}
|
|
1573
|
+
const hooksAvailable = _isOptionalPackageResolvable('@monomind/hooks');
|
|
1552
1574
|
const capabilities = `# Monomind - Complete Capabilities Reference
|
|
1553
1575
|
> Generated: ${new Date().toISOString()}
|
|
1554
1576
|
> Full documentation: https://github.com/monoes/monomind
|
|
@@ -1558,8 +1580,8 @@ async function writeCapabilitiesDoc(targetDir, options, result) {
|
|
|
1558
1580
|
1. [Overview](#overview)
|
|
1559
1581
|
2. [Swarm Orchestration](#swarm-orchestration)
|
|
1560
1582
|
3. [Available Agents (60+)](#available-agents)
|
|
1561
|
-
4. [CLI Commands
|
|
1562
|
-
5. [Hooks System (
|
|
1583
|
+
4. [CLI Commands](#cli-commands)
|
|
1584
|
+
5. [Hooks System (29 Hook Subcommands + 15 Background Workers)](#hooks-system)
|
|
1563
1585
|
6. [Memory & Intelligence](#memory--intelligence)
|
|
1564
1586
|
7. [Hive-Mind Consensus](#hive-mind-consensus)
|
|
1565
1587
|
8. [Performance Targets](#performance-targets)
|
|
@@ -1661,33 +1683,31 @@ npx monomind@latest swarm monitor
|
|
|
1661
1683
|
|
|
1662
1684
|
## CLI Commands
|
|
1663
1685
|
|
|
1664
|
-
### Core Commands
|
|
1686
|
+
### Core Commands
|
|
1665
1687
|
| Command | Subcommands | Description |
|
|
1666
1688
|
|---------|-------------|-------------|
|
|
1667
|
-
| \`init\` |
|
|
1668
|
-
| \`agent\` |
|
|
1689
|
+
| \`init\` | 5 | Project initialization |
|
|
1690
|
+
| \`agent\` | 7 | Agent lifecycle management |
|
|
1669
1691
|
| \`swarm\` | 6 | Multi-agent coordination |
|
|
1670
|
-
| \`memory\` |
|
|
1692
|
+
| \`memory\` | 12 | LanceDB with ANN vector search |
|
|
1671
1693
|
| \`mcp\` | 9 | MCP server management |
|
|
1672
|
-
| \`task\` |
|
|
1673
|
-
| \`session\` |
|
|
1694
|
+
| \`task\` | 5 | Task assignment |
|
|
1695
|
+
| \`session\` | 6 | Session persistence |
|
|
1674
1696
|
| \`config\` | 7 | Configuration |
|
|
1675
1697
|
| \`status\` | 3 | System monitoring |
|
|
1676
|
-
| \`
|
|
1677
|
-
|
|
1678
|
-
|
|
1698
|
+
| \`hooks\` | 29 | Self-learning hooks + 15 background workers${hooksAvailable ? '' : ' (background workers unavailable in this install)'} |
|
|
1699
|
+
|
|
1700
|
+
> Note: there is no \`hive-mind\`, \`workflow\`, \`neural\`, \`embeddings\`, \`claims\`, \`migrate\`, or \`process\` CLI command.
|
|
1701
|
+
> Hive-Mind consensus (byzantine/raft/quorum) is available exclusively via MCP tools, not the CLI.
|
|
1702
|
+
> Neural pattern learning was merged into \`hooks intelligence\`.
|
|
1679
1703
|
|
|
1680
|
-
### Advanced Commands
|
|
1704
|
+
### Advanced Commands
|
|
1681
1705
|
| Command | Subcommands | Description |
|
|
1682
1706
|
|---------|-------------|-------------|
|
|
1683
|
-
| \`neural\` | 5 | Pattern training |
|
|
1684
1707
|
| \`security\` | 6 | Security scanning |
|
|
1685
|
-
| \`performance\` |
|
|
1686
|
-
| \`providers\` |
|
|
1687
|
-
| \`
|
|
1688
|
-
| \`claims\` | 4 | Authorization |
|
|
1689
|
-
| \`migrate\` | 5 | V2→V1 migration |
|
|
1690
|
-
| \`process\` | 4 | Process management |
|
|
1708
|
+
| \`performance\` | 4 | Profiling & benchmarks |
|
|
1709
|
+
| \`providers\` | 4 | AI provider config |
|
|
1710
|
+
| \`guidance\` | 1 | Governance gate setup |
|
|
1691
1711
|
| \`doctor\` | 1 | Health diagnostics |
|
|
1692
1712
|
| \`completions\` | 4 | Shell completions |
|
|
1693
1713
|
|
|
@@ -1711,7 +1731,7 @@ npx monomind@latest doctor --fix
|
|
|
1711
1731
|
|
|
1712
1732
|
## Hooks System
|
|
1713
1733
|
|
|
1714
|
-
###
|
|
1734
|
+
### 29 Available Hook Subcommands${hooksAvailable ? '' : ' — background workers unavailable in this install (@monomind/hooks did not resolve)'}
|
|
1715
1735
|
|
|
1716
1736
|
#### Core Hooks (6)
|
|
1717
1737
|
| Hook | Description |
|
|
@@ -1818,6 +1838,11 @@ npx monomind@latest memory init --force
|
|
|
1818
1838
|
|
|
1819
1839
|
## Hive-Mind Consensus
|
|
1820
1840
|
|
|
1841
|
+
> **Experimental, MCP-only.** There is no \`hive-mind\` CLI command — this is
|
|
1842
|
+
> single-process vote counting exposed exclusively via MCP tools
|
|
1843
|
+
> (\`hive-mind-tools.ts\`), not distributed networking. Reach it through the
|
|
1844
|
+
> MCP server (\`npx monomind@latest mcp start\`) once connected to an MCP client.
|
|
1845
|
+
|
|
1821
1846
|
### Queen Types
|
|
1822
1847
|
| Type | Role |
|
|
1823
1848
|
|------|------|
|
|
@@ -1829,28 +1854,13 @@ npx monomind@latest memory init --force
|
|
|
1829
1854
|
\`researcher\`, \`coder\`, \`analyst\`, \`tester\`, \`architect\`, \`reviewer\`, \`optimizer\`, \`documenter\`
|
|
1830
1855
|
|
|
1831
1856
|
### Consensus Mechanisms
|
|
1832
|
-
| Mechanism | Fault Tolerance |
|
|
1833
|
-
|
|
1834
|
-
| \`byzantine\` | f < n/3 faulty |
|
|
1835
|
-
| \`raft\` | f < n/2 failed |
|
|
1836
|
-
| \`
|
|
1837
|
-
| \`
|
|
1838
|
-
| \`
|
|
1839
|
-
|
|
1840
|
-
### Hive-Mind Commands
|
|
1841
|
-
\`\`\`bash
|
|
1842
|
-
# Initialize
|
|
1843
|
-
npx monomind@latest hive-mind init --queen-type strategic
|
|
1844
|
-
|
|
1845
|
-
# Status
|
|
1846
|
-
npx monomind@latest hive-mind status
|
|
1847
|
-
|
|
1848
|
-
# Spawn workers
|
|
1849
|
-
npx monomind@latest hive-mind spawn --count 5 --type worker
|
|
1850
|
-
|
|
1851
|
-
# Consensus
|
|
1852
|
-
npx monomind@latest hive-mind consensus --propose "task"
|
|
1853
|
-
\`\`\`
|
|
1857
|
+
| Mechanism | Fault Tolerance | Status |
|
|
1858
|
+
|-----------|-----------------|--------|
|
|
1859
|
+
| \`byzantine\` / \`bft\` | f < n/3 faulty | Implemented (vote counting) |
|
|
1860
|
+
| \`raft\` | f < n/2 failed | Implemented (vote counting) |
|
|
1861
|
+
| \`quorum\` | Configurable | Implemented |
|
|
1862
|
+
| \`gossip\` | Eventually consistent | Planned — not implemented, rejected by \`hive_mind_init\` |
|
|
1863
|
+
| \`crdt\` | Conflict-free | Planned — not implemented, rejected by \`hive_mind_init\` |
|
|
1854
1864
|
|
|
1855
1865
|
---
|
|
1856
1866
|
|
|
@@ -2074,6 +2084,10 @@ function copyDirRecursive(src, dest) {
|
|
|
2074
2084
|
fs.mkdirSync(dest, { recursive: true });
|
|
2075
2085
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
2076
2086
|
for (const entry of entries) {
|
|
2087
|
+
// Skip exFAT/macOS AppleDouble junk files (e.g. "._foo.js") so they don't
|
|
2088
|
+
// get perpetuated into every newly-initialized project.
|
|
2089
|
+
if (entry.name.startsWith('._'))
|
|
2090
|
+
continue;
|
|
2077
2091
|
const srcPath = path.join(src, entry.name);
|
|
2078
2092
|
const destPath = path.join(dest, entry.name);
|
|
2079
2093
|
if (entry.isDirectory()) {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* containing hardcoded business logic. All business logic lives in MCP tool handlers.
|
|
9
9
|
*/
|
|
10
10
|
import type { MCPTool } from './mcp-tools/types.js';
|
|
11
|
+
export declare function isToolDisabled(toolName: string, cwd?: string): boolean;
|
|
11
12
|
/**
|
|
12
13
|
* MCP Client Error
|
|
13
14
|
*/
|
|
@@ -54,10 +55,14 @@ export declare function getToolMetadata(toolName: string): Omit<MCPTool, 'handle
|
|
|
54
55
|
* @param category - Optional category filter
|
|
55
56
|
* @returns Array of tool metadata
|
|
56
57
|
*/
|
|
57
|
-
export declare function listMCPTools(category?: string): Array<Omit<MCPTool, 'handler'
|
|
58
|
+
export declare function listMCPTools(category?: string): Array<Omit<MCPTool, 'handler'> & {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
}>;
|
|
58
61
|
/**
|
|
59
|
-
* Return all registered tools including their handler functions
|
|
60
|
-
* Used by startHttpServer() to register tools
|
|
62
|
+
* Return all registered tools including their handler functions, excluding
|
|
63
|
+
* any disabled via `mcp toggle`. Used by startHttpServer() to register tools
|
|
64
|
+
* with the HTTP/WS MCP server, so a disabled tool is never exposed to
|
|
65
|
+
* external MCP clients after the next server start.
|
|
61
66
|
*/
|
|
62
67
|
export declare function getAllMCPTools(): MCPTool[];
|
|
63
68
|
/**
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* This provides a simple interface for CLI commands to call MCP tools without
|
|
8
8
|
* containing hardcoded business logic. All business logic lives in MCP tool handlers.
|
|
9
9
|
*/
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
10
12
|
// Import MCP tool handlers from local package
|
|
11
13
|
import { agentTools } from './mcp-tools/agent-tools.js';
|
|
12
14
|
import { swarmTools } from './mcp-tools/swarm-tools.js';
|
|
@@ -95,6 +97,30 @@ registerTools([
|
|
|
95
97
|
// Second Brain knowledge tools
|
|
96
98
|
...knowledgeTools,
|
|
97
99
|
]);
|
|
100
|
+
/**
|
|
101
|
+
* Disabled-tools registry (`mcp toggle`)
|
|
102
|
+
*
|
|
103
|
+
* Read fresh on every check (the file is tiny and toggles are infrequent) so a
|
|
104
|
+
* `mcp toggle` run in another process/session takes effect without restarting
|
|
105
|
+
* this one. Filters both direct invocation (callMCPTool) and MCP server
|
|
106
|
+
* registration (getAllMCPTools) so a disabled tool is actually excluded, not
|
|
107
|
+
* just cosmetically hidden.
|
|
108
|
+
*/
|
|
109
|
+
function loadDisabledTools(cwd = process.cwd()) {
|
|
110
|
+
const stateFile = join(cwd, '.monomind', 'mcp-disabled-tools.json');
|
|
111
|
+
if (!existsSync(stateFile))
|
|
112
|
+
return new Set();
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(readFileSync(stateFile, 'utf8'));
|
|
115
|
+
return Array.isArray(parsed) ? new Set(parsed) : new Set();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return new Set();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function isToolDisabled(toolName, cwd) {
|
|
122
|
+
return loadDisabledTools(cwd).has(toolName);
|
|
123
|
+
}
|
|
98
124
|
/**
|
|
99
125
|
* MCP Client Error
|
|
100
126
|
*/
|
|
@@ -138,6 +164,9 @@ export async function callMCPTool(toolName, input = {}, context) {
|
|
|
138
164
|
if (!tool) {
|
|
139
165
|
throw new MCPClientError(`MCP tool not found: ${toolName}`, toolName);
|
|
140
166
|
}
|
|
167
|
+
if (isToolDisabled(toolName)) {
|
|
168
|
+
throw new MCPClientError(`MCP tool '${toolName}' is disabled. Re-enable with: mcp toggle --enable ${toolName}`, toolName);
|
|
169
|
+
}
|
|
141
170
|
try {
|
|
142
171
|
// Call the tool handler
|
|
143
172
|
const result = await tool.handler(input, context);
|
|
@@ -179,6 +208,7 @@ export function getToolMetadata(toolName) {
|
|
|
179
208
|
*/
|
|
180
209
|
export function listMCPTools(category) {
|
|
181
210
|
const tools = Array.from(TOOL_REGISTRY.values());
|
|
211
|
+
const disabled = loadDisabledTools();
|
|
182
212
|
const filtered = category
|
|
183
213
|
? tools.filter(t => t.category === category)
|
|
184
214
|
: tools;
|
|
@@ -191,14 +221,18 @@ export function listMCPTools(category) {
|
|
|
191
221
|
version: tool.version,
|
|
192
222
|
cacheable: tool.cacheable,
|
|
193
223
|
cacheTTL: tool.cacheTTL,
|
|
224
|
+
enabled: !disabled.has(tool.name),
|
|
194
225
|
}));
|
|
195
226
|
}
|
|
196
227
|
/**
|
|
197
|
-
* Return all registered tools including their handler functions
|
|
198
|
-
* Used by startHttpServer() to register tools
|
|
228
|
+
* Return all registered tools including their handler functions, excluding
|
|
229
|
+
* any disabled via `mcp toggle`. Used by startHttpServer() to register tools
|
|
230
|
+
* with the HTTP/WS MCP server, so a disabled tool is never exposed to
|
|
231
|
+
* external MCP clients after the next server start.
|
|
199
232
|
*/
|
|
200
233
|
export function getAllMCPTools() {
|
|
201
|
-
|
|
234
|
+
const disabled = loadDisabledTools();
|
|
235
|
+
return Array.from(TOOL_REGISTRY.values()).filter(t => !disabled.has(t.name));
|
|
202
236
|
}
|
|
203
237
|
/**
|
|
204
238
|
* Check if an MCP tool exists
|
|
@@ -190,9 +190,15 @@ export class MCPServerManager extends EventEmitter {
|
|
|
190
190
|
if (!pid) {
|
|
191
191
|
// No PID file found. Detect if we are running in stdio mode
|
|
192
192
|
// (e.g., launched by Claude Code via `claude mcp add`).
|
|
193
|
-
|
|
193
|
+
//
|
|
194
|
+
// SECURITY/CORRECTNESS: this must NOT fall back to a TTY heuristic
|
|
195
|
+
// (`!process.stdin.isTTY`) — that is true for ANY non-interactive
|
|
196
|
+
// invocation (piped, in CI, in a script), so it falsely reported
|
|
197
|
+
// "running" even when no server was actually started. Only trust
|
|
198
|
+
// real state: the explicit stdio-transport env var, or the
|
|
199
|
+
// `_stdioServerStarted` flag that tracks an actually-started server.
|
|
194
200
|
const envTransport = process.env.MONOMIND_MCP_TRANSPORT;
|
|
195
|
-
if (
|
|
201
|
+
if (envTransport === 'stdio' || this._stdioServerStarted) {
|
|
196
202
|
return {
|
|
197
203
|
running: true,
|
|
198
204
|
pid: process.pid,
|
|
@@ -5,5 +5,25 @@
|
|
|
5
5
|
* Includes model routing integration for intelligent model selection.
|
|
6
6
|
*/
|
|
7
7
|
import { type MCPTool } from './types.js';
|
|
8
|
+
type ClaudeModel = 'haiku' | 'sonnet' | 'opus' | 'inherit';
|
|
9
|
+
interface AgentRecord {
|
|
10
|
+
agentId: string;
|
|
11
|
+
agentType: string;
|
|
12
|
+
status: 'idle' | 'busy' | 'terminated';
|
|
13
|
+
health: number;
|
|
14
|
+
taskCount: number;
|
|
15
|
+
config: Record<string, unknown>;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
domain?: string;
|
|
18
|
+
model?: ClaudeModel;
|
|
19
|
+
modelRoutedBy?: 'explicit' | 'router' | 'agent-booster' | 'default';
|
|
20
|
+
lastResult?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
interface AgentStore {
|
|
23
|
+
agents: Record<string, AgentRecord>;
|
|
24
|
+
version: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function loadAgentStore(): AgentStore;
|
|
8
27
|
export declare const agentTools: MCPTool[];
|
|
28
|
+
export {};
|
|
9
29
|
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, statSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { randomBytes } from 'node:crypto';
|
|
10
|
-
import {
|
|
11
|
-
// Storage paths
|
|
12
|
-
|
|
10
|
+
import { getMonomindDataRoot, migrateLegacyStoreFile } from './types.js';
|
|
11
|
+
// Storage paths — relative to the git-safe data root (see getMonomindDataRoot()).
|
|
12
|
+
// Canonical location matches task-tools.ts/session-tools.ts/hive-mind-tools.ts/
|
|
13
|
+
// swarm-tools.ts so the agent store is a single physical file across all tools.
|
|
13
14
|
const AGENT_DIR = 'agents';
|
|
14
15
|
const AGENT_FILE = 'store.json';
|
|
15
16
|
function getAgentDir() {
|
|
16
|
-
return join(
|
|
17
|
+
return join(getMonomindDataRoot(), AGENT_DIR);
|
|
17
18
|
}
|
|
18
19
|
function getAgentPath() {
|
|
19
20
|
return join(getAgentDir(), AGENT_FILE);
|
|
@@ -25,9 +26,13 @@ function ensureAgentDir() {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
const MAX_AGENT_STORE_BYTES = 50 * 1024 * 1024;
|
|
28
|
-
|
|
29
|
+
// Exported so other tool modules reading the same physical store file
|
|
30
|
+
// (e.g. hive-mind-tools.ts) can reuse this hardened loader instead of
|
|
31
|
+
// maintaining their own weaker copy (missing the size cap / __proto__ guard).
|
|
32
|
+
export function loadAgentStore() {
|
|
29
33
|
try {
|
|
30
34
|
const path = getAgentPath();
|
|
35
|
+
migrateLegacyStoreFile(path, join(AGENT_DIR, AGENT_FILE));
|
|
31
36
|
if (existsSync(path)) {
|
|
32
37
|
if (statSync(path).size > MAX_AGENT_STORE_BYTES)
|
|
33
38
|
return { agents: {}, version: '3.0.0' };
|
|
@@ -60,6 +60,18 @@ function runSafe(cmd, args, cwd) {
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
/** Run a `gh` command and preserve the real failure reason instead of collapsing to null. */
|
|
64
|
+
function runSafeResult(cmd, args, cwd) {
|
|
65
|
+
try {
|
|
66
|
+
const stdout = execFileSync(cmd, args, { encoding: 'utf-8', timeout: 15000, cwd: cwd || getProjectCwd(), stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
67
|
+
return { ok: true, stdout };
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const e = err;
|
|
71
|
+
const stderr = e.stderr ? String(e.stderr).trim() : '';
|
|
72
|
+
return { ok: false, error: stderr || e.message || 'gh command failed' };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
63
75
|
/** Check if gh CLI is available */
|
|
64
76
|
function hasGhCli() {
|
|
65
77
|
return run('gh --version') !== null;
|
|
@@ -216,7 +228,11 @@ export const githubTools = [
|
|
|
216
228
|
}
|
|
217
229
|
// Fallback: local store
|
|
218
230
|
const prId = `pr-${Date.now()}`;
|
|
219
|
-
|
|
231
|
+
// Locally-assigned PR number, independent of the timestamp-based key —
|
|
232
|
+
// monotonic within this store so lookups by number are unambiguous.
|
|
233
|
+
const existingNumbers = Object.values(store.prs).map(p => p.number || 0);
|
|
234
|
+
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
|
|
235
|
+
const pr = { id: prId, number: nextNumber, title, status: 'open', branch: headBranch, baseBranch, createdAt: new Date().toISOString() };
|
|
220
236
|
store.prs[prId] = pr;
|
|
221
237
|
saveGitHubStore(store);
|
|
222
238
|
return { success: true, source: 'local-store', action: 'created', pullRequest: pr };
|
|
@@ -241,13 +257,17 @@ export const githubTools = [
|
|
|
241
257
|
if (!prNumber)
|
|
242
258
|
return { success: false, error: 'prNumber is required and must be a positive integer for merge.' };
|
|
243
259
|
if (gh) {
|
|
244
|
-
|
|
245
|
-
|
|
260
|
+
// gh CLI is actually installed — trust its real exit status. A genuine
|
|
261
|
+
// failure (branch protection, conflicts, auth) must be reported as a
|
|
262
|
+
// failure, not silently swallowed into the local-store simulation.
|
|
263
|
+
const result = runSafeResult('gh', ['pr', 'merge', String(prNumber), '--merge']);
|
|
264
|
+
if (result.ok) {
|
|
246
265
|
return { success: true, _real: true, action: 'merged', prNumber, mergedAt: new Date().toISOString() };
|
|
247
266
|
}
|
|
267
|
+
return { success: false, error: result.error };
|
|
248
268
|
}
|
|
249
|
-
// Fallback: local store
|
|
250
|
-
const prKey = Object.keys(store.prs).find(k => k.
|
|
269
|
+
// Fallback: local store (only reached when gh CLI is not installed at all)
|
|
270
|
+
const prKey = Object.keys(store.prs).find(k => store.prs[k].number === prNumber);
|
|
251
271
|
if (prKey && store.prs[prKey]) {
|
|
252
272
|
store.prs[prKey].status = 'merged';
|
|
253
273
|
saveGitHubStore(store);
|
|
@@ -259,12 +279,14 @@ export const githubTools = [
|
|
|
259
279
|
if (!prNumber)
|
|
260
280
|
return { success: false, error: 'prNumber is required and must be a positive integer for close.' };
|
|
261
281
|
if (gh) {
|
|
262
|
-
const result =
|
|
263
|
-
if (result
|
|
282
|
+
const result = runSafeResult('gh', ['pr', 'close', String(prNumber)]);
|
|
283
|
+
if (result.ok) {
|
|
264
284
|
return { success: true, _real: true, action: 'closed', prNumber, closedAt: new Date().toISOString() };
|
|
265
285
|
}
|
|
286
|
+
return { success: false, error: result.error };
|
|
266
287
|
}
|
|
267
|
-
|
|
288
|
+
// Fallback: local store (only reached when gh CLI is not installed at all)
|
|
289
|
+
const prKey = Object.keys(store.prs).find(k => store.prs[k].number === prNumber);
|
|
268
290
|
if (prKey && store.prs[prKey]) {
|
|
269
291
|
store.prs[prKey].status = 'closed';
|
|
270
292
|
saveGitHubStore(store);
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
* All graphify_* tools are deprecated. They proxy to monograph_* tools.
|
|
5
5
|
* Will be removed in next major release.
|
|
6
6
|
*/
|
|
7
|
-
import { allMonographTools } from './monograph-tools.js';
|
|
7
|
+
import { allMonographTools, monographTools } from './monograph-tools.js';
|
|
8
|
+
// Advanced monograph tools are gated behind MONOGRAPH_MCP_ADVANCED=1 (see
|
|
9
|
+
// monograph-tools.ts). Shims that target an advanced-tier tool must be gated
|
|
10
|
+
// the same way, otherwise they re-expose advanced tools under a different
|
|
11
|
+
// name regardless of the flag. `monographTools` is the already-gated export
|
|
12
|
+
// (core-only unless MONOGRAPH_MCP_ADVANCED=1); a target is "advanced" if it's
|
|
13
|
+
// present in allMonographTools but absent from monographTools.
|
|
14
|
+
const exposedMonographNames = new Set(monographTools.map(t => t.name));
|
|
8
15
|
function findMonographTool(name) {
|
|
9
16
|
const tool = allMonographTools.find(t => t.name === name);
|
|
10
17
|
if (!tool)
|
|
@@ -12,6 +19,8 @@ function findMonographTool(name) {
|
|
|
12
19
|
return tool;
|
|
13
20
|
}
|
|
14
21
|
function shimTool(graphifyName, monographName, paramMap) {
|
|
22
|
+
if (!exposedMonographNames.has(monographName))
|
|
23
|
+
return null;
|
|
15
24
|
const target = findMonographTool(monographName);
|
|
16
25
|
return {
|
|
17
26
|
name: graphifyName,
|
|
@@ -44,6 +53,6 @@ export const graphifyTools = [
|
|
|
44
53
|
shimTool('graphify_report', 'monograph_report'),
|
|
45
54
|
// Bug fix: graphify_health previously referenced `files.length` (ReferenceError) — now delegates cleanly
|
|
46
55
|
shimTool('graphify_health', 'monograph_health'),
|
|
47
|
-
];
|
|
56
|
+
].filter((t) => t !== null);
|
|
48
57
|
export default graphifyTools;
|
|
49
58
|
//# sourceMappingURL=graphify-tools.js.map
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
14
14
|
import { randomBytes } from 'node:crypto';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
|
-
import { getProjectCwd } from './types.js';
|
|
16
|
+
import { getProjectCwd, getMonomindDataRoot, migrateLegacyStoreFile } from './types.js';
|
|
17
17
|
import { weightedTally } from '../consensus/tally.js';
|
|
18
|
+
// Reuse agent-tools.ts's hardened store loader (50MB size cap + __proto__
|
|
19
|
+
// rejection) instead of maintaining a second, weaker copy that reads the
|
|
20
|
+
// same physical file — see loadAgentStore export note there.
|
|
21
|
+
import { loadAgentStore } from './agent-tools.js';
|
|
18
22
|
// Storage paths
|
|
19
23
|
const STORAGE_DIR = '.monomind';
|
|
20
24
|
const HIVE_DIR = 'hive-mind';
|
|
@@ -92,7 +96,7 @@ function tryResolveProposal(proposal, totalNodes) {
|
|
|
92
96
|
return null;
|
|
93
97
|
}
|
|
94
98
|
function getHiveDir() {
|
|
95
|
-
return join(
|
|
99
|
+
return join(getMonomindDataRoot(), HIVE_DIR);
|
|
96
100
|
}
|
|
97
101
|
function getHivePath() {
|
|
98
102
|
return join(getHiveDir(), HIVE_FILE);
|
|
@@ -107,6 +111,7 @@ const MAX_HIVE_STATE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
|
107
111
|
function loadHiveState() {
|
|
108
112
|
try {
|
|
109
113
|
const path = getHivePath();
|
|
114
|
+
migrateLegacyStoreFile(path, join(HIVE_DIR, HIVE_FILE));
|
|
110
115
|
if (existsSync(path) && statSync(path).size <= MAX_HIVE_STATE_BYTES) {
|
|
111
116
|
const data = readFileSync(path, 'utf-8');
|
|
112
117
|
return JSON.parse(data);
|
|
@@ -176,21 +181,10 @@ function getOrCreateSessionSecret() {
|
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
// Import agent store helpers for spawn functionality
|
|
179
|
-
import { existsSync as agentStoreExists,
|
|
180
|
-
// Canonical agent store path matches agent-tools.ts: .monomind/agents/store.json
|
|
181
|
-
function loadAgentStore() {
|
|
182
|
-
const storePath = join(getProjectCwd(), '.monomind', 'agents', 'store.json');
|
|
183
|
-
try {
|
|
184
|
-
if (agentStoreExists(storePath)) {
|
|
185
|
-
return JSON.parse(readAgentStore(storePath, 'utf-8'));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
catch { /* ignore */ }
|
|
189
|
-
return { agents: {} };
|
|
190
|
-
}
|
|
184
|
+
import { existsSync as agentStoreExists, writeFileSync as writeAgentStore, mkdirSync as mkdirAgentStore } from 'node:fs';
|
|
191
185
|
const HIVE_RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
192
186
|
function saveAgentStore(store) {
|
|
193
|
-
const storeDir = join(
|
|
187
|
+
const storeDir = join(getMonomindDataRoot(), 'agents');
|
|
194
188
|
if (!agentStoreExists(storeDir)) {
|
|
195
189
|
mkdirAgentStore(storeDir, { recursive: true });
|
|
196
190
|
}
|
|
@@ -284,6 +278,10 @@ export const hiveMindTools = [
|
|
|
284
278
|
properties: {
|
|
285
279
|
topology: { type: 'string', enum: ['mesh', 'hierarchical', 'ring', 'star'], description: 'Network topology' },
|
|
286
280
|
queenId: { type: 'string', description: 'Initial queen agent ID' },
|
|
281
|
+
consensus: { type: 'string', enum: ['byzantine', 'bft', 'raft', 'quorum'], description: 'Consensus strategy to govern hive-mind_consensus propose/vote (gossip/crdt are planned but not implemented and fall back to byzantine). Default: byzantine.' },
|
|
282
|
+
maxAgents: { type: 'number', description: 'Maximum number of agents in the hive. Default: 15.' },
|
|
283
|
+
persist: { type: 'boolean', description: 'Whether to persist hive state to disk. Default: true.' },
|
|
284
|
+
memoryBackend: { type: 'string', description: 'Shared memory backend for the hive. Default: hybrid.' },
|
|
287
285
|
},
|
|
288
286
|
},
|
|
289
287
|
handler: async (input) => {
|
|
@@ -294,32 +292,34 @@ export const hiveMindTools = [
|
|
|
294
292
|
const queenId = typeof rawQueenId === 'string' && rawQueenId.length > MAX_QUEEN_ID_LEN
|
|
295
293
|
? rawQueenId.slice(0, MAX_QUEEN_ID_LEN) : rawQueenId;
|
|
296
294
|
const now = new Date().toISOString();
|
|
295
|
+
// Validate consensus strategy: gossip and crdt are planned but not implemented
|
|
296
|
+
const SUPPORTED_CONSENSUS = ['byzantine', 'bft', 'raft', 'quorum'];
|
|
297
|
+
const PLANNED_CONSENSUS = ['gossip', 'crdt'];
|
|
298
|
+
const requestedConsensus = input.consensus || 'byzantine';
|
|
299
|
+
const consensusWarning = PLANNED_CONSENSUS.includes(requestedConsensus)
|
|
300
|
+
? `Strategy "${requestedConsensus}" is planned but not yet implemented; defaulting to "byzantine".`
|
|
301
|
+
: undefined;
|
|
302
|
+
const effectiveConsensus = PLANNED_CONSENSUS.includes(requestedConsensus)
|
|
303
|
+
? 'byzantine'
|
|
304
|
+
: (SUPPORTED_CONSENSUS.includes(requestedConsensus) ? requestedConsensus : 'byzantine');
|
|
297
305
|
const state = {
|
|
298
306
|
initialized: true,
|
|
299
307
|
hiveId,
|
|
300
308
|
topology: input.topology || 'mesh',
|
|
301
309
|
queen: { agentId: queenId, electedAt: now, term: 1 },
|
|
302
310
|
workers: [],
|
|
311
|
+
// Persist the chosen strategy on the hive state so hive-mind_consensus
|
|
312
|
+
// propose/vote default to it instead of silently using 'raft'.
|
|
313
|
+
// 'byzantine' is the CLI-facing name; the internal ConsensusStrategy type uses 'bft'.
|
|
314
|
+
consensusStrategy: (effectiveConsensus === 'byzantine' ? 'bft' : effectiveConsensus),
|
|
303
315
|
consensus: { pending: [], history: [] },
|
|
304
316
|
sharedMemory: {},
|
|
305
317
|
createdAt: now,
|
|
306
318
|
updatedAt: now,
|
|
307
319
|
};
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
const PLANNED_CONSENSUS = ['gossip', 'crdt'];
|
|
312
|
-
const requestedConsensus = input.consensus || 'byzantine';
|
|
313
|
-
const consensusWarning = PLANNED_CONSENSUS.includes(requestedConsensus)
|
|
314
|
-
? `Strategy "${requestedConsensus}" is planned but not yet implemented; defaulting to "byzantine".`
|
|
315
|
-
: undefined;
|
|
316
|
-
const effectiveConsensus = PLANNED_CONSENSUS.includes(requestedConsensus)
|
|
317
|
-
? 'byzantine'
|
|
318
|
-
: (SUPPORTED_CONSENSUS.includes(requestedConsensus) ? requestedConsensus : 'byzantine');
|
|
319
|
-
// Persist the chosen strategy on the hive state so hive-mind_consensus
|
|
320
|
-
// propose/vote default to it instead of silently using 'raft'.
|
|
321
|
-
// 'byzantine' is the CLI-facing name; the internal ConsensusStrategy type uses 'bft'.
|
|
322
|
-
state.consensusStrategy = (effectiveConsensus === 'byzantine' ? 'bft' : effectiveConsensus);
|
|
320
|
+
// Single write — a prior version of this handler saved state twice
|
|
321
|
+
// (once before setting consensusStrategy, once after), which was
|
|
322
|
+
// redundant.
|
|
323
323
|
saveHiveState(state);
|
|
324
324
|
return {
|
|
325
325
|
success: true,
|
|
@@ -357,7 +357,8 @@ export const hiveMindTools = [
|
|
|
357
357
|
// Load agent store once for all workers
|
|
358
358
|
const agentStore = loadAgentStore();
|
|
359
359
|
// Compute real task metrics from task store
|
|
360
|
-
const taskStorePath = join(
|
|
360
|
+
const taskStorePath = join(getMonomindDataRoot(), 'tasks', 'store.json');
|
|
361
|
+
migrateLegacyStoreFile(taskStorePath, join('tasks', 'store.json'));
|
|
361
362
|
let pendingTaskCount = 0;
|
|
362
363
|
let activeTaskCount = 0;
|
|
363
364
|
let completedTaskCount = 0;
|
|
@@ -382,7 +383,14 @@ export const hiveMindTools = [
|
|
|
382
383
|
hiveId: state.hiveId ?? `hive-${state.createdAt ? new Date(state.createdAt).getTime() : Date.now()}`,
|
|
383
384
|
status: state.initialized ? 'active' : 'offline',
|
|
384
385
|
topology: state.topology,
|
|
385
|
-
|
|
386
|
+
// Reflect the strategy actually chosen at hive-mind_init (persisted
|
|
387
|
+
// as state.consensusStrategy so it "actually governs voting" — see
|
|
388
|
+
// the field's doc comment) instead of hardcoding a default that
|
|
389
|
+
// ignored what was configured. 'bft' is the internal name for the
|
|
390
|
+
// CLI-facing 'byzantine' strategy.
|
|
391
|
+
consensus: state.consensusStrategy
|
|
392
|
+
? (state.consensusStrategy === 'bft' ? 'byzantine' : state.consensusStrategy)
|
|
393
|
+
: 'byzantine',
|
|
386
394
|
queen: state.queen ? {
|
|
387
395
|
id: state.queen.agentId,
|
|
388
396
|
agentId: state.queen.agentId,
|
|
@@ -729,9 +737,18 @@ export const hiveMindTools = [
|
|
|
729
737
|
confidence: 1.0, // uniform until confidence-probe is wired
|
|
730
738
|
}));
|
|
731
739
|
const cpwbftTally = weightedTally(weightedVotes);
|
|
732
|
-
// O-Information gate: defer resolution if we haven't seen enough divergent rounds
|
|
740
|
+
// O-Information gate: defer resolution if we haven't seen enough divergent rounds.
|
|
741
|
+
// BUT: if every expected voter has already cast a vote (electorate
|
|
742
|
+
// exhausted), no more votes can ever arrive to produce a divergent
|
|
743
|
+
// round — a unanimous first-round vote would otherwise deadlock the
|
|
744
|
+
// proposal permanently since divergenceRoundsSeen only increments on
|
|
745
|
+
// disagreement. Treat electorate exhaustion as a terminal condition
|
|
746
|
+
// that opens the gate regardless of divergence.
|
|
747
|
+
const totalVotesCast = Object.keys(proposal.votes).length;
|
|
748
|
+
const electorateExhausted = totalNodes > 0 && totalVotesCast >= totalNodes;
|
|
733
749
|
const divergenceGateOpen = !proposal.minDivergenceRounds
|
|
734
|
-
|| (proposal.divergenceRoundsSeen ?? 0) >= proposal.minDivergenceRounds
|
|
750
|
+
|| (proposal.divergenceRoundsSeen ?? 0) >= proposal.minDivergenceRounds
|
|
751
|
+
|| electorateExhausted;
|
|
735
752
|
// Try to resolve
|
|
736
753
|
const resolution = divergenceGateOpen ? tryResolveProposal(proposal, totalNodes) : null;
|
|
737
754
|
let resolved = false;
|