ruvnet-brain 3.9.134-dev → 4.0.1
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/.claude-plugin/marketplace.json +13 -0
- package/README.md +2 -2
- package/bin/install.mjs +284 -33
- package/kb/zip-extract.mjs +53 -14
- package/package.json +7 -1
- package/plugin/.claude-plugin/marketplace.json +13 -0
- package/plugin/.claude-plugin/plugin.json +23 -0
- package/plugin/.codex-plugin/plugin.json +21 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/brain-console.md +16 -0
- package/plugin/commands/configure.md +32 -0
- package/plugin/commands/rvbc.md +78 -0
- package/plugin/commands/rvcb.md +16 -0
- package/plugin/commands/whats-new.md +57 -0
- package/plugin/hooks/codex-hooks.json +160 -0
- package/plugin/hooks/hook-contracts.json +77 -0
- package/plugin/hooks/hooks.json +203 -0
- package/plugin/mcp/server.mjs +35 -6
- package/plugin/scripts/anticipate.sh +534 -0
- package/plugin/scripts/codex-hook-adapter.mjs +96 -0
- package/plugin/scripts/continuation-gate.mjs +267 -0
- package/plugin/scripts/design-wall.sh +137 -0
- package/plugin/scripts/detach.mjs +168 -0
- package/plugin/scripts/finalize-token-meter.mjs +25 -0
- package/plugin/scripts/gate-receipt.sh +35 -0
- package/plugin/scripts/ground-before-write.sh +199 -0
- package/plugin/scripts/ground-ruvnet.sh +507 -0
- package/plugin/scripts/grounding-stamp.sh +113 -0
- package/plugin/scripts/grounding-substance.mjs +595 -0
- package/plugin/scripts/hijack-ruvnet.sh +81 -0
- package/plugin/scripts/hook-input.mjs +558 -0
- package/plugin/scripts/hook-shim-bash.mjs +55 -0
- package/plugin/scripts/hook-shim.mjs +303 -0
- package/plugin/scripts/host-update.mjs +58 -0
- package/plugin/scripts/kling-preflight.sh +146 -0
- package/plugin/scripts/learn-capture.sh +154 -0
- package/plugin/scripts/learn-flush.mjs +138 -0
- package/plugin/scripts/lesson-hooks.sh +213 -0
- package/plugin/scripts/md-stamp.mjs +219 -0
- package/plugin/scripts/protect-brain-state.sh +84 -0
- package/plugin/scripts/route-dispatch.sh +147 -0
- package/plugin/scripts/routing-outcome-capture.mjs +89 -0
- package/plugin/scripts/session-start.sh +868 -0
- package/plugin/scripts/signal-watch.mjs +193 -0
- package/plugin/scripts/unprompted-runtime.mjs +377 -0
- package/plugin/scripts/update-apply.mjs +419 -0
- package/plugin/scripts/verify-interface.sh +53 -0
- package/plugin/scripts/version-bump-gate.sh +112 -0
- package/plugin/skills/brain-build/SKILL.md +123 -0
- package/plugin/skills/brain-console/SKILL.md +20 -0
- package/plugin/skills/brain-prompt/SKILL.md +83 -0
- package/plugin/skills/brain-score/SKILL.md +101 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +117 -0
- package/plugin/skills/ruvnet-brain/SKILL.md +234 -0
- package/plugin/skills/rvbc/SKILL.md +20 -0
- package/plugin/skills/savings/SKILL.md +46 -0
- package/plugin/skills/whats-new/SKILL.md +22 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "OUT-OF-SHIM HOOK CONTRACTS (ADR-055 §6). The per-hook contract — event, matcher, layer, codeRoot, mode, offBehavior, failureBehavior, timeout, budgets, state, dependencies, reachesStrangers, owner — is carried NATIVELY by plugin/scripts/hook-shim.mjs's dispatch table for every shim-routed registration. Everything registered outside that table has nowhere to carry it, which is how the whole non-plugin two thirds of this machine's mesh ended up with no declared answer to 'what happens when the brain is off' (ADR-055 F14). This file is the other half: a checked-in contract for every registration this REPO owns but does not route through the shim. scripts/hook-registry.mjs reads both, and tests/unit/hook-registry-lint.test.mjs fails any repo-owned registration absent from both — so a new one cannot be added silently. SCOPE, deliberately: this file declares only registrations THIS repo owns (plugin/hooks/hooks.json and this project's .claude/settings.json). It does NOT declare contracts for the machine owner's ~/.claude/settings.json or for third-party plugins — those are not ours to specify, and inventing an offBehavior for someone else's hook would be exactly the fiction the lint exists to prevent. They stay visible as documented machine-local findings in the lint's expected-red block.",
|
|
3
|
+
"_version": 1,
|
|
4
|
+
"contracts": [
|
|
5
|
+
{
|
|
6
|
+
"id": "project:version-bump-gate",
|
|
7
|
+
"layer": "project",
|
|
8
|
+
"event": "PreToolUse",
|
|
9
|
+
"matcher": "Bash",
|
|
10
|
+
"commandIncludes": "version-bump-gate.sh",
|
|
11
|
+
"mode": "blocking",
|
|
12
|
+
"offBehavior": "run",
|
|
13
|
+
"offReason": "ADR-054's discriminator, applied literally: this is an HONESTY wall, not a retrieval feature. It refuses a commit that changes behaviour without bumping the version — the signal every user's updater reads. Nothing about that becomes acceptable because the owner switched the retrieval brain off, and the gate reads git state only: no corpus, no network, no brain.",
|
|
14
|
+
"failureBehavior": "fail-open — a crash or unparseable payload permits the command and records degraded health; malfunction is never a decision (ADR-055 §1.2)",
|
|
15
|
+
"timeoutSeconds": 5,
|
|
16
|
+
"warmP95BudgetMs": 50,
|
|
17
|
+
"coldBudgetMs": 500,
|
|
18
|
+
"stdoutCapBytes": 4096,
|
|
19
|
+
"stateRead": ["git HEAD", "plugin/.claude-plugin/plugin.json"],
|
|
20
|
+
"stateWritten": [],
|
|
21
|
+
"dependencies": ["bash", "git"],
|
|
22
|
+
"reachesStrangers": false,
|
|
23
|
+
"owner": "ruvnet-brain (this repo only — ADR-053; it enforces a rule that is true here and nowhere else)",
|
|
24
|
+
"note": "Project-scoped on purpose: it lived in the GLOBAL settings until 2026-07-14, taxing every Bash call in 34+ projects with a rule true in one."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"matcherAllowlist": [
|
|
28
|
+
{
|
|
29
|
+
"layer": "plugin",
|
|
30
|
+
"event": "PreToolUse",
|
|
31
|
+
"matcher": "Write|Edit|Bash",
|
|
32
|
+
"handler": "hijack-ruvnet.sh",
|
|
33
|
+
"reason": "UNANCHORED, known (ADR-055 F4). Claude Code SEARCHES the tool name, so this also selects MultiEdit, NotebookEdit, TodoWrite and BashOutput — an advisory hook firing on four tools nobody chose. Advisory, so the blast radius is wasted latency rather than a wrong refusal.",
|
|
34
|
+
"retiredBy": "ADR-055 build item 3 (four plane dispatchers) — registration changes are SHELL changes and item 2 (battery v2) must land first, so this is recorded, not fixed out of order."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"layer": "plugin",
|
|
38
|
+
"event": "PreToolUse",
|
|
39
|
+
"matcher": "Bash",
|
|
40
|
+
"handler": "verify-interface.sh",
|
|
41
|
+
"reason": "UNANCHORED, known (ADR-055 F4/F12): also selects BashOutput. One of three blocking walls on the same event that each re-parse the same payload — the parse-once execution-guard is the real fix, not six anchor characters.",
|
|
42
|
+
"retiredBy": "ADR-055 build item 3 (execution-guard.mjs — ONE registration, ONE parse)"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"layer": "plugin",
|
|
46
|
+
"event": "PreToolUse",
|
|
47
|
+
"matcher": "Bash",
|
|
48
|
+
"handler": "design-wall.sh",
|
|
49
|
+
"reason": "UNANCHORED, known (ADR-055 F4/F12) — same pair as verify-interface above, same fix.",
|
|
50
|
+
"retiredBy": "ADR-055 build item 3 (execution-guard.mjs)"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"layer": "plugin",
|
|
54
|
+
"event": "PostToolUse",
|
|
55
|
+
"matcher": "Write|Edit|MultiEdit|Bash",
|
|
56
|
+
"handler": "learn-capture.sh",
|
|
57
|
+
"reason": "UNANCHORED, known (ADR-055 F4): NotebookEdit reaches the learning pipeline by substring accident, never by decision. Advisory.",
|
|
58
|
+
"retiredBy": "ADR-055 build item 3"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"layer": "plugin",
|
|
62
|
+
"event": "PostToolUse",
|
|
63
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
64
|
+
"handler": "md-stamp.mjs",
|
|
65
|
+
"reason": "UNANCHORED, known (ADR-055 F4): same NotebookEdit substring accident. Advisory.",
|
|
66
|
+
"retiredBy": "ADR-055 build item 3"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"layer": "project",
|
|
70
|
+
"event": "PreToolUse",
|
|
71
|
+
"matcher": "Bash",
|
|
72
|
+
"handler": "version-bump-gate.sh",
|
|
73
|
+
"reason": "UNANCHORED, known: also selects BashOutput, which carries no command to gate, so the gate reads an empty payload and permits. Wasted spawn, not a wrong refusal.",
|
|
74
|
+
"retiredBy": "ADR-055 build item 3, together with the plugin-layer Bash walls it sits beside"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "STABLE SPINE (ADR-023): every hook routes through hook-shim.mjs, which resolves the ACTIVE generation from ~/.cache/ruvnet-brain/active.json per invocation. This file is boot-frozen, so matcher, command, and dispatch-mode changes require restart classification. Advisory hooks keep `|| true` and can never block a turn. FOUR blocking hooks remain unguarded because their exit codes are their contract: route-dispatch, design-wall, unprompted-speech, and protect-state. Issue #48 demoted verify-interface to advisory-only: raw shell reconstruction may suggest the structured ruvnet_cli_help/ruvnet_cli_run boundary but can never authorize or deny execution. protect-state remains a blocking offBehavior:'run' consent guard. continuation-gate remains advisory with offBehavior:'run'.",
|
|
3
|
+
"_platform": "posix",
|
|
4
|
+
"hooks": {
|
|
5
|
+
"SessionStart": [
|
|
6
|
+
{
|
|
7
|
+
"matcher": "startup",
|
|
8
|
+
"hooks": [
|
|
9
|
+
{
|
|
10
|
+
"type": "command",
|
|
11
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" session-start || true",
|
|
12
|
+
"timeout": 5
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"matcher": "resume",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" session-start || true",
|
|
22
|
+
"timeout": 5
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"UserPromptSubmit": [
|
|
28
|
+
{
|
|
29
|
+
"matcher": ".*",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" ground-ruvnet || true",
|
|
34
|
+
"timeout": 10
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"matcher": "*",
|
|
40
|
+
"hooks": [
|
|
41
|
+
{
|
|
42
|
+
"type": "command",
|
|
43
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" unprompted-speech UserPromptSubmit",
|
|
44
|
+
"timeout": 10
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"PreToolUse": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "Write|Edit|Bash",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" hijack-ruvnet || true",
|
|
56
|
+
"timeout": 5
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"matcher": "^(Task|Agent)$",
|
|
62
|
+
"hooks": [
|
|
63
|
+
{
|
|
64
|
+
"type": "command",
|
|
65
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" route-dispatch",
|
|
66
|
+
"timeout": 5
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"matcher": "^(Write|Edit|MultiEdit|NotebookEdit)$",
|
|
72
|
+
"hooks": [
|
|
73
|
+
{
|
|
74
|
+
"type": "command",
|
|
75
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" ground-before-write || true",
|
|
76
|
+
"timeout": 5
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "command",
|
|
80
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" protect-state",
|
|
81
|
+
"timeout": 5
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"matcher": "Bash",
|
|
87
|
+
"hooks": [
|
|
88
|
+
{
|
|
89
|
+
"type": "command",
|
|
90
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" verify-interface || true",
|
|
91
|
+
"timeout": 5
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"matcher": "Bash",
|
|
97
|
+
"hooks": [
|
|
98
|
+
{
|
|
99
|
+
"type": "command",
|
|
100
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" design-wall",
|
|
101
|
+
"timeout": 5
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"matcher": "^(Write|Edit|MultiEdit)$",
|
|
107
|
+
"hooks": [
|
|
108
|
+
{
|
|
109
|
+
"type": "command",
|
|
110
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" unprompted-speech PreToolUse-write",
|
|
111
|
+
"timeout": 5
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"matcher": "^Bash$",
|
|
117
|
+
"hooks": [
|
|
118
|
+
{
|
|
119
|
+
"type": "command",
|
|
120
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" unprompted-speech PreToolUse-bash",
|
|
121
|
+
"timeout": 5
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"PostToolUse": [
|
|
127
|
+
{
|
|
128
|
+
"matcher": "^(Task|Agent)$",
|
|
129
|
+
"hooks": [
|
|
130
|
+
{
|
|
131
|
+
"type": "command",
|
|
132
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" routing-outcome || true",
|
|
133
|
+
"timeout": 5
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"matcher": "^(?:.*__)?search_ruvnet$",
|
|
139
|
+
"hooks": [
|
|
140
|
+
{
|
|
141
|
+
"type": "command",
|
|
142
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" grounding-stamp || true",
|
|
143
|
+
"timeout": 5
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"matcher": "Write|Edit|MultiEdit|Bash",
|
|
149
|
+
"hooks": [
|
|
150
|
+
{
|
|
151
|
+
"type": "command",
|
|
152
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" learn-capture || true",
|
|
153
|
+
"timeout": 5
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
159
|
+
"hooks": [
|
|
160
|
+
{
|
|
161
|
+
"type": "command",
|
|
162
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" md-stamp || true",
|
|
163
|
+
"timeout": 5
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"matcher": "^Bash$",
|
|
169
|
+
"hooks": [
|
|
170
|
+
{
|
|
171
|
+
"type": "command",
|
|
172
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" signal-watch || true",
|
|
173
|
+
"timeout": 5
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
"SessionEnd": [
|
|
179
|
+
{
|
|
180
|
+
"matcher": ".*",
|
|
181
|
+
"hooks": [
|
|
182
|
+
{
|
|
183
|
+
"type": "command",
|
|
184
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" learn-flush || true",
|
|
185
|
+
"timeout": 30
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"Stop": [
|
|
191
|
+
{
|
|
192
|
+
"matcher": "*",
|
|
193
|
+
"hooks": [
|
|
194
|
+
{
|
|
195
|
+
"type": "command",
|
|
196
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" continuation-gate || true",
|
|
197
|
+
"timeout": 10
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
package/plugin/mcp/server.mjs
CHANGED
|
@@ -37,7 +37,7 @@ import { callManagedCli, MANAGED_CLI_TOOLS } from './managed-cli-interface.mjs';
|
|
|
37
37
|
|
|
38
38
|
const BRAIN_HOME = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
|
|
39
39
|
const KB = process.env.RUVNET_BRAIN_KB || path.join(BRAIN_HOME, 'kb');
|
|
40
|
-
const CHILD_MCP = path.join(KB, 'forge-mcp-all.mjs');
|
|
40
|
+
const CHILD_MCP = process.env.RUVNET_BRAIN_CHILD_MCP || path.join(KB, 'forge-mcp-all.mjs');
|
|
41
41
|
const ACTIVE = path.join(BRAIN_HOME, 'active.json');
|
|
42
42
|
const LEASES = path.join(BRAIN_HOME, 'leases');
|
|
43
43
|
const LEASE = path.join(LEASES, `mcp-${process.pid}.json`);
|
|
@@ -95,10 +95,33 @@ let child = null; // { proc, generation, nextId, pending: Map<childId
|
|
|
95
95
|
let pendingCount = 0; // client tools/call requests currently in flight (drain gate for swaps)
|
|
96
96
|
let childRetirement = Promise.resolve();
|
|
97
97
|
let childStartup = null;
|
|
98
|
+
let childIdleTimer = null;
|
|
98
99
|
const CHILD_TERM_GRACE_MS = 3_000;
|
|
99
100
|
const CHILD_INIT_TIMEOUT_MS = Number(process.env.RUVNET_BRAIN_INIT_TIMEOUT_MS) || 60_000;
|
|
101
|
+
const CHILD_IDLE_MS = Number(process.env.RUVNET_BRAIN_CHILD_IDLE_MS) || 30 * 60_000;
|
|
102
|
+
|
|
103
|
+
function clearChildIdleTimer() {
|
|
104
|
+
if (childIdleTimer) clearTimeout(childIdleTimer);
|
|
105
|
+
childIdleTimer = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function armChildIdleTimer(c = child) {
|
|
109
|
+
clearChildIdleTimer();
|
|
110
|
+
if (!c || CHILD_IDLE_MS <= 0) return;
|
|
111
|
+
childIdleTimer = setTimeout(() => {
|
|
112
|
+
childIdleTimer = null;
|
|
113
|
+
if (child !== c) return;
|
|
114
|
+
if (pendingCount > 0 || c.pending.size > 0 || childStartup) {
|
|
115
|
+
armChildIdleTimer(c);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
void killChild(`idle for ${CHILD_IDLE_MS / 1000}s`);
|
|
119
|
+
}, CHILD_IDLE_MS);
|
|
120
|
+
childIdleTimer.unref?.();
|
|
121
|
+
}
|
|
100
122
|
|
|
101
123
|
function killChild(reason) {
|
|
124
|
+
clearChildIdleTimer();
|
|
102
125
|
if (!child) return childRetirement;
|
|
103
126
|
const c = child; child = null;
|
|
104
127
|
for (const [, p] of c.pending) p.reject(new Error(`brain worker ${reason}`));
|
|
@@ -158,6 +181,7 @@ async function ensureChild() {
|
|
|
158
181
|
await killChild('failed initialize');
|
|
159
182
|
throw new Error(`brain worker failed to initialize: ${e.message}`);
|
|
160
183
|
}
|
|
184
|
+
armChildIdleTimer(c);
|
|
161
185
|
return c;
|
|
162
186
|
})();
|
|
163
187
|
}
|
|
@@ -191,10 +215,11 @@ async function onChildTimeout(method, timeoutMs) {
|
|
|
191
215
|
}
|
|
192
216
|
}
|
|
193
217
|
|
|
194
|
-
// Overridable so the outage path above can actually be TESTED
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
218
|
+
// Overridable so the outage path above can actually be TESTED. The default matches the installer's
|
|
219
|
+
// real cold-start smoke budget: measured 153s after a cache repair on 2026-07-29. A 120s parent
|
|
220
|
+
// deadline killed the model just before it became usable and forced every retry to start cold.
|
|
221
|
+
// Steady-state latency remains a separate release gate; this budget prevents a permanent cold loop.
|
|
222
|
+
const CALL_TIMEOUT_MS = Number(process.env.RUVNET_BRAIN_CALL_TIMEOUT_MS) || 240_000;
|
|
198
223
|
|
|
199
224
|
function childRequest(c, method, params, timeoutMs = CALL_TIMEOUT_MS) {
|
|
200
225
|
return new Promise((resolve, reject) => {
|
|
@@ -225,7 +250,10 @@ async function handleClient(msg) {
|
|
|
225
250
|
if (c) {
|
|
226
251
|
try {
|
|
227
252
|
const r = await childRequest(c, 'tools/list', {}, 15_000);
|
|
228
|
-
if (r.result?.tools?.length)
|
|
253
|
+
if (r.result?.tools?.length) {
|
|
254
|
+
armChildIdleTimer(c);
|
|
255
|
+
return clientOk(id, { ...r.result, tools: withLocalTools(r.result.tools) });
|
|
256
|
+
}
|
|
229
257
|
}
|
|
230
258
|
catch { /* fall through to the static declaration */ }
|
|
231
259
|
}
|
|
@@ -259,6 +287,7 @@ async function handleClient(msg) {
|
|
|
259
287
|
return clientOk(id, { content: [{ type: 'text', text: `search_ruvnet error: ${e.message}` }], isError: true });
|
|
260
288
|
} finally {
|
|
261
289
|
pendingCount--;
|
|
290
|
+
armChildIdleTimer(c);
|
|
262
291
|
}
|
|
263
292
|
}
|
|
264
293
|
default:
|