monomind 1.11.14 → 1.13.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/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +24 -10
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +130 -53
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +138 -14
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +300 -42
- package/.claude/helpers/session.cjs +89 -30
- package/.claude/helpers/statusline.cjs +148 -4
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/settings.json +92 -1
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/package.json +20 -3
- package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
- package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
- package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
- package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
- package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
- package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
- package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
- package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
- package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
- package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
- package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
- package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
- package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
- package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
- package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
- package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
- package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
- package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
- package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
- package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
- package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
- package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
- package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
- package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
- package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
- package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
- package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
- package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
- package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
- package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
- package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
- package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
- package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
- package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
- package/packages/@monomind/cli/dist/src/index.js +7 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
- package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
- package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
- package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
- package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
- package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
- package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
- package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
- package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
- package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
- package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
- package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
- package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
- package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
- package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
- package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
- package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
- package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
- package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
- package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
- package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
- package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
- package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
- package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
- package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
- package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
- package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
- package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
- package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
- package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
- package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
- package/packages/@monomind/cli/dist/src/update/index.js +18 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
- package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
- package/packages/@monomind/cli/package.json +2 -3
|
@@ -105,6 +105,11 @@ export class CircuitBreaker {
|
|
|
105
105
|
this.failures.push(now);
|
|
106
106
|
// Clean old failures outside window
|
|
107
107
|
this.failures = this.failures.filter(t => t > now - this.config.failureWindowMs);
|
|
108
|
+
// Hard cap: prevent unbounded growth under rapid-fire recordFailure() calls
|
|
109
|
+
// before the window filter prunes entries (e.g. clock skew or test hammering).
|
|
110
|
+
if (this.failures.length > 10_000) {
|
|
111
|
+
this.failures = this.failures.slice(-10_000);
|
|
112
|
+
}
|
|
108
113
|
// Check if we should open the circuit
|
|
109
114
|
if (this.state === 'closed' && this.failures.length >= this.config.failureThreshold) {
|
|
110
115
|
this.transitionTo('open');
|
|
@@ -214,17 +219,26 @@ export class CircuitBreaker {
|
|
|
214
219
|
// ============================================================================
|
|
215
220
|
const circuitBreakers = new Map();
|
|
216
221
|
const MAX_CIRCUIT_BREAKERS = 1000;
|
|
222
|
+
const MAX_CIRCUIT_BREAKER_NAME_LEN = 256;
|
|
223
|
+
const FORBIDDEN_CB_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
|
|
217
224
|
/**
|
|
218
225
|
* Get or create a circuit breaker by name
|
|
219
226
|
*/
|
|
220
227
|
export function getCircuitBreaker(name, config) {
|
|
221
|
-
|
|
228
|
+
// Sanitize name: cap length and block prototype-pollution keys
|
|
229
|
+
const safeName = typeof name === 'string'
|
|
230
|
+
? name.slice(0, MAX_CIRCUIT_BREAKER_NAME_LEN)
|
|
231
|
+
: 'default';
|
|
232
|
+
if (FORBIDDEN_CB_NAMES.has(safeName)) {
|
|
233
|
+
throw new Error(`Invalid circuit breaker name: ${safeName}`);
|
|
234
|
+
}
|
|
235
|
+
if (!circuitBreakers.has(safeName)) {
|
|
222
236
|
if (circuitBreakers.size >= MAX_CIRCUIT_BREAKERS) {
|
|
223
237
|
throw new Error(`Circuit breaker registry full (max ${MAX_CIRCUIT_BREAKERS})`);
|
|
224
238
|
}
|
|
225
|
-
circuitBreakers.set(
|
|
239
|
+
circuitBreakers.set(safeName, new CircuitBreaker(config));
|
|
226
240
|
}
|
|
227
|
-
return circuitBreakers.get(
|
|
241
|
+
return circuitBreakers.get(safeName);
|
|
228
242
|
}
|
|
229
243
|
/**
|
|
230
244
|
* Get all circuit breaker stats
|
|
@@ -134,6 +134,9 @@ export class ErrorHandler {
|
|
|
134
134
|
return input;
|
|
135
135
|
if (_depth > 20)
|
|
136
136
|
return { '[MAX_DEPTH]': true };
|
|
137
|
+
// Primitives and null cannot be stored in a WeakSet — return them as-is.
|
|
138
|
+
if (input === null || typeof input !== 'object')
|
|
139
|
+
return input;
|
|
137
140
|
if (_seen.has(input))
|
|
138
141
|
return { '[CIRCULAR]': true };
|
|
139
142
|
_seen.add(input);
|
|
@@ -78,11 +78,25 @@ export class MonitoringHooks {
|
|
|
78
78
|
// Apply sampling
|
|
79
79
|
if (Math.random() > this.config.samplingRate)
|
|
80
80
|
return;
|
|
81
|
+
// Cap metric name and label keys/values to prevent DoS
|
|
82
|
+
const safeName = String(name).slice(0, 256);
|
|
83
|
+
const safeLabels = {};
|
|
84
|
+
let labelCount = 0;
|
|
85
|
+
for (const [k, v] of Object.entries(labels)) {
|
|
86
|
+
if (!Object.hasOwn(labels, k))
|
|
87
|
+
continue;
|
|
88
|
+
if (++labelCount > 20)
|
|
89
|
+
break;
|
|
90
|
+
// Block prototype pollution keys
|
|
91
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype')
|
|
92
|
+
continue;
|
|
93
|
+
safeLabels[String(k).slice(0, 64)] = String(v).slice(0, 512);
|
|
94
|
+
}
|
|
81
95
|
const event = {
|
|
82
|
-
name,
|
|
96
|
+
name: safeName,
|
|
83
97
|
type,
|
|
84
98
|
value,
|
|
85
|
-
labels: { ...this.config.globalLabels, ...
|
|
99
|
+
labels: { ...this.config.globalLabels, ...safeLabels },
|
|
86
100
|
timestamp: Date.now(),
|
|
87
101
|
};
|
|
88
102
|
this.metrics.push(event);
|
|
@@ -131,7 +145,8 @@ export class MonitoringHooks {
|
|
|
131
145
|
* Register a health check
|
|
132
146
|
*/
|
|
133
147
|
registerHealthCheck(name, check) {
|
|
134
|
-
|
|
148
|
+
// Cap name length to prevent map-key inflation
|
|
149
|
+
this.healthChecks.set(String(name).slice(0, 128), check);
|
|
135
150
|
}
|
|
136
151
|
/**
|
|
137
152
|
* Run all health checks
|
|
@@ -285,6 +300,8 @@ export class MonitoringHooks {
|
|
|
285
300
|
// Private Methods
|
|
286
301
|
// ============================================================================
|
|
287
302
|
checkAlerts(name, value) {
|
|
303
|
+
if (!Object.hasOwn(this.config.alertThresholds, name))
|
|
304
|
+
return;
|
|
288
305
|
const thresholds = this.config.alertThresholds[name];
|
|
289
306
|
if (!thresholds)
|
|
290
307
|
return;
|
|
@@ -33,6 +33,11 @@ export class RateLimiter {
|
|
|
33
33
|
* Check if a request is allowed
|
|
34
34
|
*/
|
|
35
35
|
check(operation, userId) {
|
|
36
|
+
// Cap operation and userId lengths so bucket keys cannot inflate the Map
|
|
37
|
+
const safeOp = typeof operation === 'string' ? operation.slice(0, 256) : 'unknown';
|
|
38
|
+
const safeUserId = userId ? userId.slice(0, 256) : undefined;
|
|
39
|
+
operation = safeOp;
|
|
40
|
+
userId = safeUserId;
|
|
36
41
|
// Check whitelist
|
|
37
42
|
if (this.config.whitelist.includes(operation)) {
|
|
38
43
|
return { allowed: true, remaining: Infinity, resetAt: 0 };
|
|
@@ -157,10 +162,14 @@ export class RateLimiter {
|
|
|
157
162
|
// Private Methods
|
|
158
163
|
// ============================================================================
|
|
159
164
|
getLimits(operation) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
// Object.hasOwn guards against prototype-pollution: if someone passes
|
|
166
|
+
// '__proto__' as the operation name, falling through to the default is safe.
|
|
167
|
+
return (Object.hasOwn(this.config.operationLimits, operation)
|
|
168
|
+
? this.config.operationLimits[operation]
|
|
169
|
+
: {
|
|
170
|
+
maxRequests: this.config.maxRequests,
|
|
171
|
+
windowMs: this.config.windowMs,
|
|
172
|
+
});
|
|
164
173
|
}
|
|
165
174
|
createBucket() {
|
|
166
175
|
return {
|
|
@@ -59,13 +59,15 @@ function calculateDelay(attempt, config, strategy = 'exponential') {
|
|
|
59
59
|
return Math.round(Math.max(0, delay));
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* Fibonacci helper for fibonacci backoff
|
|
62
|
+
* Fibonacci helper for fibonacci backoff — capped at n=40 to prevent
|
|
63
|
+
* excessively long loops when maxAttempts is set to a large value.
|
|
63
64
|
*/
|
|
64
65
|
function fibonacci(n) {
|
|
65
|
-
|
|
66
|
+
const capped = Math.min(n, 40);
|
|
67
|
+
if (capped <= 1)
|
|
66
68
|
return 1;
|
|
67
69
|
let a = 1, b = 1;
|
|
68
|
-
for (let i = 2; i <=
|
|
70
|
+
for (let i = 2; i <= capped; i++) {
|
|
69
71
|
[a, b] = [b, a + b];
|
|
70
72
|
}
|
|
71
73
|
return b;
|
|
@@ -99,8 +101,12 @@ function sleep(ms) {
|
|
|
99
101
|
*/
|
|
100
102
|
export async function withRetry(fn, config = {}, strategy = 'exponential') {
|
|
101
103
|
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
104
|
+
// Clamp maxAttempts and maxDelayMs to safe ranges to prevent runaway retry loops
|
|
105
|
+
finalConfig.maxAttempts = Math.min(Math.max(1, finalConfig.maxAttempts), 100);
|
|
106
|
+
finalConfig.maxDelayMs = Math.min(Math.max(0, finalConfig.maxDelayMs), 300_000); // 5 min ceiling
|
|
102
107
|
const startTime = Date.now();
|
|
103
108
|
const retryHistory = [];
|
|
109
|
+
const MAX_RETRY_HISTORY = 100;
|
|
104
110
|
let lastError;
|
|
105
111
|
let attempt = 0;
|
|
106
112
|
while (attempt < finalConfig.maxAttempts) {
|
|
@@ -129,12 +135,14 @@ export async function withRetry(fn, config = {}, strategy = 'exponential') {
|
|
|
129
135
|
}
|
|
130
136
|
// Calculate delay
|
|
131
137
|
const delay = calculateDelay(attempt, finalConfig, strategy);
|
|
132
|
-
// Record retry
|
|
133
|
-
retryHistory.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
// Record retry (cap history size and error message length to prevent OOM)
|
|
139
|
+
if (retryHistory.length < MAX_RETRY_HISTORY) {
|
|
140
|
+
retryHistory.push({
|
|
141
|
+
attempt,
|
|
142
|
+
error: lastError.message.slice(0, 512),
|
|
143
|
+
delayMs: delay,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
138
146
|
// Call retry callback
|
|
139
147
|
if (finalConfig.onRetry) {
|
|
140
148
|
finalConfig.onRetry(lastError, attempt, delay);
|
|
@@ -22,12 +22,16 @@ import { createSemanticRouting } from './embedder.js';
|
|
|
22
22
|
// don't land on stdout and corrupt the single JSON result line the parent
|
|
23
23
|
// parses. Must be set before the (lazy) transformers import in embedder.ts.
|
|
24
24
|
process.env.TRANSFORMERS_VERBOSITY ??= 'error';
|
|
25
|
+
/** Max task string length before it's silently truncated — prevents OOM in the embedding model. */
|
|
26
|
+
const MAX_TASK_LENGTH = 2_000;
|
|
25
27
|
async function main() {
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
28
|
+
const rawTask = process.argv[2];
|
|
29
|
+
if (!rawTask) {
|
|
28
30
|
process.stderr.write('embed-worker: missing task argument\n');
|
|
29
31
|
process.exit(2);
|
|
30
32
|
}
|
|
33
|
+
// Cap the task to prevent OOM when an oversized string is passed to the embedding model.
|
|
34
|
+
const task = rawTask.length > MAX_TASK_LENGTH ? rawTask.slice(0, MAX_TASK_LENGTH) : rawTask;
|
|
31
35
|
const { RouteLayer, ALL_ROUTES } = await import('@monomind/routing');
|
|
32
36
|
const semantic = await createSemanticRouting(ALL_ROUTES);
|
|
33
37
|
if (!semantic) {
|
|
Binary file
|
|
@@ -16,6 +16,8 @@ import { spawn, execSync } from 'child_process';
|
|
|
16
16
|
import { tmpdir } from 'os';
|
|
17
17
|
/** Default model for routing fallback — Haiku is fast and cheap for slug classification. */
|
|
18
18
|
const DEFAULT_ROUTING_MODEL = 'haiku';
|
|
19
|
+
/** Runtime-validated set of allowed model aliases. */
|
|
20
|
+
const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
19
21
|
/**
|
|
20
22
|
* Max time to wait for a single classification before giving up.
|
|
21
23
|
* `claude --print` is a full headless session (cold start ~10s, can spike),
|
|
@@ -23,6 +25,8 @@ const DEFAULT_ROUTING_MODEL = 'haiku';
|
|
|
23
25
|
* on timeout rather than blocking the caller.
|
|
24
26
|
*/
|
|
25
27
|
const DEFAULT_TIMEOUT_MS = 45_000;
|
|
28
|
+
/** Hard upper cap on caller-supplied timeoutMs — prevents disabling the SIGTERM/SIGKILL guard. */
|
|
29
|
+
const MAX_TIMEOUT_MS = 120_000;
|
|
26
30
|
/** Cap captured output so a runaway child can't grow parent memory unbounded. */
|
|
27
31
|
const MAX_OUTPUT = 1024 * 1024; // 1 MB — a slug response is tiny
|
|
28
32
|
let claudeAvailable = null;
|
|
@@ -49,8 +53,15 @@ export function isClaudeCodeAvailable() {
|
|
|
49
53
|
export function createClaudeLLMCaller(options = {}) {
|
|
50
54
|
if (!isClaudeCodeAvailable())
|
|
51
55
|
return null;
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
// Runtime enum guard — TypeScript union is compile-time only; JS callers
|
|
57
|
+
// could otherwise inject an arbitrary string into the --model flag.
|
|
58
|
+
const rawModel = options.model ?? DEFAULT_ROUTING_MODEL;
|
|
59
|
+
const model = ALLOWED_MODELS.has(rawModel) ? rawModel : DEFAULT_ROUTING_MODEL;
|
|
60
|
+
// Cap caller-supplied timeout so a huge value can't disable the SIGTERM/SIGKILL guard.
|
|
61
|
+
const rawTimeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
62
|
+
const timeoutMs = Math.min(typeof rawTimeout === 'number' && Number.isFinite(rawTimeout) && rawTimeout > 0
|
|
63
|
+
? rawTimeout
|
|
64
|
+
: DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
54
65
|
const cwd = options.cwd ?? tmpdir();
|
|
55
66
|
return (prompt) => new Promise((resolve, reject) => {
|
|
56
67
|
const env = { ...process.env };
|
|
@@ -25,12 +25,20 @@ const WORKER_PATH = join(dirname(fileURLToPath(import.meta.url)), 'embed-worker.
|
|
|
25
25
|
const WORKER_TIMEOUT_MS = 90_000;
|
|
26
26
|
/** Cap worker stdout so a runaway child can't grow parent memory unbounded. */
|
|
27
27
|
const MAX_WORKER_OUTPUT = 4 * 1024 * 1024; // 4 MB
|
|
28
|
+
/** Cap the task string length to prevent OOM/DoS via oversized argv. */
|
|
29
|
+
const MAX_TASK_LENGTH = 2_000;
|
|
30
|
+
/** Cap stderr reflected in error messages to prevent log-inflation attacks. */
|
|
31
|
+
const MAX_STDERR_IN_ERROR = 500;
|
|
32
|
+
/** Cap allScores array passed to the LLM fallback to prevent downstream OOM. */
|
|
33
|
+
const MAX_ALL_SCORES = 100;
|
|
28
34
|
/** Marker the worker prefixes its result line with (see embed-worker.ts). */
|
|
29
35
|
const RESULT_MARKER = '__ROUTE_RESULT__';
|
|
30
36
|
/** Run the embedding worker for one task. Resolves the RouteResult, or rejects. */
|
|
31
37
|
function runWorker(task) {
|
|
38
|
+
// Cap task length before passing as argv to prevent OOM/DoS via oversized args.
|
|
39
|
+
const safeTask = task.length > MAX_TASK_LENGTH ? task.slice(0, MAX_TASK_LENGTH) : task;
|
|
32
40
|
return new Promise((resolve, reject) => {
|
|
33
|
-
const child = spawn(process.execPath, [WORKER_PATH,
|
|
41
|
+
const child = spawn(process.execPath, [WORKER_PATH, safeTask], {
|
|
34
42
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
35
43
|
windowsHide: true,
|
|
36
44
|
});
|
|
@@ -75,7 +83,10 @@ function runWorker(task) {
|
|
|
75
83
|
settled = true;
|
|
76
84
|
clearTimeout(timer);
|
|
77
85
|
if (code !== 0) {
|
|
78
|
-
|
|
86
|
+
// Truncate stderr before embedding in Error to prevent log-inflation via
|
|
87
|
+
// a malicious or runaway child writing oversized diagnostic output.
|
|
88
|
+
const stderrSnippet = stderr.trim().slice(0, MAX_STDERR_IN_ERROR);
|
|
89
|
+
reject(new Error(stderrSnippet || `worker exited ${code}`));
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
81
92
|
// Extract the marked result line — model-loader output may have written
|
|
@@ -130,8 +141,12 @@ export async function createConfiguredRouteLayer(opts = {}) {
|
|
|
130
141
|
}
|
|
131
142
|
// 3. Below threshold → Claude fallback in the parent, reusing scores.
|
|
132
143
|
if (llmCaller && Array.isArray(semantic.allScores) && semantic.allScores.length) {
|
|
144
|
+
// Cap allScores to prevent downstream OOM if the worker sends an oversized array.
|
|
145
|
+
const scores = semantic.allScores.length > MAX_ALL_SCORES
|
|
146
|
+
? semantic.allScores.slice(0, MAX_ALL_SCORES)
|
|
147
|
+
: semantic.allScores;
|
|
133
148
|
const fallback = new LLMFallbackRouter({ llmCaller, model: 'haiku' });
|
|
134
|
-
return fallback.classify(taskDescription, ALL_ROUTES,
|
|
149
|
+
return fallback.classify(taskDescription, ALL_ROUTES, scores);
|
|
135
150
|
}
|
|
136
151
|
if (!opts.debug)
|
|
137
152
|
delete semantic.allScores;
|
|
@@ -129,6 +129,7 @@ export declare class ClaimService extends EventEmitter {
|
|
|
129
129
|
private eventLog;
|
|
130
130
|
constructor(projectRoot: string, config?: Partial<ClaimServiceConfig>);
|
|
131
131
|
initialize(): Promise<void>;
|
|
132
|
+
private static readonly MAX_CLAIMS_FILE_BYTES;
|
|
132
133
|
private loadClaims;
|
|
133
134
|
private _saveQueue;
|
|
134
135
|
private saveClaims;
|
|
@@ -57,10 +57,18 @@ export class ClaimService extends EventEmitter {
|
|
|
57
57
|
// Load existing claims
|
|
58
58
|
await this.loadClaims();
|
|
59
59
|
}
|
|
60
|
+
// Hard cap on claims.json: a file larger than this is malformed or
|
|
61
|
+
// adversarially crafted (normal claim files are tiny).
|
|
62
|
+
static MAX_CLAIMS_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
60
63
|
async loadClaims() {
|
|
61
64
|
const claimsFile = path.join(this.storagePath, 'claims.json');
|
|
62
65
|
if (fs.existsSync(claimsFile)) {
|
|
63
66
|
try {
|
|
67
|
+
const fileSize = fs.statSync(claimsFile).size;
|
|
68
|
+
if (fileSize > ClaimService.MAX_CLAIMS_FILE_BYTES) {
|
|
69
|
+
// Oversized file — skip loading rather than OOM-ing the daemon
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
64
72
|
const data = JSON.parse(fs.readFileSync(claimsFile, 'utf-8'));
|
|
65
73
|
for (const claim of data.claims || []) {
|
|
66
74
|
claim.claimedAt = new Date(claim.claimedAt);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Shared JSON config file persistence with atomic writes and Zod validation
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
|
+
import * as os from 'os';
|
|
6
7
|
import * as path from 'path';
|
|
7
8
|
/** Config file search paths in priority order */
|
|
8
9
|
const CONFIG_FILENAMES = [
|
|
@@ -123,7 +124,7 @@ export class ConfigFileManager {
|
|
|
123
124
|
* drop the first writer's API key.
|
|
124
125
|
*/
|
|
125
126
|
set(cwd, key, value) {
|
|
126
|
-
const KNOWN_SET_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks', 'neural']);
|
|
127
|
+
const KNOWN_SET_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks', 'neural', 'providers']);
|
|
127
128
|
const topSection = String(key).split('.')[0];
|
|
128
129
|
if (!KNOWN_SET_SECTIONS.has(topSection)) {
|
|
129
130
|
throw new Error(`Unknown config section: "${topSection}". Allowed: ${[...KNOWN_SET_SECTIONS].join(', ')}`);
|
|
@@ -181,6 +182,17 @@ export class ConfigFileManager {
|
|
|
181
182
|
/** Import config from a specific path */
|
|
182
183
|
importFrom(cwd, importPath) {
|
|
183
184
|
const resolved = path.resolve(cwd, importPath);
|
|
185
|
+
// Guard against path traversal: the resolved path must be within the
|
|
186
|
+
// project cwd or the user's home directory. Without this check an
|
|
187
|
+
// automated script can pass "/etc/passwd" or "../../.env" and exfiltrate
|
|
188
|
+
// files outside the project tree.
|
|
189
|
+
const projectRoot = path.resolve(cwd);
|
|
190
|
+
const home = os.homedir();
|
|
191
|
+
const isUnderProject = resolved === projectRoot || resolved.startsWith(projectRoot + path.sep);
|
|
192
|
+
const isUnderHome = resolved === home || resolved.startsWith(home + path.sep);
|
|
193
|
+
if (!isUnderProject && !isUnderHome) {
|
|
194
|
+
throw new Error(`Import path must be within the project directory or home directory: ${resolved}`);
|
|
195
|
+
}
|
|
184
196
|
if (!fs.existsSync(resolved)) {
|
|
185
197
|
throw new Error(`Import file not found: ${resolved}`);
|
|
186
198
|
}
|
|
@@ -199,7 +211,7 @@ export class ConfigFileManager {
|
|
|
199
211
|
// KNOWN_SECTIONS only validates top-level keys, leaving nested
|
|
200
212
|
// {agents:{providers:[{__proto__:{...}}]}} unsanitized.
|
|
201
213
|
const imported = sanitizeConfigObject(importedRaw);
|
|
202
|
-
const KNOWN_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks']);
|
|
214
|
+
const KNOWN_SECTIONS = new Set(['version', 'agents', 'swarm', 'memory', 'mcp', 'cli', 'hooks', 'neural', 'providers']);
|
|
203
215
|
for (const key of Object.keys(imported)) {
|
|
204
216
|
if (!KNOWN_SECTIONS.has(key)) {
|
|
205
217
|
throw new Error(`Unknown config section: "${key}"`);
|
|
@@ -1017,6 +1017,12 @@ Analyze the above codebase context and provide your response following the forma
|
|
|
1017
1017
|
let stdout = '';
|
|
1018
1018
|
let stderr = '';
|
|
1019
1019
|
let resolved = false;
|
|
1020
|
+
// Cap stdout + stderr to 10 MB each to prevent OOM if a spawned Claude
|
|
1021
|
+
// Code process emits unexpectedly large output (e.g. a worker that dumps
|
|
1022
|
+
// a large file listing). Once the cap is reached we stop appending; the
|
|
1023
|
+
// truncation marker lets callers detect that output was cut.
|
|
1024
|
+
const MAX_HEADLESS_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
1025
|
+
const TRUNCATION_MARKER = '\n[... output truncated at 10 MB ...]';
|
|
1020
1026
|
const cleanup = () => {
|
|
1021
1027
|
clearTimeout(timeoutHandle);
|
|
1022
1028
|
clearTimeout(sigkillTimer);
|
|
@@ -1024,7 +1030,12 @@ Analyze the above codebase context and provide your response following the forma
|
|
|
1024
1030
|
};
|
|
1025
1031
|
child.stdout?.on('data', (data) => {
|
|
1026
1032
|
const chunk = data.toString();
|
|
1027
|
-
stdout
|
|
1033
|
+
if (stdout.length < MAX_HEADLESS_OUTPUT_BYTES) {
|
|
1034
|
+
stdout += chunk;
|
|
1035
|
+
if (stdout.length >= MAX_HEADLESS_OUTPUT_BYTES) {
|
|
1036
|
+
stdout = stdout.slice(0, MAX_HEADLESS_OUTPUT_BYTES) + TRUNCATION_MARKER;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1028
1039
|
this.emit('output', {
|
|
1029
1040
|
executionId: options.executionId,
|
|
1030
1041
|
type: 'stdout',
|
|
@@ -1033,7 +1044,12 @@ Analyze the above codebase context and provide your response following the forma
|
|
|
1033
1044
|
});
|
|
1034
1045
|
child.stderr?.on('data', (data) => {
|
|
1035
1046
|
const chunk = data.toString();
|
|
1036
|
-
stderr
|
|
1047
|
+
if (stderr.length < MAX_HEADLESS_OUTPUT_BYTES) {
|
|
1048
|
+
stderr += chunk;
|
|
1049
|
+
if (stderr.length >= MAX_HEADLESS_OUTPUT_BYTES) {
|
|
1050
|
+
stderr = stderr.slice(0, MAX_HEADLESS_OUTPUT_BYTES) + TRUNCATION_MARKER;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1037
1053
|
this.emit('output', {
|
|
1038
1054
|
executionId: options.executionId,
|
|
1039
1055
|
type: 'stderr',
|