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.
Files changed (179) hide show
  1. package/.claude/agents/generated/channel-intelligence-director.md +87 -0
  2. package/.claude/agents/generated/chief-growth-officer.md +88 -0
  3. package/.claude/agents/generated/content-seo-strategist.md +90 -0
  4. package/.claude/agents/generated/developer-community-strategist.md +91 -0
  5. package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
  6. package/.claude/agents/generated/social-media-strategist.md +91 -0
  7. package/.claude/agents/generated/video-visual-strategist.md +90 -0
  8. package/.claude/commands/mastermind/master.md +1 -1
  9. package/.claude/helpers/auto-memory-hook.mjs +13 -4
  10. package/.claude/helpers/control-start.cjs +5 -0
  11. package/.claude/helpers/event-logger.cjs +114 -0
  12. package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
  13. package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
  14. package/.claude/helpers/handlers/compact-handler.cjs +2 -0
  15. package/.claude/helpers/handlers/edit-handler.cjs +1 -1
  16. package/.claude/helpers/handlers/gates-handler.cjs +3 -0
  17. package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
  18. package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
  19. package/.claude/helpers/handlers/route-handler.cjs +24 -10
  20. package/.claude/helpers/handlers/session-handler.cjs +11 -4
  21. package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
  22. package/.claude/helpers/handlers/task-handler.cjs +13 -5
  23. package/.claude/helpers/hook-handler.cjs +40 -0
  24. package/.claude/helpers/intelligence.cjs +130 -53
  25. package/.claude/helpers/loop-tracker.cjs +15 -3
  26. package/.claude/helpers/memory-palace.cjs +461 -0
  27. package/.claude/helpers/memory.cjs +138 -14
  28. package/.claude/helpers/metrics-db.mjs +87 -0
  29. package/.claude/helpers/router.cjs +300 -42
  30. package/.claude/helpers/session.cjs +89 -30
  31. package/.claude/helpers/statusline.cjs +148 -4
  32. package/.claude/helpers/toggle-statusline.cjs +73 -0
  33. package/.claude/helpers/token-tracker.cjs +934 -0
  34. package/.claude/helpers/utils/micro-agents.cjs +20 -4
  35. package/.claude/helpers/utils/monograph.cjs +39 -4
  36. package/.claude/helpers/utils/telemetry.cjs +3 -3
  37. package/.claude/scheduled_tasks.lock +1 -1
  38. package/.claude/settings.json +92 -1
  39. package/.claude/skills/mastermind/_protocol.md +25 -15
  40. package/.claude/skills/mastermind/architect.md +3 -3
  41. package/.claude/skills/mastermind/autodev.md +4 -2
  42. package/.claude/skills/mastermind/idea.md +10 -0
  43. package/.claude/skills/mastermind/ops.md +3 -3
  44. package/.claude/skills/mastermind/runorg.md +153 -86
  45. package/package.json +20 -3
  46. package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
  47. package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
  48. package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
  49. package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
  50. package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
  51. package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
  52. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
  53. package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
  54. package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
  55. package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
  56. package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
  57. package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
  58. package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
  59. package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
  60. package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
  61. package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
  62. package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
  63. package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
  64. package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
  65. package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
  66. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
  67. package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
  68. package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
  69. package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
  70. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
  71. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
  72. package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
  73. package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
  74. package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
  75. package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
  76. package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
  77. package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
  78. package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
  79. package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
  80. package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
  81. package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
  82. package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
  83. package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
  84. package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
  85. package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
  86. package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
  87. package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
  88. package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
  89. package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
  90. package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
  91. package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
  92. package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
  93. package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
  94. package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
  95. package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
  96. package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
  97. package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
  98. package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
  99. package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
  100. package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
  101. package/packages/@monomind/cli/dist/src/index.js +7 -3
  102. package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
  103. package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
  104. package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
  105. package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
  106. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
  107. package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
  108. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
  109. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
  110. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
  111. package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
  112. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
  113. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
  114. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
  115. package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
  116. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
  117. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
  118. package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
  119. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
  120. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
  121. package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
  122. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
  123. package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
  124. package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
  125. package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
  126. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
  127. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
  128. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
  129. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
  130. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
  131. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
  132. package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
  133. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
  134. package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
  135. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
  136. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
  137. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
  138. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
  139. package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
  140. package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
  141. package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
  142. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
  143. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
  144. package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
  145. package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
  146. package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
  147. package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
  148. package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
  149. package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
  150. package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
  151. package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
  152. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
  153. package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
  154. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
  155. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
  156. package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
  157. package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
  158. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
  159. package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
  160. package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
  161. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
  162. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
  163. package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
  164. package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
  165. package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
  166. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
  167. package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
  168. package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
  169. package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
  170. package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
  171. package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
  172. package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
  173. package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
  174. package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
  175. package/packages/@monomind/cli/dist/src/update/index.js +18 -1
  176. package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
  177. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
  178. package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
  179. package/packages/@monomind/cli/package.json +2 -3
@@ -71,15 +71,36 @@ export const taskTools = [
71
71
  handler: async (input) => {
72
72
  const store = loadTaskStore();
73
73
  const taskId = `task-${Date.now()}-${randomBytes(4).toString('hex')}`;
74
+ // Cap all string fields: they are persisted verbatim to the task JSON store.
75
+ const MAX_TASK_TYPE_LEN = 128;
76
+ const MAX_TASK_DESC_LEN = 64 * 1024; // 64 KB — realistic task descriptions
77
+ const MAX_TASK_ASSIGNEE_LEN = 256;
78
+ const MAX_TASK_ASSIGNEES = 100;
79
+ const MAX_TASK_TAG_LEN = 128;
80
+ const MAX_TASK_TAGS = 50;
81
+ const rawTaskType = input.type;
82
+ const taskType = typeof rawTaskType === 'string' && rawTaskType.length > MAX_TASK_TYPE_LEN
83
+ ? rawTaskType.slice(0, MAX_TASK_TYPE_LEN) : rawTaskType;
84
+ const rawTaskDesc = input.description;
85
+ const taskDesc = typeof rawTaskDesc === 'string' && rawTaskDesc.length > MAX_TASK_DESC_LEN
86
+ ? rawTaskDesc.slice(0, MAX_TASK_DESC_LEN) : rawTaskDesc;
87
+ const rawAssignTo = input.assignTo || [];
88
+ const assignedTo = Array.isArray(rawAssignTo)
89
+ ? rawAssignTo.slice(0, MAX_TASK_ASSIGNEES).map(a => typeof a === 'string' && a.length > MAX_TASK_ASSIGNEE_LEN ? a.slice(0, MAX_TASK_ASSIGNEE_LEN) : a)
90
+ : [];
91
+ const rawTags = input.tags || [];
92
+ const tags = Array.isArray(rawTags)
93
+ ? rawTags.slice(0, MAX_TASK_TAGS).map(t => typeof t === 'string' && t.length > MAX_TASK_TAG_LEN ? t.slice(0, MAX_TASK_TAG_LEN) : t)
94
+ : [];
74
95
  const task = {
75
96
  taskId,
76
- type: input.type,
77
- description: input.description,
97
+ type: taskType,
98
+ description: taskDesc,
78
99
  priority: input.priority || 'normal',
79
100
  status: 'pending',
80
101
  progress: 0,
81
- assignedTo: input.assignTo || [],
82
- tags: input.tags || [],
102
+ assignedTo,
103
+ tags,
83
104
  createdAt: new Date().toISOString(),
84
105
  startedAt: null,
85
106
  completedAt: null,
@@ -172,8 +193,13 @@ export const taskTools = [
172
193
  }
173
194
  // Sort by creation date (newest first)
174
195
  tasks.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
175
- // Apply limit
176
- const limit = input.limit || 50;
196
+ // Apply limit — cap to 1 000 to prevent returning the entire task store
197
+ // in one response, which could cause OOM on large deployments.
198
+ const MAX_TASK_LIMIT = 1_000;
199
+ const rawLimit = typeof input.limit === 'number' ? input.limit : 50;
200
+ const limit = Number.isFinite(rawLimit) && rawLimit > 0
201
+ ? Math.min(Math.floor(rawLimit), MAX_TASK_LIMIT)
202
+ : 50;
177
203
  tasks = tasks.slice(0, limit);
178
204
  return {
179
205
  tasks: tasks.map(t => ({
@@ -225,7 +251,7 @@ export const taskTools = [
225
251
  const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
226
252
  try {
227
253
  let agentStore = { agents: {} };
228
- if (existsSync(agentStorePath)) {
254
+ if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
229
255
  const agentRaw = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
230
256
  if (agentRaw && typeof agentRaw === 'object' && !Object.prototype.hasOwnProperty.call(agentRaw, '__proto__')) {
231
257
  agentStore = agentRaw;
@@ -298,7 +324,15 @@ export const taskTools = [
298
324
  task.progress = Math.min(100, Math.max(0, input.progress));
299
325
  }
300
326
  if (input.assignTo) {
301
- task.assignedTo = input.assignTo;
327
+ // Cap array and element lengths — task_create already does this;
328
+ // task_update must apply the same guards so the on-disk store
329
+ // cannot be inflated via the update path.
330
+ const MAX_TASK_ASSIGNEE_LEN = 256;
331
+ const MAX_TASK_ASSIGNEES = 100;
332
+ const rawAssignTo = input.assignTo;
333
+ task.assignedTo = Array.isArray(rawAssignTo)
334
+ ? rawAssignTo.slice(0, MAX_TASK_ASSIGNEES).map(a => typeof a === 'string' && a.length > MAX_TASK_ASSIGNEE_LEN ? a.slice(0, MAX_TASK_ASSIGNEE_LEN) : a)
335
+ : task.assignedTo;
302
336
  }
303
337
  saveTaskStore(store);
304
338
  return {
@@ -343,7 +377,7 @@ export const taskTools = [
343
377
  const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
344
378
  let agentStore = { agents: {} };
345
379
  try {
346
- if (existsSync(agentStorePath)) {
380
+ if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
347
381
  agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
348
382
  }
349
383
  }
@@ -426,7 +460,15 @@ export const taskTools = [
426
460
  if (task) {
427
461
  task.status = 'cancelled';
428
462
  task.completedAt = new Date().toISOString();
429
- task.result = { cancelReason: input.reason || 'Cancelled by user' };
463
+ // Cap reason: persisted verbatim to the task store on disk.
464
+ // Without a cap an attacker can inflate the store with an arbitrarily
465
+ // large cancellation reason string.
466
+ const MAX_CANCEL_REASON_LEN = 1024;
467
+ const rawReason = input.reason;
468
+ const cancelReason = typeof rawReason === 'string' && rawReason.length > MAX_CANCEL_REASON_LEN
469
+ ? rawReason.slice(0, MAX_CANCEL_REASON_LEN)
470
+ : (rawReason || 'Cancelled by user');
471
+ task.result = { cancelReason };
430
472
  saveTaskStore(store);
431
473
  return {
432
474
  success: true,
@@ -1,6 +1,7 @@
1
1
  import { getProjectCwd } from './types.js';
2
- import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
3
- import { join } from 'node:path';
2
+ import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { homedir } from 'node:os';
4
5
  import { execSync } from 'node:child_process';
5
6
  import { randomBytes } from 'node:crypto';
6
7
  // Storage paths
@@ -19,10 +20,11 @@ function ensureTerminalDir() {
19
20
  mkdirSync(dir, { recursive: true });
20
21
  }
21
22
  }
23
+ const MAX_TERMINAL_STORE_BYTES = 10 * 1024 * 1024; // 10 MB
22
24
  function loadTerminalStore() {
23
25
  try {
24
26
  const path = getTerminalPath();
25
- if (existsSync(path)) {
27
+ if (existsSync(path) && statSync(path).size <= MAX_TERMINAL_STORE_BYTES) {
26
28
  return JSON.parse(readFileSync(path, 'utf-8'));
27
29
  }
28
30
  }
@@ -74,14 +76,40 @@ export const terminalTools = [
74
76
  safeEnv[k] = String(v);
75
77
  }
76
78
  }
79
+ // Validate workingDir: must exist, be a directory, and not escape to
80
+ // system-sensitive paths. Fall back to project cwd if invalid.
81
+ let resolvedWorkingDir = getProjectCwd();
82
+ if (input.workingDir && typeof input.workingDir === 'string') {
83
+ const candidate = resolve(input.workingDir);
84
+ const projectCwd = getProjectCwd();
85
+ const home = homedir();
86
+ // Allow paths under project cwd or user home directory only.
87
+ const isUnderProject = candidate === projectCwd || candidate.startsWith(projectCwd + '/') || candidate.startsWith(projectCwd + '\\');
88
+ const isUnderHome = candidate === home || candidate.startsWith(home + '/') || candidate.startsWith(home + '\\');
89
+ if ((isUnderProject || isUnderHome) && existsSync(candidate)) {
90
+ try {
91
+ if (statSync(candidate).isDirectory()) {
92
+ resolvedWorkingDir = candidate;
93
+ }
94
+ }
95
+ catch {
96
+ // Leave resolvedWorkingDir as default
97
+ }
98
+ }
99
+ }
77
100
  const id = `term-${Date.now()}-${randomBytes(4).toString('hex')}`;
101
+ // Cap session name: stored in terminal JSON store on disk.
102
+ const MAX_TERMINAL_NAME_LEN = 256;
103
+ const rawTerminalName = input.name || `Terminal ${Object.keys(store.sessions).length + 1}`;
104
+ const terminalName = typeof rawTerminalName === 'string' && rawTerminalName.length > MAX_TERMINAL_NAME_LEN
105
+ ? rawTerminalName.slice(0, MAX_TERMINAL_NAME_LEN) : rawTerminalName;
78
106
  const session = {
79
107
  id,
80
- name: input.name || `Terminal ${Object.keys(store.sessions).length + 1}`,
108
+ name: terminalName,
81
109
  status: 'active',
82
110
  createdAt: new Date().toISOString(),
83
111
  lastActivity: new Date().toISOString(),
84
- workingDir: input.workingDir || getProjectCwd(),
112
+ workingDir: resolvedWorkingDir,
85
113
  history: [],
86
114
  env: safeEnv,
87
115
  };
@@ -114,7 +142,13 @@ export const terminalTools = [
114
142
  handler: async (input) => {
115
143
  const store = loadTerminalStore();
116
144
  const sessionId = input.sessionId;
117
- const command = input.command;
145
+ // Cap command: the metacharacter regex check at line 220 is O(n), and the
146
+ // raw command is stored verbatim in session history (up to 200 entries).
147
+ // A realistic shell command is well under 64 KB; cap there.
148
+ const MAX_TERMINAL_COMMAND_LEN = 64 * 1024;
149
+ const rawCommand = input.command;
150
+ const command = typeof rawCommand === 'string' && rawCommand.length > MAX_TERMINAL_COMMAND_LEN
151
+ ? rawCommand.slice(0, MAX_TERMINAL_COMMAND_LEN) : rawCommand;
118
152
  const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
119
153
  // Reject inherited keys (incl. toString/hasOwnProperty/etc.) so a tampered
120
154
  // store.json can't redirect bracket access into Object.prototype.
@@ -59,7 +59,13 @@ export const transferTools = [
59
59
  handler: async (input) => {
60
60
  try {
61
61
  const { detectPII } = await import('../transfer/anonymization/index.js');
62
- const result = detectPII(input.content);
62
+ // detectPII runs multiple PII regexes over the entire string — O(n × patterns).
63
+ // Cap to 1 MB to prevent ReDoS-style DoS from oversized content.
64
+ const MAX_PII_CONTENT_LEN = 1024 * 1024; // 1 MB
65
+ const rawContent = input.content;
66
+ const content = typeof rawContent === 'string' && rawContent.length > MAX_PII_CONTENT_LEN
67
+ ? rawContent.slice(0, MAX_PII_CONTENT_LEN) : rawContent;
68
+ const result = detectPII(content);
63
69
  return createResult(result);
64
70
  }
65
71
  catch (error) {
@@ -88,7 +94,13 @@ export const transferTools = [
88
94
  handler: async (input) => {
89
95
  try {
90
96
  const { resolveIPNS } = await import('../transfer/ipfs/client.js');
91
- const result = await resolveIPNS(input.name);
97
+ // Cap IPNS name length — uncapped strings may trigger O(n) path parsing
98
+ // inside the IPFS client or be reflected in error messages.
99
+ const MAX_IPNS_NAME_LEN = 512;
100
+ const rawName = input.name;
101
+ const name = typeof rawName === 'string' && rawName.length > MAX_IPNS_NAME_LEN
102
+ ? rawName.slice(0, MAX_IPNS_NAME_LEN) : rawName;
103
+ const result = await resolveIPNS(name);
92
104
  return createResult({ success: true, cid: result });
93
105
  }
94
106
  catch (error) {
@@ -132,7 +144,18 @@ export const transferTools = [
132
144
  handler: async (input) => {
133
145
  try {
134
146
  const store = await getPatternStore();
135
- const results = store.search(input);
147
+ // Cap query and limit to prevent DoS via large string or result set.
148
+ const MAX_TRANSFER_QUERY_LEN = 4 * 1024;
149
+ const MAX_TRANSFER_LIMIT = 500;
150
+ const inp = input;
151
+ const cappedInput = {
152
+ ...inp,
153
+ query: typeof inp.query === 'string' && inp.query.length > MAX_TRANSFER_QUERY_LEN
154
+ ? inp.query.slice(0, MAX_TRANSFER_QUERY_LEN) : inp.query,
155
+ limit: typeof inp.limit === 'number' && Number.isFinite(inp.limit)
156
+ ? Math.min(Math.floor(Math.max(inp.limit, 1)), MAX_TRANSFER_LIMIT) : inp.limit,
157
+ };
158
+ const results = store.search(cappedInput);
136
159
  return createResult(results);
137
160
  }
138
161
  catch (error) {
@@ -296,7 +319,17 @@ export const transferTools = [
296
319
  if (!result.success || !result.registry) {
297
320
  return createResult({ error: result.error || 'Failed to discover registry' }, true);
298
321
  }
299
- const opts = input;
322
+ // Cap query and limit before forwarding to searchPlugins.
323
+ const MAX_PLUGIN_QUERY_LEN = 4 * 1024;
324
+ const MAX_PLUGIN_LIMIT = 500;
325
+ const rawOpts = input;
326
+ const opts = {
327
+ ...rawOpts,
328
+ query: typeof rawOpts.query === 'string' && rawOpts.query.length > MAX_PLUGIN_QUERY_LEN
329
+ ? rawOpts.query.slice(0, MAX_PLUGIN_QUERY_LEN) : rawOpts.query,
330
+ limit: typeof rawOpts.limit === 'number' && Number.isFinite(rawOpts.limit)
331
+ ? Math.min(Math.floor(Math.max(rawOpts.limit, 1)), MAX_PLUGIN_LIMIT) : rawOpts.limit,
332
+ };
300
333
  const searchResult = searchPlugins(result.registry, opts);
301
334
  return createResult(searchResult);
302
335
  }
@@ -98,7 +98,12 @@ export const workflowTools = [
98
98
  handler: async (input) => {
99
99
  const store = loadWorkflowStore();
100
100
  const template = input.template;
101
- const task = input.task;
101
+ // Cap task description to prevent DoS via unbounded string stored in
102
+ // workflow record and serialised to disk.
103
+ const MAX_WORKFLOW_TASK_LEN = 16 * 1024;
104
+ const rawTask = input.task;
105
+ const task = typeof rawTask === 'string' && rawTask.length > MAX_WORKFLOW_TASK_LEN
106
+ ? rawTask.slice(0, MAX_WORKFLOW_TASK_LEN) : rawTask;
102
107
  const options = input.options || {};
103
108
  const dryRun = options.dryRun;
104
109
  // Build workflow from template or inline
@@ -192,7 +197,19 @@ export const workflowTools = [
192
197
  handler: async (input) => {
193
198
  const store = loadWorkflowStore();
194
199
  const workflowId = `workflow-${Date.now()}-${randomBytes(6).toString('hex')}`;
195
- const steps = (input.steps || []).map((s, i) => ({
200
+ // Cap string fields before storing to disk.
201
+ const MAX_WF_NAME_LEN = 512;
202
+ const MAX_WF_DESC_LEN = 16 * 1024;
203
+ const MAX_WF_STEPS = 500;
204
+ const rawWfName = input.name;
205
+ const wfName = typeof rawWfName === 'string' && rawWfName.length > MAX_WF_NAME_LEN
206
+ ? rawWfName.slice(0, MAX_WF_NAME_LEN) : rawWfName;
207
+ const rawWfDesc = input.description;
208
+ const wfDesc = typeof rawWfDesc === 'string' && rawWfDesc.length > MAX_WF_DESC_LEN
209
+ ? rawWfDesc.slice(0, MAX_WF_DESC_LEN) : rawWfDesc;
210
+ const rawSteps = input.steps || [];
211
+ const cappedSteps = rawSteps.slice(0, MAX_WF_STEPS);
212
+ const steps = cappedSteps.map((s, i) => ({
196
213
  stepId: `step-${i + 1}`,
197
214
  name: s.name || `Step ${i + 1}`,
198
215
  type: s.type || 'task',
@@ -201,8 +218,8 @@ export const workflowTools = [
201
218
  }));
202
219
  const workflow = {
203
220
  workflowId,
204
- name: input.name,
205
- description: input.description,
221
+ name: wfName,
222
+ description: wfDesc,
206
223
  steps,
207
224
  status: steps.length > 0 ? 'ready' : 'draft',
208
225
  currentStep: 0,
@@ -355,8 +372,14 @@ export const workflowTools = [
355
372
  }
356
373
  // Sort by creation date (newest first)
357
374
  workflows.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
358
- // Apply limit
359
- const limit = input.limit || 20;
375
+ // Apply limit — cap to 1 000 to prevent returning the full (potentially
376
+ // large) in-memory workflow store in one response, which could cause OOM
377
+ // or excessive serialisation latency.
378
+ const MAX_WORKFLOW_LIMIT = 1_000;
379
+ const rawLimit = typeof input.limit === 'number' ? input.limit : 20;
380
+ const limit = Number.isFinite(rawLimit) && rawLimit > 0
381
+ ? Math.min(Math.floor(rawLimit), MAX_WORKFLOW_LIMIT)
382
+ : 20;
360
383
  const totalCount = workflows.length;
361
384
  workflows = workflows.slice(0, limit);
362
385
  return {
@@ -145,7 +145,12 @@ export class EWCConsolidator {
145
145
  for (const newPattern of newPatterns) {
146
146
  if (!newPattern.embedding || newPattern.embedding.length === 0)
147
147
  continue;
148
- const existingPattern = this.patterns.get(newPattern.id);
148
+ // Cap pattern ID length: unbounded IDs fill both the Map key and the
149
+ // modifiedPatterns/protectedPatterns result arrays without any limit.
150
+ const patternId = typeof newPattern.id === 'string'
151
+ ? newPattern.id.slice(0, 256)
152
+ : String(newPattern.id).slice(0, 256);
153
+ const existingPattern = this.patterns.get(patternId);
149
154
  if (existingPattern) {
150
155
  // Calculate EWC penalty for updating existing pattern
151
156
  const penalty = this.getPenalty(existingPattern.weights, newPattern.embedding, fisher);
@@ -153,19 +158,19 @@ export class EWCConsolidator {
153
158
  const importanceScore = this.calculateImportance(existingPattern);
154
159
  if (importanceScore > this.config.importanceThreshold && penalty > this.config.lambda) {
155
160
  // Protect high-importance patterns with high penalty
156
- result.protectedPatterns.push(newPattern.id);
161
+ result.protectedPatterns.push(patternId);
157
162
  // Apply constrained update: blend old and new based on importance
158
163
  const blendFactor = 1 - importanceScore;
159
164
  const blendedWeights = this.blendWeights(existingPattern.weights, newPattern.embedding, blendFactor, fisher);
160
165
  existingPattern.weights = blendedWeights;
161
166
  existingPattern.lastUpdated = Date.now();
162
- result.modifiedPatterns.push(newPattern.id);
167
+ result.modifiedPatterns.push(patternId);
163
168
  }
164
169
  else {
165
170
  // Low importance or low penalty: allow full update
166
171
  existingPattern.weights = newPattern.embedding.slice(0, this.config.dimensions);
167
172
  existingPattern.lastUpdated = Date.now();
168
- result.modifiedPatterns.push(newPattern.id);
173
+ result.modifiedPatterns.push(patternId);
169
174
  }
170
175
  // Update Fisher diagonal for this pattern
171
176
  existingPattern.fisherDiagonal = fisher;
@@ -174,7 +179,7 @@ export class EWCConsolidator {
174
179
  else {
175
180
  // New pattern: add directly
176
181
  const weights = {
177
- id: newPattern.id,
182
+ id: patternId,
178
183
  weights: newPattern.embedding.slice(0, this.config.dimensions),
179
184
  fisherDiagonal: fisher,
180
185
  importance: 0.5,
@@ -184,8 +189,8 @@ export class EWCConsolidator {
184
189
  type: newPattern.type,
185
190
  description: newPattern.description
186
191
  };
187
- this.patterns.set(newPattern.id, weights);
188
- result.modifiedPatterns.push(newPattern.id);
192
+ this.patterns.set(patternId, weights);
193
+ result.modifiedPatterns.push(patternId);
189
194
  }
190
195
  result.patternsConsolidated++;
191
196
  }
@@ -209,7 +214,10 @@ export class EWCConsolidator {
209
214
  return result;
210
215
  }
211
216
  catch (error) {
212
- result.error = error instanceof Error ? error.message : String(error);
217
+ // Sanitize: strip filesystem paths and cap length so internal error
218
+ // messages are not reflected verbatim into CallerResult.error.
219
+ const rawMsg = error instanceof Error ? error.message : String(error);
220
+ result.error = rawMsg.replace(/\/[^\s:]+(\/|(?=\s|:|$))/g, '<path>/').slice(0, 500);
213
221
  result.duration = performance.now() - startTime;
214
222
  return result;
215
223
  }
@@ -513,6 +521,10 @@ export class EWCConsolidator {
513
521
  if (!fs.existsSync(this.config.storagePath)) {
514
522
  throw new Error('No persisted state found');
515
523
  }
524
+ const fileSize = fs.statSync(this.config.storagePath).size;
525
+ if (fileSize > 50 * 1024 * 1024) {
526
+ throw new Error(`EWC state file too large (${fileSize} bytes); refusing to load`);
527
+ }
516
528
  const content = fs.readFileSync(this.config.storagePath, 'utf-8');
517
529
  const state = JSON.parse(content);
518
530
  // Validate version
@@ -551,8 +563,12 @@ export class EWCConsolidator {
551
563
  this.patterns.set(id, pattern);
552
564
  }
553
565
  }
554
- // Restore history
555
- this.consolidationHistory = Array.isArray(state.consolidationHistory) ? state.consolidationHistory : [];
566
+ // Restore history — cap to last 100 entries so a crafted state file cannot
567
+ // bloat the in-memory array (each entry is a small object, but the array is
568
+ // summed on every getConsolidationStats() call which is O(n)).
569
+ this.consolidationHistory = Array.isArray(state.consolidationHistory)
570
+ ? state.consolidationHistory.slice(-100)
571
+ : [];
556
572
  // Update config from persisted values, clamped to a sensible range to
557
573
  // prevent negative/NaN lambda from inverting the regularization sign.
558
574
  if (state.config && typeof state.config.lambda === 'number'
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @module v1/cli/intelligence
12
12
  */
13
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
13
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, renameSync } from 'node:fs';
14
14
  import { homedir } from 'node:os';
15
15
  import { join } from 'node:path';
16
16
  // ============================================================================
@@ -332,7 +332,7 @@ class LocalReasoningBank {
332
332
  loadFromDisk() {
333
333
  try {
334
334
  const path = getPatternsPath();
335
- if (existsSync(path)) {
335
+ if (existsSync(path) && statSync(path).size <= 50 * 1024 * 1024) {
336
336
  const data = JSON.parse(readFileSync(path, 'utf-8'));
337
337
  if (Array.isArray(data)) {
338
338
  // Validate each persisted pattern. The patterns file is part of the
@@ -393,9 +393,10 @@ class LocalReasoningBank {
393
393
  renameSync(tmp, path);
394
394
  this.dirty = false;
395
395
  }
396
- catch (error) {
396
+ catch {
397
397
  // Log but don't throw - persistence failures shouldn't break training
398
- console.error('Failed to persist patterns:', error);
398
+ // Do not reflect raw error to avoid leaking internal paths
399
+ console.error('Failed to persist patterns');
399
400
  }
400
401
  }
401
402
  /**
@@ -548,7 +549,7 @@ let globalStats = {
548
549
  function loadPersistedStats() {
549
550
  try {
550
551
  const path = getStatsPath();
551
- if (existsSync(path)) {
552
+ if (existsSync(path) && statSync(path).size <= 10 * 1024 * 1024) {
552
553
  const data = JSON.parse(readFileSync(path, 'utf-8'));
553
554
  if (data && typeof data === 'object') {
554
555
  globalStats.trajectoriesRecorded = data.trajectoriesRecorded ?? 0;
@@ -599,7 +600,7 @@ async function _doInitializeIntelligence(config) {
599
600
  // Seed neural learned patterns from @monomind/neural's LearningBridge flush.
600
601
  // This is the A→B bridge reader: connects the automatic learning loop to routing.
601
602
  const neuralPatternsPath = join(getDataDir(), 'patterns.json');
602
- if (existsSync(neuralPatternsPath)) {
603
+ if (existsSync(neuralPatternsPath) && statSync(neuralPatternsPath).size <= 50 * 1024 * 1024) {
603
604
  try {
604
605
  const { generateEmbedding: genEmb } = await import('./memory-initializer.js').catch(() => ({ generateEmbedding: null }));
605
606
  const raw = readFileSync(neuralPatternsPath, 'utf-8');
@@ -804,6 +805,10 @@ export async function recordTrajectory(steps, verdict) {
804
805
  }
805
806
  }
806
807
  export async function findSimilarPatterns(query, options) {
808
+ // Cap query length to prevent OOM via embedding generation on unbounded input
809
+ if (typeof query === 'string' && query.length > 2000) {
810
+ query = query.slice(0, 2000);
811
+ }
807
812
  if (!reasoningBank) {
808
813
  const init = await initializeIntelligence();
809
814
  if (!init.success)
@@ -943,9 +948,11 @@ export function benchmarkAdaptation(iterations = 1000) {
943
948
  if (!sonaCoordinator) {
944
949
  return { totalMs: 0, avgMs: 0, minMs: 0, maxMs: 0, targetMet: false };
945
950
  }
951
+ // Cap iterations to prevent OOM/CPU exhaustion from unbounded caller input
952
+ const safeIterations = Math.min(Math.max(1, iterations >>> 0), 100_000);
946
953
  const times = [];
947
954
  const testEmbedding = Array.from({ length: 384 }, () => Math.random());
948
- for (let i = 0; i < iterations; i++) {
955
+ for (let i = 0; i < safeIterations; i++) {
949
956
  const start = performance.now();
950
957
  sonaCoordinator.recordSignal({
951
958
  type: 'test',
@@ -956,7 +963,7 @@ export function benchmarkAdaptation(iterations = 1000) {
956
963
  times.push(performance.now() - start);
957
964
  }
958
965
  const totalMs = times.reduce((a, b) => a + b, 0);
959
- const avgMs = totalMs / iterations;
966
+ const avgMs = totalMs / safeIterations;
960
967
  const minMs = Math.min(...times);
961
968
  const maxMs = Math.max(...times);
962
969
  return {
@@ -980,21 +987,75 @@ function loadSonaRoutingPatterns() {
980
987
  const sonaPath = join(process.cwd(), '.swarm', 'sona-patterns.json');
981
988
  if (!existsSync(sonaPath))
982
989
  return [];
990
+ if (statSync(sonaPath).size > 10 * 1024 * 1024)
991
+ return [];
983
992
  const raw = JSON.parse(readFileSync(sonaPath, 'utf-8'));
984
993
  const persisted = raw;
985
- if (!persisted.patterns || typeof persisted.patterns !== 'object')
994
+ if (!persisted.patterns || typeof persisted.patterns !== 'object' || Array.isArray(persisted.patterns))
986
995
  return [];
987
996
  const now = Date.now();
988
- return Object.entries(persisted.patterns).map(([key, p]) => ({
989
- id: `sona:${key}`,
990
- type: p.agent ?? 'routing',
991
- content: (p.keywords ?? [key]).join(' '),
992
- confidence: p.confidence ?? 0.5,
993
- usageCount: (p.successCount ?? 0) + (p.failureCount ?? 0),
994
- embedding: [],
995
- createdAt: p.createdAt ?? now,
996
- lastUsedAt: now,
997
- }));
997
+ const results = [];
998
+ // Cap total entries to prevent DoS via an unbounded patterns map.
999
+ // sona-patterns.json is written by the SONA optimizer but could be
1000
+ // replaced by a malicious IPFS bundle — validate every field before use.
1001
+ const MAX_SONA_ENTRIES = 500;
1002
+ let entryCount = 0;
1003
+ for (const [key, p] of Object.entries(persisted.patterns)) {
1004
+ // Prototype pollution guard — skip __proto__ / constructor / prototype keys.
1005
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype')
1006
+ continue;
1007
+ if (typeof key !== 'string' || key.length === 0 || key.length > 256)
1008
+ continue;
1009
+ if (entryCount++ >= MAX_SONA_ENTRIES)
1010
+ break;
1011
+ if (!p || typeof p !== 'object')
1012
+ continue;
1013
+ // Validate keywords: must be a bounded array of short strings.
1014
+ const rawKw = p.keywords;
1015
+ let keywords;
1016
+ if (rawKw === undefined) {
1017
+ keywords = [key];
1018
+ }
1019
+ else if (Array.isArray(rawKw) && rawKw.length <= 64
1020
+ && rawKw.every(k => typeof k === 'string' && k.length > 0 && k.length <= 128)) {
1021
+ keywords = rawKw;
1022
+ }
1023
+ else {
1024
+ continue; // malformed keywords — skip entry
1025
+ }
1026
+ // Validate agent string.
1027
+ const agent = p.agent;
1028
+ if (agent !== undefined && (typeof agent !== 'string' || agent.length === 0 || agent.length > 128))
1029
+ continue;
1030
+ // Validate confidence is a finite number in [0, 1].
1031
+ const rawConf = p.confidence;
1032
+ const confidence = rawConf !== undefined ? rawConf : 0.5;
1033
+ if (typeof confidence !== 'number' || !Number.isFinite(confidence) || confidence < 0 || confidence > 1)
1034
+ continue;
1035
+ // Validate usage counts are safe integers.
1036
+ const sc = p.successCount ?? 0;
1037
+ const fc = p.failureCount ?? 0;
1038
+ if (typeof sc !== 'number' || !Number.isFinite(sc) || sc < 0 || sc > 1e9)
1039
+ continue;
1040
+ if (typeof fc !== 'number' || !Number.isFinite(fc) || fc < 0 || fc > 1e9)
1041
+ continue;
1042
+ // Validate createdAt is a reasonable epoch ms value.
1043
+ const rawTs = p.createdAt;
1044
+ const createdAt = (rawTs !== undefined && typeof rawTs === 'number' && Number.isFinite(rawTs) && rawTs >= 0 && rawTs <= 9.9e12)
1045
+ ? rawTs
1046
+ : now;
1047
+ results.push({
1048
+ id: `sona:${key}`,
1049
+ type: agent ?? 'routing',
1050
+ content: keywords.join(' '),
1051
+ confidence,
1052
+ usageCount: sc + fc,
1053
+ embedding: [],
1054
+ createdAt,
1055
+ lastUsedAt: now,
1056
+ });
1057
+ }
1058
+ return results;
998
1059
  }
999
1060
  catch {
1000
1061
  return [];
@@ -404,8 +404,12 @@ async function getOrBuildHnswIndex(db) {
404
404
  _hnswIndexBuilt = true; // Lock prevents concurrent re-build
405
405
  try {
406
406
  const memPkg = await import('@monoes/memory');
407
- if (!memPkg?.HNSWIndex)
407
+ if (!memPkg?.HNSWIndex) {
408
+ // Release the lock so a later retry can succeed if the package becomes available.
409
+ _hnswIndexBuilt = false;
410
+ _hnswBuildFailedAt = Date.now();
408
411
  return null;
412
+ }
409
413
  const rows = db.prepare(`SELECT id, embedding FROM memory_entries WHERE status = 'active' AND (expires_at IS NULL OR expires_at > ?) AND embedding IS NOT NULL`).all(Date.now());
410
414
  const valid = [];
411
415
  for (const row of rows) {
@@ -447,7 +451,22 @@ export async function bridgeStoreEntry(options) {
447
451
  if (!ctx)
448
452
  return null;
449
453
  try {
450
- const { key, value, namespace = 'default', tags: rawTags = [], ttl } = options;
454
+ const rawKey = options.key;
455
+ const rawValue = options.value;
456
+ // SECURITY: defensive caps so no caller — current or future — can pass an
457
+ // unbounded string directly into embedder.embed (hash fallback is O(n)) or
458
+ // inflate the AgentDB row beyond practical limits.
459
+ // Memory-tools already validates to 1 MB; these caps are a last-resort
460
+ // backstop for any internal caller that forgets to pre-truncate.
461
+ const BRIDGE_MAX_KEY_LEN = 4 * 1024; // 4 KB — generous for any realistic key
462
+ const BRIDGE_MAX_VALUE_LEN = 1024 * 1024; // 1 MB — matches memory-tools validator
463
+ const key = typeof rawKey === 'string' && rawKey.length > BRIDGE_MAX_KEY_LEN
464
+ ? rawKey.slice(0, BRIDGE_MAX_KEY_LEN) : rawKey;
465
+ const value = typeof rawValue === 'string' && rawValue.length > BRIDGE_MAX_VALUE_LEN
466
+ ? rawValue.slice(0, BRIDGE_MAX_VALUE_LEN) : rawValue;
467
+ const namespace = options.namespace ?? 'default';
468
+ const rawTags = options.tags ?? [];
469
+ const ttl = options.ttl;
451
470
  // SECURITY: cap tags array length and per-tag length. Without these, any
452
471
  // memory_store caller (every spawned agent) could submit
453
472
  // tags: new Array(1e5).fill("x".repeat(1e4)) → ~1GB persisted blob,