@amoree-code/ocean 0.0.0-stage → 1.2.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 (300) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -2
  3. package/dist/application/auth/auth-orchestrator.js +77 -0
  4. package/dist/application/clients/clients.js +646 -0
  5. package/dist/application/clients/editors.js +398 -0
  6. package/dist/application/clients/hooks.js +335 -0
  7. package/dist/application/clients/native.js +410 -0
  8. package/dist/application/clients/opencode-plugin.js +158 -0
  9. package/dist/application/clients/registry.js +188 -0
  10. package/dist/application/gateway/webhook-gateway.js +137 -0
  11. package/dist/application/install/provider-installer.js +184 -0
  12. package/dist/application/integrations/charter-copies.js +76 -0
  13. package/dist/application/integrations/claude-vscode.js +106 -0
  14. package/dist/application/integrations/engine-entry-repair.js +105 -0
  15. package/dist/application/integrations/skill-filter.js +254 -0
  16. package/dist/application/layout/layout-apply.js +443 -0
  17. package/dist/application/layout/layout-plan.js +236 -0
  18. package/dist/application/layout/workspace-template.js +34 -0
  19. package/dist/application/onboarding/state.js +23 -0
  20. package/dist/application/onboarding/steps.js +249 -0
  21. package/dist/application/profiles/profile-distribution.js +53 -0
  22. package/dist/application/profiles/profile-suggestion.js +39 -0
  23. package/dist/application/profiles/profile-upgrade-service.js +208 -0
  24. package/dist/application/pull/history-index.js +476 -0
  25. package/dist/application/pull/project-match.js +132 -0
  26. package/dist/application/pull/pull.js +202 -0
  27. package/dist/application/pull/server-capture.js +307 -0
  28. package/dist/application/scheduler/local-scheduler.js +210 -0
  29. package/dist/composition/runtime.js +64 -0
  30. package/dist/engine-root.js +9 -0
  31. package/dist/infrastructure/filesystem/profile-loader.js +46 -0
  32. package/dist/infrastructure/filesystem/runtime-config-loader.js +29 -0
  33. package/dist/infrastructure/filesystem/skill-loader.js +84 -0
  34. package/dist/infrastructure/filesystem/workspace-capability.js +37 -0
  35. package/dist/infrastructure/observability/runtime-logger.js +16 -0
  36. package/dist/infrastructure/process/agent-tool-executor.js +99 -0
  37. package/dist/infrastructure/process/cli-process.js +49 -0
  38. package/dist/infrastructure/process/interactive-process.js +145 -0
  39. package/dist/infrastructure/process/process-supervisor.js +194 -0
  40. package/dist/infrastructure/process/service.js +252 -0
  41. package/dist/infrastructure/providers/browser-provider.js +2 -0
  42. package/dist/infrastructure/providers/claude-sdk.js +137 -0
  43. package/dist/infrastructure/providers/client-home.js +14 -0
  44. package/dist/infrastructure/providers/fake-browser-provider.js +146 -0
  45. package/dist/infrastructure/providers/ollama-embedder.js +74 -0
  46. package/dist/infrastructure/providers/openai-compatible.js +268 -0
  47. package/dist/infrastructure/providers/playwright-browser-provider.js +380 -0
  48. package/dist/infrastructure/providers/provider-capabilities.js +50 -0
  49. package/dist/infrastructure/providers/provider-invocation.js +184 -0
  50. package/dist/infrastructure/providers/provider-registry.js +77 -0
  51. package/dist/infrastructure/providers/providers.js +134 -0
  52. package/dist/infrastructure/wrappers/wrapper-manager.js +374 -0
  53. package/dist/interfaces/cli/auth-command.js +54 -0
  54. package/dist/interfaces/cli/browser-bridge-command.js +93 -0
  55. package/dist/interfaces/cli/browser-command.js +119 -0
  56. package/dist/interfaces/cli/capture-command.js +125 -0
  57. package/dist/interfaces/cli/client-test-command.js +133 -0
  58. package/dist/interfaces/cli/clients-command.js +129 -0
  59. package/dist/interfaces/cli/command-registry.js +1211 -0
  60. package/dist/interfaces/cli/context-command.js +172 -0
  61. package/dist/interfaces/cli/daily-command.js +131 -0
  62. package/dist/interfaces/cli/governance-command.js +55 -0
  63. package/dist/interfaces/cli/handoff-command.js +35 -0
  64. package/dist/interfaces/cli/help-command.js +207 -0
  65. package/dist/interfaces/cli/idea-command.js +47 -0
  66. package/dist/interfaces/cli/intercept-command.js +317 -0
  67. package/dist/interfaces/cli/layout-command.js +27 -0
  68. package/dist/interfaces/cli/market-command.js +52 -0
  69. package/dist/interfaces/cli/mcp-gateway-command.js +30 -0
  70. package/dist/interfaces/cli/memory-command.js +317 -0
  71. package/dist/interfaces/cli/migrate-command.js +26 -0
  72. package/dist/interfaces/cli/observe-command.js +78 -0
  73. package/dist/interfaces/cli/onboard-command.js +123 -0
  74. package/dist/interfaces/cli/operate-command.js +59 -0
  75. package/dist/interfaces/cli/profiles-command.js +70 -0
  76. package/dist/interfaces/cli/pull-command.js +54 -0
  77. package/dist/interfaces/cli/setup-command.js +143 -0
  78. package/dist/interfaces/cli/tasks-command.js +249 -0
  79. package/dist/main.js +49 -0
  80. package/dist/node-warnings.js +17 -0
  81. package/dist/version.js +8 -0
  82. package/market/mcp/chrome-devtools/ocean.capability.json +28 -0
  83. package/market/mcp/gateway/ocean.capability.json +21 -0
  84. package/market/mcp/obsidian/ocean.capability.json +21 -0
  85. package/market/mcp/ocean/ocean.capability.json +21 -0
  86. package/market/mcp/playwright/ocean.capability.json +23 -0
  87. package/node_modules/@ocean/brain/dist/brain/brain-eval.js +29 -0
  88. package/node_modules/@ocean/brain/dist/brain/brain-markdown.js +226 -0
  89. package/node_modules/@ocean/brain/dist/brain/brain-reindex.js +247 -0
  90. package/node_modules/@ocean/brain/dist/brain/brain-service.js +320 -0
  91. package/node_modules/@ocean/brain/dist/capture/inbox-sync.js +22 -0
  92. package/node_modules/@ocean/brain/dist/evidence/verification-report.js +13 -0
  93. package/node_modules/@ocean/brain/dist/infrastructure/brain/brain-index.js +285 -0
  94. package/node_modules/@ocean/brain/dist/memory/brain-dump.js +145 -0
  95. package/node_modules/@ocean/brain/dist/memory/daily-narrative.js +142 -0
  96. package/node_modules/@ocean/brain/dist/memory/index-sync.js +124 -0
  97. package/node_modules/@ocean/brain/dist/memory/lesson-activation.js +93 -0
  98. package/node_modules/@ocean/brain/dist/memory/lesson-learning.js +188 -0
  99. package/node_modules/@ocean/brain/dist/memory/lessons.js +321 -0
  100. package/node_modules/@ocean/brain/dist/memory/memory-audit.js +100 -0
  101. package/node_modules/@ocean/brain/dist/memory/memory-digest.js +49 -0
  102. package/node_modules/@ocean/brain/dist/memory/memory-harvest.js +345 -0
  103. package/node_modules/@ocean/brain/dist/memory/memory-paths.js +174 -0
  104. package/node_modules/@ocean/brain/dist/memory/memory-review.js +278 -0
  105. package/node_modules/@ocean/brain/dist/memory/memory-trash.js +152 -0
  106. package/node_modules/@ocean/brain/dist/memory/memory-write.js +216 -0
  107. package/node_modules/@ocean/brain/dist/memory/model-narrative.js +129 -0
  108. package/node_modules/@ocean/brain/dist/memory/profile-facts.js +103 -0
  109. package/node_modules/@ocean/brain/dist/memory/session-promotion.js +61 -0
  110. package/node_modules/@ocean/brain/dist/memory/session-summary.js +113 -0
  111. package/node_modules/@ocean/brain/package.json +11 -0
  112. package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
  113. package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
  114. package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
  115. package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
  116. package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
  117. package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
  118. package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
  119. package/node_modules/@ocean/context/package.json +11 -0
  120. package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
  121. package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
  122. package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
  123. package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
  124. package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
  125. package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
  126. package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
  127. package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
  128. package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
  129. package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
  130. package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
  131. package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
  132. package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
  133. package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
  134. package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
  135. package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
  136. package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
  137. package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
  138. package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
  139. package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
  140. package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
  141. package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
  142. package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
  143. package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
  144. package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
  145. package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
  146. package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
  147. package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
  148. package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
  149. package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
  150. package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
  151. package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
  152. package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
  153. package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
  154. package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
  155. package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
  156. package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
  157. package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
  158. package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
  159. package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
  160. package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
  161. package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
  162. package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
  163. package/node_modules/@ocean/core/package.json +14 -0
  164. package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
  165. package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
  166. package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
  167. package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
  168. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
  169. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
  170. package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
  171. package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
  172. package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
  173. package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
  174. package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
  175. package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
  176. package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
  177. package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
  178. package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
  179. package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
  180. package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
  181. package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
  182. package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
  183. package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
  184. package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
  185. package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
  186. package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
  187. package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
  188. package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
  189. package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
  190. package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
  191. package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
  192. package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
  193. package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
  194. package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
  195. package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
  196. package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
  197. package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
  198. package/node_modules/@ocean/loop/package.json +11 -0
  199. package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
  200. package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
  201. package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
  202. package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
  203. package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
  204. package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
  205. package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
  206. package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
  207. package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
  208. package/node_modules/@ocean/market/dist/install/lock.js +34 -0
  209. package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
  210. package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
  211. package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
  212. package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
  213. package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
  214. package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
  215. package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
  216. package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
  217. package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
  218. package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
  219. package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
  220. package/node_modules/@ocean/market/package.json +11 -0
  221. package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
  222. package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
  223. package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
  224. package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
  225. package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
  226. package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
  227. package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
  228. package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
  229. package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
  230. package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
  231. package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
  232. package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
  233. package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
  234. package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
  235. package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
  236. package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
  237. package/node_modules/@ocean/services/package.json +11 -0
  238. package/ocean-marketplace.json +415 -0
  239. package/package.json +67 -4
  240. package/skills/core/business-logic/SKILL.md +22 -0
  241. package/skills/core/catch-up/SKILL.md +17 -0
  242. package/skills/core/core-thinking/SKILL.md +11 -0
  243. package/skills/core/design-thinking/SKILL.md +19 -0
  244. package/skills/core/graft/SKILL.md +156 -0
  245. package/skills/core/loop/SKILL.md +20 -0
  246. package/skills/core/session-handoff/SKILL.md +15 -0
  247. package/skills/core/use-browser/SKILL.md +69 -0
  248. package/skills/core/verification/SKILL.md +11 -0
  249. package/skills/index.json +56 -0
  250. package/templates/README.md +4 -0
  251. package/templates/profiles/default.json +51 -0
  252. package/templates/profiles/developer.json +51 -0
  253. package/templates/profiles/reviewer.json +51 -0
  254. package/templates/profiles/strategist.json +51 -0
  255. package/templates/workspace/00-inbox/INBOX.md +7 -0
  256. package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
  257. package/templates/workspace/00-inbox/native/.gitkeep +0 -0
  258. package/templates/workspace/01-daily/.gitkeep +0 -0
  259. package/templates/workspace/02-personal/CONFLICTS.md +7 -0
  260. package/templates/workspace/02-personal/MEMORY.md +15 -0
  261. package/templates/workspace/02-personal/goals/.gitkeep +0 -0
  262. package/templates/workspace/02-personal/identity/.gitkeep +0 -0
  263. package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
  264. package/templates/workspace/03-professional/.gitkeep +0 -0
  265. package/templates/workspace/04-projects/PROJECTS.md +5 -0
  266. package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
  267. package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
  268. package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
  269. package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
  270. package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
  271. package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
  272. package/templates/workspace/06-templates/TEMPLATES.md +25 -0
  273. package/templates/workspace/06-templates/decision.md +8 -0
  274. package/templates/workspace/06-templates/intelligence.md +32 -0
  275. package/templates/workspace/06-templates/project-claude.md +48 -0
  276. package/templates/workspace/06-templates/session.md +38 -0
  277. package/templates/workspace/06-templates/task.md +42 -0
  278. package/templates/workspace/MANIFEST.json +51 -0
  279. package/templates/workspace/bridge/config/CONFIG.md +21 -0
  280. package/templates/workspace/bridge/config/runtime.json +4 -0
  281. package/templates/workspace/charter/core.md +104 -0
  282. package/templates/workspace/charter/policies/README.md +31 -0
  283. package/templates/workspace/charter/policies/claude-code-context.md +90 -0
  284. package/templates/workspace/charter/policies/concise-output.md +29 -0
  285. package/templates/workspace/charter/policies/context.md +91 -0
  286. package/templates/workspace/charter/policies/contracts/task.md +189 -0
  287. package/templates/workspace/charter/policies/git.md +60 -0
  288. package/templates/workspace/charter/policies/graph.md +47 -0
  289. package/templates/workspace/charter/policies/knowledge.md +33 -0
  290. package/templates/workspace/charter/policies/lifecycle.md +119 -0
  291. package/templates/workspace/charter/policies/memory.md +67 -0
  292. package/templates/workspace/charter/policies/minimal-change.md +43 -0
  293. package/templates/workspace/charter/policies/models.md +57 -0
  294. package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
  295. package/templates/workspace/charter/policies/response.md +39 -0
  296. package/templates/workspace/charter/policies/skill-observer.md +44 -0
  297. package/templates/workspace/charter/policies/stack.md +23 -0
  298. package/templates/workspace/charter/policies/task.md +139 -0
  299. package/templates/workspace/charter/policies/verification.md +29 -0
  300. package/templates/workspace/sessions/.gitkeep +0 -0
@@ -0,0 +1,880 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { brainNeighbors, brainRead, brainSearch, } from "@ocean/brain/brain/brain-service.js";
4
+ import { memorySettings } from "@ocean/brain/memory/memory-audit.js";
5
+ import { fileProposal, } from "@ocean/brain/memory/memory-review.js";
6
+ import { deleteMemoryRecord, memoryHistory, restoreMemory, } from "@ocean/brain/memory/memory-trash.js";
7
+ import { proposeMemory, readMemoryRecord, writeMemoryRecord, } from "@ocean/brain/memory/memory-write.js";
8
+ import { promoteSessionToKnowledge } from "@ocean/brain/memory/session-promotion.js";
9
+ import { isValidProjectId } from "@ocean/core/domain/context/project-id.js";
10
+ import { actionFingerprint, mcpApprovalSchema, } from "@ocean/core/domain/mcp/mcp-contract.js";
11
+ import { MEMORY_CONTRACT_VERSION, MEMORY_INSTRUCTIONS, MemoryContractError, } from "@ocean/core/domain/mcp/memory-contract.js";
12
+ import { redactSecrets } from "@ocean/core/domain/redaction/redaction.js";
13
+ import { oceanPath, oceanRoot, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
14
+ import { findProject, folderProject, scanProjectManifests, } from "@ocean/core/shared/project-resolution.js";
15
+ import { createHandoff, getHandoff, getTask, listHandoffs, } from "@ocean/loop/handoff/handoff-service.js";
16
+ import { oceanContext } from "@ocean/loop/hooks/session-start-hook.js";
17
+ import { openSessionStoreReadOnly } from "@ocean/loop/infrastructure/persistence/session-store.js";
18
+ import { listTasksForFolder } from "@ocean/loop/tasks/list-tasks.js";
19
+ import { hasFailures, workspaceReport } from "../../doctor/workspace-doctor.js";
20
+ import { recordMcpCall } from "./call-ledger.js";
21
+ import { serverVersion } from "./server-version.js";
22
+ import { McpToolRegistry } from "./tool-registry.js";
23
+ const tools = new McpToolRegistry([
24
+ {
25
+ name: "ocean_status",
26
+ description: "Return the Ocean runtime and workspace status.",
27
+ annotations: { readOnlyHint: true },
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {},
31
+ additionalProperties: false,
32
+ },
33
+ },
34
+ {
35
+ name: "ocean_doctor",
36
+ description: "Run the read-only Ocean workspace health checks.",
37
+ annotations: { readOnlyHint: true },
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {},
41
+ additionalProperties: false,
42
+ },
43
+ },
44
+ {
45
+ name: "ocean_profiles_list",
46
+ description: "List available Ocean profiles.",
47
+ annotations: { readOnlyHint: true },
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {},
51
+ additionalProperties: false,
52
+ },
53
+ },
54
+ {
55
+ name: "ocean_tasks_list",
56
+ description: "List Ocean tasks for the folder being worked in: that project's tasks, or every project's (labelled) when it has none or the folder is no project. Optionally filtered by state.",
57
+ annotations: { readOnlyHint: true },
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ state: { type: "string" },
62
+ project: { type: "string" },
63
+ cwd: {
64
+ type: "string",
65
+ description: "The folder the user is working in; defaults to the server's working directory.",
66
+ },
67
+ },
68
+ additionalProperties: false,
69
+ },
70
+ },
71
+ {
72
+ name: "ocean_task_get",
73
+ description: "Read one bounded Ocean task record.",
74
+ annotations: { readOnlyHint: true },
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ taskId: { type: "string" },
79
+ project: { type: "string" },
80
+ cwd: { type: "string" },
81
+ },
82
+ required: ["taskId"],
83
+ additionalProperties: false,
84
+ },
85
+ },
86
+ {
87
+ name: "ocean_handoffs_list",
88
+ description: "List compact cross-client session handoffs.",
89
+ annotations: { readOnlyHint: true },
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: { taskId: { type: "string" }, project: { type: "string" } },
93
+ additionalProperties: false,
94
+ },
95
+ },
96
+ {
97
+ name: "ocean_handoff_get",
98
+ description: "Read one bounded cross-client session handoff.",
99
+ annotations: { readOnlyHint: true },
100
+ inputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ handoffId: { type: "string" },
104
+ maxBytes: { type: "number" },
105
+ },
106
+ required: ["handoffId"],
107
+ additionalProperties: false,
108
+ },
109
+ },
110
+ {
111
+ name: "ocean_session_get",
112
+ description: "Read one session metadata record without its transcript.",
113
+ annotations: { readOnlyHint: true },
114
+ inputSchema: {
115
+ type: "object",
116
+ properties: { sessionId: { type: "string" } },
117
+ required: ["sessionId"],
118
+ additionalProperties: false,
119
+ },
120
+ },
121
+ {
122
+ name: "ocean_session_summary",
123
+ description: "Read one bounded human-readable session summary.",
124
+ annotations: { readOnlyHint: true },
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ sessionId: { type: "string" },
129
+ maxBytes: { type: "number" },
130
+ },
131
+ required: ["sessionId"],
132
+ additionalProperties: false,
133
+ },
134
+ },
135
+ {
136
+ name: "ocean_session_events",
137
+ description: "Read one bounded session event log explicitly requested by id.",
138
+ annotations: { readOnlyHint: true },
139
+ inputSchema: {
140
+ type: "object",
141
+ properties: {
142
+ sessionId: { type: "string" },
143
+ maxEvents: { type: "number" },
144
+ },
145
+ required: ["sessionId"],
146
+ additionalProperties: false,
147
+ },
148
+ },
149
+ {
150
+ name: "ocean_session_promote",
151
+ description: "Promote a completed session result into reviewed Ocean knowledge.",
152
+ annotations: { readOnlyHint: false },
153
+ inputSchema: {
154
+ type: "object",
155
+ properties: {
156
+ sessionId: { type: "string" },
157
+ target: { type: "string" },
158
+ approval: { type: "object" },
159
+ },
160
+ required: ["sessionId", "approval"],
161
+ additionalProperties: false,
162
+ },
163
+ },
164
+ {
165
+ name: "brain_search",
166
+ description: "Call this first, before answering anything about the user, their projects, preferences or past decisions: Ocean is their memory. Searches the markdown memory/knowledge index (hybrid FTS + vector when available); read only the results you need with brain_read and cite their paths. In a conversation about one project, pass its name or id as project: the search then covers that project and global records only. Otherwise the project bound to `cwd`; unbound sessions see global only.",
167
+ annotations: { readOnlyHint: true },
168
+ inputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ query: { type: "string" },
172
+ limit: { type: "number" },
173
+ types: { type: "array", items: { type: "string" } },
174
+ project: { type: "string" },
175
+ cwd: { type: "string" },
176
+ },
177
+ required: ["query"],
178
+ additionalProperties: false,
179
+ },
180
+ },
181
+ {
182
+ name: "brain_read",
183
+ description: "Read one markdown memory/knowledge record by id or path.",
184
+ annotations: { readOnlyHint: true },
185
+ inputSchema: {
186
+ type: "object",
187
+ properties: {
188
+ id: { type: "string" },
189
+ path: { type: "string" },
190
+ maxBytes: { type: "number" },
191
+ },
192
+ additionalProperties: false,
193
+ },
194
+ },
195
+ {
196
+ name: "brain_neighbors",
197
+ description: "Walk the wikilink graph around one memory/knowledge record.",
198
+ annotations: { readOnlyHint: true },
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ id: { type: "string" },
203
+ depth: { type: "number" },
204
+ direction: { type: "string", enum: ["out", "in", "both"] },
205
+ limit: { type: "number" },
206
+ project: { type: "string" },
207
+ cwd: { type: "string" },
208
+ },
209
+ required: ["id"],
210
+ additionalProperties: false,
211
+ },
212
+ },
213
+ {
214
+ name: "ocean_ping",
215
+ description: "Cheap health check: answers when the server is up.",
216
+ annotations: { readOnlyHint: true },
217
+ inputSchema: {
218
+ type: "object",
219
+ properties: {},
220
+ additionalProperties: false,
221
+ },
222
+ },
223
+ {
224
+ name: "ocean_capabilities",
225
+ description: "The memory contract version, the tool names and whether memory writes are enabled.",
226
+ annotations: { readOnlyHint: true },
227
+ inputSchema: {
228
+ type: "object",
229
+ properties: {},
230
+ additionalProperties: false,
231
+ },
232
+ },
233
+ {
234
+ name: "memory_read",
235
+ description: "Read one memory record (02-personal/... or 04-projects/<project>/memory/...) with its sha256, which memory_write update, memory_delete and memory_restore need as expectedHash.",
236
+ annotations: { readOnlyHint: true },
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: { path: { type: "string" } },
240
+ required: ["path"],
241
+ additionalProperties: false,
242
+ },
243
+ },
244
+ {
245
+ name: "memory_history",
246
+ description: "Audit entries (newest first) for one record path, or for all records; ids feed memory_restore.",
247
+ annotations: { readOnlyHint: true },
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: { path: { type: "string" }, limit: { type: "number" } },
251
+ additionalProperties: false,
252
+ },
253
+ },
254
+ {
255
+ name: "memory_write",
256
+ description: "Save a fact the user explicitly asked to remember or change. Search first (brain_search): if a record covers it, memory_read it and write the merged record back with mode `update` and its sha256 as expectedHash; otherwise mode `create` in `02-personal/<section>/<name>.md` or `04-projects/<project>/memory/<name>.md`. Inferred or imported facts go to memory_propose instead. Never secrets or temporary state.",
257
+ annotations: { readOnlyHint: false, destructiveHint: true },
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {
261
+ path: { type: "string" },
262
+ content: { type: "string" },
263
+ mode: { type: "string", enum: ["create", "update"] },
264
+ expectedHash: { type: "string" },
265
+ allowSimilar: { type: "boolean" },
266
+ },
267
+ required: ["path", "content", "mode"],
268
+ additionalProperties: false,
269
+ },
270
+ },
271
+ {
272
+ name: "memory_propose",
273
+ description: "Call this when the user states something worth keeping across sessions (a decision, preference or project fact) without asking you to remember it, for a fact you inferred, or for one imported from another tool's memory: it queues it for the user's review. It is not memory until approved. In a conversation about one project, pass its name (as the user's Claude project is called) or Ocean id as project, and kind instead of target: `fact` (a decision, preference or detail) or `doc` (a spec, brief or longer notes), each with a kebab-case name, is filed in that project's memory/ or docs/ — written at once when the user allows it; `todo` (work to do) waits for review as a backlog line. Optionally say why (reason).",
274
+ annotations: { readOnlyHint: false, destructiveHint: false },
275
+ inputSchema: {
276
+ type: "object",
277
+ properties: {
278
+ content: { type: "string" },
279
+ origin: { type: "string", enum: ["inferred", "imported"] },
280
+ target: { type: "string" },
281
+ project: { type: "string" },
282
+ kind: { type: "string", enum: ["fact", "doc", "todo"] },
283
+ name: { type: "string" },
284
+ reason: { type: "string" },
285
+ },
286
+ required: ["content", "origin"],
287
+ additionalProperties: false,
288
+ },
289
+ },
290
+ {
291
+ name: "memory_delete",
292
+ description: "Soft-delete one record the user asked to remove: it moves to Ocean's trash and memory_restore brings it back. Needs the record's sha256 from memory_read as expectedHash.",
293
+ annotations: { readOnlyHint: false, destructiveHint: true },
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ path: { type: "string" },
298
+ expectedHash: { type: "string" },
299
+ },
300
+ required: ["path", "expectedHash"],
301
+ additionalProperties: false,
302
+ },
303
+ },
304
+ {
305
+ name: "memory_restore",
306
+ description: "Put back the content a memory_history entry preserved (a deleted record, or the version an update or restore replaced). When the record exists, pass its current sha256 as expectedHash.",
307
+ annotations: { readOnlyHint: false, destructiveHint: true },
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: { id: { type: "string" }, expectedHash: { type: "string" } },
311
+ required: ["id"],
312
+ additionalProperties: false,
313
+ },
314
+ },
315
+ {
316
+ name: "ocean_context",
317
+ description: "Call this once at the start of a conversation when no Ocean start line is already in your context (clients without a session-start hook). Returns the bounded Ocean start line (256 bytes or less: the project bound to cwd and the operations available). Holds no records; search for those.",
318
+ annotations: { readOnlyHint: true },
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: { cwd: { type: "string" } },
322
+ additionalProperties: false,
323
+ },
324
+ },
325
+ {
326
+ name: "ocean_projects_list",
327
+ description: "List the user's Ocean projects (id and name only).",
328
+ annotations: { readOnlyHint: true },
329
+ inputSchema: {
330
+ type: "object",
331
+ properties: {},
332
+ additionalProperties: false,
333
+ },
334
+ },
335
+ {
336
+ name: "ocean_handoff_create",
337
+ description: "Create a cross-client session handoff, only when the user explicitly asks for one: a short summary and the next action (4 KB at most in total; secrets are redacted). It always creates a new handoff and never changes an existing one; another client reads it with ocean_handoffs_list and ocean_handoff_get.",
338
+ annotations: { readOnlyHint: false, destructiveHint: false },
339
+ inputSchema: {
340
+ type: "object",
341
+ properties: {
342
+ summary: { type: "string" },
343
+ nextAction: { type: "string" },
344
+ title: { type: "string" },
345
+ taskId: { type: "string" },
346
+ project: { type: "string" },
347
+ },
348
+ required: ["summary", "nextAction"],
349
+ additionalProperties: false,
350
+ },
351
+ },
352
+ ]);
353
+ // What one MCP handoff may hold, summed over its texts (T-307).
354
+ export const HANDOFF_MAX_BYTES = 4096;
355
+ const resources = [
356
+ {
357
+ uri: "ocean://status",
358
+ name: "Ocean status",
359
+ description: "Current Ocean workspace health.",
360
+ },
361
+ {
362
+ uri: "ocean://profiles",
363
+ name: "Ocean profiles",
364
+ description: "Available Ocean role profiles.",
365
+ },
366
+ {
367
+ uri: "ocean://tasks",
368
+ name: "Ocean tasks",
369
+ description: "Current Ocean tasks.",
370
+ },
371
+ {
372
+ uri: "ocean://handoffs",
373
+ name: "Ocean handoffs",
374
+ description: "Compact cross-client session handoffs.",
375
+ },
376
+ ];
377
+ const prompts = [
378
+ {
379
+ name: "ocean_review_workspace",
380
+ description: "Review Ocean workspace health and summarize actionable findings.",
381
+ arguments: [],
382
+ },
383
+ {
384
+ name: "ocean_review_task",
385
+ description: "Review one Ocean task and identify its next verified action.",
386
+ arguments: [
387
+ { name: "task", description: "Task identifier", required: true },
388
+ ],
389
+ },
390
+ ];
391
+ function textResult(value) {
392
+ // A string result (ocean_context) is sent as is, so its byte budget holds on the wire.
393
+ if (typeof value === "string")
394
+ return {
395
+ content: [{ type: "text", text: value }],
396
+ structuredContent: { text: value },
397
+ };
398
+ return {
399
+ content: [{ type: "text", text: JSON.stringify(value) }],
400
+ structuredContent: value,
401
+ };
402
+ }
403
+ async function profiles() {
404
+ try {
405
+ return (await readdir(oceanPath(SYSTEM_DIR, "profiles"), { withFileTypes: true }))
406
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
407
+ .map((entry) => entry.name.slice(0, -5))
408
+ .sort();
409
+ }
410
+ catch (error) {
411
+ if (error.code === "ENOENT")
412
+ return [];
413
+ throw error;
414
+ }
415
+ }
416
+ const MUTATING_TOOLS = new Set([
417
+ "memory_write",
418
+ "memory_propose",
419
+ "memory_delete",
420
+ "memory_restore",
421
+ ]);
422
+ // The connected client's name from `initialize` (one stdio server per client), recorded as the
423
+ // source of every memory change it makes.
424
+ let clientName = "unknown";
425
+ async function callTool(name, args, ports) {
426
+ if (name === "ocean_status")
427
+ return {
428
+ name: "Ocean",
429
+ version: await serverVersion(),
430
+ workspace: oceanPath(),
431
+ mcp: "stdio",
432
+ };
433
+ if (name === "ocean_doctor") {
434
+ const report = await workspaceReport(ports.wrapperManager);
435
+ return {
436
+ healthy: !hasFailures(report.findings),
437
+ findings: report.findings,
438
+ };
439
+ }
440
+ if (name === "ocean_profiles_list")
441
+ return { profiles: await profiles() };
442
+ if (name === "ocean_tasks_list") {
443
+ const text = (key) => typeof args[key] === "string" && args[key] ? args[key] : undefined;
444
+ return await listTasksForFolder({
445
+ cwd: text("cwd") ?? process.cwd(),
446
+ project: text("project"),
447
+ state: text("state"),
448
+ });
449
+ }
450
+ if (name === "ocean_task_get")
451
+ return await getTask(requiredArgument(args, "taskId"), {
452
+ projectId: typeof args.project === "string" ? args.project : undefined,
453
+ explicit: typeof args.project === "string",
454
+ cwd: typeof args.cwd === "string" ? args.cwd : undefined,
455
+ });
456
+ if (name === "ocean_handoffs_list")
457
+ return {
458
+ handoffs: await listHandoffs(ports.sessionStoreFactory, typeof args.taskId === "string" ? args.taskId : undefined, typeof args.project === "string" && args.project
459
+ ? args.project
460
+ : undefined),
461
+ };
462
+ if (name === "ocean_handoff_get")
463
+ return await getHandoff(requiredArgument(args, "handoffId"), ports.sessionStoreFactory, typeof args.maxBytes === "number"
464
+ ? Math.min(16_000, Math.max(512, args.maxBytes))
465
+ : 8_000);
466
+ if (name === "ocean_session_get") {
467
+ const store = await openSessionStoreReadOnly();
468
+ try {
469
+ const session = store.get(requiredArgument(args, "sessionId"));
470
+ if (!session)
471
+ throw new Error("Session not found");
472
+ return session;
473
+ }
474
+ finally {
475
+ store.close();
476
+ }
477
+ }
478
+ if (name === "ocean_session_summary") {
479
+ const store = await openSessionStoreReadOnly();
480
+ try {
481
+ const session = store.get(requiredArgument(args, "sessionId"));
482
+ if (!session)
483
+ throw new Error("Session not found");
484
+ if (!session.summaryPath)
485
+ throw new Error("Session summary not available");
486
+ const summaryPath = path.resolve(oceanRoot(), session.summaryPath);
487
+ const root = `${path.resolve(oceanPath(SYSTEM_DIR, "sessions", "summaries"))}${path.sep}`;
488
+ if (!summaryPath.startsWith(root))
489
+ throw new Error("Session summary path is outside the Ocean summary directory");
490
+ const maxBytes = typeof args.maxBytes === "number"
491
+ ? Math.min(12_000, Math.max(512, args.maxBytes))
492
+ : 12_000;
493
+ return {
494
+ sessionId: session.sessionId,
495
+ summaryPath: session.summaryPath,
496
+ summary: (await readFile(summaryPath, "utf8")).slice(0, maxBytes),
497
+ };
498
+ }
499
+ finally {
500
+ store.close();
501
+ }
502
+ }
503
+ if (name === "ocean_session_events") {
504
+ const store = await openSessionStoreReadOnly();
505
+ try {
506
+ const events = store.listEvents(requiredArgument(args, "sessionId"));
507
+ const max = typeof args.maxEvents === "number"
508
+ ? Math.min(100, Math.max(1, args.maxEvents))
509
+ : 20;
510
+ return { events: events.slice(-max) };
511
+ }
512
+ finally {
513
+ store.close();
514
+ }
515
+ }
516
+ if (name === "ocean_session_promote") {
517
+ const approval = mcpApprovalSchema.parse(args.approval);
518
+ const actionArgs = {
519
+ sessionId: args.sessionId,
520
+ target: typeof args.target === "string" ? args.target : "knowledge/results",
521
+ };
522
+ if (approval.fingerprint !== actionFingerprint(name, actionArgs))
523
+ throw new Error("Session promotion approval does not match the requested action");
524
+ return promoteSessionToKnowledge(requiredArgument(args, "sessionId"), ports.sessionStoreFactory, typeof args.target === "string" ? args.target : "knowledge/results", true);
525
+ }
526
+ if (name === "brain_search")
527
+ return brainSearch({
528
+ scope: await scopeFor(args),
529
+ query: requiredArgument(args, "query"),
530
+ limit: typeof args.limit === "number"
531
+ ? Math.min(20, Math.max(1, args.limit))
532
+ : undefined,
533
+ types: Array.isArray(args.types)
534
+ ? args.types.filter((item) => typeof item === "string")
535
+ : undefined,
536
+ indexPort: ports.brainIndex,
537
+ embedder: ports.embedder ? await ports.embedder() : null,
538
+ });
539
+ if (name === "brain_read") {
540
+ const idOrPath = typeof args.id === "string" && args.id
541
+ ? args.id
542
+ : typeof args.path === "string" && args.path
543
+ ? args.path
544
+ : null;
545
+ if (!idOrPath)
546
+ throw new Error("brain_read requires id or path");
547
+ return brainRead({
548
+ idOrPath,
549
+ maxBytes: typeof args.maxBytes === "number"
550
+ ? Math.min(16_000, Math.max(512, args.maxBytes))
551
+ : undefined,
552
+ indexPort: ports.brainIndex,
553
+ });
554
+ }
555
+ if (name === "brain_neighbors")
556
+ return brainNeighbors({
557
+ scope: await scopeFor(args),
558
+ idOrPath: requiredArgument(args, "id"),
559
+ depth: args.depth === 2 ? 2 : 1,
560
+ direction: args.direction === "out" || args.direction === "in"
561
+ ? args.direction
562
+ : "both",
563
+ limit: typeof args.limit === "number"
564
+ ? Math.min(50, Math.max(1, args.limit))
565
+ : undefined,
566
+ indexPort: ports.brainIndex,
567
+ });
568
+ if (name === "ocean_ping")
569
+ return { ok: true };
570
+ if (name === "ocean_capabilities")
571
+ return {
572
+ contract: MEMORY_CONTRACT_VERSION,
573
+ tools: tools.list().map((tool) => tool.name),
574
+ writes: (await memorySettings()).mcpWrites,
575
+ };
576
+ if (name === "ocean_context")
577
+ return oceanContext(typeof args.cwd === "string" && args.cwd ? args.cwd : process.cwd());
578
+ if (name === "ocean_projects_list")
579
+ return {
580
+ projects: (await scanProjectManifests()).manifests
581
+ .map((project) => ({ id: project.id, name: project.name }))
582
+ .sort((a, b) => a.id.localeCompare(b.id)),
583
+ };
584
+ if (name === "ocean_handoff_create")
585
+ return createClientHandoff(args, ports);
586
+ if (name === "memory_read")
587
+ return readMemoryRecord(requiredArgument(args, "path"));
588
+ if (name === "memory_history")
589
+ return memoryHistory({
590
+ path: typeof args.path === "string" ? args.path : undefined,
591
+ limit: typeof args.limit === "number" ? args.limit : undefined,
592
+ });
593
+ if (MUTATING_TOOLS.has(name) && !(await memorySettings()).mcpWrites)
594
+ throw new MemoryContractError("WRITES_OFF", "memory writes over MCP are off (bridge/config/memory.json)");
595
+ const source = clientName;
596
+ const text = (key) => typeof args[key] === "string" ? args[key] : undefined;
597
+ if (name === "memory_write")
598
+ return writeMemoryRecord({
599
+ path: requiredArgument(args, "path"),
600
+ content: requiredArgument(args, "content"),
601
+ mode: args.mode,
602
+ expectedHash: text("expectedHash"),
603
+ allowSimilar: args.allowSimilar === true,
604
+ source,
605
+ });
606
+ if (name === "memory_propose" && (text("project") || text("kind"))) {
607
+ if (text("target"))
608
+ throw new MemoryContractError("INVALID", "pass target, or project and kind — not both");
609
+ return fileProposal({
610
+ content: requiredArgument(args, "content"),
611
+ origin: args.origin,
612
+ project: requiredArgument(args, "project"),
613
+ kind: requiredArgument(args, "kind"),
614
+ name: text("name"),
615
+ reason: text("reason"),
616
+ source,
617
+ });
618
+ }
619
+ if (name === "memory_propose")
620
+ return proposeMemory({
621
+ content: requiredArgument(args, "content"),
622
+ origin: args.origin,
623
+ target: text("target"),
624
+ reason: text("reason"),
625
+ source,
626
+ });
627
+ if (name === "memory_delete")
628
+ return deleteMemoryRecord({
629
+ path: requiredArgument(args, "path"),
630
+ expectedHash: requiredArgument(args, "expectedHash"),
631
+ source,
632
+ });
633
+ if (name === "memory_restore")
634
+ return restoreMemory({
635
+ id: requiredArgument(args, "id"),
636
+ expectedHash: text("expectedHash"),
637
+ source,
638
+ });
639
+ throw new Error(`Unknown MCP tool: ${name}`);
640
+ }
641
+ // A handoff a client writes on the user's request: bounded, redacted, always a new record.
642
+ async function createClientHandoff(args, ports) {
643
+ const text = (key) => typeof args[key] === "string" ? args[key].trim() : "";
644
+ const summary = requiredArgument(args, "summary");
645
+ const nextAction = requiredArgument(args, "nextAction");
646
+ const title = text("title");
647
+ const bytes = [summary, nextAction, title].reduce((sum, item) => sum + Buffer.byteLength(item, "utf8"), 0);
648
+ if (bytes > HANDOFF_MAX_BYTES)
649
+ throw new Error(`handoff is ${bytes} bytes; the limit is ${HANDOFF_MAX_BYTES} — shorten the summary`);
650
+ const project = text("project");
651
+ if (project && !isValidProjectId(project))
652
+ throw new Error(`invalid project id '${project}'`);
653
+ const clean = (value) => redactSecrets(value);
654
+ const task = text("taskId")
655
+ ? await getTask(text("taskId"), {
656
+ projectId: project || undefined,
657
+ explicit: Boolean(project),
658
+ })
659
+ : null;
660
+ const handoff = await createHandoff({
661
+ task,
662
+ title: title ? clean(title) : undefined,
663
+ content: clean(summary),
664
+ nextAction: clean(nextAction),
665
+ provider: clientName,
666
+ projectId: project || undefined,
667
+ }, ports.sessionStoreFactory);
668
+ return {
669
+ handoffId: handoff.handoffId,
670
+ taskId: handoff.taskId,
671
+ projectId: handoff.projectId,
672
+ provider: handoff.provider,
673
+ redacted: [summary, nextAction, title].some((item) => clean(item) !== item),
674
+ };
675
+ }
676
+ // Memory visibility for a tool call: an explicit project, else the one bound to the cwd.
677
+ async function scopeFor(args) {
678
+ // A project named by its id or its name (T-307: a Claude project's name finds its Ocean
679
+ // project); a name Ocean does not know searches global records only.
680
+ if (typeof args.project === "string" && args.project) {
681
+ if (/[/\\]|\.\./.test(args.project))
682
+ throw new Error(`invalid project id '${args.project}'`);
683
+ const known = await findProject(args.project);
684
+ if (known)
685
+ return { project: known };
686
+ if (!isValidProjectId(args.project))
687
+ return { project: null };
688
+ return { project: args.project };
689
+ }
690
+ const cwd = typeof args.cwd === "string" && args.cwd ? args.cwd : process.cwd();
691
+ return { project: await folderProject(cwd) };
692
+ }
693
+ async function readResource(uri, ports) {
694
+ let value;
695
+ const resource = /^ocean:\/\/(.*)$/.exec(uri)?.[1];
696
+ if (resource === "status") {
697
+ const report = await workspaceReport(ports.wrapperManager);
698
+ value = {
699
+ name: "Ocean",
700
+ version: await serverVersion(),
701
+ workspace: oceanPath(),
702
+ healthy: !hasFailures(report.findings),
703
+ findings: report.findings,
704
+ };
705
+ }
706
+ else if (resource === "profiles")
707
+ value = { profiles: await profiles() };
708
+ else if (resource === "tasks")
709
+ value = await listTasksForFolder({ cwd: process.cwd() });
710
+ else if (resource === "handoffs")
711
+ value = { handoffs: await listHandoffs(ports.sessionStoreFactory) };
712
+ else
713
+ throw new Error(`Unknown Ocean resource: ${uri}`);
714
+ return {
715
+ uri,
716
+ mimeType: "application/json",
717
+ text: JSON.stringify(value, null, 2),
718
+ };
719
+ }
720
+ function requiredArgument(args, key) {
721
+ const value = args[key];
722
+ if (typeof value !== "string" || !value)
723
+ throw new Error(`${key} is required`);
724
+ return value;
725
+ }
726
+ async function getPrompt(name, args) {
727
+ if (name === "ocean_review_workspace")
728
+ return {
729
+ description: "Ocean workspace review",
730
+ messages: [
731
+ {
732
+ role: "user",
733
+ content: {
734
+ type: "text",
735
+ text: "Run ocean_doctor and summarize only actionable findings. Do not change files.",
736
+ },
737
+ },
738
+ ],
739
+ };
740
+ if (name === "ocean_review_task") {
741
+ const task = requiredArgument(args, "task");
742
+ return {
743
+ description: `Review ${task}`,
744
+ messages: [
745
+ {
746
+ role: "user",
747
+ content: {
748
+ type: "text",
749
+ text: `Read the Ocean task ${task}, report PROVEN, NOT PROVEN, or BLOCKED, and propose exactly one next verified action.`,
750
+ },
751
+ },
752
+ ],
753
+ };
754
+ }
755
+ throw new Error(`Unknown Ocean prompt: ${name}`);
756
+ }
757
+ export async function handleOceanMcpRequest(request, ports) {
758
+ if (request.method === "notifications/initialized")
759
+ return null;
760
+ if (request.method === "initialize") {
761
+ const info = request.params?.clientInfo;
762
+ if (typeof info?.name === "string" && /^[\w .@/-]{1,64}$/.test(info.name))
763
+ clientName = info.name;
764
+ return {
765
+ jsonrpc: "2.0",
766
+ id: request.id,
767
+ result: {
768
+ protocolVersion: "2025-06-18",
769
+ capabilities: { tools: {}, resources: {}, prompts: {} },
770
+ serverInfo: { name: "ocean", version: await serverVersion() },
771
+ instructions: MEMORY_INSTRUCTIONS,
772
+ },
773
+ };
774
+ }
775
+ if (request.method === "tools/list")
776
+ return { jsonrpc: "2.0", id: request.id, result: { tools: tools.list() } };
777
+ if (request.method === "resources/list")
778
+ return { jsonrpc: "2.0", id: request.id, result: { resources } };
779
+ if (request.method === "resources/read") {
780
+ try {
781
+ const contents = [
782
+ await readResource(String(request.params?.uri ?? ""), ports),
783
+ ];
784
+ await recordMcpCall(clientName, "resources/read", true);
785
+ return { jsonrpc: "2.0", id: request.id, result: { contents } };
786
+ }
787
+ catch (error) {
788
+ await recordMcpCall(clientName, "resources/read", false);
789
+ return {
790
+ jsonrpc: "2.0",
791
+ id: request.id,
792
+ error: {
793
+ code: -32000,
794
+ message: error instanceof Error ? error.message : String(error),
795
+ },
796
+ };
797
+ }
798
+ }
799
+ if (request.method === "prompts/list")
800
+ return { jsonrpc: "2.0", id: request.id, result: { prompts } };
801
+ if (request.method === "prompts/get") {
802
+ try {
803
+ const result = await getPrompt(String(request.params?.name ?? ""), (request.params?.arguments ?? {}));
804
+ await recordMcpCall(clientName, "prompts/get", true);
805
+ return { jsonrpc: "2.0", id: request.id, result };
806
+ }
807
+ catch (error) {
808
+ await recordMcpCall(clientName, "prompts/get", false);
809
+ return {
810
+ jsonrpc: "2.0",
811
+ id: request.id,
812
+ error: {
813
+ code: -32000,
814
+ message: error instanceof Error ? error.message : String(error),
815
+ },
816
+ };
817
+ }
818
+ }
819
+ if (request.method !== "tools/call")
820
+ return {
821
+ jsonrpc: "2.0",
822
+ id: request.id,
823
+ error: {
824
+ code: -32601,
825
+ message: `Unsupported MCP method: ${request.method ?? ""}`,
826
+ },
827
+ };
828
+ const name = request.params?.name;
829
+ if (typeof name !== "string" || !tools.has(name)) {
830
+ // The requested name is caller text, so it is not what gets logged.
831
+ await recordMcpCall(clientName, "unknown", false);
832
+ return {
833
+ jsonrpc: "2.0",
834
+ id: request.id,
835
+ error: { code: -32602, message: "Unknown MCP tool" },
836
+ };
837
+ }
838
+ try {
839
+ const result = textResult(await callTool(name, (request.params?.arguments ?? {}), ports));
840
+ await recordMcpCall(clientName, name, true);
841
+ return { jsonrpc: "2.0", id: request.id, result };
842
+ }
843
+ catch (error) {
844
+ await recordMcpCall(clientName, name, false);
845
+ return {
846
+ jsonrpc: "2.0",
847
+ id: request.id,
848
+ error: {
849
+ code: -32000,
850
+ message: error instanceof Error ? error.message : String(error),
851
+ ...(error instanceof MemoryContractError
852
+ ? { data: { code: error.code, ...error.details } }
853
+ : {}),
854
+ },
855
+ };
856
+ }
857
+ }
858
+ export async function runOceanMcpServer(ports) {
859
+ let buffer = "";
860
+ process.stdin.setEncoding("utf8");
861
+ process.stdin.on("data", async (chunk) => {
862
+ buffer += chunk;
863
+ while (true) {
864
+ const newline = buffer.indexOf("\n");
865
+ if (newline < 0)
866
+ return;
867
+ const line = buffer.slice(0, newline).trim();
868
+ buffer = buffer.slice(newline + 1);
869
+ if (!line)
870
+ continue;
871
+ const request = JSON.parse(line);
872
+ const response = await handleOceanMcpRequest(request, ports);
873
+ if (!response)
874
+ continue;
875
+ process.stdout.write(`${JSON.stringify(response)}\n`);
876
+ }
877
+ });
878
+ await new Promise((resolve) => process.stdin.once("end", resolve));
879
+ }
880
+ //# sourceMappingURL=ocean-server.js.map