@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,380 @@
1
+ // The only file in Ocean permitted to know a browser engine. Everything above the
2
+ // BrowserProvider boundary (browser-provider.ts) stays engine-neutral.
3
+ //
4
+ // Depends on `playwright-core`, not `playwright`: playwright-core ships the driver and
5
+ // API without downloading a Chromium binary on install, so `pnpm install` stays light and
6
+ // a missing browser binary is a reportable `detect()` failure rather than an install-time
7
+ // requirement.
8
+ //
9
+ // A session survives between CLI invocations: the browser is launched as its own
10
+ // detached process with a remote-debugging port, and later invocations reconnect over
11
+ // CDP by port. Without this, a multi-step browser task would mean a new browser per
12
+ // step, which is not a session.
13
+ import { spawn } from "node:child_process";
14
+ import { existsSync } from "node:fs";
15
+ import { mkdir } from "node:fs/promises";
16
+ import net from "node:net";
17
+ import path from "node:path";
18
+ import { oceanEnv } from "@ocean/core/shared/paths.js";
19
+ // A download's suggested filename comes from the remote page/server (e.g. a
20
+ // Content-Disposition header) and must never be trusted as a path: it can contain `../`,
21
+ // an absolute path, or backslash separators aimed at escaping destinationDir.
22
+ export function safeDownloadFilename(suggested) {
23
+ const normalized = suggested.replace(/\\/g, "/");
24
+ const base = path.posix.basename(normalized).trim();
25
+ return !base || base === "." || base === ".." ? "download" : base;
26
+ }
27
+ export function resolveDownloadPath(destinationDir, suggestedFilename) {
28
+ const resolvedDir = path.resolve(destinationDir);
29
+ const candidate = path.resolve(resolvedDir, safeDownloadFilename(suggestedFilename));
30
+ if (candidate !== resolvedDir &&
31
+ !candidate.startsWith(resolvedDir + path.sep)) {
32
+ throw new Error(`Download path escapes destination directory: ${suggestedFilename}`);
33
+ }
34
+ return candidate;
35
+ }
36
+ async function freePort() {
37
+ return new Promise((resolve, reject) => {
38
+ const server = net.createServer();
39
+ server.once("error", reject);
40
+ server.listen(0, "127.0.0.1", () => {
41
+ const address = server.address();
42
+ const port = typeof address === "object" && address ? address.port : 0;
43
+ server.close(() => resolve(port));
44
+ });
45
+ });
46
+ }
47
+ async function fetchWebSocketDebuggerUrl(port) {
48
+ try {
49
+ const response = await fetch(`http://127.0.0.1:${port}/json/version`, {
50
+ signal: AbortSignal.timeout(1000),
51
+ });
52
+ const body = (await response.json());
53
+ return body.webSocketDebuggerUrl ?? null;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ function _nthIndex(value, needle, occurrence) {
60
+ let from = 0;
61
+ for (let current = 1; current <= occurrence; current += 1) {
62
+ const index = value.indexOf(needle, from);
63
+ if (index < 0)
64
+ return -1;
65
+ if (current === occurrence)
66
+ return index;
67
+ from = index + needle.length;
68
+ }
69
+ return -1;
70
+ }
71
+ function resolveChromiumExecutable() {
72
+ const candidates = [
73
+ oceanEnv("BROWSER_EXECUTABLE"),
74
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
75
+ "/usr/bin/google-chrome",
76
+ "/usr/bin/chromium-browser",
77
+ "/usr/bin/chromium",
78
+ ].filter((candidate) => Boolean(candidate));
79
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
80
+ }
81
+ export class PlaywrightBrowserProvider {
82
+ async detect() {
83
+ const executable = resolveChromiumExecutable();
84
+ if (!executable)
85
+ return {
86
+ ok: false,
87
+ detail: "no Chromium-family browser binary found; set OCEAN_BROWSER_EXECUTABLE",
88
+ };
89
+ return { ok: true, detail: executable };
90
+ }
91
+ async launch(profileDir, port) {
92
+ const executable = resolveChromiumExecutable();
93
+ if (!executable)
94
+ throw new Error("Browser capability denied: no browser binary detected");
95
+ const resolvedPort = port ?? (await freePort());
96
+ await mkdir(profileDir, { recursive: true });
97
+ const child = spawn(executable, [
98
+ `--remote-debugging-port=${resolvedPort}`,
99
+ `--remote-debugging-address=127.0.0.1`,
100
+ `--user-data-dir=${profileDir}`,
101
+ "--no-first-run",
102
+ "--no-default-browser-check",
103
+ "--headless=new",
104
+ "--disable-gpu",
105
+ "about:blank",
106
+ ], { stdio: "ignore", detached: true });
107
+ child.unref();
108
+ let endpoint = null;
109
+ for (let attempt = 0; attempt < 100 && !endpoint; attempt += 1) {
110
+ endpoint = await fetchWebSocketDebuggerUrl(resolvedPort);
111
+ if (!endpoint)
112
+ await new Promise((resolve) => setTimeout(resolve, 200));
113
+ }
114
+ if (!endpoint) {
115
+ child.kill();
116
+ throw new Error("Browser did not expose a debugging endpoint within the timeout");
117
+ }
118
+ return { pid: child.pid ?? -1, port: resolvedPort, endpoint, profileDir };
119
+ }
120
+ async connect(launch) {
121
+ const { chromium } = await import("playwright-core");
122
+ const browser = await chromium.connectOverCDP(`http://127.0.0.1:${launch.port}`);
123
+ const context = browser.contexts()[0] ?? (await browser.newContext());
124
+ const page = context.pages()[0] ?? (await context.newPage());
125
+ return new PlaywrightHandle(browser, page);
126
+ }
127
+ async close(launch) {
128
+ if (launch.pid > 0) {
129
+ try {
130
+ process.kill(launch.pid, "SIGTERM");
131
+ }
132
+ catch {
133
+ /* already exited */
134
+ }
135
+ }
136
+ }
137
+ }
138
+ class PlaywrightHandle {
139
+ browser;
140
+ page;
141
+ constructor(browser, page) {
142
+ this.browser = browser;
143
+ this.page = page;
144
+ }
145
+ async state() {
146
+ return { url: this.page.url(), title: await this.page.title() };
147
+ }
148
+ async navigate(url, timeoutMs = 30_000) {
149
+ await this.page.goto(url, {
150
+ timeout: timeoutMs,
151
+ waitUntil: "domcontentloaded",
152
+ });
153
+ return this.state();
154
+ }
155
+ async readText() {
156
+ const text = ((await this.page.innerText("body")) ||
157
+ (await this.page.locator("body").textContent()) ||
158
+ "").slice(0, 20_000);
159
+ return { ...(await this.state()), text };
160
+ }
161
+ async observe() {
162
+ const elements = (await this.page.$$eval("a,button,input,select,textarea,[role=button]", (nodes) => nodes.slice(0, 120).map((node) => {
163
+ const element = node;
164
+ return {
165
+ tag: element.tagName.toLowerCase(),
166
+ type: element.type ?? null,
167
+ name: element.name || null,
168
+ id: element.id || null,
169
+ text: (element.innerText || element.value || "")
170
+ .trim()
171
+ .slice(0, 80),
172
+ visible: Boolean(element.offsetWidth || element.offsetHeight),
173
+ };
174
+ })));
175
+ return { ...(await this.state()), elements };
176
+ }
177
+ async extract(selector, attribute) {
178
+ const values = attribute
179
+ ? await this.page.$$eval(selector, (nodes, attr) => nodes.map((node) => node.getAttribute(attr)), attribute)
180
+ : await this.page.$$eval(selector, (nodes) => nodes.map((node) => node.innerText?.trim() ||
181
+ node.value?.trim() ||
182
+ ""));
183
+ return { selector, count: values.length, values: values.slice(0, 200) };
184
+ }
185
+ async click(selector, timeoutMs = 15_000) {
186
+ const urlBefore = this.page.url();
187
+ await this.page.click(selector, { timeout: timeoutMs });
188
+ await this.page.waitForTimeout(300);
189
+ return { ...(await this.state()), urlBefore };
190
+ }
191
+ async type(selector, text, clear = true) {
192
+ let locator = this.editorLocator(selector);
193
+ await locator.waitFor({ state: "visible", timeout: 15_000 });
194
+ const nestedEditor = locator.locator("textarea, input").first();
195
+ if ((await nestedEditor.count()) > 0)
196
+ locator = nestedEditor;
197
+ const kind = await locator.evaluate((node) => ({
198
+ tag: node.tagName.toLowerCase(),
199
+ contentEditable: node instanceof HTMLElement && node.isContentEditable,
200
+ value: "value" in node ? String(node.value) : null,
201
+ }));
202
+ if (kind.contentEditable ||
203
+ (kind.tag !== "input" && kind.tag !== "textarea")) {
204
+ await locator.click();
205
+ if (clear) {
206
+ await this.page.keyboard.press("ControlOrMeta+A");
207
+ await this.page.keyboard.press("Backspace");
208
+ }
209
+ await this.page.keyboard.insertText(text);
210
+ }
211
+ else {
212
+ if (clear)
213
+ await locator.fill("");
214
+ await locator.pressSequentially(text, { timeout: 15_000 });
215
+ }
216
+ const value = await locator.evaluate((node) => {
217
+ if ("value" in node)
218
+ return String(node.value);
219
+ return (node.textContent || node.innerText || "").trim();
220
+ });
221
+ return { selector, typed: text.length, value };
222
+ }
223
+ async replaceText(selector, oldText, newText, occurrence = 1) {
224
+ if (!oldText)
225
+ throw new Error("old-text must not be empty");
226
+ if (!Number.isInteger(occurrence) || occurrence < 1)
227
+ throw new Error("occurrence must be a positive integer");
228
+ const locator = this.editorLocator(selector);
229
+ await locator.waitFor({ state: "visible", timeout: 15_000 });
230
+ const editorKind = await locator.evaluate((node) => ({
231
+ monaco: node.classList.contains("monaco-editor") ||
232
+ Boolean(node.querySelector(".monaco-editor")),
233
+ codeMirror: node.classList.contains("CodeMirror") ||
234
+ Boolean(node.querySelector(".CodeMirror")),
235
+ }));
236
+ if (editorKind.monaco) {
237
+ const input = locator.locator("textarea.inputarea").first();
238
+ await input.click();
239
+ await this.page.keyboard.press("ControlOrMeta+f");
240
+ await this.page.keyboard.insertText(oldText);
241
+ for (let index = 1; index < occurrence; index += 1)
242
+ await this.page.keyboard.press("Enter");
243
+ await this.page.keyboard.press("Escape");
244
+ await this.page.keyboard.insertText(newText);
245
+ const value = await locator.locator(".view-lines").innerText();
246
+ if (value.includes(oldText))
247
+ throw new Error(`Text occurrence was not replaced: ${occurrence}`);
248
+ return { selector, oldText, newText, occurrence, value };
249
+ }
250
+ const result = await locator.evaluate((node, input) => {
251
+ const root = node;
252
+ const nthIndexInPage = (value, needle, occurrence) => {
253
+ let from = 0;
254
+ for (let current = 1; current <= occurrence; current += 1) {
255
+ const index = value.indexOf(needle, from);
256
+ if (index < 0)
257
+ return -1;
258
+ if (current === occurrence)
259
+ return index;
260
+ from = index + needle.length;
261
+ }
262
+ return -1;
263
+ };
264
+ const global = window;
265
+ const editors = global.monaco?.editor?.getEditors?.() ?? [];
266
+ const editor = editors.find((candidate) => candidate.getDomNode?.() === root);
267
+ const model = editor?.getModel?.() ??
268
+ global.monaco?.editor
269
+ ?.getModels?.()
270
+ .find((candidate) => candidate.getValue().includes(input.oldText));
271
+ if (model) {
272
+ const matches = model.findMatches(input.oldText);
273
+ const match = matches[input.occurrence - 1];
274
+ if (!match)
275
+ throw new Error(`Text occurrence not found: ${input.occurrence}`);
276
+ model.pushEditOperations([], [{ range: match.range, text: input.newText }], null);
277
+ return { value: model.getValue(), occurrence: input.occurrence };
278
+ }
279
+ const target = root.matches("textarea,input")
280
+ ? root
281
+ : root.querySelector("textarea,input");
282
+ const value = target && "value" in target
283
+ ? String(target.value)
284
+ : (root.textContent ?? "");
285
+ const index = nthIndexInPage(value, input.oldText, input.occurrence);
286
+ if (index < 0)
287
+ throw new Error(`Text occurrence not found: ${input.occurrence}`);
288
+ const next = value.slice(0, index) +
289
+ input.newText +
290
+ value.slice(index + input.oldText.length);
291
+ if (target) {
292
+ target.value = next;
293
+ target.dispatchEvent(new Event("input", { bubbles: true }));
294
+ }
295
+ else
296
+ root.textContent = next;
297
+ return { value: next, occurrence: input.occurrence };
298
+ }, { oldText, newText, occurrence });
299
+ return {
300
+ selector,
301
+ oldText,
302
+ newText,
303
+ occurrence: result.occurrence,
304
+ value: result.value,
305
+ };
306
+ }
307
+ editorLocator(selector) {
308
+ const separator = " >>> ";
309
+ const frameIndex = selector.indexOf(separator);
310
+ if (frameIndex >= 0) {
311
+ const frameSelector = selector.slice(0, frameIndex);
312
+ const editorSelector = selector.slice(frameIndex + separator.length);
313
+ if (!frameSelector || !editorSelector)
314
+ throw new Error("Editor frame selector must use 'frame >>> editor'.");
315
+ return this.page
316
+ .frameLocator(frameSelector)
317
+ .locator(editorSelector)
318
+ .first();
319
+ }
320
+ return this.page.locator(selector).first();
321
+ }
322
+ async select(selector, value) {
323
+ await this.page.selectOption(selector, value, { timeout: 15_000 });
324
+ return {
325
+ selector,
326
+ value: await this.page.inputValue(selector, { timeout: 5_000 }),
327
+ };
328
+ }
329
+ async scroll(deltaY = 600) {
330
+ await this.page.mouse.wheel(0, deltaY);
331
+ await this.page.waitForTimeout(150);
332
+ const scrollY = await this.page.evaluate(() => Math.round(window.scrollY));
333
+ return { scrollY };
334
+ }
335
+ async wait(options) {
336
+ const timeout = options.timeoutMs ?? 15_000;
337
+ if (options.selector)
338
+ await this.page.waitForSelector(options.selector, { timeout });
339
+ if (options.urlContains)
340
+ await this.page.waitForURL(`**${options.urlContains}**`, { timeout });
341
+ return this.state();
342
+ }
343
+ async upload(selector, paths) {
344
+ await this.page.setInputFiles(selector, paths, { timeout: 15_000 });
345
+ const attached = await this.page.$eval(selector, (node) => Array.from(node.files ?? []).map((file) => file.name));
346
+ return { selector, attached };
347
+ }
348
+ async download(selector, destinationDir, timeoutMs = 60_000) {
349
+ const [download] = await Promise.all([
350
+ this.page.waitForEvent("download", { timeout: timeoutMs }),
351
+ this.page.click(selector, { timeout: timeoutMs }),
352
+ ]);
353
+ await mkdir(destinationDir, { recursive: true });
354
+ const targetPath = resolveDownloadPath(destinationDir, download.suggestedFilename());
355
+ await download.saveAs(targetPath);
356
+ const { statSync } = await import("node:fs");
357
+ const size = existsSync(targetPath) ? statSync(targetPath).size : 0;
358
+ return { selector, path: targetPath, size };
359
+ }
360
+ async submit(selector, timeoutMs = 20_000) {
361
+ const urlBefore = this.page.url();
362
+ const bodyBefore = (await this.page.innerText("body")).slice(0, 20_000);
363
+ await this.page.click(selector, { timeout: timeoutMs });
364
+ try {
365
+ await this.page.waitForLoadState("domcontentloaded", {
366
+ timeout: timeoutMs,
367
+ });
368
+ }
369
+ catch {
370
+ /* best-effort */
371
+ }
372
+ await this.page.waitForTimeout(400);
373
+ const bodyAfter = (await this.page.innerText("body")).slice(0, 20_000);
374
+ return { ...(await this.state()), urlBefore, bodyBefore, bodyAfter };
375
+ }
376
+ async release() {
377
+ await this.browser.close();
378
+ }
379
+ }
380
+ //# sourceMappingURL=playwright-browser-provider.js.map
@@ -0,0 +1,50 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { providerCapabilitySchema, } from "@ocean/core/domain/providers/provider-capability.js";
4
+ const execFileAsync = promisify(execFile);
5
+ const commands = {
6
+ claude: "claude",
7
+ codex: "codex",
8
+ gemini: "gemini",
9
+ antigravity: "agy",
10
+ hermes: "hermes",
11
+ kilo: "kilo",
12
+ kimi: "kimi",
13
+ };
14
+ export async function discoverProviderCapabilities(provider) {
15
+ const command = commands[provider];
16
+ let installed = false;
17
+ try {
18
+ await execFileAsync("which", [command]);
19
+ installed = true;
20
+ }
21
+ catch {
22
+ /* represented as unavailable */
23
+ }
24
+ const contextTransport = provider === "hermes"
25
+ ? "environment-hint"
26
+ : provider === "claude"
27
+ ? "print-system-prompt-only"
28
+ : "provider-prompt-option-headless-only";
29
+ return providerCapabilitySchema.parse({
30
+ provider,
31
+ command,
32
+ installed,
33
+ headless: true,
34
+ resume: provider === "claude",
35
+ streaming: true,
36
+ structuredOutput: true,
37
+ authentication: "cli-managed",
38
+ interactive: true,
39
+ interactiveContext: provider === "hermes" ? "partial" : "not-proven",
40
+ contextTransport,
41
+ inputCapture: "bounded-terminal",
42
+ });
43
+ }
44
+ export function assertProviderCapability(capability, requirement) {
45
+ if (!capability.installed)
46
+ throw new Error(`Provider CLI not installed: ${capability.command}`);
47
+ if (!capability[requirement])
48
+ throw new Error(`Provider ${capability.provider} does not support ${requirement}`);
49
+ }
50
+ //# sourceMappingURL=provider-capabilities.js.map
@@ -0,0 +1,184 @@
1
+ import { spawn } from "node:child_process";
2
+ import { evaluateGuard, } from "@ocean/loop/operations/write-guard.js";
3
+ import { redactRuntimeText } from "../observability/runtime-logger.js";
4
+ import { resolveOriginalExecutable } from "./provider-registry.js";
5
+ export const PROVIDER_HEADLESS = {
6
+ claude: {
7
+ args: (prompt) => ["-p", prompt, "--output-format", "json"],
8
+ parse: "json",
9
+ },
10
+ codex: {
11
+ args: (prompt) => ["exec", prompt, "--skip-git-repo-check"],
12
+ parse: "text",
13
+ },
14
+ gemini: { args: (prompt) => ["-p", prompt, "--skip-trust"], parse: "text" },
15
+ };
16
+ export function providerHeadlessSupport(provider) {
17
+ const spec = PROVIDER_HEADLESS[provider];
18
+ if (!spec)
19
+ return {
20
+ supported: false,
21
+ reason: `provider '${provider}' has no verified headless contract in Ocean — it stays gated as unsupported`,
22
+ };
23
+ return { supported: true, spec };
24
+ }
25
+ const MAX_OUTPUT_CHARS = 4_000;
26
+ function sessionIdOf(event) {
27
+ for (const key of ["session_id", "sessionId"]) {
28
+ const value = event[key];
29
+ if (typeof value === "string" && value.length > 0)
30
+ return value;
31
+ }
32
+ const nested = event.session;
33
+ if (nested && typeof nested === "object") {
34
+ const value = nested.id;
35
+ if (typeof value === "string" && value.length > 0)
36
+ return value;
37
+ }
38
+ return null;
39
+ }
40
+ // Tolerant, deterministic stream parsing: a malformed line is counted, never thrown, and an
41
+ // unterminated trailing line is reported as partial rather than silently dropped.
42
+ export function parseProviderStream(text, mode = "json") {
43
+ if (mode === "text")
44
+ return {
45
+ events: [],
46
+ malformedLines: 0,
47
+ partial: false,
48
+ providerSessionId: null,
49
+ };
50
+ const trimmed = text.trim();
51
+ if (trimmed.length === 0)
52
+ return {
53
+ events: [],
54
+ malformedLines: 0,
55
+ partial: false,
56
+ providerSessionId: null,
57
+ };
58
+ try {
59
+ const single = JSON.parse(trimmed);
60
+ return {
61
+ events: [single],
62
+ malformedLines: 0,
63
+ partial: false,
64
+ providerSessionId: sessionIdOf(single),
65
+ };
66
+ }
67
+ catch {
68
+ // Fall through to line-by-line parsing.
69
+ }
70
+ const endsCleanly = text.endsWith("\n");
71
+ const lines = text.split("\n");
72
+ const events = [];
73
+ let malformedLines = 0;
74
+ let partial = false;
75
+ let providerSessionId = null;
76
+ for (let index = 0; index < lines.length; index += 1) {
77
+ const line = lines[index].trim();
78
+ if (!line)
79
+ continue;
80
+ const isLast = index === lines.length - 1;
81
+ try {
82
+ const event = JSON.parse(line);
83
+ events.push(event);
84
+ providerSessionId = providerSessionId ?? sessionIdOf(event);
85
+ }
86
+ catch {
87
+ if (isLast && !endsCleanly)
88
+ partial = true;
89
+ else
90
+ malformedLines += 1;
91
+ }
92
+ }
93
+ return { events, malformedLines, partial, providerSessionId };
94
+ }
95
+ function result(request, status, reason, overrides = {}) {
96
+ return {
97
+ provider: request.provider,
98
+ status,
99
+ exitCode: null,
100
+ durationMs: 0,
101
+ providerSessionId: null,
102
+ oceanSessionId: request.oceanSessionId,
103
+ parentSessionId: request.parentSessionId ?? null,
104
+ malformedLines: 0,
105
+ partial: false,
106
+ output: "",
107
+ reason,
108
+ guard: null,
109
+ ...overrides,
110
+ };
111
+ }
112
+ // Every external provider action goes through here, and the guard runs before the process is
113
+ // ever spawned. A denial returns a structured result; it never reaches spawn().
114
+ export async function invokeProviderHeadless(request) {
115
+ const support = providerHeadlessSupport(request.provider);
116
+ if (!support.supported)
117
+ return result(request, "unsupported", support.reason);
118
+ const decision = evaluateGuard(request.guard);
119
+ if (!decision.allowed) {
120
+ return result(request, "denied", `provider invocation denied by the Ocean write guard: ${decision.reason}`, { guard: decision });
121
+ }
122
+ let executable;
123
+ try {
124
+ executable =
125
+ request.executable ?? resolveOriginalExecutable(request.provider);
126
+ }
127
+ catch (error) {
128
+ return result(request, "unavailable", `provider '${request.provider}' is not available on this machine: ${error instanceof Error ? error.message : String(error)}`, { guard: decision });
129
+ }
130
+ const started = Date.now();
131
+ const timeoutMs = request.timeoutMs ?? 120_000;
132
+ return await new Promise((resolve) => {
133
+ const child = spawn(executable, support.spec.args(request.prompt), {
134
+ cwd: request.cwd,
135
+ // stdin is closed: a headless invocation must never depend on an interactive prompt.
136
+ stdio: ["ignore", "pipe", "pipe"],
137
+ env: process.env,
138
+ });
139
+ let stdout = "";
140
+ let stderr = "";
141
+ let settled = false;
142
+ const timer = setTimeout(() => {
143
+ if (settled)
144
+ return;
145
+ settled = true;
146
+ child.kill("SIGKILL");
147
+ resolve(result(request, "timeout", `provider '${request.provider}' exceeded the ${timeoutMs}ms budget and was terminated`, {
148
+ durationMs: Date.now() - started,
149
+ guard: decision,
150
+ output: redactRuntimeText(stdout).slice(0, MAX_OUTPUT_CHARS),
151
+ }));
152
+ }, timeoutMs);
153
+ child.stdout.on("data", (chunk) => {
154
+ stdout += String(chunk);
155
+ });
156
+ child.stderr.on("data", (chunk) => {
157
+ stderr += String(chunk);
158
+ });
159
+ const finish = (exitCode, failureReason) => {
160
+ if (settled)
161
+ return;
162
+ settled = true;
163
+ clearTimeout(timer);
164
+ const parsed = parseProviderStream(stdout, support.spec.parse);
165
+ const ok = exitCode === 0 && !failureReason;
166
+ resolve(result(request, ok ? "completed" : "failed", failureReason ??
167
+ (ok
168
+ ? `provider '${request.provider}' completed`
169
+ : `provider '${request.provider}' exited with code ${exitCode}`), {
170
+ exitCode,
171
+ durationMs: Date.now() - started,
172
+ providerSessionId: parsed.providerSessionId,
173
+ malformedLines: parsed.malformedLines,
174
+ partial: parsed.partial,
175
+ // Output is redacted and clipped: provider stdout/stderr never lands in a result raw.
176
+ output: redactRuntimeText(stdout || stderr).slice(0, MAX_OUTPUT_CHARS),
177
+ guard: decision,
178
+ }));
179
+ };
180
+ child.on("error", (error) => finish(null, `provider '${request.provider}' could not be executed: ${error.message}`));
181
+ child.on("close", (code) => finish(code));
182
+ });
183
+ }
184
+ //# sourceMappingURL=provider-invocation.js.map
@@ -0,0 +1,77 @@
1
+ import { accessSync, constants, existsSync, readFileSync } from "node:fs";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { registryFile } from "@ocean/core/shared/fs-utils.js";
5
+ import { oceanEnv, oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
6
+ const builtInProviders = [
7
+ { id: "claude", command: "claude", interactive: true, headless: true },
8
+ { id: "codex", command: "codex", interactive: true, headless: true },
9
+ { id: "gemini", command: "gemini", interactive: true, headless: true },
10
+ { id: "antigravity", command: "agy", interactive: true, headless: true },
11
+ { id: "hermes", command: "hermes", interactive: true, headless: true },
12
+ ];
13
+ const providerRegistryPath = () => registryFile("providers.json");
14
+ export function builtInProviderRecords() {
15
+ return builtInProviders.map((provider) => ({ ...provider }));
16
+ }
17
+ export function loadProviderRegistry() {
18
+ const file = providerRegistryPath();
19
+ if (!existsSync(file))
20
+ return builtInProviderRecords();
21
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
22
+ const custom = Array.isArray(parsed.providers) ? parsed.providers : [];
23
+ const merged = new Map();
24
+ for (const provider of [...builtInProviders, ...custom])
25
+ merged.set(provider.id, provider);
26
+ return [...merged.values()];
27
+ }
28
+ export async function saveProviderRegistry(providers) {
29
+ await mkdir(path.dirname(providerRegistryPath()), { recursive: true });
30
+ await writeFile(providerRegistryPath(), `${JSON.stringify({ version: 1, providers }, null, 2)}\n`);
31
+ }
32
+ export function findProvider(value, providers = loadProviderRegistry()) {
33
+ const provider = providers.find((candidate) => candidate.id === value || candidate.command === value);
34
+ if (!provider)
35
+ throw new Error(`Provider is not registered: ${value}`);
36
+ return provider;
37
+ }
38
+ export function resolveOriginalExecutable(command, env = process.env) {
39
+ const shimRoot = path.resolve(oceanEnv("SHIM_DIR", env) ?? oceanPath(SYSTEM_DIR, "runtime", "shims"));
40
+ const pathEntries = (env.PATH ?? "").split(path.delimiter).filter(Boolean);
41
+ const names = process.platform === "win32"
42
+ ? [command, `${command}.exe`, `${command}.cmd`, `${command}.bat`]
43
+ : [command];
44
+ for (const directory of pathEntries) {
45
+ if (path.resolve(directory) === shimRoot || isOceanShimDirectory(directory))
46
+ continue;
47
+ for (const name of names) {
48
+ const candidate = path.join(directory, name);
49
+ try {
50
+ accessSync(candidate, constants.X_OK);
51
+ return candidate;
52
+ }
53
+ catch {
54
+ // Continue through PATH until the first executable outside Ocean's shim directory.
55
+ }
56
+ }
57
+ }
58
+ throw new Error(`Provider executable not found outside Ocean shims: ${command}`);
59
+ }
60
+ export function validateExplicitExecutable(commandPath) {
61
+ if (!path.isAbsolute(commandPath))
62
+ throw new Error(`Provider executable must be an absolute path: ${commandPath}`);
63
+ const resolved = path.resolve(commandPath);
64
+ try {
65
+ accessSync(resolved, constants.X_OK);
66
+ }
67
+ catch {
68
+ throw new Error(`Provider executable is missing or not executable: ${resolved}`);
69
+ }
70
+ return resolved;
71
+ }
72
+ function isOceanShimDirectory(directory) {
73
+ const resolved = path.resolve(directory);
74
+ return (path.basename(resolved) === "shims" &&
75
+ path.basename(path.dirname(resolved)) === "runtime");
76
+ }
77
+ //# sourceMappingURL=provider-registry.js.map